ClaudeClaude Code系列第 7 / 16 篇

Claude Code 子代理进阶:放在哪一层、每个字段管什么、前台和后台、嵌套、并发上限和 fork

子代理的定义文件不止名字、说明和工具三行。本站在 Claude Code 2.1.304 上逐项实测:同名时哪一层生效,十来个字段各管什么,哪些设置在 -p 里因为文件夹没被信任而悄悄失效,前台和后台拿到的工具有什么不同,嵌套和并发的上限,以及怎么续上一个子代理、fork 和普通子代理差在哪。

发布 · 更新 · 约 14 分钟读完 · 经人工审核()· 本站的内容怎么来的

本站已实测,测试日期:2026-10-21。

本页目录(11 节)
  1. 实测环境
  2. 一、放在哪一层,同名时谁生效
  3. 二、frontmatter 的字段
  4. 三、前台和后台
  5. 四、嵌套和并发
  6. 五、续上一个子代理,以及 fork
  7. 六、关掉某些子代理
  8. 七、怎么查一个子代理到底拿到了什么
  9. 走中转站时要注意的
  10. 这次实测没有覆盖到的
  11. 相关文章

《子代理实战》写了最基本的用法:一个 Markdown 文件,给名字、说明和工具清单,Claude Code 就能把一类活交给它。这一篇往下讲官方文档里那些没展开的部分,每一项都在服务器上实测过:

  • 同一个名字的子代理放在好几处,哪一个生效;
  • frontmatter 里十几个字段各管什么,哪些在 claude -p 里会悄悄不生效;
  • 前台和后台跑的子代理拿到的工具不一样;
  • 子代理还能再派子代理,最多几层,同时最多几个;
  • 怎么续上一个跑完的子代理,fork 和普通子代理差在哪。

实测环境

项目 内容
日期 2026-10-21
版本 Claude Code 2.1.304
系统 Ubuntu 24.04
模型 主对话 Sonnet 5.5(--model claude-sonnet-5-5),effort 是 medium
连接方式 通过酷站AI 中转站,不是直连官方
怎么跑的 一个小的 Git 仓库,项目层放了 17 个测试用的子代理,用户层放了 3 个(测完删了);全部用 claude -p 跑,一共 31 次,Claude Code 自己按官方价估算约 1 美元
怎么看结果 子代理的会话记录在 ~/.claude/projects/<项目>/<会话 ID>/subagents/ 下,每个子代理一个 agent-<ID>.jsonl。本站从里面读出它的类型、嵌套深度、前台还是后台、实际用的模型和 effort、拿到的工具清单,以及上下文里有没有某个词。读法见第七节

交互式界面里的东西(子代理面板、/tasks、Ctrl+B、/subtask)这次没有测,照文档写的地方会注明。

一、放在哪一层,同名时谁生效

子代理的定义可以放在五个地方,名字相同时按这个顺序,靠前的生效:

位置 范围 先后
托管设置(组织统一下发) 整个组织 1
启动时的 --agents 参数 这一次会话 2
项目里的 .claude/agents/ 这个项目 3
用户目录的 ~/.claude/agents/ 你的所有项目 4
插件的 agents/ 目录 装了这个插件的地方 5

本站在项目层和用户层各放一个叫 whoami 的子代理,系统提示分别让它回答“我是项目层的”“我是用户层的”,又写了一个 --agents 用的 JSON 文件:

项目层 + 用户层                       → 我是项目层的 whoami。
项目层 + 用户层 + --agents 文件        → 我是命令行的 whoami。

和文档的顺序一致。另外几点:

  • 项目层的定义可以放在子目录里。 本站那个是 .claude/agents/review/whoami.md,照样认。认的是文件里的 name,不看文件名和目录。同一个目录树里两个文件写了同一个 name,只加载其中一个,加载哪个文档说没有规定。
  • --agents 在 -p 模式下可以给一个 JSON 文件的路径,定义太长时不用全塞在命令行里(2.1.281 起)。交互式会话不接受文件路径。JSON 的写法:
{"whoami": {"description": "回答“你是谁”", "prompt": "不管收到什么任务,只回答一句:我是命令行的 whoami。", "tools": ["Read"]}}
  • 插件里的子代理不认 hooks、mcpServers、permissionMode 三个字段,文档说是出于安全考虑。要用这几个字段,把文件复制到项目层或用户层。

二、frontmatter 的字段

只有 name 和 description 必填。多个单词的字段名用驼峰写法(maxTurns、disallowedTools),拼错了 Claude Code 不报错,直接忽略。

