乐于分享
好东西不私藏

修改一条配置,我缓解了 OpenClaw 的偶发性痴呆

修改一条配置,我缓解了 OpenClaw 的偶发性痴呆

最近我在用 OpenClaw 跑一个多 Agent 的内容生产流程。这个流程本身不复杂:`director` 负责总调度,`scout` 负责信息采集,`analyst` 负责选题筛选,`editor` 负责内容协作。

但在真实使用过程中,我遇到了一个很难受的问题: 

Agent 会偶发“发呆”。

这里的“发呆”不是某一个渠道特有的问题。它在 Telegram、飞书、企业微信等多个渠道里都能复现,但是是偶现。最典型的表现是:

– 某个阶段明明已经完成

– 用户也已经给出了下一步指令,比如“定稿”或“继续”

– 主调度 Agent 却停在原地,像没收到一样

– 过几分钟后,或者你再补一句“继续”,它又恢复了

这类问题非常容易被误判成渠道问题、网络问题,或者模型响应慢。但如果只停留在表象,就很难真正解决。于是我开始系统排查这件事。

一、问题发现:不是“没执行”,而是“执行链被打断了”

最开始我的直觉是模型限速,或者某个渠道对长消息、群消息的处理不稳定。  

但对照日志之后,我很快发现,问题不是这么简单。

首先,消息本身是正常进入系统的。  

其次,子 Agent 也确实在工作,`scout`、`analyst`、`editor` 都能在对应日志里看到实际执行记录。

真正异常的地方在于:  

主调度 Agent `director` 在一些关键交接点上,被内部消息打断了。

比如一个典型场景是:

– `editor` 已经完成了 Step3 草稿撰写的操作

– 系统也已经把“草稿完成,请确认”发给了用户

– 用户回复了“定稿”

– 按理说,流程应该直接进入 Stage 4

但日志显示,在这个时间点附近,`director` 并不只收到了用户的“定稿”,它同时还收到了来自内部 Agent 的完成通知,以及一些像 `NO_REPLY`、`ANNOUNCE_SKIP` 这样的控制信号。结果就是:用户的真实指令和系统内部的控制消息混在了同一条会话里,主流程节奏被扰乱了。

所以问题不是“模型没干活”,而是“系统在错误的时机,把错误类型的消息送进了主会话”。

二、问题分析:多 Agent 协作并不只有一条简单调用链

把日志和源码对起来看之后,我意识到,OpenClaw 的多 Agent 协作远比“一个 Agent 调另一个 Agent,拿结果返回”复杂。

至少在这个问题里,系统内部同时存在两条消息链:

1. `sessions_send` 触发的 **Agent-to-Agent 通信流**

2. 子任务完成后的 **subagent completion announce 流**

第一条链路的特点是:  

`director` 把任务派给子 Agent 之后,系统并不是只等一个结果回来,而是会在拿到 `reply` 后,再跑一轮 Agent-to-Agent 通信流,用于内部补充、确认和最终 announce。

第二条链路的特点是:  

子 Agent 完成任务后,系统会把这次完成包装成 `task_completion` 事件,再决定是直接投递,还是先进队列,稍后再送回主会话。

单独看,每一条设计都有合理性。  

但当主会话仍然处于活跃状态,而用户又恰好在这个时间点发来新消息时,问题就出现了:

– 用户消息正在进入 `director`

– 子 Agent 完成通知也正在进入 `director`

– Agent-to-Agent 通信流里的内部控制消息也可能进入 `director`

这样一来,主调度会话看到的上下文就会被污染。

下面这个时序图,比纯文字更容易说明问题:

这也解释了为什么问题会在多个渠道出现,而且不是必现。  

根因不在某一个渠道,而在系统内部消息编排本身:只要用户输入和内部完成通知在时间上重叠,就有机会触发这种串扰。

三、根因定位:问题不在模型,而在消息编排和完成通知叠加

继续深入之后,问题开始变得清晰。

我确认到一个关键事实:  

`sessions_send` 在拿到子 Agent 的 `reply` 之后,不会立刻把事情结束掉,而是还会继续启动一条 Agent-to-Agent 通信流。  

这条链路里,系统会显式构造类似 `”Agent-to-agent announce step.”` 的内部消息,再把它作为 `inter_session` 消息重新送回 Agent 会话。

与此同时,subagent completion announce 流也在工作。  

它并不是简单“来一个就发一个”,而是带有队列模式、debounce、direct/queue 分流和失败重试逻辑。

于是,在某些时间点上,`director` 会同时面对三类输入:

– 用户真实输入

– 子 Agent 任务完成后的 `reply`

– 系统内部的 announce / 控制信号

这三类输入如果在同一条 session 中交错,就很容易造成“看起来像发呆,实际上是在处理内部消息”的现象。

更关键的是,我在日志里还看到了长期反复出现的一类告警:  

subagent announce 在 `120000ms` 后超时,然后再按 5 秒、10 秒、20 秒的节奏继续重试。

这说明用户感知到的“停住几分钟”,有一部分并不是模型在思考,而是完成通知这条内部链路自己在超时和重试。

所以最后定位出来的根因不是某个渠道、某个模型,甚至也不是单个 skill 的提示词,而是下面几个因素叠加:

