11 KiB
summary, read_when, title
| summary | read_when | title | |||
|---|---|---|---|---|---|
| Scheduled jobs, webhooks, and Gmail PubSub triggers for the Gateway scheduler |
|
Scheduled Tasks |
Scheduled Tasks (Cron)
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
# 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
# See run history
openclaw cron runs --id <job-id>
How cron works
- Cron runs inside the Gateway process (not inside the model).
- Jobs persist at
~/.openclaw/cron/jobs.jsonso restarts do not lose schedules. - All cron executions create background task records.
- One-shot jobs (
--at) auto-delete after success by default.
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
| Kind | CLI flag | Description |
|---|---|---|
at |
--at |
One-shot timestamp (ISO 8601 or relative like 20m) |
every |
--every |
Fixed interval |
cron |
--cron |
5-field or 6-field cron expression with optional --tz |
Timestamps without a timezone are treated as UTC. Add --tz America/New_York for local wall-clock scheduling.
Recurring top-of-hour expressions are automatically staggered by up to 5 minutes to reduce load spikes. Use --exact to force precise timing or --stagger 30s for an explicit window.
Execution styles
| Style | --session value |
Runs in | Best for |
|---|---|---|---|
| Main session | main |
Next heartbeat turn | Reminders, system events |
| Isolated | isolated |
Dedicated cron:<jobId> |
Reports, background chores |
| 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). 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.
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
Delivery and output
| Mode | What happens |
|---|---|
announce |
Deliver summary to target channel (default for isolated) |
webhook |
POST finished event payload to a URL |
none |
Internal only, no delivery |
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:<id>, user:<id>).
CLI examples
One-shot reminder (main session):
openclaw cron add \
--name "Calendar check" \
--at "20m" \
--session main \
--system-event "Next heartbeat: check calendar." \
--wake now
Recurring isolated job with delivery:
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:
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
Gateway can expose HTTP webhook endpoints for external triggers. Enable in config:
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
},
}
Authentication
Every request must include the hook token via header:
Authorization: Bearer <token>(recommended)x-openclaw-token: <token>
Query-string tokens are rejected.
POST /hooks/wake
Enqueue a system event for the main session:
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"}'
text(required): event descriptionmode(optional):now(default) ornext-heartbeat
POST /hooks/agent
Run an isolated agent turn:
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-mini"}'
Fields: message (required), name, agentId, wakeMode, deliver, channel, to, model, thinking, timeoutSeconds.
Mapped hooks (POST /hooks/<name>)
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.
- Set
hooks.allowedAgentIdsto limit explicitagentIdrouting. - Keep
hooks.allowRequestSessionKey=falseunless you require caller-selected sessions. - 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.
Wizard setup (recommended)
openclaw webhooks gmail setup --account openclaw@gmail.com
This writes hooks.gmail config, enables the Gmail preset, and uses Tailscale Funnel for the push endpoint.
Gateway auto-start
When hooks.enabled=true and hooks.gmail.account is set, the Gateway starts gog gmail watch serve on boot and auto-renews the watch. Set OPENCLAW_SKIP_GMAIL_WATCHER=1 to opt out.
Manual one-time setup
- Select the GCP project that owns the OAuth client used by
gog:
gcloud auth login
gcloud config set project <project-id>
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
- Create topic and grant Gmail push access:
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
- Start the watch:
gog gmail watch start \
--account openclaw@gmail.com \
--label INBOX \
--topic projects/<project-id>/topics/gog-gmail-watch
Gmail model override
{
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
thinking: "off",
},
},
}
Managing jobs
# List all jobs
openclaw cron list
# Edit a job
openclaw cron edit <jobId> --message "Updated prompt" --model "opus"
# Force run a job now
openclaw cron run <jobId>
# Run only if due
openclaw cron run <jobId> --due
# View run history
openclaw cron runs --id <jobId> --limit 50
# Delete a job
openclaw cron remove <jobId>
# Agent selection (multi-agent setups)
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
openclaw cron edit <jobId> --clear-agent
Configuration
{
cron: {
enabled: true,
store: "~/.openclaw/cron/jobs.json",
maxConcurrentRuns: 1,
retry: {
maxAttempts: 3,
backoffMs: [60000, 120000, 300000],
retryOn: ["rate_limit", "overloaded", "network", "server_error"],
},
webhookToken: "replace-with-dedicated-webhook-token",
sessionRetention: "24h",
runLog: { maxBytes: "2mb", keepLines: 2000 },
},
}
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.
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.
Troubleshooting
Command ladder
openclaw status
openclaw gateway status
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw system heartbeat last
openclaw logs --follow
openclaw doctor
Cron not firing
- Check
cron.enabledandOPENCLAW_SKIP_CRONenv var. - Confirm the Gateway is running continuously.
- For
cronschedules, verify timezone (--tz) vs the host timezone. reason: not-duein run output means manual run was checked withopenclaw cron run <jobId> --dueand the job was not due yet.
Cron fired but no delivery
- Delivery mode is
nonemeans no external message is expected. - Delivery target missing/invalid (
channel/to) means outbound was skipped. - Channel auth errors (
unauthorized,Forbidden) mean delivery was blocked by credentials.
Timezone gotchas
- Cron without
--tzuses the gateway host timezone. atschedules without timezone are treated as UTC.- Heartbeat
activeHoursuses configured timezone resolution.
Related
- Automation & Tasks — all automation mechanisms at a glance
- Background Tasks — task ledger for cron executions
- Heartbeat — periodic main-session turns
- Timezone — timezone configuration