字段 管什么 本站
tools 允许用的工具;不写就继承 测了
disallowedTools 不许用的工具 测了
model 用哪个模型:sonnet、opus、haiku、fable、完整的模型 ID,或 inherit 测了
effort 这个子代理的 effort 测了
permissionMode 权限模式 测了
skills 启动时预先装进上下文的 skill 测了
omitClaudeMd 启动时不读 CLAUDE.md 测了
memory 给它一个跨会话的记忆目录 测了
maxTurns 最多跑几轮 测了
hooks 只在这个子代理里生效的 hooks 测了,有坑
mcpServers 只给这个子代理的 MCP 服务器 测了,有坑
background 设成 true 时总在后台跑 照文档
isolation 设成 worktree 时在一个临时的 git worktree 里干活 照文档
autoCompactWindow 让它提前压缩上下文,取值 100000 到 1000000 照文档
color、initialPrompt、experimental 显示颜色;作为整个会话的代理时自动发的第一句;experimental.cacheTtl 选缓存时长 照文档

下面逐个讲测了的。

tools 和 disallowedTools:带参数的拒绝会拿掉整个工具

disallowedTools 里写 Bash(git push *),本意是“别的命令可以跑,就是不许 push”。实测这个子代理的工具清单里整个 Bash 都没了,让它跑 git status,它说自己没有能执行命令的工具。

文档也是这么写的:带参数的条目照样移除整个工具。只想拦某几条命令,应该在设置文件的 permissions.deny 里写 Bash(git push *),主对话和子代理都管得到。

两个字段同时写时,先按 disallowedTools 去掉,再在剩下的里面按 tools 挑。MCP 工具可以整台服务器一起写:mcp__github__* 放进 tools 是给它这台服务器的全部工具,mcp__github 放进 disallowedTools 是全部拿掉。服务器名本身带 __ 时(比如 reports__v2),按 10 月 22 日的文档:tools 里要写全名加 __*,即 mcp__reports__v2__*,不带结尾的 __* 一个工具也匹配不上;disallowedTools 里写给 reports 的 mcp__reports 或 mcp__reports__*,会把 reports__v2 的工具也一起拿掉(没有实测)。

model:先后顺序,以及走中转站时 haiku 用不了

子代理用哪个模型,按这个顺序定:

  1. Claude 派活时传的 model 参数;
  2. 定义里的 model(inherit 就是跟主对话一样);
  3. 环境变量 CLAUDE_CODE_SUBAGENT_MODEL;
  4. 主对话的模型。

本站的测试 key 调不了 Haiku 5.5,正好用来看清这个顺序:

设置 子代理实际请求的模型 结果
定义里写 model: haiku claude-haiku-5-5 失败:Agent terminated early due to an API error,后面跟着 404、model_not_found
同上,另设 CLAUDE_CODE_SUBAGENT_MODEL=sonnet claude-haiku-5-5 一样失败。环境变量排在定义后面,盖不过它
再加 CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1 claude-sonnet-5-5 成功

CLAUDE_CODE_SUBAGENT_MODEL_FORCE 设成 1(2.1.257 起),会忽略定义里的 model,所有子代理一律用环境变量指定的模型。走中转站、key 只开了部分模型时,这是不改别人写的子代理文件也能跑起来的办法。不过 fork 和 model: inherit 的 skill 子代理仍然跟主对话一样。

子代理失败时,主对话收到的是完整的报错,它照实转告了,没有自己代答。

/tasks 里每个子代理那一行会写它用的模型,文档说 2.1.242 起有;本站在 -p 里没法看这个界面。

effort

定义里写 effort: low,这个子代理的请求参数里 effort 就是 low,主对话还是 medium。文档说环境变量 CLAUDE_CODE_EFFORT_LEVEL 比它优先。

permissionMode:主对话的模式说了算

主对话在 -p 默认的 Manual(default)模式下:

子代理的定义 让它新建一个文件
permissionMode: acceptEdits 写成了
不写 被拒:Permission to use Write has been denied.

但这个字段不是总生效。按文档:

  • 主对话是 bypassPermissions、acceptEdits 或 auto 时,子代理一律跟主对话一样,你写的值被忽略。
  • 主对话是 default、dontAsk 或 plan 时,才用你写的值。例外是写了 bypassPermissions(还是跟主对话),以及写了 auto 但这个子代理用不了 auto。

也就是说,主对话已经放开(acceptEdits、auto、bypass)时,子代理写什么都跟主对话一样;主对话是 Manual 时,子代理才能按自己写的放宽,但放不到 bypass。

skills:预先装进去,和自己去调不一样

