--- title: "Subagents 详解:Claude Code 如何避免上下文污染" source_url: https://mp.weixin.qq.com/s/qy_zaCZTCs1Ql3BIFmBMgg tags: [wechat, article, claude, openai, gpt, agent, harness, openclaw] source_type: wechat provenance_state: extracted sha256: 4c8677b280cf2e2e060b25f8ae2afbfe9ce9d2f41eb73e0f63d94a80c3b8a4d6 --- ---
昨天在梳理 Agent Harness 的上下文管理,我一直在想一个很小但很真实的场景:
一个 Claude Code 会话跑了半小时,模型读了几十个文件,查了很多次
grep
、
find
、
ls
,中间还跑过测试、看过日志、改过方案。等真正要写代码或做决策时,窗口里已经塞满了你再也不会回头看的中间过程。
这不是「上下文窗口不够大」这么简单。
更靠谱的说法是,很多长会话把探索过程、任务状态、文件事实和最终判断全混在了同一个窗口里。窗口看起来很热闹,真正有用的工作集反而越来越脏。
后来看到 Daniel San 那条关于 Claude Code Subagents 的帖子,正好把这个问题落到了一个很具体的机制上:会污染主窗口的探索过程,扔到独立的子代理里跑,主代理只拿回结果。
正好在想这件事,就多看了两眼。它有意思的地方倒不是 Subagent 又多了一种用法,而是把这几天一直在聊的那条线,落到了一个很具体的动作上。
前面写过,多智能体架构先看上下文边界。也聊过,Agent Harness 里的上下文,不太适合继续当聊天记录看,更像一个随时维护的工作集。
Subagents 正好卡在这两个问题中间。它的价值倒不在于让系统「看起来更像团队」,更多是把那些必须发生、但留在主窗口里就是污染的探索过程,放到独立工作区里跑完。
回头看这两个月 Claude Code 的相关讨论,主线其实在悄悄换:从「模型会不会写代码」,慢慢挪到「上下文、权限、工具、知识和验证边界能不能管住」。Subagents 算是其中一块拼图。
先放结论。
description
不是装饰字段,它是路由契约。写得越清楚,Claude Code 越容易把任务派给对的子代理。
所以从我的角度看,Subagent 与其当成「多智能体的表演」,不如当成 Agent Harness 的一种上下文卫生工具,定位会更稳。
这两个月翻 Claude Code 相关的讨论,会看到不少表面不同、底子相近的经验。
Kaxil Naik 有条长帖讲得很扎实。他是 Apache Airflow 的 PMC member 和 core committer,也在 Astronomer 做工程管理。他现在的工作流里,Skills、Hooks、MCP、CLI、Subagents、Agent teams 都在用。
帖子里他没有去比哪个模型最强,结论反而落在一句话上:Harness matters more than the model。
放到本文的语境里大致是这意思:模型能力当然重要,但长任务能不能稳定跑下去,更多看外面那层 harness。规则怎么沉淀,工具怎么暴露,权限怎么限制,失败怎么被发现,探索过程怎么隔离,这些细节往往才是分水岭。
同一条帖里他还提到一个挺扎心的点:Agent 失败很多时候不是「程序崩了」,而是「看起来挺对,差点就发出去了」。这也是我自己用得越久越警惕的地方。Subagents 当然能提速,但我更看重它让每一段工作有边界、有证据、有回收结果,最后仍然回到人来判断。
还有一条流传不少的帖子也说到类似意思:项目一大,就需要 separation of concerns。
CLAUDE.md
放标准和约束,skills 放可复用流程,hooks 放自动检查,Subagents 放隔离任务。
听上去像方法论,做起来其实很工程。如果所有东西都堆在同一段对话里,Agent 早晚会变成一锅粥。问题往往不在模型不懂,而在你给它的工作区已经没有边界。
Metabase 团队那篇复盘也是同一条线:在一个 50 万行的 Clojure 后端代码库上,他们做了 10 个 custom subagents。原因不复杂,大代码库每个子系统都有自己的习惯和坑,Claude 每次为了理解一个子系统都要重新搜索、读取、摸索,这些探索会很快吃掉主上下文。他们的解法不是再加一个更全能的 Agent,而是按领域把工作边界拆成更具体的子代理。
把这些材料放在一起看,我反倒不太想把本文写成一篇 Subagents 功能介绍。
更值得留意的是另一层信号:2026 年了, AI 编程工作流越来越像在给模型设计一套运行时。模型负责推理,外面这层 harness 负责把环境整理成它能稳定工作的样子。这个分工,去年还不太说得清,现在大家都在补。
做过稍长一点 Claude Code 任务的人,大概都有过类似体感。
刚开始,窗口很清爽。用户目标、项目结构、关键文件、约束条件都还清楚。
跑着跑着,模型开始探索:
grep -R "AuthService" .
find . -name "*.ts"
ls packages/api/src
npm test -- auth
这些动作没有问题。甚至可以说,它们是 Agent 真正能干活的前提。
问题在于,每一次工具调用的输入和输出,都会进入会话历史。
短任务里这不算什么。模型看一眼目录,读两个文件,做一个小修复,窗口足够用。
任务一长,情况就变了。
几十次搜索结果、重复的目录列表、被截断的日志、已经排除掉的代码路径,全都堆在同一段上下文里。真正有价值的信息,反而被低密度内容稀释。
Daniel San 在原帖里给了一个很直观的数字:半小时之后,你可能已经积累了 80k token 的噪音,这些信息你再也不会回头去看一遍。
更麻烦的是压缩。
当上下文接近上限,系统会做 compaction,把前面的内容压成摘要。压缩本身不是坏事,但如果窗口里大部分都是一次性探索痕迹,摘要就很容易把「无用噪音」和「关键事实」混在一起。
最后主 Agent 看到的是一段看似完整、实际变薄的历史。关键决策的依据可能在压缩中被磨掉,剩下的只是一个貌似合理的总结。
这也是前面说「上下文不能当聊天记录」的原因。聊天记录只管保存发生过什么,工作集得关心的是下一轮推理到底需要什么。
Subagent 正好挡住了其中一类污染:那些必须做、但做完之后不值得长期留在主窗口里的探索过程。
Claude Code 官方文档里对 Subagent 的描述其实很直白:当一个 side task 会把主会话塞满搜索结果、日志或文件内容,而且这些东西后面不一定会再引用时,就让 Subagent 在自己的上下文里完成工作,只把 summary 返回主会话。
这句话基本把边界讲完了。
我自己更倾向于把它当成「一次独立的工作区调用」来理解:子代理有自己的上下文窗口、自己的系统提示词、自己的工具集合和权限范围。主代理把任务派出去,它独立把活儿干完,最后只交回一份总结。
这件事真正值钱的地方,可以分成三层来看。
第一层是隔离。子代理有自己的上下文窗口。它为了查清某个问题,可以读 20 个文件、跑 30 次搜索、看一堆日志。主会话完全不用看这些过程,只接收最后相关的结论、证据和风险。
第二层是压缩。子代理返回的是最终结果,不是完整探索轨迹。一段低密度过程被自然压成了高密度信号。按原帖的说法,原本主窗口里 50 次工具调用的过程,最后只剩 3 行结论,其余中间状态全部丢弃。这里的省 token 是其次,主要是保护主 Agent 的注意力。
第三层是并行。如果几条调查路径互不依赖,就可以并行跑。一个子代理看认证模块,一个看数据库迁移,一个看 API 调用链,最后由主 Agent 汇总。
不过 Subagent 也不是哪里都好用。我自己用下来的体会是,它最适合「独立调查、结果回收」这一类任务。如果一项任务需要频繁来回讨论,或者规划、实现、测试之间共享大量中间状态,硬切出去反而会增加交接成本。
跟前面聊 Sub-Agent VS Agent Team 时的判断是一致的:能按上下文边界切开的,才适合交给 Subagent;切不开的时候,多一个 Agent 不见得是好事。
Claude Code 的 Subagent 可以用一个带 frontmatter 的 Markdown 文件定义。
原帖给了一个代码审查的例子,大致是这种形态:
---
name: code-reviewer
description: Review code quality, security, and maintainability after code changes.
tools: Read, Grep, Glob, Bash
model: sonnet
---
You are a senior code reviewer.
When invoked:
1. Run git diff to inspect recent changes
2. Focus only on modified files
3. Start the review immediately
Claude Code 会自动扫描这些文件,根据
description
决定什么时候调用哪个子代理。
文件可以放在不同位置,按从高到低的优先级大致是:
--agents
CLI flag:当前会话临时注入;
.claude/agents/
:项目级,适合纳入版本控制,团队共享;
~/.claude/agents/
:个人级,跨项目可用;
如果多个子代理同名,优先级更高的位置会生效。
手工写文件是一种方式,也可以直接用 Claude Code 的
/agents
界面来创建、查看和管理。对团队来说,我更喜欢先把项目级 agent 放进
.claude/agents/
,等模式稳定以后再沉淀成团队规范。
这里最值得留意的不是文件格式,而是
description
。
很多人会把它当成普通说明。放在 Agent 系统里,它其实是路由信号。Claude Code 要判断什么时候该调用哪个子代理,靠的就是这段描述。
描述写得太宽,比如「help with code」,就容易变成什么都能接、什么都接不稳。描述写得太窄,又可能永远触发不了。
我会把它写成三类信息:
比如一个更稳的审查代理,可以把描述写成:
description: Review modified backend code for security, correctness, and maintainability. Use after implementation, not for planning or feature design.
这比「code reviewer」四个字有用得多。
因为它不仅告诉模型「这是审查」,还告诉模型「实现后使用」「不负责规划」。边界越清楚,路由越稳。
这一点也能接上 Kaxil 那条帖子里的判断:好的 skill、hook、subagent,其实都是在把工程判断编码下来。
以前这些判断藏在人的脑子里,靠 code review、口头提醒、团队习惯来传。现在它们慢慢挪到了 Markdown、工具描述、hooks 和 agent 配置里。这部分东西的权重,在 Agent 工作流里只会越来越高。
不一定要先自己写一堆 Subagent 才能享受到隔离的好处。Claude Code 已经自带了几个最常用的内置子代理,开箱即用,原帖里重点提到两个:Explore 和 Plan。
Explore 负责搜索和理解代码库,不做修改。它会在自己的窗口里跑 grep、find、ls、glob 这一类命令,主会话只拿到「相关结果」。那些没匹配上的、看了又排除的、扫了一眼就过的中间噪音,全部留在子代理的窗口里。
Plan 负责在 plan mode 下做上下文调查。它读取文件、理解架构、梳理约束,然后输出一份分步实施方案。中间过程主 Agent 完全看不到,只看到最终的计划文档。
这两个内置子代理背后的设计,挺值得借鉴。长任务里最「脏」的部分,往往在探索阶段,倒不一定在执行阶段。
执行阶段会留下比较明确的产出:改了哪些文件、跑了哪些测试、还剩什么问题。探索阶段则相反,会产生大量临时路径:看过但无关的文件、试过但排除的方向、搜出来又没用的匹配项。这些东西对当下探索有价值,对后续主任务价值很低。让它们在子代理的独立窗口里出现,结束后只返回几条干净结论,主窗口才不会被磨花。
这一点和写系统时处理日志的逻辑很像。日志要有,但不会全塞进业务对象里。该查的时候能查,做决策时只带摘要。
Subagent 默认是 fresh context。
它只拿到主 Agent 给它的任务描述,在独立窗口里完成工作。这样最干净,也最符合「隔离探索过程」的目标。
但原帖重点提到一个新能力:fork。
如果主会话已经投入了很多上下文,比如很长的项目理解、历史讨论和约束条件,子代理从空白开始可能要重新构建背景,成本高,也容易漏掉关键前提。
这时可以用:
export CLAUDE_CODE_FORK_SUBAGENT=1
设置之后,所有新启动的子代理都默认 fork 父会话的完整上下文。也可以更精细一点,只在需要时通过
/fork
斜杠命令按需复制。
官方文档里说明了这一点:forked subagent 需要 Claude Code v2.1.117 或之后版本,仍属于 experimental。它会继承父会话到当前为止的完整上下文,看到相同的系统提示词、工具、模型和消息历史。它自己的工具调用仍然留在子代理里,最终只把结果返回主会话。
Daniel San 还提到 fork 的一个隐藏好处:fork 出来的子代理跟父代理共享 prompt cache 前缀,第二个之后的子代理在输入 token 上的成本可以低大概 10 倍。这对并行跑多个分支方案很重要。
总结一下 fork 出来的子代理的特性:
这解决了一个很真实的问题:
有些子任务如果不继承背景,根本没法独立完成。比如你已经和主 Agent 梳理了半天迁移方案,现在想让几个分支方案并行验证。让每个子代理从零读项目,反而浪费。
但我不会把 fork 当默认。
原因也很简单:fork 复制的不只是有用背景,也会复制噪音。
如果父窗口已经很脏,fork 只是把这份脏工作集复制给更多子代理。每个子代理看似「知道得更多」,实际可能一起被旧状态、无关日志和过期判断拖住。
所以我更倾向于这样用:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
说回到我自己怎么用:能用最小上下文说清的任务,尽量就不开 fork。fork 留给那些「不继承背景就完成不了」的子任务比较合适,别拿来当上下文管理的默认手段。
Subagent 跑起来之后还有一个现实问题:从控制台你很难看清主代理上下文的状态,更看不清并行运行的几个子代理在做什么。
Daniel San 自己开发了一个 hook 来解决这件事,叫 context-timeline 。
安装命令很简单:
npx claude-code-templates@latest --hook monitoring/context-timeline
它做的事不复杂:会话一开启就启动,用时间轴的方式展示主代理的上下文窗口,以及子代理如何在各自独立窗口里运行。每个正在跑的子代理状态都实时显示,等它执行完毕,返回给主代理的内容也会同步呈现。
这种工具看起来不起眼,但对长会话的可观测性很有帮助。当你能清楚看到「主窗口现在有什么」「子代理在跑什么」「它最终交回了什么」,你才能真正信任这套委派关系。
如果不打算装新工具,至少也建议在 Claude Code 里养成一个习惯:定期用
/context
这类命令观察主窗口的占用,发现某段时间塞了大量探索痕迹,就主动把它委派出去。
如果要往项目里真的放几个
.claude/agents/
,我倾向于先从很少的几个开始。
第一类是代码审查。
它适合独立,因为审查者不需要知道主 Agent 的所有中间想法,只需要看到 diff、相关文件、项目规则和测试结果。返回时最好带文件路径、问题等级、证据和建议,不要只写「整体没问题」。
第二类是影响面分析。
比如改一个接口、删一个字段、迁移一个配置项。子代理可以专门搜索引用、调用链、测试覆盖和文档残留。它的输出是「哪些地方受影响」,不是「我读了哪些文件」。
第三类是测试诊断。
测试失败时,主 Agent 往往会被日志淹没。让子代理单独看失败日志、定位可能原因、给出最小复现路径,再把结论还给主 Agent,会干净很多。
第四类是文档一致性检查。
代码改完后,另一个子代理去看 README、AGENTS.md、配置说明、示例命令有没有过期。这类工作边界清楚,也不需要和主 Agent 长时间共享状态。
但我不会一开始就建十几个。
Subagent 也是接口。接口太多,路由就会变复杂,维护成本也会上来。先放两三个高频、边界清楚、收益稳定的,跑一段时间再加。
一个我比较喜欢的 Subagent 模板,会刻意写清四件事:
---
name: backend-impact-analyzer
description: Analyze the impact of backend API or schema changes. Use before implementation or after changing shared contracts. Do not modify files.
tools: Read, Grep, Glob
model: sonnet
---
You analyze impact scope for backend changes.
Return:
1. Affected files and why they matter
2. Compatibility risks
3. Tests that should be added or updated
4. Unknowns that require human or main-agent confirmation
Do not edit files.
Do not propose broad refactors.
这里有几个细节:
Do not modify files
写进描述和正文,避免分析代理越权执行;
Read, Grep, Glob
,从权限层面就堵住「顺手改一改」的可能;
这类约束看起来啰嗦,但对 Agent 很有用。不显式写出来,它常常会自己补一套更模糊的版本。
Subagent 本身不复杂,真正容易出问题的是使用姿势。
第一个坑,是把委派写得太含糊。
「帮我看一下这个模块有没有问题」这种任务,子代理大概率会发散。更好的写法是:「只检查认证模块最近 diff 中的安全风险,重点看 token 校验、权限绕过和敏感日志,返回 P0/P1/P2 级别问题。」
任务越具体,子代理越像工具。任务越含糊,子代理越像另一个会跑偏的聊天窗口。
第二个坑,是让子代理返回太多过程。
如果子代理把所有搜索结果、完整日志、读过的文件都倒回主窗口,隔离价值就没了。
主 Agent 需要的是结论、证据、下一步动作。必要时带 2 到 3 个文件锚点就够了。
第三个坑,是把需要共享状态的任务硬切出去。
比如一个复杂重构,前端、后端、测试、文档每一步都互相影响。你强行拆成四个彼此隔离的 Subagents,最后会花更多成本做合并和纠偏。
这种任务也可以并行,但要先设计共享状态层、契约和回滚点。普通 Subagent 更适合「独立调查,结果回收」。
第四个坑,是 fork 上瘾。
fork 很强,但它解决的是「必要背景继承」,不是「上下文管理」。一个长期依赖 fork 的工作流,往往说明任务委派还不够清楚,或者稳定知识还没有沉淀到文件、规则和工具里。
更好的方向,是把可复用背景写进
.claude/agents/
、AGENTS.md、项目文档、测试和脚本。让子代理按需读取,而不是每次复制整段父会话。
把最近几篇放在一起看,线索其实很一致。
多智能体架构先看上下文边界 。上下文管理要把聊天记录改造成工作集。Subagent 则是其中一个相当实用的执行机制:一次性探索放进独立窗口,主窗口只留下结果。
所以 Subagent 不只是 Claude Code 的一个功能点。它背后是 Agent 系统在补的一层能力:模型负责推理,外面这层 harness 负责管理工作区。
哪些内容进窗口,哪些内容留在窗口外,哪些探索可以丢弃,哪些状态必须持久化,哪些任务适合 fork,哪些任务只能共享状态。这些问题不会因为模型窗口变大而消失。窗口越大,反而越需要打理。
更大的窗口很容易给人一种错觉:什么都能塞,什么都先留着。但长任务真正需要的不是「记住一切」,而是在每一轮调用前,把该看的东西摆到模型面前,把不该干扰它的东西挡在外面。
这也是 Kaxil 那句 Harness matters more than the model 让我多看了好几眼的原因。模型当然重要。只是到了长任务、多人协作、大代码库和生产系统里,模型外面的结构会越来越决定下限。
如果说 2025 年很多人还在比较模型聪不聪明,2026 年更值得盯的,可能是这些看起来朴素的东西:
.claude/agents/
里的子代理边界;
CLAUDE.md
里的项目规则;
这些东西不热闹,但它们让 Agent 从「能演示」走向「能长期用」。
如果现在要在 Claude Code 里上手 Subagents,我自己不会一上来就搭一套复杂的 Agent Team,也不会一口气写一堆角色。
更稳妥的起点,是几个最朴素的子代理:一个只做代码审查,一个只做影响面搜索,一个只做测试失败诊断。先把它们扔进
.claude/agents/
里跑一跑,观察两件事就够了:
这两件事如果都成立,基本可以确认它在帮你改善上下文工作集。坦率说,我自己在 description 这一项上就反复改过好几版,到现在也不能说调到了最稳的状态。
至于 fork、prompt cache、context-timeline 这些能力,可以放到后面再补。它们都很有价值,但前提是先把最基础的边界切对。
回头看,长任务跑不稳,原因往往不是少了一个更聪明的模型,更多是窗口里堆了太多本该用完就丢的东西。
参考来源:
Keep your Claude Code context clean with Subagents
,2026-04-27 03:35,https://x.com/dani_avila7/status/2048486242321662189
Create custom subagents
,https://code.claude.com/docs/en/sub-agents
I Haven't Written a Line of Code in 4 Months (But I Ship More Than Ever)
,2026-03-27,https://x.com/kaxil/status/2037503513350005134
How we built ten custom subagents to tame a 500K-line Clojure codebase
,2026-04-16,https://www.metabase.com/blog/ten-custom-subagents
如喜欢本文,请点击右上角,把文章分享到朋友圈
如有想了解学习的技术点,请留言给若飞安排分享
因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享
·END·
相关阅读:
版权申明:内容来源网络,仅供学习研究,版权归原创者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!
我们都是架构师!