Skip to content

第五章 消息循环与事件驱动——Agent的心跳

核心问题:你和同事同时给 Agent 发消息,两条请求会不会搅成一锅粥?Agent 没人找的时候,能不能主动发现问题?


先从一个日常场景说起。你在陌生城市找咖啡店,不会站在酒店门口把所有可能的路线都想清楚再出门——你先走一步,看到路口再决定方向,走错了回头,问了路再调整。Agent 处理任务的方式完全一样:每一步行动的结果,决定了下一步往哪走。

但现实比这更复杂一点。想象下午三点,你在重构一个模块,连续给 Agent 发了两条消息——"先创建 output.csv",然后马上跟了一条"把分析结果写入那个文件"。与此同时,你同事正在用同一个 Agent 实例跑测试脚本。

四条消息,几乎同时到达。如果系统"收到就处理",会发生什么?写入操作可能跑在创建操作前面,报"文件不存在";你的重构上下文和同事的测试上下文可能互相污染;结果对谁来说都是一团乱麻。

这不是极端场景,这是 Agent 真实运行时的日常状态。消息的到达是无序且并发的,但 Agent 是有状态的——这个矛盾,是消息系统要解决的核心问题。

本章讨论两个设计决策:泳道模型让 Agent 在并发环境下可靠运行;心跳机制让 Agent 在时间维度上拥有主动性。两者合力,回答了 Agent 架构里最本质的一个问题:它究竟是一个"工具",还是一个真正的"协作者"?


一、并发世界里的乱序问题

为什么"收到就执行"行不通

对单个用户来说,"按顺序回复"看起来理所当然。但稍微想一下就会发现,消息的到达和消息的执行是两回事。

用户发得快,网络不稳定,多个会话同时活跃——这些情况下,消息不会老老实实排成一排等你处理。

问题后果
并发冲突两条消息同时修改同一文件,数据损坏
无序执行"创建文件"和"写入文件"乱序,后者读不到前者的结果
上下文污染不同用户的会话消息混在一起,Agent 搞不清在和谁说话

这三个问题指向同一个根本矛盾:Agent 有状态,消息的到达却无序。解决这个矛盾,需要在消息到达和消息执行之间插入一个调度层——命令队列

命令队列不是性能优化,它是正确性保证。没有它,Agent 的状态在并发压力下随时可能进入不一致状态。

从"请求-响应"到"事件驱动"

传统软件用请求-响应模式:你发请求,等结果,拿到再走。这对执行时间可预期的操作没有问题。

但 Agent 的执行时间不可预测——一个问题可能三秒回答,也可能需要五分钟。而且消息来源不止一个:用户的指令、定时触发的任务、子 Agent 的回调,都需要统一处理。

事件驱动模型的优势在这里才真正显现:消息先进队列,再由调度器有序取出执行。发送者和执行者在时间上解耦——发完消息的人不需要傻等,执行者按自己的节奏处理。

更重要的是,这种模型统一了消息来源:用户消息、心跳触发、Cron 到期,在架构上都是"一条消息进入队列",调度器只负责有序执行,不管消息从哪里来。


二、泳道模型:秩序从这里来

一条规则,两个效果

命令队列的核心调度策略叫泳道模型,规则只有两条:

  • 同一泳道内:消息严格串行(一个处理完再处理下一个)
  • 不同泳道间:消息完全并行(互不等待)

一个泳道 = 一个会话(Session)

泳道A(会话A):消息1 ──────────────── 消息2 ──→ 消息3
                   顺序执行,严格串行

泳道B(会话B):      消息4 ──→ 消息5
                              与A完全并行,互不影响

泳道C(会话C):         消息6 ──────────────────→ 消息7

你的消息在 A 泳道里按顺序跑,同事的消息在 B 泳道里同步推进,两条线互不干扰。5分钟的重构任务不会阻塞同事的一个快速查询。

为什么以"会话"为隔离单元

会话是很自然的隔离边界,原因是因果一致性

你发出"先创建文件,再写入内容"——第二条消息隐含了一个假设:第一条已经完成了。这种前后依赖关系,在同一个对话里无处不在。串行执行,是把这种人类常识编码进架构的方式。

而不同用户的会话之间不存在这种因果关系。你的操作和同事的操作彼此独立。泳道模型的隔离粒度,和因果依赖的实际范围完全吻合——避免了"所有消息排成一队"(过度串行,拖慢所有人)和"同一会话也并行"(不足串行,制造乱序)两种极端。

