返回 AIPA 笔记
AIPA Day 30

handoff vs orchestrator-worker — 控制权转移的两种语义

handoff vs orchestrator-worker — 控制权转移的两种语义

2026-07-14
handofforchestratormulti-agent-pattern

日期: 2026-07-14 阶段: Phase 2 - AI-native 参考架构 标签: #handoff #orchestrator #multi-agent-pattern

核心问题

Day 29 把 Anthropic 的 orchestrator-worker 拆透了:lead 自己不干活,派子 agent 并行检索、回收压缩摘要、自己综合。但这只是多 agent 协作的一种范式。OpenAI Agents SDK 主推的是另一种——handoff(控制权移交)。两者长得像(都是「一个 agent 调动别的 agent」),但底层语义完全不同,选错会在 AML 这种强可观测、强可溯源的场景上踩坑。

今天回答三个问题:

  1. handoff 到底「转移」了什么?它和 orchestrator「调用」子 agent 的本质区别在哪?
  2. 两种范式在状态传递 / 失败恢复 / 可观测性这三个工程维度上的语义差异是什么?
  3. AML Copilot 该选哪个,为什么?

本仓库的 orchestratorAgent.ts 走的是 orchestrator-worker(lead 始终持有控制权)。要论证这个选择是对的,必须先讲清 handoff 这条没走的路差在哪。一手依据是 OpenAI Agents SDK 官方 handoff 文档(2026 当周版)。

关键内容

A. handoff 的机制:控制权完全转移,下一个 agent 接管整段对话

OpenAI Agents SDK 文档对 handoff 的定义:「Handoffs allow an agent to delegate tasks to another agent.」关键在「delegate」的语义——不是调用,是移交。原文:「it's as though the new agent takes over the conversation, and gets to see the entire previous conversation history.」

实现上,handoff 被伪装成一个工具暴露给 LLM:「Handoffs are represented as tools to the LLM. So if there's a handoff to an agent named Refund Agent, the tool would be called transfer_to_refund_agent.」命名走 Handoff.default_tool_name(),即 transfer_to_<agent_name>

模型「调用」这个工具的瞬间,发生的不是「拿到返回值继续推理」,而是控制权易主——triage agent 的本轮就此结束,refund agent 接过整段历史继续。这跟 Day 29 orchestrator 的 Agent.as_tool() 形成对照:后者「allows structured input within a single agent's execution without conversation transfer」——子 agent 算完把结果还给主 agent,主 agent 始终在驾驶座。

两种控制流的状态机对照:

【handoff(控制权转移)】              【orchestrator-worker(控制权保留)】
                                       
  [Triage]                              [Orchestrator] ◄──────────┐
     │ transfer_to_refund_agent           │ as_tool(subQ)         │ 返回 {text}
     │ (本轮结束,交出方向盘)             │ (持有方向盘)         │
     ▼                                     ▼                      │
  [Refund] ── 接管整段历史                [Sub-agent A] ──────────┘
     │  继续与用户对话                      (算完即还,不接管对话)
     ▼                                      
  最终由 Refund 收尾                       Orchestrator 综合 N 个返回 → 收尾
  (Triage 已退出)                        (子 agent 全程不直面用户)

handoff 的历史传递可调:默认接管「entire previous conversation history」,但可通过 input_filter(接收 HandoffInputData、返回新的 HandoffInputData)裁剪,比如内置的 agents.extensions.handoff_filters.remove_all_tools 去掉工具调用历史。HandoffInputDatainput_history / pre_handoff_items / new_items / input_items / run_context 五段。还可用 on_handoff 回调(「kicking off some data fetching as soon as you know a handoff is being invoked」)和 input_type(Pydantic 模型,给移交带 reason/priority/summary 等结构化元数据)。

反直觉洞察①(handoff 是「单工」不是「分身」):直觉以为 handoff 也是一种「并行调多个专家」。错。handoff 是串行的方向盘交接——同一时刻只有一个 agent 在驾驶,triage 交给 refund 后自己就下车了。它根本不能并行(没有「同时移交给三个 agent」这回事,文档明说 input_type「does not dispatch between destinations」)。而 Day 29 orchestrator 的全部威力恰恰来自并行。所以 handoff 与 orchestrator 不是「同一件事的两种写法」,而是解决不同问题:handoff 解决「该谁来接这个对话」,orchestrator 解决「怎么并行榨干 token 预算」。

B. 三个工程维度的语义差异

