首页
/ Caveman subagent-tax 实战解析:用本地捕获 Sink 测量编码 Agent 每次调用的前缀开销

Caveman subagent-tax 实战解析:用本地捕获 Sink 测量编码 Agent 每次调用的前缀开销

2026-09-06 17:09:52作者:韦蓉瑛

当 Agent 派生一个 subagent 时,它会把 harness 的完整前缀——系统提示词加上所有工具 schema——原封不动地重发给模型,然后才开始工作。Caveman 仓库中的 subagent-tax 工具(位于 packages/subagent-tax)专门回答这个问题:你的编码 harness 每次调用到底发送多大的前缀? 它在本地起一个伪装成 LLM 供应商端点的 HTTP sink,让每个已安装的 harness 向它发送一条真实请求,然后报告这条请求里装了什么。本文完整介绍其测量方法、各 harness 的注入配方、诚实性设计(token 估算口径、脱敏规则)与全部命令行参数,并结合 lib/sink.mjslib/analyze.mjs 等源码逐层展开实现细节。

一、"Subagent Tax" 是什么

每一次 subagent 调用都会重发前缀:系统提示词 + 全部工具 schema。工具捕获的就是"第一个 agent 回合请求"的大小,并按 harness 逐行报告。核心承诺是:不做任何供应商 API 调用、不需要账号、数据不出本机(唯一例外是显式选入的 --count-tokens,且运行时会明确告知)。

README 中给出的一台真实机器的输出(2026-08-07,你的机器会不同——这正是该工具的意义所在):

harness       status        wire                    tools  mcp  system  schemas  body  input tokens  variant
------------  ------------  ----------------------  -----  ---  ------  -------  ----  ------------  -------------------------------
claude        ok            anthropic-messages      91     63   42k     219k     267k  ~43k (est)    real config
opencode      ok            openai-responses        10     -    68k     20k      87k   ~14k (est)    isolated config
codex         ok            openai-responses        11     -    40k     10k      52k   ~8.3k (est)   minimal home (floor)
gemini        ok            gemini-generatecontent  8      -    30k     8.3k     39k   ~6.3k (est)   isolated home (api-key mode)
pi            ok            anthropic-messages      4      -    23k     2.8k     26k   ~4.1k (est)   isolated home (4 default tools)
cursor-agent  unmeasurable  -                       -      -    -       -        -     -             -

variant 列说明每行度量的是什么,不同 variant 的行属于不同的度量对象,不能直接横比。例如上面那台机器的 claude 行实际说的是:267k 字符的请求体里有 219k 是工具 schema,91 个工具有 63 个来自 MCP 服务器/插件——约占 schema 权重的 69%,每次调用估算约 24k token,而这发生在 Agent 做任何事之前。单个 Workflow schema 就有 20.8k 字符,一个 Notion MCP 工具 17.1k。

没有说的是"claude 比 pi 大 10 倍":claude 行是一个人装了 63 个 MCP 工具的真实环境,pi 行是只有 4 个内置工具的地板值。两者对比量的是插件装载量,不是 harness 本身。

仓库中还保存了一份脱敏后的完整报告样例 fixtures/example-report.json,可以用来和一次新运行的 report.json 做形状对比。其中 claude 行的关键数字:body_bytes: 273958system_chars: 43038tools_chars: 224655mcp_tools_count: 63 / mcp_tools_chars: 155480,opencode 行则展示了一个典型的多捕获场景(先有一个 2555 字符、0 工具的标题生成调用,主请求是 seq=2 的 89501 字符请求)。

二、测量流水线:四步完成一次捕获

METHOD.md 完整记录了方法。从源码看,整条流水线在 run.mjs 中组织,分四步:

1. 本地 sink 伪装供应商端点

lit/sink.mjslib/sink.mjs)绑定一个 loopback 端口,跨四种线协议伪装端点:anthropic-messages、openai-responses、openai-chat、gemini-generatecontent,JSON 与 SSE 都支持。协议分类按 URL 路径形状实现(见 classifyRequest):

if (/\/count_tokens$/.test(path)) return "anthropic-count-tokens";
if (/\/messages$/.test(path)) return "anthropic-messages";
if (/\/chat\/completions$/.test(path)) return "openai-chat";
if (/\/responses$/.test(path)) return "openai-responses";
if (/:streamG|g)enerateContent$/.test(path)) return "gemini-generatecontent";

