代理间协作架构:控制权最后归谁、上下文共不共享、同步还是要扛得住断线

比较 In-process Handoff、Agent-as-tool Delegation、Shared Queue/Blackboard 与 Federated A2A 四种代理间协作拓扑:控制权转移之后归谁、两个代理共不共享上下文和记忆、交换是同步的还是要扛得住断线、调用前怎么知道对方能做什么,以及转错人、专家答不全、写入没幂等、往已终态任务发消息这几种情况能波及多远、怎么收住。

一个代理走到某一步,接下来该由另一个代理来做,而不是再调一次普通工具。所有代理间协作方案都要回答四个问题:控制权转移之后归谁、两个代理共不共享上下文和记忆、这次交换是同步的还是要扛得住断线、调用前怎么知道对方到底能做什么。这四个答案,而不是用的哪个厂商的 SDK,才定义了拓扑。

想亲手走一遍四种拓扑、注入故障再恢复,打开互动 Lab:/system-design-lab/agent-to-agent-interoperability-architectures

有约束的设计问题

一个团队先后要给四种场景选代理间协作方案:一个专家该彻底接手接下来的对话,用户后续所有追问都该由它来回答;编排代理必须继续是回答用户的那一个,只是需要专业能力时找帮手;多个代理要按各自的节奏独立运行,消费者的数量还会随时间变化;要调用的另一个代理确实不受自己控制,可能是不同团队、不同厂商甚至不同框架构建的,双方无法共享内存或上下文。每一种该怎么搭?

四种 topology signature

架构控制权最后归谁共不共享上下文同步还是异步调用前怎么发现能力State owner适合主要代价
In-process Handoff接手的代理,从 handoff 那一刻起全部共享——整段对话历史一起转移同步,一个回合内完成调用方在构建时就按名字接好了目标代理转移过去的那份对话历史一个专家该彻底接手接下来的对话,不只是背后帮个忙转移之后原代理没有自动回退路径;历史整段带走,除非专门过滤
Agent-as-tool Delegation编排代理,自始至终不共享——只传这次任务需要的参数,只拿回一个值同步,像调用任何工具一样同样是构建时按名字接好编排代理自己维护的那份上下文编排代理必须继续对最终答案负责,只是需要专业能力时找帮手编排代理容易变瓶颈,要为每个专家的结果做校验和合成;专家之间不能直接协作
Shared Queue / Blackboard谁都不是——由接下来读到的那个代理决定只共享写进存储的那部分,没有任何隐式共享异步,写入不等读取靠订阅或轮询,运维层面决定,不是靠一份公开的能力文档那个共享存储本身(队列或状态存储)代理要按各自节奏独立运行,或者消费者数量会随时间变化订阅或轮询一旦落后,代理会在过时数据上继续工作而不自知;写入必须幂等
Federated A2A Protocol发起任务的一方,通过任务自己的生命周期状态跟踪完全不共享——协议就是为不共享内存、工具、上下文的代理设计的异步优先,也支持同步轮询调用前先查一份发布在固定地址的 Agent Card任务对象当前的状态,由持有它的服务端代理维护代理由不同团队、厂商或框架构建,可能跨越组织边界多一个协议和传输层依赖;每一个接受 task 对象的端点都是潜在的注入点

guardrail 和输入/输出校验是叠加在这四种拓扑之上的横切关注点,不是第五种拓扑;代理注册表/市场是 federated A2A 前面的一层发现机制,不是另一种交换模型;多代理编排框架(LangGraph、CrewAI、基于 Step Functions 的编排)是这四种拓扑的具体实现,不是额外的选项。它们在这里解释,不单独画。单个代理自己的工具调用循环见 Lab 的其他代理架构主题;MCP 作为代理和非代理服务之间的工具暴露协议见 MCP Tool Platform Architectures;通用的任务/工作流引擎见相关章节。

1. In-process Handoff:控制权连同整段历史一起转给另一个代理

