Agent 设计最佳实践:OpenClaw 的 Heartbeat 设计

让 AI 助手主动做事的心跳机制——从被动应答到主动巡检

目录:

绝大多数 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  # 工作账号不接收告警

一个重要的优化:如果 showOkshowAlertsuseIndicator 三个全为 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 两种定时机制,它们的适用场景不同:

维度HeartbeatCron
运行上下文主会话(有完整对话历史)独立会话(隔离环境)
适合场景批量巡检、需要上下文的周期任务精确时间触发、独立任务
时间精度粗粒度(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.defaultsagents.list[]channels.defaultschannels.<channel>channels.<channel>.accounts.<id>,每一层都可以覆盖上一层。这种设计在多 Agent、多通道的复杂场景下非常灵活,同时保持了简单场景下的零配置体验。

文件即协议HEARTBEAT.md 的设计延续了 OpenClaw “文件即状态"的哲学——不需要额外的数据库或 API,一个 Markdown 文件就定义了 Agent 的巡检行为。透明、可编辑、可版本控制。

成本意识内建于设计。空文件跳过、三 flag 全关跳过、可配置模型和间隔——这些都不是事后打补丁,而是从设计之初就考虑的约束。在 Token 即成本的 LLM 应用中,这种意识至关重要。

如果你正在设计自己的 Agent 系统,Heartbeat 的"定期巡检 + 静默协议 + 层级配置"模式值得借鉴。它用最小的复杂度,赋予了 AI 助手真正的主动性。


See also