Claude Code subagent 什么时候该用:同一个 Java 任务实测子代理比主对话贵 2 倍、慢一倍(2.1.299)

0 阅读13分钟

导读:Anthropic 10 月 22 日发了一篇《Building agents: When to fold subagents into your main agent loop》,意思是很多团队的子代理拆多了,应该并回主循环。掘金上相关解读的标题已经是"别学 subagent 了,很可能要凉了"。说法归说法,我想看到数字,就在 Claude Code 2.1.299 上拿同一个 Java 项目跑了 11 次 claude -p,对比"主对话自己干"和"派子代理去干"的花费、耗时和结果质量,再整理出什么时候子代理确实值得用、怎么配置和禁用。


一、先说清楚:subagent 是什么

subagent(子代理) 就是主对话里的 Claude 再派出去的一个"临时工":它有自己独立的上下文窗口,拿着主对话写给它的任务说明去读文件、搜代码,干完只把一段总结交回来,过程中读过的文件内容不会进主对话。打个比方,主对话是你,子代理是你派去档案室翻资料的同事,他翻了一下午,回来只给你一页纸。

Claude Code 内置了几个子代理,最常见的是 Explore(只读,专门搜代码),你也可以在 .claude/agents/ 下自己定义。主对话会根据任务自己决定要不要派,你也可以在提示词里点名让它派。

不管它会怎样:你可能根本没意识到 Claude 派过子代理。它的好处是主对话的上下文干净;代价是每派一次,子代理都要重新读一遍系统提示词和文件,多一轮模型调用,多一段等待,交回来的总结还可能漏掉细节。

什么时候值得动它:如果你在用订阅时经常撞用量上限、用 API Key 按 token 付费、或者在 CI 里批量跑 claude -p,值得看看子代理在你的场景里是省了还是费了。下面的数字可以当参照。


二、Anthropic 那篇文章说了什么

原文作者是 Diane Sarkis,内容来自 Anthropic 应用 AI 团队(Applied AI)在企业部署里看到的情况。核心观点两句话:

  1. 很多团队按"一个业务一个子代理、上面再放一个调度器"来搭 Agent,结构像公司组织架构图。这样做信息每转一手就丢一些,延迟和 token 成本也跟着涨。有一个客户把多代理方案和单个主循环对比,单循环的运行时间大约快了一倍
  2. 现在有了"渐进式披露"(Skills 和工具先只给模型看简短描述,用到时才加载完整内容),以前因为上下文装不下才拆子代理的理由,大部分已经不成立了。原文提到 Claude Code 针对更强模型的系统提示词缩减了 80% 以上,其中就包括这部分

原文没有说子代理没用,它列了三种仍然适合用子代理的情况:

原文给的情况我的理解
单个循环因为上下文膨胀,出现了可测量的效果下降主对话已经被大量原始材料撑满,开始答非所问
多个任务需要用全新的上下文窗口并行处理几件互不相关的事可以同时干,干完就扔
单个 Agent 选不对工具,或系统提示词里有互相冲突的指令某个角色需要一套完全不同的工具和规则

迁移建议是先建一个小的端到端评估集(原文说 10 个标准样例就够起步),然后一次只并一个子代理,比较并之前和并之后的结果,每个任务多跑几次。

这篇文章讲的是企业用 Claude API 自己搭 Agent 的架构,Claude Code 里的子代理是同一套思路的具体实现。所以我想在 Claude Code 里验证的是:一个普通的后端开发任务,派子代理到底贵多少、慢多少、结果有没有变差。


三、实测:同一个 Java 项目跑 11 次

3.1 环境和做法

