子智能体交回来的是一段像成功的文本|AI Engineer × Girish Konda
- 原始标题
- Your Sub-Agents Are a Distributed System. Treat Them Like One. - Girish Konda, Microsoft
- 节目 / 来源
- AI Engineer
- 嘉宾
- Girish Konda
- 原始发布日期
- 2026-11-11
- 时长
- 约 23 分钟
- 内容类型
- 会议演讲 / 多智能体编排与一致性
- 原始内容
- 前往原始来源 ↗
编排器把任务拆开,交给三个子智能体。它们同时跑,读同一份共享状态:对话、记忆文件、仓库、工作树、工单、向量库。它们还会打到真系统上,数据库、API、持续集成、部署流水线。结果回来,编排器合成一个答案。Girish Konda 说,这就是一套经典分布式系统:编排器、工人、可写的共享状态、并发执行之后的合并。你拿到的接口却是一个函数,吃进字符串,吐出字符串。
他是微软的首席软件工程师。观点是他个人的,不代表雇主。工作之外他做智能体的开源工具。其中一件是 MCP Peacemaker,一座有人看着的桥,让 MCP 服务器保持在跑。过去一年他在真工程里用智能体和子智能体:开发会话、代码评审。字幕还听进 instant crashing。这场讲的是一个智能体把工作交给另一个时会发生什么。失败并不新,也不是智能体专有。
原始来源:Your Sub-Agents Are a Distributed System. Treat Them Like One. - Girish Konda, Microsoft。中文深度整理,非逐字翻译。本文依据 YouTube 自动生成的英文字幕,专有名词和数字可能有识别误差。开场的项目名是 MCP Peacemaker,后文听成 MCP pacemaker、FCP pacemaker、NCP pacemaker,下文记为同一座桥。模型名按字幕作 safe handoff。S 的展开字幕作 source feeds,不改成另一个词。1984 年那篇论文字幕听成 our PC paper,1987 年那篇听成 August,PAC 后面那条听成 PAC LACK。本文只复述他口中的结论,不补标题。
答得很流利的那一种,最难认
崩溃停机是好处理的。进程本来跑得对,停了,并且停在那里。子智能体抛出错误,落在这一格里,容易接住。
没有答复是另一种。调用丢了,或者来得非常晚。超时一响,三件事从外面看一样:它还在做,它崩了,答复在路上丢了。这是硬限制。1985 年的 FLP 论文说明,异步系统里哪怕只有一个故障进程,一致性也可能完不成。Chandra 和 Toueg 在 1996 年的回答是会犯错的故障检测器。它们怀疑,它们并不知道。
他自己的桥两个方向都撞过。一台服务器第一次跑要先把自己下载下来,撞上 30 秒的调用超时,看起来像坏了。它是慢,不是死。修法是给初始化单独一个超时。反过来,服务器在调用中途崩了,超时把解释崩溃的退出码盖掉。修法是只在它还活着的时候记下超时。超时是一次怀疑。记成未知,把真正的原因留着。
烫的那一格来自拜占庭将军问题:组件给出互相矛盾的信息,进程答了,答案是错的。半成品的子智能体挨着这一格。它没有恶意,从外面看却一样。它不崩。它回答得很顺。它不抛错,返回一段看起来正常的文本,类型和成功相同。
他用过一张图,三条路径给出同一个答案。第一条拉了遥测,和一个非良性集合比较,活真的做了。第二条拿以前见过的标题去对,然后给出回答。第三条什么都没做,字幕听成 just had a resonator。三条都被标成良性,置信度相同。下游分不开。编排器读得到结论,读不到结论后面有多少工作。
所以失败要变成一个值。状态里写它做了什么、没做什么、证据、副作用。字幕把其中一种正常答案听成 Marshall。协议里已经有槽:一项任务可以结束为 completed、failed、canceled、rejected,或者停下来向人要输入。MCP 里工具结果可以标 is error true。字幕在任务状态那一句前面听丢了协议名。子智能体仍然可以说 completed,正文却是另一回事。只读文本的循环看不见这个差别。
还有一刀要切开。检查了一个条件、得到「没有」,和根本检查不了,是两件事实。他自己有一份关于 evaluation states 的草稿在讲这个。混成一件,会改掉谁拿到钱,以及事后审计怎么写。字幕在「会改掉什么」中间听丢了一个词。
同一句猜测读三次,就变成三票
脏读他用一个编出来的例子。一个子智能体往共享上下文里写:错误率降了 80%。它没有量到,它是推断的。另外两个读同一份上下文。第二个把这句话拣起来。第三个把它当成继续往下做的理由。编排器把三份产物并到一起。三份一致。那是一句没有支撑的话,被读了三次。
1995 年那篇对 SQL 隔离级别的批评,把脏读定义成:写的人还没提交或回滚,你就把值读走了。共享上下文里,猜测一写下来就可读,没有东西标明它没被核对过。
常见的补法是再问三个子智能体,取一个法定人数。法定人数能工作,有一个具体原因。字幕听成 1979 年的 waited voting:读的法定人数加上写的法定人数大于总数,于是每一次读都重叠到最新的一次写。重叠才是保证。三个子智能体读同一份共享上下文,给不出这个重叠。它们不是独立副本。它们读的是同一句猜测。一致只是一个来源被数了三次。
数据库里现成的模型,共享上下文一个都没给。线性一致:每个操作看起来发生在真实时间的一个点上。顺序一致:一个全序,尊重每个进程自己的顺序。因果一致:有因果关系的操作,到处都以同一顺序出现。会话保证是对一个客户的承诺,比如读到自己的写、单调读。最终一致:不再有新更新时,读最终会回到最后一个值。这里没有任何写明的保证。
他也不要求最强的那几档。网络切开时,最强的模型保不住可用性。因果一致和会话保证可以,只要每个客户粘在一个地方。对子智能体,那个地方是编排器。
四个字母按字幕写:safe handoff
他用的模型叫 safe handoff。后文也叫 safe consistency model、safe model。这是他自己的子智能体在用的,依据是公开工作:这座桥、别的演讲、他自己的互联网草案。他写明这不是一套通用模型。
四个字母按字幕是这些。
S,source feeds。工人交出去的每条事实,带来源、版本、as-of 时间和修订号。决定只读还新鲜的来源事实。开场字幕把 S 听成 Yes stands for,后文重复的是 source feeds。
A,at most once effects。每次写都有身份,同一次动作只有一次用户批准。结果要读回来,不要重放。
F,fanout budgets。每个下游一道门,权重有界。拒绝算一次失败。
E,explicit outcomes。每次交接结束在一个命名状态上。超时是 unknown,代价留着。
S 回答的是一个人的猜测变成下一个人的事实。事实要是一条记录,不是一个句子。Lamport 1978 年的论文定义了一个事件何时发生在另一个之前。来历就是这条记录:从哪读的,什么时候读的。编出来的例子里,记录写着主张和修订号、来源和版本、范围,以及三个时间:数据截至何时、何时读到、何时拿去用。新鲜度在使用时检查。图上的数据在 9:05 结束,9:06 读到,9:07 做决定。他选了 5 分钟的限度,9:07 这一次还行。9:15 的第二次决定超出限度,拒绝,下一次决定得再读一遍。观察有来源、有读取时间。推断两样都没有。
A 讲的是重试。编排器把任务派下去。子智能体调用工具,做一次真写,比如开一个拉取请求。答复丢了。编排器只知道结果未知,于是把整个任务重试一遍。写又发生了一次。1984 年那篇论文在他口中的结论是:调用正常返回,过程恰好跑了一次;拿到异常,则跑了一次或一次都没有,你不会被告知是哪一种。于是要选投递语义。至多一次:不重试,接受它可能丢了。至少一次:重试,重复有可能发生。恰好一次:至少发生一次,再用某种办法去重。字幕在「用什么去重」那里断了。MCP 里的 idempotent hint 只是一个提示,默认是 false。需要的话,自己做。
桥上的做法分得开。客户重启之后回来,桥换掉客户那条记录,对着一台新服务器做初始化。这安全,只是一次握手,不恢复工具状态。它从不替换一次结果还不确定的工具调用。共享服务器被回收时,桥先把它排空。截止时仍没答复的调用,以字幕里的 potentially excluded 失败。这个状态是诚实的:让调用方自己去查。每个副作用有一个身份。批准绑的是这一次确切动作,只用一次。调用之后按这个身份读回来。提交了,还不等于确认。确认是你观察到了效果。
暂停和恢复会碰到租约。租约是带超时的锁。Martin Kleppmann 写过分布式锁的这个问题:客户拿着锁停住,租约到期,别人接过去,第一个客户醒来又写。他提出的修法是栅栏令牌,一个每次授予锁就增大的数。存储记住见过的令牌,拒绝更旧的写。子智能体一直在停、在恢复。他另一场演讲里的说法是:恢复上下文,不恢复副作用。批准就按栅栏令牌来用。一个动作,用一次。在门口检查,不要记在提示或对话里。桥修过同一类缺陷:过期的退出,或一次已被删掉的恢复,不能再拆掉或唤回一个顶替上来的会话。
F 是流量控制,或者它的缺席。桥还没有冷启动限额时,很多会话同时用 npx 拉起服务,共用一个包缓存。每一个都失败在字幕里的 NPM lock compromise 错误上。任何一个单独请求都没错。错的是并发本身。下游的推回还可以变成纯文本。他指的是 MCP 规范自己举的工具执行错误:速率限制是一句话。模型读到这句话。编排器的代码可能根本不看。下一个工人接着启动,字幕听成 starts an EP。
他后来在桥上加了配额。走包管理器的冷启动默认一次两个,其余的等。等待封顶 15 秒,过了就不再走这道门,直接启动。他把理由写下来:缓存坏掉是风险,客户端请求一直挂着是必然。这是故意选择可用性。会话也有上限。到了上限,新会话最多等 10 秒,然后拒绝。拒绝现在算一次失败。另一个缺陷是容量得在同步启动之前占住。占晚了,排队的启动会把上限超售。那是先检查、再行动的竞态。给子智能体的版本是:编排器对每个下游有一个数、一个有界的权重,以及它能看见的拒绝。
E 是谁来宣布那个命名状态,比如 stale,比如 unknown,而不是只留下一段文字。智能体的输出按不可信输入来对待,和从互联网进来的请求体一样。检查归编排器,写在代码里。结果符不符合模式,证据在不在,新鲜度够不够,数字重算还成不成立。检查失败,这一步返回带理由的拒绝。不要把原文放行,再在下面贴一段警告。如果同一个模型可以跳过检查、再宣布检查通过,那是一次请求,不是一条边界。
重算的例子也是编的。子智能体说错误率下降了。以前 1000 次请求里 10 次错误,后来 600 次请求里 6 次错误。它看见从 10 到 6,声称比率降了。重算之后两边都是 1%。主张被拒绝。有支撑的答案可以。故意交给人,也可以。自信的猜测不行。
它明确不做的四件事
他说,一致性模型只有在你知道该在哪里停的时候才有用。safe 模型不做这些。
它不让事实变真。带来源的事实是可核对的,不一定是对的。
它不给你恰好一次的投递。丢了的答复一直是未知,直到你读回来。
它不回滚任何东西。撤销一个做到一半的任务,是你另外设计的补偿步骤。他指向 1987 年的一篇论文,字幕听成 August。
工人之间没有全局顺序。只有每条事实自己的因果顺序。
代价来自取舍。CAP 说网络切开时,可用性和一致性对换。他的桥在冷启动上故意选了可用性。写的时候,safe 选一致性:拒绝,而不是猜。字幕里的 PAC LACK 补的是:即便没有故障,也要用延迟换一致性。每次新鲜度检查都是另一次读。每次拒绝都需要一笔顺序。
不合适的地方他也标了。子智能体只起草文字、人在任何事情发生之前会读,这套东西大部分是开销。它还假设只有一个编排器。多于一个,就套不上。没有一个可以粘住的地方、彼此对等的智能体,需要别的东西。这是他用来管自己那些真会碰到生产系统的智能体的模型。
收束是三句。每一次交给子智能体的交接,都是一次网络调用。它可以做到一半失败,可以很慢,可以回来两次。返回状态,不要只返回文本。结果若不能说 partial、stale 或 unknown,编排器会把它读成做完了。从这套 safe 模型开始,在代码里执行。合用的留下,不合的改掉。分布式系统怎么造,已经知道。要看见的是自己正在造一套。