定时任务(Gateway 调度器)

Cron 还是 Heartbeat?Cron 与 Heartbeat 对比,帮你选对。

Cron 是 Gateway 内置的调度器。它会持久化任务、在该执行的时候唤醒 agent,还能把输出送回聊天。

想实现 “每天早上跑一下”“20 分钟后提醒我” 这类需求,用 cron 就对了。

排障指南:/automation/troubleshooting

速览

  • Cron 跑在 Gateway 内部(不是模型里面)。
  • 任务持久化在 ~/.openclaw/cron/,重启不会丢。
  • 两种执行方式:
    • 主会话:入队一个系统事件,在下次心跳时执行。
    • 隔离模式:在 cron:<jobId> 里跑独立的 agent 轮次,默认自动播报结果(也可以关闭)。
  • 唤醒是一等公民:任务可以要求”立即唤醒”或”等下次心跳”。
  • 每个任务可以单独配 webhook 投递:delivery.mode = "webhook" + delivery.to = "<url>"
  • 旧任务如果有 notify: true 且配了 cron.webhook,会走兼容逻辑,建议迁移到 webhook delivery mode。
  • 升级时,openclaw doctor --fix 可以在调度器处理之前规范化旧的 cron 存储字段。

快速上手

创建一个一次性提醒,确认它存在,然后立即执行:

openclaw cron add \
  --name "Reminder" \
  --at "2026-02-01T16:00:00Z" \
  --session main \
  --system-event "Reminder: check the cron docs draft" \
  --wake now \
  --delete-after-run

openclaw cron list
openclaw cron run <job-id>
openclaw cron runs --id <job-id>

创建一个循环的隔离任务并配置投递:

openclaw cron add \
  --name "Morning brief" \
  --cron "0 7 * * *" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Summarize overnight updates." \
  --announce \
  --channel slack \
  --to "channel:C1234567890"

工具调用等价形式(Gateway cron 工具)

完整的 JSON 结构和示例,见 工具调用的 JSON schema

任务存储位置

Cron 任务默认持久化在网关主机的 ~/.openclaw/cron/jobs.json。Gateway 把文件加载到内存,有变更时写回磁盘。所以手动编辑只有在 Gateway 停止时才安全。改任务请用 openclaw cron add/edit 或 cron 工具调用 API。

新手入门

把定时任务想成:什么时候 执行 + 执行什么

  1. 选一个调度方式

    • 一次性提醒 → schedule.kind = "at"(CLI:--at
    • 重复任务 → schedule.kind = "every"schedule.kind = "cron"
    • ISO 时间戳如果不带时区,按 UTC 处理。
  2. 选在哪里执行

    • sessionTarget: "main" → 在主会话的下次心跳中运行,带完整上下文。
    • sessionTarget: "isolated" → 在 cron:<jobId> 里跑独立的 agent 轮次。
  3. 选执行内容

    • 主会话 → payload.kind = "systemEvent"
    • 隔离模式 → payload.kind = "agentTurn"

补充说明:一次性任务(schedule.kind = "at")成功后默认删除。设 deleteAfterRun: false 可以保留(成功后会被禁用而不是删除)。

核心概念

任务(Jobs)

一个 cron 任务包含:

  • 调度规则(什么时候执行),
  • 执行负载(执行什么),
  • 可选的投递模式announcewebhooknone)。
  • 可选的 agent 绑定agentId):指定某个 agent 来跑这个任务;缺省或找不到时回退到默认 agent。

任务通过 jobId 唯一标识(CLI/Gateway API 都用这个)。在 agent 工具调用中,jobId 是标准字段;旧的 id 字段仍然兼容。一次性任务成功后默认删除;设 deleteAfterRun: false 可以保留。

调度规则

Cron 支持三种调度类型:

  • at:一次性时间戳,通过 schedule.at(ISO 8601)。
  • every:固定间隔(毫秒)。
  • cron:5 字段 cron 表达式(或 6 字段含秒),可选 IANA 时区。

Cron 表达式用 croner 解析。不指定时区时,用网关主机的本地时区。

为了降低多网关整点集中执行的负载峰值,OpenClaw 会对循环的整点表达式(比如 0 * * * *0 */2 * * *)施加最多 5 分钟的确定性偏移。固定小时的表达式(如 0 7 * * *)不受影响。

