avatar


4.SubAgent

引子

想象这么一个场景:ClaudeCode在修复一个BUG时,读了500行错误日志,其中只有3行错误栈有用;跑了一次测试,200行输出里只有5行是关键。可这500行日志、200行输出会原封不动地留在主上下文里赖着不走,挤占窗口、稀释关键信息,还会诱发更早的上下文压缩。窗口越用越脏,回答质量随之下降——这就是上下文污染

那么:高噪声的子任务,怎样不污染、不耗尽主上下文?

ClaudeCode对这个问题的解法是SubAgent(子智能体):把高噪声的子任务委派到一个独立的上下文窗口里执行,过程留在那边,只把结论带回来。在「大模型+工具调用循环+有限的上下文窗口」这条主线上,SubAgent缓解的是窗口这一环——循环照转,只是转在别处。

官方文档:https://code.claude.com/docs/en/sub-agents

原理

什么是SubAgent

SubAgent是在当前会话内运行的专用助手。它有自己独立的上下文窗口,跑自己完整的工具调用循环(agent loop),自己读文件、自己执行命令、自己迭代。

关键点在于,它与主对话的交互只有两次:接收一段任务描述,返回一份结果摘要。中间逐步的输出不会回传,它读过的几十个文件、翻过的几百行日志,随任务结束一起丢弃,主上下文始终干净。

正因为交互是"一次进、一次出",它有两个直接后果:

  1. 不适合需要来回讨论的活。
    中途没有反馈通道,需要边做边看、逐步确认的任务(比如需要你一步步拍板的重构)应该留在主对话里做,而不是委派出去。
  2. 依赖主对话UI或会话状态的工具用不了。
    例如AskUserQuestion(在主对话里向用户弹出一道选择题、用户点选后再继续)而SubAgent和用户之间根本没有这样的交互通道,所以这类工具即便写进tools也不会生效。

注意:SubAgent的窗口同样有限,接近上限时它会在自己的窗口内独立做上下文压缩,与主会话互不影响。

两种形态:Fork与Named

SubAgent按"带不带主对话的背景"分成两种形态。

