Cron jobs (Gateway scheduler)
Cron vs Heartbeat? See Cron vs Heartbeat for guidance on when to use each.Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at the right time, and can optionally deliver output back to a chat. If you want “run this every morning” or “poke the agent in 20 minutes”, cron is the mechanism.
TL;DR
- Cron runs inside the Gateway (not inside the model).
- Jobs persist under
~/.openclaw/cron/so restarts don’t lose schedules. - Two execution styles:
- Main session: enqueue a system event, then run on the next heartbeat.
- Isolated: run a dedicated agent turn in
cron:<jobId>, optionally deliver output.
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
Quick start (actionable)
Create a one-shot reminder, verify it exists, and run it immediately:Tool-call equivalents (Gateway cron tool)
For the canonical JSON shapes and examples, see JSON schema for tool calls.Where cron jobs are stored
Cron jobs are persisted on the Gateway host at~/.openclaw/cron/jobs.json by default.
The Gateway loads the file into memory and writes it back on changes, so manual edits
are only safe when the Gateway is stopped. Prefer openclaw cron add/edit or the cron
tool call API for changes.
Beginner-friendly overview
Think of a cron job as: when to run + what to do.-
Choose a schedule
- One-shot reminder →
schedule.kind = "at"(CLI:--at) - Repeating job →
schedule.kind = "every"orschedule.kind = "cron" - If your ISO timestamp omits a timezone, it is treated as UTC.
- One-shot reminder →
-
Choose where it runs
sessionTarget: "main"→ run during the next heartbeat with main context.sessionTarget: "isolated"→ run a dedicated agent turn incron:<jobId>.
-
Choose the payload
- Main session →
payload.kind = "systemEvent" - Isolated session →
payload.kind = "agentTurn"
- Main session →
deleteAfterRun: true removes successful one-shot jobs from the store.
Concepts
Jobs
A cron job is a stored record with:- a schedule (when it should run),
- a payload (what it should do),
- optional delivery (where output should be sent).
- optional agent binding (
agentId): run the job under a specific agent; if missing or unknown, the gateway falls back to the default agent.
jobId (used by CLI/Gateway APIs).
In agent tool calls, jobId is canonical; legacy id is accepted for compatibility.
Jobs can optionally auto-delete after a successful one-shot run via deleteAfterRun: true.
Schedules
Cron supports three schedule kinds:at: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC.every: fixed interval (ms).cron: 5-field cron expression with optional IANA timezone.
croner. If a timezone is omitted, the Gateway host’s
local timezone is used.
Main vs isolated execution
Main session jobs (system events)
Main jobs enqueue a system event and optionally wake the heartbeat runner. They must usepayload.kind = "systemEvent".
wakeMode: "next-heartbeat"(default): event waits for the next scheduled heartbeat.wakeMode: "now": event triggers an immediate heartbeat run.
Isolated jobs (dedicated cron sessions)
Isolated jobs run a dedicated agent turn in sessioncron:<jobId>.
Key behaviors:
- Prompt is prefixed with
[cron:<jobId> <job name>]for traceability. - Each run starts a fresh session id (no prior conversation carry-over).
- A summary is posted to the main session (prefix
Cron, configurable). wakeMode: "now"triggers an immediate heartbeat after posting the summary.- If
payload.deliver: true, output is delivered to a channel; otherwise it stays internal.
Payload shapes (what runs)
Two payload kinds are supported:systemEvent: main-session only, routed through the heartbeat prompt.agentTurn: isolated-session only, runs a dedicated agent turn.
agentTurn fields:
message: required text prompt.model/thinking: optional overrides (see below).timeoutSeconds: optional timeout override.deliver:trueto send output to a channel target.channel:lastor a specific channel.to: channel-specific target (phone/chat/channel id).bestEffortDeliver: avoid failing the job if delivery fails.
session=isolated):
postToMainPrefix(CLI:--post-prefix): prefix for the system event in main.postToMainMode:summary(default) orfull.postToMainMaxChars: max chars whenpostToMainMode=full(default 8000).
Model and thinking overrides
Isolated jobs (agentTurn) can override the model and thinking level:
model: Provider/model string (e.g.,anthropic/claude-sonnet-4-20250514) or alias (e.g.,opus)thinking: Thinking level (off,minimal,low,medium,high,xhigh; GPT-5.2 + Codex models only)
model on main-session jobs too, but it changes the shared main
session model. We recommend model overrides only for isolated jobs to avoid
unexpected context shifts.
Resolution priority:
- Job payload override (highest)
- Hook-specific defaults (e.g.,
hooks.gmail.model) - Agent config default
Delivery (channel + target)
Isolated jobs can deliver output to a channel. The job payload can specify:channel:whatsapp/telegram/discord/slack/mattermost(plugin) /signal/imessage/lastto: channel-specific recipient target
channel or to is omitted, cron can fall back to the main session’s “last route”
(the last place the agent replied).
Delivery notes:
- If
tois set, cron auto-delivers the agent’s final output even ifdeliveris omitted. - Use
deliver: truewhen you want last-route delivery without an explicitto. - Use
deliver: falseto keep output internal even if atois present.
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g.
channel:<id>,user:<id>) to avoid ambiguity. - Telegram topics should use the
:topic:form (see below).
Telegram delivery targets (topics / forum threads)
Telegram supports forum topics viamessage_thread_id. For cron delivery, you can encode
the topic/thread into the to field:
-1001234567890(chat id only)-1001234567890:topic:123(preferred: explicit topic marker)-1001234567890:123(shorthand: numeric suffix)
telegram:... / telegram:group:... are also accepted:
telegram:group:-1001234567890:topic:123
JSON schema for tool calls
Use these shapes when calling Gatewaycron.* tools directly (agent tool calls or RPC).
CLI flags accept human durations like 20m, but tool calls use epoch milliseconds for
atMs and everyMs (ISO timestamps are accepted for at times).
cron.add params
One-shot, main session job (system event):schedule.kind:at(atMs),every(everyMs), orcron(expr, optionaltz).atMsandeveryMsare epoch milliseconds.sessionTargetmust be"main"or"isolated"and must matchpayload.kind.- Optional fields:
agentId,description,enabled,deleteAfterRun,isolation. wakeModedefaults to"next-heartbeat"when omitted.
cron.update params
jobIdis canonical;idis accepted for compatibility.- Use
agentId: nullin the patch to clear an agent binding.
cron.run and cron.remove params
Storage & history
- Job store:
~/.openclaw/cron/jobs.json(Gateway-managed JSON). - Run history:
~/.openclaw/cron/runs/<jobId>.jsonl(JSONL, auto-pruned). - Override store path:
cron.storein config.
Configuration
cron.enabled: false(config)OPENCLAW_SKIP_CRON=1(env)
CLI quickstart
One-shot reminder (UTC ISO, auto-delete after success):Gateway API surface
cron.list,cron.status,cron.add,cron.update,cron.removecron.run(force or due),cron.runsFor immediate system events without a job, useopenclaw system event.
Troubleshooting
“Nothing runs”
- Check cron is enabled:
cron.enabledandOPENCLAW_SKIP_CRON. - Check the Gateway is running continuously (cron runs inside the Gateway process).
- For
cronschedules: confirm timezone (--tz) vs the host timezone.
Telegram delivers to the wrong place
- For forum topics, use
-100…:topic:<id>so it’s explicit and unambiguous. - If you see
telegram:...prefixes in logs or stored “last route” targets, that’s normal; cron delivery accepts them and still parses topic IDs correctly.