– `sessions_send` 会触发 Agent-to-Agent 通信流

– subagent completion 也会回告主会话

– announce 链路存在较长超时和重试

– 用户真实消息和内部完成通知可能撞在同一时间窗口里

这是典型的运行时消息编排问题。

四、我去 GITHUB上做了一下病友交流

为了确认这不是我本地环境的偶发故障,我又去翻了 OpenClaw 官方 GitHub 的 Issues。结果发现,这类问题并不少见,而且并不局限于某一个渠道。

https://github.com/openclaw/openclaw/issues/68414 讨论的是“新消息在 agent 完成当前 turn 的窗口进入后被静默丢弃”

https://github.com/openclaw/openclaw/issues/38300 讨论的是“subagent completion announce 降级为只发频道、不回注 parent session,导致父 Agent 不继续执行”

这些问题虽然表面症状不同,但底层都指向同一类系统性风险:用户真实消息、内部完成通知、会话注入与 announce 流之间存在竞态条件。换句话说,这不是某一个 prompt 写得不够好,而是运行时消息编排本身需要更严格的边界控制。

但是很遗憾,到目前为止,我在github上还没看到官方的回应

五、改动前的风险评估:先看清配置职责,再决定动哪一个

没办法,目前只能自己改,于是我先去研究了openclaw.json里最相关的两个配置项:

1. `agents.defaults.subagents.announceTimeoutMs`

这个配置控制的是 子 Agent 完成后,内部 announce / completion delivery / wake 这条链路允许等待多久

如果这个值过大,系统在 announce 卡住时就会拖很久;  

如果这个值过小,又可能导致完成通知还没来得及送达,就被提前判定失败。

我当前这套环境里,这个值没有显式覆盖,等于使用默认值 `120000ms`。从日志看,很多 announce 卡顿链条正好就是在这个时间点上超时。

2. `session.agentToAgent.maxPingPongTurns`

这个配置控制的是 Agent-to-Agent 通信流里,请求方和目标 Agent 最多允许来回几轮内部 reply

注意,它影响的是内部通信流,不影响子 Agent 真正执行任务本身,也不影响第一轮 `reply` 的生成。

我当前配置里,这个值是:

`session.agentToAgent.maxPingPongTurns = 3`

从行为上理解,就是:  

一次 `sessions_send` 完成后,系统最多还允许 requester 和 target 再来回三轮内部通信。

3. 这两个配置的职责差异

研究完之后,我的判断是:

– `announceTimeoutMs` 影响面更大  

  它管的是整个 subagent completion / wake 链

– `maxPingPongTurns` 影响面更聚焦  

  它主要决定 Agent-to-Agent 通信流会不会过度来回

也就是说,如果改错了 `announceTimeoutMs`,后果可能是“完成通知丢失”这种隐蔽问题;  

而如果改 `maxPingPongTurns`,更像是在减少内部 chatter,风险相对可控。

所以从工程角度看,第一刀不应该先去碰 `announceTimeoutMs`,而应该先从 `maxPingPongTurns` 这种更聚焦的参数下手。

五、最后改动:先做最保守的一刀

最终我没有同时改多个参数,而是只做了一个最保守的调整:

把`session.agentToAgent.maxPingPongTurns`从 `3` 调整为 `1`。

为什么不是直接改成 `0`?

因为改成 `0` 相当于彻底禁用 Agent-to-Agent 通信流里的多轮内部往返,动作有点太激进。  

而改成 `1`,仍然保留最小限度的内部确认能力,但不会让系统在主会话背后继续跑太多轮内部交互。

这个改动的目标很明确:

– 减少内部通信轮次

– 降低控制信号回灌主会话的概率

– 降低用户消息和内部 announce 撞车的可能性

同时,我暂时没有动 `agents.defaults.subagents.announceTimeoutMs`。  

因为这项配置的影响面更广,需要在观察一轮实际效果之后,再决定是否要继续收紧。

配置的改动是上周五,4 月24 号, 周末两天我运行了大概十几次各类复杂skill, 中间复现了 4 次,相比之前几乎每次运行都会发生, 可以说症状已经大为缓解. 我也会持续关注github上几个issue的进展. 

六、这次排查给我的启发

这次排查最有价值的地方,不是“找到一个参数改掉了”,而是让我重新理解了多 Agent 协作的复杂之处,它在于:

– 它们的完成通知怎么回流

– 内部控制消息怎么隔离

– 用户真实输入在什么时刻拥有更高优先级

– 队列、重试、announce、会话记忆会不会互相放大

如果这些基础设施没有处理好,那么业务上再漂亮的 Agent 分工,也可能在运行时被内部消息串扰拖垮。

所以,多 Agent 系统的稳定性,最后拼的往往不是提示词有多强,而是消息编排和运行时边界是否足够清晰。

如果你的龙虾也偶发此类恶疾,不妨试试我的药方,如果见效,也请推荐给您的好友. 
最后,我是金馆长,一名专注在数字员工,自媒体运营的苦哈哈AI 创业者,如果您有兴趣合作,探讨技术,甚至只想聊天,欢迎加入我们的交流群: