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 完整生命周期:从定时器到消息投递

下图展示了心跳从定时器触发到消息投递的完整链路。排障时按图定位卡在哪一步,是最快的切入方式。

spinner

几个关键设计决策值得注意:

定时器是 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.heartbeatchannels.<channel>.heartbeatchannels.<channel>.accounts.<id>.heartbeat,按从宽到窄的顺序覆盖,控制 showOkshowAlertsuseIndicator 三个开关。

多智能体配置示例

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] startend 不能相等(如 08:0008:00),这会被视为零宽度窗口,心跳将永远被跳过。

8.3.6 渠道可见性与事件系统

可见性控制

可见性控制决定了心跳消息是否真正发送到渠道:

三个开关全部为 false 时,网关直接跳过心跳轮次(不调用 LLM),这是最经济的“完全静默”模式。

事件系统

每次心跳执行都会发射事件,供 UI 和监控消费:

status
含义
触发场景

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 调用次数,又保证关键作业的时间精度。

维度
Heartbeat
Cron(main session)
Cron(isolated session)

会话

主会话

主会话(通过系统事件)

cron:<jobId> 独立会话

上下文

完整历史

完整历史

空白起步

模型

可覆盖

主会话模型

可覆盖

输出

非 OK 才投递

心跳提示词 + 事件

默认 announce 摘要

时间精度

近似(受队列负载影响)

精确(支持秒级 cron)

精确

Token 开销

一个轮次处理多项检查

加入下次心跳(无额外轮次)

每作业一个完整轮次

成本控制建议

心跳运行的是完整的智能体轮次,不加控制的话 token 消耗可观。几个降本手段:

  • 精简 HEARTBEAT.md:清单越短,每次注入的 token 越少。

  • 使用 lightContext: true:心跳轮次只注入 HEARTBEAT.md,不加载完整的工作区引导文件。

  • 使用便宜的模型:通过 heartbeat.model 为心跳指定更经济的模型(如 Haiku),核心对话仍用高端模型。

  • 设置 target: "none":如果只需要内部状态更新而不需要外发消息,跳过投递流程。

  • 拉长间隔every: "1h" 比默认的 30m 少一半调用。

  • 限制活跃时段:夜间跳过心跳可以省下约 1/3 的调用量。

最后更新于