对任何 cron 调度,你都可以通过 schedule.staggerMs 显式设置偏移窗口(0 表示精确执行)。CLI 快捷方式:

  • --stagger 30s(或 1m5m)设置显式偏移窗口。
  • --exact 强制 staggerMs = 0

主会话 vs 隔离执行

主会话任务(系统事件)

主会话任务入队一个系统事件,可选地唤醒心跳执行器。必须使用 payload.kind = "systemEvent"

  • wakeMode: "now"(默认):事件触发立即心跳。
  • wakeMode: "next-heartbeat":事件等到下次计划心跳再处理。

适合你想要正常心跳提示词 + 主会话上下文的场景。参见 Heartbeat

隔离任务(独立 cron 会话)

隔离任务在 cron:<jobId> 会话中运行独立的 agent 轮次。

关键行为:

  • 提示词前缀 [cron:<jobId> <任务名>],方便追踪。
  • 每次运行都用新的会话 ID(不会继承之前的对话上下文)。
  • 默认行为:如果不配 delivery,隔离任务会自动播报摘要(delivery.mode = "announce")。
  • delivery.mode 决定后续动作:
    • announce:把摘要投递到目标频道,同时在主会话发一条简要总结。
    • webhook:当完成事件包含摘要时,POST 到 delivery.to
    • none:仅内部处理(不投递,不发主会话摘要)。
  • wakeMode 控制主会话摘要的发送时机:
    • now:立即心跳。
    • next-heartbeat:等下次计划心跳。

隔离任务适合那些嘈杂的、高频的或”后台杂活”类任务——避免把主聊天记录搞得乱糟糟。

负载结构(执行什么)

支持两种负载类型:

  • systemEvent:仅限主会话,通过心跳提示词路由。
  • agentTurn:仅限隔离会话,运行独立的 agent 轮次。

agentTurn 常用字段:

  • message:必填,文本提示词。
  • model / thinking:可选覆盖(见下文)。
  • timeoutSeconds:可选超时覆盖。
  • lightContext:可选轻量引导模式,适合不需要工作区引导文件注入的任务。

投递配置:

  • delivery.modenone | announce | webhook
  • delivery.channellast 或指定频道。
  • delivery.to:频道目标(announce 模式)或 webhook URL(webhook 模式)。
  • delivery.bestEffort:announce 投递失败时不让任务失败。

announce 投递会在运行期间抑制消息工具的发送;用 delivery.channel/delivery.to 来指定聊天目标。当 delivery.mode = "none" 时,不会向主会话发摘要。

隔离任务如果不配 delivery,OpenClaw 默认用 announce

Announce 投递流程

delivery.mode = "announce" 时,cron 直接通过出站频道适配器投递,不会拉起主 agent 来撰写或转发消息。

具体行为:

  • 内容:使用隔离运行的出站负载(文本/媒体),保持正常的分块和频道格式。
  • 纯心跳响应(HEARTBEAT_OK,没有实质内容)不会投递。
  • 如果隔离运行已经通过消息工具向同一目标发过消息,跳过投递以避免重复。
  • 投递目标缺失或无效时任务会失败,除非设了 delivery.bestEffort = true
  • 只有 delivery.mode = "announce" 时才会向主会话发简要摘要。
  • 主会话摘要遵循 wakeModenow 触发立即心跳,next-heartbeat 等下次计划心跳。

Webhook 投递流程

delivery.mode = "webhook" 时,完成事件如果包含摘要,cron 会把负载 POST 到 delivery.to

具体行为:

  • 端点必须是合法的 HTTP(S) URL。
  • webhook 模式下不会尝试频道投递。
  • webhook 模式下不会发主会话摘要。
  • 如果配了 cron.webhookToken,认证头为 Authorization: Bearer <cron.webhookToken>
  • 兼容逻辑:旧任务如果有 notify: true,仍会 POST 到 cron.webhook(如果配了的话),同时会打警告让你迁移到 delivery.mode = "webhook"

模型和思考级别覆盖

