Triadive's existing pieces on scheduled work (Cron Jobs, Heartbeats, and Scheduled Work, Cron vs Heartbeat) cover the when and the why. This piece covers the how: the OpenClaw automation tool's schedule types, payload kinds, and delivery modes, and the combinations that come up most often in practice.
The OpenClaw automation tool is the scheduler. The CLI command is openclaw automations (the older openclaw cron name still works as an alias). Jobs persist in the Gateway's shared SQLite database, fire inside the Gateway process, and produce a run-history record per execution. Everything that follows assumes that scheduler.
The three axes of a job
Every job in OpenClaw has three independent choices: when it fires, what it does when it fires, and where the result goes. The choices compose. Two jobs with the same schedule can have wildly different behavior because they picked different payload kinds or different delivery modes.
The three axes:
1. Schedule — when the job fires. 2. Payload — what runs when the job fires. 3. Delivery — where the result goes (chat channel, webhook URL, or nowhere).
The first choice is schedule.kind. The second is payload.kind. The third is the delivery block. Picking all three is what makes a job.
Schedule types — five kinds
OpenClaw supports five schedule types. The first three cover most jobs. The last two are for event-driven work and stream processing.
at — one-shot at a fixed time
A one-shot job that fires at a specific timestamp and then disappears. Used for reminders, follow-ups, and one-time housekeeping.
openclaw automations create "2027-02-01T16:00:00Z" \
--name "Reminder" \
--session main \
--system-event "Reminder: check the automations draft" \
--wake now \
--delete-after-run
The --delete-after-run flag (or its absence) controls whether the job persists after a successful run. One-shot reminders default to delete-after-run so the schedule list does not accumulate dead jobs. Use --keep-after-run if you want the job to remain in the schedule list even after it fires (useful for audits).
A one-shot without an explicit timezone is treated as UTC. Add --tz America/New_York to interpret an offset-less timestamp in that timezone. The trap: writing 2026-12-31 23:00 and expecting midnight in your local timezone gives you midnight UTC, which is seven hours earlier in New York. Always specify --tz for one-shots that have to land at a wall-clock time.
every — fixed interval
A recurring job that fires every N minutes/hours/days. Used for heartbeats, fast probes, and tight loops.
openclaw automations add \
--name "Queue depth probe" \
--every "*/15 * * * *" \
--command "scripts/check-queue.sh" \
--command-cwd "/srv/app"
The interval can be a duration string (10m, 1h, 1d) or a cron-style expression. The --every flag does not accept --tz; the interval is wall-clock-relative, not timezone-relative.
A common mistake is using --every "5m" when the intent is "every five minutes starting at minute 0." The --every job fires five minutes after the previous run, not at clock-aligned minute boundaries. For aligned cadences, use --cron "/5 *" instead.
cron — calendar-aligned schedule
A recurring job that fires on a 5- or 6-field cron expression. Used for daily briefs, weekly reports, monthly reconciliations — anything that should fire at a specific wall-clock time.
openclaw automations add \
--name "Morning brief" \
--cron "0 7 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize overnight updates." \
--announce \
--channel slack \
--to "channel:C1234567890"
The cron expression without --tz evaluates in the Gateway host timezone. Adding --tz America/Los_Angeles evaluates the expression in that IANA timezone. The two behaviors are different: a Gateway on UTC hardware firing 0 9 * runs at 9 AM UTC, not 9 AM local. For distributed teams, the answer is always --tz.
A subtle gotcha: day-of-month and day-of-week use OR logic in OpenClaw's cron parser (which is standard Vixie cron behavior). The expression 0 9 15 1 fires at 9 AM on the 15th and on every Monday — roughly 5-6 times per month. To require both fields, use cron's + day-of-week modifier (0 9 15 +1), or guard the second condition in the prompt.
on-exit — fire when a watched command exits
An event-driven schedule that fires when a specific watched command exits. Survives turn teardown, supports an optional --on-exit-cwd, and is useful for "when X finishes, do Y" patterns.
The use case is narrow. Most cron-driven workflows do not need it. The pattern shows up in test pipelines ("when the build finishes, summarize failures"), CI runners ("when a long job exits, archive logs"), and supervised processes ("when the daemon exits, restart it"). Each of these is more naturally modeled as a separate tool than as a cron, but the schedule is there when needed.
stream — fire from a supervised command's output
An event-driven schedule that keeps an operator-authored command running under the Gateway and fires when the command emits lines on stdout or stderr. Used for log-watching, build-event ingestion, and any "react to a stream of events" pattern.
openclaw automations add \
--name "Build event stream" \
--stream-command '["node","scripts/build-events.mjs"]' \
--stream-mode match \
--stream-match '^(failed|recovered):' \
--stream-batch-ms 250 \
--session isolated \
--message "Investigate these build events."
Stream schedules are never time-due. They are event-driven. The stream-mode is line (every line fires) or match (only matching lines). The batch closes after stream-batch-ms of quiet (default 250 ms) or at maxBatchBytes (default 16 KB). Failed payloads are not retried because the events may not be idempotent. This schedule is the right one when the upstream produces a stream of small events and you want to react to each batch.
The two stream variants (on-exit and stream) require cron.triggers.enabled: true because they have the same unattended trust class as trigger scripts. The flag is off by default — turn it on only when you actually need event-driven schedules.
Payload kinds — what runs when the schedule fires
Every job has exactly one payload kind. The choice is what determines the runtime cost, the failure mode, and what the result looks like.
systemEvent — inject text into the main session
A short text injected into the main session as a system event. No model call — the message lands in the main session's queue and the agent picks it up on its next turn. Used for reminders, status pings, and "wake up and look at this" patterns.
openclaw automations add \
--name "Calendar check" \
--at "20m" \
--session main \
--system-event "Next heartbeat: check calendar." \
--wake now
The cost is near-zero. The risk is that the system event competes with other work in the main queue — a reminder can be drowned out by an active conversation. Use --wake now to make the wake immediate rather than waiting for the next heartbeat.
agentTurn — a model-backed agent turn
A prompt that runs an isolated, current, or session-bound agent turn with model inference. The most expensive payload kind. Used for anything that requires the model to read, think, write, or call tools.
openclaw automations add \
--name "Deep analysis" \
--cron "0 6 * * 1" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Weekly deep analysis of project progress." \
--model "opus" \
--thinking high \
--announce
The model and fallbacks are per-job. Pass --fallbacks openai/gpt-5.6-sol,openrouter/meta-llama/llama-3.3-70b-instruct:free to set a fallback chain. Pass --fallbacks "" to make the run strict with no fallbacks. The --model override is a job primary, not a session override, so configured fallback chains still apply.
A common pitfall: setting --model "opus" when "opus" is not in the configured catalog. The scheduler fails the run with an explicit validation error, but the failure looks like a generic "job errored" until you read the run record. Always probe with openclaw models status --agent <id> --check --probe before scheduling a job with a non-default model.
command — a shell process on the Gateway host
A shell command (or argv array) executed on the Gateway host. No model call. Used for shell probes, file checks, queue-depth probes, anything that does not need the model.
openclaw automations add \
--name "Queue depth probe" \
--cron "*/15 * * * *" \
--command "scripts/check-queue.sh" \
--command-cwd "/srv/app" \
--announce \
--channel telegram \
--to "-1001234567890"
Default timeout is 10 minutes for command payloads. The shell is the Gateway host's shell, not a sandboxed runtime. Treat every command job as an unattended code execution path on the host — keep the scripts minimal, versioned, and scoped to a known directory.
script — a headless code-mode script
A script file (or stdin) executed with the owning agent's full tool policy. Used when you want to run a short program that calls the same tools the agent has access to, without paying for a full model turn.
openclaw automations add \
--name "Heartbeat scratch check" \
--every "5m" \
--script ./scripts/heartbeat-probe.js \
--session isolated
The script runs headlessly with whatever tools the agent has been granted. The blast radius is whatever the agent has — not narrower. Scripts are the right choice when you want code-level control but the same tool access as a turn.
Delivery modes — where the result goes
Three delivery modes. The choice determines whether the result is visible to a human, fed into another system, or stored only in run history.
Announce — send to a chat channel
The result is delivered to a chat channel as an outbound message. The channel is named by --channel (e.g. slack, telegram, discord, imessage), the recipient is named by --to. With --announce and no --channel, the message goes to the last-used channel.
The result that gets delivered is whatever the model wrote as its final reply. If the agent returns NO_REPLY or no_reply (the silent token), OpenClaw suppresses delivery. If the agent returns an empty string, delivery is also suppressed. The agent has to actually produce text for the message to land.
Webhook — POST to an HTTP endpoint
The result is POSTed to a webhook URL as JSON. Used for machine-to-machine handoffs, dashboards, and downstream pipelines.
openclaw automations add \
--name "Deploy digest" \
--cron "0 18 * * 1-5" \
--message "Summarize today's deploys as JSON." \
--webhook "https://example.invalid/openclaw/cron"
Webhook delivery cannot combine with chat delivery flags. Pick one or the other. The webhook URL is subject to the strict outbound SSRF policy by default; configure cron.webhookSsrfPolicy.allowedHostnames if you need to reach a local or private receiver.
None — store in run history only
The result is not delivered anywhere. It exists in openclaw automations runs --id <jobId> for inspection. Used for jobs whose value is the side effect (writing a file, mutating state) rather than the message.
The agent can still send messages with the message tool when a chat route is available — the "none" delivery mode means the scheduler does not post the result automatically, not the agent cannot post anything. Many production jobs use delivery none plus side effects in the prompt.
The two combinations that cover most jobs
Most OpenClaw automations are one of two combinations:
1. cron schedule + agentTurn payload + announce delivery. Daily brief at 7 AM, weekly digest on Sunday, monthly reconciliation. The schedule is fixed, the work requires the model, and the result goes to a human's chat.
2. every schedule + systemEvent payload + no delivery. A heartbeat that wakes the main session with a "still alive" note. The schedule is frequent, the payload is cheap, and the visibility is the wake itself.
Everything else is a variation:
at+systemEvent— one-shot reminders.every+command— fast shell probes, queue-depth checks, log watchers.cron+agentTurn+webhook— machine-to-machine digests, dashboards.stream+agentTurn— event-driven reactions to a supervised command's output.
What Triadive has not covered (yet)
The existing pieces on Triadive cover what a cron is and when to use one. They do not yet cover the how. Specifically:
- The five schedule kinds (
at,every,cron,on-exit,stream) and when each is right. - The four payload kinds (
systemEvent,agentTurn,command,script) and the cost each carries. - The three delivery modes (announce, webhook, none) and what gets delivered in each.
- The combination table — which schedules pair with which payloads for which use cases.
This piece fills those gaps. The next two pieces in this batch build on it: one on how crons compose with skills, loops, lobsters, and workboard, and one on what makes a cron effective (idempotency, pacing, retries, condition triggers).
What this changes for operators
The shift this kind of detail introduces is mostly about choice. Most operators reach for "cron" when "every" or "at" would be cheaper. Most operators pick agentTurn when systemEvent or command would be enough. Most operators choose announce when webhook or none would be cleaner.
The right combination depends on the work's shape. Recurring prose to a human: cron + agentTurn + announce. Cheap probe of external state: every + command + none. One-shot reminder: at + systemEvent + none. Each combination is a different tool with a different cost and a different failure mode.
The discipline is to pick the simplest combination that does the job. The most expensive combination that does the job is also a valid choice — but only when the work actually needs the model, only when the work actually needs to land in a chat, and only when the cost is justified.
Related reading
- Cron Jobs, Heartbeats, and Scheduled Work — the design discipline piece this builds on.
- Cron vs Heartbeat — the comparison table.
- Cron (Scheduled Agent Run) — the basic definition.
- Inside the Agent Loop — what the agent turn that the payload runs actually does.
- Tools, Skills, and Plugins — the plugin layer that payloads call into.
- Cron Jobs in OpenClaw: Schedule Types, Payloads, and Delivery — this article.
- Crons + Skills + Loops + Lobsters + Workboard — the follow-up on how the pieces compose.
- Effective Cron Design — the follow-up on idempotency, pacing, retries, and condition triggers.