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

编排器的职责边界

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

总结

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

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

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

相关推荐
冬奇Lab1 小时前
每日一个开源项目(第147篇):HyperGraphRAG - 用超图表示 N 元关系,RAG 的第三代范式
人工智能·开源·graphql
甲维斯2 小时前
Github + 阿里云oss实现类似codex的自动更新!
人工智能
阿里云大数据AI技术4 小时前
光轮智能 × 阿里云:共建 Physical AI 云上数据、评测与持续学习基础设施
人工智能·机器学习
机器之心4 小时前
实锤了:Claude Code偷查用户,时区、中国AI实验室全是关键词
人工智能·openai
网易云信4 小时前
Cursor点燃个人开发者,企业级AI为何频频受挫?Agent工厂从提效工具到AI员工的跃迁
人工智能·开源
网易云信4 小时前
解锁触手可及的温暖:网易智企 x Wander Puffs AI 云游泡芙
人工智能
转转技术团队4 小时前
从 PRD 到可验证代码:AI 需求开发闭环实践
人工智能