主 Agent 回答"这个接口的超时重试在哪实现"要付出什么代价?grep 三个关键词命中 800 行,顺着命中读了 20 个文件,再 grep 一轮确认调用方——最终答案是 200 token 的一句话,过程却烧掉近 4 万 token,而且全部永久留在主窗口里。窗口越塞越满,后面每个任务都在垃圾堆上推理。《上下文工程》系列第 5 篇讲的 compaction 是事后补救:快满了就总结压缩。本篇讲另一种更彻底的思路——子代理架构(sub-agent):脏活在别的上下文窗口里干,主窗口只收结论。
这是《上下文工程》系列第 9 篇。同一个机制在《AI Agent 开发》系列第 24 篇从多 Agent 协作视角讲过编排与分工,本篇只盯住一个变量——上下文卫生。全文先拆解上下文污染的形成机制,再看子代理如何充当防火墙、什么任务值得外包,然后手写一个研究子代理、对照 Claude Code 的 Task 工具实现,最后讨论通信契约与成本边界。
1、上下文污染问题
一次深挖式检索对主窗口的破坏分三层。
第一层,挤占预算。200k 窗口看似很大,一次"读 20 个文件"的探索就能吃掉两成。长任务里这类探索会发生很多次,预算见底后要么压缩要么丢历史,两头都是质量损失。
第二层,稀释注意力。第 2 篇讲过上下文腐烂:无关 token 越多,模型对关键信息的注意力越涣散。那 4 万 token 里真正支撑后续决策的可能只有一句"重试在 Retry.kt 第 88 行",其余的命中列表、无关文件的类声明、重复的 import,全在跟它抢注意力。
第三层,干扰推理。这是最隐蔽的一层:留在窗口里的无关细节不只是噪音,还是错误关联的素材。模型记得自己读过的某个文件里有段降级逻辑,后续讨论超时策略时可能把它错误地牵进来。污染上下文的代价不止 token,还有答案的纯度。
注意:污染是单向累积的。工具结果进了消息历史,就只剩压缩这类有损手段能清走——所以治理的最佳时机在入口,与其事后清理,不如一开始就别让它进来。
2、子代理上下文防火墙
入口治理的答案是子代理:主 Agent 把"去把 X 查清楚"作为一个任务整体派出去,子代理在独立的上下文窗口里完成全部探索,结束后只把结论交回来。Anthropic 奠基文章的原话是:
Specialized sub-agents can handle focused tasks with clean context windows.
文章给的数量级更直观:一个子代理可能消耗数万 token 去探索,但只返回 1,000~2,000 token 的蒸馏摘要;详细的搜索过程隔离在子代理内部,主上下文自始至终没有见过它。前后对比如下:
对比的要点是进出不对称:主窗口交出去的是一个问题(几十 token),收回来的是一段摘要(几百 token),中间高达数万 token 的探索发生在子代理自己的窗口里,随子代理结束而丢弃。这就是用进程级隔离换上下文干净——子代理是一次性的工人,干完活只留交接文档。
奠基文章把这个思路归入 just-in-time context retrieval(即时上下文检索):细节不预先装进主窗口,需要时才去取,而子代理是它的隔离形态——带着干净上下文去查,只把答案带回来。Anthropic 的 Context Engineering Cookbook将它与 memory、compaction、tool clearing 并列为同一套工具箱。
3、任务外包三特征
不是所有任务都值得派给子代理。判断标准压缩成三个特征,满足得越多、收益越大:
| 特征 | 判断问题 | 典型任务 |
|---|---|---|
| 过程大结果小 | 中间产物远大于最终答案 | 代码定位、日志排查、资料调研 |
| 子任务独立 | 不依赖主对话的连续历史 | 批量文件审查、多数据源核对 |
| 人格差异 | 受益于不同的 system 约束 | 只读探索者与严谨审阅者 |
第一个特征是主因。grep 加 read 的探索过程动辄数万 token,结论就一句话,隔离收益最大。反过来,"把这份文档翻译成英文"过程与结果差不多大,隔离就没有意义。
第二个特征决定可行性。子代理看不到主对话历史,任务必须自带完整上下文——"审查这 3 个文件"可以,"接着刚才的思路继续改"不行。强依赖主循环状态的任务,要么把状态序列化进任务描述,要么干脆留在主循环里做。
第三个特征是加分项。主 Agent 的 system prompt 要同时兼顾规划、写作与工具调用,约束是稀释的;子代理却可以带极端化的人格——探索者被要求"穷尽搜索再下结论",审阅者被要求"只挑毛病不夸奖"。第 3 篇讲过的专用系统提示词,在子代理身上发挥到极致。
4、手写研究子代理
目标:主 Agent 负责规划与改码,检索类问题全部交给一个研究子代理。LangGraph 里这是标准的 agent-as-tool 模式——子代理本身是个完整 Agent,对外却暴露成一个普通工具函数。
4.1、子代理定义
先定义子代理:专属工具集、专属 system prompt、独立模型档位。代码如下:
from langchain.agents import create_agent
RESEARCH_PROMPT = """你是代码库检索研究员,只回答定位类问题。
1. 先用 grep/glob 缩小范围,再用 read 确认细节
2. 结论必须附证据(路径:行号),没找到就明说
3. 只输出 JSON:{"answer": "一句话结论",
"evidence": ["app/src/Retry.kt:88"], "confidence": "high|low"}
禁止:粘贴大段代码、展开与问题无关的发现。"""
researcher = create_agent(
model="openai:gpt-5-mini", # 探索交给便宜档
tools=[grep_tool, glob_tool, read_tool], # 只配检索三件套
system_prompt=RESEARCH_PROMPT,
)
三个"专属"就是三重隔离:工具集划出行为半径(它改不了任何文件),system prompt 塑造极端人格(穷尽搜索、只回结论),模型档位控制成本(探索用不着旗舰模型的写作能力)。最关键的一点在调用之外:researcher 每次被调用都从空白窗口起步——上一次探索留下的几万 token 垃圾,不会带进下一次。
4.2、包装成工具
再把子代理包成一个工具函数,主 Agent 像调用 grep 一样调用它。实现如下:
from langchain_core.tools import tool
@tool
def research(question: str) -> str:
"""深挖代码库,回答'XX 在哪实现/怎么工作'类问题,返回 JSON 摘要。"""
result = researcher.invoke(
{"messages": [{"role": "user", "content": question}]}
)
return result["messages"][-1].content # 只有最后一条回主窗口
lead = create_agent(
model="openai:gpt-5", # 旗舰模型只做规划与改码
tools=[research, apply_edit, run_tests],
)
research 的函数体就是全部接线:子代理内部跑完整的 ReAct 循环——几十次工具调用、几万 token 的文件内容,全部消耗在 researcher 自己的窗口里;主窗口只多出一条几百 token 的工具结果。"要不要隔离"由此变成主 Agent 的一个普通工具选择:问题简单就直接 grep,需要深挖就调 research。至于并行派发、结果聚合这些编排细节,《AI Agent 开发》系列第 24 篇已经展开,本篇聚焦的收益只有一个:主窗口的 token 曲线变平了。
5、Claude Code 子代理拆解
这套模式最成熟的产品落地是 Claude Code 的子代理机制:主会话通过 Task 工具派出子代理,每个子代理在独立上下文窗口里工作,结束后只把最终报告写回主会话,中间过程对主会话不可见。子代理用 Markdown 文件定义,放在 .claude/agents/ 目录下即可被按需调用:
---
name: repo-explorer
description: 探索代码库,回答"某功能在哪实现"类问题
tools: Grep, Glob, Read
model: sonnet
---
你是只读检索研究员。先用 Grep/Glob 缩小范围,再用 Read 确认。
最终报告只含三部分:结论一句话、证据路径列表、不确定项。
不要粘贴大段代码,不要修改任何文件。
frontmatter 的每个字段都在做隔离决策:tools 划行为半径(这个子代理只读不写),model 定成本档位,description 教主 Agent 什么时机调用它,正文就是它的专属 system prompt——正好对应 4.1 节的四个要素。官方文档还展示了并发形态:主会话可以同时派出多个子代理并行探索,各自窗口互不污染,全部结束后主会话收到的只是几份摘要,再自己做聚合。第 3 节的"批量独立子任务"特征,在这里被产品化成了一等能力。
6、通信契约设计
子代理模式的成败藏在返回值里。契约只有一条铁律:返回消化后的结论,不返回原始数据。子代理若 return 一段 grep 原始输出,防火墙上就等于开了个洞——过程 token 绕过隔离直接灌进主窗口,前面所有架构白搭。
4.1 节的三字段 JSON 不是随便选的,每个字段都服务主 Agent 的下一步动作:
| 字段 | 服务什么决策 |
|---|---|
answer |
主 Agent 直接引用的结论 |
evidence |
可复核的坐标,需要时可再派子代理验证 |
confidence |
low 时触发二次验证或换问法 |
设计契约有三条原则。字段要少而硬:主 Agent 是契约的唯一消费者,字段越多它越难稳定解析,三到五个足够。关键值保留原文:与第 5 篇总结提示词同一条理由——"Retry.kt 第 88 行"一旦被转述成"重试模块某处",后续验证就无从下手。给结果设上限:约定摘要不超过 2,000 token,超限说明任务太大,该拆成两个子代理,而不是把摘要撑大。
7、子代理成本账
子代理不是免费的隔离。每个子代理都从空白窗口起步,意味着共享信息要被反复建立:项目背景、代码规范、数据库 schema——主 Agent 知道的一切,子代理统统不知道,要么写进任务描述,要么让它自己重新读。三笔隐性开销要算清:
- 冷启动:任务描述加必要背景,每个子代理先付几百到几千 token
- 重复读:N 个子代理各读一遍同一份 schema,读文件的 token 乘以 N
- 调度税:主 Agent 写任务、解析摘要,每次外包多出两轮调用
Anthropic 复盘自家多 Agent 研究系统时给过一个量级:多 Agent 系统的 token 消耗约为单 Agent 对话的 15 倍。这不是劝你别用——探索深度换来的答案质量值这个价——而是提醒你判断划不划算。经验公式:过程大、结论小的任务(第 3 节特征一)隔离收益最高;任务浅但共享前置重的最亏——"数据库里有没有 users 表"这种一条 SQL 能答的问题,派子代理重读全套 schema 纯属浪费,主 Agent 直接查就好。
也别忘了子代理与第 5 篇 compaction 的分工:compaction 是事后压缩,子代理是事前隔离,两者不互相替代。理想的系统两层都要——先让脏活进不了主窗口,真进了再用压缩兜底;给子代理本身设 token 预算时,第 4 篇的方法论同样适用。
8、总结
子代理架构的本质,是把上下文当稀缺资源做进程级管理:探索的暴力消耗在独立窗口里发生、随窗口销毁,主窗口只沉淀结论。记住五件事:污染在入口治理而非出口;进出要不对称,问题几十 token 进、摘要几百 token 出;外包判断看过程大结果小、子任务独立、人格差异三特征;返回值必须是消化后的结论,字段少而硬、关键值保留原文;共享前置信息重的浅任务别外包,15 倍的 token 量级不是吓唬人。
留一个练习:把《AI Agent 开发》系列第 17 篇的 RAG Agent 检索循环改造成子代理版。原版每轮把检索结果全文塞进主循环的 messages;改造后,整个检索循环搬进一个研究子代理,主循环只接收 answer 加 evidence 摘要。用同一组 20 个问题跑两版,画出主窗口的 token 增长曲线并对比答案质量——你会看到主曲线从陡峭变平缓,而质量差异主要出现在多轮追问的场景,那正是隔离换来的注意力红利。
对于正在构建 Agent 系统的团队,建议本周做一件事:把现有 Agent 的一次真实会话 dump 出来,找出 token 占比最大的三次工具输出——如果它们合计超过主窗口的三成,你的第一个子代理的切入点就在那里。