从模型的角度看,handoff 就是一次普通的工具调用:「handoff 让一个代理能把任务委托给另一个代理」;「handoff 对 LLM 来说被表示成工具。如果要 handoff 给一个叫 Refund Agent 的代理,这个工具就会叫 transfer_to_refund_agent」。但这次工具调用和别的不一样,一旦执行,控制权就整个转移了:「handoff 发生时,就好像新代理接管了对话,能看到此前全部的对话历史」。转移的参数可以是结构化的:「input_type 描述的是这次 handoff 工具调用本身的参数。SDK 把这份 schema 作为工具的 parameters 暴露给模型,本地校验返回的 JSON,再把解析后的值传给 on_handoff」;还可以在真正交接前做删改:「输入过滤器是一个函数,它通过 HandoffInputData 接收现有输入,必须返回一份新的 HandoffInputData」。

控制权和历史就是这次转移的全部内容,中间没有第三方协调,也没有独立的任务对象。真正接住对话的,是那份随 handoff 整段转移过去的历史——接手的代理正是靠它才能连续回答用户接下来的所有追问,而不用再解释一遍前情。

典型事故:转 Refund Agent 和转 Billing Agent 这两个 handoff 工具的名字和描述写得太像,模型在一个含糊的请求上选错了目标,新代理接管对话、看到了全部历史,却发现这压根不是它的领域,用户不得不重新解释一遍——收窄每个 handoff 工具的名字和描述,让边界清晰到模型不会混淆,对含糊的请求先加一步确认,再给每个代理配一个能转回去的 handoff。一次转移没配输入过滤器,把用户账号验证细节这类敏感信息也原样带去了下一个代理——配置一个输入过滤器,在真正交接前把和接手代理任务无关或敏感的内容删掉。

2. Agent-as-tool Delegation:编排代理从不放权,把专家当工具调用、自己合成答案

这个模式因控制权保留而得名:「一个中心管理者/编排代理把专门的子代理当工具调用,并保留对话的控制权」。放到整个系统层面看,这就是集中式编排:「在集中式编排中,一个主导代理拆解任务、把子任务委托给专门的子代理」;「主导代理维护完整的任务上下文,跟踪每个子代理完成了什么,并把它们的输出合成起来」。

专家代理从来没有机会直接面对用户,也从来看不到编排代理维护的那份完整上下文——它只看到这次调用给它的那点参数,处理完只返回一个值。这正是这个模式的定位:编排代理必须继续对最终答案负责,只是在需要专业能力时找帮手,而不是把整段对话交出去。

典型事故:库存查询专家因为上游超时只返回了一半商品的库存数字,返回值本身没有报错、只是数组比预期短,编排代理没有校验就把这份残缺数据合成进了一份听起来很有把握的回答——给专家的返回值一个明确的形状,带上它实际覆盖了哪些条目,编排代理合成前先校验这个形状,缺失就明确告诉用户而不是悄悄当成完整数据处理。编排代理的提示词让它对每个请求都默认调用挂在它下面的全部专家代理,一个只需要查库存的简单请求触发了三个专家的调用——让编排代理先判断这次请求实际需要哪些专家,只调用相关的那几个。

3. Shared Queue / Blackboard:代理之间从不直接调用,各自读写同一个共享存储

这套拓扑里没有一条边是代理直接调用另一个代理,协作靠的是共享存储:「状态层为代理提供了一个共享的信息层,让它们不需要直接耦合就能协调」;「两个从不直接通信的代理,只要都读写同一个共享状态存储,照样能有效协作」。这意味着谁在读、有几个在读,写入方完全不需要知道——加一个新的消费者不需要通知任何人。