隔离任务(agentTurn)可以覆盖模型和思考级别:

  • model:提供商/模型字符串(如 anthropic/claude-sonnet-4-20250514)或别名(如 opus
  • thinking:思考级别(offminimallowmediumhighxhigh;仅 GPT-5.2 + Codex 模型支持)

注意:主会话任务也能设 model,但它会改变整个主会话的模型。建议只在隔离任务上覆盖模型,避免意外的上下文切换。

解析优先级:

  1. 任务负载覆盖(最高)
  2. Hook 特定默认值(如 hooks.gmail.model
  3. Agent 配置默认值

轻量引导上下文

隔离任务(agentTurn)可以设 lightContext: true 来启用轻量引导上下文。

  • 适合那些不需要工作区引导文件注入的定时杂活。
  • 实际效果是内嵌运行时以 bootstrapContextMode: "lightweight" 运行,刻意保持 cron 引导上下文为空。
  • CLI 等价:openclaw cron add --light-context ...openclaw cron edit --light-context

投递(频道 + 目标)

隔离任务可以通过顶层 delivery 配置把输出投递到频道:

  • delivery.modeannounce(频道投递)、webhook(HTTP POST)或 none
  • delivery.channelwhatsapp / telegram / discord / slack / mattermost(插件)/ signal / imessage / last
  • delivery.to:频道特定的接收目标。

announce 投递仅对隔离任务(sessionTarget: "isolated")有效。 webhook 投递对主会话和隔离任务都有效。

如果 delivery.channeldelivery.to 缺失,cron 会回退到主会话的”上次路由”(agent 上次回复的地方)。

目标格式提醒:

  • Slack/Discord/Mattermost(插件)的目标建议使用显式前缀(如 channel:<id>user:<id>)避免歧义。Mattermost 的裸 26 字符 ID 默认 用户优先 解析(如果用户存在就走私聊,否则走频道)——用 user:<id>channel:<id> 确保确定性路由。
  • Telegram 话题请用 :topic: 格式(见下文)。

Telegram 投递目标(话题 / 论坛帖子)

Telegram 通过 message_thread_id 支持论坛话题。cron 投递时,可以在 to 字段里编码话题/帖子:

  • -1001234567890(仅 chat id)
  • -1001234567890:topic:123(推荐:显式话题标记)
  • -1001234567890:123(简写:数字后缀)

带前缀的目标如 telegram:... / telegram:group:... 也能识别:

  • telegram:group:-1001234567890:topic:123

工具调用的 JSON schema

直接调用 Gateway cron.* 工具(agent 工具调用或 RPC)时使用以下结构。CLI 标志接受人类可读的时长如 20m,但工具调用中 schedule.at 应使用 ISO 8601 字符串,schedule.everyMs 使用毫秒。

cron.add 参数

一次性主会话任务(系统事件):

{
  "name": "Reminder",
  "schedule": { "kind": "at", "at": "2026-02-01T16:00:00Z" },
  "sessionTarget": "main",
  "wakeMode": "now",
  "payload": { "kind": "systemEvent", "text": "Reminder text" },
  "deleteAfterRun": true
}

循环隔离任务,带投递:

{
  "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
  }
}

说明:

  • schedule.kindatat)、everyeveryMs)或 cronexpr,可选 tz)。
  • schedule.at 接受 ISO 8601(时区可选;省略时按 UTC)。
  • everyMs 单位是毫秒。
  • sessionTarget 必须是 "main""isolated",且必须与 payload.kind 匹配。
  • 可选字段:agentIddescriptionenableddeleteAfterRunat 类型默认 true)、delivery
  • wakeMode 省略时默认 "now"

cron.update 参数

{
  "jobId": "job-123",
  "patch": {
    "enabled": false,
    "schedule": { "kind": "every", "everyMs": 3600000 }
  }
}

说明:

  • jobId 是标准字段;id 兼容使用。
  • patch 中用 agentId: null 可以清除 agent 绑定。

cron.run 和 cron.remove 参数

{ "jobId": "job-123", "mode": "force" }
{ "jobId": "job-123" }

存储与历史

  • 任务存储:~/.openclaw/cron/jobs.json(Gateway 管理的 JSON)。
  • 运行历史:~/.openclaw/cron/runs/<jobId>.jsonl(JSONL 格式,按大小和行数自动裁剪)。
  • 隔离 cron 运行会话在 sessions.json 中按 cron.sessionRetention 裁剪(默认 24h;设 false 禁用)。
  • 覆盖存储路径:配置中设 cron.store

重试策略

