在编码 Agent 工程里,"子 Agent(subagent)"不是把一个大模型换成几个小模型那么简单。它本质上是一种上下文与算力的隔离手段:每个子 Agent 拥有独立的上下文窗口、独立的工具结果流,只把"结论"回传给主循环。理解这一点,才能把子 Agent 用在刀刃上,而不是为了并行而并行。
子 Agent 到底解决什么问题
主循环(main loop)的上下文是稀缺资源。一旦它被几十个文件的原始内容、grep 的噪声输出、失败的尝试塞满,模型的注意力就会被稀释,KV 缓存也会因为频繁变动而失效。子 Agent 的核心价值,是让这些"脏活"在别处发生,主循环只接收提炼后的答案。
具体有三类典型用途:
- 用便宜模型跑子任务:主循环保持单一模型(保护其上下文缓存的稳定性),把"读 20 个文件找出哪个实现了某接口"这种体力活交给一个 haiku 级别的子 Agent。主循环模型不变,缓存命中率高,整体既快又省。
- 并行 fan-out:面对多文件、多候选、多假设时,同时派出多个子 Agent 各跑一份,最后汇总。
- orchestrator-worker 编排:主 Agent 作为编排者只负责拆解任务、分派、合并结果,真正干活的是 worker。
fan-out:把可并行的部分真正并行
fan-out 的适用前提是任务之间没有共享可变状态、没有顺序依赖。比如"在三个模块里分别定位同名函数的实现",或者"对同一个 bug 生成三种修复候选再择优"。这类任务串行做是线性时间,fan-out 后接近常数时间(受限于最慢的那个 worker)。
一段伪代码说明编排者怎么发散再收敛:
1 | # orchestrator 主循环里 |
关键纪律有两条。第一,worker 的产出契约要窄——明确要求它只回传结论(路径、行号、一句话),不要把读过的源码原样吐回来,否则 fan-in 时主循环又被噪声淹没,并行省下的上下文又赔了进去。第二,派完就别自己再做一遍。一个常见错误是主循环派出搜索子 Agent 后,自己也忍不住 grep 一遍,既浪费 token 又制造了状态分叉;正确做法是派发后等待结果。
orchestrator-worker:分层而非扁平
当任务有天然层级时——例如"实现一个新特性,涉及数据层、服务层、接口层"——orchestrator-worker 比单纯 fan-out 更合适。编排者维护全局计划与依赖关系,worker 只看到自己那一块的局部上下文。
1 | orchestrator |
注意这里 worker 之间有依赖,所以不是纯并行:A 必须先产出"数据层对外的接口契约",B 才能开工。编排者的职责正是管理这种依赖,而不是把所有东西一股脑塞进一个上下文让模型自己理顺。分层带来的副作用是每层上下文都小而聚焦,模型在每一层都不容易跑偏。
一条反直觉但好用的经验:用独立的"fresh-context 验证者"
让一个 Agent 自我批判(“再检查一遍你刚写的代码有没有问题”)效果往往不好——它和刚才写代码的是同一段上下文,带着同样的思维定式和同样的盲点,很容易自我背书。
更可靠的做法是派出一个全新上下文的验证者子 Agent:它不知道之前的纠结过程,只拿到规约(spec)和产出物,被要求独立判断"这个实现是否满足规约"。fresh context 意味着没有沉没成本、没有先入为主,它更可能发现原作者视而不见的问题。
1 | # 不要:同一上下文自我审查 |
这其实是把"代码 review 要换个人看"的工程常识,迁移到了 Agent 协作上。
什么时候不该用子 Agent
子 Agent 不是免费的:每次派发都有启动开销、序列化开销和合并开销。如果任务很小、上下文也不脏(比如改一个已知文件的一行配置),直接在主循环做更快。判断标准是:这个子任务会不会污染主上下文,或者能不能真正并行——两者皆否,就别拆。同理,能用便宜模型独立完成的体力活才值得外派;需要全局视野的决策应该留在主循环。
还有一点容易被忽略:子 Agent 的产出契约要尽量"自包含、可校验"。如果 worker 回传的是"我觉得在这附近"这种模糊结论,编排者无法核对,只能照单全收,错误就被悄悄带进了汇总。好的契约长这样——“文件路径 + 行号 + 一句话职责”,编排者甚至可以再派一个廉价子 Agent 去抽查几条是否属实。把契约设计得可机器校验,并行才不会以牺牲可靠性为代价。这与上一节的验证者思路一脉相承:隔离上下文的同时,别把可核对性也一起隔离掉。
把子 Agent 当成上下文与算力的"隔离舱",而非简单的多线程:该并行的 fan-out 出去、该分层的用 orchestrator-worker 编排、该验证的交给 fresh-context 的独立验证者——隔离用对了,省的是 token,得到的是更干净的判断。