| name | openclaw-heartbeat-cron |
| description | OpenClaw heartbeat 与 cron 定时任务配置指南。当 Agent 遇到以下问题时触发:心跳不触发、cron 任务不执行、HEARTBEAT.md 不知道怎么写、不知道选 heartbeat 还是 cron、定时消息发不出去、activeHours 配置错误、cron 时区问题、heartbeat 报错 empty-heartbeat-file / quiet-hours / requests-in-flight。也适用于想学习 OpenClaw 定时机制的 Agent。 |
OpenClaw Heartbeat & Cron 配置指南
两个机制,别选错
| 场景 | 用 Heartbeat | 用 Cron |
|---|
| 每30分钟批量检查多个事项 | ✅ | |
| 精确时间执行("每天9:00整") | | ✅ |
| 需要主 session 上下文 | ✅ | |
| 独立任务,不污染主历史 | | ✅ |
| 一次性提醒("20分钟后提醒我") | | ✅ |
| 用不同模型/思考级别执行 | | ✅ |
| 一次心跳跑多个检查 | ✅ | |
详细对比见 references/modes.md
底层机制差异
Heartbeat 本质是消息轮询,不是独立定时器。
Gateway 按配置的间隔往主 session 发一条"心跳消息"(就是你在对话里看到的那种触发文本),Agent 收到后执行 HEARTBEAT.md 里的任务。从 Agent 视角看像"被动响应消息",但从外部看效果等同于定时触发。
Cron 是 Gateway 内置的独立调度器。
到了设定时间,Gateway 直接创建一个独立的新 session 来执行任务,不经过主 session 的消息流。
| Heartbeat | Cron |
|---|
| 触发方式 | Gateway 发消息到主 session | Gateway 独立创建新 session |
| 上下文 | 带主 session 完整历史 | 隔离的,干净环境 |
| 被阻塞 | 主 session 有对话时可能延迟 | 不受主 session 影响 |
| 执行环境 | 共享主 session | 独立 session,用完即销 |
为什么这样设计? Agent 本身是无状态的,没有持久进程在跑。Gateway 是唯一常驻进程,所有定时逻辑都由它负责。好处是 Agent 重启不影响定时任务,心跳间隔可以动态调整。
Heartbeat 配置
第一步:创建 HEARTBEAT.md
在 workspace 根目录创建 HEARTBEAT.md,每次心跳会自动读取并执行。
最小可运行模板:
# HEARTBEAT.md - 定期检查
## 0️⃣ 服务保活
每次心跳执行:
```bash
bash /home/node/bin/my-daemon.sh
1️⃣ 主要任务
检查 XXX,如有异常则报告。
⚠️ HEARTBEAT.md 必须有实际内容。如果为空或只有注释,心跳会跳过并报 `empty-heartbeat-file`。
### 第二步:配置心跳参数
在 OpenClaw 配置中设置:
```json5
{
agents: {
defaults: {
heartbeat: {
every: "30m", // 心跳间隔
target: "last", // 告警投递目标(默认 "none" 不投递)
activeHours: {
start: "08:00",
end: "22:00"
// timezone: "Asia/Shanghai" // 可选,默认用 userTimezone
},
},
},
},
}
心跳不触发的排查
按顺序执行:
openclaw system heartbeat last
openclaw config get agents.defaults.heartbeat
openclaw logs --follow
常见原因:
| 报错/现象 | 原因 | 解决 |
|---|
quiet-hours | 在 activeHours 之外 | 调整 activeHours 或时区 |
requests-in-flight | 主 session 正忙 | 正常现象,会自动重试 |
empty-heartbeat-file | HEARTBEAT.md 为空 | 写入实际任务 |
alerts-disabled | 可见性设置屏蔽了投递 | 检查 visibility 配置 |
| 心跳间隔太长 | every 设置过大 | 改小,如 30m |
| 时区错误 | activeHours 用了错误时区 | 显式设置 timezone: "Asia/Shanghai" |
心跳中发消息
⚠️ 心跳时没有 inbound 上下文,必须显式传 accountId:
message(action=send, accountId="main", target="user:xxx", message="...")
否则会报 "account default not configured"。
Cron 配置
快速创建
一次性提醒:
openclaw cron add \
--name "提醒" \
--at "20m" \
--session main \
--system-event "该做某事了" \
--wake now \
--delete-after-run
每日定时任务(隔离 session):
openclaw cron add \
--name "每日早报" \
--cron "0 9 * * *" \
--tz "Asia/Shanghai" \
--session isolated \
--message "生成今日早报并发送给用户" \
--announce \
--channel telegram \
--to "-1001234567890"
定时任务(主 session):
openclaw cron add \
--name "项目检查" \
--every "4h" \
--session main \
--system-event "检查项目健康状态" \
--wake now
投递到飞书
Cron 任务可以通过 --announce 将结果投递到飞书聊天。
投递给个人用户:
openclaw cron add \
--name "每日早报" \
--cron "0 9 * * *" \
--tz "Asia/Shanghai" \
--session isolated \
--message "生成今日AI早报" \
--announce \
--channel feishu \
--account <account_id> \
--to "user:<open_id>"
投递到群聊:
openclaw cron add \
--name "定时通知" \
--cron "0 18 * * 1-5" \
--tz "Asia/Shanghai" \
--session isolated \
--message "今日工作总结" \
--announce \
--channel feishu \
--account <account_id> \
--to "chat:<chat_id>"
参数说明:
| 参数 | 说明 | 示例 |
|---|
--channel | 投递通道 | feishu, telegram, discord 等 |
--account | 多账号时的账户 ID | main, default 或你的飞书账户 ID |
--to | 目标(飞书用 open_id 或 chat_id) | user:ou_xxx, chat:oc_xxx |
--announce | 开启投递 | 不加则任务执行但不外发消息 |
--no-deliver | 禁止投递 | 即使有其他默认配置也不投递 |
多账号注意: 如果配了多个飞书账户,--account 必须显式指定,否则可能投递失败。类似心跳发消息必须带 accountId 的问题。
常用参数说明
| 参数 | 说明 | 示例 |
|---|
--cron | Cron 表达式 | "0 9 * * *", "*/30 * * * *" |
--every | 固定间隔 | "30m", "2h", "1d" |
--at | 一次性时间点 | "20m", "2026-04-14T09:00:00+08:00" |
--tz | 时区(重要!) | "Asia/Shanghai" |
--session | main 或 isolated | |
--model | 覆盖模型 | opus, gpt-4o |
--thinking | 思考级别 | high, low, off |
--announce | 投递到频道 | |
--exact | 禁止自动错峰 | |
Main Session vs Isolated Session
| Main Session | Isolated |
|---|
| Session | 共享主 session 历史 | 独立 cron:<jobId> |
| 上下文 | 有完整对话历史 | 全新,无上下文 |
| 模型 | 用主 session 模型 | 可单独覆盖 |
| 适用 | 需要上下文的提醒 | 独立任务、不同模型 |
Cron 不执行的排查
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
常见原因:
| 报错/现象 | 原因 | 解决 |
|---|
scheduler disabled | cron 被禁用 | 检查 cron.enabled 和 OPENCLAW_SKIP_CRON |
not-due | 手动运行但未到时间 | 用 openclaw cron run <id>(默认 force) |
| 连续延迟 | 任务反复失败后指数退避 | 查 cron runs 看失败原因,修复后自动恢复 |
| 时区错误 | 没设 --tz,用了主机时区 | 加 --tz "Asia/Shanghai" |
| 任务执行了但没收到消息 | delivery 配置问题 | 检查 --channel 和 --to 是否正确 |
Cron 执行了但没收到消息
- 检查
openclaw cron runs --id <jobId> 看状态是否 ok
- isolated 任务是否设了
--announce + --channel + --to
openclaw channels status --probe 检查通道连通性
- 如果 delivery mode 是
none,则不会有外部消息
失败重试机制
- 瞬态错误(429限流、网络超时、5xx):自动重试
- 一次性任务:最多 3 次,间隔 30s → 1m → 5m
- 周期任务:指数退避 30s → 1m → 5m → 15m → 60m
- 永久错误(认证失败、配置错误):立即禁用
高级技巧
状态追踪(避免重复执行)
用 JSON 文件记录上次检查时间:
{
"lastChecks": {
"task1": 1744514100,
"task2": 1744514100
}
}
每次心跳对比时间戳,超过阈值才执行。
服务保活脚本
参考 scripts/daemon-template.sh,标准保活脚本输出 running/started/failed。
日夜分工(省 token)
在 HEARTBEAT.md 中用时间判断:
- 白天:低频心跳,只做必要回复
- 夜间:高频心跳,主动互动
完整日夜分工模板见 references/heartbeat-template.md
组合使用
最高效的方案是两者结合:
- Heartbeat:批量巡检(收件箱、日历、通知),每 30 分钟一次
- Cron:精确定时(每日早报、周报),独立执行
Cron 管理
openclaw cron list
openclaw cron edit <jobId> --message "新内容"
openclaw cron edit <jobId> --exact
openclaw cron remove <jobId>
注意事项
- HEARTBEAT.md 保持精简 — 每次心跳都读取,太大会浪费 token
- Cron 时区要显式设置 — 不设
--tz 默认用主机时区,容易出错
- 心跳发消息必须带 accountId — 心跳无 inbound 上下文
- isolated 任务默认 announce — 不想投递就设
--delivery none
- ISO 时间戳不带时区 = UTC —
2026-04-14T09:00:00 是 UTC,不是本地时间