项设置
Claude Code2.1.299,claude.ai 订阅登录
模型主对话 --model sonnet --effort medium(Sonnet 5.5)
项目生成的 Java 项目,30 个 *Service 类,共 6254 行;其中 7 个类把 SimpleDateFormat 声明成 static 字段(线程不安全),4 个类用局部变量,19 个类用 DateTimeFormatter
隔离--setting-sources project --strict-mcp-config,不加载我本机的用户级设置、插件和 MCP,项目目录里没有 CLAUDE.md
主对话组--disallowedTools Agent,提示词末尾加"直接用 Read、Grep、Glob 自己完成"
子代理组提示词末尾加"必须先用 Agent 工具派一个 Explore 子代理完成文件搜索和阅读,你自己不要直接调用 Read、Grep、Glob"
数据来源--output-format json 里的 total_cost_usd、modelUsage、subagent_stats;耗时用 /usr/bin/time -p 量墙钟时间

⚠️ total_cost_usd 是 Claude Code 按标价在本地算的。我是订阅登录,这个数字和实际账单无关,只能拿来比较"哪种做法用的 token 多"。每组只跑了一到两次,数字有波动,看倍数关系,别看绝对值。

主对话组的运行命令如下,子代理组只是换了提示词末尾那句、去掉 --disallowedTools Agent:

# 在项目目录下执行;p1.txt 是任务描述
claude -p "$(cat p1.txt)
直接用 Read、Grep、Glob 自己完成。" \
  --model sonnet --effort medium \
  --setting-sources project --strict-mcp-config \
  --permission-mode acceptEdits --allowedTools Read Glob Grep Edit Agent \
  --disallowedTools Agent --output-format json > t1_main.json

三个任务:

  • 任务 1(窄):找出所有把 SimpleDateFormat 声明为 static 字段的类,说明问题和改法
  • 任务 2(宽):逐个检查 30 个类,统计 public 方法数,标出每个类格式化时间用的哪种方式,出一张表
  • 任务 3(两步):先做任务 2,然后在同一个会话里(--resume)接着把 7 个类改成 DateTimeFormatter

3.2 结果

第一轮四组是成对并行跑的,缓存是冷的;之后我按顺序把任务 1 重跑了一轮,缓存是热的,同时加测了 Haiku 子代理和禁用子代理两种配置。

任务做法花费(美元)墙钟耗时结果
任务 1,第一轮主对话自己搜0.13714.7 秒7 个类全对
任务 1,第一轮派 Explore 子代理0.27536.9 秒7 个类全对
任务 1,热缓存重跑主对话自己搜0.03311.7 秒全对
任务 1,热缓存重跑派 Explore 子代理0.09727.1 秒全对
任务 1派自定义 Haiku 子代理0.05631.9 秒全对
任务 1项目设置里禁用 Agent0.04014.5 秒全对
任务 2主对话自己查0.07320.1 秒表格全对,431 个方法
任务 2派 Explore 子代理0.15953.5 秒表格全对,但自己说"没有逐个读完"
任务 3 第一步主对话0.07019.2 秒同任务 2
任务 3 第一步派 Explore 子代理0.36571.9 秒同任务 2
任务 3 第二步(改文件)主对话会话接着改0.192—7 个文件改对
任务 3 第二步(改文件)子代理会话接着改0.165—7 个文件改对,两边 diff 完全一致

任务 3 第二步是 --resume 接着上一步的会话跑的,JSON 里的 total_cost_usd 和 modelUsage 是整个会话累计的(我核对过:主对话组第二步的缓存写入 25109 = 第一步 9252 + 本次 15857),表里填的是减掉第一步之后的增量。这一步我没量墙钟时间。

3.3 从数字里看出的四件事

1. 窄任务派子代理,花费翻 2~3 倍,耗时翻一倍多。 任务 1 本质上是一次 Grep,主对话 4 轮就答完了。派子代理时,子代理要重新加载一遍自己的系统提示词、再搜一遍,主对话还要写任务说明、读回总结。两轮测试里子代理组的花费分别是主对话的 2.0 倍和 2.9 倍,耗时是 2.5 倍和 2.3 倍。

