子代理干完活,聊天却被它接管了

你在 Telegram 里让 Hermes 派两个子代理去查资料,然后去泡了杯咖啡。回来发现:子代理的报告没到,你原来的对话却“结束”了——接下来你在同一个窗口里发的每句话,都是子代理在回答,你原本那个会话像是被人顶替了。
这不是错觉。子代理真的把聊天抢走了。
一个很怪的现象
用 delegate_task 派出去的子代理,如果里面启动了一个后台进程(或者一条较长的前台命令跑到一半被提升成后台),这个进程会在结束时报一次“完成”。奇怪的事情就发生在这一刻:
- 完成后,和你对话的那个真实会话被以
session_switch(一个“用户边界”事件)结束掉; - 聊天路由被搬到了子代理自己的执行记录上;
- 于是父级自己的委派结果到达时,被当成已经终止的会话内容丢弃,你永远看不到那份报告;
- 你在同一个窗口里继续说话,接话的是子代理,而不是你原来那个会话。
同一个 bug 在 Discord(私信、话题、频道,macOS 和 Windows 上都有人报)、Telegram(私信和群)、Slack、Zulip 和 Feishu 上都被报告过。
一次“完成”是怎么把聊天抢走的
关键在于“这个完成属于谁”这件事上,系统记错了名字。
子代理里启动的后台进程,会把子代理自己的会话 id 记成这次完成的 pin(标记)。当它跑完,网关里的解析器拿到这个 pin,看到它指向一个还活着的会话,就把这个会话当成了这个聊天的所有者。接下来的一步是 switch_session:把聊天的路由搬到子代理的执行记录上,并以一个用户边界事件结束原来的对话。用户边界是“终态”——到了那一步,父级自己那条还在路上的委派结果就被判为“属于已结束的会话”,直接丢掉。
换个说法:子代理的进程完成通知,本来应该送给“派它出去的那个人”,结果它找的是“派它出去的那个子代理”。一层认错,整条链路走偏。
修复:完成归属于人,而不是子代理
修法的核心是一句话:被 pin 的完成归属于人类面对的那个会话——也就是通过 model_config._delegate_from 能找到的那个会话。聊天的路由永远不搬到子代理身上。
具体做法:
- 投递之前,先证明一次这条完成的归属,然后把结果交给解析器。只有当路由在这次证明之后又移动过,解析器才会重新读一次归属;
- 归属不确定时重试,而不是赌一把:准入(admission)之前重试不消耗投递尝试次数;准入之后,把这条完成放回投递队列;
- 用户边界在每一跳都保持终态语义,不会因为重试被“复活”。
顺带修正的还有子代理进程通知的去向:它现在遵循 delegation.surface_child_process_notifications 这个开关(默认关闭,文档里一直是这个默认值),并且投递到拥有该会话的那个 profile 里,包括被服务的 profile 的 api_server 会话。如果中途发生了一次 process_manage 交接,通知会重新归属给父级。
为什么修在网关,而不是某个平台
每个平台适配器只做一件事:在它自己的服务和网关的通用消息格式之间转换。哪个会话拥有某个聊天,是之后在共享的网关代码里决定的:一个按聊天路由键索引的会话存储、一个完成解析器、一个 switch_session。
子代理的完成在每个平台上都走这条路,所以每个平台都有这个接管问题——修复也就应该落在共享代码里,而不是给某一个适配器打补丁。这也是为什么这次改动没有一个平台专属的补丁。
什么没有变
- 用户边界事件仍然是终态,语义没有放松;
- 子代理进程通知默认仍然不显示:子代理的综合结果才是交付物,过程通知默认被抑制。想看到它们,把
delegation.surface_child_process_notifications设为true(消息会带上子代理归属); - 委派结果本身从来不会被抑制,这次改的是它们能不能正确送达;
- 这次改动是 #132827 的一次 salvage 重做,保留了原作者的提交(作者身份保留在提交历史里)。
状态与升级
这个修复(PR #136225,标记为 P1)2026-10-11 合并进 main。当前最新标签 v0.21.6(10 月 8 日发布)里还没有它,需要等下一个 release。
升级之后怎么验证:在 Discord 或 Telegram 里派一个子代理,让它跑一条会变成后台进程的命令,然后观察子代理完成后——
- 子代理的结果应该出现在你原来的对话里;
- 你接下来发的话,应该由你原来的会话回答,而不是子代理;
- 如果开了
delegation.surface_child_process_notifications,那条进程通知会带子代理归属出现在同一个对话里。
小结
这是个典型的“看起来像模型抽风、其实是记账记错”的问题:完成通知认错了收件人,于是整个聊天的所有权被移交出去,后续对话全跑偏。修好之后,后台任务再长,也不会取代和它的对话——这条边界在多平台、多用户、多子代理同时跑的部署里尤其重要。
相关阅读:子代理的实时编排与干预讲的是怎么在看得到子代理的情况下直接介入,会话边界指南解释了一个会话从哪里开始、到哪里结束,网关平台自愈则是平台连接出问题时的另一条恢复路径。