sink 对每个请求回复一个最小的合法 DONE 完成(stop_reason: end_turn / finish_reason: stop / Gemini STOP,流式则发送完整的 SSE 事件序列,见 buildResponse),让 harness 认为回合结束而不是重试。每个带 body 的请求都会被记录成一条 capture:seq、时间戳、方法、脱敏后的 URL 与 headers、原始 body_bytes 与脱敏后的 body,按 NNN-协议slug.json 命名写入捕获目录。

两个值得注意的工程细节:

  • 写盘即脱敏REDACT_HEADERSL26-L55)覆盖凭据类 header(authorization、x-api-key、cookie…)以及账号/设备/会话标识(x-claude-code-session-idx-codex-turn-metadatax-session-id…),替换为 redacted:sha256:<12>,保留可判等性;body 侧则按 BODY_PATTERNS 高精度匹配邮箱与凭据形状字符串(sk-*ghp_*AKIA*xox*AIza*)。
  • 代理隔离:spawn 前子进程环境会清空所有继承的 HTTP 代理变量并把 loopback 加入 NO_PROXYrun.mjs L200-L205),确保公司代理收不到前缀——这是该工具"数据不出本机"承诺的落点。

2. 按配方启动每个 harness(一次性)

配方注册表在 lib/harnesses.mjs,统一提示词是 Reply with exactly: DONEPROMPT)。各 harness 的注入方式与 variant 语义:

harness 注入方式 variant 为什么
claude claude -p PROMPT + ANTHROPIC_BASE_URL 指向 sink real config 用户的真实插件/MCP 就是被测的税;默认跑真实配置
codex 临时 CODEX_HOME + 最小 config.tomlwire_api = "responses" minimal home (floor) 真实配置会触发后台 memory-agent 调用与副作用网络 I/O(插件 git clone、MCP OAuth),零接触工具不能默认触发
gemini 隔离 HOME + api-key 模式,-m 固定非 gemini-3 模型 isolated home (api-key mode) 保护用户的 OAuth 登录;固定模型是为了绕开会对纯文本 sink 回复无限重试的 strict-JSON 预检分类器
opencode OPENCODE_CONFIG_CONTENT 内联 sink provider + XDG 三目录隔离 isolated config 真实配置可能收窄模型目录导致运行失败(已观察到),XDG 隔离同时避免读取用户 auth.json
pi 临时 PI_CODING_AGENT_DIR + 最小 models.json isolated home (4 default tools) 瘦 delegate 基线,默认工具集是天然对照点
cursor-agent launch: null 结构性不可测(见第六节)

注册表里的字段有严格的语义约束(见 harnesses.mjs 头部注释):touchesRealConfig 标记故意跑真实配置的配方(今天只有 claude),confirmed: true 表示配方已对真实安装的 harness 端到端验证过,mcpPattern 仅在命名约定被确认时才设置(只有 Claude Code 的 mcp__server__tool 确认过,见 CLAUDE_MCP_PATTERN)。

3. 静默宽限期后整树停止

首个捕获落地(再加一段 --grace 静默期)后,harness 的整个进程树被停止:POSIX 用 detached 进程组,Windows 用 taskkill /t(实现见 lib/process-tree.mjs)。"捕获成功"即测量成功——harness 完成回合不是必需的,永不退出的 harness 照样被度量。run.mjs 还注册了 SIGINT/SIGTERM/SIGHUP 清理钩子(L145-L152),Ctrl-C 不会留下孤儿进程树。

4. 分析器切分前缀

lib/analyze.mjs 把主请求拆成:system 字符数、逐工具 schema 字符数、消息开销,以及(命名约定已确认时)MCP 与内置工具的拆分。所有数字都是捕获请求的 JSON 序列化字符数——不推测。

主请求选择规则是方法的关键(pickPrimary):选携带工具 schema 最多的捕获,平局取最早的。因为 harness 会在真实 agent 回合之间夹杂小型 warmup、标题生成、路由调用,其中有些带一两个工具——若选"第一个带任何工具的请求",会把路由调用那几百字节当返回为前缀。没有任何捕获带工具时退回最大 body,且该行的 pick_rule 会如实标注。解析失败(tools/messages/input 形状异常)的捕获被记入 skipped_captures 并保留错误信息,跳过而不致命——一条坏捕获不会抹掉其他 harness 的测量。

三、诚实性设计:basis 永远是 inferred