隔离还带来了一个工程好处:单个泳道的故障不污染其他泳道。某个会话进入死循环或异常状态,调度器可以终止那条泳道,其他会话照常运行。

这种故障隔离是更大的容错体系中的"会话级"一环。完整的三层容错思路是:任务级(单个工具调用失败时重试或切换策略)→ 会话级(某条泳道异常时隔离并恢复,不影响其他会话)→ 服务级(整个系统的监控与备用方案)。泳道模型处于第二层,是最能体现隔离价值的那一层。

泳道不只是"用户会话"

泳道的隔离边界,是"任务类型",而不仅仅是"用户会话"。OpenClaw 把任务分成四类泳道:

泳道类型说明
用户会话泳道每个用户会话一条泳道,处理常规对话请求
Cron 泳道定时任务独立运行,不占用用户会话的队列
子 Agent 泳道子 Agent 的调用和回调在独立泳道中执行
嵌套任务泳道嵌套任务有自己的隔离执行上下文

这意味着:一个长时间的 Cron 任务不会阻塞用户的实时请求;子 Agent 的异常不会回传污染主会话。隔离是系统范围的,不是只针对多用户并发。


三、五种队列模式

Agent 正在执行任务时,新消息怎么办

泳道模型解决了"不同用户不互相等待"的问题。但还有一个场景没有答案:同一用户的新消息,在 Agent 正在忙的时候到达,该怎么处理?

"等它忙完再处理"不一定对。有时候用户发现方向不对,需要立刻纠正;有时候只是补充信息,等一等也没关系;有时候用户真的需要打断当前任务。

不同的意图,需要不同的处理策略。OpenClaw 定义了五种队列模式:

模式行为适用场景
collect等 Agent 完成后,将等待中的多条消息合并跟进默认情况,用户可以等待
steer立即注入当前运行,引导 Agent 调整方向"不对,先做 X 再做 Y"
followup排队到当前任务完成后再处理明确有先后顺序的任务序列
interrupt立即中断当前运行,处理新消息紧急停止或方向性错误
queue标准顺序模式,按序执行普通连续任务

模式是用户意图的工程化表达

注意:同一条消息,在不同时机对应的模式可能不同。

"先做 X"——如果任务刚开始,用 steer,还来得及调整;如果任务接近完成,用 followup,等完成后再安排 X。

五种模式对应五种不同的用户心态:

  • collect 假设用户信任 Agent 会完成当前任务
  • interrupt 假设用户认为当前方向已经错误
  • steer 假设用户想在不丢失当前进度的情况下微调

把这些心态显式建模为调度参数,是将用户控制权具体化的工程方式。用户不需要了解队列实现,只需要选择意图最匹配的模式。

循环检测:系统健壮性的兜底

队列模式解决了"用户想要什么"的问题,但还有一类无声的故障需要预防:Agent 陷入工具调用的循环,反复做同一件事却毫无进展。OpenClaw 内置了四种循环检测机制:

类型检测目标
generic_repeat重复调用相同工具超过 N 次(参数基本相同)
known_poll_no_progress轮询某个状态,但结果始终没有实质变化
ping_pong两个操作互相抵消,来回切换(比如删了又建、建了又删)
global_circuit_breaker全局熔断器,当整体调用次数超过上限时强制终止,作为兜底

检测到循环时,系统中断当前执行并向用户报告,避免无意义地消耗 token 和时间。前三种针对特定模式,最后一种是最终安全网——无论什么原因导致的失控,都会在全局层面被截住。


四、心跳机制:Agent 的"生物钟"

只会等你来问的管家

泳道模型和队列模式解决了"怎么处理消息"的问题。但有一个更根本的问题没有触及:如果没有消息来,Agent 能做什么?

想象一个只在你问时才说话的管家。你不会知道:重要邮件三小时前未处理,监控服务已经停了响应,后台任务等待指令。

你需要的,是一个会主动"醒来"检查的管家。

这就是心跳机制要解决的问题。它赋予 Agent 第三层时间感知:

第一层:知道现在几点
  └─ 提示词中注入当前时间戳,能做时间相关判断



第二层:知道距离上次交互过去了多久
  └─ 持久化会话状态,感知"沉默的时长"