代价也在这里:这个存储必须扛得住重试而不出错。「幂等操作无论执行一次还是多次,结果都一样」;「对不能幂等的代理工具调用,编排层必须实现两种策略之一」。存储本身要能从失败中恢复:「持久化执行是工作流系统的一个特性,它保证一个工作流最终会走到一个终态」,重试配置能「为每个状态单独设置退避倍数、最大重试次数和抖动」。事件驱动的版本把生产者和消费者彻底解耦:「事件驱动的代理工作流把产生事件的系统和对事件做出反应的代理解耦开来」,一个具体例子是「Amazon SQS 在 EventBridge 和工作流执行之间提供缓冲」。

典型事故:一次「库存减一」的写入在网络瞬时故障后被自动重试,因为没有带去重键,同一个变化被套用了两次——每次写入都带一个去重键,协调层按这个键判断是不是同一次操作的重试,重试时直接跳过;对不能天然幂等的增量式更新,改成基于目标值的写入。一个消费者所在节点重启后订阅位置没有正确恢复,它在过时数据上继续处理却毫不知情——监控每个消费者的订阅位置和存储最新位置之间的差距,超过阈值就告警,消费者重启后先核对自己的位置。

4. Federated A2A Protocol:先按公开的 Agent Card 发现能力,再用标准协议交换任务对象

这是唯一一种默认双方互不信任、也不共享任何东西的拓扑:「A2A 让代理能够以自然、非结构化的方式协作,即使它们不共享内存、工具和上下文」;这个协议要解决的正是跨组织的协作问题:「要最大化智能代理的收益,这些代理必须能够跨越孤立的数据系统和应用,在一个动态的多代理生态里协作」;「让代理能够互操作,哪怕它们是由不同厂商或不同框架构建的,会提升自主性并放大生产力收益」。

发起任何调用之前,客户端代理先去一个公开地址查对方能做什么:「Agent Card 是由 A2A 服务器发布的 JSON 元数据文档,描述它的身份、能力、技能、服务端点和认证要求」,推荐位置是 https://{server_domain}/.well-known/agent.json;技能是这份清单的核心单位:「技能是代理能执行的一种能力单位」。真正的交换走标准协议:「A2A 用 JSON-RPC 2.0 作为所有请求和响应的 payload 格式」,「A2A 通信必须走 HTTP(S)」。每个发出的任务都有一条明确的生命周期:submitted(「服务器已收到并确认,但还没开始实际处理」)、working(「正在被代理主动处理」)、input-required(「代理需要客户端/用户提供更多输入」)、auth-required(「代理需要客户端/用户提供更多认证」)、completed(「成功完成」)、failed(「处理过程中因错误而终止」)、canceled(「被取消,例如一次 tasks/cancel 请求或服务器策略」)、rejected(「被远端代理拒绝,TaskStatus.message 可能包含错误细节」)。一旦到了终态就不能再继续:「一个已经到达终态(completed、canceled、rejected 或 failed)的任务不能被重启,往这样的任务发消息会得到一个错误」。长任务不用一直占着连接:「推送通知:通过服务器发起的 HTTP POST 请求把异步的任务更新发到客户端提供的 webhook 地址,用于长任务或断线场景」。

典型事故:客户端代理以为上次那个任务还在进行,直接往同一个 task id 发了条追加消息,但这个任务其实早就到了 completed 状态,规范写明这类请求会得到一个明确的错误——发消息前先查一次任务当前的状态,发现已经是终态就开一个新任务而不是复用旧的 task id,把这类错误当成状态已经变化的信号而不是当成需要盲目重试的普通失败。远端代理团队下线了一个技能却忘了同步更新 Agent Card,所有依据这份声明路由任务的客户端代理持续收到 rejected,直到有人注意到异常的失败率——按计划定期重新验证 Agent Card 声明的技能是否仍然可用,对持续失败的技能自动停止路由。