2. 子代理交回来的是"有损总结"。 任务 2 两组的表格数字都对,但子代理组的最终回答里写着:"子代理没有逐行读完全部 30 个文件,只精读了 OrderService 和 AuditService,其余是用 Grep 列出……",后面还说那 19 个用 DateTimeFormatter 的类,它没有逐个打开确认是否真的用到了格式化器,问我要不要再派一次子代理去查。主对话组自己 Grep,统计方式和局限说得很清楚。这就是 Anthropic 原文说的信息每转一手丢一些:主对话只能看到子代理愿意写进总结的东西。

3. 子代理的好处确实存在,但这次没赚回来。 任务 3 第二步,子代理会话的主上下文里只有一段总结,没有 30 个文件的搜索结果,接着改文件时花费比主对话会话少了约 14%(0.165 对 0.192)。可第一步多花的 0.29 美元远远没补回来。上下文隔离要在"读了大量材料、后面还要在同一个会话里干很多轮"时才划算。

4. 子代理换成 Haiku 能省钱,省不了时间。 自定义的 date-scanner 子代理设了 model: haiku,子代理本身只花了 0.002 美元,但主对话那边的 Sonnet 花了 0.054,加起来还是比主对话自己搜(0.033)贵,耗时 31.9 秒也最长。派活、等待、读总结这些固定开销,换便宜模型省不掉。

💡 一个实测细节:claude -p 里子代理组的 duration_ms 只有 7~12 秒、num_turns 都是 1,远小于墙钟的 27~72 秒,subagent_stats 里显示子代理是 started_in_background: 1。在脚本里统计耗时,别只看 duration_ms,用 time 量墙钟时间才准。


四、什么时候子代理值得用

结合原文的三种情况和上面的数字,我的建议是:

场景用不用子代理原因
查一个类、搜一个关键字、改一两个文件不用一次 Grep 能解决的事,派出去只多花钱、多等待
读几十个日志文件、扫一个很大的陌生仓库,只要结论用原始内容很大,后面又用不上,放进主对话会把上下文撑满
同一个会话后面还要干很多轮倾向用主上下文越干净,后面每轮越便宜(任务 3 第二步省了 14%)
几件互不相关的事要同时做用原文的第二种情况,各自全新上下文
需要一个只读的审查角色、不想让它有写权限用自定义子代理用 tools 字段限死工具,比在主对话里靠提示词约束可靠
想靠子代理"分工"提高质量先别这次实测里质量没提高,总结反而带来了"没读完"的不确定性

一句话:子代理是用来隔离上下文的,不是用来分工的。按业务拆一堆子代理,正是 Anthropic 那篇文章劝大家别做的事。


五、怎么配置:自定义子代理、换模型、禁用

5.1 自定义一个只读、用 Haiku 的子代理

在项目的 .claude/agents/ 下建一个 Markdown 文件,frontmatter 写名字、描述、工具和模型,正文是给子代理的指令。下面就是我实测用的那个:

---
name: date-scanner
description: 在 Java 项目里搜索日期格式化相关写法(SimpleDateFormat、DateTimeFormatter),只读,返回类名和行号清单。
tools: Read, Grep, Glob
model: haiku
---
你是只读的代码搜索助手。用 Grep 和 Glob 定位,必要时用 Read 看上下文。
只返回结论清单:类名、文件路径、行号、写法(static 字段 / 局部变量),不要贴大段代码。

实测在提示词里点名"用 date-scanner 子代理",subagent_stats.by_type 里出现 date-scanner: 1,modelUsage 里多了一项 claude-haiku-5-5,说明定义生效、模型也切过去了。

几点注意(据官方文档):

  • description 决定主对话什么时候会主动派它,写清楚"什么任务找它"
  • tools 不写就继承全部工具,只读角色一定要写
  • model 可以写 sonnet、opus、haiku、fable、完整模型 ID 或 inherit
  • 放 ~/.claude/agents/ 是个人全局可用,放项目 .claude/agents/ 可以提交到 git 给团队共用