第三层:主动在约定时刻触发自身  ← 心跳实现的层次
  └─ 设置"闹钟",在约定时间点自主执行检查清单

大多数 Agent 框架止步于第一层,少数做到第二层。第三层是"工具"和"协作者"的分界线——拥有它的 Agent 不再只是响应,它有自己的节律。

HEARTBEAT.md:检查清单

心跳机制的实现刻意保持简单:在 HEARTBEAT.md 里用自然语言写检查清单,Agent 被定期唤醒时逐项执行。

markdown
# Heartbeat checklist

- 快速扫描收件箱,有没有紧急邮件?
- 检查后台任务状态,有没有卡住的?
- 如果有用户等待回复超过 2 小时,发提醒

机制(定时唤醒)由系统提供,内容(检查什么)由用户决定。 两者分离,使心跳机制可以服务于任何使用场景,无需预先知道用户的具体需求。

改变检查清单,不需要修改代码、不需要重新部署。编辑文件,保存,下次心跳就按新清单执行。

HEARTBEAT_OK:静默哲学

心跳机制最精妙的设计,不是如何触发,而是如何不打扰

如果每次心跳都推送消息,哪怕内容只是"一切正常",用户很快会开始无视所有通知——真正重要的信息也因此被淹没。这是大多数主动性设计失败的原因:系统变得"过于健谈"。

解决方案是一个约定:检查后无需告知时,Agent 只回复 HEARTBEAT_OK,系统静默丢弃,用户无感知。

心跳触发

Agent 读取 HEARTBEAT.md,逐项检查

  ├── 没有需要关注的事 → 回复 HEARTBEAT_OK → 系统丢弃,用户无感知

  └── 发现需要关注的事 → 回复实际内容 → 用户收到通知

这体现了一个更普遍的原则:主动性的价值,在于它只在有意义时发声。 无效心跳零打扰,有内容才通知。HEARTBEAT_OK 将"静默"从默认行为变成了明确的架构选择。

心跳 vs Cron:两种主动性

OpenClaw 提供两种定时机制,各有侧重:

维度心跳(Heartbeat)Cron 定时任务
触发模式固定间隔周期触发标准时间表达式,精确到特定时刻
执行上下文主会话,有完整对话历史可选独立隔离会话
任务性质持续状态监控精确时间点的确定性任务
静默机制HEARTBEAT_OK,无结果时丢弃可配置是否推送

选择建议:需要在精确时刻执行("每周一发送周报")→ 用 Cron;需要持续关注状态("随时检查紧急消息")→ 用心跳。两者可以组合,分别处理各自最擅长的场景。

更具体的判断标准:

你的需求推荐典型例子
需要在精确时刻执行Cron"每周一早9点发周报"
需要持续状态监控Heartbeat"随时检查有没有紧急消息"
有明确的对话上下文Heartbeat在主会话里执行,能看到完整历史
与对话解耦的系统任务Cron生成报告、清理缓存、数据备份

两者的本质区别在于上下文归属:心跳在主会话中执行,Agent 能看到完整对话历史,因此能参考过去的对话内容(比如"用户上次提到要追踪这个任务")。Cron 任务更适合与特定对话解耦的系统级任务——生成报告、清理缓存。


小结

核心机制解决的问题关键设计
命令队列消息并发到达导致无序执行先进队列,再有序执行;统一所有消息来源
泳道模型多会话并发的隔离与顺序会话内串行保因果,会话间并行保吞吐
五种队列模式用户意图的精确表达collect / steer / followup / interrupt / queue
心跳机制Agent 缺乏时间维度的主动性定时唤醒 + HEARTBEAT.md 清单 + HEARTBEAT_OK 静默
Cron 任务需要在精确时刻执行的确定性任务标准时间表达式,可选独立会话

泳道模型与心跳机制共同赋予 Agent 传统软件不具备的两种属性——会话内有保障的因果顺序,以及时间驱动的自主主动性

区别就在这里:工具只在你调用时存在,等着你来激活。协作者有自己的节律,在你需要之前就已经在做该做的事。没有泳道模型,并发状态下的 Agent 是不可信赖的;没有心跳机制,Agent 只是一个被动响应的工具。

HEARTBEAT_OK 的静默设计说明:主动性的核心不是"更频繁地打扰用户",而是"只在有意义时发声"——克制本身也是能力,是让主动性机制能够长期存活的工程智慧。

第六章 统一网关