拆解 Claude Code SubAgent:隔离、专业化

📅 2026/6/23 10:32:00
拆解 Claude Code SubAgent:隔离、专业化
入门篇1. 一个比喻理解 SubAgent想象你是一个项目经理主 Agent手下有几个专员SubAgent。你不会自己去翻 200 个文件找答案——你会把任务交给调研专员让他去翻他翻完了最终再把结论汇报给你。这就是 SubAgent 做的事主 Agent 把任务派给一个独立的子进程去执行子进程干完后只把结论带回来。比如 Claude Code 内部的工具调用Agent({ SubAgent_type: Explore, prompt: 搜索整个代码库找出所有 API 端点定义 })这段调用会启动一个 Explore 类型的子代理它自己去搜索、读取文件、分析代码最后把结果摘要返回。主 Agent 只看到结论不看到过程。一句话总结SubAgent 一个拥有独立上下文窗口的自治 Worker干完活只交结论。2. SubAgent 解决的三个核心问题问题一上下文污染Claude 的上下文窗口再大也是有限的。如果让主 Agent 自己去搜 30 个文件那些搜索结果、文件内容、中间分析全部留在主对话里等真正要做决策时那上下文窗口可能已经快满了。SubAgent 的解决方案是让自己天然拥有一个独立的上下文窗口。即中间过程全都留在子代理里主对话只看结论。也就是说子代理执行完毕后这些中间内容就消失了。简单判断如果信息对当下执行是必要的但对后续决策是噪声——用子代理。问题二行为不可控主 Agent 通常拥有完整的工具权限读文件、写文件、执行命令。但某些任务你只想让它看不想让它改。对于这个问题 SubAgent 的解决方案是精确的工具权限控制。即我们可以定义一个只读型子代理只给它Read、Grep、Glob三个工具这样它想改也改不了了。# 只读型子代理代码审查 tools: Read, Grep, Glob # 开发型子代理bug 修复 tools: Read, Write, Edit, Bash # 研究型子代理技术调研 tools: Read, WebFetch, WebSearch问题三经验无法沉淀每次都要手动告诉 Claude 去查这个、用那个方式分析。这些操作步骤无法复用。针对这个问题 SubAgent 的解决方案是配置即文件。子代理的定义保存在.md文件中可以放进 Git 与团队共享好用的配置可以复制到其他项目。所以 SubAgent 可以用三个词概括隔离、约束、复用。那么再从更高层面看 SubAgent 的设计哲学其实就是将一个大脑拆成多个岗位角色每个岗位只做一件事并且有明确的权限边界。3. 你可能已经在使用了Claude Code 内置了几个 SubAgent。当你在对话里说”帮我看看代码库结构”、”先规划一下怎么做”、或者 Claude 自动走验证流程的时候这些 SubAgent 就在干活。而你可能根本没注意到。Explore代码库的搜索引擎Explore 是最常用的内置 SubAgent。它的定位很明确快速搜索、只读分析。当我们在对话里说比如”帮我找一下所有 API 端点的定义”或者”这个函数在哪些地方被调用了”Claude 就会启动 Explore 去干活。它会把成百上千行的 grep 结果、文件读取、路径分析全吞进自己的上下文里最后只给你一份干净的摘要。搜索深度分三档quick、medium默认、very thorough。这个档位是可以在 prompt 里指定的Explore 会据此调整搜多广。这不是代码层面的硬限制纯粹是 prompt 级别的指导quick 就是跑几条 grep 就收工适合”某个 class 在哪个文件”这种目标明确的问题medium 则会多搜几个路径、多读几个文件适合”这个模块的结构是怎样的”very thorough 会在多个目录和命名规范下反复搜尽量不留死角——适合”梳理认证流程从入口到数据库的完整调用链”。工具方面 Explore 能用 Glob按文件名搜、Grep按内容搜、Read读文件、Bash但只能跑只读命令。在前段时间 Claude Code 暴露的源码里使用disallowedTools硬性屏蔽了 Edit、Write、NotebookEdit。说明它确实改不了东西。外部用户跑 Explore 用的是 Haiku快且便宜。Anthropic 内部用户则会继承主 Agent 的模型。Claude Code 暴露的源码里有个不太起眼的阈值EXPLORE_AGENT_MIN_QUERIES 3。这个参数的作用是主 Agent 被告知任务只需要 1-2 次搜索就搞定的别启动 Explore直接用 Grep/Read只有明确需要 3 次以上查询时才值得派出去。另外Explore 默认省略 CLAUDE.md 和 gitStatus能到 40KB。只读代理不需要知道 commit 规范和 PR 流程自己会跑git status。这一项每周会省 5-15 Gtok。Plan动手之前先想清楚Plan 的定位是软件架构师。它不写代码专门在动手之前把方案想透。比如当我们跟 Claude 说”我想给系统加个支付模块”这个时候 Claude 就会先派 Plan 去调研Plan 会读现有代码、找已有的模式和约定、理清依赖关系、最后输出一份分步实施计划。系统提示给 Plan 定义了四步流程理解需求深入探索读代码、追踪调用链、参考已有实现设计方案考虑取舍输出计划分步策略、依赖关系、可能的坑。输出必须以”Critical Files for Implementation”结尾并列出最关键的 3-5 个文件这样主 Agent 拿到这份计划就知道下一步该读什么、改什么了。Plan 跟 Explore 一样只读——同样的使用了disallowedTools改不了文件。但模型不同Plan 继承主 Agent 的模型不会降级到 Haiku。架构设计需要更强的推理能力用便宜模型容易翻车。Explore 和 Plan 的分工边界是Explore 搜完就交结果Plan 搜完还要分析、权衡、给建议。找函数在哪用 Explore搞清”加这个功能要改哪些文件、按什么顺序改”用 Plan。General-purpose什么都干的全能选手Explore 和 Plan 都被硬性禁止了 Edit、Write 等工具但 General-purpose 没这个限制。tools: [*]即父 Agent 有什么它就能用什么这是它跟 Explore/Plan 的根本区别。搜索和规划是只读的活儿而 General-purpose 要真刀真枪改代码。系统提示很短两段话完事Given the users message, you should use the tools available to complete the task. Complete the task fully — dont gold-plate, but dont leave it half-done.意思是把活干完别画蛇添足但也别半途而废。General-purpose 适合的场景是那种连贯的多步骤流程先读代码定位问题、再改代码、再跑测试验证。比如”修复认证模块的登录 bug”这种任务。模型字段故意留空由getDefaultSubagentModel()在运行时决定是跟着会话配置走的。Claude Code Guide——产品文档专家Claude Code 还有一个不太起眼的内置 SubAgentclaude-code-guide。当你问”Claude Code 怎么配 hooks”、”Agent SDK 怎么用”的时候Claude 会派它去查官方文档。它的工具是 Glob、Grep、Read、WebFetch、WebSearch。Haiku 模型dontAsk 权限不弹确认框。干活流程是先抓code.claude.com和platform.claude.com的文档索引再定位到具体页面拿答案。Verification——专门来挑刺的Verification 的系统提示第一句话就说Your job is not to confirm the implementation works — its to try to break it.它不是来验证”代码能跑”的它是来找茬的。当主 Agent 完成一项实现任务后Verification 被自动调用。它会跑构建、测试、lint然后根据变更类型前端、后端、CLI、数据库迁移等各有各的检查套路做针对性验证还要跑边界值测试和对抗性探测。输出格式要求严格每条检查必须附带实际执行的命令和输出不能只说”看起来没问题”。最后给出 VERDICTPASS、FAIL 或 PARTIAL。默认后台运行模型继承主 Agent。这五个内置 SubAgent 各管一摊搜索、规划、执行、查文档、找茬。共同点是它们都把高噪声的工作留在子进程里不让垃圾信息堆到主对话中。4. 什么时候该用什么时候不该用其实判断标准很简单主对话需不需要承载过程本身适合用 SubAgent 的场景有高噪声输出的任务——主对话只关心结论不关心过程。比如搜索 30 个文件找一个 API 定义。角色边界非常明确的任务——天然需要和其他任务隔离开。比如代码审查只看不改。可以并行执行的研究型任务——比如同时调研三个模块的实现方式。可以拆成清晰阶段的流水线式任务——比如先调研再规划再实现。不适合用 SubAgent 的场景你想做的事该用什么读取一个已知路径的文件Read工具搜索 class Foo 在哪Grep工具在 2-3 个文件里找东西Read工具简单的文本修改Edit工具直接改重要提醒子代理不能再嵌套调用子代理。所有编排都必须由主对话完成流水线的调度中心只有一个。实践篇5. 三步创建自定义 SubAgent方式一交互式推荐新手在 Claude Code 中输入/agents按照向导操作即可。方式二手写配置文件推荐进阶直接创建.claude/agents/your-agent.md文件。优势是更精细的控制、方便版本管理、可以从其他项目复制。方式三CLI 参数临时创建适合 CI/CD通过--agents参数在启动时传入 JSON 格式的子代理定义。仅在当前会话中存在不会保存到磁盘。claude --agents [{name:lint-checker,tools:[Bash,Read]}]这种方式特别适合 CI/CD 自动化在流水线中临时创建任务专用的子代理。6. 配置文件完全指南一个完整的子代理配置文件长这样--- name: code-reviewer description: Review code for security issues and best practices. Use after code changes. tools: - Read - Grep - Glob permissionMode: plan model: sonnet skills: - chain-knowledge - recent-incidents hooks: PreToolUse: - matcher: Bash hooks: - type: command command: ./scripts/validate-readonly-query.sh --- 你是一个代码审查专家。 当被调用时 1. 首先理解代码变更的范围 2. 检查安全问题 3. 检查代码规范 4. 提供改进建议 输出格式 ## 审查结果 - 安全问题[列表] - 规范问题[列表] - 建议[列表]frontmatter 字段详解字段作用备注name子代理的唯一标识如code-reviewerdescription决定 Claude何时自动调用这个子代理说清楚做什么和什么时候用tools工具白名单只开放必要的工具disallowedTools工具黑名单不要和tools同时用model选择模型sonnet、opus、haiku等permissionMode权限模式控制遇到权限操作时如何处理skills预加载的技能列表子代理不继承主对话的 Skill需要显式列出hooks生命周期钩子只在子代理运行期间生效结束后自动清理maxTurns最大执行轮次防止无限循环effort思考努力级别0-1简单任务用低值复杂任务用高值工具权限的最小特权原则遵循一个原则能用 Read 完成的任务就不要给 Edit。只读型审计/检查 研究型信息收集 开发型读写改 ├── Read ├── Read ├── Read ├── Grep ├── Grep ├── Write └── Glob ├── Glob ├── Edit ├── WebFetch ├── Bash └── WebSearch ├── Glob └── Grep子代理存放位置与优先级子代理定义有六种来源同名冲突时高优先级覆盖低优先级。从高到低1. Built-in agents内置源码里写死的比如 Explore、Plan、General-purpose、Verification。你不能改它们也不能删。2. Plugin agents插件提供装了插件之后插件自带的子代理会自动注册。名字带命名空间前缀plugin-name:agent-name避免跟自定义代理撞名。插件代理有个安全限制frontmatter 里写了permissionMode、hooks、mcpServers会被直接忽略。源码注释说得很直白——插件是第三方代码这些字段会让代理的权限超出用户安装时批准的范围。如果你需要这些控制能力得在.claude/agents/里手写那里的定义是你自己审核过的。3. User agents用户级放在~/.claude/agents/目录下Windows 是%USERPROFILE%\.claude\agents\。对当前用户所有项目生效。比如你有一个通用的代码审查代理放到这里不管在哪个项目里都能用。创建方式直接往这个目录丢.md文件就行或者在 Claude Code 里输入/agents选择用户级位置。4. Project agents项目级放在项目根目录的.claude/agents/下。只对当前项目生效。好处是可以提交到 Git团队共享。your-project/ └── .claude/ └── agents/ ├── code-reviewer.md └── deploy-checker.md5. Flag agentsCLI 参数通过claude --agents参数在启动时传入 JSON 格式定义。只存在于当前会话关掉就没了。适合 CI/CD 流水线或者临时用一下的场景。示例claude --agents [{name:quick-check,tools:[Read,Grep]}]6. Managed agents企业管理这是最低优先级也是最少人知道的一种。源码里的 source 叫policySettings。Managed agents 存放在系统级的管理目录里macOS:/Library/Application Support/ClaudeCode/.claude/agents/Windows:C:\Program Files\ClaudeCode\.claude\agents\Linux:/etc/claude-code/.claude/agents/由 IT 管理员配置普通用户改不了。它的设计目的是让企业管理员给团队统一下发子代理定义——比如全公司通用的安全审计代理、合规检查代理。Managed agents 的加载路径来自getManagedFilePath()这个目录也存放企业级的managed-settings.json配置。源码里的getManagedFilePath()还支持一个 drop-in 目录managed-settings.d/里面可以放多个配置文件按字母顺序叠加上去。因为优先级最低如果用户或项目里有同名的代理企业下发的版本会被覆盖。这是有意为之让本地自定义优先于企业默认。正文部分子代理的系统提示词---之间的 frontmatter 是配置下面的 markdown 正文是子代理的系统提示词。子代理只会收到这段系统提示词和基本环境信息不会继承主对话的完整系统提示词。7. 前台、后台与恢复前台模式Foreground子代理在执行期间阻塞主对话。权限弹窗和问题会实时传递给用户。适用于需要人工审批、人工交互的任务。后台模式Background子代理并行执行用户可以继续在主对话中工作适合独立的探索或分析任务。Claude 会根据任务自动选择前台或后台。也可以手动控制对 Claude 说 run this in the background正在运行的前台子代理可以按CtrlB切换到后台切换到后台时Claude Code 会预先请求子代理可能需要的所有权限因为后台运行时无法弹出交互式确认。恢复Resume每个子代理执行完成后Claude 会自动获得它的 agent ID。你可以让 Claude 在之前的基础上继续用 code-reviewer 子代理审查认证模块 [子代理完成] 继续刚才的审查再看一下授权逻辑 [Claude 恢复之前的子代理保留完整上下文]恢复会保留之前的对话历史让它从上次停下的地方继续而不是重新开始。但注意Explore 和 Plan 是一次性代理执行完毕后不能通过 SendMessage 继续对话。8. 最佳实践Prompt 怎么写核心原则源码里有一段系统提示是 Claude Code 告诉自己怎么写子代理 prompt 的Brief the agent like a smart colleague who just walked into the room — it hasnt seen this conversation, doesnt know what youve tried, doesnt understand why this task matters.因为 Fresh Agent你指定了 subagent_type 的那种从零开始没有父 Agent 的任何对话历史。所以 prompt 里必须包含子代理完成任务所需的全部信息。写得差的 prompt 长什么样查一下认证模块这种 prompt 的问题子代理不知道认证模块指的是哪部分代码不知道你已经看过什么不知道你查完之后要干嘛。它会瞎逛一圈大概率找出一堆不相关的东西。写得好的 prompt 长什么样我需要了解这个项目中用户认证的完整流程。具体来说 1. 项目是一个 Next.js 应用认证相关代码可能在 src/auth/ 或 src/middleware/ 目录下 2. 我已经知道用了 NextAuth.js但不确定具体配置在哪个文件 3. 我需要找到登录入口、session 管理、权限校验中间件 4. 每个模块用了什么文件、关键函数名叫什么 最后给我一个调用链的总结从用户点击登录到请求被校验通过中间经过了哪些函数。差别在哪背景信息Next.js、NextAuth.js、已知信息已经知道用了 NextAuth、明确目标找调用链、输出格式总结调用链。子代理拿到这些就能精准行动。五条规则给背景。你在做什么项目、用了什么技术栈、为什么需要这个信息。不要假设子代理知道任何上下文。说目标别说步骤。告诉它你要什么结果让它自己决定怎么搜。找出认证流程的调用链比先搜 auth 相关文件再读每个文件再找出函数调用好得多。后者是把你的猜测当成了搜索方案万一前提错了就白费。交代已知信息。我已经看过 src/auth/login.ts排除了 cookie 方案。这样子代理不会重复你已经做过的工作。指定输出格式。200 字以内、列出每个模块对应的文件路径和关键函数名。没有格式约束的输出要么太长要么太短。不要甩锅。基于你的发现修复 bug——反面教材。子代理跑完调研你拿到结果你自己判断怎么修。让它既调研又修复等于把决策外包了。源码里的原话Never delegate understanding. Dont write based on your findings, fix the bug. Those phrases push synthesis onto the agent instead of doing it yourself. Write prompts that prove you understood: include file paths, line numbers, what specifically to change.并行和串行并行和串行是 Claude Code 内部的调度策略了解它可以帮助你更有效地给 Claude 下指令。并行如果你跟 Claude 说同时帮我调研三个模块的实现方式Claude 会在同一条消息里发出多个子代理调用。这些子代理同时启动、同时跑、各自独立返回结果。适合的场景多个互相不依赖的调研任务。比如帮我同时看一下前端路由、后端 API、数据库 schema 分别怎么设计的。串行后一个任务依赖前一个的结果。比如先调研认证模块的结构再基于调研结果决定怎么加一个新功能。这时候 Claude 会先跑第一个子代理等结果回来再决定下一步。适合的场景有依赖关系的流水线任务。你该怎么利用这点在对话里说清楚任务之间的关系就行同时帮我查 A 和 B → Claude 会并行派两个 Explore先帮我查 A查完再基于结果做 B → Claude 会串行执行帮我查 A、B、C它们之间没有依赖 → Claude 会并行派三个原理篇读源码不只是看它做了什么更重要的是为什么这么做。基于 v2.1.88 源码聊下 Claude Code SubAgent 系统背后的设计决策。9. 为什么 SubAgent 是一个微型会话Claude Code 团队没有把 SubAgent 当成一个轻量级的任务派发。他们把它当成一个完整的、独立的 Claude Code 会话的微缩版本。每次启动一个子代理系统会从磁盘或内存找到对应的 AgentDefinition为它组装一套独立的工具池构建一段独立的系统提示创建一个隔离的 ToolUseContext权限、文件状态、拒绝追踪全是新的可选地为它初始化专属的 MCP Server启动一个独立的 query() 循环跑完后在 finally 块里做十项清理这个流程跟启动一个新的 Claude Code 会话几乎没有区别只是它跑在父进程内部、生命周期由父 Agent 管理。为什么要做得这么重轻量级的做法是共享父 Agent 的上下文和状态只在工具层面做点过滤就行了。但 Claude Code 选了隔离路线。原因在于一个核心判断在 LLM 系统里上下文污染比上下文缺失更危险。共享上下文意味着子代理的中间输出会回溢到父对话里。一个 Explore 代理读 30 个文件产生的中间内容如果留在主对话里后面做决策时有效信息就被稀释了。相比之下子代理从零开始需要你在 prompt 里多写几句背景信息——这是可控的成本。上下文污染是不可控的风险。这就是为什么源码里 Fresh Agent 路径标准路径选择了零上下文继承。这是经过权衡的设计决策。10. 两条路径的设计取舍专业化 vs 缓存效率SubAgent 有两条执行路径Fresh Agent 和 Fork。Fresh Agent为专业化做的选择当指定subagent_type时系统走 Fresh 路径。它的四个特征零上下文继承专用系统提示独立工具池独立权限模式全部服务于同一个目标让每个子代理成为某个领域的专家。Explore 只有搜索工具、Plan 继承父模型做架构分析、Verification 默认跑在后台专门找茬。工具池、系统提示、模型、运行模式全都围绕这个代理的职责量身定制。这种专业化是有代价的。因为每个子代理有独立的系统提示和工具池它跟父 Agent 的 API 请求前缀不同没法共享 Prompt Cache。每次启动一个 Fresh Agent基本等于一次全新的 API 调用。源码里还特意把普通子代理的thinkingConfig设成{ type: disabled }省输出 token但进一步确保了缓存不可能命中。设计取舍很清晰Fresh Agent 用缓存效率换专业化。只要你指定了 subagent_type你就选择了让这个代理在自己的领域内做最好而不是让它尽量便宜。Fork为缓存效率做的选择Fork 是反过来的取舍。它不追求专业化追求的是让并行子代理尽量便宜。看 Fork 的定义export const FORK_AGENT { tools: [*], // 不过滤工具——保持跟父一致 model: inherit, // 不换模型——保持跟父一致 getSystemPrompt: () , // 不生成新提示——保持跟父一致 permissionMode: bubble, }每一行都在做同一件事保持跟父 Agent 的 API 请求前缀字节级一致。因为 Anthropic API 的 Prompt Cache 要求前缀完全匹配。系统提示、工具定义、模型、消息前缀、思考配置五个维度全部一致缓存才能命中。Fork 通过buildForkedMessages()克隆父 Agent 的完整对话历史给每个 tool_use 塞一个占位符 result然后附上各自的指令文本。所有 Fork 子代理的前 N 条消息完全相同只有最后一个文本块不同。父 Agent 的请求: [system][tools][msg_1]...[msg_N][assistant_tool_uses] Fork #1: [system][tools][msg_1]...[msg_N][assistant_tool_uses] ← 缓存命中 [user: 调查模块A] Fork #2: [system][tools][msg_1]...[msg_N][assistant_tool_uses] ← 缓存命中 [user: 调查模块B]派两个 Fork 子代理并行调研理论上只有各自最后的那个指令文本是新的 token。相比派两个 Fresh Agent成本可以低一个数量级。但 Fork 的限制也来自这个设计不能换模型换了缓存就废了不能自定义工具池过滤了工具定义就变了不能有独立的系统提示换了前缀就不同了。它是一个跟父 Agent 一模一样只是干不同的活的并行执行单元。还有一个设计约束Fork 不能嵌套。isInForkChild()通过扫描fork-boilerplate标签来阻止 Fork 套 Fork。原因也很实际如果 Fork 可以嵌套内层 Fork 会继承外层 Fork 已经被污染的上下文隔离性就保不住了。为什么不让 Fresh Agent 也共享缓存因为 Fresh Agent 的系统提示不同、工具池被过滤、思考配置被禁用、模型可能不同。四个维度的差异导致缓存键完全不同。这是 Fresh Agent 选择专业化的必然代价。两条路径的设计本质上是一个光谱的两端专业化 ────────────────── 缓存效率 Fresh Agent Fork 独立提示工具模型 继承一切 不共享缓存 字节级共享 适合特定职责 适合并行调研11. 权限模型单向棘轮原则源码里有一个关于权限的硬性规则看着简单背后的设计思路值得细想父 Agent 的bypassPermissions、acceptEdits和auto模式永远优先子代理降级不了。if ( agentPermissionMode state.toolPermissionContext.mode ! bypassPermissions state.toolPermissionContext.mode ! acceptEdits !(feature(TRANSCRIPT_CLASSIFIER) state.toolPermissionContext.mode auto) ) { toolPermissionContext { ...toolPermissionContext, mode: agentPermissionMode } }这是一个单向棘轮ratchet设计。权限只能往更严格的方向调不能往更宽松的方向调。考虑一个场景你用--allowedTools参数限制了子代理只能用 Read 和 Grep结果子代理定义里写了tools: [*]。如果子代理的权限覆盖了你的限制你花心思设的工具白名单就白费了。这违背了调用者控制安全边界的原则。类似的棘轮设计还有好几个allowedTools参数替换所有会话级规则但保留 CLI 参数级规则。会话级规则是你运行时手动批准的CLI 参数级规则是你启动时明确指定的。后者优先级更高因为它是更早、更明确的安全决策。异步 SubAgent 有独立的拒绝计数器localDenialTracking不影响父 Agent。因为异步代理跑在后台它的权限拒绝不应该污染父 Agent 的交互体验。异步代理强制设置shouldAvoidPermissionPrompts true即不弹确认框未授权操作直接拒绝。这同样是为了保安全边界后台跑着的代理没法跟你交互确认所以宁可直接拒绝也不默认放行。所有这些设计的共同点是宁可让子代理功能受限也不让安全边界被突破。这在 LLM 系统里尤其重要因为 LLM 的行为不可预测。权限边界的严谨性是最后一道防线。12. 工具池设计最小权限原则的实际落地工具池组装的逻辑看代码就几行但设计思路值得琢磨。