8.3 Heartbeat 心跳机制:周期性巡检与主动通知
上一节讲的是面向精确时间点的 Cron 作业;本节介绍 OpenClaw 内建的另一个调度原语——Heartbeat(心跳)。如果说 Cron 回答的是“几点该干什么”,那 Heartbeat 回答的是“有没有什么需要关注的”。
[!TIP] 不确定选 Heartbeat 还是 Cron?快速判断:需要精确时间 → Cron;需要周期性感知并按需通知 → Heartbeat。详细对比见 8.3.8 选型决策树。
8.3.1 核心概念
Heartbeat 是网关层面的定时智能体轮次:每隔固定间隔(默认 30 分钟),网关向智能体的主会话注入一条心跳提示词,触发一次完整的智能体推理。智能体读取工作区中的 HEARTBEAT.md 清单,检查收件箱、日历、待办等状态,然后做出两种回应之一:
无事发生:回复
HEARTBEAT_OK,网关静默吞掉该回复,不打扰用户。有事需要关注:返回告警文本,网关将其投递到指定渠道(WhatsApp、Telegram、Slack 等)。
这意味着你无需为每个周期性检查写单独的 Cron 作业——一个心跳轮次可以批量处理多个巡检项,且只有“有事”才会发消息,天然避免信息轰炸。
8.3.2 完整生命周期:从定时器到消息投递
下图展示了心跳从定时器触发到消息投递的完整链路。排障时按图定位卡在哪一步,是最快的切入方式。
几个关键设计决策值得注意:
定时器是 per-agent 的。 每个智能体独立维护自己的心跳间隔和上次执行时间,多智能体场景下各自按自己的节奏心跳,不会相互阻塞。当配置热更新时,系统会重新计算所有智能体的间隔。
唤醒合并防止风暴。 多个触发源(定时器到期、Cron 事件、exec 完成)可能在短时间内同时请求心跳。系统用 250ms 的合并窗口将它们归并为一次执行,且按优先级保留最高等级的唤醒原因(ACTION > DEFAULT > INTERVAL > RETRY)。
心跳不续命。 如果心跳回复是 HEARTBEAT_OK(无实质内容),网关会回滚 session 的 updatedAt 时间戳到心跳前的值。这是为了确保空闲超时(idle expiry)正常工作——纯心跳不应该无限延长会话寿命。
8.3.3 发给 LLM 的消息与 HEARTBEAT_OK 协议
理解心跳行为的关键,是搞清楚每次心跳轮次到底给 LLM 发了什么,以及系统如何解读回复。
系统提示词中的 Heartbeat 段
当 promptMode 不是 "minimal" 时,网关会在系统提示词中注入一段心跳协议说明:
如果使用轻量上下文模式(lightContext: true),这个段落不会被注入——因为轻量模式下只加载 HEARTBEAT.md,不加载完整的系统提示词段落。
项目上下文
系统提示词的 # Project Context 部分会注入工作区引导文件。心跳轮次中注入哪些文件取决于 lightContext 配置:
完整模式(默认)
SOUL.md、README.md、TOOLS.md、MEMORY.md、HEARTBEAT.md 等全部引导文件
需要完整上下文做判断
轻量模式(lightContext: true)
仅 HEARTBEAT.md
巡检清单明确,不需要其他上下文,节省 token
用户消息体
用户消息是心跳的“触发指令”,根据触发来源有三种形态:
① 常规心跳(定时器到期)
默认提示词可通过 heartbeat.prompt 完全替换(注意是替换而非合并)。尾部的时间行自动追加,格式固定为 Current time: [本地时间] ([时区]) / [UTC时间] UTC。
② Cron 事件触发
当 Cron 作业产生系统事件时,下一次心跳会自动拾取:
③ 异步命令(exec)完成触发
isHeartbeat 标记的影响
心跳轮次通过 isHeartbeat: true 标记传递给整个调用链,沿途产生以下效应:
模型选择
可使用 heartbeat.model 覆盖默认模型
引导上下文
lightContext: true 时切换为轻量过滤(仅 HEARTBEAT.md)
工具错误告警
可通过配置抑制工具错误告警,避免干扰心跳判断
转录裁剪
回复 HEARTBEAT_OK 后裁剪 transcript,避免上下文污染
HEARTBEAT_OK 响应协议
HEARTBEAT_OK 不只是一个字符串,它是一个双向约定的协议 token:
回复开头或结尾
被识别为确认 token,剥离后若剩余文本 ≤ ackMaxChars(默认 300),整条回复被丢弃
回复中间
不做特殊处理,视为普通文本
非心跳轮次中出现
开头/结尾的 HEARTBEAT_OK 被静默剥离并记录日志;仅含此 token 的消息被丢弃
系统的标准化过程还会处理 LLM 偶尔添加的格式标记——<b>HEARTBEAT_OK</b> 和 **HEARTBEAT_OK** 都会被正确识别和剥离。
8.3.4 配置详解
最小配置只需保留默认值即可工作。完整配置如下:
作用域与优先级
心跳配置有两个维度的层叠:
智能体维度: agents.defaults.heartbeat 设置全局默认;agents.list[].heartbeat 为特定智能体覆盖。一旦任何一个智能体声明了 heartbeat 块,只有那些声明了的智能体才会运行心跳。
渠道可见性维度: channels.defaults.heartbeat → channels.<channel>.heartbeat → channels.<channel>.accounts.<id>.heartbeat,按从宽到窄的顺序覆盖,控制 showOk、showAlerts、useIndicator 三个开关。
多智能体配置示例
8.3.5 HEARTBEAT.md 清单与活跃时段
心跳清单
HEARTBEAT.md 是放在智能体工作区根目录下的一个可选文件,充当心跳巡检清单。默认提示词会指示智能体读取并严格执行它。
设计要点:
保持精简。 这个文件每次心跳都会被注入上下文,膨胀的清单直接转化为 token 开销。
空文件等于关闭。 如果
HEARTBEAT.md只包含空行和 Markdown 标题(如# Heading),网关会跳过心跳轮次以节省 API 调用。文件不存在时,心跳仍然会运行,但智能体需要自行决定做什么。智能体可以自我修改。 你可以在正常对话中让智能体更新
HEARTBEAT.md,也可以在心跳提示词中写上“如果清单过时,自行更新”。不要放敏感信息。 API Key、电话号码等不应出现在此文件中——它会成为提示词的一部分。
活跃时段
activeHours 在心跳触发前做时区感知的过滤,支持跨午夜的窗口。不在窗口内的心跳被跳过,日志记录原因 quiet-hours。
常见模式:
工作时间才心跳
activeHours: { start: "09:00", end: "18:00" }
全天候运行
不设 activeHours(默认行为)
避免深夜打扰
activeHours: { start: "08:00", end: "24:00" }
[!WARNING]
start和end不能相等(如08:00到08:00),这会被视为零宽度窗口,心跳将永远被跳过。
8.3.6 渠道可见性与事件系统
可见性控制
可见性控制决定了心跳消息是否真正发送到渠道:
三个开关全部为 false 时,网关直接跳过心跳轮次(不调用 LLM),这是最经济的“完全静默”模式。
事件系统
每次心跳执行都会发射事件,供 UI 和监控消费:
sent
消息已投递
告警内容成功发送到渠道
ok-empty
空回复
LLM 无输出,可选发送 HEARTBEAT_OK
ok-token
Token 确认
回复仅含 HEARTBEAT_OK,已剥离
skipped
跳过
alerts-disabled / duplicate / quiet-hours / no-target 等
failed
失败
LLM 调用或投递过程出错
指示器类型映射:ok-empty / ok-token → “ok”(绿色);sent → “alert”(黄色);failed → “error”(红色)。在 WebChat 的 Debug 页面中,可以看到最近的心跳事件 JSON 快照。
8.3.7 手动触发与系统事件
不必等待下一个心跳周期,可以立即触发:
如果多个智能体配置了心跳,--mode now 会立即触发所有智能体的心跳。
系统事件不仅限于手动——Cron 作业和异步命令(exec)完成时也会产生事件,心跳会在下一次触发时自动纳入处理。系统会检查待处理的事件队列,针对不同事件类型生成专属提示词(如 Cron 提醒内容会被直接嵌入提示词体内,见 8.3.3 用户消息体)。
8.3.8 Heartbeat vs Cron:选型决策树
最佳实践:二者配合使用。 Heartbeat 处理日常周期性巡检(收件箱、日历、通知),一个轮次批量完成多项检查;Cron 处理需要精确时间的独立作业(日报、周会回顾、定点提醒)。这样既减少 API 调用次数,又保证关键作业的时间精度。
会话
主会话
主会话(通过系统事件)
cron:<jobId> 独立会话
上下文
完整历史
完整历史
空白起步
模型
可覆盖
主会话模型
可覆盖
输出
非 OK 才投递
心跳提示词 + 事件
默认 announce 摘要
时间精度
近似(受队列负载影响)
精确(支持秒级 cron)
精确
Token 开销
一个轮次处理多项检查
加入下次心跳(无额外轮次)
每作业一个完整轮次
成本控制建议
心跳运行的是完整的智能体轮次,不加控制的话 token 消耗可观。几个降本手段:
精简 HEARTBEAT.md:清单越短,每次注入的 token 越少。
使用
lightContext: true:心跳轮次只注入HEARTBEAT.md,不加载完整的工作区引导文件。使用便宜的模型:通过
heartbeat.model为心跳指定更经济的模型(如 Haiku),核心对话仍用高端模型。设置
target: "none":如果只需要内部状态更新而不需要外发消息,跳过投递流程。拉长间隔:
every: "1h"比默认的30m少一半调用。限制活跃时段:夜间跳过心跳可以省下约 1/3 的调用量。
最后更新于