项目里有一个 skill,内容里写着项目代号。两个子代理都只给了 Read、都被要求不读文件:

定义 问它项目代号 上下文里有没有这个词
写了 skills: [house-style] 答对了,还说是从这个 skill 里看到的 有
没写 不知道 没有

skills 管的是“启动时预先装进去”,整篇内容都会进上下文(最多前 32 个),不只是说明。不写这个字段,子代理在干活时仍然可以自己用 Skill 工具去调项目、用户和插件里的 skill;不想让它碰 skill,把 Skill 从 tools 里去掉。

omitClaudeMd:启动时不读 CLAUDE.md

项目的 CLAUDE.md 里写了一个暗号:

定义 问它暗号 上下文里有没有 CLAUDE.md 的内容
写了 omitClaudeMd: true 不知道 没有
没写 答对了 有

两个都看得到仓库的分支和最近的提交:git 状态的快照照样会给,文档说除了内置的 Explore 和 Plan,别的子代理都会带上。

除了 CLAUDE.md,文档列了子代理启动时还会拿到什么:它自己的系统提示、Claude 写的任务说明、git 状态、预装的 skill,以及(在它能用 SendMessage、而且会话里有别的具名代理时)一份可以发消息的代理名单。拿不到的:你的对话历史、你调用过的 skill、主对话读过的文件、输出风格、主对话的自动记忆。

memory:给它一个自己的笔记本

memory: project 的子代理只给了 Read。让它“记住:发布之前要先跑 make check”:

  • 它的工具清单里自动多了 Write 和 Edit,系统提示里多了一段记忆的说明;
  • 它在 .claude/agent-memory/mem/ 下写了一个笔记文件,又在同一目录的 MEMORY.md 里加了一行索引;
  • 换一个新会话问它“发布之前要做什么”,它答出了 make check,并说这是记忆里的约定。

三种范围:user 存在 ~/.claude/agent-memory/<名字>/,project 存在项目的 .claude/agent-memory/<名字>/(可以提交到仓库,文档推荐这个),local 存在 .claude/agent-memory-local/<名字>/。关掉了自动记忆(autoMemoryEnabled 或 CLAUDE_CODE_DISABLE_AUTO_MEMORY)的话,这个字段不起作用。

maxTurns:到上限交回部分结果

maxTurns: 2 的子代理被要求一个一个读完 7 个文件再报告行数。两轮之后它停了,主对话收到的结果开头是:

NOTE: this agent stopped at its 2-turn limit before finishing. The text below is PARTIAL output; treat it as incomplete. Send the agent a message (SendMessage) to let it continue from where it stopped.

主对话照实说了它没做完,并问要不要用 SendMessage 让它接着做。

hooks 和 mcpServers:在 -p 里,项目层的会悄悄失效

这是这次测出来最容易踩的坑。

子代理放在 frontmatter 里的 PreToolUse hook frontmatter 里自带的 MCP 服务器
项目层 .claude/agents/ 没有触发 没有加载,工具清单里没有它的工具
用户层 ~/.claude/agents/ 触发了 加载了

两个都没有报错,子代理照常跑,只是那部分配置不生效。原因是文档里的信任规则:项目层子代理的 frontmatter hooks 和自带的 MCP 服务器,要你信任过这个文件夹(交互式启动时那个信任对话框)才会加载;父目录信任过不算,-p 会话也不算。用户层的和用 --agents 传进来的不受这条限制(自带的 MCP 服务器,托管设置里的也不受)。

所以要在 CI 或脚本里用带 hooks、带 MCP 的子代理,定义放在用户层或者用 --agents 传,别依赖项目层。

用户层那个带 MCP 的子代理还碰到第二道关:工具在了,但调用时被拒,提示还没授予权限。-p 默认模式下 MCP 工具要批准,加上 --allowedTools mcp__history__get_release_notes 之后才查到了结果。

自带的 MCP 服务器只在子代理启动时连上、结束时断开,主对话里没有它的工具,也就不占主对话的上下文。写成服务器名字(比如 - github)则是复用主对话里已经配好的那一台。

三、前台和后台

同一个不限工具的子代理,跑两次:

方式 拿到的工具
前台(主对话等它做完) 10 个:Agent、Bash、Edit、ListAgents、Read、ReportFindings、Skill、ToolSearch、Write,加一个延迟加载工具的占位项
后台(run_in_background) 8 个:少了 ListAgents 和 ReportFindings

文档说后台子代理只保留一张内置工具的白名单(读写、搜索、命令、网页、任务清单、Skill、SendMessage 等),不在名单上的一律去掉,而且不报错。同一个定义,前台和后台拿到的工具可能不一样,写子代理时如果依赖某个工具,要留意它在不在后台的名单里。