四种拓扑共同的底线

  • 说清控制权转移之后归谁。 Handoff 是接手的代理,Agent-as-tool 是从不放权的编排代理,Shared Queue 是谁都不是、由读到的那个代理决定,Federated A2A 是发起任务的一方通过任务生命周期跟踪。
  • 说清两个代理共不共享上下文。 Handoff 全部共享,Agent-as-tool 只传必要参数不共享,Shared Queue 只共享写进存储的那部分,Federated A2A 完全不共享——这决定了能不能直接把一份内部状态传过去。
  • 说清同步还是异步、能不能扛住断线。 前两种是同步的一个回合内完成;Shared Queue 写入不等读取;Federated A2A 异步优先,长任务不需要占着连接。
  • 调用前先知道对方能做什么,不要靠猜。 Handoff 和 Agent-as-tool 靠构建时接好的名字,Shared Queue 靠运维层面的订阅关系,Federated A2A 靠一份公开可查的 Agent Card。
  • 把每一个接收到的任务对象或返回值当成不可信输入。 无论是哪种拓扑,接收方都要校验内容的形状和真实性,而不是默认信任发送方。

故障与恢复

架构故障用户看到什么恢复
In-process Handoff模型把含糊请求转给了错的专家新代理接了一段它解决不了的对话收窄 handoff 工具描述;含糊请求先确认;配一个转回去的 handoff
In-process Handoff转移没配过滤器,敏感历史原样带走接手代理看到不该看到的信息配置输入过滤器,交接前先删改历史
Agent-as-tool Delegation专家返回残缺结果,编排代理未校验就合成用户看到看似完整实则缺失的回答校验返回值的形状;缺失就明确告知用户
Agent-as-tool Delegation编排代理默认调用全部专家简单请求的延迟和成本被无谓放大先判断实际需要哪些专家,只调用相关的
Shared Queue / Blackboard写入没有去重键,重试后套用两次数据被错误地变更了两次每次写入带去重键;协调层跳过已处理的重试
Shared Queue / Blackboard消费者订阅落后,处理陈旧数据代理的输出和当下真实状态脱节监控订阅位置落差;重启后先核对再处理
Federated A2A Protocol往已到终态的任务发消息客户端收到一个明确的错误发消息前先查任务状态;终态就开新任务
Federated A2A ProtocolAgent Card 声明的技能已下线任务持续被 rejected定期重新验证声明的技能;持续失败就停止路由

怎么选

  • 一个专家该彻底接手接下来的对话:In-process Handoff,把控制权和整段历史一起转移,收窄工具描述、配好输入过滤器和转回路径。
  • 编排代理必须继续对最终答案负责:Agent-as-tool Delegation,专家只接必要参数、只回一个值,编排代理校验并合成。
  • 代理要按各自节奏独立运行、消费者数量会变化:Shared Queue / Blackboard,写入必须幂等,加消费者不用通知写入方。
  • 另一个代理确实不受你控制、双方无法共享上下文:Federated A2A Protocol,先查 Agent Card 再用标准协议交换 task 对象,把每个 task 对象当成不可信输入。
  • 无论哪种:写下控制权转移之后归谁、共不共享上下文、是同步还是要扛得住断线。

回到开头的四类场景:专家该彻底接手走 In-process Handoff;编排代理要负责走 Agent-as-tool Delegation;代理独立运行走 Shared Queue / Blackboard;跨组织不共享上下文走 Federated A2A Protocol。

面试时这样回答

  1. 先复述约束:专家该不该彻底接管、编排代理要不要继续负责、代理是不是要独立运行、对方受不受你控制。
  2. 说交接路径:点名图上的边,例如「handoff 把控制权和全部历史一起转移」「编排代理只传必要参数调用专家、自己合成答案」「代理各自独立读写共享存储、从不直接调用」或「先查 Agent Card 再用 JSON-RPC 发 task 对象」。
  3. 说控制权或状态归谁:handoff 是转移过去的对话历史、agent-as-tool 是编排代理自己维护的上下文、共享队列是那个存储本身、federated A2A 是任务对象自己的生命周期状态。
  4. 说代价与一个故障:例如往一个已到终态的任务发消息会得到明确错误,修法是发消息前先查任务状态,终态就开新任务而不是复用旧的 task id。

一手证据