2026-01-06 22:28:32 +00:00
|
|
|
|
---
|
|
|
|
|
|
summary: "Cron jobs + wakeups for the Gateway scheduler"
|
|
|
|
|
|
read_when:
|
|
|
|
|
|
- Scheduling background jobs or wakeups
|
|
|
|
|
|
- Wiring automation that should run with or alongside heartbeats
|
2026-01-23 19:00:17 +00:00
|
|
|
|
- Deciding between heartbeat and cron for scheduled tasks
|
2026-01-06 22:28:32 +00:00
|
|
|
|
---
|
|
|
|
|
|
# Cron jobs (Gateway scheduler)
|
|
|
|
|
|
|
2026-01-23 19:00:17 +00:00
|
|
|
|
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
|
|
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
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 `~/.clawdbot/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”.
|
|
|
|
|
|
|
2026-01-13 04:55:39 +00:00
|
|
|
|
## Beginner-friendly overview
|
|
|
|
|
|
Think of a cron job as: **when** to run + **what** to do.
|
|
|
|
|
|
|
|
|
|
|
|
1) **Choose a schedule**
|
|
|
|
|
|
- One-shot reminder → `schedule.kind = "at"` (CLI: `--at`)
|
|
|
|
|
|
- Repeating job → `schedule.kind = "every"` or `schedule.kind = "cron"`
|
|
|
|
|
|
- If your ISO timestamp omits a timezone, it is treated as **UTC**.
|
|
|
|
|
|
|
|
|
|
|
|
2) **Choose where it runs**
|
|
|
|
|
|
- `sessionTarget: "main"` → run during the next heartbeat with main context.
|
|
|
|
|
|
- `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:<jobId>`.
|
|
|
|
|
|
|
|
|
|
|
|
3) **Choose the payload**
|
|
|
|
|
|
- Main session → `payload.kind = "systemEvent"`
|
|
|
|
|
|
- Isolated session → `payload.kind = "agentTurn"`
|
|
|
|
|
|
|
|
|
|
|
|
Optional: `deleteAfterRun: true` removes successful one-shot jobs from the store.
|
|
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
## 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).
|
2026-01-12 10:23:45 +00:00
|
|
|
|
- optional **agent binding** (`agentId`): run the job under a specific agent; if
|
|
|
|
|
|
missing or unknown, the gateway falls back to the default agent.
|
2026-01-08 21:39:03 +01:00
|
|
|
|
|
|
|
|
|
|
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
|
2026-01-08 21:43:22 +01:00
|
|
|
|
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
|
2026-01-13 04:55:39 +00:00
|
|
|
|
Jobs can optionally auto-delete after a successful one-shot run via `deleteAfterRun: true`.
|
2026-01-08 21:39:03 +01:00
|
|
|
|
|
|
|
|
|
|
### Schedules
|
2026-01-06 22:28:32 +00:00
|
|
|
|
Cron supports three schedule kinds:
|
2026-01-13 04:55:39 +00:00
|
|
|
|
- `at`: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC.
|
2026-01-06 22:28:32 +00:00
|
|
|
|
- `every`: fixed interval (ms).
|
2026-01-08 21:39:03 +01:00
|
|
|
|
- `cron`: 5-field cron expression with optional IANA timezone.
|
2026-01-06 22:28:32 +00:00
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
Cron expressions use `croner`. If a timezone is omitted, the Gateway host’s
|
|
|
|
|
|
local timezone is used.
|
2026-01-06 22:28:32 +00:00
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
### Main vs isolated execution
|
2026-01-06 22:28:32 +00:00
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
#### Main session jobs (system events)
|
2026-01-06 22:28:32 +00:00
|
|
|
|
Main jobs enqueue a system event and optionally wake the heartbeat runner.
|
2026-01-08 21:39:03 +01:00
|
|
|
|
They must use `payload.kind = "systemEvent"`.
|
2026-01-06 22:28:32 +00:00
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
- `wakeMode: "next-heartbeat"` (default): event waits for the next scheduled heartbeat.
|
|
|
|
|
|
- `wakeMode: "now"`: event triggers an immediate heartbeat run.
|
2026-01-06 22:28:32 +00:00
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
This is the best fit when you want the normal heartbeat prompt + main-session context.
|
|
|
|
|
|
See [Heartbeat](/gateway/heartbeat).
|
|
|
|
|
|
|
|
|
|
|
|
#### Isolated jobs (dedicated cron sessions)
|
|
|
|
|
|
Isolated jobs run a dedicated agent turn in session `cron:<jobId>`.
|
2026-01-06 22:28:32 +00:00
|
|
|
|
|
|
|
|
|
|
Key behaviors:
|
|
|
|
|
|
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
|
2026-01-16 21:27:44 +00:00
|
|
|
|
- Each run starts a **fresh session id** (no prior conversation carry-over).
|
2026-01-08 21:39:03 +01:00
|
|
|
|
- A summary is posted to the main session (prefix `Cron`, configurable).
|
2026-01-06 22:28:32 +00:00
|
|
|
|
- `wakeMode: "now"` triggers an immediate heartbeat after posting the summary.
|
2026-01-13 07:15:57 +00:00
|
|
|
|
- If `payload.deliver: true`, output is delivered to a channel; otherwise it stays internal.
|
2026-01-08 21:39:03 +01:00
|
|
|
|
|
2026-01-10 22:32:09 -08:00
|
|
|
|
Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spam
|
2026-01-08 21:39:03 +01:00
|
|
|
|
your main chat history.
|
|
|
|
|
|
|
2026-01-11 10:57:30 +00:00
|
|
|
|
### 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.
|
|
|
|
|
|
|
|
|
|
|
|
Common `agentTurn` fields:
|
|
|
|
|
|
- `message`: required text prompt.
|
|
|
|
|
|
- `model` / `thinking`: optional overrides (see below).
|
|
|
|
|
|
- `timeoutSeconds`: optional timeout override.
|
2026-01-13 07:15:57 +00:00
|
|
|
|
- `deliver`: `true` to send output to a channel target.
|
|
|
|
|
|
- `channel`: `last` or a specific channel.
|
|
|
|
|
|
- `to`: channel-specific target (phone/chat/channel id).
|
2026-01-11 10:57:30 +00:00
|
|
|
|
- `bestEffortDeliver`: avoid failing the job if delivery fails.
|
|
|
|
|
|
|
|
|
|
|
|
Isolation options (only for `session=isolated`):
|
2026-01-17 01:48:02 +00:00
|
|
|
|
- `postToMainPrefix` (CLI: `--post-prefix`): prefix for the system event in main.
|
|
|
|
|
|
- `postToMainMode`: `summary` (default) or `full`.
|
|
|
|
|
|
- `postToMainMaxChars`: max chars when `postToMainMode=full` (default 8000).
|
2026-01-11 10:57:30 +00:00
|
|
|
|
|
2026-01-10 22:32:09 -08:00
|
|
|
|
### 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`)
|
2026-01-07 17:17:38 -08:00
|
|
|
|
- `thinking`: Thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`; GPT-5.2 + Codex models only)
|
2026-01-10 22:32:09 -08:00
|
|
|
|
|
2026-01-11 10:52:40 +00:00
|
|
|
|
Note: You can set `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.
|
|
|
|
|
|
|
2026-01-10 22:32:09 -08:00
|
|
|
|
Resolution priority:
|
|
|
|
|
|
1. Job payload override (highest)
|
|
|
|
|
|
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
|
|
|
|
|
|
3. Agent config default
|
|
|
|
|
|
|
2026-01-13 07:15:57 +00:00
|
|
|
|
### Delivery (channel + target)
|
|
|
|
|
|
Isolated jobs can deliver output to a channel. The job payload can specify:
|
2026-01-23 00:19:23 +00:00
|
|
|
|
- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`
|
2026-01-13 07:15:57 +00:00
|
|
|
|
- `to`: channel-specific recipient target
|
2026-01-08 21:39:03 +01:00
|
|
|
|
|
2026-01-13 07:15:57 +00:00
|
|
|
|
If `channel` or `to` is omitted, cron can fall back to the main session’s “last route”
|
2026-01-08 21:39:03 +01:00
|
|
|
|
(the last place the agent replied).
|
|
|
|
|
|
|
2026-01-20 17:56:12 +00:00
|
|
|
|
Delivery notes:
|
|
|
|
|
|
- If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted.
|
|
|
|
|
|
- Use `deliver: true` when you want last-route delivery without an explicit `to`.
|
|
|
|
|
|
- Use `deliver: false` to keep output internal even if a `to` is present.
|
|
|
|
|
|
|
2026-01-11 10:57:30 +00:00
|
|
|
|
Target format reminders:
|
2026-01-23 00:19:23 +00:00
|
|
|
|
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) to avoid ambiguity.
|
2026-01-11 10:57:30 +00:00
|
|
|
|
- Telegram topics should use the `:topic:` form (see below).
|
|
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
#### Telegram delivery targets (topics / forum threads)
|
|
|
|
|
|
Telegram supports forum topics via `message_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)
|
|
|
|
|
|
|
2026-01-08 23:25:45 +01:00
|
|
|
|
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
2026-01-08 21:39:03 +01:00
|
|
|
|
- `telegram:group:-1001234567890:topic:123`
|
2026-01-06 22:28:32 +00:00
|
|
|
|
|
|
|
|
|
|
## Storage & history
|
2026-01-08 21:39:03 +01:00
|
|
|
|
- Job store: `~/.clawdbot/cron/jobs.json` (Gateway-managed JSON).
|
2026-01-06 22:28:32 +00:00
|
|
|
|
- Run history: `~/.clawdbot/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned).
|
|
|
|
|
|
- Override store path: `cron.store` in config.
|
|
|
|
|
|
|
|
|
|
|
|
## Configuration
|
|
|
|
|
|
|
|
|
|
|
|
```json5
|
|
|
|
|
|
{
|
|
|
|
|
|
cron: {
|
2026-01-08 21:39:03 +01:00
|
|
|
|
enabled: true, // default true
|
2026-01-06 22:28:32 +00:00
|
|
|
|
store: "~/.clawdbot/cron/jobs.json",
|
2026-01-08 21:39:03 +01:00
|
|
|
|
maxConcurrentRuns: 1 // default 1
|
2026-01-06 22:28:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Disable cron entirely:
|
|
|
|
|
|
- `cron.enabled: false` (config)
|
2026-01-08 21:39:03 +01:00
|
|
|
|
- `CLAWDBOT_SKIP_CRON=1` (env)
|
2026-01-06 22:28:32 +00:00
|
|
|
|
|
|
|
|
|
|
## CLI quickstart
|
|
|
|
|
|
|
2026-01-13 04:55:39 +00:00
|
|
|
|
One-shot reminder (UTC ISO, auto-delete after success):
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot cron add \
|
|
|
|
|
|
--name "Send reminder" \
|
|
|
|
|
|
--at "2026-01-12T18:00:00Z" \
|
|
|
|
|
|
--session main \
|
|
|
|
|
|
--system-event "Reminder: submit expense report." \
|
|
|
|
|
|
--wake now \
|
|
|
|
|
|
--delete-after-run
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-06 22:28:32 +00:00
|
|
|
|
One-shot reminder (main session, wake immediately):
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot cron add \
|
|
|
|
|
|
--name "Calendar check" \
|
|
|
|
|
|
--at "20m" \
|
|
|
|
|
|
--session main \
|
|
|
|
|
|
--system-event "Next heartbeat: check calendar." \
|
|
|
|
|
|
--wake now
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Recurring isolated job (deliver to WhatsApp):
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot cron add \
|
|
|
|
|
|
--name "Morning status" \
|
|
|
|
|
|
--cron "0 7 * * *" \
|
|
|
|
|
|
--tz "America/Los_Angeles" \
|
|
|
|
|
|
--session isolated \
|
|
|
|
|
|
--message "Summarize inbox + calendar for today." \
|
|
|
|
|
|
--deliver \
|
2026-01-13 06:51:20 +00:00
|
|
|
|
--channel whatsapp \
|
2026-01-06 22:28:32 +00:00
|
|
|
|
--to "+15551234567"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
Recurring isolated job (deliver to a Telegram topic):
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot cron add \
|
|
|
|
|
|
--name "Nightly summary (topic)" \
|
|
|
|
|
|
--cron "0 22 * * *" \
|
|
|
|
|
|
--tz "America/Los_Angeles" \
|
|
|
|
|
|
--session isolated \
|
|
|
|
|
|
--message "Summarize today; send to the nightly topic." \
|
|
|
|
|
|
--deliver \
|
2026-01-13 06:51:20 +00:00
|
|
|
|
--channel telegram \
|
2026-01-08 21:39:03 +01:00
|
|
|
|
--to "-1001234567890:topic:123"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-10 22:32:09 -08:00
|
|
|
|
Isolated job with model and thinking override:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot cron 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 \
|
|
|
|
|
|
--deliver \
|
2026-01-13 06:51:20 +00:00
|
|
|
|
--channel whatsapp \
|
2026-01-10 22:32:09 -08:00
|
|
|
|
--to "+15551234567"
|
2026-01-12 10:23:45 +00:00
|
|
|
|
|
|
|
|
|
|
Agent selection (multi-agent setups):
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# Pin a job to agent "ops" (falls back to default if that agent is missing)
|
|
|
|
|
|
clawdbot cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
|
|
|
|
|
|
|
|
|
|
|
|
# Switch or clear the agent on an existing job
|
|
|
|
|
|
clawdbot cron edit <jobId> --agent ops
|
|
|
|
|
|
clawdbot cron edit <jobId> --clear-agent
|
|
|
|
|
|
```
|
2026-01-10 22:32:09 -08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-06 22:28:32 +00:00
|
|
|
|
Manual run (debug):
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot cron run <jobId> --force
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-11 10:57:30 +00:00
|
|
|
|
Edit an existing job (patch fields):
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot cron edit <jobId> \
|
|
|
|
|
|
--message "Updated prompt" \
|
|
|
|
|
|
--model "opus" \
|
|
|
|
|
|
--thinking low
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-06 22:28:32 +00:00
|
|
|
|
Run history:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot cron runs --id <jobId> --limit 50
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Immediate wake without creating a job:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot wake --mode now --text "Next heartbeat: check battery."
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
## Gateway API surface
|
2026-01-06 22:28:32 +00:00
|
|
|
|
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
|
|
|
|
|
|
- `cron.run` (force or due), `cron.runs`
|
|
|
|
|
|
- `wake` (enqueue system event + optional heartbeat)
|
|
|
|
|
|
|
2026-01-08 21:39:03 +01:00
|
|
|
|
## Troubleshooting
|
|
|
|
|
|
|
|
|
|
|
|
### “Nothing runs”
|
|
|
|
|
|
- Check cron is enabled: `cron.enabled` and `CLAWDBOT_SKIP_CRON`.
|
|
|
|
|
|
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
|
|
|
|
|
- For `cron` schedules: 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.
|