维度handoff(控制权转移)orchestrator-worker(控制权保留)
状态传递隐式:接管方继承整段历史(或经 input_filter 裁剪);状态「随对话流动」显式:主 agent 把 subQuery 作为参数下发,子 agent 只回 {text};状态「汇聚回中心」
失败恢复难:refund agent 中途失败,triage 已退出,无人接管,需外层兜底重路由易:子 agent 抛错,主 agent 仍在驾驶座,可重试/换路由/降级,控制点单一
可观测性控制流是 DAG,无中心节点;跨 handoff 追错需把 trace context 透传到每个消息边界(实践中要给每个 inter-agent payload 加 span_context/state transition span 才能拼回全图,2026 口径)控制流是星形,主 agent 是天然的中心 span,每次 dispatch 是一对清晰的 call/return,trace 树自然成形
并行能力无(串行交接)有(这是它的核心价值)
可溯源弱:最终答案由接管方产出,来源散在被移交的各段历史里强:所有结论汇到主 agent,可统一挂引用(Day 29 的 CitationAgent 一趟)

状态传递的差异最致命。handoff 的状态是「随对话流动」的——一旦 refund agent 改了共享状态又失败,triage 已经下车,没有干净的回滚点。OpenAI 自己的可观测指南(2026)承认这点:并发子 agent 场景里「one poisons shared state」时,必须「trace context propagated through every message boundary」,团队不得不「added a span_context field to every inter-agent payload」才能把 DAG 拼起来。这恰恰说明:handoff 的可观测性需要额外工程才能补齐,而 orchestrator 的星形拓扑天生就有中心。

把失败恢复这一行再拆细,因为它对 AML 是生死线。考虑一个三步失败场景:triage→refund 移交成功,refund 调外部退款 API 时超时失败。在 handoff 模型下,此刻「谁负责重试」是个没有明确答案的问题——triage 已经退出了控制流,refund 自己正卡在失败里,没有一个更高层的协调者持有「重路由 / 降级 / 通知人工」的决策权。要补救,只能在 handoff 之外再套一层外部编排(如 Temporal 工作流)来捕获并重启,这等于承认「单纯的 handoff 不足以支撑可靠的失败恢复」。而在 orchestrator 模型下,sub-agent 的失败只是一次工具调用抛了异常,主 agent 仍稳稳坐在驾驶座,可以当场决定「换一个 sub-agent 重试 / 降级到规则引擎 / 把这笔标记为待人工」——决策权从未离开过中心。对一个错一次就可能触发监管事件的 AML 系统,「失败时永远有一个明确的人/agent 负责」这件事本身,就值得为它放弃 handoff 那几行代码的便利。

反直觉洞察②(handoff 的「省事」是把复杂度从设计期挪到调试期):handoff 写起来很爽——一行 handoff(refund_agent) 就把活甩出去,不用写综合逻辑。但这份省事是借的:出问题时,控制权已经在多个 agent 间击鼓传花过,你要从一条没有中心节点的 DAG 里反查「是哪次移交丢了上下文」。orchestrator 写起来啰嗦(要写分解+综合),但调试时永远有一个中心 agent 可以下断点。对要过合规审计的 AML,调试期的确定性远比设计期的几行代码值钱。

C. 何时选哪个:决策表

场景特征选 handoff选 orchestrator-worker
任务是「分诊→转专科」的对话路由✅ 天然契合(客服转账/退款)❌ 过度设计
需要并行探索多个独立方向❌ 不能并行✅ 唯一选择(Day 29)
强可溯源 / 要过审计❌ 来源散在各段历史✅ 结论汇中心,统一挂引用
强失败恢复 / 要可回滚❌ 控制点流动,回滚难✅ 控制点单一
子任务结果需主逻辑综合❌ 接管方各自收尾✅ 主 agent 综合
单一专家能独立把对话走完✅ 交出去最省事❌ 没必要绕一圈

一句话判据:「该谁来接这个对话」用 handoff;「怎么把活拆开并行再合起来」用 orchestrator。 两者甚至可以嵌套——分层共识里(2026-04),宏观工作流编排(Temporal)+ 微观推理循环(LangGraph)就是把「路由」和「编排」分到不同层。

需要澄清一个常见误区:很多人把 handoff 当成「省 token 的多 agent」——因为 handoff 不像 orchestrator 那样要跑一个 lead 全程占着上下文。这个想法在「单一专家能独立把对话走完」时确实成立(比如纯客服分诊后专科自己收尾,省掉了 lead 综合那趟)。但它有个隐藏前提:任务必须是「一个接一个的串行专科」,而不是「需要把多路结果合起来」。一旦任务需要综合(AML 的「把三个对手方的尽调结果合成一个风险结论」就是),handoff 根本做不到——因为最后接管的那个 agent 只看得到它自己那一段历史 + 之前的对话,它没有「把另外两路独立结果汇总」的位置。强行用 handoff 串起来,会退化成「一个 agent 修改前一个 agent 的输出」的脆弱链条,每一环都可能丢掉前面的信息。所以 handoff 省的那点 token,是用「无法做综合型任务」换来的——对 AML 这种本质是「多源证据汇聚成一个判断」的场景,这笔交易不成立。

设计要点/决策表

