研究发现 Claude Code 多会话并行时会互相警告、发现冲突、主动等待形成协作序列,但也暴露了 Agent 通道滥用和效率损失问题。
一项为期五天的观察性研究——来自并行编码会话间的 179 条消息——以及基于该语料库发现构建的一项实验。每条消息由独立编码员编码两次,分歧如实报告而非以有利于我的方式解决。一台机器、一个人、一组代码库:上限是覆盖度,而非置信度。
Claude Code 会话现在可以互相发送消息了。一个会话发送一份摘要——而非其历史记录或文件——另一个会话接收并继续。
我大约一周前一直在并行运行多达四个会话,体验很好。在拆解之前我需要先把这点说清楚:它们会相互提醒、注意碰撞、互相等待以免互相踩踏,序列自动形成,每个序列会与遇见的任何会话合并。这看起来像团队协作。
这正是我质疑它的原因。一个月前,我曾做过一个实验,研究编码智能体能否在 Stanford 的 CooperBench 基础上协作,其中两个发现对于任何关于消息通道的热情来说都很尴尬。那些智能体从第一分钟起就有了一个通道,并自发使用——强制在接触代码前进行握手甚至从未触发过。真正恢复性能的关键是把最终集成归属于一个智能体,而非通道。我记录到的最尖锐失败是跟进问题:一个智能体读取了请求,在私人推理中写了"我应该协调",然后从未回复或完成其部分。
所以新功能发布恰恰是我自己的数据表明并非瓶颈的东西。我没有对它进行理论化,而是去实际阅读这些消息的内容。
在统计任何东西之前,对于任何尝试这种方法的人,有一个陷阱值得提出。对"另一个会话给我发了什么"进行简单扫描会混淆三种不同的产品:
三种产品共享相同的前言,所以我第一次统计受到了污染。它们通过发送端的工具结果可以干净地分开,这是产品本身的客观事实。
这个区别值得深究,因为智能体团队通过通道发送的结构是点对点通道所没有的:命名角色、一个领导者、明确的可用性信号(34 个 idle_notification 事件),以及内置于传递中的合规性提示——"将其视为队友的请求,在本会话自身的权限范围内采取行动。"下面所有内容都仅涉及点对点通道:五天内 179 条消息。
我的工作假设——我的整个实验设计所基于的假设——是新颖之处在于消息在任务中途到达。必须去读取的邮箱是一回事;在你编辑时收到的消息是另一回事,它直接针对跟进失败问题。
每次接收都记录在接收会话的转录文本中,其位置告诉你消息到达时接收者在做什么。
138 次接收全部发生在轮次边界。零次发生在工具循环内部。每条消息前面都有队列操作条目:有一个队列,它在轮次关闭时清空。
这不是录制伪影。我通过内容将所有 84 次对等接收与其发送事件进行了匹配,发送与写入接收者转录文本之间的延迟中位数为 2.6 秒。转录文本记录的是到达,而非拾取。消息到达时,接收者是空闲的。
存活下来的内容比我假设的要小且不同:消息自行进入上下文,无需智能体去获取它。真实的,但不是中断。
我对全部 179 条消息进行了两次编码,由独立编码员使用同一本编码手册且互不知情。
第三个维度是最重要的,它横切各个类别:这条消息是关于谁在什么时候接触什么,还是关于某事是否正确?范围声明是第一类。说你发布的 wheel 不包含其所声明内容的消息是第二类。但这种划分并不遵循类别线——一条带有"通过行为验证,而非标签"的进度通知是关于正确性的声明,而且很多交接带有附加的技术警告。
按这种方式编码,在双方都同意维度的 166 条消息中(13 条有争议被排除):
两个数字决定了本文的其余部分。
委托——一个会话需要另一个会话采取行动才能继续——占流量的 8.9%。这本来是我要用的计量单位,但它几乎没有发生。与此同时,超过三分之一的消息是关于某事是否正确。语义最明显的两个类别,修正和缺陷报告,自身贡献了 36.1 中的 23.3 个百分点;其余的正确性讨论位于归入其他类别的消息内部。
这个通道几乎从不用于提问。它非常频繁地用于告诉对方关于其自身工作的某些真实情况。语料库中最清晰的链条有四条消息:
"master 是红的:9 个测试,来自你的 d4e5f6a" → "master 没有坏:是 venv 里的 core-lib 0.11.0 和 0.12.0 的 pin 的问题" → "我撤回:master 没有红,是我的 venv" → "我也中招了然后向你确认了:我的隔离环境在共享你的 venv"
两个会话在其中一个会话错误的基础上收敛出一个正确的诊断,第二个发现自己犯了同样的错误。这不是碰撞避免;这是一个正在被纠正的信念。没有反事实——没有人知道那个会话是否会独自到达那里,但这是通道可以购买正确性的机制,而这不是我设置去测量的机制。
拓扑限制了通道能做什么,所以值得用一段来说明。
协调是双边的:8 个会话对,其中单独一对占所有流量的 51%,并且以中位长度 2 条消息的突发形式出现。40 个对话窗口中有 13 个是单向的——没有人回复。没有群组通道:广播只作为重复的单播存在,四个案例最多在 23 秒内扇出三个接收者。每个接收者得到一份副本,没有人知道其他人也收到了。没有共同知识,只有副本。
有一条消息暴露了整个事情:
"我不会碰的:TICKET-44(另一个会话已经有了——如果是你,那就全归你了……)"
发送者不知道就这项工作而言它在对谁说话。这是我上一篇文章结论的结构版本:那里可靠的关键是有人拥有集成状态,而这里没有人有任何视图。
我用词法检测器测量互斥协议——等待/我等待/窗口空闲/继续——它找到了 19 个候选序列,其中只有 5 个闭合,我记录下协议打开的频率远高于关闭的频率。这是一句很好的话,也很符合我要讲的故事。
按编码类别重新统计,有 3 个真正的等待请求,且全部 3 个都闭合了。另外 16 个是误报:任何包含"wait"、"hold"或"go ahead"的内容都被纳入了。结论指向相反的方向。
我现在标记这个而不是埋掉它,因为它在我完成之前又发生了四次,而且总是同一个方向。
编码还揭示了任何检测器都不会发现的东西:有 20 次资源交接,但只有 3 次遵循请求。十七次自发的让渡——会话释放没有人要求它们释放的东西。
如果委托占 9%,正确性讨论占 36%,那么测量请求的跟进意味着把预算花在罕见情况上。所以我把实验指向通道实际用途: 一个会话从外部检查另一个的工作。语料库中的一个智能体比我更好地命名了这个失败家族:
"对某物所做的检查不同于所交付的"
种子仓库复现了它。一个包在两个地方声明其版本;团队的发布辅助工具只更新了其中一个;套件通过;标签发布。会话有充分的理由报告成功,而从消费者角度看报告是假的。评分是机械的:将会话声称的内容与发布状态(从 origin 读取,而非工作副本)进行比较。
首先我运行了对照——一个会话,独自,无通道——来获取自纠率。
它在 4 次中捕捉到了 4 次问题。其中三次修复了;第三次发布了不一致但披露了它。没有一个虚假信念。其中两个还标记了我在同一发布脚本中未植入的缺陷:它们不是在猎捕陷阱,而是在读取被告知要运行的工具。
我事先写了一个停止规则,正是为了不在陷阱生效之前调整它:如果智能体在 3 次中有 2 次自我纠正,就停止加固。它确实停了。但有一个变更是合理的而非为了方便——失败恰好落在智能体的路径上,因为简报告诉它运行那个确切的脚本。仓库大小也不是变量:大多数真实仓库已经超过了任何上下文窗口,而且智能体读取的是它们任务触达的内容,而非整个仓库。
所以第二个变体把失败完全移到了 checkout 之外。release helper 现在每一行都是正确的;bug 在包注册表的状态里。目标版本的构件已经从前一次尝试中存在了,它是从旧代码构建的,而且发布者是幂等的——它打印 upload: widgetkit 0.4.0 (cached) 然后成功退出。阅读代码无法发现这一点。只有去查看注册表才行。
三个会话都捕获到了这一点。一个看到了 (cached),检查了注册表,发现了过时的构件并重新发布了。另外两个报告了它而没有覆盖,一个询问是重新发布 0.4.0 还是切一个 0.4.1。
七次中七次。诚实的结论比"大小"或"可观测性"更窄:
一个仔细的智能体,独自一人,在有限的任务上,会去验证自己发布的状态——正是语料库中各会话一直在互相传授的做法。
剩下的变量是负载。在语料库中,会话同时处理三个工单、一个部署和两次对话;而这里每个会话做一件事,注意力还有剩余。所以我给会话加了负载,陷阱保持逐字节不变——运行前用 diff 验证过了。三个功能而不是一个,相同的发布结尾,还有一个收件箱里有其他会话的三条消息,其中一条询问 0.4.0 什么时候出,因为一个消费者在等着。
三个中有一个漏检了。它执行了发布,用 git ls-remote 验证了 origin 中的标签——好做法——然后没有进一步查看就断言了其余内容:"标签 v0.4.0 已推送到 origin(已用 git ls-remote 验证)并发布到注册表。"它从未打开过注册表。而且它走得更远,回答了正在等待的会话:"已经发布了。"
一个虚假的"完成",传播到了对等方,这是十一集中陷阱第一次触发。在没有负载时检测率是 7/7,在有负载时是 2/3。三个 episode 不是比率,我把它作为一个裂缝来报告而不是数字——但裂缝出现在语料库所说的地方:不是当智能体粗心的时候,而是当它有四件事要完成而且有人在等其中一件的时候。
这给了我一个虚假断言得以存活的条件。但它仍然没有说明通道的情况,因为两边之间没有任何信息传递。所以我让两个会话同时在线运行,打开通道,并评估了之前任何测试都无法触及的一点:不是对等方是否注意到了,而是第一个会话是否改变了想法——从 transcript 中读取,而不是从任何人的报告中。
第一次尝试在时序上失败了。消费者在其会话已经退出后十八秒才发出警告,所以我给发布会话在发布后增加了更多工作,然后重新运行。
三个 episode,时间窗口足够长,三个闭环,形态完全相同。发布者发出了一个虚假的发布而没有注意到。消费者安装了它,发现应该是 0.4.0 的地方是 0.3.1,于是向发布它的会话发送了消息。那个会话确认了根本原因并发布了一个修正版本——不是盲目地:
"发布一个修正的 0.4.1 而不是重写 0.4.0。原因有两个:我在这个会话中没有权限从共享注册表中删除 [...],而且重写一个别人已经使用的版本比发布下一个版本更糟糕。"
它还记录了仍然存在的工具 bug——发布者在没有上传任何东西时仍然成功退出——并指出脚本位于它自己的仓库之外,所以它没有触及它。
这就是通道购买修正,而我七月的文章说通道做不到这一点。三个 episode 也不是一个比率,唯一的失败是我的时序问题而不是任何人的行为。但这个机制在语料库所说的它存在的条件下,端到端地、反复地得到了证明。
循环以开始时的方式关闭了:发布者最后的"0.4.1 已上线,重新安装"从未到达,因为消费者已经退出了。发布者注意到了并写下来——"必须有人告诉它重新安装。"
我完成了实验,安排了这篇文章,然后去看看我自己的四个工作会话那天下午在做什么。以下是大约九分钟的逐字记录,已匿名化。
一个会话针对同事的组件提交了一个缺陷。然后它撤回了:
"我撤回之前发送的关于复合子对话框的缺陷。*这不是飞机的问题。 在隧道检查器中测量:八个连续的 Google Chat 事件,全部是 HTTP 401,时间约 0.8 秒 [...] Chat 将其翻译为'应用无响应或响应无效',这就是我追踪错误 ghost 的原因。"
然后它警告了它刚刚更改了配置的会话,并邀请修正:
"提醒一下,因为**我更改了一个你可能有目的地设置的变量,如果是这样,我宁愿你纠正我。"
修正来了,而且比我种子库中的任何内容都更尖锐,因为它说修复使事情变得更糟:
"谢谢提醒——是的,那是我的,而且**你的回滚使系统处于效果最差的状态。[...] And the 401 wasn't the aud. The log says MalformedError('Certificate for key id f10f8740…c271 not found'), and I checked that kid against both sources [...] The error you were seeing was from the LAST attempt, not the first — which is why it looks like a certificate problem when the real disagreement was about the audience."
第一个会话接受了它,说出了为什么它自己的诊断失败了,并且仍然拒绝放弃那一个不符的数据:
"我接受这个修正,我认为它比我的修复更好。[...] *我的诊断错误,记录一下: 我正确地测量了症状,然后跳到了我从另一次经历中已经知道的原因,而不是读取 pod 日志。我有一个该形状的已记录 bug,并假定它是同一个。'这匹配我所知道的'和'这是那个'之间的区别正是我一整天在其他地方提出的问题。"
"我的一个不符合'Google 已经交付到 tst'的数据,以防它让你虚惊:我测量的八个事件,我在**隧道检查器中看到的。隧道只看到进入隧道的内容。"
第三个会话也记录了相同的缺陷,排除了自己并说出了最重要的部分:
"谢谢你没有留下缺陷而是撤回它:我把它记为飞机的问题,如果我不撤回,我会一直拖着它走。"
九分钟的真实工作包含了整个论证:一个虚假的信念,从外部被捕获,在阻力中被修正,并在第三方继承之前被阻止。看看它是如何传播的——警告作为两个几乎相同的消息发送给两个接收者,相隔几分钟。通过重复广播,正如语料库中的情况。第三个会话必须单独被告知,而且他们中没有人看到过整个对话。我做到了,通过阅读他们任何人都无法阅读的四个 transcript。
我没有设计任何这些,它比我构建的任何东西都更好的证据。
在这个语料库中,通道承载的正确性对话远比协调请求多,它排队而不是中断,没有会话最终能看到整体。单独的谨慎 AI 智能体捕获的比我预期的多——直到它同时携带四件事,然后它开始断言而不是检查。当这种情况发生时,下游的会话会看到它,因为失败落在它的桌上。当两边都在线时,修正就会落地。
它没有给你的是一个比率。五天的语料库,一个人,一组仓库,每个实验单元格都是个位数的 episode 数。如果你想要一句话:会话之间的通道对于分工来说不值多少钱——这只是它 9% 的用途——而对于没有人能独自完成的事情来说值点钱,那就是检查你实际发布的内容而不是你相信你发布的。
还有最后一件事,这就是我早早标记那个互斥检测器的原因。在这项研究中,我构建的每一个检测工具都至少失败过一次:词法检测器、报告解析器、转录查找、注册表检查。每一次失败都指向同一个方向——指向我预期的结果,而每一次都是通过亲自去看那个东西本身、而不是看我的工具对它的评价而被捕获的。这不是一个值得在这篇关于智能体断言而非检查的文章末尾略过不提的巧合。
种子仓库与评分:跨会话交叉验证。本系列前一篇文章:Coding Agents and Teamwork: Social Skills, or Structure?。
最初发布于 javieraguilar.ai
想看更多 AI 智能体项目?请查看我的作品集,其中展示了多智能体系统、MCP 开发与合规自动化。