Caveman subagent-tax 实战解析:用本地捕获 Sink 测量编码 Agent 每次调用的前缀开销
当 Agent 派生一个 subagent 时,它会把 harness 的完整前缀——系统提示词加上所有工具 schema——原封不动地重发给模型,然后才开始工作。Caveman 仓库中的 subagent-tax 工具(位于 packages/subagent-tax)专门回答这个问题:你的编码 harness 每次调用到底发送多大的前缀? 它在本地起一个伪装成 LLM 供应商端点的 HTTP sink,让每个已安装的 harness 向它发送一条真实请求,然后报告这条请求里装了什么。本文完整介绍其测量方法、各 harness 的注入配方、诚实性设计(token 估算口径、脱敏规则)与全部命令行参数,并结合 lib/sink.mjs、lib/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: 273958、system_chars: 43038、tools_chars: 224655、mcp_tools_count: 63 / mcp_tools_chars: 155480,opencode 行则展示了一个典型的多捕获场景(先有一个 2555 字符、0 工具的标题生成调用,主请求是 seq=2 的 89501 字符请求)。
二、测量流水线:四步完成一次捕获
METHOD.md 完整记录了方法。从源码看,整条流水线在 run.mjs 中组织,分四步:
1. 本地 sink 伪装供应商端点
lit/sink.mjs(lib/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_HEADERS(L26-L55)覆盖凭据类 header(authorization、x-api-key、cookie…)以及账号/设备/会话标识(x-claude-code-session-id、x-codex-turn-metadata、x-session-id…),替换为redacted:sha256:<12>,保留可判等性;body 侧则按 BODY_PATTERNS 高精度匹配邮箱与凭据形状字符串(sk-*、ghp_*、AKIA*、xox*、AIza*)。 - 代理隔离:spawn 前子进程环境会清空所有继承的 HTTP 代理变量并把 loopback 加入
NO_PROXY(run.mjs L200-L205),确保公司代理收不到前缀——这是该工具"数据不出本机"承诺的落点。
2. 按配方启动每个 harness(一次性)
配方注册表在 lib/harnesses.mjs,统一提示词是 Reply with exactly: DONE(PROMPT)。各 harness 的注入方式与 variant 语义:
| harness | 注入方式 | variant | 为什么 |
|---|---|---|---|
| claude | claude -p PROMPT + ANTHROPIC_BASE_URL 指向 sink |
real config | 用户的真实插件/MCP 就是被测的税;默认跑真实配置 |
| codex | 临时 CODEX_HOME + 最小 config.toml(wire_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.AgentRunRequestprotobuf,其 schema 只有会话回合、模型标识与用户自己的 MCP 工具,没有系统提示词字段、没有内置工具 schema,bundle 里也找不到任何 LLM 供应商主机名。Agent 循环跑在 Cursor 服务器上,前缀根本不在线上,任何本地 sink 都无法测量(唯一可测的切片是用户自己的 MCP 工具 schema,见 cursor-agent 配方注释)。 - opencode 的线协议是 OpenAI Responses(经其内置 openai provider),尽管生态文档常标注为 chat completions;换
@ai-sdk/openai-compatibleprovider 时说的是 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 握手。
七、把它用起来:三步上手
- 探测环境:
node run.mjs --list,确认本机装了哪些 harness、各配方状态(cursor-agent 会显示 unmeasurable 及原因)。 - 全量测量:
node run.mjs——先看 stderr 中针对 claude 行的真实配置副作用披露;产物落在./subagent-tax-report/。 - 复核与对比:读
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 工具或插件,它是衡量"装这些到底每次调用多付多少税"的本地工具。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00