在哪里跑,各个入口不一样:

  • claude -p 和 Agent SDK:由 Claude 自己判断要不要等。本站这 31 次里,大多数它选了前台,有几次放到后台、跑完再回答,你要求“等它做完”它就会等。
  • 交互式终端、VS Code、桌面应用:子代理默认在后台。想让 Claude 等某个子代理的结果,要先关掉 fork 模式(CLAUDE_CODE_FORK_SUBAGENT=0),见下面 fork 一节。等的时候按 Ctrl+B 可以把它放到后台。这部分本站没有测。

后台子代理遇到要批准的操作,会把确认框弹到主对话里,并写明是哪个子代理在问。你选了“这次会话都允许”,这个许可对主对话也生效。

四、嵌套和并发

子代理也能派子代理。 本站写了一个会“看看自己有没有 Agent 工具,有就再派一个自己”的子代理:

设置 结果
默认 派到第 3 层,第 3 层的工具里没有 Agent,到此为止
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=2 到第 2 层为止

默认是主对话下面三层,设成 1 就是不许嵌套。到了上限,Claude Code 直接把 Agent 工具从子代理手里拿走,它只能自己干完、交回一份总结。不想让某个子代理再往下派,把 Agent 从它的 tools 里去掉就行。这个默认值改过几次(2.1.172 到 2.1.216 是五层,2.1.217 到 2.1.218 是一层),版本旧的话以自己的为准。

同时跑的子代理有上限。 默认 20 个。本站把上限设成 2:

export CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS=2

再让它一次在后台派 4 个:前两个正常,后两个当场返回

Concurrent subagent limit reached. You can run 2 subagents at once. Do not retry. If the user wants more concurrent subagents, ask them to increase CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS.

主对话照报错的要求没有重试,汇总时说明了有两个没派出去。这个上限只管同时在跑的,一个会话一共派多少个不限。

五、续上一个子代理,以及 fork

续上

每次派子代理都是一个新的实例。想接着它刚才的活干,让 Claude 续上它:子代理跑完时 Claude 会拿到它的 ID,之后用 SendMessage 发给这个 ID,子代理就带着完整的历史接着跑。

本站先在一个会话里让 lister 列出 src,再用 claude -p --resume <会话 ID> 说“让刚才那个 lister 接着列出 .claude/agents,续上它,不要新开”。主对话调了 SendMessage,返回 Resuming agent,还是原来那个 ID,它列完了第二个目录。

几个注意的地方,都出自文档:内置的 Explore 和 Plan 是一次性的,没有 ID,续不了;子代理的会话记录和主对话分开存,主对话压缩不影响它们,默认 30 天后清理。

fork

fork 是一种特殊的子代理:它继承到这一刻为止的整个对话,系统提示、工具、模型都和主对话一样,只是它中间的工具调用不进你的主对话,最后只交回结果。

本站在提示里先说了一个数字,再让 Claude 分别派一个 fork 和一个 general-purpose 子代理去回答“用户的幸运数字是多少”,并要求任务说明里不写这个数字:

子代理 回答 上下文里有没有这个数字
fork(主对话调 Agent 时 subagent_type 是 fork) 答对了,说是从对话上下文里读到的 有
general-purpose 不知道,还翻了几个文件也没找到 没有

文档里 fork 和普通子代理的区别:

fork 普通子代理
上下文 整个对话历史 从任务说明开始
系统提示和工具 和主对话一样 来自它的定义,后台跑时还要再筛一遍
模型 和主对话一样 来自 model 字段
提示缓存 和主对话共用,所以同样的活 fork 更便宜 单独一份

fork 模式从 2.1.302 起默认打开,Claude 可以自己决定开 fork。不想要:只禁 fork,在 permissions.deny 里加 Agent(fork);整个关掉,设 CLAUDE_CODE_FORK_SUBAGENT=0。交互式会话里还可以自己用 /subtask 任务 开一个 fork,本站没有测。

六、关掉某些子代理

想做的 怎么设
不许用某个子代理 permissions.deny 里写 Agent(Explore)、Agent(你的子代理名),或者启动时加 --disallowedTools "Agent(Explore)"
只去掉内置的 Explore 和 Plan 环境变量 CLAUDE_CODE_DISABLE_EXPLORE_PLAN_AGENTS=1
-p 和 Agent SDK 里去掉全部内置子代理,只用自己的 环境变量 CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1
一个也不让派 在 permissions.deny 里拒绝 Agent 工具本身

