Clipping 微信公众号

Claude Code v2.1.172- 子代理可以嵌套了,最多 5 层

by 扶苏 原文 ↗
Created: 2026-06-15

公众号名称:奇点先锋

作者名称:扶苏

发布时间:2026-06-15 08:00

2026 年 6 月 10 日,Claude Code v2.1.172 悄悄放出了一个大招:子代理可以嵌套了,最多 5 层。这条一行字的 changelog,意味着你以前写的 Agent 编排模板可能要全部重写。但别急着兴奋——嵌套带来的是 Token 成本的指数级膨胀一套全新的配置陷阱,以及一个几乎没人告诉你的”静默忽略”行为。

太长不看:

  1. 嵌套 ≠ 无限递归:硬上限 5 层,但真正有用的深度是 2-3 层(根 Opus → 中 Sonnet → 叶 Haiku)

  2. Token 成本按分支独立计算:官方数据显示 Agent 团队约 7 倍消耗,嵌套同理——不设 modelmaxTurns 就是给账单开绿灯

  3. 最大陷阱:子代理文件里写的 Agent(name1, name2) 白名单在嵌套场景下会被静默忽略,只有 claude --agent 主线程模式才生效


一、嵌套子代理能做什么、不能做什么

先上一张速查表,帮你建立直觉:

✅ 可以做的❌ 不能做的
从子代理内部再产生子代理,最深 5 层省略 toolsmodelmaxTurns 的配置——默认会继承父级所有东西
每一层路由到不同模型(opussonnethaiku在嵌套子代理之间共享上下文——每个都在独立的 Context Window 里运行
从子代理的 tools 列表中移除 Agent,阻止进一步嵌套在子代理定义里用 Agent(name1, name2) 当白名单——括号里的内容会被忽略,除非该 Agent 以 claude --agent 主线程身份运行
定义仅在该子代理生命周期内有效的内联 mcpServershooks在任何子代理里使用 EnterPlanModeAskUserQuestionScheduleWakeupWaitForMcpServers(这些工具需要主线程状态)
settings.json 中设置 permissions.deny: ["Agent(name)"] 全局阻止特定子代理类型从第 5 层的后台子代理中再产生子代理——该层级的 Agent 工具会被设计性地移除

30 秒心智模型:嵌套子代理就是”带着 5 帧栈限制的递归”,每一帧有自己的 System Prompt 和 Model。父级只读叶节点的摘要,中间过程全部消耗 Token 然后消失。


二、决策框架:什么时候该嵌套、什么时候别碰

✅ 该嵌套的场景

  1. 工作是树状结构,不是列表——比如一个顶层 Reviewer 需要向三个专业子 Reviewer(安全、性能、代码风格)分别提问,每个专家有自己的 MCP Server 或 Skill 预加载

  2. 工作本身就是递归形态——比如探索一个 Monorepo,每个 packages/* 目录需要各自独立的探索范围和规范,叶节点的摘要向上汇总

  3. 你在第 1 层就撞上了 Context Window 压力——单层子代理返回 30K Token 的报告,把主对话淹没了。再往下压一层,让每个叶节点先压缩再上传

❌ 不该嵌套的场景

  1. 工作是顺序的——五个互相依赖的步骤应该放在一个 maxTurns: 20 的子代理里,而不是五层嵌套。每一层都意味着一次不可逆的上下文分裂和一次 System Prompt 写入

  2. 你需要并行 Worker 互相通信——那是 Agent Teams 的事,不是嵌套。嵌套子代理只能上下通信,不能横向对话

  3. 叶节点只需要调一个工具——这时候你在为一个 Read 或 Grep 调用付一整个子代理的 System Prompt 成本。直接从父级调工具就好

🛑 停止规则

在嵌套之前,把期望的叶节点摘要写在一张便签上。如果它不到 ~500 Token,或者父级多调两次工具就能自己产出——别嵌套。 嵌套的价值在于:叶节点做了有分量的工作,并返回了一个有分量的压缩结果。


三、系统要求

⚠️ 如果你还在 v2.1.171 或更低版本,用 npm i -g @anthropic-ai/claude-code 升级。旧版本会静默忽略嵌套调用,把内部的 Agent 工具调用当成普通 Read 处理。


四、手把手搭建一个三层嵌套子代理链

下面的示例构建一个三级链:顶层的 triage-lead(分诊主管)分类一个 Bug 报告,委托给 repro-runner(复现运行器)确认可复现性,后者再委托给 log-summariser(日志摘要器),将原始容器日志压缩为 200 行摘要。

第 1 步:定义叶节点

创建 .claude/agents/log-summariser.md

```markdown
---
name: log-summariser
description: 读取原始容器日志,返回结构化的错误、时序和可疑模式摘要。
tools: Read, Grep
model: haiku
maxTurns: 8
---

你是一个日志摘要器。给定一个日志文件路径,返回:
1. 频率最高的前 5 个不同错误签名
2. 观测到的最早和最晚时间戳
3. 任何 panic、OOM 或 segfault 条目及行号

严格返回 Markdown 格式——不要前缀废话。
```

预期结果:如果你通过 /agents 创建,文件会立即被识别;如果是直接编辑磁盘文件,下次会话启动时生效。

第 2 步:定义中间层

创建 .claude/agents/repro-runner.md

```markdown
---
name: repro-runner
description: 根据描述复现 Bug,捕获日志,返回故障是否为确定性。
tools: Agent, Read, Bash
model: sonnet
maxTurns: 12
---

你负责复现 Bug。运行失败的命令,将日志捕获到 /tmp/repro.log,
然后调用 log-summariser 子代理压缩输出后再返回。
报告格式:是否确定性?是/否,加上摘要。
```

这里的 tools 中包含 Agent 就是关键的新变化。在 v2.1.172 之前,Agent 工具在子代理上下文中被完全禁用;列出来也没效果。从 v2.1.172 起,列出 Agent 才允许子代理产生嵌套子代理。

⚠️ 重要提醒Agent(specific-name) 这种带括号的写法在子代理定义里不是有效的白名单——官方文档明确说括号中的类型列表在此上下文中”会被忽略”。要真正限制能产生哪些子代理,要么从叶节点文件中移除 Agent(让它们根本无法嵌套),要么在 .claude/settings.json 中使用 permissions.deny: ["Agent(name)"] 全局阻止特定类型。

第 3 步:定义根节点

创建 .claude/agents/triage-lead.md

```markdown
---
name: triage-lead
description: 端到端分诊传入的 Bug 报告——分类、复现、日志审查。
tools: Agent(repro-runner), Read, Grep, Bash
model: opus
maxTurns: 15
---

对 Bug 进行分类(P0/P1/P2),将复现委托给 repro-runner,然后
返回一份分诊备忘录,包含严重程度、复现状态和摘要后的日志。
```

这里的 tools: Agent(repro-runner) 白名单只有在 triage-lead 作为主线程运行时才生效(见第 4 步)。当 triage-lead 从普通会话中作为常规子代理被调用时,括号中的列表会被忽略,该代理可以产生任何已注册的子代理类型。

调用链:主会话 → triage-lead(Opus)→ repro-runner(Sonnet)→ log-summariser(Haiku)。三层。

第 4 步:从主会话调用

有两种调用模式,行为不同:

```bash
# 模式 A — triage-lead 作为子代理运行(其文件中的白名单会被忽略)
> 使用 triage-lead 代理处理位于 ./bugs/2026-06-11-checkout-500.md 的 Bug 报告

# 模式 B — triage-lead 作为主线程运行(其 Agent(...) 白名单会被强制执行)
claude --agent triage-lead
> 分诊 ./bugs/2026-06-11-checkout-500.md
```

当你需要 triage-lead 上的 Agent(repro-runner) 白名单真正生效时,用模式 B。模式 A 适合你信任子代理能合理委托、且想保持当前会话打开的场景。

第 5 步:验证嵌套确实发生了

运行完成后,检查 /agents(Running 标签页),或在对话记录中滚动查找三个不同的子代理面板。如果你只看到一个,说明父级把 spawn 当成了普通工具调用——几乎肯定是版本不对。重新运行 claude --version 确认是 2.1.172+。

/agents → Running 标签页
> triage-lead         (+2)   opus     ✓ 已完成
> repro-runner        (+1)   sonnet   ✓ 已完成
> log-summariser             haiku    ✓ 已完成

每行旁边的 (+N) 是后代计数。三行且叶节点没有后代——这就是”成功”的最低标准。5 层上限是栈限制,不是目标——大多数有用的链在 2-3 层深度。


五、常见错误及修复方案

症状可能原因快速修复
子代理的可用工具中没有 Agent版本低于 v2.1.172,或父级的 tools 字段中遗漏了 Agent升级 Claude Code,然后在父级的 tools 中添加 Agent(子代理定义中不需要括号)
子代理产生了子节点,但子节点没有父级的 MCP ServerMCP Server 不会通过嵌套继承——每个子代理根据自己的 frontmatter 重新连接在子节点的 mcpServers 字段中添加服务器名,或提升到用户级 settings.json
子代理文件中的 Agent(name) 白名单不生效文档明确说明:括号在任何子代理定义中都会被忽略;白名单仅在 Agent 以 claude --agent 主线程运行时生效对嵌套调用,将限制移到 .claude/settings.jsonpermissions.deny: ["Agent(name)"],或从子代理的 tools 中移除 Agent 以完全阻止嵌套
permissionMode/ hooks / mcpServers 从子代理中被静默移除Agent 文件是从插件的 agents/ 目录加载的;插件安全策略会移除这些字段将 Agent 文件复制到 .claude/agents/~/.claude/agents/ 中,而不是留在插件目录
嵌套子代理到达第 6 层后无明确错误地停止达到了 5 层递归上限;第 6 层调用在某些对话记录中会静默失败重构:要么拍平一层,要么将工作拆分为两次顶层调用
父级报告”已完成”但子代理仍显示”活跃”状态v2.1.172 修复了最常见变体;仍可能在后台子代理中出现更新到 v2.1.172+,或通过 /agents Running 标签页 kill 子代理后重跑
Token 账单暴涨 7-12 倍但行为无明显变化每个嵌套层都要写一次 System Prompt;默认 Model 继承主会话的(很多用户是 Opus)设置 CLAUDE_CODE_SUBAGENT_MODEL=haiku 强制所有未覆盖的子代理使用 Haiku,然后按需逐层提升

六、Token 数学:为什么嵌套成本按分支累加

这是决定嵌套技术成败的关键计算。单线程的 Claude Code 会话在所有轮次间复用一个缓存的 System Prompt。子代理在自己的 Context Window 中运行,有自己的 System Prompt(比主会话的小,但不是免费的)。嵌套之后,每个深度都要付同样的开销

深度写入内容可复用性每次调用的近似 Token 开销
0(主会话)主 System Prompt + 工具列表 + CLAUDE.md✅ 是(跨轮次缓存)基准值
1(子代理)子代理 System Prompt + 工具列表 + 任务描述部分——工具列表在分支内缓存基准值的 +30-60%
2(嵌套)内部子代理 Prompt + 工具列表 + 父级摘要的任务描述更低——每个子节点写入全新的前缀在深度 1 基础上 +30-60%
3-5(深层嵌套)同样的结构,累积叠加几乎为零——每个叶节点的前缀都是短命的累积数倍于单线程

官方成本管理指南提到 Agent 团队在 Plan 模式下约 7 倍的 Token 乘数——那是互相通信的独立 Claude Code 实例。嵌套子代理不是同一个原语,但每当树形展开时趋势类似,因为每个分支都维护自己的 Context Window 并写入新的子代理 Prompt 前缀。

🔧 两个对抗成本的杠杆

  1. 按深度分层模型——叶节点几乎不需要 Opus。一个典型的分诊链配置:根用 Opus,第 1 层用 Sonnet,第 2 层起用 Haiku。Opus 和 Haiku 之间的输入 Token 价格差大约一个数量级,叶节点层级的节省会在每次嵌套调用中复利累积

  2. 每层设置 maxTurns——不设的话,叶节点可能空转 30+ 轮试图”更有帮助”。叶节点上限 8 轮,中间层 12 轮;大多数时候你不会触到上限,触到的时候通常意味着任务定义有问题


七、3 大烧钱反模式

🔥 反模式 1:通用递归导致的失控嵌套

最省力的做法是给每个子代理都配上 tools: Agent,然后让模型自己决定该产生谁。几个小时后你会看到这样的对话记录:分诊代理产生了一个”通用研究员”,它又产生了一个”调查专家”,它又产生了一个”日志阅读器”,它又产生了一个……通用研究员。每层都在加 Token。5 层的深度上限能救你脱离无限循环,但救不了你的钱包。

修复方案

由于 Agent(name1, name2) 括号白名单在子代理定义中被静默忽略,你无法在文件级别控制嵌套。两个真正有效的机制:

  1. 从叶节点文件中移除 Agent——叶节点的 tools: Read, Grep(没有 Agent)物理上无法再嵌套。将此作为默认做法,只在真正需要委托的中间层文件上重新加回 Agent

  2. 全局阻止特定子节点——在 .claude/settings.json 中设置 permissions.deny: ["Agent(general-purpose)"]。这会在项目中所有子代理上强制执行限制,不管各自的 frontmatter 怎么写

🔥 反模式 2:到处都用 Opus

让每个子代理默认使用主会话的模型——这是默认行为,也是多层树形结构中最错误的做法。根节点用 Opus 是合理的(你在做综合判断)。但在一个只是读日志文件、数错误频率的叶节点上用 Opus——那是用顶级价格为 Haiku 就能搞定的工作买单。

修复方案

在 Shell 层面设置 CLAUDE_CODE_SUBAGENT_MODEL=haiku,让所有未显式覆盖的子代理默认用 Haiku。然后只提升真正需要更强能力的——通常只有根节点和第 1 层。

🔥 反模式 3:把子代理文件中的 Agent(name) 当成真正的白名单

项目子代理文件被提交到版本控制中,你很自然地想写 tools: Agent(repro-runner, log-summariser), Read, Grep, Bash 来”锁定”该子代理能产生哪些子节点。

但文档明确说这不管用:在子代理定义中,“括号内的任何类型列表都会被忽略”。文件读起来像是在做限制,但运行时该子代理仍然可以产生任何已注册类型。白名单只在 Agent 以 claude --agent 主线程运行时才强制执行。

修复方案

真正的项目级限制应该写在 .claude/settings.json 中:

```json
{
"permissions": {
"deny": [
"Agent(general-purpose)",    // 阻止产生通用代理
"Agent(migration-runner)"    // 阻止产生迁移运行器
]
}
}
```

这个 denylist 会被统一执行。在 Agent 文件本身中,要么把 Agenttools 行中移除(不嵌套),要么不带括号地列出它(允许嵌套,由全局设置管辖)。任何对 permissions.deny 的修改都应视为安全审查,就像你对待 permissions.allow 一样。


八、团队 / 多开发者配置规范

对于在多个仓库中运行共享 Claude Code 配置的团队,以下三个约定能让嵌套子代理保持低成本、可预测、可审查:

约定设置位置为什么重要
在共享 Shell rc 中设置 CLAUDE_CODE_SUBAGENT_MODEL=haiku每个开发者的 dotfiles默认消除”到处用 Opus”的泄漏;需要 Sonnet/Opus 的团队按文件覆盖
叶节点子代理提交到 .claude/agents/tools 中不含 Agent每个仓库物理上阻止叶节点产生进一步子代理;代码审查能发现任何对叶节点文件添加 Agent 的行为
对子代理密集型项目设置 permissions.deny: ["Agent(general-purpose)"].claude/settings.json强制专业化委托;在唯一对嵌套调用有效的层面阻止递归反模式

💡 对于在多个仓库间标准化的组织,还建议用一个 dotfiles 管理的 ~/.claude/agents/ 目录存放跨项目通用的 Agent(review、security、doc-summariser)。用户级定义仅在项目未重新声明同名时生效,所以约定是:共享的放用户作用域,仓库特定的放项目作用域


九、进阶:通过网关按层级路由到不同模型

CLAUDE_CODE_SUBAGENT_MODEL 环境变量适用于每个没有设置自己 model 的子代理。当指向一个 Anthropic 兼容网关时,这一个变量就能把你的整个叶节点层从一个模型别名切换到另一个,而不用动任何子代理文件。

```bash
# 设置 Anthropic 兼容网关的基础 URL
export ANTHROPIC_BASE_URL=https://api.ofox.ai/anthropic

# 设置你的 API Key
export ANTHROPIC_API_KEY=

# 所有未显式覆盖 model 的子代理默认使用 Haiku
export CLAUDE_CODE_SUBAGENT_MODEL=haiku

claude
```

任何深度下没有显式覆盖 model 字段的子代理,现在都会通过网关路由到 Haiku。根代理和任何显式设置 model: opusmodel: sonnet 的子代理仍然路由到对应层级——CLAUDE_CODE_SUBAGENT_MODEL 是默认值,不是强制覆盖。

对于超长的嵌套运行,5 分钟的默认 Prompt Cache TTL 会成为下一个瓶颈。设置 ENABLE_PROMPT_CACHING_1H=1 可以将其延长到一小时(缓存写入成本 2 倍)——当单条分诊链超过 15 分钟墙钟时间时,通常值得开。


内容效果不满意?点此反馈

输入关键词开始搜索