5.2 让内置的 Explore 也跑 Haiku

内置 Explore 默认用主对话的模型。我这次主对话是 Sonnet,modelUsage 里也只有 Sonnet 一项。官方文档的做法是在用户级或项目级定义一个同样叫 Explore 的子代理,它会覆盖内置的那个,用你写的 model:

---
name: Explore
description: 只读的代码搜索与探索,用于定位文件、搜索代码、理解代码结构。
tools: Read, Grep, Glob
model: haiku
---
只读探索代码库,返回结论和文件路径、行号,不贴大段代码。

这一条我没单独实测,按上面 3.3 第 4 点的数字,换 Haiku 能降子代理本身的花费,但派活的固定开销还在。

5.3 团队里禁用子代理

不想让 Claude 派子代理,在项目 .claude/settings.json 里把 Agent 工具禁掉:

{
  "permissions": {
    "deny": ["Agent"]
  }
}

实测加了这条后,我在提示词里明确要求"必须派 Explore 子代理",Claude 回答"我没法派 Explore 子代理,因为这里没有 Agent 工具,所以是直接用 Grep 搜的",subagent_stats.spawned 为 0,花费 0.040 美元。

只想在某次 claude -p 里禁用,用命令行参数:

# 只对这一次运行禁用子代理
claude -p "找出所有 static SimpleDateFormat 字段" --disallowedTools Agent

⚠️ 全局禁掉要想清楚:大仓库里的探索类任务、plan 模式里的调研(用的是内置 Plan 子代理)都会受影响。我更倾向于不全局禁,而是在 CLAUDE.md 里写一句"简单搜索直接用 Grep,不要派子代理",这一条的效果我没做对照测试。


六、速查表

想做的事做法
看一次运行有没有派子代理claude -p ... --output-format json,看 subagent_stats.spawned 和 by_type
看子代理花了多少子代理用了不同模型时看 modelUsage 里各模型的 costUSD;同模型时 usage 只算主对话,modelUsage 是合计
统计真实耗时用 time 量墙钟,别只看 duration_ms
自定义子代理.claude/agents/<name>.md,frontmatter 写 name、description、tools、model
子代理换便宜模型frontmatter 写 model: haiku
覆盖内置 Explore定义一个 name: Explore 的子代理
项目里禁用子代理.claude/settings.json 里 permissions.deny: ["Agent"]
单次运行禁用--disallowedTools Agent

七、常见问题

Q:Anthropic 说要并回主循环,Claude Code 的子代理是不是要被砍了?

原文讲的是企业用 API 搭 Agent 时别按业务拆子代理,没有说要去掉 Claude Code 的子代理功能,还专门列了三种适合用子代理的情况。"别学了"这个说法过头了,该学的是什么时候不用。

Q:我没让它派,它自己派了,怎么办?

主对话会根据任务自己判断要不要派 Explore。先用 --output-format json 看看你常用的任务里它派了多少次;派得太频繁,再考虑在 CLAUDE.md 里写规则或者禁用 Agent。

Q:交互模式里的 /agents 命令怎么不是向导了?

据官方文档,2.1.197 及更早版本的 /agents 会打开一个交互向导,之后的版本改成提示你直接让 Claude 写,或者手动编辑 .claude/agents/ 下的文件。我在 2.1.299 交互界面里没截图验证。

Q:子代理是串行还是并行跑的?

这次每次只派了一个子代理,subagent_stats 显示它是在后台启动的。一次派多个时是否真并行,我没测,不下结论。


参考链接


你用 --output-format json 跑一下自己最常做的任务,看看 subagent_stats.spawned 是几?你们团队是放开让它派子代理,还是在设置里禁掉了?评论区说说,我挑典型的补进 FAQ。

觉得有用的话点个赞、收个藏,下次调 Claude Code 花费时翻得到。