目录:
绝大多数 AI 助手都是被动的——用户不说话,它就沉默。这在"问答"场景下没问题,但如果你想让 AI 助手真正成为助手,它需要主动意识:定期检查收件箱有没有紧急邮件、日历上有没有即将到来的会议、GitHub 上有没有需要关注的 PR。OpenClaw 的 Heartbeat(心跳)机制正是为此设计的。本文将深入解析这一设计的工程细节和最佳实践。
为什么需要 Heartbeat
传统 AI 助手的交互模式是"请求-响应":用户发消息,AI 回复,对话结束。这种模式下,AI 永远不会主动联系你。但一个好的助手应该像人类助理一样,会在合适的时间主动告诉你:“老板,下午三点有个会议,资料我已经准备好了。”
Heartbeat 解决的就是这个问题。它在 Agent 的主会话中周期性地触发一个 Agent Turn,让模型有机会审视当前状态,决定是否需要主动通知用户。
关键设计约束是:不打扰用户。如果没什么值得说的,心跳应该完全静默。
Heartbeat 的工作原理
Heartbeat 的核心流程可以用下图表示:
┌──────────────────────────────────────────────────┐
│ Gateway 定时器 │
│ 每 30m 触发一次心跳 │
└──────────────────────┬───────────────────────────┘
│
▼
┌────────────────┐
│ 检查 activeHours │ ── 不在活跃时段 ──→ 跳过
└────────┬───────┘
│ 在时段内
▼
┌────────────────┐
│ 发送心跳 Prompt │ ──→ Agent 读取 HEARTBEAT.md
│ 到 Agent 主会话 │ + 审视当前上下文
└────────┬───────┘
│
┌──────┴──────┐
│ │
▼ ▼
没有需要通知 发现紧急事项
回复 HEARTBEAT_OK 回复告警内容
│ │
▼ ▼
静默丢弃 发送到目标通道
(用户无感知) (WhatsApp/Telegram/等)
整个流程的关键是 Response Contract:如果没有需要关注的事项,Agent 必须回复 HEARTBEAT_OK。Gateway 看到这个标记后会静默丢弃消息,用户完全无感知。只有真正需要通知的内容才会被投递。
快速上手
最简配置只需要三步:
1. 启用心跳(默认已开启,间隔 30 分钟):
{
"agents": {
"defaults": {
"heartbeat": {
"every": "30m",
"target": "last"
}
}
}
}
// generated by hugo's coding agent
target: "last" 表示将心跳消息投递到用户最后使用的通道(比如你最近在 Telegram 上和 Agent 聊天,那心跳消息就发到 Telegram)。默认值 "none" 表示心跳运行但不投递,适合只想做内部状态更新的场景。
2. 创建 HEARTBEAT.md 清单(可选但推荐):
# Heartbeat checklist
- Quick scan: anything urgent in inboxes?
- If it's daytime, do a lightweight check-in if nothing else is pending.
- If a task is blocked, write down _what is missing_ and ask Peter next time.
这个文件就是 Agent 的"巡检清单"。每次心跳触发时,默认 Prompt 会告诉 Agent 去读这个文件并严格按照清单执行。
3. 可选:限制活跃时段,避免半夜被 AI 叫醒:
{
"agents": {
"defaults": {
"heartbeat": {
"every": "30m",
"target": "last",
"activeHours": {
"start": "09:00",
"end": "22:00",
"timezone": "Asia/Shanghai"
}
}
}
}
}
// generated by hugo's coding agent
早上 9 点到晚上 10 点之间,心跳正常运行;其他时间自动跳过。
Response Contract:静默协议
Heartbeat 设计中最精妙的部分是它的静默协议。规则很简单:
| 场景 | Agent 行为 | Gateway 行为 |
|---|---|---|
| 没有需要关注的事项 | 回复 HEARTBEAT_OK | 静默丢弃,用户无感知 |
| 发现需要通知的内容 | 回复告警文本(不含 HEARTBEAT_OK) | 投递到目标通道 |
HEARTBEAT_OK + 少量文本 | 例如 HEARTBEAT_OK,已检查完毕 | 如果附加文本 ≤ 300 字符,仍然静默丢弃 |
ackMaxChars(默认 300)控制了 HEARTBEAT_OK 之后允许保留多少字符。这个阈值避免了 Agent “HEARTBEAT_OK,但我想说一下……” 这种冗长回复被意外投递的问题。
一个重要细节:HEARTBEAT_OK 只在回复的开头或结尾才会被识别。如果它出现在中间,不会被特殊处理。这防止了误截断正常内容。
多 Agent 心跳:精细控制
在多 Agent 架构中,心跳可以按 Agent 粒度配置。一个常见模式是:主 Agent 不需要心跳(它是被动响应的),运维 Agent 需要定期巡检。
{
"agents": {
"defaults": {
"heartbeat": {
"every": "30m",
"target": "last"
}
},
"list": [
{ "id": "main", "default": true },
{
"id": "ops",
"heartbeat": {
"every": "1h",
"target": "whatsapp",
"to": "+15551234567",
"prompt": "Read HEARTBEAT.md if it exists. Check system health and pending alerts. If nothing needs attention, reply HEARTBEAT_OK."
}
}
]
}
}
// generated by hugo's coding agent
这里的优先级规则是:如果任何 agents.list[] 条目包含 heartbeat 块,只有这些 Agent 运行心跳。上面的配置中,main Agent 没有 heartbeat 块,所以它不会运行心跳;只有 ops Agent 每小时执行一次心跳,并通过 WhatsApp 投递结果。
Per-Agent 的 heartbeat 配置会与 agents.defaults.heartbeat 合并(merge),所以你可以在 defaults 中设置共享默认值,只在具体 Agent 中覆盖需要差异化的字段。
投递通道与可见性控制
Heartbeat 的投递机制分为两层:去哪和给谁看。
去哪(target + to)
target: "none"— 运行心跳但不投递(默认)target: "last"— 投递到上次对话的通道target: "whatsapp"/"telegram"/"slack"等 — 指定通道to— 通道内的具体接收者(如 WhatsApp 手机号、Telegram chat ID)
给谁看(Visibility Controls)
可以按通道甚至按账号控制心跳消息的可见性:
channels:
defaults:
heartbeat:
showOk: false # 隐藏 HEARTBEAT_OK(默认)
showAlerts: true # 显示告警消息(默认)
useIndicator: true # 发出指示器事件(默认)
telegram:
heartbeat:
showOk: true # Telegram 上也显示 OK 确认
whatsapp:
accounts:
work:
heartbeat:
showAlerts: false # 工作账号不接收告警
一个重要的优化:如果 showOk、showAlerts、useIndicator 三个全为 false,OpenClaw 会直接跳过心跳运行——不调用模型,节省 Token。
HEARTBEAT.md:心跳清单的设计要点
HEARTBEAT.md 是心跳系统的灵魂,但它的设计有几个关键要点:
保持精简。这个文件每 30 分钟被读一次,写太多会造成 Prompt 膨胀和 Token 浪费。短清单或几条提醒就够了。
空文件 = 跳过心跳。如果 HEARTBEAT.md 存在但内容只有空行和标题(如 # Heading),OpenClaw 会跳过心跳运行以节省 API 调用。
文件缺失也没关系。如果文件不存在,心跳仍然运行,模型自行判断该做什么。
Agent 可以自更新。你可以在正常对话中告诉 Agent:“更新 HEARTBEAT.md,加一条每日检查日历。” Agent 会直接修改这个文件。你甚至可以在心跳 Prompt 中写:“如果清单过时了,用更好的内容更新 HEARTBEAT.md。”
不要放敏感信息。HEARTBEAT.md 会成为 Prompt 上下文的一部分,不要往里面写 API Key、密码或私人 Token。
手动唤醒与即时心跳
除了定时触发,你还可以手动触发一次立即心跳:
openclaw system event --text "Check for urgent follow-ups" --mode now
--mode now 立即触发所有配置了心跳的 Agent。如果你想等到下一个自然周期再触发,用 --mode next-heartbeat。
这在"我感觉有什么事需要处理,但不想自己去查"的场景下很有用。
Heartbeat vs Cron:什么时候用哪个
OpenClaw 同时提供 Heartbeat 和 Cron 两种定时机制,它们的适用场景不同:
| 维度 | Heartbeat | Cron |
|---|---|---|
| 运行上下文 | 主会话(有完整对话历史) | 独立会话(隔离环境) |
| 适合场景 | 批量巡检、需要上下文的周期任务 | 精确时间触发、独立任务 |
| 时间精度 | 粗粒度(30m/1h) | 精确到分钟 |
| 模型选择 | 可独立配置 | 可独立配置 |
| 会话影响 | 不重置会话空闲计时器 | 在独立 Session 中运行 |
简单总结:需要"看看有什么事"用 Heartbeat,需要"在特定时间做特定事"用 Cron。
成本控制
心跳每次运行都是一个完整的 Agent Turn,会消耗 Token。控制成本的几种方式:
- 加长间隔:把
every从"30m"改成"1h"或"2h" - 限制活跃时段:
activeHours让非工作时间不触发心跳 - 使用便宜模型:通过
model字段为心跳指定一个成本更低的模型 - 精简 HEARTBEAT.md:清单越短,消耗的输入 Token 越少
- 用
target: "none":如果你只需要内部状态更新而不需要投递
设计启示
OpenClaw 的 Heartbeat 设计给 Agent 系统设计者的几点启示:
主动性不等于打扰。通过 HEARTBEAT_OK 静默协议和可见性控制,心跳做到了"有事说事、没事静默"。这是 Agent 从工具变成助手的关键一步。
配置的层级化设计。从 agents.defaults → agents.list[] → channels.defaults → channels.<channel> → channels.<channel>.accounts.<id>,每一层都可以覆盖上一层。这种设计在多 Agent、多通道的复杂场景下非常灵活,同时保持了简单场景下的零配置体验。
文件即协议。HEARTBEAT.md 的设计延续了 OpenClaw “文件即状态"的哲学——不需要额外的数据库或 API,一个 Markdown 文件就定义了 Agent 的巡检行为。透明、可编辑、可版本控制。
成本意识内建于设计。空文件跳过、三 flag 全关跳过、可配置模型和间隔——这些都不是事后打补丁,而是从设计之初就考虑的约束。在 Token 即成本的 LLM 应用中,这种意识至关重要。
如果你正在设计自己的 Agent 系统,Heartbeat 的"定期巡检 + 静默协议 + 层级配置"模式值得借鉴。它用最小的复杂度,赋予了 AI 助手真正的主动性。