上下文爆炸:你迟早会遇到的问题
让 Agent 做一件复杂任务------比如"帮我调研竞品,整理成报告"。
Agent 开始工作:搜索网页、读取文件、解析日志......十几轮工具调用之后,主对话的上下文里塞满了搜索结果片段、文件内容、中间推理过程。这些信息大部分只是"工作痕迹",最终报告根本不需要它们。但它们已经永久占据了上下文窗口。
后果是双重的:推理质量下降 (模型要在海量噪声里找关键信息)和 Token 成本飙升(每一轮调用都要携带这些历史)。
这就是上下文爆炸。随着 Agent 执行的任务越来越复杂,这个问题不是偶发的,是必然的。
SubAgent 解决的本质问题是:把"不需要主 Agent 记住的工作"派给隔离的上下文去做,只拿回一句摘要。
这和软件工程里的"关注点分离"是同一个道理------不是因为子模块不重要,而是因为它的实现细节不应该污染上层调用者的状态。
SubAgent 的核心模型:三个要素
SubAgent 不是一个新模型,而是一种运行实例的隔离方式。理解它需要抓住三个核心要素。
要素一:隔离的上下文窗口
这是 SubAgent 最根本的特征。
每个 SubAgent 运行在自己独立的上下文窗口里。它不知道父 Agent 在聊什么、做了什么------它只有:
- 自己的 System Prompt(定义它是谁、能做什么)
- 当前被委派的任务描述
- 自己的工具调用历史
父 Agent 的整个对话历史对 SubAgent 是完全不可见的。当 SubAgent 完成任务,它把结果打包返回给父 Agent------父 Agent 看到的只是结论,而不是 SubAgent 走过的每一步。
css
主对话上下文 SubAgent 上下文
┌─────────────────────┐ ┌─────────────────────┐
│ 用户消息 1 │ │ System Prompt │
│ Agent 回复 1 │ 委派任务 │ (SubAgent 的角色定义)│
│ 工具调用 A │ ─────────→ │ 任务描述 │
│ 用户消息 2 │ │ 工具调用 X │
│ Agent 回复 2 │ ←───────── │ 工具调用 Y │
│ [SubAgent 返回摘要] │ 只返回摘要 │ 工具调用 Z │
└─────────────────────┘ └─────────────────────┘
主对话完全不知道 SubAgent 调用了多少次工具,搜索了多少内容------那些工作过程永远留在 SubAgent 的独立上下文里,随着任务结束而消失。
要素二:能力约束
SubAgent 可以被精确限制"能做什么",从两个维度控制:
工具访问(Tool Access)
yaml
# 只读型 SubAgent:只能读,不能写
tools: Read, Grep, Glob, Bash
# 排除型 SubAgent:继承所有工具,但禁止写文件
disallowedTools: Write, Edit
# 全能型 SubAgent:继承父对话的所有工具
# (不设置 tools 字段,默认继承)
模型选择(Model Selection)
SubAgent 可以使用和父 Agent 不同的模型。这是成本控制的关键杠杆:
yaml
model: haiku # 快速、低成本,适合简单探索任务
model: sonnet # 均衡,适合中等复杂度任务
model: opus # 高能,适合需要深度推理的任务
model: inherit # 继承父 Agent 的模型(默认)
要素三:Description 驱动的委托
父 Agent 怎么知道什么时候该调用哪个 SubAgent?
答案是 description 字段。这个字段是父 Agent 的调度索引------它根据当前任务的语义,匹配最合适的 SubAgent 并委派。
yaml
---
name: code-reviewer
description: >
专门用于代码审查。当需要检查代码质量、安全漏洞、
性能问题、或评估代码是否符合最佳实践时触发。
不适用于代码编写或修改任务。
---
description 写得越精准,委托触发的准确率越高。写得太宽泛,会在不该触发的场景下触发;写得太窄,会漏掉合理的委托机会。
委托机制的内部流程
了解了三要素,再来看完整的委托流程。
markdown
父 Agent 接到任务
↓
判断:这个任务适合委托给 SubAgent 吗?
(匹配所有已注册 SubAgent 的 description)
↓
找到匹配的 SubAgent
↓
创建独立上下文,注入 system prompt + 任务描述
↓
SubAgent 自主执行(工具调用、推理、迭代)
↓
SubAgent 返回结果摘要
↓
父 Agent 接收摘要,继续主流程
有一个关键约束必须记住:SubAgent 不能再生成 SubAgent。
这是刻意的设计,防止无限嵌套导致系统失控。如果 SubAgent 也能派生子 SubAgent,那么一个复杂任务可能触发层层委托,上下文爆炸的问题不但没解决,反而变得更难追踪。
Claude Code 的内置 SubAgent 就体现了这个约束:Plan SubAgent 在 Plan 模式下负责收集代码库信息,但它自己不会再派生 SubAgent 去做进一步的探索。
三种执行模式
当系统里有多个 SubAgent 协同工作时,它们的执行关系有三种基本模式。
顺序模式(Sequential)
前一个 SubAgent 的输出作为下一个的输入,通过共享状态(Session State)传递。
css
SubAgent A SubAgent B SubAgent C
(数据收集) → (数据清洗) → (报告生成)
↓ ↓ ↓
写入 state['raw'] 读取并写入 读取 state['clean']
state['clean'] 生成最终报告
适用场景:有严格先后依赖关系的流水线任务。每一步都需要前一步的结果,顺序不能乱。
并行模式(Parallel)
多个 SubAgent 同时执行独立任务,各自写入不同的状态键,最后由汇总 Agent 合并结果。
css
┌─ SubAgent A(分析竞品 1)─→ state['product_a']─┐
主 Agent ──┤─ SubAgent B(分析竞品 2)─→ state['product_b']─┤─→ 汇总 Agent
└─ SubAgent C(分析竞品 3)─→ state['product_c']─┘
适用场景:多个独立子任务可以同时进行,且最终需要合并。并行模式可以大幅缩短端到端执行时间。
循环模式(Loop)
SubAgent 反复执行,直到满足终止条件(达到最大迭代次数,或某个 SubAgent 发出"完成"信号)。
markdown
生成 SubAgent → 评估 SubAgent → 满足质量标准?
↑ │
└──────── 否,继续迭代 ─────────┘
│
是,输出最终结果
这本质上就是 Evaluator-Optimizer 模式:一个 Agent 负责生成,另一个负责评估,循环直到输出质量达标。适用于需要多轮精炼的创作或推理任务。
两种通信范式
SubAgent 执行完任务之后,结果怎么流回父 Agent?有两种截然不同的范式。
范式一:Agent as Tool(工具调用范式)
父 Agent 始终持有控制权。SubAgent 被包装成一个工具,父 Agent 调用它、拿到返回值,然后继续自己的工作流。
python
# 伪代码示意
result = invoke_subagent(
agent="code-reviewer",
task="审查 src/auth.py 的安全性"
)
# 父 Agent 继续处理 result
summary = synthesize_results(result, other_data)
这种范式的特点:父 Agent 是主动的调度者 ,SubAgent 是被动的执行者。适用于需要把多个 SubAgent 的结果综合起来的场景------父 Agent 需要看到所有子结果才能做出最终判断。
范式二:Handoff(控制权移交范式)
父 Agent 把控制权完全移交给 SubAgent,由 SubAgent 接管后续的对话或任务。父 Agent 退出执行,不再参与。
arduino
用户: "帮我做一个完整的技术方案"
↓
路由 Agent(分析需求类型)
↓
移交给 → 技术方案 SubAgent(接管后续所有对话)
这种范式的特点:适用于任务性质发生了本质性转变,后续工作由专业 SubAgent 全权负责。SubAgent 有完整的领域专注------它的 system prompt 不用照顾"万一还有其他类型的任务",可以写得极为专业和精准。
两种范式的核心区别:
| Agent as Tool | Handoff | |
|---|---|---|
| 控制权 | 父 Agent 持有 | 转移给 SubAgent |
| 适用场景 | 需要合并多个子结果 | 任务类型明确切换 |
| 父 Agent 后续 | 继续工作 | 退出执行 |
| SubAgent 专注度 | 完成单个子任务 | 全权负责后续 |
Claude Code 中的完整实现
Claude Code 把上述原理做成了完整的工程实现,值得仔细看。
文件格式
SubAgent 是一个 Markdown 文件,YAML frontmatter 定义配置,文件正文是 System Prompt:
markdown
---
name: security-auditor
description: >
安全审计专家。当需要检查代码的安全漏洞、OWASP 风险、
SQL 注入、XSS、密钥硬编码等问题时触发。
tools: Read, Grep, Glob
model: sonnet
maxTurns: 20
color: red
---
你是一个专业的代码安全审计员。
审查时重点关注:
- SQL 注入风险
- 不安全的依赖项
- 硬编码的密钥或凭证
- XSS 漏洞
- 不安全的文件操作
按严重程度分组输出:❌ Critical / ⚠️ Warning / ℹ️ Info
SubAgent 接收到的 System Prompt 只有这个文件的正文,不包含父 Agent 的完整 Claude Code 系统提示------这正是隔离的来源,也是为什么 SubAgent 的行为更专注、更可预期。
内置 SubAgent 的设计逻辑
Claude Code 自带三个内置 SubAgent,它们的设计充分体现了"按需约束"的原则:
| SubAgent | 模型 | 工具权限 | 设计意图 |
|---|---|---|---|
| Explore | Haiku(快速低延迟) | 只读(禁止 Write/Edit) | 代码库探索,结果不需要保留在主对话 |
| Plan | 继承父 Agent | 只读 | Plan 模式下收集背景信息,自身不能再生成 SubAgent |
| General-purpose | 继承父 Agent | 全量工具 | 需要同时探索和修改的复杂多步任务 |
Explore 使用 Haiku 而不是更强的模型,是一个经典的成本优化决策------探索代码库这个任务不需要顶级推理能力,用小模型足够,省下来的成本可以用在更需要的地方。
作用域与优先级
SubAgent 文件存放在哪里决定了它的作用域,多个同名 SubAgent 按优先级解析:
markdown
优先级(高 → 低)
1. 组织级(Managed Settings)--- 管理员统一部署
2. CLI 参数(--agents)--- 当次会话有效,适合临时测试
3. 项目级(.claude/agents/)--- 当前项目,推荐提交到版本控制
4. 用户级(~/.claude/agents/)--- 所有项目可用
5. 插件级(plugin's agents/)--- 随插件安装
模型优先级解析
当父 Agent 调用 SubAgent 时,模型的最终选择按以下顺序解析:
markdown
1. 环境变量 CLAUDE_CODE_SUBAGENT_MODEL(最高优先级)
2. 调用时传入的 model 参数
3. SubAgent 定义文件里的 model 字段
4. 父 Agent 当前使用的模型(默认继承)
这个设计允许在不修改 SubAgent 定义的情况下,通过环境变量统一覆盖所有 SubAgent 的模型------在需要临时切换成本策略时非常方便。
用 SubAgent 做成本优化
SubAgent 架构天然提供了成本管控的抓手,可以从三个维度利用。
维度一:按任务复杂度路由模型
yaml
# 简单的代码搜索和探索:Haiku 完全够用
---
name: file-explorer
model: haiku
---
# 需要理解业务逻辑的代码审查:Sonnet
---
name: code-reviewer
model: sonnet
---
# 架构设计和复杂推理:Opus
---
name: architect
model: opus
---
维度二:用 maxTurns 防止失控
yaml
---
name: researcher
maxTurns: 15 # 最多 15 轮工具调用,超出自动停止
---
没有 maxTurns 限制的 SubAgent 在遇到复杂任务时可能无限循环------设置合理的上限是防止 Token 失控的保险丝。
维度三:上下文隔离本身就是节省
SubAgent 把探索过程封装在独立上下文里。如果没有 SubAgent,这些中间过程全部留在主对话------每次后续的父 Agent 调用都要携带这些历史,成本随任务数量线性增长。SubAgent 把这部分成本"切断"了。
什么时候用,什么时候不用
不是所有任务都适合委托给 SubAgent,用错了反而增加复杂度。
适合 SubAgent 的场景:
- 探索型任务:搜索代码库、查文档、分析日志------结果是一个结论,过程不需要留在主上下文
- 需要专业约束的任务:安全审计、代码格式化------用受限工具集的专业 SubAgent,防止越权操作
- 可并行的独立子任务:同时调研多个方向,最后汇总
- 重复使用的固定模式:团队里反复需要的同类任务,做成 SubAgent 复用
不适合 SubAgent 的场景:
- 任务和主流程高度耦合:SubAgent 的中间推理过程需要参与父 Agent 的后续决策------这时候隔离反而是障碍
- 需要持续双向交互:SubAgent 执行中需要频繁向父 Agent 确认或获取补充信息
- 任务太简单:创建和调度 SubAgent 本身有开销,一个简单的单步操作直接在主 Agent 里做更合理
判断的核心问题只有一个:这个任务的执行细节,父 Agent 需要知道吗? 如果答案是否,那就是 SubAgent 的候选任务。
总结
SubAgent 本质上是 AI 系统里的"委托与隔离"模式,和软件工程中的关注点分离是同一个设计哲学的不同形态。
回顾核心链路:
| 概念 | 本质 |
|---|---|
| 隔离的上下文窗口 | 执行细节不污染调用者 |
| Description 驱动委托 | 语义匹配的自动调度 |
| 能力约束 | 最小权限原则在 AI 里的实现 |
| Sequential / Parallel / Loop | 组合多个 Agent 的三种基本拓扑 |
| Agent as Tool vs Handoff | 控制权是否转移的两种协作方式 |
理解这套原理,不是为了把所有任务都拆成 SubAgent------而是知道什么时候"委托出去只拿结论"比"自己全程参与"更合理。这个判断力,才是构建高效 Agent 系统的核心能力。
参考资料
- Claude Code SubAgent 官方文档
- Anthropic: Building Effective Agents
- OpenAI Agents SDK: Multi-Agent Patterns
- Google ADK: Multi-Agent Systems