mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 05:12:15 +00:00
docs: restructure automation section as Automation & Tasks
This commit is contained in:
@@ -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 re‑auth flow over SSH.
|
||||
- `scripts/termux-quick-auth.sh`: one‑tap 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 don’t need phone automation or systemd timers, skip these scripts.
|
||||
This page moved to [Authentication](/gateway/authentication). See [Authentication](/gateway/authentication) for auth monitoring documentation.
|
||||
|
||||
@@ -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 Gateway’s 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 don’t lose schedules.
|
||||
- Two execution styles:
|
||||
- **Main session**: enqueue a system event, then run on the next heartbeat.
|
||||
- **Isolated**: run a dedicated agent turn in `cron:<jobId>` 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 host’s
|
||||
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 session’s
|
||||
“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 it’s explicit and unambiguous.
|
||||
- If you see `telegram:...` prefixes in logs or stored “last route” targets, that’s normal;
|
||||
cron delivery accepts them and still parses topic IDs correctly.
|
||||
- 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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": "媒体与设备",
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user