README 的 "Honesty by construction" 一节(packages/subagent-tax/README.md)和 lib/report.mjs 中的 HONESTY_LINES 把这条纪律写进了每次运行的输出:

  • 口径永远是 inferred:这是前缀大小,不是账单、不是支出、不是节省。带热缓存时前缀重读是打折的(honesty block 原文:Anthropic 约 0.1x,OpenAI/Gemini 更高、约 0.25–0.5x),完整尺寸只在冷缓存或缓存失效时按全额计费。因此工具里不出现任何节省数字
  • token 数分两档,绝不混用lib/tokens.mjs):
    • est:字符数 ÷ 比率,默认 6.4 chars/token(DEFAULT_CHARS_PER_TOKEN),保留 2 位有效数字并加 ~。该比率于 2026-08-07 对 provider-exact 的 Claude Code 前缀校准(实测 5.9–6.9 chars/token,±8% 带宽),所以再多一位小数只是噪声。注意校准本身是 Anthropic 分词器来源、却应用于所有协议,codex/opencode 与 gemini 行还叠加一层未量化的跨分词器误差——跨 harness 的 token 对比是近似值,字符列才是精确的
    • exact:仅 anthropic 协议行可选,通过 --count-tokens 把捕获 body 原样 POST 到 Anthropic 免费 count_tokens 端点(countTokensAnthropic)。需要 ANTHROPIC_API_KEY;没有 key 时警告并维持估算,失败则标注 est (count_tokens failed) 而不是静默降级。
  • mcp 列的 - 表示"未知",不是零:只有 Claude Code 的 MCP 命名约定被确认过。
  • 词汇纪律verified 一词在该仓库是节省核算的保留词,本工具的输出里刻意不用;经过核查的配方叫 confirmed
  • claude 行的副作用会先披露:默认跑真实配置意味着启动你的 MCP 服务器、运行你的 hooks,并在 ~/.claude/projects 留下一份会话转录。run.mjs L369-L379 在启动任何东西之前打印这段说明,--isolate 可退出。

四、完整命令行参考