另外,内置的 Explore 在主对话用 Fable、并且是通过 ANTHROPIC_BASE_URL 接网关时,用的是 opus 别名指向的模型。走中转站的读者,如果这把 key 没开 Opus,可以照文档在项目里定义一个同名的 Explore,写上自己能用的 model。这一条本站没有测。

七、怎么查一个子代理到底拿到了什么

文档给了会话记录的位置:~/.claude/projects/<项目>/<会话 ID>/subagents/agent-<ID>.jsonl。本站这次的结论都是从这些文件里读出来的。下面这几样是本站在 2.1.304 上观察到的格式,文档没有写,以后的版本可能会变:

  • 同一目录下的 agent-<ID>.meta.json 里有子代理的类型(agentType)、嵌套深度(spawnDepth)、前台还是后台(requestShape)。
  • .jsonl 里 type 为 api-request-shape 的那一行,shape.tools 是它拿到的工具清单,shape.system 是它的系统提示。
  • type 为 api-request 的行,params 里有实际请求的模型和 effort。

查“为什么这个子代理没有某个工具”“它到底用的哪个模型”,看这几行最直接。这些文件里有你的代码和对话内容,发给别人之前先看一遍。

走中转站时要注意的

  • 子代理请求的模型这把 key 没开,子代理直接失败,主对话收到 404。用 CLAUDE_CODE_SUBAGENT_MODEL 加 CLAUDE_CODE_SUBAGENT_MODEL_FORCE(设成 1)统一换成能用的模型。
  • 子代理换成和主对话不同的模型时,本站以前在这家中转站上碰到过主对话报 400 的问题,对方已经修好,经过见《子代理实战》。这次子代理用 Sonnet、主对话也用 Sonnet,没有碰到。

这次实测没有覆盖到的

  • 交互式界面都没测:子代理面板、/tasks、Ctrl+B、/subtask、后台子代理把确认框弹到主对话。
  • isolation: worktree、background、autoCompactWindow、initialPrompt、experimental.cacheTtl 没有测,照文档写。文档特别提醒 autoCompactWindow 要写成 200000 这样的整数,写 200k 会被读成 200,超出范围后被忽略。
  • --agent 把整个会话变成某个子代理没有测,见《子代理实战》里的简介。
  • 拼错字段名的检查要用 claude --debug 看日志,本站的测试环境不开调试日志(会带出请求的细节),没有测。
  • 插件和托管设置里的子代理没有测。
  • 每一项只跑了一两次。嵌套、并发、权限这类是硬规则,结果稳定;Claude 选前台还是后台这类是模型的判断,换个说法可能不一样。

相关文章

来源

通过 API 中转站使用这些模型?到 Veridrop 免费检测中转站给你的是不是它声称的模型,或者看看中转站排行榜前 100 名。

相关文章

  • Claude Code 的 agent view:一屏管住多个后台会话,以及让会话之间互相传话教程 · Claude Code · 约 14 分钟

    手上有几件互不相干的事要交给 Claude Code,不想开一排终端盯着。agent view 把它们变成一张表:谁在干活、谁在等你回答、谁做完了。本站在服务器上实测了派发、回答提问、进入对话、删除会话,以及让一个会话去问另一个会话。通过中转站都能用。

  • Claude Code 代理团队实战:让三个队友互相质疑着审同一段代码教程 · Claude Code · 约 13 分钟

    代理团队是一项实验功能:一个会话当组长,开出几个各有独立上下文的队友,队友之间能直接互发消息。本站实测了一个三人的评审团队:三个队友互发了 20 条消息,推翻了组长清单里的一条错误结论。也记下了协调上的毛病:有人没等别人的发现就交了报告,行号要组长复核。整个会话估算 1.51 美元。

  • Claude Code 子代理实战:写一个只读的测试评审员,让写的和查的分开教程 · Claude Code · 约 14 分钟

    子代理是一份 Markdown 文件:给它一个名字、一段说明、一份工具清单,Claude Code 就能把一类任务交给它在独立的上下文里做。本站实测了一个只读的测试评审员,它查出了上一篇里补的测试的真问题。还试了让子代理换用便宜的模型,并记下通过中转站时遇到过的一条报错。

  • CLAUDE.md、skill、hook、子代理、MCP、插件:Claude Code 的六种扩展各管什么,先用哪个教程 · Claude Code · 约 12 分钟

    Claude Code 的扩展方式有六七种,名字相近,容易用错地方。按官方的选型说明整理:每一种解决什么问题、什么信号出现时该加哪一种、相近的几种怎么区分、各占多少上下文。占用的数字用的是本站这个系列实测到的。