前言
Agent 基座系列进入进阶篇。单 Agent 跑长任务时,探索过程会把主对话塞满——上一篇的压缩是事后补救,更好的办法是让脏活发生在别处:把子任务派生给独立的子 Agent,每个子 Agent 有自己的上下文,跑完只把结论带回来。本篇讲清三件事:为什么需要子 Agent、三种编排模式、怎么在手写基座上实现派生。
一、为什么需要子 Agent:上下文隔离
先纠正一个直觉:多 Agent 的核心价值不是「多个模型同时干活」,而是上下文隔离。
一个典型场景:让 Agent 审查一个陌生代码库的登录模块。它要 grep 关键词、读十几个文件、跑测试看输出——这些探索过程会产生几十 K 的工具输出。等它真正开始写审查意见时,主上下文里 90% 是已经没用的中间产物,而且它们会一直留在后续每一轮请求里。
子 Agent 的解法:派一个子 Agent 去做「读代码 + 定位问题」,它在自己独立的上下文里随便折腾,读 50 个文件也没关系;结束后只回传一段摘要:「问题在 auth.py 第 42 行,token 没校验过期,复现步骤如下」。过程留在子上下文里死去,结论进入主上下文活着。
两个附带收益:
- 工具面收窄:子 Agent 只拿任务所需的工具(只读工具给探索者,不给写权限),攻击面和误操作面都更小;
- 可并行:互不依赖的子任务同时派发,墙钟时间从累加变成取最大值。
二、三种编排模式
| 模式 | 结构 | 适用场景 | 要点 |
|---|---|---|---|
| Pipeline 流水线 | A 的产出作为 B 的输入,串行 | 写代码 → 审查 → 修复 | 交接产物要结构化 |
| Fan-out 并行 | 同一任务派 N 个探索者,各自独立上下文 | 多方向调研、大范围搜索 | 只回传摘要,主上下文合并 |
| Review 循环 | 产出者 ↔ 审查者对抗循环,直到通过 | 质量敏感任务(代码、文档) | 判决权归审查者,设最大轮数 |
Pipeline:接力棒要交接清楚
环节之间传的不是「聊天记录」而是「产物」——代码 diff、结论清单、待办列表。下游 Agent 的任务描述里要带上上游产物的全文,而不是让它去翻历史。Fan-out:探索广度换深度
比如调研一个技术选型,派三个子 Agent 分别查性能、生态、成本,各自回传一页纸结论。注意 fan-out 的子 Agent 之间互相不知道对方存在,合并判断由主 Agent 做。Review 循环:对抗出质量
写作者和审查者必须是两个独立上下文——自己审自己,报喜不报忧。审查者的判决要结构化(通过 / 不通过 + 具体问题清单),并设最大循环轮数(如 3 轮),防止「改不满意—再审—再改」死循环。
三、手写:在基座上实现子 Agent 派生
基于第一篇的agent()函数改造,核心是把它包装成一个工具——主 Agent 调用subagent工具时,harness 起一个独立的messages跑子任务:
importanthropic client=anthropic.Anthropic()MODEL="claude-opus-5-5"defrun_agent(user_input:str,tools,max_steps:int=20)->str:"""第一篇的 agentic loop,原样复用:跑完返回最终文本。"""messages=[{"role":"user","content":user_input}]for_inrange(max_steps):resp=client.messages.create(model=MODEL,max_tokens=8000,tools=tools,messages=messages)messages.append({"role":"assistant","content":resp.content})ifresp.stop_reason!="tool_use":returnnext((b.textforbinresp.contentifb.type=="text"),"")results=[]forblockinresp.content:ifblock.type=="tool_use":out,err=execute_tool(block.name,block.input)# 工具执行层results.append({"type":"tool_result","tool_use_id":block.id,"content":out,**({"is_error":True}iferrelse{})})messages.append({"role":"user","content":results})return"(子任务达到步数上限未完成)"SUBAGENT_TOOL={"name":"subagent","description":"派一个子 Agent 独立完成一项子任务,返回结论摘要。""适合:大范围搜索/调研、读大量文件定位问题等探索型工作。""不适合:需要与用户交互的任务。","input_schema":{"type":"object","properties":{"task":{"type":"string","description":"子任务描述,要自包含:目标、范围、期望的返回格式"},},"required":["task"],"additionalProperties":False,},}defspawn_subagent(task:str)->tuple[str,bool]:"""子 Agent 派生:独立 messages + 收窄的工具面(只读)。"""readonly_tools=[READ_FILE,GREP,LIST_DIR]# 不给写工具try:summary=run_agent(task,tools=readonly_tools)returnsummaryor"(子任务无输出)",FalseexceptExceptionase:returnf"子 Agent 执行失败:{type(e).__name__}:{e}",True主 Agent 的工具表里加上SUBAGENT_TOOL,execute_tool里加一个分支if name == "subagent": return spawn_subagent(tool_input["task"]),派生能力就接上了。
三个实现要点:
任务描述必须自包含
子 Agent 看不到主对话,task里要写全目标、范围、期望格式。这是 fan-out 质量的第一决定因素。只回传摘要,不回传过程
run_agent的返回值就是最终文本——子 Agent 的整个messages在它结束后即丢弃,永远不进入主上下文。要控制摘要质量,在 task 里规定格式:「返回:结论 / 关键证据(文件:行号)/ 建议下一步,200 字内」。失败要隔离
子 Agent 超时、达到步数上限,用is_error: True回传(第一篇的协议),主 Agent 决定重派还是换路线,不能让一个子任务的崩溃终结整个任务。
四、什么时候不要用多 Agent
多 Agent 不是免费的午餐,两个账要先算:
成本账
每个子 Agent 都要重新携带系统提示词和工具定义。3 个子 Agent ≈ 3 份前缀成本。好消息是前缀稳定、缓存能救(见上一篇),但依然是开销。判断标准:中间过程是否值得留在主上下文
- 值得派子 Agent:探索过程又长又脏(大范围搜索、试错、读大量文件),结论一小段;
- 不值得:两三步就能完成的任务、需要与用户频繁交互的任务、需要全程可审计留痕的任务(子 Agent 的过程默认丢弃,审计要单独落日志)。
一个反直觉的事实:大多数任务单 Agent + 好的工具就够了。多 Agent 是给「探索成本远高于结论体积」的任务准备的。
参考资料
- Building effective agents(Anthropic 工程博客)
- Multi-Agent Research System(Anthropic)
- 本系列第一篇:《从零手写 Agent 基座》