心跳 (Gateway)
心跳 vs Cron? 请参阅 Cron vs Heartbeat 了解何时使用哪个。
心跳在主会话中运行周期性的代理轮次,以便模型可以在不打扰您的情况下浮现任何需要关注的事项。
快速入门 (初学者)
- 保持心跳启用(默认为
30m,对于 Anthropic OAuth/setup-token 为1h)或设置您自己的频率。 - 在代理工作空间中创建一个小型
HEARTBEAT.md检查清单(可选但推荐)。 - 决定心跳消息应该发送到哪里(
target: "last"是默认值)。 - 可选:启用心跳推理传递以提高透明度。
- 可选:将心跳限制在活动时间(本地时间)内。
配置示例:
{
agents: {
defaults: {
heartbeat: {
every: "30m",
target: "last",
// activeHours: { start: "08:00", end: "24:00" },
// includeReasoning: true, // optional: send separate `Reasoning:` message too
},
},
},
}默认值
- 间隔:
30m(或当检测到 Anthropic OAuth/setup-token 认证模式时为1h)。设置agents.defaults.heartbeat.every或按代理设置agents.list[].heartbeat.every;使用0m禁用。 - 提示正文(可通过
agents.defaults.heartbeat.prompt配置):Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK. - 心跳提示作为用户消息逐字发送。系统提示包含一个"心跳"部分,运行在内部标记。
- 活动时间(
heartbeat.activeHours)在配置的时区内检查。 在窗口外,心跳会被跳过,直到下一个窗口内的时钟周期。
心跳提示的用途
默认提示故意设计得很宽泛:
- 后台任务:"考虑未完成的任务"会促使代理检查后续事项(收件箱、日历、提醒、排队工作)并浮现任何紧急事项。
- 人工检查:"白天时不时检查你的人类"会促使偶尔发送轻量级的"需要什么帮助吗?"消息,但通过使用您配置的本地时区避免夜间打扰(参见 /concepts/timezone)。
如果您希望心跳执行非常具体的操作(例如"检查 Gmail PubSub 统计信息"或"验证网关健康状况"),请将 agents.defaults.heartbeat.prompt(或 agents.list[].heartbeat.prompt)设置为自定义正文(逐字发送)。
响应约定
- 如果没有需要关注的事项,请回复
HEARTBEAT_OK。 - 在心跳运行期间,当
HEARTBEAT_OK出现在回复的开头或结尾时,OpenClaw 将其视为确认。该标记会被剥离,如果剩余内容 ≤ackMaxChars(默认:300),回复将被丢弃。 - 如果
HEARTBEAT_OK出现在回复的中间,则不会特殊处理。 - 对于警报,不要包含
HEARTBEAT_OK;只返回警报文本。
在心跳之外,出现在消息开头/结尾的零散 HEARTBEAT_OK 会被剥离并记录;仅包含 HEARTBEAT_OK 的消息会被丢弃。
配置
{
agents: {
defaults: {
heartbeat: {
every: "30m", // default: 30m (0m disables)
model: "anthropic/claude-opus-4-5",
includeReasoning: false, // default: false (deliver separate Reasoning: message when available)
target: "last", // last | none | <channel id> (core or plugin, e.g. "bluebubbles")
to: "+15551234567", // optional channel-specific override
prompt: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
ackMaxChars: 300, // max chars allowed after HEARTBEAT_OK
},
},
},
}范围和优先级
agents.defaults.heartbeat设置全局心跳行为。agents.list[].heartbeat在其上合并;如果任何代理有heartbeat块,仅这些代理运行心跳。channels.defaults.heartbeat为所有频道设置可见性默认值。channels.<channel>.heartbeat覆盖频道默认值。channels.<channel>.accounts.<id>.heartbeat(多账户频道)覆盖每个频道的设置。
每个代理的心跳
如果任何 agents.list[] 条目包含 heartbeat 块,仅这些代理运行心跳。每个代理的块合并在 agents.defaults.heartbeat 之上(因此您可以设置一次共享默认值并按代理覆盖)。
示例:两个代理,只有第二个代理运行心跳。
{
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 (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
},
},
],
},
}字段说明
every:心跳间隔(持续时间字符串;默认单位 = 分钟)。model:心跳运行的可选模型覆盖(provider/model)。includeReasoning:启用后,在可用时也传递单独的Reasoning:消息(形状与/reasoning on相同)。session:心跳运行的可选会话键。main(默认):代理主会话。- 显式会话键(从
openclaw sessions --json或 sessions CLI 复制)。 - 会话键格式:参见 Sessions 和 Groups。
target:last(默认):传递到最后使用的外部频道。- 显式频道:
whatsapp/telegram/discord/googlechat/slack/msteams/signal/imessage。 none:运行心跳但不对外传递。
to:可选的接收者覆盖(特定于频道的 id,例如 WhatsApp 的 E.164 或 Telegram 的聊天 id)。prompt:覆盖默认提示正文(不合并)。ackMaxChars:传递前HEARTBEAT_OK后允许的最大字符数。
传递行为
- 心跳默认在代理的主会话中运行(
agent:<id>:<mainKey>),或当session.scope = "global"时在global中运行。设置session以覆盖到特定频道会话(Discord/WhatsApp 等)。 session仅影响运行上下文;传递由target和to控制。- 要传递到特定频道/接收者,请设置
target+to。使用target: "last",传递使用该会话的最后一个外部频道。 - 如果主队列繁忙,心跳会被跳过并稍后重试。
- 如果
target解析为没有外部目标,运行仍会发生但不会发送出站消息。 - 仅心跳回复不会保持会话活动;最后的
updatedAt会被恢复,因此空闲过期行为正常。
可见性控制
默认情况下,HEARTBEAT_OK 确认被抑制,而警报内容会被传递。您可以按频道或按账户调整:
channels:
defaults:
heartbeat:
showOk: false # Hide HEARTBEAT_OK (default)
showAlerts: true # Show alert messages (default)
useIndicator: true # Emit indicator events (default)
telegram:
heartbeat:
showOk: true # Show OK acknowledgments on Telegram
whatsapp:
accounts:
work:
heartbeat:
showAlerts: false # Suppress alert delivery for this account优先级:按账户 → 按频道 → 频道默认值 → 内置默认值。
各标志的作用
showOk:当模型返回仅 OK 回复时发送HEARTBEAT_OK确认。showAlerts:当模型返回非 OK 回复时发送警报内容。useIndicator:为 UI 状态界面发出指示器事件。
如果所有三个都为 false,OpenClaw 会完全跳过心跳运行(不调用模型)。
按频道 vs 按账户示例
channels:
defaults:
heartbeat:
showOk: false
showAlerts: true
useIndicator: true
slack:
heartbeat:
showOk: true # all Slack accounts
accounts:
ops:
heartbeat:
showAlerts: false # suppress alerts for the ops account only
telegram:
heartbeat:
showOk: true常见模式
| 目标 | 配置 |
|---|---|
| 默认行为(静默 OK,警报开启) | (无需配置) |
| 完全静默(无消息,无指示器) | channels.defaults.heartbeat: { showOk: false, showAlerts: false, useIndicator: false } |
| 仅指示器(无消息) | channels.defaults.heartbeat: { showOk: false, showAlerts: false, useIndicator: true } |
| 仅在一个频道中显示 OK | channels.telegram.heartbeat: { showOk: true } |
HEARTBEAT.md (可选)
如果工作空间中存在 HEARTBEAT.md 文件,默认提示会告诉代理读取它。将其视为您的"心跳检查清单":小巧、稳定,可以安全地每 30 分钟包含一次。
如果 HEARTBEAT.md 存在但实际上是空的(仅有空行和 markdown 标题如 # Heading),OpenClaw 会跳过心跳运行以节省 API 调用。如果文件缺失,心跳仍会运行,模型决定如何操作。
保持简短(简短的检查清单或提醒)以避免提示膨胀。
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.代理可以更新 HEARTBEAT.md 吗?
可以 — 如果您要求它这样做。
HEARTBEAT.md 只是代理工作空间中的一个普通文件,因此您可以在正常聊天中告诉代理类似:
- "更新
HEARTBEAT.md以添加每日日历检查。" - "重写
HEARTBEAT.md,使其更短并专注于收件箱跟进。"
如果您希望主动发生这种情况,您还可以在心跳提示中包含明确的一行,例如:"如果检查清单过时,请使用更好的内容更新 HEARTBEAT.md。"
安全提示:不要将机密信息(API 密钥、电话号码、私有令牌)放入 HEARTBEAT.md — 它会成为提示上下文的一部分。
手动唤醒(按需)
您可以使用以下命令将系统事件加入队列并触发立即心跳:
openclaw system event --text "Check for urgent follow-ups" --mode now如果多个代理配置了 heartbeat,手动唤醒会立即运行这些代理的心跳。
使用 --mode next-heartbeat 等待下一个预定周期。
推理传递(可选)
默认情况下,心跳仅传递最终的"答案"有效载荷。
如果您想要透明度,请启用:
agents.defaults.heartbeat.includeReasoning: true
启用后,心跳还将传递带有 Reasoning: 前缀的单独消息(形状与 /reasoning on 相同)。当代理管理多个会话/代码库并且您想了解它为何决定 ping 您时,这可能很有用 — 但它也可能泄露比您想要的更多内部细节。建议在群聊中保持关闭状态。
成本意识
心跳运行完整的代理轮次。较短的间隔会消耗更多令牌。保持 HEARTBEAT.md 简短,如果您只想要内部状态更新,请考虑使用更便宜的 model 或 target: "none"。