Claude Code subagent:上下文隔离到什么程度、fork 和普通 subagent 差在哪、三种限额各管什么
用 subagent 的判断标准是一句话:这个side任务会不会用一堆你之后不会再看的搜索结果、日志、文件内容淹没主对话。会,就交给 subagent——它在自己的上下文里干活,只把摘要返回。
但隔离是双向的。subagent 看不到你的对话历史、看不到你已经调用的 skill、看不到 Claude 已经读过的文件。所以它需要的背景信息必须在委派时说清楚。
fork 是这条规则的例外,也是它存在的理由:fork 继承整个对话,并且因为系统提示和工具定义与父会话完全一致,它的第一次请求能复用父会话的 prompt 缓存——所以对于需要相同上下文的任务,fork 比新起一个 subagent 更便宜。
内置 subagent
Section titled “内置 subagent”| agent | 模型 | 工具 | 用途 |
|---|---|---|---|
| Explore | 继承主对话(Claude API 上封顶 Opus) | 只读,Write 和 Edit 被拒 | 文件发现、代码搜索、代码库探索 |
| Plan | 继承主对话 | 只读 | plan 模式下的代码库调研 |
| general-purpose | 继承主对话 | subagent 可用的全部工具 | 需要探索加修改的复杂多步任务 |
Explore 和 Plan 跳过你的 CLAUDE.md 文件和父会话的 git status,其他所有内置和自定义 subagent 都加载这两样。
这个设计的理由是「让调研又快又便宜」:探索阶段不需要知道你的代码规范,加载它们只是浪费 token 和时间。
推论很实际:主对话是带着完整 CLAUDE.md 上下文读 Explore 和 Plan 的结果的,所以大部分规则不需要传到 subagent 里面。但如果某条规则必须到位,比如「忽略 vendor/ 目录」,就要在委派时的提示词里重述一遍。
Explore 的模型行为在 v2.1.198 变过:以前总是跑 Haiku,现在继承主对话的模型。Claude API 上继承值封顶 Opus——主对话在更高档时 Explore 跑 Opus,主对话在 Sonnet 或 Haiku 时跑同一个。想让探索固定在低成本模型上,定义一个自己的 Explore(用户或项目层)覆盖内置的,并写 model: haiku。
关掉内置 Explore 和 Plan:CLAUDE_CODE_DISABLE_EXPLORE_PLAN_AGENTS=1,之后 Claude 直接读文件而不委派。
定义文件与优先级
Section titled “定义文件与优先级”| 位置 | 范围 | 优先级 |
|---|---|---|
managed 设置里的 .claude/agents/ | 组织全体 | 1(最高) |
--agents CLI 标志 | 当前会话 | 2 |
.claude/agents/ | 当前项目 | 3 |
~/.claude/agents/ | 你的所有项目 | 4 |
插件的 agents/ 目录 | 插件启用处 | 5(最低) |
项目 subagent 从当前工作目录往上走查找,直到仓库根,每一级 .claude/agents/ 都会被扫。多个嵌套目录定义了同名 subagent 时,用离工作目录最近的那个定义。
.claude/agents/ 和 ~/.claude/agents/ 是递归扫描的,可以用 agents/review/、agents/research/ 这样的子文件夹组织。子目录路径不影响 subagent 的标识和调用方式——身份只来自 name frontmatter 字段。
插件的 agents/ 目录不同:子文件夹会成为作用域标识符的一部分。插件 my-plugin 里的 agents/review/security.md 注册为 my-plugin:review:security。
name 里不能有 :,那是插件作用域标识符的保留字符。v2.1.218 之前这种名字是被接受的。
frontmatter 全字段
Section titled “frontmatter 全字段”只有 name 和 description 是必填的。
| 字段 | 说明 |
|---|---|
name | 小写字母和连字符组成的唯一标识。hook 收到的 agent_type 就是这个值。文件名不必匹配 |
description | Claude 何时该委派给这个 subagent |
tools | 可用工具。省略时继承 subagent 可用的全部工具 |
disallowedTools | 要拒绝的工具,从继承或指定的列表里移除 |
model | sonnet / opus / haiku / fable / 完整 model ID / inherit。默认 inherit |
permissionMode | default / acceptEdits / auto / dontAsk / bypassPermissions / plan |
maxTurns | 停止前的最大 agentic 轮数 |
skills | 启动时预加载进上下文的 skill。注入的是完整内容,不只是描述 |
mcpServers | 这个 subagent 可用的 MCP 服务器。可以是已配置服务器的名字,也可以是内联定义 |
hooks | 限定在这个 subagent 生命周期内的 hook |
memory | 持久记忆作用域:user / project / local |
background | true 时总在后台运行。未设时 Claude 决定,v2.1.198 起默认后台 |
effort | 这个 subagent 活跃时的 effort level |
isolation | 设 worktree 在临时 git worktree 里运行 |
color | 任务列表和 transcript 里的显示颜色 |
initialPrompt | 作为主会话 agent 运行时自动提交的第一轮用户输入 |
插件 subagent 不支持 hooks、mcpServers、permissionMode 三个字段,出于安全原因加载时被忽略。需要它们就把 agent 文件复制到 .claude/agents/ 或 ~/.claude/agents/。
工具集会被过滤两次
Section titled “工具集会被过滤两次”这一节是 subagent 行为里最容易造成困惑的地方:同一份定义在前台和后台可能解析出不同的工具集。
subagent 继承主对话的内置工具和 MCP 工具,然后经过两道过滤。
第一道移除这些工具,即使你在 tools 里列了也一样:
Agent(在深度上限时)AskUserQuestionEndConversationEnterPlanModeExitPlanMode(除非permissionMode是plan)ScheduleWakeupTaskOutputWaitForMcpServersWorkflow
第二道只对后台运行的 subagent 生效(这是默认)。后台 subagent 保留全部 MCP 工具,但内置工具只剩:Read、Grep、Glob、Bash、PowerShell、Edit、Write、NotebookEdit、WebFetch、WebSearch、TodoWrite、Skill、ToolSearch、EnterWorktree、ExitWorktree、Monitor、TaskStop、SendMessage、Artifact。
其他内置工具全被移除,无论是继承的还是在 tools 里列的。移除不报错,除非最后 tools 列表解析结果为空。
fork 跳过这两道过滤,拿到主对话的完整工具池。
限制工具用 tools 做白名单或 disallowedTools 做黑名单:
---name: safe-researcherdescription: Research agent with restricted capabilitiestools: Read, Grep, Glob, Bash------name: no-writesdescription: Inherits the available tools except file writesdisallowedTools: Write, Edit---两个都设时:先应用 disallowedTools,再在剩余池里解析 tools。两边都列的工具被移除。
两个字段都接受 MCP 服务器级模式:mcp__<server> 或 mcp__<server>__* 授予或移除该服务器的全部工具。disallowedTools 里的 mcp__* 移除所有服务器的全部 MCP 工具。
tools 里没有任何条目解析成工具时(比如全拼错了),Claude Code 通常拒绝启动这个 subagent,Agent 工具返回一个点名未解析条目的错误。v2.1.208 之前那个 subagent 会带着零工具启动,返回空的或令人困惑的结果。
把 MCP 服务器限定给 subagent
Section titled “把 MCP 服务器限定给 subagent”mcpServers 字段有一个很实用的用法:让一个 MCP 服务器完全不进主对话,避免它的工具描述在那里消耗上下文。
---name: browser-testerdescription: Tests features in a real browser using PlaywrightmcpServers: # 内联定义:只对这个 subagent 可见 - playwright: type: stdio command: npx args: ["-y", "@playwright/mcp@latest"] # 按名引用:复用已配置的服务器 - github---
Use the Playwright tools to navigate, screenshot, and interact with pages.内联定义的服务器在 subagent 启动时连接、结束时断开。字符串引用共享父会话的连接。
subagent 拿到工具,父对话拿不到。这比在 .mcp.json 里定义再想办法屏蔽要干净。
主会话适用的 MCP 限制同样覆盖 subagent frontmatter 里声明的服务器:--strict-mcp-config、--bare、企业 managed MCP 配置、allowedMcpServers 和 deniedMcpServers 策略。
权限模式的继承规则
Section titled “权限模式的继承规则”subagent 继承主对话的权限上下文,可以覆盖模式,但有两个例外:
- 父会话用
bypassPermissions或acceptEdits时,这个优先,无法被覆盖 - 父会话用 auto mode 时,subagent 继承 auto mode,frontmatter 里的
permissionMode被忽略——分类器用与父会话相同的规则评估 subagent 的工具调用
这个设计防的是「通过 subagent 提权」:如果 subagent 能声明比父会话更宽的权限模式,那么权限模式这个机制就形同虚设了。
预加载 skill 与「反向」用法
Section titled “预加载 skill 与「反向」用法”---name: api-developerdescription: Implement API endpoints following team conventionsskills: - api-conventions - error-handling-patterns---
Implement API endpoints. Follow the conventions and patterns from the preloaded skills.注入的是每个 skill 的完整内容,不只是描述。
这个字段控制的是「预加载哪些」,不是「能访问哪些」:不设它,subagent 仍然能在执行期间通过 Skill 工具发现并调用项目、用户、插件 skill。要彻底禁止,从 tools 里省掉 Skill 或加到 disallowedTools。
设了 disable-model-invocation: true 的 skill 不能被预加载,因为预加载取自「Claude 能调用的 skill」这同一个集合。内置的 /verify 和 /code-review 也在其中。
这和「在 subagent 里跑 skill」是互为反向的两种用法:
| 系统提示来自 | 任务是 | |
|---|---|---|
subagent 配 skills 字段 | subagent 的 markdown 正文 | Claude 的委派消息 |
skill 配 context: fork | 你指定的 agent 类型 | SKILL.md 内容 |
底层是同一套系统,区别在谁提供系统提示、谁提供任务。
memory 字段给 subagent 一个跨对话存续的目录:
---name: code-reviewerdescription: Reviews code for quality and best practicesmemory: user---
You are a code reviewer. As you review code, update your agent memory withpatterns, conventions, and recurring issues you discover.| 作用域 | 位置 | 何时用 |
|---|---|---|
user | ~/.claude/agent-memory/<name>/ | 跨所有项目记住 |
project | .claude/agent-memory/<name>/ | 项目特定且可通过版本控制共享 |
local | .claude/agent-memory-local/<name>/ | 项目特定但不进版本控制 |
project 是推荐的默认值,因为它让 subagent 的知识可以通过版本控制共享。
subagent 记忆是自动记忆的一部分:关掉自动记忆(autoMemoryEnabled 或 CLAUDE_CODE_DISABLE_AUTO_MEMORY),memory 字段就无效了。
启用时:
- 系统提示包含读写记忆目录的指令
- 系统提示还包含记忆目录里
MEMORY.md的前 200 行或 25KB(先到者为准) Read、Write、Edit工具自动启用,让 subagent 能管理自己的记忆文件
把记忆指令直接写进 subagent 的 markdown 正文,让它主动维护自己的知识库:
Update your agent memory as you discover codepaths, patterns, librarylocations, and key architectural decisions. Write concise notes aboutwhat you found and where.启动时到底加载了什么
Section titled “启动时到底加载了什么”非 fork subagent 的初始上下文包含:
- 系统提示:agent 自己的提示加上 Claude Code 附加的环境细节,不是完整的 Claude Code 系统提示
- 任务消息:Claude 交接工作时写的委派提示
- CLAUDE.md 文件:主对话加载的每一层,含
~/.claude/CLAUDE.md、项目规则、CLAUDE.local.md、managed 策略文件。Explore 和 Plan 跳过 - git status:父会话开始时的快照。不在 git 仓库里或
includeGitInstructions为 false 时没有。Explore 和 Plan 无论如何都跳过 - 预加载的 skill:
skills字段里点名的 skill 的完整内容 - 兄弟名册:列出
main和会话里其他所有具名 agent 的系统提醒,每个都是SendMessage的合法to值
有几样主对话状态永远不到非 fork subagent:
- 输出样式:subagent 跑自己的系统提示,你的输出样式不影响它的回复
- 自动记忆:主对话的自动记忆不加载。要给 subagent 自己的持久记忆用
memory字段 - 上下文窗口大小:subagent 的窗口由它自己的模型决定,不是父会话的。委派给窗口更小的模型,那个 subagent 就只有更小的窗口
最后一条容易忽略:给一个需要读大量内容的任务指定 model: haiku 省钱,可能因为窗口不够而失败。
三种限额,各管一件事
Section titled “三种限额,各管一件事”这三个是独立的,各有自己的环境变量。混起来会误判问题在哪。
| 限额 | 默认 | 变量 | 管什么 |
|---|---|---|---|
| 会话总量 | 200 | CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION | 一个会话里累计能起多少个 |
| 并发 | 20 | CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS | 同时运行多少个 |
| 嵌套深度 | 3 | CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH | subagent 能往下嵌几层 |
会话总量:已完成的 subagent 仍然计数。达到上限时 Agent 工具报 Subagent spawn limit reached,错误信息告诉 Claude 用自己的工具完成剩余工作。/clear 重置计数。
并发:达到上限时报 Concurrent subagent limit reached,错误告诉 Claude 不要重试。运行数降下来之后又能起。恢复一个已完成的 subagent 会拿一个新槽位且不检查限额,所以恢复操作可以把运行数推过上限。
嵌套深度:默认 3 层。到达深度上限时,Claude Code 对除 fork 以外的每个 subagent 收回 Agent 工具,所以在上限的 subagent 自己干完委派的活并返回一份摘要。设 1 关掉嵌套。
嵌套的历史默认值变过好几次:v2.1.172 到 v2.1.216 默认可嵌套最多 5 层且不可改;v2.1.217 到 v2.1.218 默认是 1;v2.1.219 提到 3。看老资料时注意这一点。
fork:继承一切的那种 subagent
Section titled “fork:继承一切的那种 subagent”/subtask draft unit tests for the parser changes so farfork 继承到目前为止的整个对话,而不是从零开始。它放弃了 subagent 本来提供的输入隔离:fork 看到与主会话相同的系统提示、工具、模型、消息历史,所以你可以把一个 side 任务交给它而不必重新解释情况。
fork 自己的工具调用仍然留在主对话之外,只有最终结果回来,所以主上下文窗口保持干净。
| fork | 具名 subagent | |
|---|---|---|
| 上下文 | 完整对话历史 | 全新,带你传的提示词 |
| 系统提示和工具 | 与主会话相同 | 来自定义文件,后台运行时被过滤 |
| 模型 | 与主会话相同 | 来自 model 字段 |
| prompt 缓存 | 与主会话共享 | 独立缓存 |
共享 prompt 缓存这一条是 fork 的成本优势来源:系统提示和工具定义与父会话完全一致,所以第一次请求复用父会话的缓存。
什么时候用 fork:具名 subagent 需要太多背景才能有用时,或者你想从同一个起点并行试几种方案时。
fork 出现在提示输入框下面的面板里,后台运行。控制键:
| 键 | 动作 |
|---|---|
| ↑ / ↓ | 在行之间移动 |
| Enter | 打开选中 fork 的 transcript 并给它发后续消息 |
| x | 移除已完成的 fork 或停止运行中的 |
| Esc | 焦点回到提示输入框 |
打开某个 fork 的 transcript 时,后续消息和 skill 发给那个 agent,但内置命令仍在主对话里跑。v2.1.199 起,在那个视图里敲 /model 或 /fast 会提示它改的是主对话而不是所看的 agent。
fork 不能再 fork。
命令名变过:v2.1.212 起是 /subtask;v2.1.161 到 v2.1.211 是 /fork。现在的 /fork 在 agent view 开启时是把整个会话复制成一个新的后台会话,语义不同了。
显式调用的三档
Section titled “显式调用的三档”自动委派不够用时,三种方式,从一次性建议升级到会话级默认:
自然语言:点名 subagent,Claude 决定是否委派。
Use the test-runner subagent to fix failing tests@-mention:保证那个 subagent 为这一个任务运行。
@"code-reviewer (agent)" look at the auth changes注意:你的完整消息仍然发给 Claude,由 Claude 根据你的要求写 subagent 的任务提示。@-mention 控制的是调用哪个 subagent,不是它收到什么提示词。
会话级:整个会话用那个 subagent 的系统提示、工具限制和模型。
claude --agent code-reviewer这时 subagent 的系统提示完全替换默认的 Claude Code 系统提示,和 --system-prompt 一样。CLAUDE.md 和项目记忆仍然通过正常消息流加载。
要让它成为项目里每个会话的默认值,在 .claude/settings.json 里设 agent:
{ "agent": "code-reviewer" }CLI 标志优先于设置。
输出扫描:一层容易忽略的防御
Section titled “输出扫描:一层容易忽略的防御”Claude Code 在 Claude 读到之前扫描每个 subagent 的最终报告。
理由是:subagent 可能读了你从未审查的文件、网页或命令输出,那些来源的文本可以携带针对主对话的指令。
扫描从不删除或改写内容,只做两种可见的改动:
- 插入反斜杠:模仿 Claude Code 自己输出的文本(如
<system-reminder>标签,或以Human:/Assistant:开头的行)会被插入反斜杠,让模仿读作普通文本而不被当成对话的一部分 - 标记行:报告模仿
<system-reminder>这类标签、或提到bypassPermissions、--dangerously-skip-permissions这类权限设置时,前面加一行[harness: subagent output matched instruction-shaped pattern(s):
扫描不判断内容是否恶意,也不改变报告里的指令能做什么:报告引导 Claude 做出的工具调用仍然经过会话的权限检查和沙箱。它不是「限制 subagent 能碰什么」的替代品。
需要 v2.1.210 或更高版本。
什么时候不该用 subagent
Section titled “什么时候不该用 subagent”用主对话,当:
- 任务需要频繁来回或迭代打磨
- 多个阶段共享大量上下文(规划、实现、测试)
- 你在做一个快速的定点改动
- 延迟要紧。subagent 从零开始,可能需要时间收集上下文
用 subagent,当:
- 任务产生你不需要留在主上下文里的冗长输出
- 你想强制特定的工具限制或权限
- 工作自成一体,能返回一份摘要
想要「可复用的提示词或流程,但在主对话上下文里跑」,用 skill 而不是 subagent。
对话里已有内容的快速提问,用 /btw:它看得到你的完整上下文但没有工具访问,答案会被丢弃而不加入历史。
恢复 subagent
Section titled “恢复 subagent”每次调用创建一个新实例,上下文全新。要接着已有 subagent 的工作而不是重来,让 Claude 恢复它。恢复的 subagent 保留完整对话历史,包括之前所有工具调用、结果和推理。
内置 Explore 和 Plan 是一次性的,不返回 agent ID,因此不能恢复。需要接着做时用 general-purpose 或自定义 subagent。
transcript 存在 ~/.claude/projects/{project}/{sessionId}/subagents/,每个文件是 agent-{agentId}.jsonl。
subagent transcript 独立于主对话存续:
- 主对话压缩时 subagent transcript 不受影响,它们在单独的文件里
- transcript 在会话内存续,重启 Claude Code 后恢复同一个会话即可恢复 subagent
- 超过
cleanupPeriodDays(默认 30 天)后被自动删除
v2.1.198 起,subagent 把来自启动它的 agent 的消息当作正常任务指导,包括任务中途的纠偏。两条限制始终成立:没有任何 agent 消息算作你对待处理权限提示的批准;没有任何 agent 消息能改变 subagent 的权限设置、CLAUDE.md 或配置。
一个实用模式:hook 做条件校验
Section titled “一个实用模式:hook 做条件校验”tools 字段的粒度是「工具级」,有时不够——比如想允许 Bash 但只允许只读 SQL。这时用 PreToolUse hook:
---name: db-readerdescription: Execute read-only database queriestools: Bashhooks: PreToolUse: - matcher: "Bash" hooks: - type: command command: "./scripts/validate-readonly-query.sh"---#!/bin/bashINPUT=$(cat)COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -iE '\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\b' > /dev/null; then echo "Blocked: Only SELECT queries are allowed" >&2 exit 2fi
exit 0macOS 和 Linux 上记得 chmod +x,否则 hook 是失败而不是拦截——这个区别很关键,失败的 hook 拦不住任何东西。
系统提示里也告诉 subagent 拒绝写请求,hook 作为兜底。两层都要有:系统提示让它主动配合,hook 保证它做不到。
要让项目层 subagent 的 frontmatter hook 运行,需要接受该文件夹的 workspace 信任对话框。~/.claude/agents/ 里的用户层 subagent 和 --agents 传入的定义不需要这一步。v2.1.218 之前 frontmatter hook 可以从未信任的文件夹运行。
- 让一个 subagent 跑测试套件,只报告失败的测试和错误信息,对比直接在主对话里跑测试后
/context的差异。这是 subagent 上下文价值最直观的一次测量。 - 定义一个自己的
Explore覆盖内置的,写model: haiku,看探索质量和成本的变化。 - 用
/subtask起一个 fork,同时在主会话继续工作,体会「共享缓存」在响应速度上的差别。
还没确认的点
Section titled “还没确认的点”initialPrompt字段的完整行为。文档说它作为主会话 agent 运行时自动提交为第一轮,命令和 skill 会被处理,但与用户提供的提示词如何拼接的细节没有核实。isolation: worktree的清理条件。文档说 subagent 没做改动时 worktree 会被自动清理,「有改动」的判定标准未见说明。- 兄弟名册(sibling roster)的完整触发条件。确认了它需要
SendMessage在工具列表里且至少一个其他 agent 有名字,但名册内容的更新时机只知道是启动时快照。 - 并发限额下「恢复已完成 subagent 不检查限额」是否会导致实际运行数无上限。文档陈述了这个行为,没有说明是否有其他兜底。