From 48d83b756666986ebb30750c873c30f8ff76d520 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 23:05:47 -0700 Subject: [PATCH] docs(cron-jobs): rewrite around Steps, Tabs, ParamField, AccordionGroup, and Warning callouts --- docs/automation/cron-jobs.md | 411 ++++++++++++++++++----------------- 1 file changed, 206 insertions(+), 205 deletions(-) diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md index 40a16c38581..289ebd1105c 100644 --- a/docs/automation/cron-jobs.md +++ b/docs/automation/cron-jobs.md @@ -5,29 +5,37 @@ read_when: - Wiring external triggers (webhooks, Gmail) into OpenClaw - Deciding between heartbeat and cron for scheduled tasks title: "Scheduled tasks" +sidebarTitle: "Scheduled tasks" --- Cron is the Gateway's built-in scheduler. It persists jobs, wakes the agent at the right time, and can deliver output back to a chat channel or webhook endpoint. ## Quick start -```bash -# Add a one-shot reminder -openclaw cron add \ - --name "Reminder" \ - --at "2026-02-01T16:00:00Z" \ - --session main \ - --system-event "Reminder: check the cron docs draft" \ - --wake now \ - --delete-after-run - -# Check your jobs -openclaw cron list -openclaw cron show - -# See run history -openclaw cron runs --id -``` + + + ```bash + openclaw cron add \ + --name "Reminder" \ + --at "2026-02-01T16:00:00Z" \ + --session main \ + --system-event "Reminder: check the cron docs draft" \ + --wake now \ + --delete-after-run + ``` + + + ```bash + openclaw cron list + openclaw cron show + ``` + + + ```bash + openclaw cron runs --id + ``` + + ## How cron works @@ -38,18 +46,13 @@ openclaw cron runs --id - All cron executions create [background task](/automation/tasks) records. - One-shot jobs (`--at`) auto-delete after success by default. - Isolated cron runs best-effort close tracked browser tabs/processes for their `cron:` session when the run completes, so detached browser automation does not leave orphaned processes behind. -- Isolated cron runs also guard against stale acknowledgement replies. If the - first result is just an interim status update (`on it`, `pulling everything -together`, and similar hints) and no descendant subagent run is still - responsible for the final answer, OpenClaw re-prompts once for the actual - result before delivery. +- Isolated cron runs also guard against stale acknowledgement replies. If the first result is just an interim status update (`on it`, `pulling everything together`, and similar hints) and no descendant subagent run is still responsible for the final answer, OpenClaw re-prompts once for the actual result before delivery. -Task reconciliation for cron is runtime-owned: an active cron task stays live while the -cron runtime still tracks that job as running, even if an old child session row still exists. -Once the runtime stops owning the job and the 5-minute grace window expires, maintenance can -mark the task `lost`. + +Task reconciliation for cron is runtime-owned: an active cron task stays live while the cron runtime still tracks that job as running, even if an old child session row still exists. Once the runtime stops owning the job and the 5-minute grace window expires, maintenance can mark the task `lost`. + ## Schedule types @@ -84,35 +87,46 @@ This fires ~5–6 times per month instead of 0–1 times per month. OpenClaw use | Current session | `current` | Bound at creation time | Context-aware recurring work | | Custom session | `session:custom-id` | Persistent named session | Workflows that build on history | -**Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). Those system events do not extend daily/idle reset freshness for the target session. **Isolated** jobs run a dedicated agent turn with a fresh session. **Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries. + + + **Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). Those system events do not extend daily/idle reset freshness for the target session. **Isolated** jobs run a dedicated agent turn with a fresh session. **Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries. + + + For isolated jobs, "fresh session" means a new transcript/session id for each run. OpenClaw may carry safe preferences such as thinking/fast/verbose settings, labels, and explicit user-selected model/auth overrides, but it does not inherit ambient conversation context from an older cron row: channel/group routing, send or queue policy, elevation, origin, or ACP runtime binding. Use `current` or `session:` when a recurring job should deliberately build on the same conversation context. + + + For isolated jobs, runtime teardown now includes best-effort browser cleanup for that cron session. Cleanup failures are ignored so the actual cron result still wins. -For isolated jobs, “fresh session” means a new transcript/session id for each run. OpenClaw may carry safe preferences such as thinking/fast/verbose settings, labels, and explicit user-selected model/auth overrides, but it does not inherit ambient conversation context from an older cron row: channel/group routing, send or queue policy, elevation, origin, or ACP runtime binding. Use `current` or `session:` when a recurring job should deliberately build on the same conversation context. + Isolated cron runs also dispose any bundled MCP runtime instances created for the job through the shared runtime-cleanup path. This matches how main-session and custom-session MCP clients are torn down, so isolated cron jobs do not leak stdio child processes or long-lived MCP connections across runs. -For isolated jobs, runtime teardown now includes best-effort browser cleanup for that cron session. Cleanup failures are ignored so the actual cron result still wins. + + + When isolated cron runs orchestrate subagents, delivery also prefers the final descendant output over stale parent interim text. If descendants are still running, OpenClaw suppresses that partial parent update instead of announcing it. -Isolated cron runs also dispose any bundled MCP runtime instances created for the job through the shared runtime-cleanup path. This matches how main-session and custom-session MCP clients are torn down, so isolated cron jobs do not leak stdio child processes or long-lived MCP connections across runs. + For text-only Discord announce targets, OpenClaw sends the canonical final assistant text once instead of replaying both streamed/intermediate text payloads and the final answer. Media and structured Discord payloads are still delivered as separate payloads so attachments and components are not dropped. -When isolated cron runs orchestrate subagents, delivery also prefers the final -descendant output over stale parent interim text. If descendants are still -running, OpenClaw suppresses that partial parent update instead of announcing it. - -For text-only Discord announce targets, OpenClaw sends the canonical final -assistant text once instead of replaying both streamed/intermediate text payloads -and the final answer. Media and structured Discord payloads are still delivered -as separate payloads so attachments and components are not dropped. + + ### Payload options for isolated jobs -- `--message`: prompt text (required for isolated) -- `--model` / `--thinking`: model and thinking level overrides -- `--light-context`: skip workspace bootstrap file injection -- `--tools exec,read`: restrict which tools the job can use + + Prompt text (required for isolated). + + + Model override; uses the selected allowed model for the job. + + + Thinking level override. + + + Skip workspace bootstrap file injection. + + + Restrict which tools the job can use, for example `--tools exec,read`. + -`--model` uses the selected allowed model for that job. If the requested model -is not allowed, cron logs a warning and falls back to the job's agent/default -model selection instead. Configured fallback chains still apply, but a plain -model override with no explicit per-job fallback list no longer appends the -agent primary as a hidden extra retry target. +`--model` uses the selected allowed model for that job. If the requested model is not allowed, cron logs a warning and falls back to the job's agent/default model selection instead. Configured fallback chains still apply, but a plain model override with no explicit per-job fallback list no longer appends the agent primary as a hidden extra retry target. Model-selection precedence for isolated jobs is: @@ -121,16 +135,9 @@ Model-selection precedence for isolated jobs is: 3. User-selected stored cron session model override 4. Agent/default model selection -Fast mode follows the resolved live selection too. If the selected model config -has `params.fastMode`, isolated cron uses that by default. A stored session -`fastMode` override still wins over config in either direction. +Fast mode follows the resolved live selection too. If the selected model config has `params.fastMode`, isolated cron uses that by default. A stored session `fastMode` override still wins over config in either direction. -If an isolated run hits a live model-switch handoff, cron retries with the -switched provider/model and persists that live selection for the active run -before retrying. When the switch also carries a new auth profile, cron persists -that auth profile override for the active run too. Retries are bounded: after -the initial attempt plus 2 switch retries, cron aborts instead of looping -forever. +If an isolated run hits a live model-switch handoff, cron retries with the switched provider/model and persists that live selection for the active run before retrying. When the switch also carries a new auth profile, cron persists that auth profile override for the active run too. Retries are bounded: after the initial attempt plus 2 switch retries, cron aborts instead of looping forever. ## Delivery and output @@ -142,16 +149,9 @@ forever. Use `--announce --channel telegram --to "-1001234567890"` for channel delivery. For Telegram forum topics, use `-1001234567890:topic:123`. Slack/Discord/Mattermost targets should use explicit prefixes (`channel:`, `user:`). Matrix room IDs are case-sensitive; use the exact room ID or `room:!room:server` form from Matrix. -For isolated jobs, chat delivery is shared. If a chat route is available, the -agent can use the `message` tool even when the job uses `--no-deliver`. If the -agent sends to the configured/current target, OpenClaw skips the fallback -announce. Otherwise `announce`, `webhook`, and `none` only control what the -runner does with the final reply after the agent turn. +For isolated jobs, chat delivery is shared. If a chat route is available, the agent can use the `message` tool even when the job uses `--no-deliver`. If the agent sends to the configured/current target, OpenClaw skips the fallback announce. Otherwise `announce`, `webhook`, and `none` only control what the runner does with the final reply after the agent turn. -When an agent creates an isolated reminder from an active chat, OpenClaw stores -the preserved live delivery target for the fallback announce route. Internal -session keys may be lowercase; provider delivery targets are not reconstructed -from those keys when current chat context is available. +When an agent creates an isolated reminder from an active chat, OpenClaw stores the preserved live delivery target for the fallback announce route. Internal session keys may be lowercase; provider delivery targets are not reconstructed from those keys when current chat context is available. Failure notifications follow a separate destination path: @@ -162,44 +162,44 @@ Failure notifications follow a separate destination path: ## CLI examples -One-shot reminder (main session): - -```bash -openclaw cron add \ - --name "Calendar check" \ - --at "20m" \ - --session main \ - --system-event "Next heartbeat: check calendar." \ - --wake now -``` - -Recurring isolated job with delivery: - -```bash -openclaw cron add \ - --name "Morning brief" \ - --cron "0 7 * * *" \ - --tz "America/Los_Angeles" \ - --session isolated \ - --message "Summarize overnight updates." \ - --announce \ - --channel slack \ - --to "channel:C1234567890" -``` - -Isolated job with model and thinking override: - -```bash -openclaw 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 \ - --announce -``` + + + ```bash + openclaw cron add \ + --name "Calendar check" \ + --at "20m" \ + --session main \ + --system-event "Next heartbeat: check calendar." \ + --wake now + ``` + + + ```bash + openclaw cron add \ + --name "Morning brief" \ + --cron "0 7 * * *" \ + --tz "America/Los_Angeles" \ + --session isolated \ + --message "Summarize overnight updates." \ + --announce \ + --channel slack \ + --to "channel:C1234567890" + ``` + + + ```bash + openclaw 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 \ + --announce + ``` + + ## Webhooks @@ -224,52 +224,61 @@ Every request must include the hook token via header: Query-string tokens are rejected. -### POST /hooks/wake + + + Enqueue a system event for the main session: -Enqueue a system event for the main session: + ```bash + curl -X POST http://127.0.0.1:18789/hooks/wake \ + -H 'Authorization: Bearer SECRET' \ + -H 'Content-Type: application/json' \ + -d '{"text":"New email received","mode":"now"}' + ``` -```bash -curl -X POST http://127.0.0.1:18789/hooks/wake \ - -H 'Authorization: Bearer SECRET' \ - -H 'Content-Type: application/json' \ - -d '{"text":"New email received","mode":"now"}' -``` + + Event description. + + + `now` or `next-heartbeat`. + -- `text` (required): event description -- `mode` (optional): `now` (default) or `next-heartbeat` + + + Run an isolated agent turn: -### POST /hooks/agent + ```bash + curl -X POST http://127.0.0.1:18789/hooks/agent \ + -H 'Authorization: Bearer SECRET' \ + -H 'Content-Type: application/json' \ + -d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.4"}' + ``` -Run an isolated agent turn: + Fields: `message` (required), `name`, `agentId`, `wakeMode`, `deliver`, `channel`, `to`, `model`, `thinking`, `timeoutSeconds`. -```bash -curl -X POST http://127.0.0.1:18789/hooks/agent \ - -H 'Authorization: Bearer SECRET' \ - -H 'Content-Type: application/json' \ - -d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.4"}' -``` + + + Custom hook names are resolved via `hooks.mappings` in config. Mappings can transform arbitrary payloads into `wake` or `agent` actions with templates or code transforms. + + -Fields: `message` (required), `name`, `agentId`, `wakeMode`, `deliver`, `channel`, `to`, `model`, `thinking`, `timeoutSeconds`. + +Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy. -### Mapped hooks (POST /hooks/\) - -Custom hook names are resolved via `hooks.mappings` in config. Mappings can transform arbitrary payloads into `wake` or `agent` actions with templates or code transforms. - -### Security - -- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy. - Use a dedicated hook token; do not reuse gateway auth tokens. - Keep `hooks.path` on a dedicated subpath; `/` is rejected. - Set `hooks.allowedAgentIds` to limit explicit `agentId` routing. - Keep `hooks.allowRequestSessionKey=false` unless you require caller-selected sessions. - If you enable `hooks.allowRequestSessionKey`, also set `hooks.allowedSessionKeyPrefixes` to constrain allowed session key shapes. - Hook payloads are wrapped with safety boundaries by default. + ## Gmail PubSub integration Wire Gmail inbox triggers to OpenClaw via Google PubSub. -**Prerequisites**: `gcloud` CLI, `gog` (gogcli), OpenClaw hooks enabled, Tailscale for the public HTTPS endpoint. + +**Prerequisites:** `gcloud` CLI, `gog` (gogcli), OpenClaw hooks enabled, Tailscale for the public HTTPS endpoint. + ### Wizard setup (recommended) @@ -285,31 +294,34 @@ When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts ` ### Manual one-time setup -1. Select the GCP project that owns the OAuth client used by `gog`: + + + Select the GCP project that owns the OAuth client used by `gog`: -```bash -gcloud auth login -gcloud config set project -gcloud services enable gmail.googleapis.com pubsub.googleapis.com -``` + ```bash + gcloud auth login + gcloud config set project + gcloud services enable gmail.googleapis.com pubsub.googleapis.com + ``` -2. Create topic and grant Gmail push access: - -```bash -gcloud pubsub topics create gog-gmail-watch -gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \ - --member=serviceAccount:gmail-api-push@system.gserviceaccount.com \ - --role=roles/pubsub.publisher -``` - -3. Start the watch: - -```bash -gog gmail watch start \ - --account openclaw@gmail.com \ - --label INBOX \ - --topic projects//topics/gog-gmail-watch -``` + + + ```bash + gcloud pubsub topics create gog-gmail-watch + gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \ + --member=serviceAccount:gmail-api-push@system.gserviceaccount.com \ + --role=roles/pubsub.publisher + ``` + + + ```bash + gog gmail watch start \ + --account openclaw@gmail.com \ + --label INBOX \ + --topic projects//topics/gog-gmail-watch + ``` + + ### Gmail model override @@ -353,16 +365,14 @@ openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --mes openclaw cron edit --clear-agent ``` + Model override note: - `openclaw cron add|edit --model ...` changes the job's selected model. -- If the model is allowed, that exact provider/model reaches the isolated agent - run. -- If it is not allowed, cron warns and falls back to the job's agent/default - model selection. -- Configured fallback chains still apply, but a plain `--model` override with - no explicit per-job fallback list no longer falls through to the agent - primary as a silent extra retry target. +- If the model is allowed, that exact provider/model reaches the isolated agent run. +- If it is not allowed, cron warns and falls back to the job's agent/default model selection. +- Configured fallback chains still apply, but a plain `--model` override with no explicit per-job fallback list no longer falls through to the agent primary as a silent extra retry target. + ## Configuration @@ -384,17 +394,21 @@ Model override note: } ``` -The runtime state sidecar is derived from `cron.store`: a `.json` store such as -`~/clawd/cron/jobs.json` uses `~/clawd/cron/jobs-state.json`, while a store path -without a `.json` suffix appends `-state.json`. +The runtime state sidecar is derived from `cron.store`: a `.json` store such as `~/clawd/cron/jobs.json` uses `~/clawd/cron/jobs-state.json`, while a store path without a `.json` suffix appends `-state.json`. Disable cron: `cron.enabled: false` or `OPENCLAW_SKIP_CRON=1`. -**One-shot retry**: transient errors (rate limit, overload, network, server error) retry up to 3 times with exponential backoff. Permanent errors disable immediately. + + + **One-shot retry**: transient errors (rate limit, overload, network, server error) retry up to 3 times with exponential backoff. Permanent errors disable immediately. -**Recurring retry**: exponential backoff (30s to 60m) between retries. Backoff resets after the next successful run. + **Recurring retry**: exponential backoff (30s to 60m) between retries. Backoff resets after the next successful run. -**Maintenance**: `cron.sessionRetention` (default `24h`) prunes isolated run-session entries. `cron.runLog.maxBytes` / `cron.runLog.keepLines` auto-prune run-log files. + + + `cron.sessionRetention` (default `24h`) prunes isolated run-session entries. `cron.runLog.maxBytes` / `cron.runLog.keepLines` auto-prune run-log files. + + ## Troubleshooting @@ -411,45 +425,32 @@ openclaw logs --follow openclaw doctor ``` -### Cron not firing - -- Check `cron.enabled` and `OPENCLAW_SKIP_CRON` env var. -- Confirm the Gateway is running continuously. -- For `cron` schedules, verify timezone (`--tz`) vs the host timezone. -- `reason: not-due` in run output means manual run was checked with `openclaw cron run --due` and the job was not due yet. - -### Cron fired but no delivery - -- Delivery mode `none` means no runner fallback send is expected. The agent can - still send directly with the `message` tool when a chat route is available. -- Delivery target missing/invalid (`channel`/`to`) means outbound was skipped. -- For Matrix, copied or legacy jobs with lowercased `delivery.to` room IDs can - fail because Matrix room IDs are case-sensitive. Edit the job to the exact - `!room:server` or `room:!room:server` value from Matrix. -- Channel auth errors (`unauthorized`, `Forbidden`) mean delivery was blocked by credentials. -- If the isolated run returns only the silent token (`NO_REPLY` / `no_reply`), - OpenClaw suppresses direct outbound delivery and also suppresses the fallback - queued summary path, so nothing is posted back to chat. -- If the agent should message the user itself, check that the job has a usable - route (`channel: "last"` with a previous chat, or an explicit channel/target). - -### Cron or heartbeat appears to prevent `/new`-style rollover - -- Daily and idle reset freshness is not based on `updatedAt`; see - [Session management](/concepts/session#session-lifecycle). -- Cron wakeups, heartbeat runs, exec notifications, and gateway bookkeeping may - update the session row for routing/status, but they do not extend - `sessionStartedAt` or `lastInteractionAt`. -- For legacy rows created before those fields existed, OpenClaw can recover - `sessionStartedAt` from the transcript JSONL session header when the file is - still available. Legacy idle rows without `lastInteractionAt` use that - recovered start time as their idle baseline. - -### Timezone gotchas - -- Cron without `--tz` uses the gateway host timezone. -- `at` schedules without timezone are treated as UTC. -- Heartbeat `activeHours` uses configured timezone resolution. + + + - Check `cron.enabled` and `OPENCLAW_SKIP_CRON` env var. + - Confirm the Gateway is running continuously. + - For `cron` schedules, verify timezone (`--tz`) vs the host timezone. + - `reason: not-due` in run output means manual run was checked with `openclaw cron run --due` and the job was not due yet. + + + - Delivery mode `none` means no runner fallback send is expected. The agent can still send directly with the `message` tool when a chat route is available. + - Delivery target missing/invalid (`channel`/`to`) means outbound was skipped. + - For Matrix, copied or legacy jobs with lowercased `delivery.to` room IDs can fail because Matrix room IDs are case-sensitive. Edit the job to the exact `!room:server` or `room:!room:server` value from Matrix. + - Channel auth errors (`unauthorized`, `Forbidden`) mean delivery was blocked by credentials. + - If the isolated run returns only the silent token (`NO_REPLY` / `no_reply`), OpenClaw suppresses direct outbound delivery and also suppresses the fallback queued summary path, so nothing is posted back to chat. + - If the agent should message the user itself, check that the job has a usable route (`channel: "last"` with a previous chat, or an explicit channel/target). + + + - Daily and idle reset freshness is not based on `updatedAt`; see [Session management](/concepts/session#session-lifecycle). + - Cron wakeups, heartbeat runs, exec notifications, and gateway bookkeeping may update the session row for routing/status, but they do not extend `sessionStartedAt` or `lastInteractionAt`. + - For legacy rows created before those fields existed, OpenClaw can recover `sessionStartedAt` from the transcript JSONL session header when the file is still available. Legacy idle rows without `lastInteractionAt` use that recovered start time as their idle baseline. + + + - Cron without `--tz` uses the gateway host timezone. + - `at` schedules without timezone are treated as UTC. + - Heartbeat `activeHours` uses configured timezone resolution. + + ## Related