任务失败时,OpenClaw 把错误分为瞬态(可重试)和永久(立即禁用)两类。

瞬态错误(会重试)

  • 限流(429、too many requests、resource exhausted)
  • 提供商过载(例如 Anthropic 529 overloaded_error、过载回退摘要)
  • 网络错误(超时、ECONNRESET、fetch failed、socket)
  • 服务端错误(5xx)
  • Cloudflare 相关错误

永久错误(不重试)

  • 认证失败(无效 API key、未授权)
  • 配置或验证错误
  • 其他非瞬态错误

默认行为(不配任何重试策略)

一次性任务(schedule.kind: "at"):

  • 瞬态错误:最多重试 3 次,指数退避(30s → 1m → 5m)。
  • 永久错误:立即禁用。
  • 成功或跳过:禁用(如果 deleteAfterRun: true 则删除)。

循环任务(cron / every):

  • 任何错误:在下次计划执行前施加指数退避(30s → 1m → 5m → 15m → 60m)。
  • 任务保持启用;下次成功后退避重置。

通过 cron.retry 自定义这些默认值(见 配置)。

配置

{
  cron: {
    enabled: true, // 默认 true
    store: "~/.openclaw/cron/jobs.json",
    maxConcurrentRuns: 1, // 默认 1
    // 可选:覆盖一次性任务的重试策略
    retry: {
      maxAttempts: 3,
      backoffMs: [60000, 120000, 300000],
      retryOn: ["rate_limit", "overloaded", "network", "server_error"],
    },
    webhook: "https://example.invalid/legacy", // 废弃的兼容回退,给 notify:true 的旧任务用
    webhookToken: "replace-with-dedicated-webhook-token", // 可选 webhook bearer token
    sessionRetention: "24h", // 时长字符串或 false
    runLog: {
      maxBytes: "2mb", // 默认 2_000_000 字节
      keepLines: 2000, // 默认 2000
    },
  },
}

运行日志裁剪行为:

  • cron.runLog.maxBytes:运行日志文件大小上限,超过就裁剪。
  • cron.runLog.keepLines:裁剪时只保留最新的 N 行。
  • 两者都作用于 cron/runs/<jobId>.jsonl 文件。

Webhook 行为:

  • 推荐做法:在每个任务上设 delivery.mode: "webhook" + delivery.to: "https://..."
  • Webhook URL 必须是合法的 http://https:// URL。
  • 投递内容是 cron 完成事件的 JSON 负载。
  • 如果配了 cron.webhookToken,认证头为 Authorization: Bearer <cron.webhookToken>
  • 如果没配 cron.webhookToken,不发 Authorization 头。
  • 兼容逻辑:旧任务的 notify: true 仍然使用 cron.webhook(如果有配的话)。

彻底关闭 cron:

  • cron.enabled: false(配置)
  • OPENCLAW_SKIP_CRON=1(环境变量)

维护

Cron 有两个内置维护路径:隔离运行会话保留和运行日志裁剪。

默认值

  • cron.sessionRetention24h(设 false 禁用运行会话裁剪)
  • cron.runLog.maxBytes2_000_000 字节
  • cron.runLog.keepLines2000

工作原理

  • 隔离运行会创建会话条目(...:cron:<jobId>:run:<uuid>)和转录文件。
  • 清理器会移除超过 cron.sessionRetention 的过期运行会话条目。
  • 对于已被移除且不再被会话存储引用的运行会话,OpenClaw 会归档转录文件,并在相同保留窗口内清理旧的已删除归档。
  • 每次运行追加后,cron/runs/<jobId>.jsonl 会检查大小:
    • 如果文件大小超过 runLog.maxBytes,裁剪到最新的 runLog.keepLines 行。

高频调度的性能注意事项

高频 cron 配置可能产生大量运行会话和运行日志。维护机制是内置的,但如果限制太宽松,仍然会造成不必要的 IO 和清理工作。

关注点:

  • 保留窗口 cron.sessionRetention 过长,隔离运行又很多
  • cron.runLog.keepLines 很高且 runLog.maxBytes 也很大
  • 很多嘈杂的循环任务写同一个 cron/runs/<jobId>.jsonl

