docs: restructure automation section as Automation & Tasks

This commit is contained in:
Vincent Koc
2026-04-03 03:16:41 +09:00
parent 3bd2bbea34
commit d9c662dc69
23 changed files with 372 additions and 1695 deletions

View File

@@ -1,44 +1,8 @@
---
summary: "Monitor OAuth expiry for model providers"
read_when:
- Setting up auth expiry monitoring or alerts
- Automating Claude Code / Codex OAuth refresh checks
summary: "Redirect to /gateway/authentication"
title: "Auth Monitoring"
---
# Auth monitoring
# Auth Monitoring
OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for
automation and alerting; scripts are optional extras for phone workflows.
## Preferred: CLI check (portable)
```bash
openclaw models status --check
```
Exit codes:
- `0`: OK
- `1`: expired or missing credentials
- `2`: expiring soon (within 24h)
This works in cron/systemd and requires no extra scripts.
## Optional scripts (ops / phone workflows)
These live under `scripts/` and are **optional**. They assume SSH access to the
gateway host and are tuned for systemd + Termux.
- `scripts/claude-auth-status.sh` now uses `openclaw models status --json` as the
source of truth (falling back to direct file reads if the CLI is unavailable),
so keep `openclaw` on `PATH` for timers.
- `scripts/auth-monitor.sh`: cron/systemd timer target; sends alerts (ntfy or phone).
- `scripts/systemd/openclaw-auth-monitor.{service,timer}`: systemd user timer.
- `scripts/claude-auth-status.sh`: Claude Code + OpenClaw auth checker (full/json/simple).
- `scripts/mobile-reauth.sh`: guided reauth flow over SSH.
- `scripts/termux-quick-auth.sh`: onetap widget status + open auth URL.
- `scripts/termux-auth-widget.sh`: full guided widget flow.
- `scripts/termux-sync-widget.sh`: sync Claude Code creds → OpenClaw.
If you dont need phone automation or systemd timers, skip these scripts.
This page moved to [Authentication](/gateway/authentication). See [Authentication](/gateway/authentication) for auth monitoring documentation.

View File