Fork(分支) Named(具名)
上下文 继承主对话完整历史 全新上下文
系统提示 与主会话相同 定义文件的正文
工具与权限 全部继承主会话 定义文件的tools等字段控制
定义文件 .claude/agents/*.md
调起方式 /fork命令 自动匹配description,或@agent-<name>显式点名
适用场景 需要全部背景的分身,比如从当前进度分一条线去起草测试 背景必须干净的专家,比如只读的代码审查员

Fork的入口只有/fork命令。它没有定义文件、没有自己的系统提示、没有路由,一切都是从主会话拷贝的。
本章其后所有内容(路由与硬约束、定义文件与字段、内置Agent)针对的都是Named形态。Fork不再单独讨论。

注意:Skill里也有一个叫context: fork的字段,但名字里的"fork"指的是"分叉到另一个上下文去执行",并不是Fork SubAgent。声明了context: fork的Skill被调用时,开的是一个普通SubAgent,不继承对话历史,用的是指定Agent类型(默认为general-purpose)的系统提示,Skill正文作为任务描述送入。它仍然隔离、仍然只回传摘要,但它的上下文里没有主对话的历史。细节见《3.Skill》

委派机制

description

是否委派、委派给谁,由ClaudeCode自动决定,依据就是description字段。
ClaudeCode会拿当前任务与每个SubAgent的description做匹配,匹配上就委派。

这与Skill靠description常驻匹配是同一个机制,见《3.Skill》;差异只在Skill把正文注入当前上下文,SubAgent把任务送进一个独立的上下文。

再委派

SubAgent自己也能再委派SubAgent,但有两个限制:

  1. 嵌套上限5层。
    超过5层无法继续生成。而且深度在SubAgent被生成的那一刻就确定、并写进它的运行记录;之后即便恢复(resume)会话,沿用的也是当初的深度,不会重新计算。也就是说,不能靠"关掉再恢复"来绕过这个上限。
  2. 一次性。
    每个SubAgent执行完即结束,中间过程不保留;部分内置Agent(如Explore、Plan)甚至完全不可恢复。

选型:SubAgent还是Skill

隔离噪声只是手段之一。SubAgent与Skill都能让ClaudeCode按需获得额外能力,但机制不同,适用场景也不同:

维度 SubAgent Skill
运行位置 会话内的独立上下文 主上下文内
上下文成本 隔离,只回传摘要 全部计入主对话
适用场景 隔离高噪声操作 跨任务复用指令

一句话分工:要复用指令,用Skill;要隔离噪声,用SubAgent。

示例

随项目共享的具名Agent

例如,我们创建一个项目级的code-reviewer,把审查员定义进项目仓库,团队每个人都能用。

示例代码(.claude/agents/code-reviewer.md):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
name: code-reviewer
description: 资深代码审查专家,审查代码质量、安全漏洞与最佳实践。代码有改动时主动使用(use proactively)。
tools: Read, Grep, Glob, Bash
model: sonnet
---

你是一名资深代码审查员。被调用时:

1. 运行`git diff`找出本次改动
2. 聚焦被修改的文件
3. 按优先级组织反馈:严重、警告、建议
4. 每条反馈附带具体的修改示例

不要输出你读过的文件内容,只输出审查结论。
  • tools只给了读取检索类工具加Bash(用来跑git diff),没有EditWrite,审查员永远改不了代码。
  • 正文最后一句约束了回传内容,确保带回主对话的是结论而不是噪声。

只为当次会话临时定义的Agent

有些SubAgent只为当前这一次排查服务,不值得在仓库里留文件,用--agents在启动时临时定义。会话结束即消失,不落盘、不污染项目配置。

例如,会话级的临时debugger,示例代码:

1
2
3
4
5
6
7
8
claude --agents '{
"debugger": {
"description": "Debugging specialist for errors",
"prompt": "You are an expert debugger. Analyze errors, identify root causes, provide fixes.",
"tools": ["Read", "Edit", "Bash"],
"model": "sonnet"
}
}'

带持久记忆的领域专家

SubAgent本身是一次性的,但配上memory字段,它可以跨会话积累知识。

示例代码(.claude/agents/domain-expert.md):

1
2
3
4
5
6
7
8
---
name: domain-expert
description: 本项目的领域知识专家,回答架构、约定与历史决策类问题时使用。
memory: project
---

你是本项目的领域专家。回答问题时优先查阅你的记忆。
每次任务结束前,把新确认的架构模式、团队约定和反复出现的问题更新进记忆。
  • memory为这个SubAgent开启持久记忆。每次任务结束时它把新知识写入.claude/agent-memory/domain-expert/,下次调用时自动注入上下文。
    作用域有三种:project(随项目提交、团队共享)、user(跨项目个人)、local(仅本机,不提交)。记忆的读写机制见《2.Memory》
  • 设了memory字段后,Read/Write/Edit会自动开启,SubAgent需要用它们来读写记忆文件,不必在tools里重复声明。
  • SubAgent的执行是一次性的,但沉淀下来的记忆不是。

实践

内置Agent

ClaudeCode内置了几个常用Agent:

Agent 模型 工具 用途 可恢复
Explore 继承主会话(Claude API上封顶Opus) 只读工具,无Write/Edit 文件发现、代码搜索、快速探索
Plan 继承主会话 只读工具 计划模式下的前置研究
general-purpose 继承主会话 全部工具 复杂多步任务、需要改动文件
statusline-setup Sonnet ReadEdit 服务于/statusline命令,配置状态栏 -
claude-code-guide Haiku BashReadWebFetchWebSearch 回答ClaudeCode功能类问题 -
  • 当我们想委派的是"接着干活"而不只是"探索"时,选general-purpose
  • statusline-setupclaude-code-guide是窄用途Agent,各自只服务表中那一件事。

Explore和Plan则有三个刻意的设计:

  1. 只读。 两者都没有写工具,怎么调用都不会改动文件系统。
  2. 跳过CLAUDE.md与git状态。 为了快和省,它们启动时不加载CLAUDE.md层级,也不读git状态;其余所有SubAgent(内置或自定义)则两者都会加载。
  3. 一次性、不可恢复。 执行完即销毁、不返回agent ID,因此无法恢复——为什么这是设计而非缺陷,见前文「路由与两条硬约束」。

此处容易有个疑问:plan模式要产出计划,作为其中一环的Plan这个SubAgent怎能不读CLAUDE.md
因为计划不在Plan这个SubAgent里定稿。官方文档把Plan这个SubAgent定位为"计划模式下为出计划所做的代码库调研",它的产出回传给主对话;由带着完整CLAUDE.md的主对话读取,并在主对话保持只读的前提下最终向用户呈现计划。也就是说,受CLAUDE.md约束的审阅与定稿落在主对话这一层,Plan跳过CLAUDE.md并不会让规则被绕过。

定义位置

自定义的SubAgent可以放在多个位置,同名时按优先级从高到低胜出。

优先级(1最高) 位置 说明
1 Managed设置目录内的.claude/agents/ 组织统一下发
2 CLI参数--agents传入的JSON 仅当次会话有效
3 项目级.claude/agents/ 随项目提交、团队共享,支持子目录递归
4 用户级~/.claude/agents/ 个人所有项目可用
5 插件内的agents/目录 随插件分发

文件格式与字段

格式

定义文件由YAML front matter和Markdown正文组成。

  1. YAML front matter是配置。
  2. Markdown正文是这个SubAgent的系统提示。
    会话级的临时定义则是一段等价的JSON,见前文「会话级的临时debugger」

front matter字段

字段 必填 说明
name 小写字母加连字符的唯一标识,在Hook(钩子)事件中以agent_type传递
description 自动委派的路由依据
tools 工具白名单,省略则继承主对话的全部工具
disallowedTools 工具黑名单,与tools同时出现时在白名单基础上再剔除(黑名单优先)
model sonnet/opus/haiku/fable别名、完整模型ID或inherit,默认inherit
permissionMode default/acceptEdits/auto/dontAsk/bypassPermissions/plan(v2.1.200起manualdefault的别名)
maxTurns 最大轮数上限
skills 预加载的Skill列表,启动时注入完整内容,Skill机制见《3.Skill》
mcpServers 可用的MCP(Model Context Protocol,模型上下文协议)服务器,支持内联定义或按名称引用,机制见《6.MCP》
hooks 生命周期Hook配置,机制见《5.Hook》
memory user/project/local,开启跨会话的持久化记忆目录,记忆机制见《2.Memory》
background true时总是后台运行;省略时由Claude决定(v2.1.198起默认后台运行)
effort low/medium/high/xhigh/max,覆盖会话级设置
isolation 设为worktree时在临时分支中运行,无改动的分支自动清理
color UI显示颜色:red/blue/green/yellow/purple/orange/pink/cyan
initialPrompt --agent作为整会话身份运行时,自动提交的首轮提示

注意:

  • model并不总是说了算,它上面还有一条优先级链:环境变量 > 调用时参数 > front matter的model字段 > 主会话模型。若发现SubAgent用的模型不是定义里写的那个,先查环境变量CLAUDE_CODE_SUBAGENT_MODEL,它优先级最高,会盖掉定义文件里的model
  • permissionMode也不一定能生效,它可能被主会话的权限模式级联覆盖:主会话处于bypassPermissionsacceptEdits时,SubAgent的permissionMode无法覆盖它;主会话是auto时,SubAgent也随之进入auto。各权限模式的定义见《1.Start》
  • toolsdisallowedTools对MCP工具的语义不对称,拆成三件事说:
    • 白名单会连带排除MCP工具。tools: Read, Bash的含义是"只允许这两个内置工具",主会话的MCP工具会被一并排除。
    • 要在限制工具的同时保留MCP工具,得改用黑名单。如disallowedTools: Write, Edit,未列入黑名单的工具(含MCP工具)照常继承。
    • 黑名单还能按MCP维度剔除。disallowedTools: mcp__github移除该服务器的全部工具,mcp__*移除所有MCP工具;这种通配写法只在disallowedTools里有效。
  • 依赖主对话UI或会话状态的工具,写进tools也不会在SubAgent中生效:AskUserQuestionEnterPlanModeScheduleWakeup等。
  • 插件里的Agent不支持hooksmcpServerspermissionMode三个字段,写了会被忽略,详见《7.Plugin》

立即生效

ClaudeCode会监听~/.claude/agents/.claude/agents/,在磁盘上新增或修改定义文件后,几秒内就会被检测到、下一次委派即用新定义,无需重启

但是两种情况例外,仍需重启:

  1. 在会话启动时尚不存在的agents目录里创建该目录的第一个定义文件(监听只覆盖启动时已存在的目录)。
  2. --disable-slash-commands启动的会话(完全不监听)。

调用与管理

调起一个SubAgent,有六个入口:

方式 用法 说明
自动委派 正常提需求 Claude按description匹配后自动委派
显式指定 @agent-code-reviewer 看一下auth模块 @agent-<name>点名,不经过自动匹配
/agents 会话内输入/agents 管理入口,行为随版本不同,见后文「排障速查」
--agents claude --agents '<JSON>' 会话级临时定义,不持久化
--agent claude --agent code-reviewer 整个会话以该Agent身份运行,此时initialPrompt作为首轮提示自动提交
/fork /fork draft unit tests 开一个继承完整上下文的Fork分支去干活

自动委派是主路径:日常使用时不必记住每个SubAgent的名字,路由的依据见前文「路由与两条硬约束」。
显式指定和/fork是补充:前者用于"我明确知道该谁干",后者用于"我要一个带着全部背景的分身"。

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/12804X
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板