对策:

  • cron.sessionRetention 在调试/审计需求允许的范围内尽量短
  • 用适度的 runLog.maxBytesrunLog.keepLines 控制运行日志大小
  • 嘈杂的后台任务用隔离模式,配好投递规则避免不必要的消息
  • 定期用 openclaw cron runs 检查增长,在日志变大之前调整保留策略

自定义示例

保留运行会话一周,允许更大的运行日志:

{
  cron: {
    sessionRetention: "7d",
    runLog: {
      maxBytes: "10mb",
      keepLines: 5000,
    },
  },
}

禁用隔离运行会话裁剪,但保留运行日志裁剪:

{
  cron: {
    sessionRetention: false,
    runLog: {
      maxBytes: "5mb",
      keepLines: 3000,
    },
  },
}

高频 cron 场景调优示例:

{
  cron: {
    sessionRetention: "12h",
    runLog: {
      maxBytes: "3mb",
      keepLines: 1500,
    },
  },
}

CLI 快速上手

一次性提醒(UTC ISO,成功后自动删除):

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

一次性提醒(主会话,立即唤醒):

openclaw cron add \
  --name "Calendar check" \
  --at "20m" \
  --session main \
  --system-event "Next heartbeat: check calendar." \
  --wake now

循环隔离任务(播报到 WhatsApp):

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"

循环 cron 任务,显式设置 30 秒偏移:

openclaw cron add \
  --name "Minute watcher" \
  --cron "0 * * * * *" \
  --tz "UTC" \
  --stagger 30s \
  --session isolated \
  --message "Run minute watcher checks." \
  --announce

循环隔离任务(投递到 Telegram 话题):

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"

隔离任务,覆盖模型和思考级别:

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 选择(多 agent 场景):

# 把任务绑定到 "ops" agent(该 agent 不存在时回退到默认)
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops

# 修改或清除现有任务的 agent
openclaw cron edit <jobId> --agent ops
openclaw cron edit <jobId> --clear-agent

手动运行(默认强制执行,用 --due 表示仅在到期时执行):

openclaw cron run <jobId>
openclaw cron run <jobId> --due

cron.run 在手动运行入队后就返回确认,不会等任务跑完。成功的入队响应形如 { ok: true, enqueued: true, runId }。如果任务正在运行或 --due 发现还没到期,响应是 { ok: true, ran: false, reason }。用 openclaw cron runs --id <jobId>cron.runs gateway 方法查看最终完成记录。

编辑现有任务(补丁字段):

openclaw cron edit <jobId> \
  --message "Updated prompt" \
  --model "opus" \
  --thinking low

强制现有 cron 任务精确执行(不偏移):

openclaw cron edit <jobId> --exact

运行历史:

openclaw cron runs --id <jobId> --limit 50

不创建任务直接发送系统事件:

openclaw system event --mode now --text "Next heartbeat: check battery."

Gateway API 接口

  • cron.listcron.statuscron.addcron.updatecron.remove
  • cron.run(强制或到期)、cron.runs 不创建任务直接发系统事件,用 openclaw system event

排障

”什么都不跑”

  • 检查 cron 是否启用:cron.enabledOPENCLAW_SKIP_CRON
  • 确认 Gateway 在持续运行(cron 跑在 Gateway 进程内部)。
  • cron 类型的调度:确认时区(--tz)和主机时区是否一致。

循环任务失败后一直延迟

  • OpenClaw 对循环任务在连续失败后施加指数退避:30s、1m、5m、15m,然后 60m。
  • 下次成功后退避自动重置。
  • 一次性(at)任务对瞬态错误(限流、过载、网络、服务端错误)最多重试 3 次并退避;永久错误立即禁用。见 重试策略

Telegram 投递到了错误的地方

  • 论坛话题用 -100…:topic:<id> 格式,显式且无歧义。
  • 日志或”上次路由”目标中出现 telegram:... 前缀是正常的;cron 投递能正确解析话题 ID。

子 agent 播报投递重试

  • 子 agent 运行完成时,gateway 会向请求方会话播报结果。
  • 如果播报流程返回 false(比如请求方会话正忙),gateway 最多重试 3 次,通过 announceRetryCount 追踪。
  • 超过 endedAt 5 分钟的播报会被强制过期,防止过时条目无限循环。
  • 如果日志中看到重复的播报投递,检查子 agent 注册表中 announceRetryCount 值较高的条目。