@@ -1,48 +1,20 @@
---
summary: "Cron jobs + wakeups for the Gateway scheduler"
summary: "Scheduled jobs, webhooks, and Gmail PubSub triggers for the Gateway scheduler"
read_when:
- Scheduling background jobs or wakeups
- Wiring automation that should run with or alongside heartbeats
- Wiring external triggers (webhooks, Gmail) into OpenClaw
- Deciding between heartbeat and cron for scheduled tasks
title: "Cron Jobs"
title: "Scheduled Tasks"
---
# Cron jobs (Gateway scheduler)
# Scheduled Tasks (Cron)
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/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 deliver output back to a chat channel or webhook endpoint.
Cron is the Gateways built-in scheduler. It persists jobs, wakes the agent at
the right time, and can optionally deliver output back to a chat.
All cron executions create [background task](/automation/tasks) records. The key difference is visibility:
- `sessionTarget: "main"` creates a task with `silent` notify policy — it schedules a system event for the main session and heartbeat flow but does not generate notifications.
- `sessionTarget: "isolated"` or `sessionTarget: "session:..."` creates a visible task that shows up in `openclaw tasks` with delivery notifications.
If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_,
cron is the mechanism.
Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting)
## TL;DR
- Cron runs **inside the Gateway** (not inside the model).
- Jobs persist under `~/.openclaw/cron/` so restarts dont 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>` or a custom session, with delivery (announce by default or none).
- **Current session**: bind to the session where the cron is created (`sessionTarget: "current"`).
- **Custom session**: run in a persistent named session (`sessionTarget: "session:custom-id"`).
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
- Webhook posting is per job via `delivery.mode = "webhook"` + `delivery.to = "<url>"`.
- Legacy fallback remains for stored jobs with `notify: true` when `cron.webhook` is set, migrate those jobs to webhook delivery mode.
- For upgrades, `openclaw doctor --fix` can normalize legacy cron store fields, including old top-level delivery hints such as `threadId`.
## Quick start (actionable)
Create a one-shot reminder, verify it exists, and run it immediately:
## Quick start
```bash
# Add a one-shot reminder
openclaw cron add \
--name "Reminder" \
--at "2026-02-01T16:00:00Z" \
@@ -51,12 +23,48 @@ openclaw cron add \
--wake now \
--delete-after-run
# Check your jobs
openclaw cron list
openclaw cron run <job-id>
# See run history
openclaw cron runs --id <job-id>
```
Schedule a recurring isolated job with delivery:
## How cron works
- Cron runs **inside the Gateway** process (not inside the model).
- Jobs persist at `~/.openclaw/cron/jobs.json` so restarts do not lose schedules.
- All cron executions create [background task](/automation/tasks) records.
- One-shot jobs (`--at`) auto-delete after success by default.
## Adding jobs
### 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.
### 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 \
@@ -70,246 +78,211 @@ openclaw cron add \
--to "channel:C1234567890"
```
## Tool-call equivalents (Gateway cron tool)
Isolated job with model and thinking override:
For the canonical JSON shapes and examples, see [JSON schema for tool calls](/automation/cron-jobs#json-schema-for-tool-calls).
```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
```
## Where cron jobs are stored
## Execution styles
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.
| 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 |
## Beginner-friendly overview
**Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). They use `payload.kind = "systemEvent"`.
Think of a cron job as: **when** to run + **what** to do.
**Isolated** jobs run a dedicated agent turn. Each run starts a fresh session (no carry-over) unless using a custom session. Default delivery is `announce` (summary to chat).
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**.
**Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries.
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>`.
- `sessionTarget: "current"` → bind to the current session (resolved at creation time to `session:<sessionKey>`).
- `sessionTarget: "session:custom-id"` → run in a persistent named session that maintains context across runs.
### Payload options for isolated jobs
Default behavior (unchanged):
- `systemEvent` payloads default to `main`
- `agentTurn` payloads default to `isolated`
- `--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
To use current session binding, explicitly set `sessionTarget: "current"`.
## Delivery and output
3. **Choose the payload**
- Main session → `payload.kind = "systemEvent"`
- Isolated session → `payload.kind = "agentTurn"`
| 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 |
Optional: one-shot jobs (`schedule.kind = "at"`) delete after success by default. Set
`deleteAfterRun: false` to keep them (they will disable after success).
Use `--announce --channel telegram --to "-1001234567890"` for channel delivery.
## Concepts
For Telegram forum topics, use `-1001234567890:topic:123`. Slack/Discord/Mattermost targets should use explicit prefixes (`channel:<id>`, `user:<id>`).
### Jobs
## Webhooks
A cron job is a stored record with:
Gateway can expose HTTP webhook endpoints for external triggers. Enable in config:
- a **schedule** (when it should run),
- a **payload** (what it should do),
- optional **delivery mode** (`announce`, `webhook`, or `none`).
- optional **agent binding** (`agentId`): run the job under a specific agent; if
missing or unknown, the gateway falls back to the default agent.
```json5
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
},
}
```
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
One-shot jobs auto-delete after success by default; set `deleteAfterRun: false` to keep them.
### Authentication
### Schedules
Every request must include the hook token via header:
Cron supports three schedule kinds:
- `Authorization: Bearer <token>` (recommended)
- `x-openclaw-token: <token>`
- `at`: one-shot timestamp via `schedule.at` (ISO 8601).
- `every`: fixed interval (ms).
- `cron`: 5-field cron expression (or 6-field with seconds) with optional IANA timezone.
Query-string tokens are rejected.
Cron expressions use `croner`. If a timezone is omitted, the Gateway hosts
local timezone is used.
### POST /hooks/wake
To reduce top-of-hour load spikes across many gateways, OpenClaw applies a
deterministic per-job stagger window of up to 5 minutes for recurring
top-of-hour expressions (for example `0 * * * *`, `0 */2 * * *`). Fixed-hour
expressions such as `0 7 * * *` remain exact.
Enqueue a system event for the main session:
For any cron schedule, you can set an explicit stagger window with `schedule.staggerMs`
(`0` keeps exact timing). CLI shortcuts:
```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"}'
```
- `--stagger 30s` (or `1m`, `5m`) to set an explicit stagger window.
- `--exact` to force `staggerMs = 0`.
- `text` (required): event description
- `mode` (optional): `now` (default) or `next-heartbeat`
### Main vs isolated execution
### POST /hooks/agent
#### Main session jobs (system events)
Run an isolated agent turn:
Main jobs enqueue a system event and optionally wake the heartbeat runner.
They must use `payload.kind = "systemEvent"`.
```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.2-mini"}'
```
- `wakeMode: "now"` (default): event triggers an immediate heartbeat run.
- `wakeMode: "next-heartbeat"`: event waits for the next scheduled heartbeat.
Fields: `message` (required), `name`, `agentId`, `wakeMode`, `deliver`, `channel`, `to`, `model`, `thinking`, `timeoutSeconds`.
This is the best fit when you want the normal heartbeat prompt + main-session context.
See [Heartbeat](/gateway/heartbeat).
### Mapped hooks (POST /hooks/\<name\>)
Main-session cron jobs create [background task](/automation/tasks) records with `silent` notify policy (no notifications by default). They appear in `openclaw tasks list` but do not generate delivery messages.
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.
#### Isolated jobs (dedicated cron sessions)
### Security
Isolated jobs run a dedicated agent turn in session `cron:<jobId>` or a custom session.
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
- Use a dedicated hook token; do not reuse gateway auth tokens.
- Set `hooks.allowedAgentIds` to limit explicit `agentId` routing.
- Keep `hooks.allowRequestSessionKey=false` unless you require caller-selected sessions.
- Hook payloads are wrapped with safety boundaries by default.
Key behaviors:
## Gmail PubSub integration
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
- Each run starts a **fresh session id** (no prior conversation carry-over), unless using a custom session.
- Custom sessions (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries.
- Default behavior: if `delivery` is omitted, isolated jobs announce a summary (`delivery.mode = "announce"`).
- `delivery.mode` chooses what happens:
- `announce`: deliver a summary to the target channel and post a brief summary to the main session.
- `webhook`: POST the finished event payload to `delivery.to` when the finished event includes a summary.
- `none`: internal only (no delivery, no main-session summary).
- `wakeMode` controls when the main-session summary posts:
- `now`: immediate heartbeat.
- `next-heartbeat`: waits for the next scheduled heartbeat.
Wire Gmail inbox triggers to OpenClaw via Google PubSub.
Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spam
your main chat history.
**Prerequisites**: `gcloud` CLI, `gog` (gogcli), OpenClaw hooks enabled, Tailscale for the public HTTPS endpoint.
These detached runs create [background task](/automation/tasks) records visible in `openclaw tasks` and subject to task audit and maintenance.
### Wizard setup (recommended)
### Payload shapes (what runs)
```bash
openclaw webhooks gmail setup --account openclaw@gmail.com
```
Two payload kinds are supported:
This writes `hooks.gmail` config, enables the Gmail preset, and uses Tailscale Funnel for the push endpoint.
- `systemEvent`: main-session only, routed through the heartbeat prompt.
- `agentTurn`: isolated-session only, runs a dedicated agent turn.
### Gateway auto-start
Common `agentTurn` fields:
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.
- `message`: required text prompt.
- `model` / `thinking`: optional overrides (see below).
- `timeoutSeconds`: optional timeout override.
- `lightContext`: optional lightweight bootstrap mode for jobs that do not need workspace bootstrap file injection.
- `toolsAllow`: optional array of tool names to restrict which tools the job can use (e.g. `["exec", "read", "write"]`).
### Manual one-time setup
Delivery config:
1. Select the GCP project that owns the OAuth client used by `gog`:
- `delivery.mode`: `none` | `announce` | `webhook`.
- `delivery.channel`: `last` or a specific channel.
- `delivery.to`: channel-specific target (announce) or webhook URL (webhook mode).
- `delivery.threadId`: optional explicit thread or topic id when the target channel supports threaded delivery.
- `delivery.bestEffort`: avoid failing the job if announce delivery fails.
```bash
gcloud auth login
gcloud config set project <project-id>
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
```
Announce delivery suppresses messaging tool sends for the run; use `delivery.channel`/`delivery.to`
to target the chat instead. When `delivery.mode = "none"`, no summary is posted to the main session.
2. Create topic and grant Gmail push access:
If `delivery` is omitted for isolated jobs, OpenClaw defaults to `announce`.
```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
```
#### Announce delivery flow
3. Start the watch:
When `delivery.mode = "announce"`, cron delivers directly via the outbound channel adapters.
The main agent is not spun up to craft or forward the message.
```bash
gog gmail watch start \
--account openclaw@gmail.com \
--label INBOX \
--topic projects/<project-id>/topics/gog-gmail-watch
```
Behavior details:
### Gmail model override
- Content: delivery uses the isolated run's outbound payloads (text/media) with normal chunking and
channel formatting.
- Heartbeat-only responses (`HEARTBEAT_OK` with no real content) are not delivered.
- If the isolated run already sent a message to the same target via the message tool, delivery is
skipped to avoid duplicates.
- Missing or invalid delivery targets fail the job unless `delivery.bestEffort = true`.
- A short summary is posted to the main session only when `delivery.mode = "announce"`.
- The main-session summary respects `wakeMode`: `now` triggers an immediate heartbeat and
`next-heartbeat` waits for the next scheduled heartbeat.
```json5
{
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
thinking: "off",
},
},
}
```
#### Webhook delivery flow
### Test
When `delivery.mode = "webhook"`, cron posts the finished event payload to `delivery.to` when the finished event includes a summary.
```bash
gog gmail send --account openclaw@gmail.com --to openclaw@gmail.com --subject "watch test" --body "ping"
gog gmail watch status --account openclaw@gmail.com
```
Behavior details:
## Managing jobs
- The endpoint must be a valid HTTP(S) URL.
- No channel delivery is attempted in webhook mode.
- No main-session summary is posted in webhook mode.
- If `cron.webhookToken` is set, auth header is `Authorization: Bearer <cron.webhookToken>`.
- Deprecated fallback: stored legacy jobs with `notify: true` still post to `cron.webhook` (if configured), with a warning so you can migrate to `delivery.mode = "webhook"`.
```bash
# List all jobs
openclaw cron list
### Model and thinking overrides
# Edit a job
openclaw cron edit <jobId> --message "Updated prompt" --model "opus"
Isolated jobs (`agentTurn`) can override the model and thinking level:
# Force run a job now
openclaw cron run <jobId>
- `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)
# Run only if due
openclaw cron run <jobId> --due
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.
# View run history
openclaw cron runs --id <jobId> --limit 50
Resolution priority:
# Delete a job
openclaw cron remove <jobId>
1. Job payload override (highest)
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
3. Agent config default
### Lightweight bootstrap context
Isolated jobs (`agentTurn`) can set `lightContext: true` to run with lightweight bootstrap context.
- Use this for scheduled chores that do not need workspace bootstrap file injection.
- In practice, the embedded runtime runs with `bootstrapContextMode: "lightweight"`, which keeps cron bootstrap context empty on purpose.
- CLI equivalents: `openclaw cron add --light-context ...` and `openclaw cron edit --light-context`.
### Delivery (channel + target)
Isolated jobs can deliver output to a channel via the top-level `delivery` config:
- `delivery.mode`: `announce` (channel delivery), `webhook` (HTTP POST), or `none`.
- `delivery.channel`: `last` or any deliverable channel id, for example `discord`, `matrix`, `telegram`, or `whatsapp`.
- `delivery.to`: channel-specific recipient target.
- `delivery.threadId`: optional thread/topic override for channels like Telegram, Slack, Discord, or Matrix when you want a specific thread without encoding it into `delivery.to`.
`announce` delivery is only valid for isolated jobs (`sessionTarget: "isolated"`).
`webhook` delivery is valid for both main and isolated jobs.
If `delivery.channel` or `delivery.to` is omitted, cron can fall back to the main sessions
“last route” (the last place the agent replied).
Target format reminders:
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) to avoid ambiguity.
Mattermost bare 26-char IDs are resolved **user-first** (DM if user exists, channel otherwise) — use `user:<id>` or `channel:<id>` for deterministic routing.
- Telegram topics should use the `:topic:` form (see below).
#### 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)
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
- `telegram:group:-1001234567890:topic:123`
# 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
```
## JSON schema for tool calls
Use these shapes when calling Gateway `cron.*` tools directly (agent tool calls or RPC).
CLI flags accept human durations like `20m`, but tool calls should use an ISO 8601 string
for `schedule.at` and milliseconds for `schedule.everyMs`.
### cron.add params
One-shot, main session job (system event):
One-shot main session job:
```json
{
@@ -322,440 +295,94 @@ One-shot, main session job (system event):
}
```
Recurring, isolated job with delivery:
Recurring isolated job with delivery:
```json
{
"name": "Morning brief",
"schedule": { "kind": "cron", "expr": "0 7 * * *", "tz": "America/Los_Angeles" },
"sessionTarget": "isolated",
"wakeMode": "next-heartbeat",
"payload": {
"kind": "agentTurn",
"message": "Summarize overnight updates.",
"lightContext": true
},
"delivery": {
"mode": "announce",
"channel": "slack",
"to": "channel:C1234567890",
"bestEffort": true
}
"payload": { "kind": "agentTurn", "message": "Summarize overnight updates." },
"delivery": { "mode": "announce", "channel": "slack", "to": "channel:C1234567890" }
}
```
Recurring job bound to current session (auto-resolved at creation):
```json
{
"name": "Daily standup",
"schedule": { "kind": "cron", "expr": "0 9 * * *" },
"sessionTarget": "current",
"payload": {
"kind": "agentTurn",
"message": "Summarize yesterday's progress."
}
}
```
Recurring job in a custom persistent session:
```json
{
"name": "Project monitor",
"schedule": { "kind": "every", "everyMs": 300000 },
"sessionTarget": "session:project-alpha-monitor",
"payload": {
"kind": "agentTurn",
"message": "Check project status and update the running log."
}
}
```
Notes:
- `schedule.kind`: `at` (`at`), `every` (`everyMs`), or `cron` (`expr`, optional `tz`).
- `schedule.at` accepts ISO 8601. Tool/API values without a timezone are treated as UTC; the CLI also accepts `openclaw cron add|edit --at "<offset-less-iso>" --tz <iana>` for local wall-clock one-shots.
- `everyMs` is milliseconds.
- `sessionTarget`: `"main"`, `"isolated"`, `"current"`, or `"session:<custom-id>"`.
- `"current"` is resolved to `"session:<sessionKey>"` at creation time.
- Custom sessions (`session:xxx`) maintain persistent context across runs.
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`),
`delivery`, `toolsAllow`.
- `toolsAllow`: optional array of tool names to restrict which tools the job can use (e.g. `["exec", "read"]`). Omit or set `null` to use all tools.
- `wakeMode` defaults to `"now"` when omitted.
### cron.update params
```json
{
"jobId": "job-123",
"patch": {
"enabled": false,
"schedule": { "kind": "every", "everyMs": 3600000 }
}
}
```
Notes:
- `jobId` is canonical; `id` is accepted for compatibility.
- Use `agentId: null` in the patch to clear an agent binding.
### cron.run and cron.remove params
```json
{ "jobId": "job-123", "mode": "force" }
```
```json
{ "jobId": "job-123" }
```
## Storage & history
- Job store: `~/.openclaw/cron/jobs.json` (Gateway-managed JSON).
- Run history: `~/.openclaw/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned by size and line count).
- Isolated cron run sessions in `sessions.json` are pruned by `cron.sessionRetention` (default `24h`; set `false` to disable).
- Override store path: `cron.store` in config.
## Retry policy
When a job fails, OpenClaw classifies errors as **transient** (retryable) or **permanent** (disable immediately).
### Transient errors (retried)
- Rate limit (429, too many requests, resource exhausted)
- Provider overload (for example Anthropic `529 overloaded_error`, overload fallback summaries)
- Network errors (timeout, ECONNRESET, fetch failed, socket)
- Server errors (5xx)
- Cloudflare-related errors
### Permanent errors (no retry)
- Auth failures (invalid API key, unauthorized)
- Config or validation errors
- Other non-transient errors
### Default behavior (no config)
**One-shot jobs (`schedule.kind: "at"`):**
- On transient error: retry up to 3 times with exponential backoff (30s → 1m → 5m).
- On permanent error: disable immediately.
- On success or skip: disable (or delete if `deleteAfterRun: true`).
**Recurring jobs (`cron` / `every`):**
- On any error: apply exponential backoff (30s → 1m → 5m → 15m → 60m) before the next scheduled run.
- Job stays enabled; backoff resets after the next successful run.
Configure `cron.retry` to override these defaults (see [Configuration](/automation/cron-jobs#configuration)).
## Configuration
```json5
{
cron: {
enabled: true, // default true
enabled: true,
store: "~/.openclaw/cron/jobs.json",
maxConcurrentRuns: 1, // default 1
// Optional: override retry policy for one-shot jobs
maxConcurrentRuns: 1,
retry: {
maxAttempts: 3,
backoffMs: [60000, 120000, 300000],
retryOn: ["rate_limit", "overloaded", "network", "server_error"],
},
webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
webhookToken: "replace-with-dedicated-webhook-token", // optional bearer token for webhook mode
sessionRetention: "24h", // duration string or false
runLog: {
maxBytes: "2mb", // default 2_000_000 bytes
keepLines: 2000, // default 2000
},
webhookToken: "replace-with-dedicated-webhook-token",
sessionRetention: "24h",
runLog: { maxBytes: "2mb", keepLines: 2000 },
},
}
```
Run-log pruning behavior:
Disable cron: `cron.enabled: false` or `OPENCLAW_SKIP_CRON=1`.
- `cron.runLog.maxBytes`: max run-log file size before pruning.
- `cron.runLog.keepLines`: when pruning, keep only the newest N lines.
- Both apply to `cron/runs/<jobId>.jsonl` files.
### Retry policy
Webhook behavior:
**One-shot jobs**: retry transient errors (rate limit, overload, network, server error) up to 3 times with exponential backoff. Permanent errors disable immediately.
- Preferred: set `delivery.mode: "webhook"` with `delivery.to: "https://..."` per job.
- Webhook URLs must be valid `http://` or `https://` URLs.
- When posted, payload is the cron finished event JSON.
- If `cron.webhookToken` is set, auth header is `Authorization: Bearer <cron.webhookToken>`.
- If `cron.webhookToken` is not set, no `Authorization` header is sent.
- Deprecated fallback: stored legacy jobs with `notify: true` still use `cron.webhook` when present.
**Recurring jobs**: exponential backoff (30s to 60m) between retries. Backoff resets after the next successful run.
Disable cron entirely:
### Maintenance
- `cron.enabled: false` (config)
- `OPENCLAW_SKIP_CRON=1` (env)
## Maintenance
Cron has two built-in maintenance paths: isolated run-session retention and run-log pruning.
### Defaults
- `cron.sessionRetention`: `24h` (set `false` to disable run-session pruning)
- `cron.runLog.maxBytes`: `2_000_000` bytes
- `cron.runLog.keepLines`: `2000`
### How it works
- Isolated runs create session entries (`...:cron:<jobId>:run:<uuid>`) and transcript files.
- The reaper removes expired run-session entries older than `cron.sessionRetention`.
- For removed run sessions no longer referenced by the session store, OpenClaw archives transcript files and purges old deleted archives on the same retention window.
- After each run append, `cron/runs/<jobId>.jsonl` is size-checked:
- if file size exceeds `runLog.maxBytes`, it is trimmed to the newest `runLog.keepLines` lines.
### Performance caveat for high volume schedulers
High-frequency cron setups can generate large run-session and run-log footprints. Maintenance is built in, but loose limits can still create avoidable IO and cleanup work.
What to watch:
- long `cron.sessionRetention` windows with many isolated runs
- high `cron.runLog.keepLines` combined with large `runLog.maxBytes`
- many noisy recurring jobs writing to the same `cron/runs/<jobId>.jsonl`
What to do:
- keep `cron.sessionRetention` as short as your debugging/audit needs allow
- keep run logs bounded with moderate `runLog.maxBytes` and `runLog.keepLines`
- move noisy background jobs to isolated mode with delivery rules that avoid unnecessary chatter
- review growth periodically with `openclaw cron runs` and adjust retention before logs become large
### Customize examples
Keep run sessions for a week and allow bigger run logs:
```json5
{
cron: {
sessionRetention: "7d",
runLog: {
maxBytes: "10mb",
keepLines: 5000,
},
},
}
```
Disable isolated run-session pruning but keep run-log pruning:
```json5
{
cron: {
sessionRetention: false,
runLog: {
maxBytes: "5mb",
keepLines: 3000,
},
},
}
```
Tune for high-volume cron usage (example):
```json5
{
cron: {
sessionRetention: "12h",
runLog: {
maxBytes: "3mb",
keepLines: 1500,
},
},
}
```
## CLI quickstart
One-shot reminder (UTC ISO, auto-delete after success):
```bash
openclaw cron add \
--name "Send reminder" \
--at "2026-01-12T18:00:00Z" \
--session main \
--system-event "Reminder: submit expense report." \
--wake now \
--delete-after-run
```
One-shot reminder (main session, wake immediately):
```bash
openclaw cron add \
--name "Calendar check" \
--at "20m" \
--session main \
--system-event "Next heartbeat: check calendar." \
--wake now
```
Recurring isolated job (announce to WhatsApp):
```bash
openclaw cron add \
--name "Morning status" \
--cron "0 7 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize inbox + calendar for today." \
--announce \
--channel whatsapp \
--to "+15551234567"
```
Recurring cron job with explicit 30-second stagger:
```bash
openclaw cron add \
--name "Minute watcher" \
--cron "0 * * * * *" \
--tz "UTC" \
--stagger 30s \
--session isolated \
--message "Run minute watcher checks." \
--announce
```
Recurring isolated job (deliver to a Telegram topic):
```bash
openclaw cron add \
--name "Nightly summary (topic)" \
--cron "0 22 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize today; send to the nightly topic." \
--announce \
--channel telegram \
--to "-1001234567890:topic:123"
```
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 \
--channel whatsapp \
--to "+15551234567"
```
Agent selection (multi-agent setups):
```bash
# Pin a job to agent "ops" (falls back to default if that agent is missing)
openclaw 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
openclaw cron edit <jobId> --agent ops
openclaw cron edit <jobId> --clear-agent
```
Tool allowlists (restrict which tools a job can use):
```bash
# Only allow exec and read tools for this job
openclaw cron add --name "Scoped job" --cron "0 8 * * *" --session isolated --message "Run scoped checks" --tools exec,read
# Update an existing job's tool allowlist
openclaw cron edit <jobId> --tools exec,read,write
# Remove a tool allowlist (use all tools)
openclaw cron edit <jobId> --clear-tools
```
Manual run (force is the default, use `--due` to only run when due):
```bash
openclaw cron run <jobId>
openclaw cron run <jobId> --due
```
`cron.run` now acknowledges once the manual run is queued, not after the job finishes. Successful queue responses look like `{ ok: true, enqueued: true, runId }`. If the job is already running or `--due` finds nothing due, the response stays `{ ok: true, ran: false, reason }`. Use `openclaw cron runs --id <jobId>` or the `cron.runs` gateway method to inspect the eventual finished entry.
Edit an existing job (patch fields):
```bash
openclaw cron edit <jobId> \
--message "Updated prompt" \
--model "opus" \
--thinking low
```
Force an existing cron job to run exactly on schedule (no stagger):
```bash
openclaw cron edit <jobId> --exact
```
Run history:
```bash
openclaw cron runs --id <jobId> --limit 50
```
Immediate system event without creating a job:
```bash
openclaw system event --mode now --text "Next heartbeat: check battery."
```
## Gateway API surface
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
- `cron.run` (force or due), `cron.runs`
For immediate system events without a job, use [`openclaw system event`](/cli/system).
- `cron.sessionRetention` (default `24h`): prune isolated run-session entries.
- `cron.runLog.maxBytes` / `cron.runLog.keepLines`: auto-prune run-log files.
## Troubleshooting
### "Nothing runs"
### Command ladder
- Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`.
- Check the Gateway is running continuously (cron runs inside the Gateway process).
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
```bash
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
```
### A recurring job keeps delaying after failures
### Cron not firing
- OpenClaw applies exponential retry backoff for recurring jobs after consecutive errors:
30s, 1m, 5m, 15m, then 60m between retries.
- Backoff resets automatically after the next successful run.
- One-shot (`at`) jobs retry transient errors (rate limit, overloaded, network, server_error) up to 3 times with backoff; permanent errors disable immediately. See [Retry policy](/automation/cron-jobs#retry-policy).
- 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 called without `--force`.
### Telegram delivers to the wrong place
### Cron fired but no delivery
- For forum topics, use `-100…:topic:<id>` so its explicit and unambiguous.
- If you see `telegram:...` prefixes in logs or stored “last route” targets, thats normal;
cron delivery accepts them and still parses topic IDs correctly.
- Run succeeded but delivery mode is `none` means 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.
### Subagent announce delivery retries
### Heartbeat suppressed or skipped
- When a subagent run completes, the gateway announces the result to the requester session.
- If the announce flow returns `false` (e.g. requester session is busy), the gateway retries up to 3 times with tracking via `announceRetryCount`.
- Announces older than 5 minutes past `endedAt` are force-expired to prevent stale entries from looping indefinitely.
- If you see repeated announce deliveries in logs, check the subagent registry for entries with high `announceRetryCount` values.
- `reason=quiet-hours`: outside `activeHours`.
- `requests-in-flight`: main lane busy, heartbeat deferred.
- `empty-heartbeat-file`: `HEARTBEAT.md` has no actionable content and no cron event is queued.
### Timezone gotchas
- Cron without `--tz` uses the gateway host timezone.
- `at` schedules without timezone are treated as UTC.
- Heartbeat `activeHours` uses configured timezone resolution.
## Related
- [Automation Overview](/automation) — all automation mechanisms at a glance
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — when to use each
- [Automation & Tasks](/automation) — all automation mechanisms at a glance
- [Background Tasks](/automation/tasks) — task ledger for cron executions
- [Heartbeat](/gateway/heartbeat) — periodic main-session turns
- [Troubleshooting](/automation/troubleshooting) — debugging automation issues
- [Timezone](/concepts/timezone) — timezone configuration

View File

@@ -1,299 +1,8 @@
---
summary: "Guidance for choosing between heartbeat and cron jobs for automation"
read_when:
- Deciding how to schedule recurring tasks
- Setting up background monitoring or notifications
- Optimizing token usage for periodic checks
summary: "Redirect to /automation"
title: "Cron vs Heartbeat"
---
# Cron vs Heartbeat: When to Use Each
# Cron vs Heartbeat
Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case.
One important distinction:
- **Heartbeat** is a scheduled **main-session turn** — no task record created.
- **Cron (main)** is a scheduled **system event into the main session** — creates a task record with `silent` notify policy.
- **Cron (isolated)** is a scheduled **background run** — creates a task record tracked in `openclaw tasks`.
All cron job executions (main and isolated) create [task records](/automation/tasks). Heartbeat turns do not. Main-session cron tasks use `silent` notify policy by default so they do not generate notifications.
## Quick Decision Guide
| Use Case | Recommended | Why |
| ------------------------------------ | ------------------- | ---------------------------------------- |
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
| Background project health check | Heartbeat | Piggybacks on existing cycle |
## Heartbeat: Periodic Awareness
Heartbeats run in the **main session** at a regular interval (default: 30 min). They're designed for the agent to check on things and surface anything important.
### When to use heartbeat
- **Multiple periodic checks**: Instead of 5 separate cron jobs checking inbox, calendar, weather, notifications, and project status, a single heartbeat can batch all of these.
- **Context-aware decisions**: The agent has full main-session context, so it can make smart decisions about what's urgent vs. what can wait.
- **Conversational continuity**: Heartbeat runs share the same session, so the agent remembers recent conversations and can follow up naturally.
- **Low-overhead monitoring**: One heartbeat replaces many small polling tasks.
### Heartbeat advantages
- **Batches multiple checks**: One agent turn can review inbox, calendar, and notifications together.
- **Reduces API calls**: A single heartbeat is cheaper than 5 isolated cron jobs.
- **Context-aware**: The agent knows what you've been working on and can prioritize accordingly.
- **Smart suppression**: If nothing needs attention, the agent replies `HEARTBEAT_OK` and no message is delivered.
- **Natural timing**: Drifts slightly based on queue load, which is fine for most monitoring.
- **No task record**: heartbeat turns stay in main-session history (see [Background Tasks](/automation/tasks)).
### Heartbeat example: HEARTBEAT.md checklist
```md
# Heartbeat checklist
- Check email for urgent messages
- Review calendar for events in next 2 hours
- If a background task finished, summarize results
- If idle for 8+ hours, send a brief check-in
```
The agent reads this on each heartbeat and handles all items in one turn.
### Configuring heartbeat
```json5
{
agents: {
defaults: {
heartbeat: {
every: "30m", // interval
target: "last", // explicit alert delivery target (default is "none")
activeHours: { start: "08:00", end: "22:00" }, // optional
},
},
},
}
```
See [Heartbeat](/gateway/heartbeat) for full configuration.
## Cron: Precise Scheduling
Cron jobs run at precise times and can run in isolated sessions without affecting main context.
Recurring top-of-hour schedules are automatically spread by a deterministic
per-job offset in a 0-5 minute window.
### When to use cron
- **Exact timing required**: "Send this at 9:00 AM every Monday" (not "sometime around 9").
- **Standalone tasks**: Tasks that don't need conversational context.
- **Different model/thinking**: Heavy analysis that warrants a more powerful model.
- **One-shot reminders**: "Remind me in 20 minutes" with `--at`.
- **Noisy/frequent tasks**: Tasks that would clutter main session history.
- **External triggers**: Tasks that should run independently of whether the agent is otherwise active.
### Cron advantages
- **Precise timing**: 5-field or 6-field (seconds) cron expressions with timezone support.
- **Built-in load spreading**: recurring top-of-hour schedules are staggered by up to 5 minutes by default.
- **Per-job control**: override stagger with `--stagger <duration>` or force exact timing with `--exact`.
- **Session isolation**: Runs in `cron:<jobId>` without polluting main history.
- **Model overrides**: Use a cheaper or more powerful model per job.
- **Delivery control**: Isolated jobs default to `announce` (summary); choose `none` as needed.
- **Immediate delivery**: Announce mode posts directly without waiting for heartbeat.
- **No agent context needed**: Runs even if main session is idle or compacted.
- **One-shot support**: `--at` for precise future timestamps.
- **Task tracking**: isolated jobs create [background task](/automation/tasks) records visible in `openclaw tasks` and `openclaw tasks audit`.
### Cron example: Daily morning briefing
```bash
openclaw cron add \
--name "Morning briefing" \
--cron "0 7 * * *" \
--tz "America/New_York" \
--session isolated \
--message "Generate today's briefing: weather, calendar, top emails, news summary." \
--model opus \
--announce \
--channel whatsapp \
--to "+15551234567"
```
This runs at exactly 7:00 AM New York time, uses Opus for quality, and announces a summary directly to WhatsApp.
### Cron example: One-shot reminder
```bash
openclaw cron add \
--name "Meeting reminder" \
--at "20m" \
--session main \
--system-event "Reminder: standup meeting starts in 10 minutes." \
--wake now \
--delete-after-run
```
See [Cron jobs](/automation/cron-jobs) for full CLI reference.
## Decision Flowchart
```
Does the task need to run at an EXACT time?
YES -> Use cron
NO -> Continue...
Does the task need isolation from main session?
YES -> Use cron (isolated)
NO -> Continue...
Can this task be batched with other periodic checks?
YES -> Use heartbeat (add to HEARTBEAT.md)
NO -> Use cron
Is this a one-shot reminder?
YES -> Use cron with --at
NO -> Continue...
Does it need a different model or thinking level?
YES -> Use cron (isolated) with --model/--thinking
NO -> Use heartbeat
```
## Combining Both
The most efficient setup uses **both**:
1. **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
2. **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders.
### Example: Efficient automation setup
**HEARTBEAT.md** (checked every 30 min):
```md
# Heartbeat checklist
- Scan inbox for urgent emails
- Check calendar for events in next 2h
- Review any pending tasks
- Light check-in if quiet for 8+ hours
```
**Cron jobs** (precise timing):
```bash
# Daily morning briefing at 7am
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --announce
# Weekly project review on Mondays at 9am
openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated --message "..." --model opus
# One-shot reminder
openclaw cron add --name "Call back" --at "2h" --session main --system-event "Call back the client" --wake now
```
## Lobster: Deterministic workflows with approvals
Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals.
Use it when the task is more than a single agent turn, and you want a resumable workflow with human checkpoints.
### When Lobster fits
- **Multi-step automation**: You need a fixed pipeline of tool calls, not a one-off prompt.
- **Approval gates**: Side effects should pause until you approve, then resume.
- **Resumable runs**: Continue a paused workflow without re-running earlier steps.
### How it pairs with heartbeat and cron
- **Heartbeat/cron** decide _when_ a run happens.
- **Lobster** defines _what steps_ happen once the run starts.
For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster.
For ad-hoc workflows, call Lobster directly.
### Operational notes (from the code)
- Lobster runs as a **local subprocess** (`lobster` CLI) in tool mode and returns a **JSON envelope**.
- If the tool returns `needs_approval`, you resume with a `resumeToken` and `approve` flag.
- The tool is an **optional plugin**; enable it additively via `tools.alsoAllow: ["lobster"]` (recommended).
- Lobster expects the `lobster` CLI to be available on `PATH`.
See [Lobster](/tools/lobster) for full usage and examples.
## Main Session vs Isolated Session
Both heartbeat and cron can interact with the main session, but differently:
| | Heartbeat | Cron (main) | Cron (isolated) |
| -------------------------- | ------------------------------- | ------------------------ | ----------------------------------------------- |
| Session | Main | Main (via system event) | `cron:<jobId>` or custom session |
| History | Shared | Shared | Fresh each run (isolated) / Persistent (custom) |
| Context | Full | Full | None (isolated) / Cumulative (custom) |
| Model | Main session model | Main session model | Can override |
| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Announce summary (default) |
| [Tasks](/automation/tasks) | No task record | Task record (silent) | Task record (visible in `openclaw tasks`) |
### When to use main session cron
Use `--session main` with `--system-event` when you want:
- The reminder/event to appear in main session context
- The agent to handle it during the next heartbeat with full context
- No separate isolated run
```bash
openclaw cron add \
--name "Check project" \
--every "4h" \
--session main \
--system-event "Time for a project health check" \
--wake now
```
### When to use isolated cron
Use `--session isolated` when you want:
- A clean slate without prior context
- Different model or thinking settings
- Announce summaries directly to a channel
- History that doesn't clutter main session
```bash
openclaw cron add \
--name "Deep analysis" \
--cron "0 6 * * 0" \
--session isolated \
--message "Weekly codebase analysis..." \
--model opus \
--thinking high \
--announce
```
## Cost Considerations
| Mechanism | Cost Profile |
| --------------- | ------------------------------------------------------- |
| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
| Cron (main) | Adds event to next heartbeat (no isolated turn) |
| Cron (isolated) | Full agent turn per job; can use cheaper model |
**Tips**:
- Keep `HEARTBEAT.md` small to minimize token overhead.
- Batch similar checks into heartbeat instead of multiple cron jobs.
- Use `target: "none"` on heartbeat if you only want internal processing.
- Use isolated cron with a cheaper model for routine tasks.
## Related
- [Automation Overview](/automation) — all automation mechanisms at a glance
- [Heartbeat](/gateway/heartbeat) — full heartbeat configuration
- [Cron jobs](/automation/cron-jobs) — full cron CLI and API reference
- [Background Tasks](/automation/tasks) — task ledger, audit, and lifecycle
- [System](/cli/system) — system events + heartbeat controls
This page moved to [Automation & Tasks](/automation). See [Automation & Tasks](/automation) for the decision guide comparing cron and heartbeat.

View File

@@ -1,256 +1,8 @@
---
summary: "Gmail Pub/Sub push wired into OpenClaw webhooks via gogcli"
read_when:
- Wiring Gmail inbox triggers to OpenClaw
- Setting up Pub/Sub push for agent wake
summary: "Redirect to /automation/cron-jobs"
title: "Gmail PubSub"
---
# Gmail Pub/Sub -> OpenClaw
# Gmail PubSub
Goal: Gmail watch -> Pub/Sub push -> `gog gmail watch serve` -> OpenClaw webhook.
## Prereqs
- `gcloud` installed and logged in ([install guide](https://docs.cloud.google.com/sdk/docs/install-sdk)).
- `gog` (gogcli) installed and authorized for the Gmail account ([gogcli.sh](https://gogcli.sh/)).
- OpenClaw hooks enabled (see [Webhooks](/automation/webhook)).
- `tailscale` logged in ([tailscale.com](https://tailscale.com/)). Supported setup uses Tailscale Funnel for the public HTTPS endpoint.
Other tunnel services can work, but are DIY/unsupported and require manual wiring.
Right now, Tailscale is what we support.
Example hook config (enable Gmail preset mapping):
```json5
{
hooks: {
enabled: true,
token: "OPENCLAW_HOOK_TOKEN",
path: "/hooks",
presets: ["gmail"],
},
}
```
To deliver the Gmail summary to a chat surface, override the preset with a mapping
that sets `deliver` + optional `channel`/`to`:
```json5
{
hooks: {
enabled: true,
token: "OPENCLAW_HOOK_TOKEN",
presets: ["gmail"],
mappings: [
{
match: { path: "gmail" },
action: "agent",
wakeMode: "now",
name: "Gmail",
sessionKey: "hook:gmail:{{messages[0].id}}",
messageTemplate: "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
model: "openai/gpt-5.2-mini",
deliver: true,
channel: "last",
// to: "+15551234567"
},
],
},
}
```
If you want a fixed channel, set `channel` + `to`. Otherwise `channel: "last"`
uses the last delivery route (falls back to WhatsApp).
To force a cheaper model for Gmail runs, set `model` in the mapping
(`provider/model` or alias). If you enforce `agents.defaults.models`, include it there.
To set a default model and thinking level specifically for Gmail hooks, add
`hooks.gmail.model` / `hooks.gmail.thinking` in your config:
```json5
{
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
thinking: "off",
},
},
}
```
Notes:
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
- Fallback order: `hooks.gmail.model``agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
- Gmail hook content is wrapped with external-content safety boundaries by default.
To disable (dangerous), set `hooks.gmail.allowUnsafeExternalContent: true`.
To customize payload handling further, add `hooks.mappings` or a JS/TS transform module
under `~/.openclaw/hooks/transforms` (see [Webhooks](/automation/webhook)).
## Wizard (recommended)
Use the OpenClaw helper to wire everything together (installs deps on macOS via brew):
```bash
openclaw webhooks gmail setup \
--account openclaw@gmail.com
```
Defaults:
- Uses Tailscale Funnel for the public push endpoint.
- Writes `hooks.gmail` config for `openclaw webhooks gmail run`.
- Enables the Gmail hook preset (`hooks.presets: ["gmail"]`).
Path note: when `tailscale.mode` is enabled, OpenClaw automatically sets
`hooks.gmail.serve.path` to `/` and keeps the public path at
`hooks.gmail.tailscale.path` (default `/gmail-pubsub`) because Tailscale
strips the set-path prefix before proxying.
If you need the backend to receive the prefixed path, set
`hooks.gmail.tailscale.target` (or `--tailscale-target`) to a full URL like
`http://127.0.0.1:8788/gmail-pubsub` and match `hooks.gmail.serve.path`.
Want a custom endpoint? Use `--push-endpoint <url>` or `--tailscale off`.
Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`
via Homebrew; on Linux install them manually first.
Gateway auto-start (recommended):
- 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 (useful if you run the daemon yourself).
- Do not run the manual daemon at the same time, or you will hit
`listen tcp 127.0.0.1:8788: bind: address already in use`.
Manual daemon (starts `gog gmail watch serve` + auto-renew):
```bash
openclaw webhooks gmail run
```
## One-time setup
1. Select the GCP project **that owns the OAuth client** used by `gog`.
```bash
gcloud auth login
gcloud config set project <project-id>
```
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
2. Enable APIs:
```bash
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
```
3. Create a topic:
```bash
gcloud pubsub topics create gog-gmail-watch
```
4. Allow Gmail push to publish:
```bash
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
```bash
gog gmail watch start \
--account openclaw@gmail.com \
--label INBOX \
--topic projects/<project-id>/topics/gog-gmail-watch
```
Save the `history_id` from the output (for debugging).
## Run the push handler
Local example (shared token auth):
```bash
gog gmail watch serve \
--account openclaw@gmail.com \
--bind 127.0.0.1 \
--port 8788 \
--path /gmail-pubsub \
--token <shared> \
--hook-url http://127.0.0.1:18789/hooks/gmail \
--hook-token OPENCLAW_HOOK_TOKEN \
--include-body \
--max-bytes 20000
```
Notes:
- `--token` protects the push endpoint (`x-gog-token` or `?token=`).
- `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main).
- `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw.
Recommended: `openclaw webhooks gmail run` wraps the same flow and auto-renews the watch.
## Expose the handler (advanced, unsupported)
If you need a non-Tailscale tunnel, wire it manually and use the public URL in the push
subscription (unsupported, no guardrails):
```bash
cloudflared tunnel --url http://127.0.0.1:8788 --no-autoupdate
```
Use the generated URL as the push endpoint:
```bash
gcloud pubsub subscriptions create gog-gmail-watch-push \
--topic gog-gmail-watch \
--push-endpoint "https://<public-url>/gmail-pubsub?token=<shared>"
```
Production: use a stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:
```bash
gog gmail watch serve --verify-oidc --oidc-email <svc@...>
```
## Test
Send a message to the watched inbox:
```bash
gog gmail send \
--account openclaw@gmail.com \
--to openclaw@gmail.com \
--subject "watch test" \
--body "ping"
```
Check watch state and history:
```bash
gog gmail watch status --account openclaw@gmail.com
gog gmail history --account openclaw@gmail.com --since <historyId>
```
## Troubleshooting
- `Invalid topicName`: project mismatch (topic not in the OAuth client project).
- `User not authorized`: missing `roles/pubsub.publisher` on the topic.
- Empty messages: Gmail push only provides `historyId`; fetch via `gog gmail history`.
## Cleanup
```bash
gog gmail watch stop --account openclaw@gmail.com
gcloud pubsub subscriptions delete gog-gmail-watch-push
gcloud pubsub topics delete gog-gmail-watch
```
This page moved to [Scheduled Tasks](/automation/cron-jobs#gmail-pubsub-integration). See [Scheduled Tasks](/automation/cron-jobs#gmail-pubsub-integration) for Gmail PubSub documentation.

View File

@@ -15,7 +15,7 @@ Hooks provide an extensible event-driven system for automating actions in respon
Hooks are small scripts that run when something happens. There are two kinds:
- **Hooks** (this page): run inside the Gateway when agent events fire, like `/new`, `/reset`, `/stop`, or lifecycle events.
- **Webhooks**: external HTTP webhooks that let other systems trigger work in OpenClaw. See [Webhook Hooks](/automation/webhook) or use `openclaw webhooks` for Gmail helper commands.
- **Webhooks**: external HTTP webhooks that let other systems trigger work in OpenClaw. See [Webhooks](/automation/cron-jobs#webhooks) or use `openclaw webhooks` for Gmail helper commands.
Hooks can also be bundled inside plugins; see [Plugin hooks](/plugins/architecture#provider-runtime-hooks). `openclaw hooks list` shows both standalone hooks and plugin-managed hooks.
@@ -1372,5 +1372,5 @@ node -e "import('./path/to/handler.ts').then(console.log)"
- [CLI Reference: hooks](/cli/hooks)
- [Bundled Hooks README](https://github.com/openclaw/openclaw/tree/main/src/hooks/bundled)
- [Webhook Hooks](/automation/webhook)
- [Webhooks](/automation/cron-jobs#webhooks)
- [Configuration](/gateway/configuration-reference#hooks)

View File

@@ -1,15 +1,15 @@
---
summary: "Overview of all automation mechanisms: heartbeat, cron, tasks, hooks, webhooks, and more"
summary: "Overview of automation mechanisms: tasks, cron, hooks, standing orders, and TaskFlow"
read_when:
- Deciding how to automate work with OpenClaw
- Choosing between heartbeat, cron, hooks, and webhooks
- Choosing between heartbeat, cron, hooks, and standing orders
- Looking for the right automation entry point
title: "Automation Overview"
title: "Automation & Tasks"
---
# Automation
# Automation & Tasks
OpenClaw provides several automation mechanisms, each suited to different use cases. This page helps you choose the right one.
OpenClaw runs work in the background through tasks, scheduled jobs, event hooks, and standing instructions. This page helps you choose the right mechanism and understand how they fit together.
## Quick decision guide
@@ -23,44 +23,65 @@ flowchart TD
C -->|No| G[Standing Orders]
```
## Mechanisms at a glance
| Use case | Recommended | Why |
| ------------------------------------ | ------------------- | ---------------------------------------- |
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
| Send daily report at 9 AM sharp | Cron (isolated) | Exact timing needed |
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
| React to commands or lifecycle | Hooks | Event-driven, runs custom scripts |
| Persistent agent instructions | Standing orders | Injected into every session |
| Mechanism | What it does | Runs in | Creates task record |
| ---------------------------------------------- | -------------------------------------------------------- | ------------------------ | ------------------- |
| [Heartbeat](/gateway/heartbeat) | Periodic main-session turn — batches multiple checks | Main session | No |
| [Cron](/automation/cron-jobs) | Scheduled jobs with precise timing | Main or isolated session | Yes (all types) |
| [Background Tasks](/automation/tasks) | Tracks detached work (cron, ACP, subagents, CLI) | N/A (ledger) | N/A |
| [Hooks](/automation/hooks) | Event-driven scripts triggered by agent lifecycle events | Hook runner | No |
| [Standing Orders](/automation/standing-orders) | Persistent instructions injected into the system prompt | Main session | No |
| [Webhooks](/automation/webhook) | Receive inbound HTTP events and route to the agent | Gateway HTTP | No |
## Core concepts
### Specialized automation
### Tasks
| Mechanism | What it does |
| ---------------------------------------------- | ----------------------------------------------- |
| [Gmail PubSub](/automation/gmail-pubsub) | Real-time Gmail notifications via Google PubSub |
| [Polling](/automation/poll) | Periodic data source checks (RSS, APIs, etc.) |
| [Auth Monitoring](/automation/auth-monitoring) | Credential health and expiry alerts |
The background task ledger tracks all detached work: ACP runs, subagent spawns, isolated cron executions, and CLI operations. Tasks are records, not schedulers. Use `openclaw tasks list` and `openclaw tasks audit` to inspect them.
See [Background Tasks](/automation/tasks).
### Scheduled tasks (cron)
Cron is the Gateway's built-in scheduler for precise timing. It persists jobs, wakes the agent at the right time, and can deliver output to a chat channel or webhook. Supports one-shot reminders, recurring expressions, and inbound webhook triggers.
See [Scheduled Tasks](/automation/cron-jobs).
### TaskFlow
TaskFlow is the flow orchestration substrate above background tasks. It manages durable multi-step flows with managed and mirrored sync modes, revision tracking, and `openclaw tasks flow list|show|cancel` for inspection.
See [TaskFlow](/automation/taskflow).
### Heartbeat
Heartbeat is a periodic main-session turn (default every 30 minutes). It batches multiple checks (inbox, calendar, notifications) in one agent turn with full session context. Heartbeat turns do not create task records. Use `HEARTBEAT.md` to define what the agent checks.
See [Heartbeat](/gateway/heartbeat).
### Hooks
Hooks are event-driven scripts triggered by agent lifecycle events (`/new`, `/reset`, `/stop`), session compaction, gateway startup, message flow, and tool calls. Hooks are automatically discovered from directories and can be managed with `openclaw hooks`.
See [Hooks](/automation/hooks).
### Standing orders
Standing orders grant the agent permanent operating authority for defined programs. They live in workspace files (typically `AGENTS.md`) and are injected into every session. Combine with cron for time-based enforcement.
See [Standing Orders](/automation/standing-orders).
## How they work together
The most effective setups combine multiple mechanisms:
1. **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
2. **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders.
3. **Hooks** react to specific events (tool calls, session resets, compaction) with custom scripts.
4. **Standing Orders** give the agent persistent context ("always check the project board before replying").
5. **Background Tasks** automatically track all detached work so you can inspect and audit it.
See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for a detailed comparison of the two scheduling mechanisms.
## TaskFlow
[TaskFlow](/automation/taskflow) is the flow orchestration substrate above background tasks. It manages durable multi-step flows with managed and mirrored sync modes, and exposes `openclaw tasks flow list|show|cancel` for inspection and recovery. See [TaskFlow](/automation/taskflow) for details.
- **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
- **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders. All cron executions create task records.
- **Hooks** react to specific events (tool calls, session resets, compaction) with custom scripts.
- **Standing orders** give the agent persistent context and authority boundaries.
- **TaskFlow** coordinates multi-step flows above individual tasks.
- **Tasks** automatically track all detached work so you can inspect and audit it.
## Related
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — detailed comparison guide
- [TaskFlow](/automation/taskflow) — flow orchestration above tasks
- [Troubleshooting](/automation/troubleshooting) — debugging automation issues
- [Heartbeat](/gateway/heartbeat) — periodic main-session turns
- [Configuration Reference](/gateway/configuration-reference) — all config keys

View File

@@ -1,86 +1,8 @@
---
summary: "Poll sending via gateway + CLI"
read_when:
- Adding or modifying poll support
- Debugging poll sends from the CLI or gateway
summary: "Redirect to /tools/message"
title: "Polls"
---
# Polls
## Supported channels
- Telegram
- WhatsApp (web channel)
- Discord
- Microsoft Teams (Adaptive Cards)
## CLI
```bash
# Telegram
openclaw message poll --channel telegram --target 123456789 \
--poll-question "Ship it?" --poll-option "Yes" --poll-option "No"
openclaw message poll --channel telegram --target -1001234567890:topic:42 \
--poll-question "Pick a time" --poll-option "10am" --poll-option "2pm" \
--poll-duration-seconds 300
# WhatsApp
openclaw message poll --target +15555550123 \
--poll-question "Lunch today?" --poll-option "Yes" --poll-option "No" --poll-option "Maybe"
openclaw message poll --target 123456789@g.us \
--poll-question "Meeting time?" --poll-option "10am" --poll-option "2pm" --poll-option "4pm" --poll-multi
# Discord
openclaw message poll --channel discord --target channel:123456789 \
--poll-question "Snack?" --poll-option "Pizza" --poll-option "Sushi"
openclaw message poll --channel discord --target channel:123456789 \
--poll-question "Plan?" --poll-option "A" --poll-option "B" --poll-duration-hours 48
# Microsoft Teams
openclaw message poll --channel msteams --target conversation:19:abc@thread.tacv2 \
--poll-question "Lunch?" --poll-option "Pizza" --poll-option "Sushi"
```
Options:
- `--channel`: `whatsapp` (default), `telegram`, `discord`, or `msteams`
- `--poll-multi`: allow selecting multiple options
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
- `--poll-duration-seconds`: Telegram-only (5-600 seconds)
- `--poll-anonymous` / `--poll-public`: Telegram-only poll visibility
## Gateway RPC
Method: `poll`
Params:
- `to` (string, required)
- `question` (string, required)
- `options` (string[], required)
- `maxSelections` (number, optional)
- `durationHours` (number, optional)
- `durationSeconds` (number, optional, Telegram-only)
- `isAnonymous` (boolean, optional, Telegram-only)
- `channel` (string, optional, default: `whatsapp`)
- `idempotencyKey` (string, required)
## Channel differences
- Telegram: 2-10 options. Supports forum topics via `threadId` or `:topic:` targets. Uses `durationSeconds` instead of `durationHours`, limited to 5-600 seconds. Supports anonymous and public polls.
- WhatsApp: 2-12 options, `maxSelections` must be within option count, ignores `durationHours`.
- Discord: 2-10 options, `durationHours` clamped to 1-768 hours (default 24). `maxSelections > 1` enables multi-select; Discord does not support a strict selection count.
- Microsoft Teams: Adaptive Card polls (OpenClaw-managed). No native poll API; `durationHours` is ignored.
## Agent tool (Message)
Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `channel`).
For Telegram, the tool also accepts `pollDurationSeconds`, `pollAnonymous`, and `pollPublic`.
Use `action: "poll"` for poll creation. Poll fields passed with `action: "send"` are rejected.
Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select.
Teams polls are rendered as Adaptive Cards and require the gateway to stay online
to record votes in `~/.openclaw/msteams-polls.json`.
This page moved to [Message tool](/cli/message). See [Message tool](/cli/message) for poll documentation.

View File

@@ -247,8 +247,8 @@ Each program should have:
## Related
- [Automation Overview](/automation) — all automation mechanisms at a glance
- [Automation & Tasks](/automation) — all automation mechanisms at a glance
- [Cron Jobs](/automation/cron-jobs) — schedule enforcement for standing orders
- [Hooks](/automation/hooks) — event-driven scripts for agent lifecycle events
- [Webhooks](/automation/webhook) — inbound HTTP event triggers
- [Webhooks](/automation/cron-jobs#webhooks) — inbound HTTP event triggers
- [Agent Workspace](/concepts/agent-workspace) — where standing orders live, including the full list of auto-injected bootstrap files (AGENTS.md, SOUL.md, etc.)

View File

@@ -9,7 +9,7 @@ title: "Background Tasks"
# Background Tasks
> **Cron vs Heartbeat vs Tasks?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for choosing the right scheduling mechanism. This page covers **tracking** background work, not scheduling it.
> **Looking for scheduling?** See [Automation & Tasks](/automation) for choosing the right mechanism. This page covers **tracking** background work, not scheduling it.
Background tasks track work that runs **outside your main conversation session**:
ACP runs, subagent spawns, isolated cron job executions, and CLI-initiated operations.
@@ -252,10 +252,8 @@ A task's `runId` links to the agent run doing the work. Agent lifecycle events (
## Related
- [Automation Overview](/automation) — all automation mechanisms at a glance
- [Automation & Tasks](/automation) — all automation mechanisms at a glance
- [TaskFlow](/automation/taskflow) — flow orchestration above tasks
- [Cron Jobs](/automation/cron-jobs) — scheduling background work
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — choosing the right mechanism
- [Scheduled Tasks](/automation/cron-jobs) — scheduling background work
- [Heartbeat](/gateway/heartbeat) — periodic main-session turns
- [CLI: tasks](/cli/index#tasks) — CLI reference for `openclaw tasks flow`
- [CLI: Tasks](/cli/index#tasks) — CLI command reference

View File

@@ -1,122 +1,8 @@
---
summary: "Troubleshoot cron and heartbeat scheduling and delivery"
read_when:
- Cron did not run
- Cron ran but no message was delivered
- Heartbeat seems silent or skipped
summary: "Redirect to /automation/cron-jobs"
title: "Automation Troubleshooting"
---
# Automation troubleshooting
# Automation Troubleshooting
Use this page for scheduler and delivery issues (`cron` + `heartbeat`).
## Command ladder
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
Then run automation checks:
```bash
openclaw cron status
openclaw cron list
openclaw system heartbeat last
```
## Cron not firing
```bash
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw logs --follow
```
Good output looks like:
- `cron status` reports enabled and a future `nextWakeAtMs`.
- Job is enabled and has a valid schedule/timezone.
- `cron runs` shows `ok` or explicit skip reason.
Common signatures:
- `cron: scheduler disabled; jobs will not run automatically` → cron disabled in config/env.
- `cron: timer tick failed` → scheduler tick crashed; inspect surrounding stack/log context.
- `reason: not-due` in run output → manual run called without `--force` and job not due yet.
## Cron fired but no delivery
```bash
openclaw cron runs --id <jobId> --limit 20
openclaw cron list
openclaw channels status --probe
openclaw logs --follow
```
Good output looks like:
- Run status is `ok`.
- Delivery mode/target are set for isolated jobs.
- Channel probe reports target channel connected.
Common signatures:
- Run succeeded but delivery mode is `none` → no external message is expected.
- Delivery target missing/invalid (`channel`/`to`) → run may succeed internally but skip outbound.
- Channel auth errors (`unauthorized`, `missing_scope`, `Forbidden`) → delivery blocked by channel credentials/permissions.
## Heartbeat suppressed or skipped
```bash
openclaw system heartbeat last
openclaw logs --follow
openclaw config get agents.defaults.heartbeat
openclaw channels status --probe
```
Good output looks like:
- Heartbeat enabled with non-zero interval.
- Last heartbeat result is `ran` (or skip reason is understood).
Common signatures:
- `heartbeat skipped` with `reason=quiet-hours` → outside `activeHours`.
- `requests-in-flight` → main lane busy; heartbeat deferred.
- `empty-heartbeat-file` → interval heartbeat skipped because `HEARTBEAT.md` has no actionable content and no tagged cron event is queued.
- `alerts-disabled` → visibility settings suppress outbound heartbeat messages.
## Timezone and activeHours gotchas
```bash
openclaw config get agents.defaults.heartbeat.activeHours
openclaw config get agents.defaults.heartbeat.activeHours.timezone
openclaw config get agents.defaults.userTimezone || echo "agents.defaults.userTimezone not set"
openclaw cron list
openclaw logs --follow
```
Quick rules:
- `Config path not found: agents.defaults.userTimezone` means the key is unset; heartbeat falls back to host timezone (or `activeHours.timezone` if set).
- Cron without `--tz` uses gateway host timezone.
- Heartbeat `activeHours` uses configured timezone resolution (`user`, `local`, or explicit IANA tz).
- Cron `at` schedules treat ISO timestamps without timezone as UTC unless you used CLI `--at "<offset-less-iso>" --tz <iana>`.
Common signatures:
- Jobs run at the wrong wall-clock time after host timezone changes.
- Heartbeat always skipped during your daytime because `activeHours.timezone` is wrong.
Related:
- [/automation/cron-jobs](/automation/cron-jobs)
- [/gateway/heartbeat](/gateway/heartbeat)
- [/automation/cron-vs-heartbeat](/automation/cron-vs-heartbeat)
- [/concepts/timezone](/concepts/timezone)
This page moved to [Scheduled Tasks](/automation/cron-jobs#troubleshooting). See [Scheduled Tasks](/automation/cron-jobs#troubleshooting) for troubleshooting documentation.

View File

@@ -1,217 +1,8 @@
---
summary: "Webhook ingress for wake and isolated agent runs"
read_when:
- Adding or changing webhook endpoints
- Wiring external systems into OpenClaw
summary: "Redirect to /automation/cron-jobs"
title: "Webhooks"
---
# Webhooks
Gateway can expose a small HTTP webhook endpoint for external triggers.
## Enable
```json5
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
// Optional: restrict explicit `agentId` routing to this allowlist.
// Omit or include "*" to allow any agent.
// Set [] to deny all explicit `agentId` routing.
allowedAgentIds: ["hooks", "main"],
},
}
```
Notes:
- `hooks.token` is required when `hooks.enabled=true`.
- `hooks.path` defaults to `/hooks`.
## Auth
Every request must include the hook token. Prefer headers:
- `Authorization: Bearer <token>` (recommended)
- `x-openclaw-token: <token>`
- Query-string tokens are rejected (`?token=...` returns `400`).
- Treat `hooks.token` holders as full-trust callers for the hook ingress surface on that gateway. Hook payload content is still untrusted, but this is not a separate non-owner auth boundary.
## Endpoints
### `POST /hooks/wake`
Payload:
```json
{ "text": "System line", "mode": "now" }
```
- `text` **required** (string): The description of the event (e.g., "New email received").
- `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
Effect:
- Enqueues a system event for the **main** session
- If `mode=now`, triggers an immediate heartbeat
### `POST /hooks/agent`
Payload:
```json
{
"message": "Run this",
"name": "Email",
"agentId": "hooks",
"sessionKey": "hook:email:msg-123",
"wakeMode": "now",
"deliver": true,
"channel": "last",
"to": "+15551234567",
"model": "openai/gpt-5.2-mini",
"thinking": "low",
"timeoutSeconds": 120
}
```
- `message` **required** (string): The prompt or message for the agent to process.
- `name` optional (string): Human-readable name for the hook (e.g., "GitHub"), used as a prefix in session summaries.
- `agentId` optional (string): Route this hook to a specific agent. Unknown IDs fall back to the default agent. When set, the hook runs using the resolved agent's workspace and configuration.
- `sessionKey` optional (string): The key used to identify the agent's session. By default this field is rejected unless `hooks.allowRequestSessionKey=true`.
- `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
- `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging channel. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped.
- `channel` optional (string): The messaging channel for delivery. Use `last` or any configured channel or plugin id, for example `discord`, `matrix`, `telegram`, or `whatsapp`. Defaults to `last`.
- `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack/Mattermost (plugin), conversation ID for Microsoft Teams). Defaults to the last recipient in the main session.
- `model` optional (string): Model override (e.g., `anthropic/claude-sonnet-4-6` or an alias). Must be in the allowed model list if restricted.
- `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
Effect:
- Runs an **isolated** agent turn (own session key)
- Always posts a summary into the **main** session
- If `wakeMode=now`, triggers an immediate heartbeat
## Session key policy (breaking change)
`/hooks/agent` payload `sessionKey` overrides are disabled by default.
- Recommended: set a fixed `hooks.defaultSessionKey` and keep request overrides off.
- Optional: allow request overrides only when needed, and restrict prefixes.
Recommended config:
```json5
{
hooks: {
enabled: true,
token: "${OPENCLAW_HOOKS_TOKEN}",
defaultSessionKey: "hook:ingress",
allowRequestSessionKey: false,
allowedSessionKeyPrefixes: ["hook:"],
},
}
```
Compatibility config (legacy behavior):
```json5
{
hooks: {
enabled: true,
token: "${OPENCLAW_HOOKS_TOKEN}",
allowRequestSessionKey: true,
allowedSessionKeyPrefixes: ["hook:"], // strongly recommended
},
}
```
### `POST /hooks/<name>` (mapped)
Custom hook names are resolved via `hooks.mappings` (see configuration). A mapping can
turn arbitrary payloads into `wake` or `agent` actions, with optional templates or
code transforms.
Mapping options (summary):
- `hooks.presets: ["gmail"]` enables the built-in Gmail mapping.
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
- `hooks.transformsDir` (if set) must stay within the transforms root under your OpenClaw config directory (typically `~/.openclaw/hooks/transforms`).
- `transform.module` must resolve within the effective transforms directory (traversal/escape paths are rejected).
- Use `match.source` to keep a generic ingest endpoint (payload-driven routing).
- TS transforms require a TS loader (e.g. `bun` or `tsx`) or precompiled `.js` at runtime.
- Set `deliver: true` + `channel`/`to` on mappings to route replies to a chat surface
(`channel` defaults to `last` and falls back to WhatsApp).
- `agentId` routes the hook to a specific agent; unknown IDs fall back to the default agent.
- `hooks.allowedAgentIds` restricts explicit `agentId` routing. Omit it (or include `*`) to allow any agent. Set `[]` to deny explicit `agentId` routing.
- `hooks.defaultSessionKey` sets the default session for hook agent runs when no explicit key is provided.
- `hooks.allowRequestSessionKey` controls whether `/hooks/agent` payloads may set `sessionKey` (default: `false`).
- `hooks.allowedSessionKeyPrefixes` optionally restricts explicit `sessionKey` values from request payloads and mappings.
- `allowUnsafeExternalContent: true` disables the external content safety wrapper for that hook
(dangerous; only for trusted internal sources).
- `openclaw webhooks gmail setup` writes `hooks.gmail` config for `openclaw webhooks gmail run`.
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
## Responses
- `200` for `/hooks/wake`
- `200` for `/hooks/agent` (async run accepted)
- `401` on auth failure
- `429` after repeated auth failures from the same client (check `Retry-After`)
- `400` on invalid payload
- `413` on oversized payloads
## Examples
```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/agent \
-H 'x-openclaw-token: SECRET' \
-H 'Content-Type: application/json' \
-d '{"message":"Summarize inbox","name":"Email","wakeMode":"next-heartbeat"}'
```
### Use a different model
Add `model` to the agent payload (or mapping) to override the model for that run:
```bash
curl -X POST http://127.0.0.1:18789/hooks/agent \
-H 'x-openclaw-token: SECRET' \
-H 'Content-Type: application/json' \
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.2-mini"}'
```
If you enforce `agents.defaults.models`, make sure the override model is included there.
```bash
curl -X POST http://127.0.0.1:18789/hooks/gmail \
-H 'Authorization: Bearer SECRET' \
-H 'Content-Type: application/json' \
-d '{"source":"gmail","messages":[{"from":"Ada","subject":"Hello","snippet":"Hi"}]}'
```
## Security
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
- Use a dedicated hook token; do not reuse gateway auth tokens.
- Prefer a dedicated hook agent with strict `tools.profile` and sandboxing so hook ingress has a narrower blast radius.
- Repeated auth failures are rate-limited per client address to slow brute-force attempts.
- If you use multi-agent routing, set `hooks.allowedAgentIds` to limit explicit `agentId` selection.
- Keep `hooks.allowRequestSessionKey=false` unless you require caller-selected sessions.
- If you enable request `sessionKey`, restrict `hooks.allowedSessionKeyPrefixes` (for example, `["hook:"]`).
- Avoid including sensitive raw payloads in webhook logs.
- Hook payloads are treated as untrusted and wrapped with safety boundaries by default.
If you must disable this for a specific hook, set `allowUnsafeExternalContent: true`
in that hook's mapping (dangerous).
This page moved to [Scheduled Tasks](/automation/cron-jobs#webhooks). See [Scheduled Tasks](/automation/cron-jobs#webhooks) for webhook documentation.

View File

@@ -566,7 +566,7 @@ Subcommands:
### `webhooks gmail`
Gmail Pub/Sub hook setup + runner. See [/automation/gmail-pubsub](/automation/gmail-pubsub).
Gmail Pub/Sub hook setup + runner. See [Gmail Pub/Sub](/automation/cron-jobs#gmail-pubsub-integration).
Subcommands:

View File

@@ -12,8 +12,8 @@ Webhook helpers and integrations (Gmail Pub/Sub, webhook helpers).
Related:
- Webhooks: [Webhook](/automation/webhook)
- Gmail Pub/Sub: [Gmail Pub/Sub](/automation/gmail-pubsub)
- Webhooks: [Webhooks](/automation/cron-jobs#webhooks)
- Gmail Pub/Sub: [Gmail Pub/Sub](/automation/cron-jobs#gmail-pubsub-integration)
## Gmail
@@ -22,4 +22,4 @@ openclaw webhooks gmail setup --account you@example.com
openclaw webhooks gmail run
```
See [Gmail Pub/Sub documentation](/automation/gmail-pubsub) for details.
See [Gmail Pub/Sub documentation](/automation/cron-jobs#gmail-pubsub-integration) for details.

View File

@@ -334,7 +334,7 @@
},
{
"source": "/auth-monitoring",
"destination": "/automation/auth-monitoring"
"destination": "/gateway/authentication"
},
{
"source": "/camera",
@@ -382,7 +382,7 @@
},
{
"source": "/cron-vs-heartbeat",
"destination": "/automation/cron-vs-heartbeat"
"destination": "/automation"
},
{
"source": "/dashboard",
@@ -438,7 +438,7 @@
},
{
"source": "/gmail-pubsub",
"destination": "/automation/gmail-pubsub"
"destination": "/automation/cron-jobs"
},
{
"source": "/grammy",
@@ -618,7 +618,7 @@
},
{
"source": "/poll",
"destination": "/automation/poll"
"destination": "/cli/message"
},
{
"source": "/presence",
@@ -778,7 +778,7 @@
},
{
"source": "/webhook",
"destination": "/automation/webhook"
"destination": "/automation/cron-jobs"
},
{
"source": "/whatsapp",
@@ -883,6 +883,30 @@
{
"source": "/automation/clawflow",
"destination": "/automation/taskflow"
},
{
"source": "/automation/poll",
"destination": "/cli/message"
},
{
"source": "/automation/auth-monitoring",
"destination": "/gateway/authentication"
},
{
"source": "/automation/troubleshooting",
"destination": "/automation/cron-jobs"
},
{
"source": "/automation/cron-vs-heartbeat",
"destination": "/automation"
},
{
"source": "/automation/webhook",
"destination": "/automation/cron-jobs"
},
{
"source": "/automation/gmail-pubsub",
"destination": "/automation/cron-jobs"
}
],
"navigation": {
@@ -1118,20 +1142,14 @@
]
},
{
"group": "Automation",
"group": "Automation & Tasks",
"pages": [
"automation/index",
"automation/hooks",
"automation/standing-orders",
"automation/cron-jobs",
"automation/cron-vs-heartbeat",
"automation/tasks",
"automation/cron-jobs",
"automation/taskflow",
"automation/troubleshooting",
"automation/webhook",
"automation/gmail-pubsub",
"automation/poll",
"automation/auth-monitoring"
"automation/hooks",
"automation/standing-orders"
]
},
{
@@ -1793,17 +1811,8 @@
]
},
{
"group": "自动化",
"pages": [
"zh-CN/automation/hooks",
"zh-CN/automation/cron-jobs",
"zh-CN/automation/cron-vs-heartbeat",
"zh-CN/automation/troubleshooting",
"zh-CN/automation/webhook",
"zh-CN/automation/gmail-pubsub",
"zh-CN/automation/poll",
"zh-CN/automation/auth-monitoring"
]
"group": "自动化与任务",
"pages": ["zh-CN/automation/hooks", "zh-CN/automation/cron-jobs"]
},
{
"group": "媒体与设备",

View File

@@ -114,7 +114,7 @@ openclaw models status --check
```
Optional ops scripts (systemd/Termux) are documented here:
[/automation/auth-monitoring](/automation/auth-monitoring)
[Auth monitoring scripts](/help/scripts#auth-monitoring-scripts)
> `claude setup-token` requires an interactive TTY.

View File

@@ -8,7 +8,7 @@ title: "Heartbeat"
# Heartbeat (Gateway)
> **Heartbeat vs Cron?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
> **Heartbeat vs Cron?** See [Automation & Tasks](/automation) for guidance on when to use each.
Heartbeat runs **periodic agent turns** in the main session so the model can
surface anything that needs attention without spamming you.
@@ -16,7 +16,7 @@ surface anything that needs attention without spamming you.
Heartbeat is a scheduled main-session turn — it does **not** create [background task](/automation/tasks) records.
Task records are for detached work (ACP runs, subagents, isolated cron jobs).
Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting)
Troubleshooting: [Scheduled Tasks](/automation/cron-jobs#troubleshooting)
## Quick start (beginner)
@@ -400,8 +400,7 @@ Heartbeats run full agent turns. Shorter intervals burn more tokens. To reduce c
## Related
- [Automation Overview](/automation) — all automation mechanisms at a glance
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — when to use each
- [Automation & Tasks](/automation) — all automation mechanisms at a glance
- [Background Tasks](/automation/tasks) — how detached work is tracked
- [Timezone](/concepts/timezone) — how timezone affects heartbeat scheduling
- [Troubleshooting](/automation/troubleshooting) — debugging automation issues
- [Troubleshooting](/automation/cron-jobs#troubleshooting) — debugging automation issues

View File

@@ -239,7 +239,7 @@ Common signatures:
Related:
- [/automation/troubleshooting](/automation/troubleshooting)
- [/automation/cron-jobs#troubleshooting](/automation/cron-jobs#troubleshooting)
- [/automation/cron-jobs](/automation/cron-jobs)
- [/gateway/heartbeat](/gateway/heartbeat)

View File

@@ -1016,7 +1016,7 @@ for usage/billing and raise limits as needed.
openclaw cron runs --id <jobId> --limit 50
```
Docs: [Cron jobs](/automation/cron-jobs), [Cron vs Heartbeat](/automation/cron-vs-heartbeat).
Docs: [Cron jobs](/automation/cron-jobs), [Automation & Tasks](/automation).
</Accordion>
@@ -1041,7 +1041,7 @@ for usage/billing and raise limits as needed.
- **Heartbeat** for "main session" periodic checks.
- **Isolated jobs** for autonomous agents that post summaries or deliver to chats.
Docs: [Cron jobs](/automation/cron-jobs), [Cron vs Heartbeat](/automation/cron-vs-heartbeat),
Docs: [Cron jobs](/automation/cron-jobs), [Automation & Tasks](/automation),
[Heartbeat](/gateway/heartbeat).
</Accordion>

View File

@@ -19,8 +19,7 @@ Use these when a task is clearly tied to a script; otherwise prefer the CLI.
## Auth monitoring scripts
Auth monitoring scripts are documented here:
[/automation/auth-monitoring](/automation/auth-monitoring)
Auth monitoring is covered in [Authentication](/gateway/authentication). The scripts under `scripts/` are optional extras for systemd/Termux phone workflows.
## When adding scripts

View File

@@ -231,7 +231,7 @@ flowchart TD
Deep pages:
- [/gateway/troubleshooting#cron-and-heartbeat-delivery](/gateway/troubleshooting#cron-and-heartbeat-delivery)
- [/automation/troubleshooting](/automation/troubleshooting)
- [/automation/cron-jobs#troubleshooting](/automation/cron-jobs#troubleshooting)
- [/gateway/heartbeat](/gateway/heartbeat)
</Accordion>
@@ -350,4 +350,4 @@ flowchart TD
- [Gateway Troubleshooting](/gateway/troubleshooting) — gateway-specific issues
- [Doctor](/gateway/doctor) — automated health checks and repairs
- [Channel Troubleshooting](/channels/troubleshooting) — channel connectivity issues
- [Automation Troubleshooting](/automation/troubleshooting) — cron and heartbeat issues
- [Automation Troubleshooting](/automation/cron-jobs#troubleshooting) — cron and heartbeat issues

View File

@@ -60,7 +60,7 @@ For a complete map of the docs, see [Docs hubs](/start/hubs).
- [Sessions](/concepts/session)
- [Cron jobs](/automation/cron-jobs)
- [Webhooks](/automation/webhook)
- [Gmail hooks (Pub/Sub)](/automation/gmail-pubsub)
- [Webhooks](/automation/cron-jobs#webhooks)
- [Gmail hooks (Pub/Sub)](/automation/cron-jobs#gmail-pubsub-integration)
- [Security](/gateway/security)
- [Troubleshooting](/gateway/troubleshooting)

View File

@@ -79,8 +79,8 @@ Use these hubs to discover every page, including deep dives and reference docs t
- [iMessage (legacy)](/channels/imessage)
- [Location parsing](/channels/location)
- [WebChat](/web/webchat)
- [Webhooks](/automation/webhook)
- [Gmail Pub/Sub](/automation/gmail-pubsub)
- [Webhooks](/automation/cron-jobs#webhooks)
- [Gmail Pub/Sub](/automation/cron-jobs#gmail-pubsub-integration)
## Gateway + operations
@@ -111,7 +111,7 @@ Use these hubs to discover every page, including deep dives and reference docs t
- [PDF tool](/tools/pdf)
- [Elevated mode](/tools/elevated)
- [Cron jobs](/automation/cron-jobs)
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat)
- [Automation & Tasks](/automation)
- [Thinking + verbose](/tools/thinking)
- [Models](/concepts/models)
- [Sub-agents](/tools/subagents)
@@ -119,7 +119,7 @@ Use these hubs to discover every page, including deep dives and reference docs t
- [Terminal UI](/web/tui)
- [Browser control](/tools/browser)
- [Browser (Linux troubleshooting)](/tools/browser-linux-troubleshooting)
- [Polls](/automation/poll)
- [Polls](/cli/message)
## Nodes, media, voice

View File

@@ -343,6 +343,6 @@ One public example: a “second brain” CLI + Lobster pipelines that manage thr
## Related
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — scheduling Lobster workflows
- [Automation & Tasks](/automation) — scheduling Lobster workflows
- [Automation Overview](/automation) — all automation mechanisms
- [Tools Overview](/tools) — all available agent tools