决策点取舍理由
AML 主编排范式orchestrator-worker强可溯源+强失败恢复+需并行,B/C 节三项全中
是否用 handoff仅在「AML 对话分诊→转某专门 sub-agent 后该 sub-agent 独立收尾」时考虑,当前不用AML 结论必须汇中心挂引用,handoff 散来源不可接受
状态传递显式 subQuery 下发 + {text} 回收对应 B 节「状态汇聚回中心」,trace 树自然成形
可观测主 agent 作中心 span,无需给每个 payload 补 span_context星形拓扑天生有中心,省掉 handoff 那套补丁工程

对本项目的落地

  • orchestratorAgent.ts 当前用的是 orchestrator-worker,且应坚持不切 handoff:代码里 dispatchKnowledge/Research/Portfolio 三个工具都是「子 agent 算完 return { text: r.text }、控制权回到 runOrchestrator」——这正是 B 节「状态汇聚回中心」。onSubAgentStart/onSubAgentEnd/onToolCall/onToolResult 这套回调让主 agent 成为天然中心 span,对应 src/agent/trace/useTraceStore.ts 的 trace 收集。若改成 handoff,这套清晰的 call/return trace 会塌成无中心 DAG,AML 审计追溯成本陡增。
  • failure 恢复点设在主 agent:依据 B 节,子 agent(runResearchAgent 等)抛错时,runOrchestrator 仍持有控制权——这给了 budget.tsassert* 异常一个干净的捕获点。若走 handoff,子 agent 失败时主 agent 已退出,Budget 兜底会失效。这是选 orchestrator 的一条硬工程理由。
  • handoff 的 input_type 思路可借用到 dispatch 的参数约束:虽然不用 handoff 的控制转移,但它的 input_type(给移交带 reason/priority 结构化元数据)启发我们:dispatchKnowledgesubQuery 当前只是 z.string().min(3),可计划升级为带 { subQuery, priority, expectedFormat } 的结构化输入(对应 Day 29 A 节「给子 agent 死边界 objective+format」),降低子 agent 目标漂移——这是计划中的改造。
  • 诚实标注:AML 暂不引入 handoff;若 P3 上线后出现「合规专家 agent 需独立接管整段对话直到结案」的真实场景,再评估在 orchestrator 内层嵌一个 handoff,届时须补 span_context 透传以维持可观测。当前 W 仅落 orchestrator 路径。

参考资料

  1. OpenAI Agents SDK — Handoffs(官方文档):handoff 定义「new agent takes over the conversation」;transfer_to_<agent> 工具命名;HandoffInputData 五段与 input_filteron_handoff 回调;input_type 结构化元数据;与 Agent.as_tool() 的对比 (2026 当周版)
  2. OpenAI Agents SDK — Orchestrating multiple agents:handoffs(让 LLM 决策路由)vs LLM-as-orchestrator(agents-as-tools,主 agent 保持控制)两范式 (2026)
  3. OpenAI API — Integrations and observability:handoff trace 跨边界追踪难,需透传 trace context、给 inter-agent payload 加 span_context/state transition span 才能拼回 DAG (2026)
  4. 本仓库 src/agent/orchestrator/orchestratorAgent.ts(orchestrator-worker,控制权保留)、src/agent/trace/useTraceStore.ts(中心 span trace)(2026-06)

SOTA 检查 (2026-06-11)

  • handoff vs orchestrator 两范式在 2026-06 已是主流框架的标准对偶:OpenAI Agents SDK 把二者并列为「handoffs」与「agents-as-tools」;Microsoft Agent Framework 1.0(2026-04,AutoGen+SK 统一后继)、LangGraph 1.0(2025-10)也都同时支持「控制权转移的状态图」与「中心编排」两种结构,本笔记的语义对照仍是 live 的。
  • 2026 趋势是「分层而非二选一」:分层共识(2026-04)——Temporal(×OpenAI Agents SDK GA,2026-03)管宏观工作流路由(接近 handoff 的「谁接活」),LangGraph 管微观推理循环(接近 orchestrator 的「拆-并-合」)。即把 handoff 和 orchestrator 分到不同抽象层,而非在同一层互斥。本项目当前规模只需单层 orchestrator,分层是 P3 规模化后的选项。
  • handoff 可观测性的「补丁工程」在 2026 仍是痛点:把 trace context 透传到每个 inter-agent 消息边界、给 payload 加 span_context 仍需手工(ThoughtWorks Radar Vol 34,2026-04,把 agent topologies 列为需谨慎评估项)。这反向印证本项目选 orchestrator(星形天生有中心 span)在可观测上的省力优势。
  • 待跟踪:OpenAI Agents SDK 的 handoff API 在快速迭代,HandoffInputData 字段与 input_filter 接口可能调整;本项目若 P3 引入内层 handoff,执行当周须重新核对官方文档字段名,不沿用本笔记快照。