Workflow 系列(04):Multi-Agent 协调:编排器边界、并发控制与上下文隔离

作者:袖梨 2026-07-02

编排器的职责边界

Orchestrator(主 Agent)和 Subagent(子 Agent)的分工不清楚,是 Multi-Agent 工作流最常见的设计问题。

Workflow 系列(04):Multi-Agent 协调——编排器边界、并发控制与上下文隔离

Orchestrator 做三件事:

1. 决策:读取状态,判断下一步2. 分派:spawn 子 Agent,传递 task prompt3. 收集:读取子 Agent 的输出文件,更新状态

Orchestrator 不做的事:

× 不直接执行业务逻辑(分析 Bug、写代码、查日志)× 不读取原始日志文件或长文本(会让上下文膨胀)× 不修改业务数据(只有子 Agent 有操作权限)

主 Agent 只接收结构化结论(JSON),不接收原始输出。子 Agent 用 output.json 向主 Agent 汇报,不用消息流。

# ✅ 正确:主 Agent 读结构化结论result = json.loads(Path("phase3/analysis_final.json").read_text())if result["confidence"] >= 95:proceed_to_phase4()# ❌ 错误:主 Agent 读原始日志log_content = Path("crash.log").read_text()# 10万行日志进了主 Agent 的上下文decision = llm.analyze(log_content) # 主 Agent 不该做这个

这个边界带来两个好处:主 Agent 的上下文保持可控(只有状态和结论,没有原始数据);子 Agent 的业务逻辑可以独立测试,不依赖主 Agent 的会话历史。

子 Agent 的设计原则

原则 1:输入完备性

子 Agent 的 task prompt 必须包含它完成任务所需的一切。

# ❌ 不完备的 task prompt分析这个 Bug 的根因,参考之前的分析结果。# ✅ 完备的 task prompt## 任务分析以下 Bug 的根因。## 输入Bug 信息:{{ bug_info.summary }}{{ bug_info.stack_trace }}日志目录:{{ log_dir }}## 输出要求写入 analysis_final.json,格式:{"confidence": float, "root_cause": str, "evidence": [str]}

"参考之前的分析结果"依赖子 Agent 能访问主 Agent 的上下文历史,在隔离会话里这不成立。每个子 Agent 只知道 task prompt 里给它的内容。

原则 2:输出契约严格性

子 Agent 必须按约定的 JSON Schema 写输出文件。主 Agent 依赖这个 Schema 做路由决策,字段缺失或类型错误会导致主 Agent 的判断逻辑失败。

# 子 Agent 输出 Schema(在 templates/ 里定义)OUTPUT_SCHEMA = {"passed": bool, # 必填,主 Agent 路由决策依赖"confidence": float,# 必填,范围 0-1"root_cause": str,# 必填"evidence": list[str],# 必填"error": str | None # 失败时填写}

原则 3:失败时写结构化错误

子 Agent 失败时,必须仍然写输出文件,只是 passed=false

// 失败时的输出{"passed": false,"error": "日志文件不存在:/workspace/logs/crash_20260601.log","confidence": 0,"root_cause": null,"evidence": []}

如果失败时不写输出文件,主 Agent 会误认为子 Agent 超时,走超时处理逻辑。结构化错误让主 Agent 能区分"子 Agent 失败"和"子 Agent 超时",采取不同的处理策略。

Fan-out / Fan-in 并发控制

Fan-out 设计

Fan-out 意味着一个触发点产生 N 个并发子 Agent。两个关键约束:

约束 1:每个子 Agent 写不同的输出文件

# ✅ 正确:每个候选写独立文件candidates = ["candidate_a", "candidate_b", "candidate_c"]for c in candidates:spawn_subagent(task_prompt=build_prompt(c, bug_info),output_file=f"phase4/{c}.json"# 文件名唯一)# ❌ 错误:所有候选写同一个文件(并发写冲突)spawn_subagent(task_prompt=..., output_file="phase4/result.json")spawn_subagent(task_prompt=..., output_file="phase4/result.json")# 冲突!

约束 2:主 Agent 要等待所有子 Agent 完成

Fan-out 后主 Agent 进入等待状态,不继续执行后续 Phase。等待方式取决于是否有异步能力:

# 同步等待(轮询)import timedef wait_all_candidates(candidates: list[str], timeout: int = 300) -> dict:results = {}deadline = time.time() + timeoutwhile len(results) < len(candidates) and time.time() < deadline:for c in candidates:if c not in results:output_file = Path(f"phase4/{c}.json")if output_file.exists():results[c] = json.loads(output_file.read_text())time.sleep(5)return results

