Orchestrator(主 Agent)和 Subagent(子 Agent)的分工不清楚,是 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 的 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 里给它的内容。
子 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 # 失败时填写}
子 Agent 失败时,必须仍然写输出文件,只是 passed=false。
// 失败时的输出{"passed": false,"error": "日志文件不存在:/workspace/logs/crash_20260601.log","confidence": 0,"root_cause": null,"evidence": []}
如果失败时不写输出文件,主 Agent 会误认为子 Agent 超时,走超时处理逻辑。结构化错误让主 Agent 能区分"子 Agent 失败"和"子 Agent 超时",采取不同的处理策略。
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 时如果部分子 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││ task prompt(只包含子 Agent需要的字段)▼子 Agent(隔离会话,无历史)││ output_file(约定路径的 JSON 文件)▼主 Agent(读取文件,不读对话历史)
子 Agent 知道 task prompt 里的内容,知道约定的输出路径。它不知道主 Agent 做了什么,不知道其他子 Agent 的结果,也不知道工作流进行到哪里。
如果一个子 Agent 需要"了解背景"才能完成任务,task prompt 不完备。把背景信息显式写进去,而不是让子 Agent 去访问主 Agent 的历史。
Orchestrator 职责
子 Agent 设计
passed 字段{"passed": false, "error": "..."} 输出文件并发控制
上下文隔离
欢迎访问 PrimeSkills —— 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页