入口是 node run.mjs(package.json 同时把它注册为 subagent-tax bin,要求 Node >= 18.17,测试脚本为 node --test --test-force-exit tests/*.test.mjs)。parseArgs 中每个带值 flag 都做了严格校验(缺失或非正数会直接报错退出,而不是让运行挂死):

参数 默认值 说明
--harness a,b,c 所有已安装 选择要测的 harness(已知:claude, codex, gemini, opencode, cursor-agent, pi)
--out DIR ./subagent-tax-report repro pack 输出目录;工具拒绝写入非自己创建的非空目录(需带 .subagent-tax-report 标记文件,见 claimOutDir
--timeout N 90 每个 harness 的捕获超时(秒)
--grace N 3 最后一次捕获后的静默秒数
--repeat N 1 每个 harness 跑 N 次,报告中位数行 + 观测 min–max 分布(不是置信区间,不是方差声明);每次试验的捕获保留在 trial-2/trial-3//… 可审计
--isolate 测 harness 地板而非真实配置(注意:claude 登录态在配置目录里,隔离运行通常会以 "Not logged in" 退出并报告无捕获,harness 首行输出会作为原因展示)
--count-tokens 用 Anthropic count_tokens 把 anthropic 行升级为 provider-exact。会把捕获 body(你的真实系统提示词)发送到 api.anthropic.com;需要 ANTHROPIC_API_KEY,仅选入、永不自发
--ratio N 6.4 估算用 chars-per-token
--json 向 stdout 输出机器可读的 report.json
--list 打印 harness 注册表 + 检测结果(<bin> --version 探测;仅 shell 别名/函数形式的安装会报 not installed)
--timeout/--grace 90/3 每 harness 的捕获计时

典型工作流(METHOD.md 的 Reproducing 一节):

node run.mjs                  # 所有已安装 harness
node run.mjs --list           # 什么装了 + 配方状态
node run.mjs --harness claude,pi --repeat 3
node --test tests/*.test.mjs  # 免 harness 测试套件(fake-harness e2e)

每次运行写出的 repro pack--out 指向)包含:report.json(逐工具拆分、校准说明、honesty 行)、每个 harness 的原始捕获、harness 输出日志、使用的临时配置、覆盖所有产物的 manifest.sha256。manifest 是哈希链而非签名;发布签名结果走仓库 CaveBench 的 Ed25519 receipt 路径与治理流程(见 METHOD.md)。仍然敏感的内容:body 是你 harness 的真实系统提示词——本地路径、skill 列表、MCP 工具名、全局指令文件,分享 repro pack 前请人工检查。

五、输出表格的列语义

表格列(lib/report.mjs 的 LEGEND 与 honesty 行)的精确含义:

  • system / schemas / body:捕获请求(已清洗)的 JSON 字符数。body 是分析后的总和;report.json 另存原始 body_bytes(脱敏前后相差几个字符)。
  • system 按协议分别拼装:顶层 system/instructions 加上 messages/input 内部的 system/developer 角色条目——因为不少 harness 把指令主体放在那里(openaiResponsesParts 同时统计 instructions 与 input 内 system 项)。跨 harness 比较此列是近似的。
  • t1st:到首个捕获 LLM 请求的墙钟时间,包含 harness 启动,不是延迟基准。
  • mcp- 表示命名约定未确认(未知),从不表示零。
  • --repeat 时行是中位数试验,spread 是同一台机器 N 次运行的观测 min–max,不是置信区间

表格下方还会打印每个 harness 的 top-5 schema 权重(topSchemaHogs)与异常说明:非 most-tools 的选择规则、no capture 时 harness 的原话(如 "Not logged in")、被跳过的捕获数。

六、已知限制(来自 METHOD.md 与源码)

  • cursor-agent 报 unmeasurable,不是因为无法重定向:隐藏的 -e/--endpoint 确实能把它指到本地服务器(2026-08-07 实测)。但到达的请求不携带前缀——客户端通过双向 Connect/HTTP-2 RPC 流式发送 agent.v1.AgentRunRequest protobuf,其 schema 只有会话回合、模型标识与用户自己的 MCP 工具,没有系统提示词字段、没有内置工具 schema,bundle 里也找不到任何 LLM 供应商主机名。Agent 循环跑在 Cursor 服务器上,前缀根本不在线上,任何本地 sink 都无法测量(唯一可测的切片是用户自己的 MCP 工具 schema,见 cursor-agent 配方注释)。
  • opencode 的线协议是 OpenAI Responses(经其内置 openai provider),尽管生态文档常标注为 chat completions;换 @ai-sdk/openai-compatible provider 时说的是 chat。分析器两者都处理。
  • 首个捕获后就停止,意味着多请求启动序列(codex memory agent、opencode 标题调用)被捕获但不穷尽探索;它们都在 all_captures 中可见。
  • harness 检测跑 <bin> --version,仅以 shell 别名/函数形式存在的安装会报 not installed。
  • Windows 上 npm 命令 shim 经 PATH/PATHEXT 解析后直接启动其 Node 入口而不经过 shell;清理用 taskkill /t /f,原生 Windows CI 覆盖启动与进程树契约。
  • harness 可能尝试自身旁路流量(遥测、更新检查),配方能抑制的会抑制;codex 配方刻意使用最小临时 home,正是因其真实配置会触发插件 git clone 与 MCP OAuth 握手。

七、把它用起来:三步上手

  1. 探测环境node run.mjs --list,确认本机装了哪些 harness、各配方状态(cursor-agent 会显示 unmeasurable 及原因)。
  2. 全量测量node run.mjs——先看 stderr 中针对 claude 行的真实配置副作用披露;产物落在 ./subagent-tax-report/
  3. 复核与对比:读 report.json 的逐工具拆分,对照 fixtures/example-report.json 的结构;想验证稳定性加 --repeat 3,想拿精确 token 数再显式加 --count-tokens(并设置 ANTHROPIC_API_KEY,理解其网络代价)。

测试侧,tests/ 是一套免 harness 的 node --test 套件(用 fake-harness 做 e2e),覆盖 sink、分析、token 口径、进程树与报告渲染各模块,可作为理解各契约的速读材料。

小结:subagent-tax 的价值不在某一组绝对数字,而在于它把"每次调用重发的前缀"从估算话题变成了本机可复现、可审计、带完整 repro pack 的实测对象——并且用 est/exact 两档口径、variant 标签、- 表示未知、写盘即脱敏等机制,保证输出的每个数字都与其证据边界一致。如果你正在给 Agent 装载 MCP 工具或插件,它是衡量"装这些到底每次调用多付多少税"的本地工具。

登录后查看全文
热门项目推荐
相关项目推荐