Token Slimmer
在内容首次进入请求之前缩减它,削减会话每一轮重发的输入 token。
这不是在压缩文本,而是在处理一个被忽略的账本问题:一次工具结果不是付一次钱。它进入 surface 之后,往后每一次请求都会重新发送它。
成本归因:一次本地分析(动机,不是收益承诺)
2026-09-28 从 ~/.dsh/sessions 解压 8 个真实会话(2,373 步)做过一次成本归因,把每条 surface 内容按"之后还有多少请求会重发它"加权:
| 成本面 | 重发成本 | 占比 |
|---|
| 工具结果 | 33,722,853 | 40.7% |
| reasoning 块 | 38,816,942 | 46.9% |
| 合计 | 82,811,400 | 100% |
单条最大浪费:一次 read 8,126 token、后面还有 127 步 → 重发成本 1,032,002 token。
reasoning 是最大的单一成本面,而它不在任何工具级守卫的视野里——它住在 assistant 消息里,不是工具结果。这组数字说明问题在哪、预算该花在哪,不构成插件收益的承诺;本插件的收益口径见下文「可移植基线」与「统计与账本」。
两条机制,一条共同原则
工具结果在 tools/post-execute 有界化;reasoning 在 agent/pre-step 通过一个插件自有事件经 message projection 有界化(写入默认关闭,理由见下文)。
共同原则:压缩只有在被压内容从未被请求携带过时才是免费的。
同一个预算放在两个位置,回收量几乎一样,代价完全不同:
| 预算 | 事后压缩历史 | 首次进入时压缩 |
|---|
| 500 | 46.1% | 46.2% |
事后压缩会重写 provider 已经缓存的前缀,从改动点起的整个后缀重新计费。首次进入的内容从未发出,压它不击穿任何缓存。这个领域里其他做历史压缩的框架都承认这一点——OpenHands 的 README 和 SWE-agent 的源码注释都写明了。
回本条件可以精确写出:
(50 − 49h) × [1 + (R−1)r] > 50
h 是稳态缓存命中率,R 是剩余步数,r 是压缩比例。在实测的 99% 命中率下这等于 (R−1)r > 32.6,约 70 步(47% 压缩率)。auto 策略不需要这个数,因为它只做两件无条件免费的事。
四层保护
缺任何一层,压缩都会变成净亏损。
1. 不可分割载荷保护(payloadLineGuardChars: 2000)
单行超过 2000 字符的内容不参与有界化。长行就是载荷本身——API 返回的 base64 文件体、协议 trace 的 hex dump、minified bundle。保留头尾等于交给模型半个用不了的载荷。
这条是踩坑换来的。默认开行截断时压缩率显示 22.8%,核对原文发现约 20 个百分点来自把 hex dump 和 base64 拦腰砍断。指标赢了,任务输了。
2. 加法保留
错误行、数值异常(>2σ)、结构标记、novel 行按规则保留,不参与预算竞争,即使超出预算也留。这是"压缩但关键信息一定在",而不是"保留头尾碰运气"。
3. 原文恢复(recovery-store.js)
发布有损候选之前,插件实际收到的原文先存为会话隔离的快照(独立 UTF-8 文件 + manifest + SHA-256,默认 256 MiB 上限,存满后保留原文并报告 recovery-unavailable)。真实日志里 988 个结果中有 87 个被投递多次——模型确实会为了拿回被省略的部分而重试,但"重新执行"不等于"拿回原文":依赖时钟、计数器或可变文件的工具会返回别的东西。因此恢复路径是读快照,不是重跑工具。
4. 可恢复标记
每次省略都在原位写明行号区间、省略的行数与 token 数、以及恢复方式。read 给 offset=;其余内容指向恢复快照的 artifact。不存在"恢复不回来却声称已恢复"的状态——快照保存失败时结果保持原文并明确报告原因。
恢复指令的投递是位置性的:内核渲染标记时经 recoveryNoteFor(blockIndex) 写入真实指令,插件在快照落盘后渲染第二遍。不存在事后文本搜索——引用完整标记语法的内容(本仓库自己的测试、diff、散文)因此逐字节安全。这一条是缺陷换来的:早期实现写占位符再全局替换,既改写了引用该语法的原文,也在引用先出现时让真标记留着占位符。
重跑同一工具拿到全文的旧路径保留为兼容选项 fullTextOnRepeat,默认关闭:它放行的是重新执行的结果,不是原文,只适用于已知幂等的调用。
评分与路由(L1 / L2 / L3)
L1 重要性评分(importance.js)——机械头尾是位置猜测,对散文成立,对日志/diff/trace 失效,那些场景里关键的那一行通常在中间。评分层用确定性函数给每行打分:结构标记、声明行、novel 内容、信息密度、缩进深度。
L2 意图保护(policy.js)——用户消息含分析意图(分析/排查/为什么/debug/root cause…)时预算 ×2。当用户在问"为什么坏了",每一行都是证据;那行解释 bug 的代码按定义不是你能猜到的那行。
L3 内容类型路由(policy.js)——7 类确定性检测,各自带权重与预算系数:
| 类型 | 预算系数 | 加权保留 |
|---|
| diff | ×1.3 | hunk 头、增删行、文件头 |
| code | ×1.2 | 声明、签名、注释、顶层缩进 |
| search | ×1.0 | 匹配行、路径前缀 |
| json | ×0.9 | 键名、含 error/message 的字段 |
| table | ×0.9 | 表头 |
| log | ×0.8 | 错误、时间戳 |
按工具身份路由而非仅按格式,是这个细分位置与 headroom 的区别——后者把 git status 和 npm install 都判成 LogCompressor。
配置
profile 的 cordis.patch.yml(见 cordis.patch.yml):
- id: token-slimmer
config:
maxResultTokens: 2000
readMaxResultTokens: 5000
reasoning:
strategy: auto # never | newest | cold | all | auto
budgetTokens: 500
coldAfterMs: 300000
dryRun: true # 只记录预测,不改会话;生产写入由 REASONING_WRITES_VERIFIED 门禁(当前禁用,净值为负)
fullTextOnRepeat 默认关闭,不写即为关;需要放行重复调用原文的部署方自己打开,并接受它重新执行工具的副作用。
预算怎么选——可移植基线,由账本生成(node scripts/readme-baseline.mjs,勿手改数字):
| 预算(通用/read) | 节省率 | 触及样本 |
|---|
| 4000 / 12000 | 63.9% | 6 / 11 |
| 3000 / 8000 | 68.3% | 8 / 11 |
| 2000 / 5000 | 75.1% | 10 / 11 |
| 1500 / 4000 | 79.1% | 10 / 11 |
口径:确定性合成语料(11 个样本、约 11 万 token,刻意全部远超预算,压测的是内核)× 真实管线(slimContent)× metrics.js 账本(summarizeMetrics)。分母是全部样本,未触及的计入分母不计节省。**这组数字描述这套语料和这个管线版本,不对应任何真实会话的样本分布,不构成收益承诺。**在真实会话上重测用 node test/budget-swap.mjs。
历史对照:旧管线曾在 2,989 个真实工具结果上量得 2000/5000 档 21.7%(2026-09-28 口径)。当时的行为含空白清理与行截断,其占比有后来被保真修复剔除的成分,且语料是真实会话——与上表不可比,仅留作口径演进的记录。
DEFAULTS 的三个 log 归约(stripCarriageReturns / stripTrailingWhitespace / foldRepeatedLines)现为 false,且 cordis.patch.yml 未开启它们。实测影响很小:同一批真实会话(27 个、3,556 步)在 node test/cost-replay.mjs 上的重发节省率从旧行为的 14.29% 降到 13.90%——0.39 个百分点(compaction 记账修复前的口径)。口径更新 2026-10-07:同一份存储重跑为 11.92%(27 个会话 / 3,753 步,baseline 146,836,848 tokens)——bccc652 之后只有真正产出 summary 的 compaction 才折算历史,此前失败 compaction(harness 在 compaction/end 上写 error: summarization truncated at the token cap,不产出 summary)抹掉的历史被从账上删掉了,所以 13.90% 偏乐观。折叠重复行与剥离 CRLF 的收益本来就有限,合成语料看不出来,真实会话才测得准。上表的合成基线不受影响(语料不含受这三个归约作用的形态,重跑逐位一致)。
分内容类型的可压缩性(推荐档 2000 / 5000,同一账本):
| 内容类型 | 输入 token | 节省 token | 节省率 |
|---|
| log | 52999 | 48404 | 91.3% |
| json | 17751 | 13897 | 78.3% |
| code | 17651 | 12410 | 70.3% |
| diff | 14858 | 7060 | 47.5% |
| read | 6840 | 919 | 13.4% |
read 的预算更高、可压缩性最低,都是有意的:read 是模型按路径主动要的内容,通常用于编辑,而且工具自身的 readMaxBytes 是部署方刻意设定的。
reasoning 策略(历史回测,前提已被证伪):
| 策略 | 成本 ¥ | vs 不做 | 击穿步数 |
|---|
| never | 59.033 | — | 0 |
| newest | 38.096 | −20.937(−35.5%) | 0 |
| cold | 39.027 | −20.006 | 0 |
| all | 38.096 | −20.937 | 0 |
| auto | 38.096 | −20.937 | 0 |
这组数字来自 2026-09-28 的回测,成立前提是改写 reasoning 免费。那个前提已被实测证伪:改写改变前缀,provider 从变更点起重算后缀,那部分按未命中价计费。实测(test/reasoning-net.mjs,17 次真实改写)——移除 77 token(按命中价省),破坏 733 token(按未命中价赔),盈亏平衡需 467 剩余步而实际只有 16。在 50× 的缓存差价下,改写已发送历史是净亏的。 上表的相对关系因此不成立,仅保留为口径演进记录。
cold 反而更差——它把压缩和冷窗口绑定,主动跳过了 113 个非冷窗口的机会。真正的原则不是"缓存冷的时候压",而是内容还没被发送的时候压。
统计与账本
运行统计以 metrics.js 的账本为权威来源:index.js 逐结果记录,summarizeMetrics 汇总(schemaVersion: 2)。
estimatedAppliedSavings:实际应用的首进入压缩节省。旧 savedTokens 只是它的兼容别名,不再混入 dry-run 预测。
estimatedPotentialSavings:dry-run 预测,按 session/seq/内容哈希/选项哈希去重——同一候选重复观察只计次数,不重复累加预测收益。
- 分母完整:未修改、逃生、恢复失败的结果都计入输入分母。压缩率为零就报零。
- 费用只来自逐请求 usage(hit/miss/output)与版本化费率(
priceUsage,缺字段报 unknown 不猜零;reasoning 已含在 output 中不重复计费)。只有文本估算时显示模拟,不用整请求全 hit/全 miss 推导"实测账单"。
回放侧同口径:test/helpers/replay-ledger.mjs 按 event.seq + surfaceOp 重建每次请求的可见消息,compaction 以实际替换事件为准——替换掉的 reasoning 不会继续计费(三步验收场景:2,000-token reasoning 被替换为 2-token 摘要后,会话账单为 0.00008008 而非 0.00016008,统一 hit 费率 0.04/M 仅为核对账本的测试常量)。
旁路与载荷的可见度:totals.recoveryBypassed(哈希验证通过的恢复读取)、totals.recoveryUnverified(路径属于本插件但哈希失配,不授予旁路、走正常压缩)、stats.payloadLinesPreserved(密度判定为载荷而整体放行的行)。三个计数都存在,是因为它们在正常情况下分别是零、零和非零——看不见的豁免等于没有豁免。
跨 agent 使用
内核是零依赖纯函数,与 harness 解耦。任何能起 shell 的 agent 都能通过 bin/slim.mjs 接入:
cat build.log | slim --budget 2000
slim --kind read --budget 8000 < result.txt
slim --json < out.txt # 统计到 stdout,文本到 stderr
slim --intent --budget 2000 < log.txt # 声明这是分析场景,预算 ×2
slim --type diff < patch.txt # 强制内容类型
slim --save-original ./store < result.txt # 先存原文再压缩,打印 artifact id
slim --restore <artifact-id> --store ./store [--offset 1 --limit 50] # 从快照恢复
slim --full < result.txt # 原样通过,不做任何变换
退出码:0 压缩了 / 3 未变 / 1 用法错误。恢复的边界:--restore 读的是快照,原文在手才能恢复;--full 只原样输出调用方仍持有的输入,CLI 对普通文本 stdin 无法区分"真实原文"和"已是压缩表示",永远按原文处理。Claude Code 的 PostToolUse hook、Codex hook、MCP tool 都只要把输出喂进来、把 stdout 传回去,不需要适配器代码。
不做什么
- 不为压缩调用模型。 任何需要额外一次推理的机制,在短输入上都是净亏。LLMLingua 靠 GPT-2-small/LLaMA-7B 算 perplexity 逐 token 剔除,headroom 的 Kompress 走 ONNX 本地推理——两者都为此付出了依赖和延迟。
- 不注册新工具。 多一个工具的 schema 会在每一轮重复计费,抵消它带来的一切收益。
- 不注入检索工具。 headroom 的 CCR 会注入
headroom_retrieve,代价是每轮都带这个 schema。这里的等价物是恢复快照,零 schema 成本。
- 不改工具定义。 工具描述在这个 harness 里是行为契约(
pwsh 里那句 sandbox 重试指令、plugin_manager 的"List first"),不是 API 文档。toolgz 在 MCP 场景剥 prose 无损,在这里会直接改变行为。
- 不做语义缓存。 精确匹配缓存需要跨会话存储与失效策略,收益不确定。
插件层的能力边界:想从根上消除"压缩 × 缓存"冲突,只有改服务端 kernel(Leyline 的闭式 RoPE 修正)或改模型注意力(ReCache 的 resource-wise attention)——两条路都不在插件层。
测试
npm test # 内核套件 + 全部 *.test.mjs(fidelity / recovery /
# result-budget / reasoning-lifecycle / metrics /
# replay-ledger / cli / plugin-recovery / config)
node test/perf.mjs # 性能基线(时序敏感,不进默认套件)
node test/budget-swap.mjs # 预算档位对比(读取私人会话,显式入口)
node test/replay.mjs # 按工具回放真实压缩率(显式入口)
node test/audit.mjs # 成本结构审计(显式入口)
node test/strategy.mjs # reasoning 策略回测(显式入口)
node test/inspect.mjs pwsh 1 # 人工核对压缩前后原文
node scripts/readme-baseline.mjs # 可移植基线:重生成 README 预算表
覆盖:envelope 逐字节无损往返、行号单调可解析、载荷保护、加法保留、快照恢复往返(含跨重启与哈希不符)、共享预算规划(结果级一份预算)、可信状态幂等、评分内部、类型检测、意图识别、策略分派、选项校验、指标账本(分母与去重)、回放账本(compaction 不产生幽灵费用)、CLI,以及 1MB 输入的性能上限。
未验证的部分
- 压缩后的推理质量没有量化。 这个领域里没人做到——CMV 论文自承"下游推理质量下降未量化",pi-fold 自承标题里的「无损」完全没验证(82 个请求里 agent 调用 context 工具 0 次)。要判断只能靠 A/B:固定 20–50 个任务,对比压缩前后的合格率,不看模型自述"有信心"。
- reasoning 生产写入默认禁用(
REASONING_WRITES_VERIFIED = false),理由是净值为负,不是兼容性未验证。
- 机制侧已经查清并修好:写入不能是
assistant/message + surfaceOp: replace(该类型禁止携带 sourceEventSeqs,而 replace 要求引用全部被遮蔽节点——两条规则合起来使写入必然抛错)。正确路径是自有事件类型 + registerMessageProjection,已实现并验证可用。
- 收益侧已被实测否定:见上文「reasoning 策略」。这是一个不会自行改变的结论——50 倍的缓存差价是算术,不是待验证的契约。
- 测量脚本
test/reasoning-cost.mjs(毛值)与 test/reasoning-net.mjs(净值)保留为显式入口。费率结构若变化,重跑它们即可重新判定。
read 结果被压缩后用于 edit 的失败率没有测。结构上由更宽的预算 + 恢复快照覆盖,但没有实测。
安装与使用(其他人)
零运行时依赖,只需要 dsh 本体(仅用 Node.js 内置模块)。
方式一(推荐):用 dsh 的插件页添加 —— 打开「添加插件」,填入本仓库地址(Error0422-bit/dsh-token-slimmer,完整 URL 或本地目录路径都可以),按需选择安装源,装完重启 dsh 生效。
方式二(手工,等价):往 profile 里加一条 bundle —— 适合脚本化或离线部署。在 ~/.dsh/profiles/<profile>/cordis.patch.yml 里加:
- insert:
- id: token-slimmer
name: 'dsh-token-slimmer'
config:
maxResultTokens: 2000
readMaxResultTokens: 5000
再把本仓库加进该 profile 的依赖(pnpm add file:<本仓库路径> 或你的包管理器),重启 dsh 生效。
B. 只当命令行用 —— 不接 dsh:
cat huge.log | node bin/slim.mjs --budget 2000
node bin/slim.mjs --kind read --budget 5000 < result.txt
自检:npm test(内核套件 + 每个 *.test.mjs)。
权限:见 LICENSE——版权归作者所有(All rights reserved)。可以拿去在自己的 dsh 上使用;再分发、修改或商用需作者书面许可。
省多少(实测,2026-10-07)
三种口径必须分开读,混在一起就会误导:
① 内核压测(合成语料:11 个样本 / 11 万 token,全部刻意远超预算)——由 node scripts/readme-baseline.mjs 生成,衡量内核本身:
| 预算(通用/read) | 节省率 | 触及样本 |
|---|
| 4000 / 12000 | 63.9% | 6 / 11 |
| 3000 / 8000 | 68.3% | 8 / 11 |
| 2000 / 5000 | 75.1% | 10 / 11 |
| 1500 / 4000 | 79.1% | 10 / 11 |
② 真实工具结果的首次进入压缩(本机 store,3,707 个真实结果)——node test/budget-swap.mjs:
| 预算(通用/read) | 输入 tokens | 输出 tokens | 节省 | 被切结果数 |
|---|
| 4000 / 12000 | 222,417 | 148,454 | 33.3% | 27 |
| 3000 / 8000 | 290,147 | 170,329 | 41.3% | 47 |
| 2000 / 5000 | 449,191 | 246,425 | 45.1% | 90 |
| 1500 / 4000 | 523,571 | 268,052 | 48.8% | 131 |
分母是全部结果——没被触及的也计入分母、不计节省(只看被切的结果当然更高)。同一配置在不同时间复测落在 45–47%,差异来自语料组成,别当成 0.1pp 级的精度。
③ 账单口径(按重发计费)——node test/cost-replay.mjs,27 个真实会话 / 3,780 步:
baseline 重发 tokens 151,497,844
启用本插件 133,909,282
节省 17,588,562 → 11.61%
折钱(cached ¥0.04/M … uncached ¥2/M):¥0.70 … ¥35.18
为什么 ② 有 45% 而 ③ 只有约 12%:首次进入的节省会被"这条内容之后还会被重发多少次"稀释,而大多数结果只被重发有限次。按首次进入算会高估一个数量级——所以 ① 那组数字不是收益承诺。
没碰到的部分:reasoning 占重发成本约 87%(131.6M / 151.5M tokens),本插件不压缩它,理由是实测的:改写已发送历史会让 provider 缓存从改动点失效、后缀按未命中价重算——每个改写点一次性多付 ~915 tokens,而前缀只缩减 ~59 tokens/步,回本要 ~760 个后续步,实际只剩 6 步(中位)。因此写入闸保持关闭。