Fan-in:失败策略选择

Fan-in 时如果部分子 Agent 失败,有两种策略:

fail-fast(任一失败即中止)

# 适合:所有分支结果都必须存在,一个失败整批无意义phase_parallel_analysis:fan_in_strategy: fail-faston_any_failure: trigger_gate_A# 任一失败则触发确认门

适用场景: 3 个子 Agent 同时获取来自不同来源的数据,缺少任何一个数据源都无法继续分析。

collect-all(汇总全部,包含失败)

# 适合:部分失败仍有价值,选用成功的结果phase_4_fix:fan_in_strategy: collect-allselection_criteria:require_any_passed: true# 至少 1 个通过才继续select_by: max_test_coverage# 从通过的候选里选覆盖率最高的on_all_failed: trigger_gate_B # 全部失败才触发确认门

适用场景: 3 个代码修复候选并发执行,1 个通过测试就够了,失败的候选丢弃即可。

选择原则

所有分支结果缺一不可→ fail-fast部分成功即可继续→ collect-all(代码修复、生成候选)需要比较多个结果质量→ collect-all(比较后选优)

Bug 修复工作流的 Phase 4 用 collect-all:3 个修复候选并发跑,选通过单元测试且覆盖率最高的候选,只有全部 3 个都失败才触发人工确认门。

上下文隔离

子 Agent 必须在隔离会话里运行,不能访问主 Agent 的对话历史。

主 Agent 的上下文包含工作流的完整历史:所有文件内容、所有子 Agent 的原始输出、所有中间决策。把这些全部传给一个专注于"写一段修复代码"的子 Agent,会导致:

  • 子 Agent 的上下文从几 K tokens 膨胀到几十 K tokens
  • 无关信息干扰子 Agent 的注意力,输出质量下降
  • Token 成本翻倍
  • 子 Agent 更容易被主 Agent 历史里的"噪声"误导

信息只从两个方向流动:

Agent││ task prompt(只包含子 Agent需要的字段)▼子 Agent(隔离会话,无历史)││ output_file(约定路径的 JSON 文件)▼主 Agent(读取文件,不读对话历史)

子 Agent 知道 task prompt 里的内容,知道约定的输出路径。它不知道主 Agent 做了什么,不知道其他子 Agent 的结果,也不知道工作流进行到哪里。

如果一个子 Agent 需要"了解背景"才能完成任务,task prompt 不完备。把背景信息显式写进去,而不是让子 Agent 去访问主 Agent 的历史。

设计 Checklist

Orchestrator 职责

  • 主 Agent 只读取结构化 JSON 输出,不读取原始日志或长文本
  • 主 Agent 不直接执行业务逻辑(分析、写代码、查询)
  • 路由决策依赖状态文件 + 子 Agent 输出,不依赖对话历史

子 Agent 设计

  • task prompt 包含子 Agent 完成任务所需的所有字段(不依赖隐含上下文)
  • 输出 Schema 在 templates/ 里明确声明,包含 passed 字段
  • 失败时仍然写 {"passed": false, "error": "..."} 输出文件

并发控制

  • Fan-out 时每个子 Agent 写不同路径的输出文件
  • Fan-in 策略明确标注 fail-fast 或 collect-all
  • collect-all 有明确的 selection_criteria 和 on_all_failed 行为

上下文隔离

  • 子 Agent 在独立会话里运行,不访问主 Agent 的对话历史
  • 子 Agent 需要的背景信息全部显式写入 task prompt

总结

  1. Orchestrator 只做决策和分派:读 JSON 结论、spawn 子 Agent、收 JSON 结论,不做业务执行——保持上下文可控是核心目的
  2. Fan-in 策略决定工作流的韧性:代码修复这类"解空间多样"的场景用 collect-all,只有全部失败才升级人工;数据收集这类"缺一不可"的场景用 fail-fast,任一失败即触发
  3. 上下文隔离是质量保证:子 Agent 只知道 task prompt 里的内容,多余的上下文是干扰,不是帮助;如果子 Agent 需要"了解背景",就在 task prompt 里显式给出这个背景

欢迎访问 PrimeSkills —— 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。

更多实用知识和有趣产品,欢迎访问我的个人主页

相关文章

精彩推荐