编排器的职责边界
Orchestrator(主 Agent)和 Subagent(子 Agent)的分工不清楚,是 Multi-Agent 工作流最常见的设计问题。
Orchestrator 做三件事:
markdown
1. 决策:读取状态,判断下一步
2. 分派:spawn 子 Agent,传递 task prompt
3. 收集:读取子 Agent 的输出文件,更新状态
Orchestrator 不做的事:
× 不直接执行业务逻辑(分析 Bug、写代码、查日志)
× 不读取原始日志文件或长文本(会让上下文膨胀)
× 不修改业务数据(只有子 Agent 有操作权限)
主 Agent 只接收结构化结论 (JSON),不接收原始输出。子 Agent 用 output.json 向主 Agent 汇报,不用消息流。
python
# ✅ 正确:主 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 必须包含它完成任务所需的一切。
markdown
# ❌ 不完备的 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 的判断逻辑失败。
python
# 子 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。
json
// 失败时的输出
{
"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 写不同的输出文件
python
# ✅ 正确:每个候选写独立文件
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。等待方式取决于是否有异步能力:
python
# 同步等待(轮询)
import time
def wait_all_candidates(candidates: list[str], timeout: int = 300) -> dict:
results = {}
deadline = time.time() + timeout
while 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(任一失败即中止)
yaml
# 适合:所有分支结果都必须存在,一个失败整批无意义
phase_parallel_analysis:
fan_in_strategy: fail-fast
on_any_failure: trigger_gate_A # 任一失败则触发确认门
适用场景: 3 个子 Agent 同时获取来自不同来源的数据,缺少任何一个数据源都无法继续分析。
collect-all(汇总全部,包含失败)
yaml
# 适合:部分失败仍有价值,选用成功的结果
phase_4_fix:
fan_in_strategy: collect-all
selection_criteria:
require_any_passed: true # 至少 1 个通过才继续
select_by: max_test_coverage # 从通过的候选里选覆盖率最高的
on_all_failed: trigger_gate_B # 全部失败才触发确认门
适用场景: 3 个代码修复候选并发执行,1 个通过测试就够了,失败的候选丢弃即可。
选择原则
sql
所有分支结果缺一不可 → 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 历史里的"噪声"误导
信息只从两个方向流动:
javascript
主 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
总结
- Orchestrator 只做决策和分派:读 JSON 结论、spawn 子 Agent、收 JSON 结论,不做业务执行------保持上下文可控是核心目的
- Fan-in 策略决定工作流的韧性:代码修复这类"解空间多样"的场景用 collect-all,只有全部失败才升级人工;数据收集这类"缺一不可"的场景用 fail-fast,任一失败即触发
- 上下文隔离是质量保证:子 Agent 只知道 task prompt 里的内容,多余的上下文是干扰,不是帮助;如果子 Agent 需要"了解背景",就在 task prompt 里显式给出这个背景
欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页