# 实测：快在哪儿，不快在哪儿

> **当前默认（1.5.0）**：不按步数收尾。`finishLineSteps` 与 `stepBudget` 默认都是 `0`；什么时候结束由任务状态和完成证据决定（复用已通过的检查、换方法、看门狗），不由计数器决定。
>
> 1.4.4 把「第 10 步提醒收尾」设为默认时确实更快（下节 14 对中位 1.86×、正确性零损失），但它对**仍在推进的长任务**同样开火，这是 1.5.0 撤回该默认的原因。**以下 1.4.4 及更早的数字全部是历史与消融证据，不是 1.5.0 的速度声明**；`fl10` 就是那条退役默认的消融臂。1.5.0 默认在隔离环境下的干净重基线见下一节「1.5.1」（10 家族、30 个单元、总墙钟 0.72×；复现块 W5、W7 总量各 0.73×，对照块 W8b 逐格中位 0.717×；W3+W5+W7 合计 65 个有效单元 0.74×）。
>
> **口径（1.5.1 起）**：一个单元只在 `exit == 0 && 步数 ≥ 1 && 输出 > 0 && 判分完成 && 未泄漏` 时计数；运行崩溃的单元整格作废并点名，不算红、也不算快。所以「全绿」一律指**有效单元全绿**（1.5.1 29/29、W5 19/19、W7 17/17）。
> **1.7.0**：`lazy` 与它改 `reasoningEffort` 的那一半已删除，换成三个默认关闭的纯提示开关（`probeFirst` / `actBeforePlan` / `repeatNudge`）。第一次实测见下节「块 DP17」：三开 12 对 **0.647×**、CI95 [0.501, 0.893]、判分全绿；单开三臂都还没到显著。
> **1.7.1**：`actBeforePlan` 多带一条按思考长度的提醒（不新增开关，面板仍七行）。发布版本上三开对全关九对 **0.372×**、CI95 [0.240, 0.563]、判分 138/138（下节「块 DP20」）；四块合池 30 对 **0.505×**、CI95 [0.401, 0.635]、26/30 更快。

## 块 DP20（2026-11-02）：1.7.1 上三开 `trio17` 对 `off` 九对 **0.372×**，CI95 [0.240, 0.563]，判分 138/138

`DP19` 只量了 `ab17`，且任务偏短。这一块在 1.7.1 的真实 profile 上直接对打「三开」与「全关」，任务换成 `DP18` 那三族，看发布版本在更长的任务上是否仍成立。

**方法。** 家族 `codeimpl`（14 项判分）、`ledger`（22 项判分）、`stategraph`；每族 3 对、共 9 对；两臂 `off` 与 `trio17`（`probeFirst` + `actBeforePlan` + `repeatNudge`）同对 ABBA 交替先后；18 次运行全部有效，判分 138/138 与 `off` 逐格一致、零失败、零泄漏（`bench/results/suite-DP20b.jsonl`）。运行的 CLI 是当前桌面 Host 所用的 bundle，不是旧包。

| 臂 | 汇总墙钟比 | CI95 | 更快 | 单对范围 |
|---|---|---|---|---|
| `trio17` | **0.372×** | **[0.240, 0.563]** | 8/9 | 0.10 / 1.04 |

分家族（逐对几何）：`codeimpl` **0.601×**（2/3 更快）、`ledger` **0.437×**（3/3）、`stategraph` **0.196×**（3/3）。思考字符（几何均值）`off` **83823** → `trio17` **27621**（−67%）；输出 token 27636 → 11914（−57%）；步数 13.22 → 6.67；墙钟几何均值 464.9 秒 → 173.1 秒。

**与前三块合池（30 对）：** `trio17` **0.505×** [0.401, 0.635]、26/30 更快；`ab17` 0.706× [0.586, 0.858]、20/29；`pf17` 0.809× [0.646, 1.009]；`rn17` 1.007× [0.844, 1.225]。合池质量：`trio17` 30/30 绿、438/438 项；`off` 38/38 绿、546/546 项；`ab17` 29/29、408/408。几何均值输出 token `off` 16037 → `trio17` 9380、思考字符 42380 → 20872、步数 11.84 → 8.30。

**读法。** 三点：

1. **发布版本本身有效，不是只对旧任务有效。** 这一块用的就是 1.7.1 的代码与当前 Host 的 CLI，9 对点估计 0.372×，比 `DP18` 的 0.491× 更低；判分 138/138 说明省下来的是叙述与空转，不是验证。
2. **省的是思考，不是检查。** 思考字符降 67%、输出 token 降 57%、步数降 49%，判分项数不变、泄漏计数 0。
3. **`codeimpl` 有一对反过来（1.04×，步数 6→8）。** 与 `DP18` 的 `codeimpl` 0.869× 同向：实现类任务里「先动手」偶尔会多绕一步。所以推荐措辞仍是「大多数时候」，不是无条件。

**局限。** 每族 3 对，单对区间 0.10–1.04×，`stategraph` 的 0.196× 尤其需要更多样本才能当承诺。这一块只有两臂，**没有** `ab17`，所以无法在此块内把「长度提醒」与「先动手」的贡献拆开；拆账证据仍是 `DP19`（`ab17` 0.701×、`debugfix` 4/4 与 `migrate` 4/4 触发长度提醒）。另有一块 `suite-DP20.jsonl` 因误用旧包 CLI（`exit=1`、`steps=1`、`llm=0`）整块作废，已移到 `bench/results/archive/suite-DP20.void.jsonl`，其 18 行**不在**任何数字里；本块是修好 CLI 后的重跑。

## 块 DP19（2026-11-01）：1.7.1 的回归复核——`actBeforePlan` 多带一条长度提醒后，`debugfix`+`migrate` 8 对 **0.701×**，判分 16/16

1.7.1 把「本回合思考写太长」的提醒合进了 `actBeforePlan`（不新增开关，面板仍七行）。要回答的问题是：这个臂在 `DP17` 的两族旧任务上曾经是 `debugfix` 0.582×、`migrate` 0.996×，多一个行为之后会不会退化。

**方法。** `debugfix`、`migrate` 各 4 对、共 8 对；两臂 `off` 对 `ab17`，ABBA 交替先后，16 次运行，判分 16/16 绿、零失败、零泄漏（`bench/results/suite-DP19.jsonl`）。

| 臂 | 汇总墙钟比 | CI95 | 更快 | 单对范围 |
|---|---|---|---|---|
| `ab17`（1.7.1，两个行为） | **0.701×** | **[0.516, 0.930]** | 6/8 | 0.33 / 1.24 |

分家族（逐对几何，括号里是同一族在 `DP17` 的 1.7.0 读数）：`debugfix` **0.756×**（4/4 更快；`DP17` 0.582×）、`migrate` **0.649×**（2/4 更快；`DP17` 0.996×）。思考字符几何均值 `off` 28275 → `ab17` **17834**；步数 9.27 → 7.62。

**提醒确实在真实运行里发出了（翻会话记录数的，不是从代码推的）：** 4 个 `ab17` 会话各收到 1–2 条长度提醒（每回合上限 2），4 个 `off` 会话 0 条；同批会话里每回合思考字符 5350–120897，4000 的门槛确实被越过。

**结论与局限。** 加长度提醒没有让这个臂在旧任务上退化：点估计 0.701×、CI 上界 0.930，比 `DP17` 同期两族（0.582× / 0.996×）没有变差的方向证据。但只有 8 对、`migrate` 只有 2/4 更快、单对区间 0.33–1.24×，**不足以说它更快**，更不足以把两个行为各自的贡献拆开——那需要一个只带长度提醒的独立 arm，本轮没做，也不打算为了拆账再加一个面板开关。


## 块 DP18（2026-11-01）：三族复核 1.7.0 的三臂，`trio17` 9 对 **0.491×**，CI95 [0.314, 0.788]，判分全绿

`DP17` 只覆盖两族短任务，所以在三族更长的任务上复核同一批 arm。**这一块量的是 1.7.0 的代码**——长度提醒（1.7.1）还没上，所以 `ab17` 在这里只代表「先执行后解释」一个行为。

**方法。** 家族 `codeimpl`（14 项判分）、`ledger`（22 项判分）、`stategraph`；每族 3 对、共 9 对；四臂 `off` / `pf17` / `ab17` / `trio17` 按对轮转，同对内交替先后；共 36 次运行，全部单元判分与 `off` 逐格一致、零失败、零泄漏。

| 臂 | 汇总墙钟比 | CI95 | 更快 | 单对范围 |
|---|---|---|---|---|
| `pf17` | 0.719× | [0.492, 0.999] | 7/9 | 0.21 / 1.56 |
| `ab17` | 0.643× | [0.427, 0.920] | 6/9 | 0.21 / 1.39 |
| `trio17` | **0.491×** | **[0.314, 0.788]** | **8/9** | 0.18 / 1.97 |

分家族（逐对几何）：`codeimpl` 上 `pf17` 0.953× / `ab17` 0.784× / `trio17` 0.869×；`ledger` 上 0.490× / 0.979× / **0.331×**；`stategraph` 上 0.797× / **0.346×** / 0.411×。思考字符（几何均值）`off` **71275** → `pf17` 59930 / `ab17` 44617 / `trio17` **32463**；步数 12.86 → 7.21 / 8.98 / 6.87。

**与 `DP17` 合池（21 对）：** `pf17` 0.809× [0.643, 1.002]、`ab17` **0.708×** [0.558, 0.898]、`trio17` **0.575×** [0.444, 0.748]。两块方向一致：这三个臂不是「只对短任务有效」。

**读法。** 三点：

1. **`ledger` 与 `stategraph` 是这次收益来源。** `trio17` 在这两族 0.331× / 0.411×，`ab17` 在 `stategraph` 0.346×；思考字符从 71275 降到 32463，方向仍然是「少想，不少验证」——判分 36/36 与 `off` 相同。
2. **`ab17` 在 `ledger` 上不赚（0.979×）。** 与 `DP17` 的 `migrate`（0.996×、步数 11.0 → 15.7）同类：需要先列清单的任务被「先动手」推着绕路。所以它保持默认关，按任务形状自己试。
3. **`trio17` 在两块里都是唯一 CI 不跨 1 的臂**（`DP17` 0.647×、`DP18` 0.491×、合池 0.575×）。三开不是三个平均值，组合在两个方向上都不亏。

**局限。** 每族只有 3 对（单对区间 0.18–1.97×），比 `DP17` 的 6 对更薄；原计划 5 对，为了「同一时刻只跑一个块、不长时间占满 CPU」的纪律减到 3 对，代价是区间更宽。所以对外仍只说合池的 `trio17` 0.575× 与两块各自的点估计，不拿单族数字做承诺。`repeatNudge` 在 `DP17` 里只被触发 3 次（详见该块局限），这一块没再单独量它。

## 块 DP17（2026-11-01）：三个纯提示侧开关，读一次事实 / 先动手 / 重复提醒；三开 12 对 **0.647×**，CI95 [0.501, 0.893]，判分全绿

1.7.0 删掉了 `lazy` 与它改 `reasoningEffort` 的那一半（依据是 `LZ1` 里两臂对打 1.106×、CI 跨 1，见下文 `LZ1` 块），换成三个**只改提示、不改任何请求字段**的开关。这一块就是它们的第一次真测量。

**这一块量的是 1.7.0 的代码**（1.7.1 给 `actBeforePlan` 补了按思考长度的提醒，所以本块的 `ab17` 只代表「先执行后解释」一个行为）。

**方法。** 家族 `debugfix`、`migrate`；每族 6 对、共 12 对；五个 arm 配置（`off`、`pf17`=`probeFirst`、`ab17`=`actBeforePlan`、`rn17`=`repeatNudge`、`trio17`=三开）按对轮转，同对内的 `off` 与各臂交替先后。共 60 次运行，全部单元的判分与 `off` 逐格一致，零失败。比值 = 该臂墙钟 / 同对 `off` 墙钟，家族与合池都用逐对几何平均；CI 是 4000 次 bootstrap 的 2.5 / 97.5 分位。

| 家族 | 开关 | 墙钟比 | 更快 | 步数（几何均值） | 思考字符（几何均值） |
| --- | --- | --- | --- | --- | --- |
| `debugfix` | `pf17` | 0.993× | 3/6 | 4.8 / 5.3 | 7329 / 8249 |
| `debugfix` | `ab17` | **0.582×** | **6/6** | 4.2 / 5.3 | **3152 / 8249** |
| `debugfix` | `rn17` | 1.038× | 4/6 | 5.0 / 5.3 | 8792 / 8249 |
| `debugfix` | `trio17` | **0.632×** | **6/6** | 4.8 / 5.3 | 4241 / 8249 |
| `migrate` | `pf17` | 0.785× | 4/6 | 10.6 / 11.0 | 45499 / 61560 |
| `migrate` | `ab17` | 0.996× | 2/6 | 15.7 / 11.0 | 52268 / 61560 |
| `migrate` | `rn17` | 0.978× | 3/6 | 10.5 / 11.0 | 52945 / 61560 |
| `migrate` | `trio17` | **0.663×** | 4/6 | 11.3 / 11.0 | **34783 / 61560** |

| 合池（12 对） | 墙钟比 | CI95 | 更快 | 最快 / 最慢单对 |
| --- | --- | --- | --- | --- |
| `pf17` | 0.883× | [0.663, 1.176] | 7/12 | 0.31 / 1.99 |
| `ab17` | 0.761× | [0.578, 1.032] | 8/12 | 0.44 / 2.12 |
| `rn17` | 1.007× | [0.839, 1.227] | 7/12 | 0.62 / 1.90 |
| `trio17` | **0.647×** | **[0.501, 0.893]** | **10/12** | 0.30 / 2.44 |

**读法。** 三点必须一起说清：

1. **`actBeforePlan` 是这次真正对着「思考链过长」的那一刀。** `debugfix` 上它 0.582×、6/6 更快，思考字符（几何均值）8249 → 3152（**0.38×**），步数（几何均值）5.3 → 4.2。也就是说收益确实来自「少想」，不是少做验证——同族判分逐格相同。
2. **它不是通用加速。** `migrate` 上它 0.996×、2/6 更快，步数反而 11.0 → 15.7：该族要先列清单再动手，硬推「先动手」会绕路。所以它保持默认关，按任务形状自己试。
3. **`repeatNudge` 单开等于没变（1.007×），但也不拖后腿。** 单开 12 对里 7 对更快、CI 完全盖住 1；放进三开组合后合池 10/12 更快、0.647×，是这一版唯一 CI 不跨 1 的臂。它的价值在「组合里不亏」，不在单开收益；这一点如实写，不做成「重复提醒有效」。

**局限。** 每族 6 对、两族合计 12 对，`trio17` 的单对区间是 0.30–2.44×，尾部有反向格；`ab17` 的合池 CI 上界 1.032 仍然擦过 1。所以对外只说 `trio17` 这一条 0.647×（CI 不跨 1），单开三臂一律写成「方向一致、尚未单独显著」。更宽的三族复核（`codeimpl`/`ledger`/`stategraph`）已跑完，见上一节「块 DP18」。

**`repeatNudge` 的 1.007× 是「几乎没触发」的读数，不是「提醒无效」的读数。** 把 `rn17` 那 12 个会话的记录翻出来数原文：12 个会话里只有 2 个收到过提醒（合计 3 条），而且全部来自「连续结果内容相同」那条分支（观察到相同条数 3、7、7）；「同一调用本回合已跑 ≥2 次」这条分支一次都没满足。离线回放同一批会话：`stale` 分支在 5 次 `debugfix` 关闭态会话里会触发，`migrate` 里 0 次；每回合 `signature` 最多重复次数均值 1.18、`digest` 最长连击均值 2.76。也就是说阈值对着「极端打转」而不是「日常长链」——所以 1.7.1 才在 `actBeforePlan` 里补了按思考长度触发的第二条提醒，而不是继续调这个阈值猜测。



## 提醒投递的稳定性（2026-10-29）：压缩重入会让同一轮收到两条

`bench/turns.py` 按 `turn/start` 切轮，把「够长却没收到提醒」「同一轮收到两次以上」「后续轮是否重新上膛」从会话记录里数出来，不看墙钟、不看主观印象。

72 小时窗口、68 个真实桌面会话、1446 轮（`--dir='--Users-*' --since-hours 72`）：

| 指标 | 读数 |
| --- | --- |
| 合格轮（≥ 4 步） | 647 |
| 收到压缩提醒 | 646（**99.8 %**） |
| 同一轮收到 ≥ 2 条 | 114 |
| 其中两次之间夹着 `compaction` | **105（92 %）** |
| 多轮会话后续轮重新上膛 | 599 / 600 |

前两行说明闸门本身没有漏投；最后一行说明「每轮重新上膛」是真的在工作。问题只在第三行：**轮内上下文压缩之后，同一轮会再收到一次同一条提醒**。

根因在宿主侧的重入：压缩时该轮没有结束，宿主用**同一个 turn 号**再次进入（`turn/start` 不落盘，所以只会在插件的事件流里看到）。`noteTurn` 原先只要看到 `turn/start` 就清空每轮闩锁，`disciplineSent` 被复位，所以压缩后第 4 步又发一次。

1.5.28 用 turn 号判别：同号 = 续跑，不清闩锁、不清计数器；号变大才是新轮。事件流没有 turn 号时保持旧行为。压缩后本就有独立的 reassert 提醒，所以压掉这条重复提醒不降低纪律覆盖。

## 1.5.1 干净重基线（2026-10-26）：10 家族 30 个单元，总墙钟 0.72×、逐格中位 0.64×、隐藏测试 60/60 全绿

1.5.0 那一节的数字（0.86× / 0.95× / 0.85×）因两个测量缺陷整体撤回，这一节是修好缺陷之后、用当前默认（`def15`，即插件 `enabled: true` 的出厂配置）重跑的结果。**这是 1.5.x 唯一的速度声明。**

实验：10 个家族（codebase、codeimpl、csvpipe、debugfix、ledger、migrate、perfrefactor、shellops、shellpipeline、stategraph）× 3 对 × 2 臂（`off` / `def15`），共 60 次运行、30 个完整单元。`off` 与 `def15` 都是同一台机器上的真实 DSH 会话，只是插件开关不同；每对按 ABBA 交错跑，抵消机器负载漂移。

### A. 总量（30 个单元，60 次运行）

| 臂 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
|---|---|---|---|---|---|---|
| `off` | 3434.7 s | 1.00× | 591 244 | 1.00× | 390 | 1.00× |
| `def15`（1.5.0 默认） | 2472.5 s | **0.72×** | 439 708 | **0.74×** | 276 | **0.71×** |

逐格（每个「家族 × 对」算一格，比较该格两臂的整任务墙钟）：**中位 0.64×、均值 0.84×、90 % bootstrap 区间 [0.59, 0.79]、30 格里 24 格更快**。输出 token 逐格中位 0.69×。

两个口径必须一起看：**总和 0.72×** 被少数几格的反向尾部抬起（`csvpipe` p1 2.19×、`debugfix` p3 2.24×、`perfrefactor` p1 2.11×、`ledger` p2 1.50×——这四格的 `def15` 在一次会话里写了更多推理 token，属模型自身的输出长度波动，不是调度器行为），**典型是 0.64×**。只报 0.64× 会盖住这几格真实的变慢，只报 0.72× 会低估典型收益。**不过逐格中位在本文件下一节 W5 复现块里没有复现（0.842×）**，所以 1.5.1 之后对外只用总量口径 0.72–0.73×，中位只作这一批的分布记录。

### B. 分家族（3 对加总，`def15` ÷ `off`：墙钟 / 输出 / 步数）

| 家族 | 墙钟 × | 输出 × | 步数 × |
|---|---|---|---|
| codebase | 0.55× | 0.57× | 0.71× |
| codeimpl | 0.71× | 0.71× | 0.81× |
| csvpipe | 1.12× | 0.96× | 0.83× |
| debugfix | 0.88× | 2.01× | 1.82× |
| ledger | 0.64× | 0.72× | 0.79× |
| migrate | 0.45× | 0.56× | 0.60× |
| perfrefactor | 1.18× | 1.06× | 1.00× |
| shellops | 0.57× | 0.56× | 0.41× |
| shellpipeline | 0.84× | 0.82× | 0.72× |
| stategraph | 0.64× | 0.72× | 0.68× |

省得最多的是「同一条检查被反复重跑」的任务（`migrate`、`shellops`、`codebase`）；`csvpipe`/`debugfix`/`perfrefactor` 这类短任务的收益不稳定，`debugfix` 这轮还多写了 2 倍输出——短任务本来就没有可省的重复验证，提醒本身也是成本。

### C. 质量门禁

隐藏测试在 agent 看不到的副本里执行，30 个单元 × 2 臂 **60/60 全绿**，两侧通过率都是 1.000，没有「基线绿而该臂红」的单元。这是有限任务上的证据，不是质保证明；发布句子照旧：「在已测的有限任务中观察到加速，未观察到这些隐藏断言上的退化。」

### D. 这一轮修掉的测量缺陷（都会让旧数字失真）

1. **运行目录必须在仓库外**：旧批次在 `bench/.runs/` 跑，探索中的会话能读到 `suite/<task>/hidden/test_*.py` 和 `ref/`。现在运行根是 `$TMPDIR/dsh-runs/`，判分在临时副本里做；跑批器扫描产物目录，命中 `hidden/`、`ref/`、`bench/suite/`、`MANIFEST.json` 字样的行标 `leak: true` 并丢弃。
2. **指标不能按字节偏移切**：旧口径按文件偏移读 `~/.dsh/fast-mode-metrics.jsonl`，会把同机器其他会话的行算进来（约 12 步的运行被记成 156 步）。现在按 `session id` 白名单 + 开跑前 `pre_sids` 差集取行。
3. **判分不能在产物目录里做**：`shellops` 臂会把自己的工作目录 `chmod` 成只读，判分脚本往里写隐藏测试时 `EACCES`，整块批次中断（W3 在 31/60 行时崩过一次，已从断点续跑）。现在判分先 `copytree` 到临时目录再跑。
4. **fixture 改动要作废旧摘要**：`bench/MANIFEST.json` 的 `aggregate` 是整套 fixture 的 sha256，跑批器开跑前校验，对不上就拒绝运行。

### E. 本轮顺手查清的一件事：W1 里那格「红」是 fixture 歧义，不是退化

W1 出现过唯一一格门禁违规：`ledger` p1 的 `def15` 红在 `test_round_half_up`。逐条重跑该格的隐藏测试后确认：测试用 `offset: "0.005"`（三位小数）要求 `Decimal` 精确参与四舍五入，而 `PROMPT.txt` 只写了「四舍五入到分」并让 `parse_amount` 对非法输入抛 `ValueError`——「把规则里的 `0.005` 先按分解析」也是一种合理读法，两个实现都能自证。参考实现 22 条断言全绿，说明是**题面欠明确**，不是快速模式降质。已把题面补成「`multiplier`/`offset` 是任意位小数的金额字符串，按 Decimal 原样解析，只有最终结果取两位小数」，fixture 摘要从 `2699cd64…` 变为 `2d7546f8…`；W3 的 ledger 三格用的是旧题面（两臂看到的是同一份，且都没红），W4 起全部用新题面。

## 全量池化复核（2026-10-27）：7 个干净块合池 120 对，总量 **0.80×**，「26–28 %」撤回

> **这一节是历史记录，数字已被后面的 12 块 138 对口径取代**（见文末「合池的自助区间与逐任务稳定性（2026-10-28，12 块 138 对）」：0.8234×、CI95 0.7418–0.9133）。留在这里是为了说明「26–28 %」为什么被撤回，不要引用本节表格里的家族数字。

之前对外说的是「1.5.1 那一块 0.72×、W5 0.73×、W7 0.73×，三块合并 65 个有效单元 0.74×」。那三块是**挑出来的**：它们都恰好是默认臂表现好的块。样本扩大以后，把**所有**有 `off` 锚的干净块一起加总——不再挑块、不再排除任何任务——数字变差，所以 26–28 % 撤回，改成约 **20 %**。

口径与判据：

- 有效格（bench 判据，不是 `green`）：`exit == 0 && steps >= 1 && out > 0 && hasSessionIds && !leak`。`green` 只说明隐藏断言过了，崩溃到没产出的会话也可能 green，所以不能当计量单位。
- 配对：同一 `(tag, task, pair)` 内 `off` 与该臂都有效才算一对；比值一律「该臂 / 同块 `off`」，跨块不混算。
- 排除：`V15`/`TA3`/`TA4` 三块已因 fixture 泄漏 + byte-window 指标混用撤回，且缺 `hasSessionIds` 字段，严格口径下 0 条有效。
- 字段更名：`bench/suite-run.py` 现在写 `hasSessionIds`（该行的 steps/out 是否归属本会话自己的 session id）。历史 result 行里同一个布尔叫 `exact`，所有读取点走 `row.get('hasSessionIds', row.get('exact'))`，旧文件不需要转换；`exact` 从来不是「产物精确性」。
- 复算：`python3 bench/pool.py`（只读 `bench/results/*.jsonl`，不跑 bench、不写文件）。

| 块 | 对数 | 墙钟总量 | 更快 |
|---|---:|---:|---|
| W1 | 11 | 0.711× | 7/11 |
| W3 | 29 | 0.721× | 23/29 |
| W5 | 20 | 0.793× | 11/20 |
| W6 | 19 | 0.931× | 11/19 |
| W7 | 17 | 0.734× | 13/17 |
| W8b | 19 | 0.819× | 13/19 |
| REL16 | 5 | 0.967× | 3/5 |
| **合池** | **120** | **0.798×** | **81/120** |

合池另外两个量：输出 token 总量 **0.806×**、步数总量 **0.800×**、逐格墙钟中位 **0.829×**。也就是整任务总墙钟省 **20 %**，逐格中位省 17 %：**总量口径对外说 20 %，不再说 26–28 %**。

按任务拆开，收益集中在有多份产物/多轮复验的中长任务，短任务打平或更慢——这不是噪声，方向和单格结论一致：

| task | 对数 | 墙钟总量 | 更快 | 读法 |
|---|---:|---:|---|---|
| codeimpl | 13 | **0.608×** | 12/13 | 省 39 %，全表最稳（13 对里 12 对更快） |
| stategraph | 10 | 0.708× | 8/10 | 省 29 % |
| migrate | 13 | 0.748× | 10/13 | 省 25 % |
| shellpipeline | 12 | 0.749× | 10/12 | 省 25 % |
| shellops | 12 | 0.803× | 8/12 | 省 20 % |
| codebase | 11 | 0.808× | 7/11 | 省 19 %，但块间 0.40–1.80，方差大 |
| ledger | 13 | 0.811× | 8/13 | 省 19 % |
| csvpipe | 13 | 0.982× | 8/13 | 打平，收益在噪声内 |
| perfrefactor | 11 | 1.077× | 5/11 | **更慢 8 %** |
| debugfix | 12 | 1.245× | 5/12 | **更慢 25 %**（6 块里 5 块中位 >1） |

两个更慢的任务都是 1–5 步、输出 5k token 以内的小改修：没有可省的重复复验，提醒本身按「注入一次 ≈0.66–0.92 s」计价，小任务上这笔钱收不回来。它们不进「适合场景」的说法，默认不做按任务名的特殊处理（判据是请求形状，不是任务名），文档里点名是为了不让读者在短任务上期待收益。

成本模型（`W8b` 的 `off` 臂，n=19）同步重算，结论没变：`wall = a + b·out + c·steps`，OLS `a=-8.8 s`、`b=13.48 s/1k tok`、`c=1.621 s/step`、R²=0.922；NNLS（`a=0`）`b=13.59`、`c=0.739`、R²=0.921。**输出项占墙钟 ~97–98 %，步数项 ~3 %**。跨 task 外推会高估 1.5–2.1×（各 task 自己的每 1k token 价格是 6.75–9.60 s，W8b 是 13.89 s），所以只能读结构比例，不能拿系数给别的任务定价。

因此上限这条链重新写一遍，避免读者把 40 % 当待办：要省 40 % 墙钟就得省约 40 % 输出 token（输出占 97 %）；现在合池实测输出 0.806×，已经兑现了「省输出 → 省墙钟」的大部分可用余量；剩下能动的（重复复验 6 %、重复写文件 0.8 %）即使全压掉也只值 2–6 %。**0.67× 在「不动 `reasoningEffort`、不加请求字段」的约束内不可达**，这一条没有变，见下一节「0.67× 在现有约束下的可达性」。

另有两处副产品值得记：`pp16`（`preferPatch`）在 `W8b` 上账面最好（19 对总量 0.70×、中位 0.66×、13/19 更快），但只跑过一块，样本面太窄；`open15` 在 `codebase`/`csvpipe`/`debugfix` 上明显强于 `def15`（中位 0.46/0.47/0.48）却在 `shellops`/`shellpipeline` 上更慢，说明「按形状分流」比「全局换臂」更合理，但它同样只跑过一块。

**`pp16` 的结论已经出来，是否定的（PPX + PPX2，同块对照，n=12）。** 过程值得记：PPX 那一块（4 对，`codeimpl`/`migrate`/`debugfix`/`perfrefactor`）给出 0.426×，看着像强机制；把同样的臂在 PPX2 上复测 8 对得到 **1.193×**（比 `off` 还慢），两坑合起来 **1.035×**、6/12 更快、输出 1.056× —— 也就是说 0.426× 是 4 个格子的运气，不是机制。同一块里 `def15` 是 0.911×（PPX2）和 0.850×（两坑合并），方向一致但幅度远小于 PPX 给人的印象。`preferPatch` 因此保持默认关闭，不因为单块好账面就翻默认。

## 复现块 W5（2026-10-26）：三臂同块对照，总量复现 0.73×，逐格中位不复现

这一块是为了两件事：① 用另一批样本复算 1.5.1 的 `def15` 总量；② 在同一块内直接对照 `bv15`（`batchVerify: true`），因为 W4 只有单臂对基线，无法排除那一轮基线本身偏慢。

实验：10 家族 × 2 对 × 3 臂（`off` / `def15` / `bv15`），60 次运行；`shellpipeline` p1 的 `bv15` 一行因读到 fixture 被剔除（见 `bench/README.md` 协议第 12 条），三臂齐全的完整单元 19 个。fixture 摘要 `2d7546f8…`，与 1.5.1 同。

### A. 总量（19 个完整单元）

| 臂 | 总墙钟 | × | 总输出 × | 总步数 × | 逐格中位 | 更快格数 |
|---|---|---|---|---|---|---|
| `off` | 1855.5 s | 1.00× | 1.00× | 1.00× | — | — |
| `def15`（默认） | 1357.7 s | **0.73×** | 0.77× | 0.89× | 0.842× | 11/19 |
| `bv15` | 1550.6 s | 0.84× | 0.84× | 0.83× | 0.800× | 10/19 |

> **撤回（2026-10-26，W8b 之后）**：`bv15` 这一行不是 `batchVerify` 的效果。逐会话检索下发文案「files changed since the last run」，20 个会话里命中 **0**——这个提醒在整个块里一次都没到过模型。根因是同一步里调度结果被多个钩子读取，只有其中一个会投递提醒，只读钩子把一次性提醒标成「已用」却没显示（详见 W8b 一节的第 3、4 条）。所以 0.84× 测到的是「与默认基本等价 + 噪声」。`batchVerify` 因此回到**未测**，不是被证伪；默认仍保持 `false`，修复后重测。

### B. 三条结论

1. **总量复现**：`def15` 在这块里是 0.73×，与 1.5.1 的 0.72× 一致。两块样本不同（30 格与 19 格、fixture 摘要不同），总量口径仍是 0.72–0.73×，这是目前唯一稳定的速度声明。
2. **`bv15` 不能读成「机制变慢」，也还不能读成任何结论**（见上表下的撤回说明）：它在同一块里比 `def15` 慢（0.84× 对 0.73×），但那一臂实际一条提醒都没发出去。W4 筛出的 0.76× 既没被复现、也没被否定，`batchVerify` 回到未测。
3. **逐格中位不是稳定声明，撤回「典型一格约快 36%」**：1.5.1 那批的逐格中位是 0.643×（90 % CI [0.586, 0.791]），这一批是 0.842×（CI [0.717, 1.315]）；两批的逐格均值都接近 1（0.841× / 1.001×，CI 都跨过 1）。收益集中在少数长格——本批 `csvpipe` p2 0.29×（`off` 227.0 s / 21 步 → 8 步）、`ledger` p1 0.30×（217.2 s / 30 步 → 7 步）、`stategraph` p1 0.36×、`codeimpl` p2 0.44×、`migrate` p2 0.58×——同时有 6 格明显更慢（`debugfix` p1 2.58×、`shellops` p1 1.56×、`csvpipe` p1 1.52×、`codebase` p2 1.49×、`debugfix` p2 1.39×、`shellops` p2 1.33×）。也就是说：快速模式降低的是「一次会话越走越远」的频率（步数 390→276、210→186），不是每一格都变快；短任务本来没有可省的重复验证，那几格就是提醒本身的成本。对外的速度承诺只用总量口径 0.72–0.73×。
4. **分家族同样不稳**：1.5.1 里 `shellops` 是 0.57×，本批是 1.43×；`perfrefactor` 1.18× 对 0.80×。族级数字只作分布说明，不当承诺。

### C. 质量门禁

`off` 20/20、`def15` 20/20、`bv15` 19/19 全绿，0 红，没有「基线绿而该臂红」的单元。泄漏 1 行，已点名剔除。

### D. 泄漏事件与治具加固

`shellpipeline` p1 的 `bv15`（墙钟 109.03 s、29 步、17 603 输出 token、隐藏测试全绿）在会话里从临时运行目录一路 `find` 到仓库，读了 `hidden/test_shellpipeline.py` 与 `ref/`，随后还 `grep` 了整个 `bench/suite/`。这一行按泄漏剔除，不计入任何比值；该格因此少一个 `bv15` 样本。

治具随之加固（协议第 12 条）：跑批器开跑前把每个任务的 `hidden/`、`ref/` 置为不可读（题面与测试模块此时已读进内存，`verify()` 从内存把测试写进临时副本判分，`seed` 复制不受影响），块结束或收到 `SIGINT`/`SIGTERM` 时恢复；仓库内 `bench/.runs/` 已删除并写进 `.gitignore`；仓库外两处历史批次归档一并置为不可读。泄漏哨兵保留，任何一块出现泄漏行都必须点名。

## 筛查块 W6（2026-10-26，**结论已由 W7 证伪**）：`openBatch` 曾以 0.77× 快过默认的 0.93×

> **后记（W7）**：这一块的 0.77× 没有复现。同一块内重测（W7），`open15` 是 0.91×、`def15` 是 0.73×，两者直接头对头 `open15/def15` 逐格均值 **1.136×**（90 % CI [0.932, 1.343]）、中位 0.986×、20 格里只有 10 格更快。按「基线绿而该臂红即淘汰、复现不成立即淘汰」的规则，`openBatch` **不晋升默认**，`src/config.js` 里默认仍是 `false`。以下 W6 的数字只作筛查记录，不当速度声明。

`openBatch` 是最后一个没在干净批次里测过的默认关闭机制：每轮只在第一步发一次「把已知要做的独立调用放进同一块、shell 命令用 `&&` 串起来」，之后不再插入任何内容（`src/auto.js` 的 `open-batch` 分支）。旧批次里它被记成 1.03× 并有一次 325 步失控，但那个批次整体撤回，所以重测。

实验：10 家族 × 2 对 × 3 臂（`off` / `def15` / `open15` = `openBatch: true`），60 次运行；`stategraph` p2 的 `off` 一行读到 fixture 被剔除（见协议第 12 条），三臂齐全的完整单元 19 个。

### A. 总量（19 个完整单元）

| 臂 | 总墙钟 | × | 总输出 × | 总步数 × | 逐格中位 | 更快格数 | 最长步数 |
|---|---|---|---|---|---|---|---|
| `off` | 2144.6 s | 1.00× | 1.00× | 1.00× | — | — | 35 |
| `def15`（默认） | 1997.3 s | 0.93× | 0.96× | 0.96× | 0.962× | 11/19 | 38 |
| `open15` | 1654.0 s | **0.77×** | 0.84× | **0.64×** | 0.662× | 11/19 | **23** |

逐格均值的 90 % 区间：`def15` 1.069× [0.885, 1.265]（跨 1，和本块总量 0.93× 一致——这一块里默认臂几乎没省），`open15` 0.817× [0.673, 0.961]（**不跨 1**，是至今唯一一个逐格均值区间也排除「打平」的臂）。p50 墙钟 78.4 s 对 101.0 s / 100.8 s。

### B. 结论与边界

1. `open15` 在这一块里同时赢总量（0.77× 对 0.93×）、中位（0.662× 对 0.962×）与最长尾（23 步对 38 步），且门禁 20/20 全绿、0 红。旧批次「1.03× 墙钟 + 325 步失控」没有复现，那种失控来自被撤回的测量口径。
2. 机制仍在约束内：一次开局提醒，不按步数结束回合、不改完成判定、不省复验（门禁全绿）、不动 `reasoningEffort`、不加请求体字段。
3. **这只是筛查**：19 个单元、2 对、单块。`def15` 自己的总量比在两块之间从 0.73× 摆到 0.93×，说明块间波动很大；`open15` 的 0.77× 必须再拿一块独立样本同块复测（W7）才算数，不能凭这一块改默认。
4. 分家族：`csvpipe` 0.36×、`codebase` 0.42×、`debugfix` 0.76×、`perfrefactor` 0.87× 都有收益，`stategraph` 1.06×、`shellops` 0.95× 基本打平；没有哪一族被显著拖慢。

## 复现块 W7（2026-10-26）：默认臂 0.73× 复现，`openBatch` 被证伪；7 行运行崩溃、3 个单元作废

这一块做三件事：① 再拿一批独立样本复算 `def15` 的总量（第三块复现）；② 在同块内复测 `openBatch`（W6 的 0.77×）；③ 把「运行崩溃」和「机制变慢」分开——旧口径把两者算进同一个分母，会让一块里的基础设施故障看起来像门禁红灯。

实验：10 家族 × 2 对 × 3 臂（`off` / `def15` / `open15`），60 次运行；`stategraph` p2 三臂全部、`codebase` p2 的 `def15`+`open15`、`shellpipeline` p2 的 `off`+`def15` 落在 `exit=1 / 1 步 / 0 输出 / ~80 s` 的会话启动崩溃上，20 个单元里三臂齐全且可用的 **17 个**进入统计，崩溃的 3 个单元点名作废（不是红灯，也不是质量结论）。0 行泄漏。fixture 摘要 `2d7546f8…`，与 1.5.1 同。

### A. 总量（17 个有效单元）

| 臂 | 总墙钟 | × | 总输出 × | 总步数 × | 逐格中位 | 更快格数 | 最长步数 |
|---|---|---|---|---|---|---|---|
| `off` | 3724.4 s | 1.00× | 1.00× | 1.00× | — | — | 36 |
| `def15`（默认） | 2734.8 s | **0.73×** | 0.75× | 0.59× | 0.709× | 13/17 | 17 |
| `open15` | 3390.2 s | 0.91× | 0.84× | 0.67× | 0.890× | 12/17 | 29 |

逐格均值的 90 % 区间：`def15` **0.816× [0.692, 0.948]**（不跨 1，这是三块复现里第一块总量与逐格均值同时排除「打平」的），`open15` 0.837× [0.677, 1.008]。

### B. 结论

1. **默认臂第三块复现**：0.73×，与 1.5.1 的 0.72×、W5 的 0.73× 一致。三块复现（W3+W5+W7）合并 65 个有效单元，总墙钟 **0.742×**、总输出 **0.758×**。
2. **`openBatch` 淘汰**：W6 的 0.77× 不成立。W7 里它对默认头对头均值 1.136×、中位 0.986×、只有 10/20 格更快；三臂同块的绝对量也是 0.91× 对 0.73×。W6 那一块 `def15` 自己就摆到 0.93×，说明 W6 的 0.77× 是筛查噪声，不是机制。默认保持 `openBatch: false`。
3. **步数压到 0.59× 但输出只到 0.75×**：`def15` 把步数从 236 压到 138（最长一格 36 步 → 17 步），每步输出反而变长（0.75 / 0.59 ≈ 1.27×）——少掉的步数里有一部分是把工作并进了更长的步骤。这解释了为什么墙钟停在 0.73× 而不是跟着步数到 0.6×：墙钟的 93 % 是解码时间，而解码量由输出 token 决定，步数只是它的代理。
4. **分家族**：`codebase`（有效单元只剩 1 个）1.59×、`perfrefactor` 0.88×、`ledger` 0.83×、`shellpipeline` 0.83×、`debugfix` 0.85×、`csvpipe` 0.70×、`migrate` 0.71×、`shellops` 0.68×、`codeimpl` 0.55×、`stategraph` 0.51×。族级数字在块间不稳（W5 里 `shellops` 1.43×、这里 0.68×），只作分布说明。

### C. 质量门禁

`off` 17/17、`def15` 17/17、`open15` 17/17 全绿，0 红，没有「基线绿而该臂红」的有效单元。作废的 3 个单元不是红：它们三臂（或其中两臂）根本没有产出会话，判分无从谈起。

### D. 崩溃行与做法修正

7 行崩溃（`exit=1`、1 步、0 输出、墙钟 ≈ 80 s）都是 DSH 会话启动阶段退出，工具没跑过、产物目录为空，和机制无关；重跑其中一部分仍是同样的行。旧口径会把这类行当作「输出 0 的极快运行」或「0 步的红」，两种都会污染比值和门禁，所以从这一块起把一个单元的**有效性**写进判据：`exit == 0 && steps >= 1 && 输出 > 0 && 判分完成 && 未泄漏`，缺一条就整格作废并点名。1.5.1 那一块 30 个单元里有 1 个同类崩溃（`debugfix` p1 的 `off`），所以「60/60 全绿」的准确说法是**有效单元 29/29 全绿、另 1 个单元因运行崩溃作废**；W5、W7 同理（19/19、17/17）。

## 对照块 W8b（2026-10-26）：`def15` 逐格中位 0.717× 复现；`preferPatch` 臂因提醒未下发而作废

这一块原本要测第四个杠杆 `preferPatch`（大整文件写入之后，提醒下一步只发改动的行）。60 次运行、20 个单元（10 家族 × 2 对 × `off` / `def15` / `pp16`），其中 `ledger` p2 的 `off` 行泄漏（读了隐藏测试，输出 53 133 token），整格作废，**59 个有效单元，有效单元零红**。fixture 摘要与 1.5.1 同。

### A. 结果（59 个有效单元）

| 臂 | n | 总墙钟 | × `off` | 总输出 token | 总步数 | 最长步数 |
|---|---|---|---|---|---|---|
| `off` | 19 | 5138.5 s | 1.00× | 369 845 | 197 | 25 |
| `def15`（默认） | 20 | 4798.7 s | **0.934×** | 343 042 | 215 | 24 |
| `pp16`（`preferPatch`） | 20 | 4129.3 s | 0.804× | 294 156 | 197 | 26 |

逐格稳健统计（每格取该格两臂同一对任务整任务墙钟之比）：`def15/off` **中位 0.717×、几何均值 0.819×、19 格里 13 格更快**，输出逐格中位 0.823×，步数逐格中位 **1.000×**。

### B. 结论

1. **默认臂逐格中位第四块复现**：0.717×，与 1.5.1 的 0.64×、W5 的 0.842×、W7 的 0.709× 同量级。但本块 `def15` 的**总量只有 0.934×**——这一批的反向尾部更重（少数几格 `def15` 推理更长），所以总量与逐格中位要一起看，不能只报一个。对外口径仍是三块合并（W3+W5+W7，65 个有效单元）的总墙钟 **0.742×**；W8b 这一块总量偏软，只作分布与门禁记录。
2. **本块 `def15` 步数没降（逐格中位 1.000×），省下的时间来自输出长度**：与 W7「步数 0.59×、输出 0.75×」互补，说明两条路都能省墙钟，具体走哪条取决于任务形状。
3. **`pp16` 臂作废，不计入任何结论**：`preferPatch` 在这次运行里**一次提醒都没有下发**（20 个会话里检索下发文案 `that step sent a whole file` 命中 0）。根因是 liveness 缺陷——调度结果在一个步里被多个钩子读取，其中只有一个会真正投递提醒，其余只读钩子也调用了同一个「一次性」提醒函数，把提醒标成已用却从未显示给模型。它真实的行为差异只是占掉那一步的提醒槽位、把该步的 late-pressure 提醒挤掉，所以 `pp16` 对 `def15` 的中位 0.942× 是噪声，**既不能读成 `preferPatch` 的效果，也不能读成它的反证**。
4. 修复见 `3cb94e4`：只读调用改为 `{emit: false}`，提醒只在真正投递时消耗；回归测试与单格验证（同一格修复前 0 次下发、修复后 1 次下发）都过。修复后的筛选块 W9 用 `def15` / `pp16` 两臂在 5 个写入型家族上重测。

### C. 机制余量预估（在 W9 跑之前登记，用来判断「还差多少」）

W9 会给出实测量，但先把「最多能省多少」算出来，免得把噪声当效果：

1. **墙钟几乎全是输出 token**。用本块 `off` 臂 19 个有效格回归 `墙钟 = a + b·输出 + c·步数`：`b = 13.5 ms/token`、`c = 1.62 s/步`、`a = −8.8 s`；输出项合计 4986 s / 总计 5138 s（97 %），步数项 319 s（6 %），逐格残差中位 18 %。所以「墙钟比值 ≈ 输出 token 比值」是可靠近似，任何想省掉的秒数都必须来自输出 token。
2. **输出里约 23 % 是工具调用参数**。对 134 个 `def15` 有效格的会话做统计：`tool/call` 参数字符数 ÷ 3.6 再除以该格输出 token，中位 **23.1 %**（均值 25.9 %）。剩下约 77 % 是推理与正文。
3. **这部分参数基本不可压**。15 个 `def15` 会话里 153 次整文件 `write` 合计 716 468 字符；其中**写到本会话里已经写过的同一路径**（即真正的整文件重写浪费）只有 5 876 字符，占 0.8 %。也就是说参数是在**创建交付物本身**，不是重复劳动。据此预登记：`preferPatch` 的上限约等于输出的 0.2 %，W9 的预期是「与默认无差异」，若出现更大的差就是噪声。
4. **精确重复的工具调用也很少**：373 次调用里同名同参重复 8 次（2.1 %），「别重复读同一文件」这类机制同样没有余量。
5. **重复验证这块已测完，方向不成立**：1534 次调用里 187 次是跑测试/校验类命令（12.2 %），145 个会话里 25 个出现 ≥3 次，合并这类步骤正是 `batchVerify` 的目标。修好投递后 BV8 的 16 对给出合并 1.188×、步数 1.229×（见「`batchVerify` 复测块 BV8」）——提醒没有把验证合并掉，反而多出往返。这条线关闭。
6. **因此 0.67× 的算术约束**：三块合并 `def15` 输出 0.758×，其中参数项 0.175×、推理项 0.583×。要墙钟 0.67× 就要输出 0.67×；参数项压不动，只能让推理再降 15 %（0.583 → 0.495）。推理量与步数同步（W7 步数 0.585×），所以这条路等价于**把步数从 0.585× 再降到约 0.50×**。`reasoningEffort` 不动、不加请求字段的前提下，唯一还开着的口子是「让步数在长回合里继续下降」，而 BV8 证明「提醒合并验证」是反向的（步数上升），所以这条算术约束目前没有已验证的实现路径。

### D. W9（`preferPatch` 单格复核）：4 个单元后中止，只作「提醒确实下发」的凭证

W8b 之后用 `def15` / `pp16` 在 5 个写入型家族上重测 `preferPatch`，跑到 4 个有效单元时中止（改用更有余量的 `batchVerify` 臂，见 C 第 5 条）。这 4 格全部有效、全绿：

| 格 | `def15` | `pp16` | 比值 |
|---|---|---|---|
| `csvpipe` p1 | 334 s / 9 步 / 24 900 tok | 343 s / 12 步 / 30 607 tok | 1.03× |
| `ledger` p1 | 151 s / 7 步 / 15 341 tok | 577 s / 6 步 / 33 534 tok | 3.82× |

两条结论，都不涉及效果大小：

1. **修复后提醒确实到过模型**：`csvpipe` p1 的 `pp16` 会话里检索到下发文案 `that step sent a whole file` 命中 1 次。W8b 的 20/20 零下发就此失效，`preferPatch` 从「因 liveness 缺陷作废」变成「已下发、效果待测」。
2. **4 格不足以下任何比值结论**，尤其 `ledger` 那格 `pp16` 输出翻倍（15 k → 34 k tok），属于单格长尾。按 C 第 3 条的预登记，这个机制的余量上限约等于输出的 0.2 %，所以在拿到整块之前**不许把上面两个比值当效果**。

## 筛选块 W10（2026-10-26）：`workspaceMap` 首测，10 个单元全绿，逐格中位 0.565×、总量 0.80×，两格长尾反向

`workspaceMap` 是 `def15` 之上加一个开关的臂（`bench/arms/map15.yml`）：会话第一次工具调用之前，harness 自己把工作目录走一遍（深度 2、≤60 条、跳 `node_modules`/`.git`/`dist`，正文 ≤1.4 kB），把「相对路径 + 字节数」和看得见的测试入口作为一条通知交给模型。它替代的正是 W8b 里最集中的那类调用——`ls`/`find`/`cat`/`grep` 占 221 次调用的 35.7 %，前两次调用里 40 % 是它。

### A. 总量（10 个完整单元，20 次运行）

| 臂 | 墙钟 | 墙钟比 | 输出 token | 输出比 | 步数 | 步数比 |
|---|---|---|---|---|---|---|
| `def15` | 2181.8 s | 1.00 | 112 597 | 1.00 | 72 | 1.00 |
| `map15` | 1755.3 s | **0.80×** | 101 388 | **0.90×** | 82 | 1.14× |

逐格墙钟中位 **0.565×**，10 格里 7 格更快；p50 从 101.2 s 降到 122.7 s（反向尾部更重），p90 从 425.3 s 降到 314.3 s。

### B. 分家族与逐格（`map15` ÷ `def15`）

| 家族 | 对数 | 墙钟 / 输出 / 步数（2 对加总口径） |
|---|---|---|
| `migrate` | 2 | 0.49 / 0.58 / 1.00 |
| `debugfix` | 2 | 0.58 / 0.60 / 0.77 |
| `perfrefactor` | 2 | 0.78 / 0.71 / 0.90 |
| `stategraph` | 2 | 1.11 / 1.30 / 1.50 |
| `codebase` | 2 | 1.18 / 1.34 / 1.42 |

| 格 | `def15` 墙钟/步数/输出 | `map15` 墙钟/步数/输出 | 墙钟比 |
|---|---|---|---|
| `codebase` p1 | 85.1 s / 7 / 5 570 | 47.8 s / 6 / 3 002 | 0.56× |
| `codebase` p2 | 89.1 s / 5 / 3 171 | 157.4 s / 11 / 8 720 | 1.77× |
| `debugfix` p1 | 54.5 s / 8 / 3 343 | 47.3 s / 4 / 2 575 | 0.87× |
| `debugfix` p2 | 113.3 s / 5 / 6 735 | 50.1 s / 6 / 3 498 | 0.44× |
| `migrate` p1 | 389.6 s / 9 / 22 211 | 156.1 s / 7 / 9 485 | 0.40× |
| `migrate` p2 | 483.9 s / 10 / 23 696 | 274.8 s / 12 / 17 328 | 0.57× |
| `perfrefactor` p1 | 81.7 s / 5 / 3 885 | 89.3 s / 4 / 4 119 | 1.09× |
| `perfrefactor` p2 | 83.3 s / 5 / 5 463 | 39.7 s / 5 / 2 502 | 0.48× |
| `stategraph` p1 | 418.7 s / 6 / 20 820 | 222.5 s / 7 / 17 801 | 0.53× |
| `stategraph` p2 | 382.5 s / 12 / 17 703 | 670.2 s / 20 / 32 358 | 1.75× |

### C. 质量门禁与投递

- **10/10 次运行隐藏测试全绿，零泄漏，零崩溃**；两臂通过率都是 1.000，没有「基线绿而该臂红」。
- **投递逐格核对**：10 个 `map15` 会话各命中 `Auto map: one inventory` **1 次**，10 个 `def15` 会话各 **0 次**——因此臂与默认确实不是同一个东西，差值不是「提醒没发」造成的噪声。
- 清单本身很小：`codeimpl` 那格实测 291 字符（fixture 只一个文件），深度与条数上限在 `src/map.js`。

### D. 结论与下一步

1. **主结论：在 7/10 格上更快，逐格中位 0.565×，总量 0.80×，零红**。省的地方对得上机制：省掉的探索步让输出整体降到 0.90×，`migrate`/`debugfix` 这种「先找文件再改」的任务收益最大。
2. **两格反向长尾必须点名**：`codebase` p2（1.77×，步数 5→11、输出 3.2 k→8.7 k）与 `stategraph` p2（1.75×，步数 12→20、输出 17.7 k→32.4 k）。两格都是 `map15` 变长而不是变短，说明清单本身不保证省——重任务里它可能让模型更早展开工作范围。**这两格不足以否定机制，但足以否决「只报中位」**。
3. **不晋升为默认**：10 个单元、一次块，且总量只有 0.80×，不满足「独立复现」的门槛。按协议开 W11 复现块（7 家族 × 2 对 = 14 个单元，含 `codeimpl`/`csvpipe` 这类短任务用来查固定成本），复现通过才在 1.5.2 里把 `workspaceMap` 设成默认。**W11 没复现（合并 23 个单元 0.927×，尾部更慢），结论见下一节：保持默认关闭。**

## 复现块 W11（2026-10-26）：`workspaceMap` 没复现 W10 的收益，合并 23 个单元 0.927×，**不进默认**

W10 的 0.565× 逐格中位需要独立复现才能当结论。W11 用 7 个家族 × 2 对（14 个单元，含 `codeimpl`/`csvpipe` 这类短任务查固定成本）同块对照 `def15` 与 `map15`。

### A. 总量（13 个有效单元，26 次有效运行）

| 臂 | 墙钟 | 墙钟比 | 输出 token | 输出比 | 步数 | 步数比 |
|---|---|---|---|---|---|---|
| `def15` | 2935.2 s | 1.00 | 162 971 | 1.00 | 99 | 1.00 |
| `map15` | 2988.6 s | **1.018×** | 145 584 | **0.893×** | 106 | 1.071× |

去掉的 1 个单元是 `csvpipe` p1：`def15` 那次运行 `steps=0 / out=0 / wall=806.97`，是会话空转而不是「极快」，按判据作废，`map15` 那半格即使绿也不成对。

### B. 分家族（`map15` ÷ `def15`，2 对加总口径；`csvpipe` 只剩 1 对）

| 家族 | 墙钟 / 输出 / 步数 |
|---|---|
| `debugfix` | 0.47 / 0.53 / 0.79 |
| `migrate` | 0.49 / 0.42 / 0.83 |
| `codebase` | 0.65 / 0.74 / 1.20 |
| `perfrefactor` | 1.30 / 1.18 / 0.90 |
| `codeimpl` | 1.32 / 1.40 / 1.29 |
| `stategraph` | 1.50 / 1.25 / 1.26 |

反向 ≥1.3× 的格点名：`stategraph` p1 1.65×、`perfrefactor` p1 1.58×、`codeimpl` p2 1.40×、`stategraph` p2 1.40×。

### C. 门禁与投递

- 13/13 有效单元、26/26 次运行隐藏测试全绿，零泄漏、零崩溃；两臂 pass rate 都是 1.000。
- 投递按 `bench/audit-delivery.py` 逐格复核：`map15` 每格恰好 1 条 `Auto map:`，`def15` 每格 0 条。

### D. 合并读数与结论

W10（10 个有效单元）+ W11（13 个有效单元）合并，23 个单元：

| 口径 | 值 |
|---|---|
| 总墙钟 | **0.927×** |
| 总输出 token | **0.896×** |
| 总步数 | **1.099×** |
| 逐格墙钟中位 | **0.791×** |
| 更快的格 | 13 / 23 |

1. **机制方向被证实一半**：输出 token 确实降了 10 %（W10 0.90×、W11 0.89×，两块一致），说明「先给一份目录清单」确实省掉了模型自己找文件的 token。
2. **另一半没兑现**：步数两轮都升（W11 1.071×），净墙钟因此只剩 0.927×；而分母是 `def15` 而非 `off`，对外口径不能拿这 7 % 当卖点。
3. **否决点是尾部，不是中位**：`map15` 的 p90 558.6 s、最慢 713.8 s，`def15` 是 369.1 s / 510.3 s。也就是说开关打开后**一部分长任务会更慢**，最长格慢了 40 %。W10 与 W11 都出现同一形状（`stategraph` 两轮都反向），不是单次噪声。
4. **因此不晋升默认**：按「长任务不能变慢 + 需要两轮同向复现」的门槛，`workspaceMap` 保持默认关闭，作为旋钮发布并附上这两块的全部数字。1.5.2 的默认行为与 1.5.1 相同。

## 已排除的方向（截至 1.5.2，按成本模型排序）

| 方向 | 实测 | 判据 | 剩余余量依据 |
|---|---|---|---|
| `outputBudget`/`bud8` | ~~0.920×~~ **作废** | 未测量：机制没接线（1.5.10 才修） | 正文只占输出 6 % |
| `fanoutWidth: 12`/`wide15` | 1.148× | 慢于默认，淘汰 | 并发不减推理 |
| `openBatch`/`open15` | 0.837×、头对头 1.136× | 未复现，淘汰 | — |
| `preferPatch`/`pp16` | 未测出效果 | 余量上限 ≈ 输出 0.2 % | 153 次整文件写入里重复写同路径只占 0.8 % |
| 工具结果截断 | 未开跑，已测余量 | 无余量 | 236 条工具结果中位 1.2 kB、最大 18 kB、单会话中位 26 kB |
| 丢弃旧工具结果（上下文裁剪） | 未实现，先测余量 | 无余量 | 193 步里 `corr(上下文 totalTokens, 每步输出)` = **0.067**，每步输出中位 663 token 与上下文长度基本无关 |
| `workspaceMap` | 0.927×（23 单元） | 尾部变慢，不进默认 | 输出 −10 % 但步数 +10 % |
| `batchVerify` | 合并 16 对 **1.188×** | 淘汰（BV8，2026-10-28） | 下发 8/8 治疗会话；步数 1.229× 反而变多，codeimpl 1.478×、shellpipeline 0.956× |

最后一行是这一轮算清的账：`batchVerify` 合并的是「重复验证的步」，而步数项只占 1.62 s × 319 步 ≈ 6 % 墙钟（输出项 97 %）。即使把它压到零，总量也只能到 0.94× 左右，**它不是通往 0.67× 的路**。

## 0.67× 在现有约束下的可达性（结论）

三块合并（W3+W5+W7，65 个有效单元）默认臂 0.742× 墙钟 / 0.758× 输出 / 0.585× 步数。要 0.67× 就必须再省 10 % 墙钟，而墙钟 97 % 来自输出 token；输出里 23 % 是不可压的工具参数（重复写只占 0.8 %、重复调用 2.1 %），工具结果本身很小，剩下的 71 % 是模型自己的推理。**在不改 `reasoningEffort`、不加请求字段、不砍完成判定与必要复验的前提下，harness 侧能动的量已经测完：目录清单 −10 % 输出但 +10 % 步数（净 0.93×）、重复验证 ≤6 % 墙钟、并发与预算类旋钮全部反向。** 因此 1.5.2 的诚实口径是：**已测范围内 0.72×–0.74× 是这套约束的地板，0.67× 需要放宽约束（`reasoningEffort` 或请求侧字段），本版不做。** 下一步若允许放宽，最小改动是测 `reasoningEffort` 低一档在同一套门禁下的表现；这需要用户明确授权。

## 1.5.0 重基线（2026-10-25，**已撤回，仅作历史**）：9 家族 54 个单元，总墙钟 0.86×，总输出 0.95×

> **撤回（2026-10-26）**：本节及 TA3/TA4 三块批次的每一步数字都作废，原因是两个测量缺陷：
> ① 运行目录在仓库内（`bench/.runs/`），探索中的会话能读到 `../../suite/<task>/hidden/test_*.py` 与 `ref/`，30 个 SG 会话里确认 4 个读到了隐藏测试或参考实现；
> ② 指标按字节偏移切片，把同机器其他会话的行算进来（一次约 12 步的运行被记成 156 步）。
> 因此 `0.86× / 0.95× / 0.85×` 与所有步数比不再作为声明；隔离后的干净复测见下面的「1.5.1」。本节其余内容保留，只作为当时口径的记录。

口径换了一次，这是本节和 1.4.4 及更早各节不能直接比数字的原因：**只看整任务完成后的总量**——每个「任务 × 对」把三臂都跑完才算一个单元，单元内比较总墙钟、总输出 token、总步数，再把 54 个单元（9 家族 × 6 对）加总。隐藏测试是门禁（runner 事后执行，agent 看不到测试文件），任何一臂红即该单元作废。

> 本文件 1.5.0 的每个数字都可以在仓库里复算：fixture、跑批器、汇总器与三块原始结果都在 [`bench/`](bench/README.md)。`python3 bench/report.py --files bench/results/suite-W3.jsonl --baseline off` 会原样打出下面的总量表、分家族表与尾部分布，并逐条列出红行与「基线绿而该臂红」的门禁违规。改任何 fixture 都会让 `bench/MANIFEST.json` 的摘要对不上，跑批器会拒绝开跑。

### A. 三臂总量（54 个单元，162 次运行）

| 臂 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
|---|---|---|---|---|---|---|
| `off` | 7142.3 s | 1.00× | 1 196 707 | 1.00× | 1155 | 1.00× |
| `def15`（1.5.0 默认：late 文风纪律） | 6119.2 s | **0.86×** | 1 140 982 | **0.95×** | 983 | **0.85×** |
| `fl10`（退役默认：late + 第 10 步收尾线） | 5579.4 s | **0.78×** | 939 664 | **0.79×** | 831 | **0.72×** |

总体结论：默认配置在 9 个家族上省下 14 % 墙钟、5 % 输出 token、15 % 步数；`fl10` 省得更多（22 % / 21 % / 28 %），但代价见 D 节。

### B. 分家族（总墙钟 × / 总输出 token ×，相对 `off`）

| 家族 | 类型 | n | `off` 基线 | `def15` 墙钟/输出 | `fl10` 墙钟/输出 |
|---|---|---|---|---|---|
| `csvpipe` | 数据管道（读多写少） | 6 | 1310.3 s / 212 380 t | 0.79× / 0.85× | 0.55× / 0.57× |
| `debugfix` | 修 bug（小步） | 6 | 331.7 s / 65 562 t | 0.56× / 0.51× | 0.38× / 0.30× |
| `ledger` | 从零实现（深） | 6 | 1317.3 s / 254 050 t | 0.73× / 0.67× | 0.71× / 0.61× |
| `stategraph` | 状态机重写 | 6 | 1034.9 s / 157 600 t | 0.82× / 0.92× | 0.94× / 1.08× |
| `shellpipeline` | 终端流水线 | 6 | 780.3 s / 133 252 t | 0.87× / 0.91× | 0.72× / 0.77× |
| `shellops` | 终端运维（长命令） | 6 | 688.0 s / 100 152 t | 0.85× / 1.39× | 1.01× / 1.24× |
| `migrate` | 批量迁移（写多） | 6 | 878.0 s / 129 561 t | 1.02× / 1.34× | 0.78× / 0.78× |
| `codeimpl` | 代码实现（写多） | 6 | 631.8 s / 114 388 t | 1.09× / 1.13× | 1.03× / 0.91× |
| `perfrefactor` | 性能重构（改既有码） | 6 | 170.0 s / 29 762 t | 1.33× / 1.64× | 1.45× / 1.43× |

读多写少和"从零写"两类赢得很稳（csvpipe、debugfix、ledger、shellpipeline、stategraph 五个家族墙钟 0.55–0.87×）；**三个写文件型家族（migrate、codeimpl、perfrefactor）没有稳定加速，默认臂甚至更慢**（1.02×–1.33×）。这一点由下面的四臂消融单独确认，不靠总量掩盖。

### C. 尾部（同一批单元，按单次运行）

| 臂 | p50 | p90 | max | 单次最大步数 |
|---|---|---|---|---|
| `off` | 122.2 s | 263.3 s | 326.8 s | 137 |
| `def15` | 105.5 s | 201.6 s | 292.7 s | 63 |
| `fl10` | 92.1 s | 178.7 s | 277.1 s | 62 |

尾部也被压住了：p90 从 263 s 降到 202 s（`def15`）/179 s（`fl10`），单次最长运行从 137 步降到 63/62 步。**`off` 的 p90 与 max 明显被两个自己打转的基线运行撑着**（见 G 节）。

### D. 被否掉的默认：`fl10` 仍然更快，但不作默认

`fl10` 的三项总量都优于 `def15`（0.78× vs 0.86×、0.79× vs 0.95×、0.72× vs 0.85×），可它在**写文件型家族上是不稳定的**：`perfrefactor` 1.45×、`shellops` 1.01×、`migrate` 0.78×、`codeimpl` 1.03×。1.4.4 的「中位 1.86×」是 14 对逐对中位数，混合语料以读多重任务为主；换成"整任务总量 + 写文件家族"的口径后，它省的是**重复验证的输出**，而在"测试通过后又改代码"的任务上压掉的正是必要复验。因此 1.5.0 的默认是 `def15`，`fl10` 只保留为消融臂——**1.4.4 的 1.86× 不再作为 1.5.0 的速度声明**。

### E. 四臂消融：问题不在"提示词"，在"写文件"

在 4 个写文件家族（`codeimpl`、`migrate`、`perfrefactor`、`shellops`）上，12 个完整四臂单元（48 次运行，全绿）：

| 臂 | 配置 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
|---|---|---|---|---|---|---|---|
| `off` | 关闭 | 1587.2 s | 1.00× | 171 288 | 1.00× | 132 | 1.00× |
| `def15` | `guidance: true` + `outputDiscipline: 'late'` | 1633.9 s | 1.03× | 175 775 | 1.03× | 142 | 1.08× |
| `g1` | `guidance: true`，无文风纪律 | 2095.3 s | 1.32× | 210 993 | 1.23× | 139 | 1.05× |
| `s1` | 文风纪律全程静态 | 1520.7 s | 0.96× | 154 050 | 0.90× | 107 | 0.81× |

- 关掉文风纪律（`g1`）**更慢**：调度器只剩并行提示，模型在大段写入时没有被要求压缩输出，输出 +23 %、墙钟 +32 %。这一条否掉了"少提醒=更快"的直觉。
- 静态纪律（`s1`）在写文件家族有小幅正收益（0.96× / 0.90× / 0.81×），但不足以抵消它在长读任务上的提示税，所以不进默认。
- 结论：写文件家族的速度不由提示词决定，这是 1.5.0 不再加提示词、转而做 harness 层机制（复用检查、无进展诊断、token 预算）的原因。

### F. 新增经典编程家族 `codebase`（3 臂 × 3 对，9/9 绿）

小仓库（`inventory/` 包 + 可见测试 + README），任务是修 2 个真 bug、加 TTL、加分页；隐藏套件 18 条只调公共 API。种子状态隐藏测试 **6/18 红**（确实要干活），参考解 18/18 绿。

| 臂 | 总墙钟 | × | 总输出 token | × | 步数 | 隐藏测试 |
|---|---|---|---|---|---|---|
| `off` | 143.6 s | 1.00× | 15 299 | 1.00× | 21 | 9/9 绿 |
| `def15` | 120.6 s | **0.84×** | 12 086 | **0.79×** | 19 | 9/9 绿 |
| `fl10` | 153.2 s | 1.07× | 15 580 | 1.02× | 21 | 9/9 绿 |

这是最接近"日常改一个真实小仓库"的场景：默认臂 0.84× 墙钟、0.79× 输出，`fl10` 反而略慢——和 D 节的结论一致，收尾线在这种"改完还要再验一次"的任务上没有便宜可占。

### G. 对照臂：按真实 usage 的 `outputBudget`（3 家族 × 3 对，9/9 绿）

> **本节数字于 1.5.10 整体作废，`outputBudget` 回到「未测量」。** 原因不是结果不好，而是**机制没有接线**：`outputBudget` 在 `src/config.js` 里声明了，但 `src/effort.js` 构造策略对象时不带这个字段，调度器读到的永远是 `undefined`，所以 `bud8` 臂实际跑的就是 `def15`——两条臂的差值是噪声。同一轮的 `bud8` 原始结果文件（本应对应 3 家族 × 3 对）在 `bench/results/` 里也已不存在，只剩 W4 里 20 个同样失效的 `bud8` 单元。**别引用本节表格里的任何数字**；1.5.10 修好传递链后，`outputBudget` 需要在新块里重新测。同理，`shapeGate` 的 `gate15`/`gateonly`/`sg15` 三臂也从未真正生效（见文末「1.5.10 的接线审计与撤回」）。

前面说过写文件家族的问题不在提示词。这一轮就把"压输出"做成 harness 机制而不是再写一段提示：`outputBudget` 读上游 `usage.outputTokens`，本轮累计到线就发一次「收掉当前闭环、跑一次证明它的检查、汇报」，每轮最多一次、默认 `0`（关）。对照臂 `bud8` = 默认配置 + `outputBudget: 8000`，与 `off`、`def15` 同轮 ABBA，`codeimpl` / `migrate` / `codebase` 各 3 对：

| 臂 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
|---|---|---|---|---|---|---|
| `off` | 1060.8 s | 1.00× | 140 555 | 1.00× | 109 | 1.00× |
| `def15` | 749.4 s | 0.71× | 106 081 | 0.75× | 85 | 0.78× |
| `bud8` | 726.6 s | **0.68×** | 98 443 | **0.70×** | 95 | 0.87× |

分家族（墙钟 × / 输出 ×）：

| 家族 | `off` | `def15` | `bud8` |
|---|---|---|---|
| `codeimpl`（写多、单次写入最长） | 559.0 s / 81 101 t | 0.58× / 0.60× | **0.55× / 0.46×** |
| `migrate`（批量改写） | 408.8 s / 44 573 t | 0.85× / 1.02× | 0.77× / 0.99× |
| `codebase`（小仓库改 bug，单步写入短） | 93.0 s / 14 881 t | 0.82× / 0.81× | 1.13× / 1.16× |

读法：

- **机制确实压得下输出**：在最能验证它的 `codeimpl` 上，`bud8` 相对 `def15` 再省 23 % 输出（0.60× → 0.46×）、再快 3 个百分点（0.58× → 0.55×）。这是本轮唯一一个直接针对"输出 token"的机制，方向成立。
- **它对单步写入短的任务是负担**：`codebase` 上 1.13× / 1.16×——预算线在第八千 token 才响，小任务要么响不到、要么在已经接近完成时把模型从"再验一次"推到"就这样报"，于是多花一步。**这不是可以设成默认的东西**，默认仍是 `0`。
- **样本很小**：每家族 n=3、单臂、只有一个阈值（8000）。它够用来回答"这条路有没有用"（有），不够用来定"阈值该设多少"（没测 4000/16000，也没测它是否会像 `fl10` 那样压掉必要复验）。
- 27 次运行全部 `exit 0`、`green: true`，没有出现基线绿而对照臂红的单元。

### H. 第四臂：`openBatch`（9 家族 × 6 对，53 个完整单元，0 红）

目标里点名要测的第三臂是 `openBatch`（一次就绪的独立调用合成一步发出）。它在 `off`/`fl10` 之外单独跑了一块（9 家族 × 6 对 = 54 次运行，`open15` = 1.5.0 默认 + `openBatch: true`），再按 `(task,pair)` 与 V15 的 `off`/`fl10` 同单元配对：

| 臂 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
|---|---|---|---|---|---|---|
| `off` | 6875.9 s | 1.00× | 1 169 089 | 1.00× | 1137 | 1.00× |
| `fl10` | 5491.9 s | 0.80× | 929 729 | 0.80× | 824 | 0.72× |
| `open15` | 5041.8 s | **0.73×** | 753 421 | **0.64×** | 450 | 0.40× |

分家族（墙钟 × / 输出 ×）：

| 家族 | `off` | `fl10` | `open15` |
|---|---|---|---|
| `codeimpl` | 631.8 s / 114 388 t | 1.03× / 0.91× | **0.76× / 0.68×** |
| `csvpipe` | 1310.3 s / 212 380 t | 0.55× / 0.57× | 0.68× / 0.61× |
| `debugfix` | 331.7 s / 65 562 t | 0.38× / 0.30× | 0.51× / 0.39× |
| `ledger` | 1317.3 s / 254 050 t | 0.71× / 0.61× | 0.56× / 0.45× |
| `migrate` | 611.7 s / 101 943 t | 0.97× / 0.89× | 1.04× / 1.01× |
| `perfrefactor` | 170.0 s / 29 762 t | 1.45× / 1.43× | 0.97× / 0.66× |
| `shellops` | 688.0 s / 100 152 t | 1.01× / 1.24× | 0.92× / 0.81× |
| `shellpipeline` | 780.3 s / 133 252 t | 0.72× / 0.77× | 0.78× / 0.65× |
| `stategraph` | 1034.9 s / 157 600 t | 0.94× / 1.08× | **0.69× / 0.73×** |

尾巴：`off` p50 121.6 / p90 241.3 / max 326.8 s；`fl10` 92.1 / 178.7 / 277.1 s；`open15` **78.9 / 158.0 / 353.6 s**，最长的那次是 `csvpipe` p4（23 步、52 562 t）。

读法：

- **这是目前测到最快的配置**：总量 0.73× 墙钟、0.64× 输出，而且 53 个完整单元里 **0 红**（`off` 绿的单元 `open15` 全绿）。
- **输出下降不是靠少想**：它把"已经知道要做的独立调用"压进同一步，少掉的是重复的上下文重读与中间的过渡文字，所以输出 token 掉得比墙钟更多（−36 % vs −27 %）。
- **步数 0.40× 有一半是机械的**：一步发多个调用，步数必然降；所以步数不能单独当成绩，墙钟才是。墙钟也降，说明省的是真实往返。
- **它仍然治不了 `migrate`**（1.04× / 1.01×）：串行改写本来就没有可并行的调用。
- **这次不是交错 ABBA**：`open15` 是单独一块跑完再按单元配对，机器负载在块间会漂移，所以下面单独做交错复核。

**交错复核（4 家族 × 3 对，12 个完整单元、24 次运行、0 红）**：`open15` 与 `def15` 只差 `openBatch` 一个键，同单元交替跑：

| 臂 | n | 总墙钟 | × | 总输出 token | × | 步数 | p50 / p90 / max |
|---|---|---|---|---|---|---|---|
| `def15` | 12 | 1724.6 s | 1.00× | 260 293 | 1.00× | 146 | 120.8 / 261.4 / 298.1 s |
| `open15` | 12 | 1185.8 s | **0.69×** | 186 964 | **0.72×** | 114 | 99.4 / 147.5 / 174.2 s |

配对中位 **0.68×**、**9/12 更快**；分家族（墙钟 / 输出）：`codeimpl` 0.54× / 0.63×、`ledger` 0.67× / 0.67×、`shellpipeline` 0.67× / 0.70×、`migrate` 0.88× / 0.90×。方向与上面非交错块的 0.73× / 0.64× 一致，幅度略大；`migrate` 仍是唯一逼近 1 的家族（串行改写没有可并行的调用），也正是新增 `shapeGate` 想直接让路的那一类。整个 9 家族 × 6 对 × 三臂（`off`/`fl10`/`open15`）的交错块见下面的 H2；这 12 个单元的方向在补测 5 个家族后反转（H3）。
- 有一次运行 `exit -9`：那是我自己 `pkill suite-run.py` 时误杀了子进程（`hasEntry: false`、0 步、36 s），已删掉重跑同一单元，不是质量红。

### H2. 三臂交错 ABBA：9 家族 × 6 对（171 次运行，54 个完整单元）

这是本轮的主实验：`off` / `fl10` / `open15` 三臂在 9 个家族上真正的交错 ABBA（每臂每家族 6 对，配对顺序逐对翻转），隐藏测试逐单元门禁。171 次运行里 **54 个单元三臂全绿**可使用，另 3 个单元有红（见下），不进入任何一臂的总量。`ledger` 的 22 条断言偏紧，前 6 对里有 3 对因某臂红而整对作废，于是把它补到 9 对，才凑满 6 个完整单元（用的是 p3/p4/p5/p7/p8/p9）。

| 臂 | n | 总墙钟 | × | 总输出 token | × | 总步数 | × |
|---|---|---|---|---|---|---|---|
| `off` | 54 | 7841.3 s | 1.00× | 2 035 299 | 1.00× | 3960 | 1.00× |
| `fl10` | 54 | 5160.6 s | **0.66×** | 1 390 465 | **0.68×** | 2655 | **0.67×** |
| `open15` | 54 | 5419.4 s | **0.69×** | 1 438 884 | 0.71× | 2800 | 0.71× |

逐对中位（对 `off`）：`fl10` **0.68×**、44/54 更快；`open15` **0.67×**、40/54 更快。两臂之间是打平而非谁支配谁：`open15`/`fl10` 逐对中位 1.06×，`fl10` 在 30/54 单元更快、`open15` 在 24/54 更快；总量上 `fl10` 略前（0.66× vs 0.69× 墙钟），中位上 `open15` 略前。

分家族（墙钟 × / 输出 ×）：

| 家族 | n | `off` | `fl10` | `open15` |
|---|---|---|---|---|
| `codeimpl` | 6 | 884.3 s / 293 276 t | 0.68× / 0.60× | 0.73× / **0.56×** |
| `csvpipe` | 6 | 1438.3 s / 322 202 t | 0.55× / 0.64× | **0.46× / 0.51×** |
| `debugfix` | 6 | 194.8 s / 50 405 t | **0.69× / 0.78×** | 0.91× / 1.24× |
| `ledger` | 6 | 1616.9 s / 398 128 t | **0.62× / 0.66×** | 0.72× / 0.82× |
| `migrate` | 6 | 885.1 s / 210 915 t | **0.67× / 0.69×** | 0.82× / 1.07× |
| `perfrefactor` | 6 | 287.8 s / 71 076 t | 0.77× / 0.68× | **0.62× / 0.59×** |
| `shellops` | 6 | 663.1 s / 254 229 t | 0.78× / 0.55× | **0.75× / 0.37×** |
| `shellpipeline` | 6 | 910.3 s / 234 962 t | **0.52× / 0.61×** | 0.83× / 0.79× |
| `stategraph` | 6 | 960.5 s / 200 106 t | 0.87× / 1.16× | **0.63× / 0.88×** |

尾巴（同一批单元）：`off` p50 147.0 / p90 248.2 / max 407.3 s（最长 215 步）；`fl10` 92.7 / 161.4 / 305.7 s（167 步）；`open15` **85.0 / 185.2 / 427.0 s**（176 步——p50 最低、尾部最重，说明它减少的是请求数，不是单次请求的时长）。

读法：

- **两条「少发请求」的路线都能拿掉约三分之一墙钟**，输出 token 也降三成；这不是本轮才出现的效应，前面 H 节与 V15 的方向一致，现在 9 个家族都是每臂 6 个完整单元。
- **`fl10` 按判定规则不合格**：`ledger` p1 上 `off` 绿（22/22）、`fl10` 红（21/22），按「基线绿而该臂红即不合格」的规则出局；该单元随后重跑 5 次全绿（1/6 失败率，非确定）。它仍然是消融臂，不做默认。`open15` 在 H3 的 5 家族对照里也有一次同样形式的门禁违规（`shellops` p7），所以「三臂都过门禁的更快配置」本轮不存在。
- **`off` 自己也红过两次**（`ledger` p2 与 p6，均 21/22），因此这两对按规则整对作废、不进入任何一臂的总量；`ledger` 的 6 个完整单元是补跑 p7–p9 后才补齐的。这也说明该家族的 22 条隐藏断言对模型偏紧，不是某一臂的退化。
- **补跑的两处口径**：`ledger` p7 第一次跑出 9 步 / 332.0 秒、单次调用 330.6 秒的卡顿（宿主侧停摆，不是模型行为），按「重跑覆盖旧行」的规则用稳定环境下的重跑（`off` 303.2 s / 180 步，`fl10` 114.2 s / 49 步，`open15` 185.6 s / 143 步，三臂全绿）替换；`bench/results/suite-TA3.jsonl` 里保留的是替换后的行。
- **结论不依赖 `ledger`**：剔掉这一族、只用其余 48 个单元重算，两臂比值不动（`fl10` 0.67× / 0.69× / 0.69×，`open15` 0.68× / 0.68× / 0.69×）。
- **两臂互补而非互相支配**：`fl10` 在 `shellpipeline` 0.52×、`debugfix` 0.69×、`codeimpl` 0.68×、`ledger` 0.62× 更好；`open15` 在 `csvpipe` 0.46×、`perfrefactor` 0.62×、`stategraph` 0.63×、`shellops` 0.37× 输出上更狠。两者都在某处留了输出回升：`open15` 在 `debugfix` 1.24× / `migrate` 1.07×，`fl10` 在 `stategraph` 1.16×。这也说明「单一全局开关」的收益上限就在 0.65–0.7× 附近，再往下要靠形状判断而不是更狠的压制。

是否把 `openBatch` 提为默认，要看它与**当前默认 `def15`** 的交错对照（下面 H3）。答案是**不提**：换对手之后它 1.03× 墙钟（输出与步数持平或略少），还出现一次基线绿而它红的单元与一次 325 步 / 5.2 倍 token 的失控。

### H3. `openBatch` 对 1.5.0 默认（`def15`）：扩到 5 家族后被推翻

H 节里「`openBatch` 比默认快」的印象来自两块小样本：非交错的 9 家族对 `off`（0.73×，对手不是默认臂）与交错的 4 家族对 `def15`（12 个单元，0.69×）。补测 5 个家族（`csvpipe`、`debugfix`、`perfrefactor`、`shellops`、`stategraph`，`perfrefactor` 7 对、其余 6 对），共 62 次运行、30 个完整单元（`csvpipe` p6 有一次尝试卡住，约 15 分钟没有任何新步，杀掉重跑 68.0 s 全绿；重跑那次计入），对手换成**当前默认 `def15`**：

| 臂 | n | 总墙钟 | × | 总输出 token | × | 总步数 | × |
|---|---|---|---|---|---|---|---|
| `def15`（默认） | 30 | 2707.2 s | 1.00× | 758 146 | 1.00× | 1586 | 1.00× |
| `open15` | 30 | 2785.7 s | **1.03×** | 749 801 | 0.99× | 1502 | **0.95×** |

逐对中位 0.91×（19/30 更快）看起来还行——输出总量基本持平、步数还少 5 %——但墙钟被一个单元拖翻：

| 家族 | n | `def15` | `open15` 墙钟/输出/步数 |
|---|---|---|---|
| `csvpipe` | 6 | 898.0 s / 273 832 t | **1.18× / 1.19× / 1.12×** |
| `debugfix` | 5 | 126.6 s / 15 679 t | 0.78× / 1.34× / 1.12× |
| `perfrefactor` | 7 | 239.8 s / 77 620 t | 0.86× / 0.85× / 0.67× |
| `shellops` | 6 | 604.2 s / 147 341 t | 0.86× / 0.77× / 0.64× |
| `stategraph` | 6 | 838.7 s / 243 674 t | 1.07× / 0.92× / 1.06× |

两个硬结果：

- **没过门禁**：`shellops` p7，`def15` 绿 14/14，`open15` 红 13/14。按「基线绿而该臂红即不合格」，`openBatch` **不合格**。
- **合批会放大失控**：`csvpipe` p5，`open15` 用了 **325 步 / 499.0 s / 188 731 t**，同单元 `def15` 是 82 步 / 133.9 s / 36 433 t——**5.2 倍输出 token**。合批把「先看清再动手」的串行检查压成一次大爆写，写错之后要整片重写。它自己没有收敛机制，因为合批只改调度、不发新约束。

结论：`openBatch` 保持默认 `false`，只作实验旋钮，并在 REFERENCE 里写明「本版实测不优于默认、且未过隐藏测试门禁」。H 节那 12 个单元的 0.69× 是**小样本上界**，不作为声明。

### I. 边界与两个必须披露的偏差

- **基线里有两个失控运行**：`csvpipe` p1 `off` 137 步 / 263.3 s / 101 534 t，`ledger` p2 `off` 126 步 / 293.4 s / 101 443 t。两者都是绿的（最终做完并通过隐藏测试），但把 `off` 的总步数抬了 263 步、输出抬了约 203 k。**把它们剔掉后（剩 52 个单元），`def15` 的墙钟比从 0.86× 回到 0.88×，输出比从 0.95× 回到 1.04×，步数比从 0.85× 回到 1.01×**——加速的六成来自墙钟，而输出/步数上「默认更省」在剔掉基线打转后消失。本节所有总量都按"不剔任何运行"给，因为那是真实成本；但这个敏感性必须一起读。
- **机器是共享且超载的**：本轮期间同一台机器上还有另一个项目在跑基准（`run_ab.py`，约 20 load / 12 核），绝对墙钟被整体抬高，ABBA 配对与同单元加总仍然有效，但跨轮次的绝对值不可比。
- **臂集合**：`off` / `def15` / `fl10` 交错 ABBA（54 个完整单元，V15）是默认选择的依据；`off`/`fl10`/`open15` 另做过 9 家族交错三臂（54 个单元，H2），`openBatch` 与默认的正面交锋是 5 家族交错（30 个单元，H3）。三条加速路线（`fl10`、`openBatch`、`shapeGate`）里前两条都因门禁不合格留在实验旋钮，`shapeGate` 尚无单独对照臂，因此 `openBatch` 与 `shapeGate` 在 1.5.0 里都是默认 `false`。
- **`fl10` 出现过一次真实的隐藏测试失败**：新交错块里 `ledger` p1 的 `fl10` 单元（166 步 / 205.7 s / 92 209 t）通过 21/22，同单元 `off` 绿（215 步 / 262.1 s / 107 845 t）。该单元随后重跑 5 次全绿（27–122 步），即失败率约 1/6、非确定性。按本项目的判定规则「基线绿而该臂红即不合格」，`fl10` **不合格**，只能留作消融臂；这也和它的机制一致——它压掉的正是「测试通过后的复验」。
- **隐藏测试门禁**：V15 167 行、AB2 48 行、CB1 9 行、BUD1 27 行、OPEN15 54 行，全部 `green: true`（其中 OPEN15 有 1 次 `exit -9` 是我自己 `pkill` 误杀子进程，删掉重跑，不算红），没有出现「基线绿而加速臂红」的单元。因此本轮的结论可以说成：**在已测的有限任务中观察到加速，未观察到这些隐藏断言上的退化**——不能说成"不会降智"。

### J. 本轮之后的方向

写文件家族既然不是提示词问题，下一步只能是 harness 机制：①复用已通过的检查（已实现，按产物版本失效）；②无进展只诊断不判决（已实现）；③**按真实 usage 的 `outputBudget`**——本轮已实现并作为对照臂测过（G 节：`codeimpl` 上输出再降 23 %，小任务上变慢）。默认仍为 `0`（关闭），因为在完成度不足时提前收口正是 1.4.4 犯过的错。

本轮把三条加速路线都走完了，也都用隐藏测试门禁筛过一遍：`finishLineSteps: 10`（最快，0.66× 墙钟，但 `ledger` 上 1/6 失败）、`openBatch`（0.69× 对 `off`，但对默认 `def15` 是 1.03×，且有一次 325 步失控与一次红）、`shapeGate`（要放行写密集任务，尚未单独跑对照臂）。**能同时满足「比默认快」和「不出现基线绿而该臂红」的配置，本轮一个都没有**，所以 1.5.0 的默认仍是 `def15`，`shapeGate` 保持默认关；它在隔离干净复测里的墙钟/输出比见「1.5.1」（V15 的 `0.86×` 已撤回，不再引用）。下一步若要再往下走，只能做「按任务形状分流」——对读多写少的任务开 `fl10`，对写密集任务放行——但那需要先做出一套可靠的形状判据，并且逐家族复测，不是再加一条提示词能解决的。

## 1.4.4（历史/消融）：收尾线定版——7 家族 14 对，中位 1.86×，输出 −52%，正确性零损失（2026-10-25）

这一轮把“长任务的尾巴”当成单独的对象来测。上一版只证明了“时间随步数出现”，却没回答怎么在**不伤正确性**的前提下把尾巴剪掉。答案是一个 harness 层的软收尾线：第 10 步由调度器注入一次“别再扩大范围、不重跑已通过的检查、只修还在报的错”，不阻断工具、不截断步骤。

### I. 语料：5 轮 ABBA，272 次运行，7 个家族

7 个长/短混合家族（每家族 4–32 步、10–22 条隐藏断言、确定性评分）：`csvpipe`、`ledger`、`migrate`、`stategraph`、`shellpipeline`、`debugfix`、`perfrefactor`。全部运行 `exit 0`，隐藏测试由 runner 事后执行，agent 看不到测试文件。

| 轮次 | 目的 | 臂 | 运行数 |
|---|---|---|---|
| A1 | 长任务基线 | off / on / late | 42 |
| B1 | 归因：提示段、并行宽度 | off / on / late / late-par1 / late-guid0 | 70 |
| C1 | 步数旋钮 | off / late / fl8 / fl12 / b15 | 50 |
| D1 | 跨家族确认 + 短任务守卫 | off / fl8 / b15 / combo | 56 |
| E1 | 定版候选 vs 现有默认 | off / late / fl10 | 54 |

### J. 定版结果：`finishLineSteps: 10`

E1，7 家族、每臂 14 对、隐藏测试全绿：

| 臂 | 墙钟中位 | 变快对 | 符号检验 | 输出 token | 步数 | 正确性 |
|---|---|---|---|---|---|---|
| `fl10`（late + 第 10 步收尾线） | **1.86×** | **13/14** | **p = 0.002** | **−52 %** | **−10.0** | 14/14 绿 |
| `late`（1.4.3 默认） | 1.31× | 11/14 | p = 0.057 | −28 % | −8.0 | 14/14 绿 |

同轮次的直接配对（`fl10` ÷ `late`，14 对）：中位 **1.27×**、11/14 更快（p = 0.057）、输出 **−22 %**、步数中位 **不变**。也就是说收尾线的增益主要来自**少写**（不再重跑已通过的检查），而不是少做步骤——这正是 1.4.3 延迟模型里 decode 占 58 % 的那一块。

分桶（按 off 组步数）：

| 桶 | 对数 | `fl10` 中位 | 变快 | 输出 |
|---|---|---|---|---|
| 1–5 步 | 1 | 0.66× | 0/1 | +53 % |
| 6–10 步 | 1 | 2.51× | 1/1 | −58 % |
| 11–15 步 | 4 | **1.86×** | 4/4 | −47 % |
| 16–99 步 | 6 | **1.87×** | 6/6 | −46 % |

### K. 被否掉的三个候选

| 候选 | 证据 | 结论 |
|---|---|---|
| 硬步数预算 `stepBudget: 15`（b15） | C1 中位 2.04×（8/10）、D1 1.26×（10/14），14/14 与 10/10 全绿 | 速度不稳、且到线直接要求“停止并报告”，在 D1 的短任务桶掉到 0.43–0.77×。保留为可配，不作默认 |
| 更早的收尾线 `finishLineSteps: 8`（fl8） | C1 1.63×（9/10，p=0.021）、D1 1.50×（7/14） | 短任务桶 6–10 步 0.44×（输出 +165 %）；第 8 步对 10 步左右的任务太早 |
| 两条一起 `combo`（8 + 15） | D1 1.14×（8/14），16+ 步 1.50×（6/8） | 叠加不增益，短任务更贵 |

并行宽度与提示段归因来自 B1：`late-guid0`（关掉提示段）0.96×，`late` 1.26×——**提示段本身是载荷**，并行宽度（`parallel: 1`，1.10×）不是。

### L. 这一版的边界

- 只有本地 `deepseek-v4.1-flash` 网关的数据；单轮方差 0.41×–4.76×，所以所有结论都按配对中位与符号检验给，不报单次最好成绩。
- 收尾线是**建议**，不是截断：模型可以继续做。它靠的是“别再重复验证”这句话在有界上下文里真的生效，证据是输出 token 中位 −52 %。
- 短任务仍无收益：1–5 步桶 0.66×（n=1，绝对值 17 s），4 步以内结束的一轮根本到不了第 10 步，代价为零。
- 单一家族曾测到 2.68×（ledger，6 对），那是该家族的乐观值；跨 7 家族的定版数字是 **1.86×**。
- 同一 `（任务、臂、对）` 有补跑时按各次运行的中位聚合再配对，避免挑最有利的一次；E1 的 54 行记录压成 42 个单元后 n 仍为 14。
- **12 对 vs 14 对**：下面分桶表只覆盖 14 对中落在四个步数桶里的 12 对，另 2 对不在桶内；E1 的原始记录未提交，无法从已提交数据重算那 14 对的比值，桶表不能当作 14 对的完整分解。
- **语料与复现边界**：272 次运行里包含 C1/D1 的调参轮（不是每条都进定版结论）；7 个家族 fixture 与 runner 未公开发布（只在本机 `/tmp/fm-bench`），因此外部无法逐字复跑；全部数据来自本地 `deepseek-v4.1-flash` 网关，换路由后的绝对值不可假定。

## 1.4.3：速度随步数增长——短任务 0 收益，长任务 1.49×（2026-10-24）

这一轮回答“A 对短任务收尾是不是收益不够”。答案是够的，只是**收益随步数出现**：短任务里没有多少可省的东西，长任务里输出 token 与步数都会被压下来。

### G. 步数分桶：1–5 步无收益，6–10 步 1.23×

同一份 80 对 ABBA 语料（`results.jsonl`，外部墙钟 `elapsedSec`，全部运行 `exit 0`）按 off 组步数分桶：

| 桶 | 对数 | 中位加速 | p25–p75 | 变快对 |
|---|---|---|---|---|
| 1–5 步 | 47 | **1.00×** | 0.82–1.20 | 23/47 |
| 6–10 步 | 33 | **1.23×**（符号检验 p = 0.035） | 0.99–1.43 | 23/33 |
| 11 步以上 | 0 | — | — | — |

`corr（输出 token 变化，加速）= −0.77`：快的那些对，都是模型写得少。输出 token 中位 831 → 756。这条规律解释了为什么“总体中位 1.06×”看起来弱——它把 47 对几乎没有可省空间的短任务平均进去了。

同一份语料还证明**并发不是来源**：短任务上并行宽度早就够用，长任务上的加速与工具扇出无关。

### H. 输出纪律 A/B：−15.4% token，正确性零损失，墙钟测不出来

深 fixture（4 模块 / 20 测试）12 对 ABBA，24 次运行全部 `exit 0`、测试文件未改：

| 组 | 墙钟中位 | 步数 | 输出 token | 测试 |
|---|---|---|---|---|
| 关 | 29.9 s | 9.0 | 4558 | 96/96 |
| 开 | 25.2 s | 9.0 | 3858 | 96/96 |

token **−15.4%**，未达预设的 20% 门槛；配对中位 1.13×，但只有 7/12 对更快（p = 0.774），配对 token 变化中位 −14.5%、仅 7/12 对下降（最差一对 +47%）。单看这一项，**在 n=12 上与 ±30% 的任务内噪声分不开**。验收条件“token 降 ≥20% 或判负即结案”由此触发的结论是：不作为独立卖点，正确性零损失这一条成立。

### I. 长任务 A/B：16.5 步 → 10.5 步，墙钟 1.49×，隐藏测试 22/22 全对

fixture 是“从零实现一个模块”——`ledger.py`，6 个函数，规范写清但边界很多（会计括号负数、货币符号、千位逗号、`Decimal` 精度、ROUND_HALF_UP、规则优先序、并列取 ASCII 最小、报表排版）。**隐藏测试**放在模型看不到的目录，跑完由 runner 复制进去执行，22 条断言；写不出来就是 0 分，等于把“正确性”做成可观测量而不是主观判断。8 对 ABBA、16 次运行：

| 组 | 墙钟中位 | 步数中位 | 输出 token 中位 | 隐藏测试 | 绿 |
|---|---|---|---|---|---|
| 关 | 215.2 s | **16.5** | 32 718 | 22.0/22 | 8/8 |
| 开 | 144.5 s | **10.5** | 24 274 | 22.0/22 | 8/8 |

- 墙钟 **1.49×**（配对中位 1.56×，均值 1.66×；5/8 对更快，p = 0.29——赢的三对赢得大，2.3–2.7×，输的三对只输 5–24%）。
- **步数 8/8 对全部下降**（符号检验 p = 0.008）：这是最干净的一条，机制也直接——精简输出就少一步“先说明再动手”，少一次重复验证。
- 输出 token **−25.8%**（配对中位 −38.6%，5/8 对下降），这次越过了 20% 门槛。
- **正确性 16/16 次运行、22/22 全对，两个臂都一样**。这是本轮最重要的质量证据：省下来的是过程文字，不是实现质量。

对照实验：上一版“12 个 bug 的修复型 fixture”只花 4–6 步就被模型一次改完，结果 **0.94×、输出 +11%**——它压根不是长任务，只是“短任务 + 更多文件”。它反过来印证 G 的分桶：步数不够，收益就是零甚至是负的。

### J. 结论与产品含义

- 可辩护的说法：**6 步以上约 1.2×；真长任务（16 步量级）1.49×，步数 −36%，输出 −26%，正确性不变；≤5 步为 0**。
- 因为收益随步数出现，**“短任务不介入、长任务自动介入”有实测依据**，`outputDiscipline: 'late'` 由此实现：系统提示里不放文风规则（短任务零成本），第 4 步由调度器注入一次压缩提醒（`src/auto.js` 的 `compress-output` 指令，每轮一次）。

### J. 迟到介入 A/B：长任务 2.68×，正确性同样零损失

同一 fixture、同一 runner，6 对 ABBA（12 次运行），比较 `off` 与 `outputDiscipline: 'late'`：

| 组 | 墙钟中位 | 步数中位 | 输出 token 中位 | 隐藏测试 | 绿 |
|---|---|---|---|---|---|
| 关 | 184.2 s | 10.5 | 30 178 | 22.0/22 | 6/6 |
| late | **68.6 s** | **7.5** | **14 480** | 22.0/22 | 6/6 |

- 墙钟 **2.68×**（配对中位 2.82×；4/6 对更快，输的两对 0.77×/0.79×）。
- 步数 5/6 对下降，中位 −3 步；输出 token **−52.0%**（配对中位 −59.6%，4/6 对下降）。
- 正确性仍是 12/12 次运行、22/22 全对。n=6 是 pilot，只能当方向；但它与上一节 8 对的 1.49× 同向，且“全程纪律”那一版在同一 fixture 上是 1.49×——迟到介入没有变慢。
- **短任务对照**（上一版 4–6 步 fixture，6 对）：late 13.5 s vs 关 17.6 s（中位 1.31×，配对中位 1.26×，4/6 对更快，步数不变）。这条落在 ±30% 的任务内噪声里，只能说“短任务没有付出代价、也没赚到”，与用户设想的“短任务不介入”一致；全程纪律版在同一 fixture 上是 0.94×，两者都在噪声内、方向相反，正好说明这个 fixture 量不出东西。
- 因此**默认值改为 `late`**：短任务不付提示税，长任务在第 4 步拿到同一句压缩指令。

## 1.4.2：把“并发”量出来——子代理是真并行，shell 扇出工具是负收益（2026-10-24）

这一轮只问两个问题：harness 层面到底还有多少并发没被用上？我们为此加的提示段和工具，究竟赚还是亏？

### A. 子代理并发：4 路，跨度 21 秒（串行应 80 秒）

四个真实子代理，每个只做 `sleep 20`，各自把开始/结束时间戳追加到同一个日志：

| 探针 | 开始 | 结束 |
|---|---|---|
| agent1 | +0 s | +20 s |
| agent2 | +0 s | +21 s |
| agent3 | +1 s | +21 s |
| agent4 | +0 s | +20 s |

整段跨度 **21 s**，串行基线 80 s → **约 3.8×**。四个子代理的启动在 1 秒内完成，说明限制不在调度器，而在**模型是否愿意把工作拆给子代理**。这条与早前 k=4 的并发测量（4 个相同请求并发 6.5 s vs 串行 23.2 s = 3.6× 吞吐，单步 llm 只涨 14%）互相独立、结论一致。

### B. `shell_parallel` 工具：工具本身有效，产品化是负收益（3 对 ABBA）

fixture 是 6 个互不依赖的作业，每个 `sleep 5`（`tp`），同题三对、顺序轮转、六次运行全部 `exit 0`：

| 组 | 墙钟 | 步数 | 工具内时间 |
|---|---|---|---|
| on p1 | 26.3 s | 7 | 15.3 s |
| off p1 | 16.0 s | 6 | 5.2 s |
| on p2 | 54.5 s | 7 | 45.5 s |
| off p2 | 8.9 s | 3 | 5.1 s |
| on p3 | 15.2 s | 6 | 5.2 s |
| off p3 | 9.1 s | 3 | 5.1 s |
| **中位** | **26.3 s** | 7 | 15.3 s |
| **中位** | **9.1 s** | 3 | 5.1 s |

**0.35×（on / off）**。会话日志给出了确切机制，两条都不是“工具写错了”：

1. **工具确实是并发的**：6 个 5 秒作业在 5.1 秒内跑完（`tool/call` +1.4 s → `tool/result` +6.5 s）。
2. **基线本来就会并行**：off 组一次 `bash` 里就写了 `for d in job-0*; do (cd $d && python3 slow.py) & done; wait`，模型自己把六个作业并行起来了。
3. **多出来的工具诱发复核**：on 组先跑 `shell_parallel`，再 `cat slow.py`、再 `date; ls done.txt`、再跑第二遍；其中一次落进串行 `for` 循环验证（+22.1 s → +52.4 s，正好 30 s）。六次工具调用、七步，墙钟翻倍还多。

结论：**默认关闭**（`shellParallel: false`），提示段也只在工具真的注册时才提它的名字（否则模型会去调一个不存在的工具）。需要时一个开关打开。

### C. 一次把自己证伪的三臂实验（诚实记录）

我本想用 `guidance: false` 做“有提示段 / 无提示段 / 关插件”三臂对照，跑了 3 任务 × 6 对 = 54 次（全部 `exit 0`）。结果出来后复核代码才发现：**`resolveConfig` 没有把 `guidance` 透传出去**，三臂里其实有两臂配置完全相同。

| 任务 | off | 名义 notext | 名义 text |
|---|---|---|---|
| t2 | 5.6 s | 5.5 s | 5.6 s |
| t3 | 3.9 s | 7.5 s | 5.2 s |
| tp | 9.7 s | 17.6 s | 16.0 s |
| **总体中位** | **5.6 s** | **7.5 s** | **5.6 s** |

于是这组数据不能当 A/B 用，但它**变成了一次噪声标定**：两个完全相同的配置，在每任务 6 对的样本下给出了 **0.75× 和 1.01×** 两种“效应”。也就是说，本套小任务上 n=6 的每任务中位数**噪声达 ±30%**——任何小于这个幅度的单任务差异都不该被当成结论。我此前“notext 1.01× / text 0.88×”的说法**作废**（`guidance` 开关当时并未生效）。

### D. 子代理扇出做真实工作：小任务上是 0.25×（2 对 pilot）

A 证明了“并发原语能用”，但没用它做任何事。这一组把真实工作交给子代理：fixture 有 4 个模块，每个模块 120 行 `key=value`，规则写在各自 `NOTES.md`（能被 7 整除的 key 是 hot），要求算 hot 条数、最大 cold 值并汇总成表。两臂都用 `off`，唯一差别是提示里是否明确要求“为每个模块开一个子代理并行分析”。两对 ABBA、四次运行全部 `exit 0`：

| 臂 | 墙钟 | 步数 | sum(llm) | sum(tool) | 输出 token |
|---|---|---|---|---|---|
| 自己串行做 p1 | 10.9 s | 6 | 9.9 s | 0.2 s | 1798 |
| 子代理扇出 p1 | 49.4 s | 27 | 54.2 s | 30.8 s | 9130 |
| 子代理扇出 p2 | 23.2 s | 30 | 66.0 s | 0.7 s | 10054 |
| 自己串行做 p2 | 7.4 s | 5 | 6.4 s | 0.1 s | 1348 |
| **中位，串行** | **9.2 s** | 5.5 | 8.2 s | 0.2 s | 1573 |
| **中位，扇出** | **36.3 s** | 28.5 | 60.1 s | 0.7 s | 9592 |

**0.25×（扇出 / 串行）**，而且不是调度器的问题：`sum(llm)` 60 s 是**四个子代理各自的解码之和**，它们确实在并行跑（单次跨度只有 23–49 s）；真正的代价是

1. 每个子代理都要付一次完整启动 + 系统提示 + 至少 4–5 步（读 data、读 notes、算、写报告），4 个孩子就是 4 份固定成本；
2. 子代理的报告天生啰嗦：输出 token 从 ~1.5 K 涨到 ~9.6 K，**6 倍**；
3. 父会话还要额外花步数去派发与等待（步数 5.5 → 28.5）。

**这说明扇出的盈亏线不在“并发度”，在“子任务本身的深度”。** 每个子任务只有一两千 token 级别的活时，拆出去必亏；只有当单个子任务本身就要跑十几步、几千 token 时，扇出才可能赢。真实任务大多落在前者。

### E. 深子任务扇出：仍然是 0.60×（2 对 pilot，含会话取证）

D 的反对意见是“每个子任务太浅，所以扇出当然亏”。E 把子任务做深：4 个模块，每个 5 个分片共 300 行 `<class>:<key>=<value>`，规则在 `NOTES.md`（值能被 7 整除即 hot），权重在 `weights.txt`，每个模块要写出 `hot` / `cold_sum`（冷样本加权和）/ `cold_max`，最后汇总。串行做要 11–14 步、3–7 K 输出 token。两臂都用 `off`，差别只在提示是否要求“每模块各开一个子代理”。

| 臂 | 墙钟 | 步数 | sum(llm) | 输出 token | 正确性 |
|---|---|---|---|---|---|
| 串行 p1 | 20.4 s | 11 | 18.8 s | 3253 | **4/4** |
| 扇出 p1 | 45.8 s | 42 | 93.0 s | 13817 | **4/4** |
| 扇出 p2 | 50.7 s | 44 | 127.5 s | 20004 | **0/4** |
| 串行 p2 | 37.3 s | 14 | 35.6 s | 6931 | **4/4** |
| **中位，串行** | **28.8 s** | 12.5 | 27.2 s | 5092 | 4/4、4/4 |
| **中位，扇出** | **48.2 s** | 43 | 110.3 s | 16910 | 4/4、0/4 |

**0.60×（扇出 / 串行），而且一次还做错了。** 会话取证说明问题不在“有没有并行”：

- 扇出臂确实开了 **4 个子会话**，四个 `createdAt` 相差 **3 毫秒**——派发是**同一瞬间**发生的，调度器没有排队；
- 但每个孩子耗了 **44–81 个事件（约 15–25 步）** 才算完一个模块，而串行臂用 **11–14 步** 就把四个模块全做完了；
- 输出 token 5.1 K → 16.9 K（3–4 倍），`sum(llm)` 27 s → 110 s（四个孩子各自的解码之和）；
- 父会话在派发后还要读四份报告、写汇总，步数 12.5 → 43。

**换句话说：把活切成四份之后，每一份的代价几乎和整份一样大。** 子代理的启动、系统提示、以及“自己重新摸清这个模块”的探索成本，与切片大小基本无关；切片越小，摊销得越差。这是子代理架构的固定成本，不是插件能改的。

结论：**扇出在这套 harness 上已经两次被证伪**——浅子任务 0.25×（D），深子任务 0.60× 且有一次结果错误（E）。并发原语是真的（A：4 路 21 s；E：4 个孩子 3 ms 内派出），但“把活交给更多 agent”不是提速手段，反而放大 token 与出错面。

### F. “重复调用就短路”这条机制：先做人群统计，然后否掉（1198 个会话日志）

思路本来是纯程序化的、不碰提示词：如果某一步又调了**同一个工具、同样的参数**，就把上一步的结果直接回灌，省掉一次执行。在动手之前先数了一下它到底能命中多少——把仓库里所有 bench 会话日志（**560 个会话目录、1198 个日志、7168 次工具调用**）全扫了一遍：

| 判重口径 | 命中率 | 命中的工具 |
|---|---|---|
| 严格（参数逐字节相同） | **10 / 7168 = 0.1%** | `shell_parallel` 8 次（就是这个已经被关掉的工具）、`list_agents` 2 次 |
| 宽松（去 `cd … &&`、压空白、去 `\| head`） | **314 / 7168 = 4.4%** | `bash` 300、`grep` 4、`shell_parallel` 8 |

而且命中是**极度集中**的：4.4% 里绝大部分来自 74 个会话（占 1198 的 **6%**），典型形态是「同一形状的复核命令连发 5 次」。抽查最集中的那个会话（t3-on-p2），七次调用里六次是：

```
1..6  bash | python3 gen.py      ← 同一个生成脚本，连跑六遍
7     write
```

三条否掉理由：

1. **量太小**：严格口径 0.1%，宽松口径 4.4%，且集中在 6% 的会话里；对总体墙钟的期望收益是毫秒级——这些重复是 `ls`/`cat`/`date`/`python3 gen.py` 这类**便宜调用**，不是重跑 5 秒作业那种贵调用。
2. **省的不是那 0.5 秒**：短路能省的是**工具执行时间**，省不掉**那一步的 LLM 开销**（模型仍然要生成这次调用、仍然要等一次 TTFT）。而整套测量的结论恰恰是每步 `TTFT ≈ 0.5 s` 是主要固定成本——这一步该由既有的步数预算/停滞看门狗去砍，不是由结果缓存去砍。
3. **会引入正确性风险**：命中最多的 `python3 gen.py` 是**有状态**的命令（重新生成数据文件）。把它的第二次调用短路成“上次的结果”，等于把一个会改变现场的命令当成纯读——这是会静默写坏状态的优化。

结论：**不做**。同时说明一件事——`shell_parallel` 那个工具的 8 次重复，是它自己诱发出来的，这也是它被关掉的第三个理由。

### 这一轮的净结论

- 唯一有量级的杠杆是**并发**：子代理 3.8×、k=4 吞吐 3.6×，都不需要改提示词。
- 提示段和 `shell_parallel` 都没有带来可测收益；后者在本 fixture 上稳定更慢，已改默认关闭。
- 小任务上的 `1.06×`（76 对）仍是仓库里唯一的大样本结论，且它落在 CI 下沿——按上面的噪声标定，它应当被读作“几个百分点”，不是“快一截”。
- **扇出到子代理已经两次证伪**：浅子任务 **0.25×**（D），深子任务 **0.60×** 且两次里有一次算错（E）。取证显示四个孩子在 3 毫秒内同时派出——并行是真的，亏在“切片后每一片的固定成本几乎和整片一样大”。这条线到此为止。
- “重复调用短路”也被否掉（F：严格 0.1%、宽松 4.4%，且都是便宜调用与有状态命令）——它省的是执行时间，省不掉那一步 0.5 s 的 TTFT。
- 时间构成的实测比例是：**TTFT/排队约 31%、解码约 58%、工具与间隙约 11%**，解码按 `4.0 ms / output token` 计。（**修正，1.5.1 隔离复测**：在干净会话里 TTFT 只有 0.59 s/步，占 `llm` 的 7 %；解码占 93 %，按 **6.0 ms / output token** 计。旧比例来自被污染与跨会话混合的样本，已作废。）因此唯一还剩量级的杠杆是**减少输出 token**（`outputDiscipline` 从未被单独 A/B 过），而不是并行、不是缓存。
- 因此不再做 `fanout` 工具，也不在提示里推子代理。插件能碰的只剩“每步少一次派发、每步少解码一点”，而上界由 76 对样本给出：**中位 1.06×**。产品的诚实定位是“小幅提速 + 卡死看门狗 + 每步计量”，不是倍数级加速。

## 1.4.1 修正：扩大样本后，1.4.0 的 1.86× 被证伪（76 对）

1.4.0 发布时的每任务数字只有 5 对，我把 t1/t3 扩到 15 对（另加两个新形状：t9 写扇出、t10 读写混合，各 8 对）之后，结论变了：

- t1：5 对时 1.86× → **15 对时 0.85×**（升级后再测，判为“逐项输出”走原生，回到 **1.02×**）。原因是“一次抓全”把八次读并成一步，但那一步要一次解码整张 per-file 表，输出 token 从 1069 涨到 1197，比省下的轮次贵。
- t3：5 对时 1.77× → **15 对时 1.03×**；步数确实从 4.7 降到 3.5、`llmMs` 从 4578 降到 3752，但墙钟只有 ~8% 的改善，中位数被几个慢跑抵消。
- t9（6 模块独立改写，写扇出）：**0.98×（8 对）**。宽块里写六个独立文件是安全的，但没有变快——模型本来就会在一次里写多个。
- t10（10 日志聚合报告，读写混合）：**1.11×（8 对）**，方向为正但不显著。

合并 76 对：**中位 1.06×，CI 0.996–1.155×，45/76 对变快，均值 1.15×**。也就是说，在本套任务和这条网关上，fast mode 的真实收益是“几个百分点 + 更大的方差”，不是“明显快一截”。

为什么：一步的墙钟 ≈ `720 ms + 6.6 ms × 输出 token + 预填充`；把 N 步压成 1 步，省下 N×0.7 s 的固定开销，却常常让那一步多解码几百 token（≈2 s）。只有“步数下降且单步输出不涨”的形状能净赚，t3 的 shell 扇出是唯一稳定的例子。

1.4.1 因此只留一个行为改动：**逐项输出形状（`one object keyed by file`）判原生**，避免 0.85× 的回归；同时保留 shell 扇出与聚合命令。

## 1.4.0：形状分类 + 一次抓全 + 聚合命令（43 对，2026-10-24）

三项调度改动，全部是程序化并行，不改请求体、不改 `reasoningEffort`：

1. **请求形状分类**（1.3.0 起）：串行/shell/聚合/扇出，第一步就决定宽窄，不再有新会话 4 宽探测。
2. **一次抓全**：已知的目标集合必须在第一个块里抓完，不许为决定读什么而先读一个。
3. **聚合命令**：目标超过约十个或是求和/计数/求交集时，用一条 `rg -c` / `grep -r` / 短脚本，而不是把每个文件都读进上下文；shell 扇出（每目录一个作业）也允许同一块。

口径同前：真实 headless 启动，`company/deepseek-v4.1-flash`，`reasoningEffort: medium`，ABBA 配对，每对同一任务同一 fixture 的两份独立副本；质量 120/120 全部 PASS；表中墙钟是各臂均值。

| 任务（形状） | 墙钟：关 → 开 | 加速比 | 95 % CI |
|---|---|---|---|
| t1：8 文件抽摘要 | 14.9 s → 8.8 s | 1.86× | 0.86–3.77 |
| t2：40 个独立文件计数 | 5.8 s → 6.0 s | 0.98× | 0.82–1.34 |
| t3：6 目录逐个求和（shell 为主） | 8.5 s → 6.6 s | 1.77× | 0.41–2.19 |
| t4：12 模块审计报告 | 9.1 s → 11.0 s | 1.03× | 0.55–1.19 |
| t5：12 模块 × 5 文件查热符号 | 6.8 s → 7.6 s | 1.07× | 0.43–1.35 |
| t6：24 个日志文件按级别求和 | 8.7 s → 5.6 s | 1.43× | 1.00–2.57 |
| t7：18 节点依赖链（必须串行） | 4.4 s → 4.3 s | 1.06× | 0.76–1.30 |
| t8：16 个 manifest 找重复依赖 | 6.9 s → 5.4 s | 1.29× | 0.98–1.61 |

- 43 对合并：**中位加速比 1.09×（10 000 次 bootstrap CI 1.03–1.33×）**，30/43 对变快，均值 1.26×；平均步数 5.5 → 4.7。
- CI 首次不跨 1，但每任务只有 5–8 对，单任务数字只能看方向；t1/t3 的 1.8× 来自“抓全 + shell 扇出”，t2/t4/t7 基本是设计的无操作对照组（聚合、串行、必须串行）。
- 输出 token 均值反而略降（884 → 767/次运行），因为输出纪律同时生效；输入 token 上升，是本版有意用 token 换轮次。

## 1.3.0 pilot：请求形状分类（32 对）

`src/shape.js` 从用户消息本身判断形状：串行词（chain / one by one / 依次 / 逐个）或 shell 词（bash / python3 / 执行）→ 原生；扇出词（independently / every file / across / per file / 每个 / 所有）或 ≥4 个指名目标 → 8；其余原生。新会话不再有 4 宽探测。

- 8 任务 × 4 对 ABBA = 64 次运行；质量 63/64（唯一失败在**对照组** t8，与插件无关）。
- 32 对合并：**中位加速比 1.08×（10 000 次 bootstrap CI 0.88–1.21×），18/32 对变快**，均值 1.11×。
- 分任务（各 4 对，只看方向）：t1 1.16×、t2 1.08×、t3 1.03×、t4 1.20×、t5 0.83×、t6 0.92×、t7 1.32×、t8 0.97×。
- 与 1.2.1 的 80 对相比，点估计 0.96× → 1.08×，t1/t4 不再倒挂；但 CI 仍跨 1，**不构成显著结论**，只是从“有代价”变成“看不出代价、方向为正”。

## 1.2.1：8 任务 80 次运行（相对值，2026-10-24）

这是当前版本唯一权威的一组数据：**8 个多文件任务 × 5 对 ABBA 配对 = 80 次真实 headless 运行**，同 provider（`company/deepseek-v4.1-flash`）、同路由、同 `reasoningEffort: medium`，两臂只差 `fast-mode.enabled`。每次运行使用全新工作目录，确定性 `verify.py` 判定，**80/80 全部 PASS**（每任务两臂各 5/5）。

口径（和 README 一致，务必按这个读）：

- **墙钟**：进程启动到 headless 回合结束的端到端秒数，含模型延迟、工具等待与插件开销。
- **加速比** = `墙钟(关) / 墙钟(开)`，大于 1 才是变快；1.28× 即省 22 % 时间。95 % CI 用 10 000 次配对 bootstrap 得到，跨过 1 表示与关闭不可区分。
- 只报相对值；`ms = 720 + 6.63 × 输出tok` 那类绝对模型是 1.0.0 之前的数据，已不作为本版依据。

| 任务（形状） | 对数 | 质量 | 墙钟 关 → 开 | 加速比 | 95 % CI | 输出 token 比 |
|---|---|---|---|---|---|---|
| t1 8 文件抽摘要 | 5 | 5/5 + 5/5 | 8.6 s → 11.3 s | 0.78× | 0.43–1.30 | 0.96 |
| t2 40 独立文件计数 | 5 | 5/5 + 5/5 | 7.2 s → 6.4 s | 1.14× | 0.93–1.24 | 0.90 |
| t3 6 目录逐个求和 | 5 | 5/5 + 5/5 | 5.0 s → 6.5 s | 0.81× | 0.53–1.20 | 1.13 |
| t4 12 模块审计 | 5 | 5/5 + 5/5 | 8.7 s → 13.1 s | 0.71× | 0.55–1.21 | 1.43 |
| t5 12 模块 × 5 文件热符号 | 5 | 5/5 + 5/5 | 13.9 s → 12.2 s | 1.14× | 0.90–1.82 | 0.81 |
| t6 24 日志文件求和 | 5 | 5/5 + 5/5 | 5.7 s → 5.2 s | 1.28× | 0.68–1.59 | 0.87 |
| t7 18 节点依赖链 | 5 | 5/5 + 5/5 | 4.6 s → 5.6 s | 0.98× | 0.38–1.51 | 1.02 |
| t8 16 manifest 找重复依赖 | 5 | 5/5 + 5/5 | 6.8 s → 6.0 s | 1.04× | 0.68–1.27 | 0.90 |

合并：**中位加速比 0.96×（CI 0.80–1.14×），19/40 对变快。**

怎么读：

- **收益集中在"大量互不依赖的读"**：t2/t5/t6 为 1.14×–1.28×，t5 的步数比 0.62、输入 token 比 0.63。
- **摘要/审计形状会变慢**（t1/t4），说明第一步那 4 宽探测在不需要扇出的任务上仍是净成本；这是本版已知的代价，不是噪声。
- **单轮任务的先天限制**：形状判据来自已经发生的调用，而 8 个任务都在一轮内结束，只能吃到新会话那一次探测。多轮会话的第二轮会继承第一轮的形状，单轮任务没有这个机会。把宽度选择改成读请求入口形状 + 在线 bandit，是下一步。

这份文件回答一个问题，只回答这个问题：**开了这个插件，请求真的更快了吗？**

先把版本交代清楚，因为这份文件的大部分内容是历史：

- 实验 A、B、E、F 全部跑在 **1.0.0 之前**：E 和 F 在 0.8.0 上，A 和 B 更早。那时候每个开启的档位除了「一步多宽」，还带一套从第二步开始逐步下调 `reasoning_effort` 的阶梯。
- 1.0.0 把那套阶梯**整条删了**：`agent/request` 现在只是 await 之后原样返回请求，快速模式**永不改变思考强度**。所以下面凡是「档位更高所以更快」的表格，描述的都是一个已经不存在的东西。
- 保留它们的理由：它们正好解释了那条阶梯为什么该删（实验 B、F 直接测出降档没有用），以及 1.0.0 还剩下什么、还缺哪一块证据。

1.0.0 能诚实说的只有一句话：**同样的思考强度，任务允许的时候更少往返。** 没有百分比。

- 已知的：每步的耗时账记得下来，而且几乎全由输出长度决定（`ms = 720 + 6.63 × 输出tok`，R² = 0.93）。这台路由**不产出思考 token**，所以减思考不是杠杆。
- 历史证据：0.8.0 的 `turbo` 臂（扇出 + 预读 + 输出纪律 + 降档阶梯）在 16 个同任务会话上比 `safe` 快 40 % —— 但那是四样东西的合力，其中降档那一条在 1.0.0 已经不存在了（实验 E）。
- 已知测不出的：干净的单文件任务上，各档位是控制组的 0.95×–1.09×，在噪声内（实验 F）。这不是插件坏了，是那类任务没有「几个互相独立的查询」可以并行。
- **未测**：1.0.0 现在这套（自适应扇出 2–8 + 卡顿取消 + 循环中断）**还没有在 1.0 规模上做过 A/B**。

## 结论速览

| 结论 | 证据 | 强度 |
| --- | --- | --- |
| 单步耗时几乎完全由**输出 token 数**决定 | `ms = 720 + 6.63 × 输出tok`，R² = 0.93，n = 689 | 强 |
| 这条路由**不产出思考 token** | 750 行拟合数据里 `reasoning` 全为 0；同一份实验日志的 1,888 步同样全为 0 | 强 |
| 因此降 `reasoning_effort` 不可能省下解码时间 | 四个档位 ms/token 9.0–10.6；按输出长度回归后，残差都在 ±350 ms 内且符号互相矛盾（实验 B、F） | 强 |
| 档位越高、一步里的并行调用越多（**0.8.0 数据**） | 多调用步占比：关闭 2% → `turbo`/`max` 22–23% | 中 |
| 0.8.0 的 `turbo` 整包比 `safe` 快 40 %（**0.8.0 数据，不是 1.0.0 的预测**） | 同任务 16 会话：中位 Σ步时 25.9 s → 15.6 s，步数中位 7.5 → 4.5，p = 0.008；其中降档那一条 1.0.0 已删除 | 强（n 小、两臂不均，见实验 E） |
| 1.0.0 剩下的整包（扇出 + 卡顿取消 + 循环中断）快多少 | 没有测过 | **未测** |
| 公开单文件题上，各档位落在控制组的 0.95×–1.09× | HumanEval 100 会话 + AIME 39 会话（0.8.0） | 强（只证明「该形状测不出差异」） |
| 1.5.1 默认臂整任务墙钟 **0.72–0.74×**，输出 **0.76×**、步数 **0.59×**，有效单元零红 | 1.5.1 重基线 + W3 + W5 + W7（65 个有效单元合并 0.742×），W8b 逐格中位 0.717× | 强（四块同向，门禁全绿） |
| 墙钟 ≈ 0.97 × 输出 token（bench 实测斜率 13.5 ms/token，步数项 6 %） | W8b `off` 臂 19 格回归，逐格残差中位 18 % | 强 |
| 输出里 23 % 是工具调用参数，而这部分基本是**在建交付物**（整文件重写的重复量只有 0.8 %） | 134 个 `def15` 格参数占比统计 + 15 会话 153 次 `write` 逐路径比对 | 强 |
| ~~还剩的杠杆是**重复验证步**~~（已否证：BV8 合并 1.188×、步数 1.229×） | 145 个 `def15` 会话的调用分类 + BV8 16 对 | 已测，方向不成立 |
| 0.67× 需要推理再降 15 %（等价于步数 0.585× → 约 0.50×） | W8b-C 的算术：参数项压不动，只能压推理 | 强（算术），达成方式**未验证** |

一句话：**在这台自建网关的 `company/deepseek-v4.1-flash` 上，拧 `reasoning_effort` 不会让生成变快，因为这条路由根本不思考；1.5.1 的默认臂已经把整任务墙钟压到 0.72–0.74×，而墙钟几乎等于输出 token、输出里 23 % 是必须写出来的交付物，所以不碰推理档位的前提下，剩下的空间只够再挤「重复验证的步数」。**

## 方法

- **比较单位是「步」，不是会话。** 会话墙钟里大部分是人在读和打字（本机 12 轮里，生成只占 59%），拿会话时长比快慢等于在测打字速度。
- 每步记一行 JSONL：`llmMs`（`step/start` → 最后一条 `assistant/message`）、`stepMs`、`output`/`input`/`cacheRead`/`reasoning`、`calls`、`effort`、`mode`/`gear`、`route`。
- **分档不靠手点**：实验文件按 `FNV-1a(会话 id) mod 档位数` 分配，同一个会话每次进来都落在同一档；`off` 是合法的对照组。手点分档会让"我觉得这档快"悄悄决定结果。
- 关闭状态的会话也照常记录，否则没有对照组。
- **来源**：A、B、E、F 四组实验都在 1.0.0 之前跑。当时开启的档位是「宽度 + 输出纪律 + 逐步降 effort」的整包，所以表格里写的「档位」是这个整包，不是单一变量。1.0.0 的档位只剩宽度，没有 effort 这一层。

## 实验 A：被自己的工具结果污染了

六个会话、24 步、同一个任务。其中一个收到了 **33,763 字符**的工具结果，触发 `bigChars`，把 `signals.struggling` 置上，于是后面几步的 effort 被钉住。两组不可比，实验作废。

它暴露的是当时的真实缺陷：一次 `git diff` 就能让降档卡住六步。1.0.0 里这条路径连同整个 effort 策略一起删了（`bigChars` / `struggling` 在代码里已经不存在），所以这个 bug 不再存在。留在这里是因为它说明「用一个观察信号直接决定策略」有多脆 —— 1.0.0 的调度器仍然读会话观察，但改的是宽度和上限，不是用户选的思考强度。

## 实验 B：控制住变量之后

八个独立会话，同一提示词、同一模型、同一台机器、同一分钟启动，每个三步，每步以 `BENCH-DONE` 结束。四个臂：`off` ×3、`safe` ×3、`turbo` ×1、`max` ×1 —— 共 24 步。

| 档位 | 步数 | effort（第 1→3 步） | LLM p50 | 输出 tok/步 | **ms/token** |
| --- | --- | --- | --- | --- | --- |
| `off`（对照） | 9 | `medium` / `medium` / `medium` | 1978 ms | 247 | 9.08 |
| `safe` | 9 | `medium` / `low` / `low` | 1974 ms | 227 | 9.11 |
| `turbo` | 3 | `medium` / `low` / `low` | 2262 ms | 254 | 9.01 |
| `max` | 3 | `medium` / `low` / `low` | 2216 ms | 220 | 9.47 |

插件确实降了档 —— `safe`/`turbo`/`max` 从第 2 步起把 `medium` 降到 `low`，对照臂一直停在 `medium`。但它没有变成任何东西：每 token 成本一致（9.01–9.47，5% 的散布），平均 LLM 时间也一致（2244 / 2071 / 2293 / 2081 ms）。**这张表现在的作用是反向的：它是 1.0.0 删掉降档的依据。** 一条能把 effort 从 `medium` 走到 `low`、却一分钟都没省下来的机制，正确的处理是移除，而不是再调低一档。

## 实验 E：0.8.0 的三个杠杆真的更快

> **先说边界：这是 0.8.0 的整包结果，不是 1.0.0 的预测。** 治疗臂 `turbo` = 扇出目标 6 + 投机预读 + 完整输出纪律 **+ 逐步降 effort 的阶梯**。−40 % 是这四样东西的合力，实验 E 没有把它们拆开；其中 effort 那一条在 1.0.0 已经不存在了。

实验 A 和 B 只证明了 effort 不是杠杆。实验 E 检验的是 0.8.0 真正换上的东西：**更宽的步（扇出 + 预读）、更少的步（少往返）、更短的步（输出纪律）**。

- **任务有可核对的答案**：读一个仓库，报四件事 —— ① `test/` 下每个文件的 `test(` 数，② `src/` 下每个文件的行数，③ `package.json` 的版本 / 导出键数 / 依赖，④ `git log -3` 和 `git status --porcelain`。
- **16 个独立会话，两波各八个**，同一路由（`company/deepseek-v4.1-flash`）、同一提示词、同时启动。分档同样按 `FNV-1a(会话 id) mod 2` 分配 —— 没有人手点。
- 对照 `safe`（不注入 guidance，即插件 0.8.0 之前的行为）；治疗 `turbo`（扇出目标 6 + 投机预读 + 完整输出纪律 + 降档阶梯）。

**结果（每会话 Σ step time = 该会话的 `stepMs` 之和）**

| 指标 | `safe` (n=4) | `turbo` (n=12) | 变化 |
| --- | --- | --- | --- |
| Σ步时，中位 | 25.9 s | **15.6 s** | **−40 %** |
| Σ步时，均值 | 25.7 s | 17.4 s | −33 % |
| 步数，中位 | 7.5 | **4.5** | **−40 %** |
| 输出 token/会话，中位 | 3,798 | **2,180** | −43 % |
| 输出 token/步 | 510 | 535 | +5 %（一步里干更多活，这是预期的） |
| 工具调用/步 | 1.41 | 1.37 | 持平 |
| Mann–Whitney U（Σ步时） | — | — | U = 46, z = 2.67, **p = 0.008** |

> `输出 token/会话` 那一行是整包的副产物（步少了，总量自然少），**不是 1.0.0 的目标**；1.0.0 明确接受更多 token，见后面「1.0.0 改了什么」。

两波方向一致：第一波 −32 %，第二波 −40 %。单会话在 3–8 步之间，所以这个差不是某一个离群值拖出来的中位数。

**质量没掉。** 16 个会话报的 ①②③ 完全一致（唯一看着不一致的 175 vs 173，是字面 `test(` 数和真实用例数的差别，两个都对）。④ 的差异只是我一边跑一边在改 README，脏文件数从 7 变成 8。这是一个确定性的事实题，十个数字全对上了。

**诚实的读法**

- **扇出的直接证据反而没有出现**：calls/step 1.41 → 1.37，没升。这 40 % 不是「每步更宽」挣来的，而是「步更少」（7.5 → 4.5）和「每步更短」。任务只有四个查找，对照臂本来就已经一步两个调用了，没有多少可并的余地。实验 E 也没有单独测「只开扇出、不降 effort」那一臂，所以 −40 % **既不能记在扇出头上，也不能记在降档上**（B 和 F 都说降档是 0）—— 它是一个拆不开的整包。
- 两臂人数不均（4 vs 12），是 `FNV-1a` 哈希分档的运气问题，不是挑选。n 也小（16 个会话、29 vs 54 步）。
- 实验期间有 1 个**非实验**会话在同时跑（07:21–07:37），两臂平均受它干扰，但它不进统计。
- `turbo` 的并行目标是内置的 6。1.0.0 的 `auto` 改成 2–8 自适应、默认目标 4；宽度本身没有单独做过对照。

## 实验 F：公开 benchmark 上的对照（HumanEval 100 会话，AIME 2025 39 会话）

实验 E 只有 16 个会话，而且是一个仓库任务。为了回答"换个任务还成不成立"，又跑了两组公认的公开题。**结论先写在前面：这两个 benchmark 在结构上就测不出 fan-out，所以它们能证伪的东西很少 —— 但它们证伪了"档位普遍更快"这个说法。**

**来源**：这轮同样跑在 0.8.0 上，每个开启的臂都带着降 effort 的阶梯。那条阶梯解释不了任何差异（每一行 `reasoning` 都是 0），但下面几张表里的「档位」仍然是整包，数字是历史，不是 1.0.0 的预测。

### 设计

- **任务**：HumanEval 前 10 题（题面全文给出，实现写进各自目录的 `solution.py`，用官方 `test` 本地跑通）；AIME 2025 前 10 题（最终整数写进 `answer.txt`，与官方答案比对）。
- **一题一个沙箱**：每题独立目录、独立进程、独立输出文件，互不可见；跑完逐题校验，不靠模型自述。
- **档位由 hash 分配**，不是手点：`FNV-1a(会话 id) mod 档位数`，`off` 是合法对照臂。手点会让"我觉得这档快"悄悄决定分组。
- **AB 轮转抵消时段与缓存**：每波 10 个会话同分钟启动，波内五档轮转，波与波之间重排档位。会话级取中位数，再跨会话取中位数（两级中位数，避免长尾把均值拖走）。
- **缓存变量单独看**：prompt cache 在所有臂上命中规模相同（`cacheRead` 80–85 k/会话，平的），所以"总 token"只算 `input + output`（新 token），`cacheRead` 单列 —— 否则 91–92% 的数字是缓存读，档位差异会被稀释成噪声。

下面三张表只保留原始中位数。旧版还有"最快于 safe"和"新 token ×"两列倍率，**已经去掉**：按档位切出来的单步中位数不是加速比，token 倍率也不是 1.0.0 要下的结论。**同一档位两轮之间就能漂 ±11 %**（见下面第 2 条），所以任何一位小数上的"更快"都不该发布。

### HumanEval（100 会话，10 波 × 10 题，每波 10/10 通过）

RUN A（第 1–6 波，60 会话）：

| 档位 | n | 步数/会话 | Σ步时 中位 | 步时 中位 | 输出/步 | 新 token/会话 | cacheRead | 调用/步 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `safe` | 14 | 4.2 | 6 754 ms | 1 519 ms | 481 | 550 | 83 744 | 0.80 |
| `fast` | 11 | 4.0 | 5 369 ms | 1 342 ms | 313 | 455 | 82 816 | 0.75 |
| `turbo` | 9 | 4.2 | 6 793 ms | 1 491 ms | 440 | 650 | 84 224 | 0.79 |
| `max` | 12 | 4.2 | 5 394 ms | 1 310 ms | 346 | 430 | 83 040 | 0.80 |
| `ludicrous` | 14 | 4.1 | 5 370 ms | 1 342 ms | 315 | 442 | 80 384 | 0.84 |

RUN B（第 7–10 波，40 会话）：

| 档位 | n | 步数/会话 | Σ步时 中位 | 步时 中位 | 输出/步 | 新 token/会话 | cacheRead | 调用/步 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `off` | 9 | 4.4 | 6 411 ms | 1 361 ms | 337 | 464 | 83 712 | 0.82 |
| `safe` | 12 | 4.3 | 6 064 ms | 1 364 ms | 423 | 537 | 83 776 | 0.78 |
| `turbo` | 9 | 4.0 | 5 554 ms | 1 388 ms | 341 | 466 | 85 376 | 0.78 |
| `ludicrous` | 10 | 3.9 | 5 656 ms | 1 420 ms | 339 | 469 | 84 544 | 0.85 |

### AIME 2025（39 会话，4 波）

| 档位 | n | 步数/会话 | Σ步时 中位 | 步时 中位 | 输出/步 | 新 token/会话 | cacheRead | 调用/步 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `off` | 9 | 5 | 19 357 ms | 2 047 ms | 234 | 2 810 | 103 936 | 0.80 |
| `safe` | 4 | 4.5 | 13 494 ms | 2 105 ms | 197 | 1 396 | 93 216 | 0.78 |
| `fast` | 8 | 5 | 16 572 ms | 2 510 ms | 184 | 1 724 | 107 552 | 0.80 |
| `turbo` | 8 | 4 | 10 147 ms | 1 894 ms | 136 | 968 | 81 248 | 0.75 |
| `ludicrous` | 10 | 6 | 12 432 ms | 2 007 ms | 148 | 1 252 | 122 080 | 0.90 |

`fast` 的 Σ步时中位（16 572 ms）比 `safe`（13 494 ms）还高，只是因为 `safe` 只有 4 个会话 —— 这正是这张表不报倍率的原因。

### 这两个 benchmark 教给我们的事

1. **步数被任务形状钉死。** HumanEval 每题 4–5 步，AIME 4–6 步，92 % 的会话 ≤ 15 步。fan-out 的收益随"独立查询的条数"增长，而单文件题的条数是 1 —— 所以**不是档位没效果，是这个任务没有效果可给**。
2. **`off` 和所有开启档位在噪声内。** HumanEval RUN B：`off` 0.95×、`turbo` 1.09×、`ludicrous` 1.07×（相对 `safe`）；AIME 的散布更宽（0.70×–1.33×）。而同一档位在两轮之间的漂移就有这么大（`safe` RUN A 6 754 ms vs RUN B 6 064 ms，±11 %）。**组间差 ≤ 组内漂移，就不下结论。**
3. **唯一稳定的可见差异是输出长度，而它没有变成速度。** `safe` 在两组里都是最啰嗦的一臂（输出/步 481、423），其余臂更低。方向与"输出纪律"一致，但它没有穿过上面那 ±11 % 的漂移；而且 1.0.0 不卖"省 token"这件事 —— 一步变宽会读进更多上下文，token 变多是接受的代价，不是回归。
4. **质量没有掉。** HumanEval 100 会话逐题跑官方 `test`，每波 10/10 通过；AIME 4 波分别是 9/1、9/1、8/2、7/3（通过/未通过）。没有一例因为档位而变得更差。
5. **AIME 这一轮暴露了真正的墙钟大敌 —— 不是档位，是长尾。** 39 个会话里有 5 个在执行被 task board 判超时后仍在写，单会话 550–597 s、77–132 步。把它们剔除后，`ludicrous` 的中位 Σ步时从 12 432 掉到 6 633 ms、均值从 178 s 掉到 12 s；`fast` 臂的中位从 16 572 掉到 12 840 ms。**中位数能活下来，均值活不下来 —— 但这几对数字也说明中位数不能当加速比发布**：5 个尾部会话就能让同一档的中位挪一倍，两个档位之间 0.95×–1.09× 的差在这些数字面前没有意义。这也是 0.9.0 起加"单次调用上限"、1.0.0 保留它的原因：真正吃掉时间的是少数几个停不下来的调用，而不是每步多 100 ms。

**所以这张表能认证什么、不能认证什么**：能认证"公开题上不掉质量、`off` 不慢"；不能认证"档位让公开题变快"。fan-out 的证据仍然只有实验 E 那一个 —— 而且是 0.8.0 的整包，没有单独隔离扇出的那一臂。把两组结论放在同一页，而不是只挑好看的那组 —— 这才是可复现的意思。

原始日志：[`docs/bench-raw-2026-10-24.jsonl.gz`](docs/bench-raw-2026-10-24.jsonl.gz) —— 这 179 个实验会话（含试点、实验 E 和 F）逐步骤的 `ts / sid / gear / route / ttftMs / llmMs / stepMs / calls / errors / input / output / cacheRead / cacheWrite`。会话本身和逐题沙箱在跑完后已经删掉，上面每个数字都能只靠这份日志、按声明的约定（两级中位数、波内轮转）重算出来。

## 为什么没效果：延迟模型

把 689 步全放进去做最小二乘，`llmMs` 对输出 token 数的拟合是：

```
ms = 720 + 6.63 × 输出tok        R² = 0.93
```

- **720 ms 的固定开销**（首字 + 排队 + 预处理），与输出长短无关。
- **6.63 ms/token** 的解码速度，约 150 tok/s。
- 输出长度解释了 93% 的耗时差异。剩下 7% 才是工具、路由、调度能碰的地方。

于是一步的墙钟是三项之和：

```
墙钟 ≈ 720 ms × 步数 + 6.63 ms × 输出 token + 工具等待
```

> **修正（1.5.1 隔离复测）**：上式的步数项在隔离基准里被高估。干净会话拟合是 `墙钟 ≈ 590 ms × 步数 + 6.0 ms × 输出 token`（TTFT 0.59 s/步，解码占 `llm` 的 93 %），即**步数几乎免费、输出 token 才是主项**；这正是后文“减少输出 token 是唯一量级杠杆”的实测依据。

前两项来自那 689 步的拟合；第三项不在拟合里（它是 `stepMs − llmMs`），也正是长尾和卡顿待的地方 —— 1.0.0 的单次调用上限就是冲它去的。

再看 effort 的残差（扣掉输出长度影响后的平均偏差）：

| effort | 步数 | 平均输出 | 平均耗时 | 残差 |
| --- | --- | --- | --- | --- |
| `minimal` | 45 | 625 | 5356 ms | **+488 ms** |
| `low` | 451 | 534 | 4152 ms | −110 ms |
| `medium` | 87 | 402 | 3347 ms | −39 ms |
| `high` | 106 | 500 | 4328 ms | +292 ms |

中位残差全部落在 ±350 ms 内，符号还互相矛盾（最"省"的 `minimal` 残差最正）。**这不是快慢，这是噪声。**

直接证据更简单：750 行拟合数据里 `reasoning` 全为 0；同一份实验日志的 1,888 步也全为 0 —— 四个臂、五个档位，一个例外都没有。这条自建路由不产出思考 token，**所以降 effort 不可能省下任何解码时间：没有可省的解码。** 插件的降档逻辑没有写错，它只是在一个没有思考可省的路由上工作。

这正是 1.0.0 **删掉**而不是**再调低一点**的原因：在一个不产出思考的路由上把 effort 重新标定一次，仍然是在改用户的问题，而不是在改回答的速度；而且改写会随请求头变成宿主的"最近使用档位"，用户选的 Medium 会漂到 Low 并跨重启留在那里（0.9.x 之前确实如此，1.0.0 的 CHANGELOG 记了这条）。删掉之后，请求是纯透传。

顺带一个反直觉的数字：输入侧几乎免费。全部 750 步里 `cacheRead` 累计 4,086 万 token，而真正新读入的 `input` 只有 2.4 万 —— 提示缓存把输入侧抹平了，所以"省输入"也不是一个有用的方向。反过来，1.0.0 让一步变宽时多读的那点输入，在这本账上很便宜。

## 1.0.0 改了什么

1. **删掉的**：`floor`、`decideEffort`、每步降一档、`ludicrous` 发 `reasoningEffort: 'off'`、面板上的档位选择器。`agent/request` 现在只做一件事：`const resolved = await next()` …… `return resolved`。代码里唯一还读 `reasoningEffort` 的地方是把它记进指标（记录用户选了什么），从不写回。测试里有一条扫描源码、保证不再出现 effort 改写。
2. **留下的只有两类**，都只影响"同样的思考怎么走完"：
   - **让一步更宽**：`auto` 的扇出目标从 4 起，顺利两步且上一步至少 2 个调用时放到 8，报错或无进展时收窄到 2 —— 它只是"目标"，模型自己决定发几个；输出纪律只收叙述、不收答案（`light` / `full`）。
   - **让停不下来的停下来**：单次调用上限在 `auto` 下从 120 s 起，按本会话最近调用时长的 p90 × 3 往上抬、封顶 300 s（`safe` 不设限）；到点取消调用，并把"换个做法"写回给模型。循环中断：连续 6 步没有新东西 → 警告改法；12 步 → 要求停下报告；一轮 30 步同样触发。
3. **这套组合没有在 1.0 规模上做过 A/B。** 实验 E 的 −40 % 是 0.8.0 的整包，其中 effort 那一层已经被删了；实验 F 说明单文件题上本来就测不出。所以对 1.0.0 我们只有机制和方向，没有百分比。**任何把 −40 % 当成 1.0.0 数字的写法都是错的。**
4. **诚实的话术**：「同样思考强度，任务允许时更少往返」。不要说"快 40 %"，也不要说"省 43 % token" —— 后一句在 1.0.0 的方向上是反的。
5. **token 是交易，不是回归。** 一步更宽意味着一次读入更多上下文，token 可能变多；1.0.0 明确接受这一点并写进了 CHANGELOG。要报 token，必须报在"同任务、同质量、同思考强度"的 A/B 上，否则报的只是任务形状的差。
6. **下一次该测什么**：多仓库、多文件任务，每步有好几个互相独立的查询（例如一次看 5 个仓库的版本、5 个目录的测试数、5 份文件的导出）。对照用 `--gears off,safe,auto`（1.0.0 的默认是 `auto`，不是 `turbo`）；指标用 calls/step、steps/session、Σ step time，并把工具等待单列。单文件题上测不出差异是**预期**，不该再拿它当反例或正例。

## 那什么才真的更快

延迟模型自己给出了答案：`720 ms × 步数 + 6.63 ms × 输出 token + 工具等待`。可动的量只有两个，都不是"解码速度"。

| 杠杆 | 现状（本机，1.0.0 之前的日志） | 1.0.0 动不动 |
| --- | --- | --- |
| **步数** | 关闭态每轮 12.9 步；一步固定烧掉 720 ms | 动：更宽的步 + 循环中断，目标是十几次试探并成几次 |
| **工具等待** | 4,480 s 总耗时里 1,536 s 在工具里；bash p90 5.5 s、最长一次 818 s | 动：并行摊开，加上唯一一个硬上限（单次调用到点取消） |
| 输出 token | 平均 ~500 tok/步 | 只动叙述，答案不动；**不承诺总量下降** |
| 解码速度 | ~150 tok/s | 不动（1.0.0 连 effort 都不碰） |
| 首字 | 720 ms 里的固定部分 | 不动（没有流式首字信号可测，见下） |

旧版这里写的是"并行把 token 从 58 k 推到 142 k，这笔账是亏的"。1.0.0 不再这么算：token 是买墙钟的货币，账要记在同任务、同质量、同思考强度的 A/B 上，而那个 A/B 还没有做。能确认的只有一个方向性结果：0.8.0 的 16 个同任务会话少用 3 步、少 40 % 墙钟，代价是每步多 5 % 输出。它说明方向，不构成 1.0.0 的证据。

## 这份实测的局限

- **一条路由**。`company/deepseek-v4.1-flash` 经自建网关；官方 DeepSeek 路由声明了 `off/low/high/max` 并有真实的思考 token，结论不能直接搬过去。
- **样本小**。实验 B 每档 1–3 个会话，够看出"没有差异"，不够看出"差异是 3%"。
- **没有 1.0.0 的 A/B**。A/B/E/F 全部是 1.0.0 之前的；1.0.0 的扇出、卡顿取消、循环中断从未在同一个任务上与 `safe` 对照过。这是这份文档最大的缺口。
- **所有档位数字都是旧整包的**。实验 E 的 −40 % 和实验 F 的 0.95×–1.09× 都带着降 effort 那一层；去掉之后会变成什么样，没有测过。
- **首字是近似值**。`assistant/message` 每步只提交一次，所以 `ttftMs` 就是 `llmMs`，不是真正的流式首字。这一栏目前只能当"整步耗时"读。
- **实验 F 的臂不均、有长尾**。档位由哈希分配，`safe` 只有 4 个会话而 `off` 有 9 个；5 个 550–597 s 的超时会话能把同一档的中位数挪一倍。

## 复现

```bash
# 仓库里直接跑，等价于安装后的 dsh-fast-mode-experiment / dsh-fast-mode-report
node bin/experiment.mjs start --gears off,safe,auto   # 1.0.0 该测的对照：关 / 只有边界 / 自适应整包
node bin/experiment.mjs status                        # 每档已有多少会话/步
node bin/report.mjs --since 2d                        # 两张表 + 相对关闭态的倍率
node bin/experiment.mjs stop                          # 结束，之后的会话回到插件配置
```

- `--gears` 不传时默认是 `off,safe,fast,turbo,max`；A/B/E/F 那几轮用的就是这套固定宽度臂（`--gears safe,turbo` 是实验 E 的最小复现）。1.0.0 里实验臂会按名字原样生效（包括 `off`），`turbo`/`max` 仍是固定宽度 6/8，但**不再降 effort** —— 所以重跑得到的是"扇出 + 纪律 + 卡顿上限"，数字不会等于当年的 −40 %。
- 报告的两个倍率表在**任务形状不一致**时不要读：本机那两张全量表里 `fast` 显示"模型耗时 0.89×"，那是它恰好落在长输出会话里的结果，不是加速。要比就比实验 B 那种同任务、同步数的小样本，或者直接用上面的延迟模型做残差比较 —— 后者对任务形状免疫。
- 实验 E 的最小复现：`--gears safe,turbo`，然后并发跑两波、每波 8 个同提示词的会话，最后按 `gear` 分组算每会话的 `ΣstepMs` 中位数。原始行在 `~/.dsh/fast-mode-metrics.jsonl` 里（本机存在），按 `sid` 分组即可重算，不需要重跑模型。
- 0.8.0 那一轮的原始日志在 [`docs/bench-raw-2026-10-24.jsonl.gz`](docs/bench-raw-2026-10-24.jsonl.gz)（已随仓库提交），按上面的约定可以重算实验 E 和 F 的每一个数。
- 测多仓库任务时，不要直接读 `dsh-fast-mode-report` 的全量倍率表当结论；先按任务分组，再比同组内的中位数。

---

# Measured: where it is faster, and where it is not

> **The default changed in 1.5.0, so every number in this file is history or ablation evidence — not the 1.5.0 speed claim.** 1.5.0 sets both `finishLineSteps` and `stepBudget` to `0` (off), so the 1.86× in the 1.4.4 section below is an **ablation on a retired default (`finishLineSteps: 10`)**; `fl10` is now just an ablation arm, not the shipping configuration. A re-baseline under the 1.5.0 defaults is still running and will be added here when it lands. Three limits apply to everything below: the 272 runs include tuning rounds (not a held-out sample); the 7 fixture families and the runner are not published (they lived in a local `/tmp` directory); and every measurement came from a local `deepseek-v4.1-flash` gateway, so absolute values need not transfer to another route. The step-bucket table in section J also covers only 12 of the 14 pairs — the other 2 fall outside the four buckets, and the E1 raw logs were never committed (the raw JSONL has no `arm` field and no `fl10` rows), so those pairs cannot be recomputed from what is in the repo.

One question, one answer: **did the request actually get faster?**

Version first, because most of this file is history:

- Experiments A, B, E and F all predate **1.0.0**: E and F ran on 0.8.0, A and B earlier still. Back then every enabled gear carried a ladder that lowered `reasoning_effort` from the second step of a turn, on top of "how wide is one step".
- 1.0.0 deleted that ladder outright: `agent/request` now awaits and returns the resolved request unchanged, and fast mode **never touches the thinking effort**. Every table below that reads "higher gear, faster" describes a bundle that no longer exists.
- They stay because they are the evidence for deleting it (B and F measured the downshift doing nothing), and because they show what 1.0.0 kept and which measurement is still missing.

The only honest 1.0.0 claim: **same thinking effort, fewer round trips where the task allows them.** No percentage.

- Known: the per-step ledger, and that step latency is almost entirely output length (`ms = 720 + 6.63 × outTok`, R² = 0.93). This route emits no reasoning tokens, so trimming thought is not a lever.
- Historical: 0.8.0's `turbo` arm (fan-out + prefetch + output discipline + effort ladder) beat `safe` by 40 % on 16 same-task sessions — a bundle of four things, and the effort one is gone (Experiment E).
- Known to be unmeasurable here: on clean single-file tasks gears land at 0.95×–1.09× of control, inside the noise (Experiment F). That is the task shape having no independent queries to fan out, not the plugin failing.
- **Unmeasured**: 1.0.0's current bundle (adaptive fan-out 2–8 + stalled-call cancellation + loop breaking) has never been A/B measured at 1.0 scale.

## Summary

| Claim | Evidence | Strength |
| --- | --- | --- |
| Step latency is almost entirely output tokens | `ms = 720 + 6.63 × outTok`, R² = 0.93, n = 689 | strong |
| This route emits no reasoning tokens | `reasoning` is 0 in all 750 fit rows; also 0 in all 1,888 steps of the same experiment log | strong |
| So lowering `reasoning_effort` cannot save decode time | per-token latency 9.0–10.6 ms across four gears; residuals within ±350 ms with contradictory signs (B, F) | strong |
| Higher gears fan out more tool calls per step (**0.8.0 data**) | multi-call steps: off 2% → `turbo`/`max` 22–23% | medium |
| 0.8.0's `turbo` bundle beat `safe` by 40 % (**0.8.0 data, not a 1.0.0 prediction**) | 16 same-task sessions: median Σ step time 25.9 s → 15.6 s, median steps 7.5 → 4.5, p = 0.008; the effort layer is deleted in 1.0.0 | strong (small n, uneven arms — see Experiment E) |
| How much 1.0.0's remaining bundle (fan-out + stall cancellation + loop breaking) is worth | not measured | **unmeasured** |
| On public single-file suites every gear sits at 0.95×–1.09× of control | HumanEval 100 + AIME 39 (0.8.0) | strong (only that the shape cannot show a difference) |

In one line: **on `company/deepseek-v4.1-flash` behind a self-hosted gateway, turning `reasoning_effort` up does not make generation faster, because this route does not think.** The only levers left are fewer round trips and less spinning; 1.0.0 does exactly those two, and how much they are worth together has not been measured.

## Method

- The unit is the **step**, not the session: most of a session's wall clock is a human reading and typing (generation was 59% of step time across 12 turns here).
- Each step appends one JSONL row: `llmMs`, `stepMs`, `output`/`input`/`cacheRead`/`reasoning`, `calls`, `effort`, `mode`/`gear`, `route`.
- **Arms are assigned, not clicked**: the experiment file assigns by `FNV-1a(session id) mod gears`, so a session keeps its arm and nobody's hunch picks it. `off` is a legal control arm. Sessions with the switch off are recorded too, otherwise there is no baseline.
- **Provenance**: A, B, E and F all predate 1.0.0. An enabled arm was then the bundle "width + output discipline + a per-step effort downshift", so "gear" in those tables means the bundle, not one variable. A 1.0.0 gear is width only.

## Experiment A: contaminated by its own tool output

Six sessions, 24 steps, identical task. One of them received a **33,763-character** tool result, tripping `bigChars`, which set `signals.struggling` and pinned the effort for the following steps. The comparison is void, and it exposed a real plugin defect: one `git diff` can block downshifting for six steps.

That path is gone with the rest of the effort policy (`bigChars` / `struggling` no longer exist in the code), so the bug is gone too. It stays here because it shows how brittle "one observation directly sets the policy" is — 1.0.0's scheduler still reads session observations, but it changes width and ceilings, never the thinking effort the user chose.

## Experiment B: variables controlled

Eight independent sessions, one prompt, one model, one machine, started in the same minute. Three steps each, every one ending in `BENCH-DONE`. Four arms: `off` ×3, `safe` ×3, `turbo` ×1, `max` ×1 — 24 steps total.

| Gear | Steps | Effort (step 1→3) | LLM p50 | Output tok/step | **ms/token** |
| --- | --- | --- | --- | --- | --- |
| `off` (control) | 9 | `medium` / `medium` / `medium` | 1978 ms | 247 | 9.08 |
| `safe` | 9 | `medium` / `low` / `low` | 1974 ms | 227 | 9.11 |
| `turbo` | 3 | `medium` / `low` / `low` | 2262 ms | 254 | 9.01 |
| `max` | 3 | `medium` / `low` / `low` | 2216 ms | 220 | 9.47 |

The plugin did downshift — `safe`/`turbo`/`max` drop `medium` → `low` from step 2 while the control stays at `medium`. But it bought nothing: the per-token cost is identical (9.01–9.47, a 5% spread), and so is mean LLM time (2244 / 2071 / 2293 / 2081 ms). **The table now works in reverse: it is the reason 1.0.0 deleted the downshift.** A mechanism that can walk effort from `medium` to `low` without saving a single minute should be removed, not tuned one notch lower.

## Experiment E: 0.8.0's three levers are actually faster

> **Boundary first: this is 0.8.0's bundle, not a 1.0.0 prediction.** The treatment arm `turbo` = fan-out target 6 + speculative prefetch + full output discipline **+ the per-step effort ladder**. The −40 % is the four together; E did not isolate them, and the effort one is gone in 1.0.0.

Experiments A and B only proved that effort is not a lever. Experiment E tests the levers 0.8.0 does pull: **wider steps (fan-out + prefetch), fewer steps (fewer round trips), shorter steps (output discipline)**.

- **Identical task with a checkable answer**: read one repository and report four things — ① per-file `test(` counts under `test/`, ② per-file line counts under `src/`, ③ `package.json` version / export-key count / dependencies, ④ `git log -3` and `git status --porcelain`.
- **16 independent sessions in two waves of eight**, same route (`company/deepseek-v4.1-flash`), same prompt, started concurrently. Arms again assigned by `FNV-1a(session id) mod 2` — nobody clicked anything.
- Control `safe` (no guidance section injected, i.e. the plugin's pre-0.8.0 behaviour); treatment `turbo` (fan-out target 6 + speculative prefetch + full discipline + effort ladder).

**Results (per-session Σ step time = sum of that session's `stepMs`)**

| Metric | `safe` (n=4) | `turbo` (n=12) | Change |
| --- | --- | --- | --- |
| Σ step time, median | 25.9 s | **15.6 s** | **−40 %** |
| Σ step time, mean | 25.7 s | 17.4 s | −33 % |
| Steps, median | 7.5 | **4.5** | **−40 %** |
| Output tokens per session, median | 3,798 | **2,180** | −43 % |
| Output tokens per step | 510 | 535 | +5 % (a fatter step is the point) |
| Tool calls per step | 1.41 | 1.37 | flat |
| Mann–Whitney U (Σ step time) | — | — | U = 46, z = 2.67, **p = 0.008** |

> The `output tokens per session` row is a by-product of the bundle (fewer steps, smaller total), **not a 1.0.0 goal**: 1.0.0 explicitly accepts more tokens. See "What 1.0.0 changes" below.

Both waves agree in direction: −32 % in wave one, −40 % in wave two. Individual sessions range from 3 to 8 steps, so the gain is not one outlier dragging the median.

**Quality did not drop.** All 16 sessions reported items ①②③ identically (the one apparent disagreement, 175 vs 173, is the literal `test(` count versus the real test-case count — both correct). Item ④ differed only because I was editing the README while they ran, so the dirty-file count moved from 7 to 8. This was a deterministic fact task and all ten numbers landed.

**Honest reading**

- **The direct fan-out evidence is missing**: calls per step went 1.41 → 1.37, i.e. it did not rise. The win came from fewer steps (7.5 → 4.5) and shorter steps. The task has four lookups and the control arm already batched two calls into one step, so there was little to merge. E also had no arm that isolated fan-out from the effort ladder, so the −40 % **cannot be credited to fan-out, and cannot be credited to the downshift** (B and F say that was zero) — it is one bundle that cannot be split.
- The arms are uneven (4 vs 12) because of the `FNV-1a` hash, not because of selection, and n is small (29 vs 54 steps).
- One **non-experiment** session ran alongside (07:21–07:37). It hits both arms equally and is excluded from the statistics.
- `turbo`'s built-in fan-out target is 6. 1.0.0's `auto` replaces it with adaptive 2–8 starting at 4; the widths themselves were never compared.

## Experiment F: the same comparison on public benchmarks (HumanEval 100 sessions, AIME 2025 39 sessions)

Experiment E had 16 sessions and one repo task. To ask whether it generalises, two
well-known public suites were run. **The answer up front: both are structurally
unable to exercise fan-out — but they do falsify "every gear is faster".**

**Provenance**: this run is also from 0.8.0, where every enabled arm still carried
the effort ladder. That ladder cannot explain any of the differences in either
direction (`reasoning` is 0 in every row), but the arms are still bundles, so the
numbers below are history, not a 1.0.0 prediction.

### Design

- **Tasks**: the first 10 HumanEval problems (full statement given; the solution
  goes into its own `solution.py`, checked with the official `test` locally) and
  the first 10 AIME 2025 problems (final integer into `answer.txt`, compared with
  the official answer).
- **One sandbox per problem**: own directory, own process, own output file; every
  answer is checked, never taken on the model's word.
- **Arms are hashed, not clicked**: `FNV-1a(session id) mod gears`; `off` is a
  legal control. Clicking lets a hunch pick the groups.
- **AB rotation cancels time-of-day and cache drift**: ten sessions start in the
  same minute per wave, gears rotate inside the wave and are reshuffled between
  waves. Per-session medians, then a median across sessions — a two-stage median,
  so a long tail cannot drag the mean.
- **Cache is looked at separately**: `cacheRead` is flat across arms (80–85 k per
  session), so "total tokens" counts `input + output` (new tokens) only and
  `cacheRead` gets its own column. Otherwise 91–92 % of the sum is cache reads and
  the gear difference drowns.

The three tables keep raw medians only. The old `faster than safe` and
`new tokens ×` multiplier columns are **gone on purpose**: a per-gear step-time
median is not a speed ratio, and a token ratio is not the claim 1.0.0 makes. One
arm drifts ±11 % between two runs (point 2 below), so no "faster" worth a decimal
place can be published from them.

### HumanEval (100 sessions, 10 waves × 10 problems, 10/10 passing per wave)

RUN A (waves 1–6, 60 sessions):

| Gear | n | steps/session | median Σ step ms | median step ms | out/step | new tok/session | cacheRead | calls/step |
|---|---|---|---|---|---|---|---|---|
| `safe` | 14 | 4.2 | 6 754 ms | 1 519 ms | 481 | 550 | 83 744 | 0.80 |
| `fast` | 11 | 4.0 | 5 369 ms | 1 342 ms | 313 | 455 | 82 816 | 0.75 |
| `turbo` | 9 | 4.2 | 6 793 ms | 1 491 ms | 440 | 650 | 84 224 | 0.79 |
| `max` | 12 | 4.2 | 5 394 ms | 1 310 ms | 346 | 430 | 83 040 | 0.80 |
| `ludicrous` | 14 | 4.1 | 5 370 ms | 1 342 ms | 315 | 442 | 80 384 | 0.84 |

RUN B (waves 7–10, 40 sessions):

| Gear | n | steps/session | median Σ step ms | median step ms | out/step | new tok/session | cacheRead | calls/step |
|---|---|---|---|---|---|---|---|---|
| `off` | 9 | 4.4 | 6 411 ms | 1 361 ms | 337 | 464 | 83 712 | 0.82 |
| `safe` | 12 | 4.3 | 6 064 ms | 1 364 ms | 423 | 537 | 83 776 | 0.78 |
| `turbo` | 9 | 4.0 | 5 554 ms | 1 388 ms | 341 | 466 | 85 376 | 0.78 |
| `ludicrous` | 10 | 3.9 | 5 656 ms | 1 420 ms | 339 | 469 | 84 544 | 0.85 |

### AIME 2025 (39 sessions, 4 waves)

| Gear | n | steps/session | median Σ step ms | median step ms | out/step | new tok/session | cacheRead | calls/step |
|---|---|---|---|---|---|---|---|---|
| `off` | 9 | 5 | 19 357 ms | 2 047 ms | 234 | 2 810 | 103 936 | 0.80 |
| `safe` | 4 | 4.5 | 13 494 ms | 2 105 ms | 197 | 1 396 | 93 216 | 0.78 |
| `fast` | 8 | 5 | 16 572 ms | 2 510 ms | 184 | 1 724 | 107 552 | 0.80 |
| `turbo` | 8 | 4 | 10 147 ms | 1 894 ms | 136 | 968 | 81 248 | 0.75 |
| `ludicrous` | 10 | 6 | 12 432 ms | 2 007 ms | 148 | 1 252 | 122 080 | 0.90 |

`fast`'s median Σ step time (16 572 ms) is higher than `safe`'s (13 494 ms) only
because `safe` has 4 sessions — which is exactly why this table carries no ratio.

### What these two benchmarks taught us

1. **Step count is pinned by the task shape.** 4–5 steps per HumanEval problem,
   4–6 per AIME problem, 92 % of sessions ≤ 15 steps. Fan-out pays per independent
   query, and a single-file problem has one — so this is not "the gear does
   nothing", it is "this task has nothing to give".
2. **`off` and every enabled gear sit inside the noise.** HumanEval RUN B: `off`
   0.95×, `turbo` 1.09×, `ludicrous` 1.07× against `safe`; AIME spreads wider
   (0.70×–1.33×). The drift of one arm between the two runs is the same size
   (`safe` 6 754 ms in RUN A vs 6 064 ms in RUN B, ±11 %). **When the between-group
   gap is ≤ the within-group drift, no conclusion is drawn.**
3. **The only stable visible difference is output length, and it does not become
   speed.** `safe` is the verbose arm in both suites (481 and 423 out/step); the
   rest are lower. That is the direction output discipline predicts, but it never
   crossed the ±11 % drift above — and 1.0.0 does not sell a token saving: a wider
   step reads more context, so more tokens are an accepted cost, not a regression.
4. **Quality did not drop.** HumanEval ran the official `test` on all 100 sessions,
   10/10 passing in every wave; AIME's four waves scored 9/1, 9/1, 8/2, 7/3. No
   case got worse because of the gear.
5. **The AIME run exposed the real wall-clock enemy, and it is not the gear.** Five
   of 39 sessions kept writing after the task board judged them timed out — 550–597 s
   and 77–132 steps each. Drop them and `ludicrous`'s median Σ step time falls from
   12 432 to 6 633 ms while its mean falls from 178 s to 12 s; the `fast` arm's
   median falls from 16 572 to 12 840 ms. **The median survives, the mean does not —
   and these same pairs show why a median must not be published as a speed ratio**:
   five tail sessions can move one arm's median by a factor of two, so the
   0.95×–1.09× gap between arms means nothing next to them. It is also why 0.9.0
   added a ceiling on one tool call and 1.0.0 kept it: what eats the clock is a few
   calls that never stop, not 100 ms per step.

**What this table can and cannot certify**: it certifies that public tasks keep
their quality and that `off` is not slower. It does not certify that a gear makes a
public task faster. The fan-out evidence is still Experiment E alone — and that is
0.8.0's bundle, with no arm that isolates fan-out. Both results stay on the same
page instead of publishing the flattering one — that is what reproducible means
here.

Raw log: [`docs/bench-raw-2026-10-24.jsonl.gz`](docs/bench-raw-2026-10-24.jsonl.gz)
— per-step rows (`ts / sid / gear / route / ttftMs / llmMs / stepMs / calls /
errors / input / output / cacheRead / cacheWrite`) for the 179 experiment sessions,
including the pilot and Experiments E and F. The sessions themselves and the
per-problem sandboxes were deleted once the run finished; every number above is
reproducible from this log alone under the stated conventions (two-stage medians,
in-wave gear rotation).

## Why: the latency model

Least squares over 689 steps:

```
ms = 720 + 6.63 × outTok        R² = 0.93
```

720 ms of fixed cost per step, 6.63 ms per output token (~150 tok/s). Output length explains 93% of the variance; tools, routing and scheduling share what is left.

So one step's wall clock is three terms:

```
wall clock ≈ 720 ms × steps + 6.63 ms × output tokens + tool wait
```

The first two come from that 689-step fit; the third is not in the fit (it is `stepMs − llmMs`) and is exactly where the long tail and the stalls live — which is why 1.0.0's per-call ceiling is aimed there.

Effort residuals (mean deviation after removing the length effect): `minimal` +488 ms, `low` −110 ms, `medium` −39 ms, `high` +292 ms — median residuals all within ±350 ms with contradictory signs. That is noise, not speed.

The simplest evidence: `reasoning` is 0 in all 750 fit rows, and also 0 in all 1,888 steps of the same experiment log — four arms, five gears, not one exception. This route produces no thinking tokens, **so lowering effort could not have saved any decode time: there was no decode to save.** The downshift logic was not wrong; it was working on a route with nothing to save.

That is why 1.0.0 **removes** the lever instead of tuning it lower: re-calibrating effort on a route that does not think is still changing the user's question, not the speed of the answer — and the rewrite reached the provider as a request header, so the composer kept showing "last used effort" and a conversation chosen at Medium drifted to Low across restarts (true before 0.9.x; the 1.0.0 CHANGELOG records the fix). With the rewrite gone, the request is a pass-through.

One counter-intuitive number to finish: input is nearly free. Across all 750 steps `cacheRead` accumulates 40.86 M tokens while genuinely new `input` is 24 k — prompt caching flattens the input side, so "save input" is not a useful direction either. Which also means the extra input a wider step reads is cheap on this ledger.

## What 1.0.0 changes about these conclusions

1. **Deleted**: `floor`, `decideEffort`, the per-step drop, `ludicrous` sending `reasoningEffort: 'off'`, and the gear picker in the panel. `agent/request` now does one thing: `const resolved = await next()` … `return resolved`. The only place that still reads `reasoningEffort` is the metrics label — it records what the user chose and never writes back, and a test scans the source to keep it that way.
2. **Kept, and it only comes in two kinds**, both about how the same thinking gets carried out:
   - **making a step wider**: `auto` starts with a fan-out target of 4, widens to 8 after two clean steps that each carried ≥2 calls, and narrows to 2 after errors or no progress — a *target*, the model still chooses the calls; output discipline trims narration and never the answer (`light` / `full`).
   - **stopping what will not stop**: under `auto` the per-call ceiling starts at 120 s and grows with the session's own p90 call latency ×3, capped at 300 s (`safe` has no ceiling); at the deadline the call is cancelled and "change the approach" is written back to the model. Loop breaking: 6 consecutive no-progress steps → change-approach warning; 12 → stop and report; 30 steps in one turn does the same.
3. **This combination has not been A/B measured at 1.0 scale.** E's −40 % is 0.8.0's bundle with the effort layer still in it; F says single-file tasks cannot show the effect anyway. So 1.0.0 has a mechanism and a direction, not a percentage. **Quoting −40 % as a 1.0.0 number is wrong.**
4. **The honest line**: "same thinking effort, fewer round trips where the task allows them". Not "40 % faster", and not "43 % fewer output tokens" — the latter is backwards for 1.0.0.
5. **Tokens are a trade, not a regression.** A wider step reads more context, so token counts may rise; 1.0.0 accepts that explicitly (it is in the CHANGELOG). A token number only means something in a same-task, same-quality, same-effort A/B, and that A/B has not been run.
6. **What to measure next**: multi-repository, multi-file tasks with several independent queries per step (five repos' versions, five directories' test counts, five files' exports). Arms: `--gears off,safe,auto` (`auto` is the 1.0.0 default, not `turbo`). Metrics: calls/step, steps/session, Σ step time, tool wait on its own line. Showing no difference on a single-file task is **expected**, and is no longer a useful counter-example in either direction.

## What would actually be faster

The model answers it itself: `720 ms × steps + 6.63 ms × output tokens + tool wait`. Only two terms can move, and neither is "decode rate".

| Lever | Here (local log, pre-1.0.0) | 1.0.0 moves it? |
| --- | --- | --- |
| **Steps** | 12.9 steps per turn with the switch off; each burns 720 ms of fixed cost | yes — wider steps plus the breaker, so a dozen probes become a few |
| **Tool wait** | 1,536 s of 4,480 s total sat in tools; bash p90 5.5 s, one call 818 s | yes — spread out in parallel, plus the one hard ceiling (a call is cancelled at its deadline) |
| Output tokens | ~500 tok/step on average | only narration, never the answer; **no promise of a lower total** |
| Decode rate | ~150 tok/s | no (1.0.0 does not even touch effort) |
| Time to first token | the fixed part of the 720 ms | no (no streaming signal to measure — see Limits) |

The old version of this section argued "fan-out pushed tokens from 58 k to 142 k per session, so the trade is a loss". 1.0.0 does not do that arithmetic: tokens are the currency that buys wall clock, and the ledger is only valid on a same-task, same-quality, same-effort A/B that has not been run. The one directional result is 0.8.0's: 16 same-task sessions, three steps fewer, 40 % less wall clock, at 5 % more output per step. That shows a direction; it is not 1.0.0's evidence.

## Limits

- **One route.** `company/deepseek-v4.1-flash` through a self-hosted gateway; the official DeepSeek route declares `off/low/high/max` and does produce thinking tokens, so these conclusions do not transfer.
- **Small sample.** Experiment B has 1–3 sessions per arm: enough to see "no difference", not enough to see "a 3 % difference".
- **No 1.0.0 A/B.** A/B/E/F all predate 1.0.0; 1.0.0's fan-out, stalled-call cancellation and loop breaking have never been compared with `safe` on one task. This is the largest gap in this document.
- **Every gear number is a bundle.** E's −40 % and F's 0.95×–1.09× both carry the effort layer; what they become without it has not been measured.
- **Time to first token is approximate.** `assistant/message` is emitted once per step, so `ttftMs` equals `llmMs`; read that column as "whole-step time".
- **F's arms are uneven and long-tailed.** Hash assignment gave `safe` 4 sessions against `off`'s 9, and five 550–597 s timeout sessions can move one arm's median by a factor of two.

## Reproduce

```bash
# from the repo, equivalent to the installed dsh-fast-mode-experiment / dsh-fast-mode-report
node bin/experiment.mjs start --gears off,safe,auto   # what 1.0.0 should be compared on: off / edges only / the adaptive bundle
node bin/experiment.mjs status                        # sessions and steps per arm
node bin/report.mjs --since 2d                        # the two tables plus ratios against the off arm
node bin/experiment.mjs stop                          # end it; later sessions go back to the plugin config
```

- Without `--gears` the default is `off,safe,fast,turbo,max`, which is the fixed-width set A/B/E/F used (`--gears safe,turbo` is E's minimal reproduction). In 1.0.0 an experiment arm is honoured as written, including `off`; `turbo`/`max` are still fixed widths 6/8 but **no longer lower effort**, so a rerun measures "fan-out + discipline + stall ceiling" and will not reproduce −40 %.
- Do not read the report's two ratio tables when **task shapes differ**: in the local full-history table `fast` shows "model time 0.89×" only because it happened to land on long-output sessions, which is not speed. Compare a same-task, same-step-count sample like Experiment B, or use the latency model's residual comparison — that one is immune to task shape.
- Experiment E's minimal reproduction: `--gears safe,turbo`, then two concurrent waves of eight sessions on one prompt, and a per-session `ΣstepMs` median grouped by `gear`. The raw rows are in `~/.dsh/fast-mode-metrics.jsonl` (present on this machine); grouping by `sid` recomputes the table without re-running a model.
- The 0.8.0 raw log is [`docs/bench-raw-2026-10-24.jsonl.gz`](docs/bench-raw-2026-10-24.jsonl.gz) (committed with the repo); under the stated conventions it reproduces every number in Experiments E and F.
- For multi-repo tasks, do not treat the whole-history ratio table from `dsh-fast-mode-report` as a conclusion; group by task first, then compare medians inside a group.
- 跑 `bench/suite-run.py` 前先确认 `python3` 能用：它在 :176 用 `subprocess.run(['python3','-m','unittest', ...])` 给每个格子判分。2026-10-27 这块机器上 `/usr/bin/python3` 因为 Xcode 许可失效直接失败（`You have not agreed to the Xcode license agreements`，rc=69），当时 PPX2 有 2 个格子因此没落盘。修法：把可用的解释器放前面，例如 `mkdir -p /tmp/fm/bin && ln -sf /Users/apple/.local/share/uv/python/cpython-3.11-macos-aarch64-none/bin/python3.11 /tmp/fm/bin/python3 && export PATH=/tmp/fm/bin:$PATH`（该解释器已装 pyyaml，`load_token()` 也要用）。

## 发布前复核块 REL16（2026-10-26，默认 vs `off`，5 家族 × 1 对 = 10 个单元）

- **目的**：发版前用当时的默认（与 1.5.1 行为相同）对 `off` 再做一块同块对照，只回答两件事——隐藏测试有没有退化、总量方向有没有反过来。
- **质量：两臂 10/10 全绿，逐任务通过数完全相同**（`shellops` 14/14、`ledger` 22/22、`codebase` 18/18、`stategraph` 10/10、`migrate` 14/14），无 leak、无超时、无 `steps=0`。
- **速度**：墙钟合计 **0.967×**（1543.5 s → 1493.3 s），逐格中位 **0.999×**、3/5 格更快，步数 0.66×。逐格：`ledger` 0.78×、`shellops` 0.98×、`migrate` 1.00×、`stategraph` 1.31×、`codebase` 1.79×。
- **读法**：单对、n=5，噪声带比合池 0.798× 的总口径宽，**不用这块替换总量结论**；它只用于回答「发版这一步有没有把质量或方向弄坏」。质量答案是没坏；速度方向在这一块落进噪声（0.97×）。
- 原始结果 `bench/results/suite-REL16.jsonl`；`python3 bench/report.py --files bench/results/suite-REL16.jsonl --baseline off` 可复算。
## 合池的自助区间与逐任务稳定性（2026-10-28，12 块 138 对）

合池点估计 **0.8234×** 是 138 对有效单元的比值加总，不是承诺值。给这 138 对做 4000 次自助重抽（`python3 bench/pool.py --ci 4000`）：**墙钟总比 95 % 区间 [0.7418, 0.9133]**、逐格中位 0.7994×（区间 [0.7026, 0.8850]）、几何均值 0.8248×（区间 [0.7532, 0.9001]）、93/138 对更快（符号检验 p = 0.0001）；同池输出比 **0.8179×**、步数比 **0.8062×**。`bench/results/suite-*.jsonl` 里每行都带 validity 判定，合池只吃 `exit==0 && steps>=1 && out>0 && hasSessionIds && !leak` 的单元（历史行里 `hasSessionIds` 写作 `exact`，读取时回退）（`bench/pool.py` 的 `valid()`）。

12 个块全部有 `off` 锚、全部在现行 validity 规则下测出：`W1`、`W3`、`W5`、`W6`、`W7`、`W8b`、`REL16`、`PPX2`、`SG2`、`NX1_IMPL`、`NX1_PIPE`、`NX1_SG`。**块间不可比**：每块只做自己块内 `(tag, task, pair)` 的 `def15 / off` 配对，跨块加总走「先算每对的比值、再对 138 个比值取中位/几何/加总比」。`V15`、`TA3`、`TA4` 三个块按名字剔除（fixtures 泄漏，`llm` 列是字节窗口的产物）。

| 块 | n | 墙钟总比 | 更快 |
|---|---|---|---|
| `W1` | 11 | 0.711× | 7/11 |
| `W3` | 29 | 0.721× | 23/29 |
| `W5` | 20 | 0.793× | 11/20 |
| `W6` | 19 | 0.931× | 11/19 |
| `W7` | 17 | 0.734× | 13/17 |
| `W8b` | 19 | 0.819× | 13/19 |
| `REL16` | 5 | 0.967× | 3/5 |
| `PPX2` | 8 | 0.912× | 6/8 |
| `SG2` | 4 | 1.235× | 2/4 |
| `NX1_IMPL` | 2 | 0.874× | 1/2 |
| `NX1_PIPE` | 2 | 0.545× | 2/2 |
| `NX1_SG` | 2 | 1.115× | 1/2 |

`SG2` 1.235×、`NX1_SG` 1.115× 这两块是**反向样本**（`ledger` 在 SG2 里 2.024×、输出 34.1k vs 18.7k token，`perfrefactor` 1.427×），它们留在分母里就是合池没能更低的代价：把 0.809×（9 块 132 对时代的旧口径）如实抬到 0.8234×。剔除它们再算不是「更好的口径」，是挑样本。

| 任务 | n | 中位墙钟 | 均值 | 四分位区间 | 更快 | 符号检验 p |
|---|---|---|---|---|---|---|
| `codebase` | 11 | 0.586 | 0.898 | 0.51–1.29 | 7/11 | 0.549 |
| `stategraph` | 12 | 0.667 | 0.964 | 0.59–0.72 | 9/12 | 0.146 |
| `shellops` | 13 | 0.683 | 0.896 | 0.58–1.33 | 9/13 | 0.267 |
| `codeimpl` | 17 | 0.709 | 0.778 | 0.62–0.90 | 14/17 | 0.013 |
| `migrate` | 15 | 0.739 | 0.849 | 0.53–1.00 | 11/15 | 0.118 |
| `shellpipeline` | 14 | 0.775 | 0.794 | 0.59–0.88 | 12/14 | 0.013 |
| `ledger` | 14 | 0.812 | 0.908 | 0.45–1.22 | 8/14 | 0.791 |
| `perfrefactor` | 14 | 0.961 | 1.140 | 0.72–1.36 | 7/14 | 1.000 |
| `debugfix` | 15 | 0.976 | 1.287 | 0.77–1.70 | 8/15 | 1.000 |
| `csvpipe` | 13 | 0.983 | 1.014 | 0.54–1.48 | 8/13 | 0.581 |

- **稳的（两行）**：`codeimpl`（17 对，中位 0.709×，14/17 更快，p = 0.013）与 `shellpipeline`（14 对，中位 0.775×，12/14，p = 0.013）。这两个家族的收益同时出现在墙钟和步数上，是唯一能对外说「这个形状有稳定收益」的两行；`codeimpl` 的 n 从 15 涨到 17（NX1_IMPL）后方向不变，`shellpipeline` 同理（NX1_PIPE）。
- **中位好看但不稳的**：`codebase` 中位 0.586× 但均值 0.898×、四分位 0.51–1.29×、p = 0.549 —— 靠少数长格拉低中位，不能说成「普遍快 41 %」。`shellops` 与 `stategraph` 同理（`stategraph` 均值 0.964×，四分位 0.59–0.72× 但 p = 0.146）。
- **平均在变慢的**：`debugfix` 中位 0.976×、均值 1.287×（输出 1.29×）、`perfrefactor` 中位 0.961×、均值 1.140×、`csvpipe` 中位 0.983×、均值 1.014×。这三类不是「快得少」，是**平均在变慢**；默认指引写的是「先试一次，复验变多就关」。
- **不许跨块引用的行**：`debugfix` 在 PPX2 这一块里是 0.651×（2/2 更快）、`perfrefactor` 是 0.687×（2/2 更快），与合池方向相反；`ledger` 在 SG2 里 2.024×。块内 n=2 撑不起任何引用，写在这里只是提醒别拿单块当结论——参考「空转对照」：同一策略跑两遍，9 对里也能差到 0.889–1.342×。
- **输出口径**：合池输出 0.8179×，与墙钟同向 —— 速度来自少写、少复验，不是来自更快地写同样多的东西。`debugfix` 输出 1.29× 是唯一明显变长的家族。
- **不能说的**：0.8234× 是点估计，95 % 区间上界 0.9133 距今只有 8.7 个百分点；这不是「下限承诺」。也**不能说「省了 26 %」**（1/0.8234 的反算，会被读成承诺）、不能说「思考 token 变少」（遥测里 `reasoning` 恒为 0，这里没有可测的量）。
- 开销口径：这些区间是**墙钟**的自助区间；块内两臂逐对交替跑，所以不含跨时段机器漂移。合池只做同 `(tag, task, pair)` 的配对，跨块只聚合比值、不聚合原始秒数。

### 输出里究竟有什么：97–99 % 是工具参数，不是正文（2026-10-28，抽样）

为什么「再压正文」这条路走不远，看构成就清楚了。从留存下来的 run 目录里抽 40 个 `def15` 会话，把每条助手消息拆成「可见正文」和「工具调用参数」两部分：正文 6,267 字符，工具参数 518,773 字符，**正文只占输出字符的 1.2 %**。输出的大头是 `bash` 命令、`write`/`edit` 的文件内容和补丁，也就是产物本身。

同一批会话里再看步的形状（60 个 `def15` run 目录、528 个 on-step）：**479/528 = 91 % 的步只发一个工具调用**，其中 353 个是 `bash`；只有 8 % 的步发了两个。`bash` 调用共 409 次，76.5 % 自己就带 `&&`、管道或分号，命令长度中位 344 字符、p90 3,045 字符 —— 也就是说「一步一个」不是模型不会合并，而是每一步本来就是一条已经串好的长命令。

所以 `def15` 的输出降到 0.8179× 不可能是「正文写短了」：正文总量太小，压到 0 也只有 1 % 量级。它只能是**步数 0.8062× 的副产品**（每步产物量近似不变，少一步就少一份命令或一份写入），也就是**同一件活少做一遍**，不是「同样多的活写得更省」。注意这里说的是**输出的成分**，不是提醒的措辞：把整套规则提前全程生效（`odt15`，W1 的 12 对）实测 1.213× 更慢，那是「早期步骤也被强行压缩」的成本。提前**触发时点**、措辞不变是另一条线（`d2`），正由 D2 块在测（`debugfix` 7 对已出：几何 0.535×，见「压缩提醒的触发时点」一节）——两者不能混为一谈。

反面对照来自 `batchVerify` 的 codeimpl 块（同块两臂，8 对）：`def15` 4.5 步/会话、参数 11,593 字符/会话；`bvl15` 6.9 步/会话、14,339 字符/会话，非 `bash` 调用从 1.2 次/会话涨到 2.9 次/会话。多出来的正是复验步和随之而来的写盘 —— 与那一臂 1.478× 的墙钟对得上，不是提醒文本本身的开销。

口径限制：这些数字来自**还留在磁盘上的 run 目录抽样**，不是 138 对全池的成分统计；结论只用来说方向（正文 ≪ 参数、单调用步占多数），不要当成全池精确占比引用。

### 复核：1.2 % 的正文占比现在有命令了（2026-10-28）

上面那 40 个会话的数字（6,267 / 518,773 = 1.19 %）没有留下抽样命令，无法复现。用 `bench/textshare.py` 重算，口径固定、可重跑：

- 计数单位：会话 transcript 里每条 `assistant/message`。分子 = `text` part 的 `len(text)` 之和；分母 = 分子 + `tool-call` part 的 `len(arguments)` 之和。不含 reasoning、不含工具返回、不含用户消息。
- 抽样规则：`~/.dsh/sessions` 下 run 目录名匹配 `suite-<tag>-<task>-p<pair>-<arm>` 的会话，按目录名排序取前 40 个；`--limit 0` 取全量。同样的库必得同样的数。
- `python3 bench/textshare.py`：n=38 会话 / 368 条消息 / 408 次调用，正文 6,555 字符、工具参数 456,476 字符 ⇒ **1.42 %**（逐会话中位 1.43 %、p10 0.07 %、p90 2.80 %）。
- `python3 bench/textshare.py --limit 0`：全量 `def15`，n=341 / 3,327 条消息 / 3,569 次调用，正文 51,366、参数 4,315,096 ⇒ **1.18 %**（中位 0.98 %、p90 4.08 %）。

全量 1.18 % 与文首的 1.2 % 一致，结论（正文 ≪ 工具参数）在两套样本上都成立。后续引用建议用 `--limit 0` 的 1.18 % 并写明 n；两套样本都不含 reasoning 与工具返回，不是「输出 token 的全部」。

## `batchVerify` 复测块 BV8（2026-10-28）：codeimpl 1.478× 变慢、shellpipeline 0.956× 无差，合并 1.188× —— **淘汰**

`batchVerify` 是最后一个「修好 liveness 缺陷后再测一次」的默认关闭机制。W4 曾筛出 0.76×，但 W8b 的逐会话检索证明那一臂**一条提醒都没下发**（20 个会话命中 0），所以 0.76× 不是它的效果；W5 同样零下发。修复投递后第一次真测就是这两块。

**跑之前定的判据**：合并两个家族共 16 对（`codeimpl` 8 对 + `shellpipeline` 8 对），两臂全绿，**且**合并几何均值 ≤ 0.90× 才考虑翻默认；单家族变快、另一家族变慢的「家族分裂」不算机制，只照实记录。

**臂差**：`bvl15` = `def15` + `batchVerify: true`。两臂的 `outputDiscipline` 都是默认的 `late`，所以 `batchVerify` 是唯一有效差异。

**下发核对（精确文案，不用泛词）**：`8/16 sessions delivered`，命中恰好是 8 个 `bvl15` 会话（每会话 2–6 次），8 个 `def15` 会话全为 0 —— 机制在这两块里确实运行了。

| 家族 | n | 几何均值 | 中位 | 总量 | 更快 | 符号检验 p | 步数 | 输出 | 区间 |
|---|---|---|---|---|---|---|---|---|---|
| `codeimpl` | 8 | **1.478×** | 1.332× | 1.282× | 2/8 | 0.289 | 1.379× | 1.474× | 0.622–5.965 |
| `shellpipeline` | 8 | **0.956×** | 0.941× | 1.039× | 5/8 | 0.727 | 1.096× | 0.922× | 0.449–2.010 |
| 合并 | 16 | **1.188×** | 1.148× | — | 7/16 | — | 1.229× | 1.166× | 0.449–5.965 |

16 对两臂 32 格全绿（各 15/15 或对应断言全过），没有一格因质量或崩溃作废。

**三个结论**：

1. **这是「变慢 + 家族分裂」，不是机制**：合并 1.188×、只有 7/16 更快、步数 1.229× 反而变多。文案要求「先把这批改动做完，再跑一次覆盖全部改动的检查」，模型实际做的是**多跑几轮**而不是少跑几轮 —— 两家族的步数都升（1.379×、1.096×），输出也同步升。这一族的边界在这里：提醒不能把「重复验证」合并掉，只能改变模型对进度的解释，代价是更多往返。
2. **PIPE 的 4 对读数是 0.633×，8 对读数是 0.956×**：前 4 对看着像「大幅变快」（3/3 更快），补齐 8 对后中位 0.941×、p=0.727。这正是「空转对照」那一节量的东西 —— 同策略跑两遍也能差到 0.889–1.342×，n=4 的方向不足以当结论。此处把 0.633× 标为**已撤回**，任何引用都改用 0.956×。
3. **默认保持 `batchVerify: false`**，并把「重复验证步」这条线关掉：W8b 算过即使把它压到零总量也只到 0.94× 左右（步数项只占 6 % 墙钟），现在实测连这个方向都不成立 —— 强行合并带来的是更多步。以后不再为这一族开臂。

## 按场景开关值不值：132 对上的反事实算账（2026-10-27，纯分析，不新增格子）

「按场景动态启用/关闭」听起来比全局开更优，但在已有样本上直接算反事实就很清楚：

| 策略 | 开着的单元 | 合池总墙钟 |
|---|---|---|
| 全关（`off` 基线） | 0/132 | 1.0000× |
| 只开 `codeimpl`+`stategraph` | 25/132 | 0.9313× |
| 再加 `shellpipeline`+`shellops` | 50/132 | 0.8763× |
| 再加 `migrate`+`codebase` | 76/132 | 0.8414× |
| **全开（当前默认）** | 132/132 | **0.8247×** |

三个独立估计量一致，不是尾部样本撑出来的：逐对中位 **0.8128×**（CI95 0.7095–0.8830）、几何均值 **0.8232×**（CI95 0.7530–0.8971）、总输出 token 比 **0.8221×**；132 对里 89 对更快，符号检验 p = 0.0001（`python3 bench/pool.py --ci 2000` 一行可复算）。
| 全开但 `debugfix`/`perfrefactor` 关 | 103/132 | 0.8235× |

读法两条，都是负结果，但有用：

1. **按「只开稳的家族」做路由，总量比全开更差**（0.93× / 0.88× / 0.84×）。原因很朴素：`migrate`（0.822×）、`codebase`（0.808×）、`ledger`（0.916×）这些「不够稳所以不该单独引用」的家族，加总起来仍然是净收益；把它们关掉等于把收益丢掉，只留下几格漂亮中位。
2. **只把明显回退的两类关掉（`debugfix` 1.032×、`perfrefactor` 1.009×），总量从 0.8247× 变 0.8235×**——省下 0.12 个百分点。也就是说**按场景开关不是速度杠杆，是回归规避杠杆**：它买到的是「这两类任务不会变慢」，买不到更快的总墙钟。

所以默认仍然是「全局开 + 按形状提示先试」，没有引入按家族名的硬开关：运行时看不到家族标签，算账也不涨速度，而按步数硬停会让任务「没做完就停」——这条线不碰。

## 预注册：`shapeGate`（运行时按形状静音）的通过判据（2026-10-27，先写判据再跑）

`shapeGate` 是实现里唯一真正「按场景动态关闭」的开关（`src/auto.js:596`：一个回合里已有 ≥4 次调用、且写类调用占 60 % 以上时，调度器不再发任何加速指令，回到原生沉默）。它默认关。要开它，先固定判据，跑完照判据判，不许事后改口径：

1. **质量门禁**：同一块内 `gateonly` 与 `off` 的隐藏测试必须全部 exact、无 leak、exit 0；任何一个 BAD 即不通过。
2. **回退家族**：`debugfix` 与 `csvpipe`（已知写重、小改修）上 `gateonly/def15` 的同块墙钟比 ≤ 1.00，逐对中位也 ≤ 1.03。
3. **不伤收益**：`codeimpl`（最快家族）上 `gateonly/def15` ≤ 1.03，且三家族合计 ≤ 1.00。
4. **样本量**：每家族 ≥ 2 个同块有效配对；不足 2 对则结论只能是「未判定」，不能写成通过。
5. 通过才把它并入默认，并在 CHANGELOG 写明「在写重回合自动静音」；不通过就保持默认关，把负结果写在这里。

块：`--tasks debugfix,csvpipe,codeimpl --arms off,def15,gateonly --pairs 2 --tag GATE3`（18 个单元，单块串行，load ≤ 6.5 才开跑）。

## 成本模型只能在块内解释：跨块回归是假的（2026-10-27，纯分析）

把九个干净块的 133 个 `off` 单元丢进一个回归，得 `wall = 18.58 + 8.50·out/1k − 0.93·step`，R²=0.46，而块内（W8b，n=19）是 `wall = −8.8 + 13.48·out/1k + 1.62·step`，R²=0.92。步数系数在跨块回归里**翻成负数**——这不是机制，是块间负载差混进了截距：机器忙闲不同，同一份工作量在 W3 和 W8b 上的墙钟差得比 `out` 能解释的多。所以「每步值多少 token」「砍 40 % 步数能到 0.60×」这类算账只能在单个块内做，跨块算出来的数一律当噪声，别写进报告。

同时在**真实会话**的 18 030 个 `mode=on` 步骤上量了时间去哪了（这是运行时自己的遥测，不是基准数据，只作方向参考）：`ΣllmMs/ΣstepMs = 0.68`，其中 TTFT 占 LLM 时间的 0.19、解码占 0.81，解码速率 53 tok/s。也就是说**每步墙钟里有约 32 % 根本不在模型调用里**（工具执行、宿主调度、渲染）。推论：纯注入式插件即使把模型时间砍到零，上限也只有约 0.68×；要到 0.60× 必须同时减少步数与工具往返，而不只是压输出——这跟「不许把没做完的活强行停下」直接冲突，所以 0.60× 在当前约束下应当继续按「不可达」处理，对外只报 0.8247×（CI95 0.747–0.914）。

## 加样本修不了置信区间：132 对的重采样宽度预算（2026-10-27，纯分析）

在现有 132 对上做 2000 次重采样，合池比值 0.8247×，CI95 宽度 0.184。再按同样口径外推：

| 单元数 | CI95 宽度（重采样均值） |
|---|---|
| 132（现有） | 0.184 |
| 144（+12 对，即 NX1 全部落地） | ≈0.166 |
| 156（+24 对） | ≈0.154 |
| 37 对（三个快家族单独看） | 0.184（n=12 时 0.298，n=37 时 0.184） |

结论：**「再多跑一些对」不是收紧精度的有效手段**——把样本翻一倍量级才可能把宽度压到 0.12 附近，而要 ±0.03 需要几百对，在单块串行、load ≤7 的纪律下不现实。所以对外口径继续用「点估计 + 已有 CI95 + 符号检验」，把增量精力放在**降方差**（严格 ABBA、同块对照、剔除负载持续超标的单元）和**中位数/符号检验**上，而不是堆样本量。NX1 的 12 对按上表只把宽度从 0.184 拉到 ≈0.166，它的真实价值是给 `codeimpl`/`shellpipeline`/`stategraph` 三个家族各多一对同块证据（符号检验从 37 对变 49 对的稳健性），不是让总口径变窄。

## 预注册：`outputBudget`（回合内输出预算，非请求体字段）的通过判据（2026-10-27，先写判据再跑）

机制：`outputBudget` 走的是调度器话术——当本回合输出 token 超过阈值时，`src/auto.js:667–673` 发一条「收敛输出」指令，只发一次（`outBudgetSent`），不改请求体、不改 `reasoningEffort`。是唯一还没在同块对照下测过的输出侧杠杆。待跑命令（`bench/arms/bud8.yml`，`outputBudget: 8000`）：

```
python3 bench/suite-run.py --tasks debugfix,ledger,codeimpl --arms off,def15,bud8 --pairs 2 --tag BUD3
```

判据（全部满足才并入默认，否则记为负结果并保持默认关）：

1. **质量门禁**：`BUD3` 块内 `bud8` 每个单元 `exit==0`、`hasSessionIds`、无 `leak`；`off` 与 `def15` 在同块同样全绿（分母按有效单元算，不按格数算）。
2. **输出真的降了**：`bud8/def15` 的同块输出 token 总和比 ≤ 0.90；若 ≥ 0.97 判定「机制未生效」，不许用墙钟数字解释。
3. **墙钟不倒退**：`bud8/def15` 同块墙钟总和比 ≤ 1.00，逐对中位数 ≤ 1.03；三个家族里至少两个家族单独 ≤ 1.00。
4. **每家族 ≥ 2 个同块有效对**，否则该家族写「未判定」，不并入总体结论。
5. 通过 → `outputBudget` 默认值从关改为 8000 并进 CHANGELOG；不通过 → 在本文档记录负结果，默认保持关。

注意：`bud8` 的历史 20 行来自**没有 tag 的遗留块**（每家族仅 1 对、无 ABBA 保证），已判定不可用，不得拿来替代 `BUD3`；上面第 2 条的旧值 `out/def15≈0.94` 只作方向参考。

## 负载前提改了：从「等安静」改成「同块交错 + 记录负载」（2026-10-27，方法学变更）

连续 4 小时以上等不到 `load1 < 6.5`。查了是谁在占 CPU：不是外部的抓取任务，而是**这台机器现在的常态**——DSH 自己的两个渲染进程（70 % + 25 %）、SkyLight（71 %）、WebKit、一个虚拟机。也就是说 `load1` 8–10 是本机 GUI 会话的基线，原来的「安静窗口」前提在这台机器上永远不会满足，继续等等于把实验停摆。

新的前提（写下来是为了以后不被人拿绝对墙钟数字做文章）：

1. **成块门槛从「安静」改成「不病态」**：`load1 < 12` 即开跑；只有 `load1 ≥ 14 连续 6 分钟`（真异常）或「24 分钟没有新增结果行」（真挂死）才中止。
2. **抗污染靠设计而不是靠等**：`suite-run.py` 本来就是每对交错跑臂（ABBA），同块内 `off` 与 `def15`（或 `gateonly`/`bud8`）吃同一段负载。**对外报的量是同块比值，负载在时间上的整体缩放对比值近似一阶抵消**；受影响的只有绝对墙钟，而这些块的绝对墙钟一律不许跨块比较、也不许当「本机单任务耗时」引用。
3. **每个块把自己的负载样本落盘**：`bench/results/suite-<TAG>.load.json`（`samples/mean/max`），分析时必须把块所属负载区间一并写出，例如「NX1_IMPL 在 mean 9.8 下测得」。
4. **方差预期**：负载高时单格方差变大，所以结论继续用「同块总和比 + 逐对中位数 + 符号检验」，且**每家族 ≥ 2 个同块有效对**才算判定；只有 1 对的行一律写「未判定」。

这一条替换掉此前「`load` 持续 ≥ 8 就整块作废」的规则（那版规则在 20:44–20:53 的 NX1_IMPL 上刚把一个块砍到只剩 1 行，而阈值 8 恰好等于本机空闲基线，等于永久停机）。

## 拆开看：快的那 18% 全部来自「输出 token 变少」，不是「每一步变快」

132 对干净配对逐对分解，恒等式先验证：`几何均值(wall) = 几何均值(步数) × 几何均值(每步墙钟)`，残差 0.0000。

- 几何均值：**步数 0.827 × 每步 0.996 = 0.823×**（逐对中位 0.818 × 0.999）。
- 也就是说：**每步并没有变快**（0.996×，等于没变），省下来的是**回合数**；而回合数省掉之后，每步的输出反而变长，两者相乘才是那 18%。
- 每步墙钟 ≈ 每步的输出 token：`corr(log llm/step, log out/step) = 0.949`（n = 132 对）。块内回归 `wall ≈ 9.45 s / 1k 输出 token`。模型侧解码速度是模型自己的，不归本插件管，所以**插件能动的那一项只有「总共写多少 token」**。
- 反过来验证：`corr(log 墙钟比, log 输出比) = 0.940`（n = 145 对，含未入池块），逐对 `wall` 比的 sd 0.52、`out` 比的 sd 0.46 —— **方差几乎全部由输出长度决定，不是负载**。所以之前「负载让区间变宽」的说法只对了一半：负载抬绝对墙钟，但逐对方差的主因是模型每次写多写少。

逐家族（同块逐对中位，n 见括号；`每步`=wall/步 的比值，`输出/步`=out/步 的比值）：

| 家族 | 步数 | wall | 每步 | 输出/步 |
|---|---|---|---|---|
| codebase (11) | 1.000 | 0.586 | 0.806 | 0.835 |
| stategraph (10) | 1.085 | 0.667 | 0.639 | 0.671 |
| shellops (13) | 0.769 | 0.683 | 1.152 | 1.275 |
| codeimpl (15) | 0.778 | 0.709 | 0.860 | 0.960 |
| migrate (15) | 0.778 | 0.739 | 0.940 | 1.022 |
| ledger (14) | 0.591 | 0.812 | 1.252 | 1.445 |
| shellpipeline (12) | 0.807 | 0.844 | 1.027 | 0.932 |
| perfrefactor (14) | 1.000 | 0.961 | 0.981 | 0.914 |
| debugfix (15) | 1.000 | 0.976 | 0.972 | 0.986 |
| csvpipe (13) | 0.923 | 0.983 | 1.039 | 1.040 |

两条互斥的省钱路子，家族各走一条：`codebase`/`stategraph` **步数不减甚至变多，但每步写得少**（每步 0.64–0.81）；`ledger`/`shellops` **步数砍掉四到六成，但每步写得多**（每步 1.15–1.25）。混合池 0.823× 就是把这两类平均后的结果；把它们混在一起比较「哪个机制强」没有意义。

**一个被否掉的猜想**：曾怀疑「每步变便宜」来自提示词缓存命中，因为注入文本若放在 prompt 尾部、前缀稳定，cacheRead 会变高。实测否掉：live 遥测 446 个 on 步里 `corr(log cacheRead, log llmMs/输出) = −0.026`（缓存命中不预测每 token 延迟），且 461 行遥测里**没有任何一个会话同时有 on 与 off 步**（0 个），所以「on 的 cacheRead 中位 66k vs off 34k」是上下文长度不同导致的混淆量，不可引用。每 token 时间基本是常数，这也解释了为什么 `每步` 完全跟着 `输出/步` 走（0.949）。

**这条拆解给出的天花板**：0.60× 需要总输出少 40 %，即模型在同样的任务里少写 40 % 的 token（少写解释、少写重复复验、少贴长 diff）。那不是调度能做到的，是**拿任务质量换的**，与「速度不能以牺牲任务质量为代价」直接冲突。所以声明：在「不动请求体、不动推理档、不牺牲正确率」的约束下，**可达区间是 0.80–0.85×**，逐家族最优 0.59–0.71×；再往下必须走被明令禁止的杠杆（提高 max_tokens 压缩比、砍推理档、跳过复验）。这条写在这里，是为了以后不再有人拿 0.60× 当目标返工。

## 质量对等：138 对里断言 2037/2038 对 2038/2038（2026-10-28 更新）

> 本节表格与结论已按 **12 块 138 对**现行口径重算；下面「132 对」的那段历史正文保留，只用来说明当时的口径。

- 断言通过数：`off` **2038/2038（100.00 %）**，`def15` **2037/2038（99.951 %）**，差 **1 条断言**。
- 逐对看：137/138 对两臂断言数完全相同；唯一不同的还是 `W1 / ledger / pair 1`（`def15` 21/22，`off` 22/22），即 `RX` 那格题面欠明确的历史样本，不是新出现的回归。
- 会话归属 `hasSessionIds`：两臂都是 138/138。（这不是「产物精确性」：该字段只说 steps/out 归属本会话；产物质量看断言计数与 `green`。）
- 结论只能说到这里：**在这 10 个家族、138 个任务上，速度不是用正确率换来的**；1 条断言的分差在统计上无法与噪声区分（也无法证明「绝对无损」），所以对外表述用「断言对等，1/2038 差异」而不是「零质量损失」。

## 质量对等（历史口径）：132 对里断言 1959/1960 对 1960/1960

「快了 18%」必须配一条质量口径，否则就是拿正确率换的。同一批 132 个有效配对逐对核对隐藏断言：

- 断言通过数：`off` **1960/1960（100.00 %）**，`def15` **1959/1960（99.95 %）**，差 **−0.05 个百分点 = 1 条断言**。
- 逐对看：131/132 对两臂断言数完全相同；唯一不同的是 `W1 / ledger / pair 1`，`def15` 21/22、`off` 22/22。
- 会话归属 `hasSessionIds`（旧名 `hasSessionIds`）：两臂都是 132/132。（入口文件与期望的逐字节比较是另一件事，由断言计数 `passed/total` 与 `green` 覆盖。）
- 结论只能说到这里：**在这 10 个家族、132 个任务上，速度不是用正确率换来的**；1 条断言的分差在统计上无法与噪声区分（也无法证明「绝对无损」），所以对外表述用「断言对等，1/1960 差异」而不是「零质量损失」。

另外记一条运行可靠性的事实，避免以后误读：同批数据里有 **`off` 6 行、`def15` 3 行**是零输出的退化运行（`out=0`、`steps` 为 1、任务只做了一半），按有效单元规则已经全部排除，不参与任何比值。这类行的出现与宿主负载相关，不是某一个臂的固有缺陷；它们也解释了为什么 `suite-run.py` 必须按「有效行」而不是「有行」来判断进度（见 `bench/scrub.py`）。

## 机制判定：`shapeGate` 没有可测收益（6 对，3 个家族）

1.5.10 之前 `shapeGate` / `outputBudget` 在 `resolveConfig()` 里被丢掉了，所以旧块其实全跑默认配置；1.5.10 修好接线后重新测了 `shapeGate`（块 `GATE3_DBG` / `GATE3_CSV` / `GATE3_IMPL`，每块 2 对、三臂 `off` / `def15` / `gateonly` 同块交错）：

| 家族 | 对 | 逐对 `gateonly/def15` 墙钟 | 几何均值 |
| --- | --- | --- | --- |
| debugfix | 2 | 0.889 / 2.246 | 1.413 |
| csvpipe | 2 | 1.303 / 0.337 | 0.663 |
| codeimpl | 2 | 1.269 / 0.978 | 1.114 |
| **合计** | **6** | — | **1.014**（中位 1.123，`gateonly` 3/6 更快；输出 token 几何 0.949） |

判据在跑之前就写死了：同一家族先看逐对方向是否一致，再合池；只要逐对方向随机就不给结论。这里 6 对里 3 对更快、3 对更慢，几何 1.014 与 1.0 无法区分，输出 token 只少 5 % 也没换来墙钟 ⇒ **`shapeGate` 不是速度杠杆**，与上面的分解一致（省时只能靠少输出/少轮次，而 gate 的输出变化不稳定）。因此它保持默认关闭；它的价值只在「形态约束」本身，不在速度。

**全语料复核宽度引导本身（251 个已录会话，离线读 transcript，0 成本）。** 上面测的是 `shapeGate` 这个开关；再往下问一层：**请求形状判定的宽度建议，到底有没有让模型真的并行**？做法是把每个 run 目录的会话 transcript 反解出来，取第一条 `user/message` 文本喂给 `classifyEntry()`，再数第 1 步真实的 `tool/call` 事件数。结果：251 个会话里判定为宽（width>1）只有 **24** 个，而这 24 个里 **22 个第 1 步只发了 1 次调用**（只 2 个 ≥2）；反过来判为 native 的 227 个里也只有 2 个第 1 步 ≥3 次。按「任务、对」把 `off` 与开档臂配对看，**24 对里 23 对第 1 步调用数完全相同**，剩下 1 对是关闭档更多：宽度建议在语料里**没有可测行为效果**。原因不是判定错了，而是这套基准语料是「读一个仓库、迭代修改」的串行型任务，本来就不该扇出（这也和前面 90.7% 的步是单调用互相印证）。所以：收益**不来自批量**，只来自步数与输出长度；宽度引导作为「形态约束」保留，但不能再被当成提速机制来讲。

这也顺带说明 1.5.10 那个接线 bug 的影响面：旧块里 `bud8`/`sg15`/`gateonly`/`gate15` 的分支数据一律标 **untested**，不能当机制证据；上面这张表是修复后的第一次真测量。

## 反例：`outputDiscipline: true`（从第 1 步就压输出）比默认 `late` 慢 21 %

默认值 `outputDiscipline: 'late'` 是「短任务不付提示成本、第 4 步之后提醒一次」。把规则提前全程生效（实验臂 `odt15`）在同一批 12 个块内配对里（`W1`，`codeimpl`/`csvpipe`/`debugfix`/`ledger`/`migrate`/`shellpipeline` 各 2 对）：

- 几何均值 **1.213×**（即慢 21 %），`odt15` 只赢 4/12 对。
- 输的方式不是「话变多」而是 **步数变多**：`debugfix` 4→12 步、`ledger` 8→26 步、`shellpipeline` 19→31 步；输出 token 也跟着涨（`ledger` 2.32×、`shellpipeline` 1.84×）。
- 机制解释与主结论一致：把「精简输出」当常驻文风后，模型会改用更多轮次去「确认」，而轮次和输出是绑在一起的（`steps` 0.827 × 每步输出 ≈ 墙钟）。

所以默认值不改成 `true`，也不做成「从第一步就提醒」。这条和 `shapeGate` 的空结果一起，构成「不是所有听起来合理的省 token 开关都真省时间」的两个反例——它们都是**先测后定**，不是按直觉定的。

## `outputBudget`：同一个 8000 token 上限，长任务省一半、短任务慢一半（6 对，3 个家族）

1.5.10 修好接线后测 `outputBudget: 8000`（实验臂 `bud8`，块 `BUD3_DBG` / `BUD3_IMPL` / `BUD3_LEDGER`，每块 2 对，`def15` 与 `bud8` 同块交错）：

| 家族 | 对 | 逐对 `bud8/def15` 墙钟 | 几何均值 | 步数变化 | 输出变化 |
| --- | --- | --- | --- | --- | --- |
| ledger | 2 | 0.531 / 0.976 | **0.720** | 18→5、16→13 | 0.45×、0.89× |
| debugfix | 2 | 1.220 / 1.124 | 1.171 | 5→6、6→4 | 1.31×、0.91× |
| codeimpl | 2 | 1.466 / 1.403 | **1.434** | 5→8、4→6 | 1.94×、1.90× |
| **合计** | **6** | — | 1.066（`bud8` 2/6 更快） | — | — |

读法：预算本身不是速度杠杆，它是一个**收尾扳机**。`ledger`（22 个隐藏断言、本来要写 3–5 万 token）在 8000 处被提醒一次，直接砍到 5 步、输出 0.45×，墙钟 0.53×——真的省。`codeimpl` 相反：任务只需要约 1.2 万 token，预算在它还没写出实现时就把「收掉闭环」的意见插进去，结果多出 2 步重开（4→6 / 5→8），输出和墙钟一起涨 40 %。

所以默认值保持 0（关闭），并且不按家族静态开：判据是「这一轮是否已经长到值得收」，不是一个固定 token 数。据此把预算改成 **延迟生效**（`outputBudgetAfterSteps`，默认 0 = 立即），实验臂 `budlate` = `outputBudget 8000` + `outputBudgetAfterSteps 6`，正在跑 `BUDL_DBG`/`BUDL_IMPL`/`BUDL_LEDGER` 各 2 对。

## 报价更新：并入 NX1 三块后 138 对（2026-10-27 夜）

`NX1_IMPL` / `NX1_PIPE` / `NX1_SG` 三块是 1.5.10 修线之后的 `off` vs `def15` 同块配对（每块 2 对、ABBA 交错），与旧的 9 块口径相同，因此并入 `bench/pool.py` 的 `CLEAN`，合池从 132 对变 **138 对**（`python3 bench/pool.py --ci 4000`）：

| 估计量 | 132 对（旧口径） | 138 对（并 NX1） |
| --- | --- | --- |
| 墙钟总比 | 0.8247×（CI95 0.747–0.914） | **0.8234×（CI95 0.742–0.913）** |
| 逐格中位 | 0.8128×（0.710–0.883） | **0.7994×（0.703–0.885）** |
| 几何均值 | 0.8232×（0.753–0.897） | **0.8248×（0.753–0.900）** |
| 步数总比 / 输出总比 | 0.8085× / 0.8221× | **0.8062× / 0.8179×** |
| 更快对数 / 符号检验 | 89/132，p=0.0001 | **93/138，p=0.0001** |
| 断言（`off` / `def15`） | 1960/1960 / 1959/1960 | **2038/2038 / 2037/2038** |

三点读法：

1. **点估计没动**（0.8247 → 0.8234，差 0.13 个百分点，远小于区间宽度 0.18），所以对外仍是「约 1.21× 快、约 17–18 % 省时」，区间照旧报 CI95，不报单点。新增的 6 对确实更偏快（中位从 0.813 掉到 0.799），但样本量只涨 4.5 %，不足以支撑新报价。
2. **唯一的断言差异还是那一个**：`(W1, ledger, 1)` 的 `def15` 21/22 对 `off` 22/22；其余 137 对断言计数完全一致，`hasSessionIds` 138/138。质量措辞仍是「断言对等」，不是「零质量损失」。
3. 三个估计量互相不一致的程度（0.799 / 0.823 / 0.825）正好说明为什么发布口径必须并列报，而不能挑最小的那个当中位卖点。

## 空转对照：开关根本没触发时，同一对能差多少（9 对，纯分析，不新增格子）

这一轮 mechanism 实验里有 9 对是**结构上必然空转**的：实验臂多开的那一路开关，在那一跑里一次都没触发，所以两臂的行为完全一样，它们测到的比值就是**测量本身的噪声加偏差**。判定依据不是猜的，是逐跑数会话日志里的注入条数（`zstd -dc session.v4.jsonl.zstd | grep -c`）：

| 块 | 任务 | 逐对 `arm/def15` | 为什么是空转 |
| --- | --- | --- | --- |
| `BUD3_DBG` | debugfix p1 / p2 | 1.220 / 1.124 | `outputBudget 8000` 从未触发（输出 2526、5137 token < 8000），`Auto schedule` 计数 1/1 |
| `BUDL_DBG` | debugfix p1 / p2 | 1.342 / 1.123 | 同上，延迟到第 6 步的预算也没触发，计数 1/1 |
| `BUDL_IMPL` | codeimpl p1 | 1.151 | 8119 token 只在最后一步越过阈值，没有下一步可提醒，计数 1/1 |
| `GATE3_DBG` | debugfix p1 | 0.889 | `shapeGate` 没有抑制任何条注入，计数 1/1 |
| `GATE3_CSV` | csvpipe p1 | 1.303 | 同上，计数 1/1 |
| `GATE3_IMPL` | codeimpl p1 / p2 | 1.269 / 0.978 | 同上，计数 1/1 |

9 对的分布：**几何均值 1.146×、中位 1.151×、范围 0.889–1.342×、7/9 偏向「实验臂更慢」**。也就是说同一份代码、同一份提示词、同一个任务，换一次跑就能差 ±20 %，最坏一头一尾差 1.51 倍。

这条数字的用处是把「不可判定」从口头判断变成可引用的边界：

1. **单家族 n=2 的裁决没有信号**。`BUD3_DBG` 报的 `bud8` 慢 17 %、`GATE3_DBG` 报的 `gateonly` 慢 41 %，都落在空转对的范围里——那两块的「结论」本来就不该写成结论。之前 `shapeGate` 合池 1.014×（3/6 胜）也是同一回事：6 对里 4 对根本没抑制过注入。
2. **超过 ±20 % 才算有东西**。真正触发了开关的对给出的是 0.531×（ledger）、1.466× / 1.403×（codeimpl），幅度超出空转带，可以当机制证据；幅度在带内的只能进合池，不能单独成段结论。
3. **偏差是方向性的，不是纯随机**：9 对里 7 对偏向同一边（实验臂慢）。因此逐对比较里出现系统性慢半拍的可能来自那一跑的位置/负载，而不是开关本身——这也是块内交错 + 只报合池比值的理由，不是「再补几对就能救单家族结论」。

## 把预算推迟到「回合已经长了」以后：0.956×，但赢面没了（BUDL，6 对）

`outputBudgetAfterSteps: 6` 的设计假设是：BUD3 里 8000 token 的伤害只发生在短任务（那里预算在第 2–3 步就烧起来，提前喊收尾），而收益发生在真正长的手上任务。于是同一条上限改成「第 6 步起才生效」，再跑三块 `off` 无关的 `def15` vs `budlate` 同块配对：

| 块 | 家族 | 逐对 `budlate/def15` | 预算触发 | 几何均值 |
| --- | --- | --- | --- | --- |
| `BUDL_DBG` | debugfix | 1.342 / 1.123 | 0 / 0（输出 4300、5769 token < 8000） | 1.228×（空转） |
| `BUDL_IMPL` | codeimpl | 1.151 / 0.839 | 0 / 1 | 0.983× |
| `BUDL_LEDGER` | ledger | 0.918 / 0.994 | 1 / 1 | **0.956×** |
| 合池 | — | — | 2/6 触发 | **1.048×** |

结论三条：

1. **推迟消除了收益，没消除噪声。** 同一家族 ledger，BUD3 里 `bud8` 是 0.720×（一对 0.531），推迟后只剩 0.956×——因为这一跑的 `def15` 基线本来就只走 10 和 13 步（BUD3 那两跑是 18 和 16 步），长回合的收益场景没有出现。可见「长任务」不是任务标签给的，是同一标签下跑出来的步数给的，**用步数当扳机等于用上一跑的结果赌这一跑的形状**。
2. **代码实现类的伤害没有随推迟消失**：`BUDL_IMPL` 里触发的那一对 0.839×，没触发的 1.151×，两对之间差 37 %，再次落进空转带。
3. 合池 1.048×（n=6，2 对空转）落在上面 9 对空转对照的 0.89–1.34 带内 ⇒ **判定为「无效果」**，而不是「略慢」。因此 `outputBudget` 家族到此收口：上限不是速度杠杆（BUD3 已证），推迟触发也不是补救（BUDL 已证），`outputBudget` / `outputBudgetAfterSteps` 两个开关**保持默认 0 / 0**，只作为可分家族复现的实验旋钮保留。

顺带一个测量警告：同样是 `ledger` + `def15`，BUD3 某跑 830 s / 18 步，BUDL 某跑 717 s / 10 步——**同一任务同一条臂的步数能差 80 %**。所以跨块比较绝对时间没有意义，只有块内交错配对的比值能用。

## 开局批量化（openBatch）提示：16 对里 1.022×，家族之间符号相反（OPEN8，纯诊断）

机制假设：`def15` 的系统提示已经说了「能并行就并行」，但**没有说「第一件事就把这一轮的调用一次发齐」**。`openBatch` 只在第 1 步补这一句，等于直接砍掉一个往返。为了让 n 足够大，选两个短任务家族各跑 8 对（`def15` vs `open15`，同块交错，共 16 对 32 格）。指令确认在每一条 `open15` 跑里都出现了（`grep` 会话转录，8/8 命中），所以这是「开关真的触发了」的对照，不是空转。

| 家族 | n | 几何均值 | 中位数 | 更快 | 步数（和对和） |
| --- | --- | --- | --- | --- | --- |
| debugfix | 8 | **1.228×** | 1.213 | 3/8 | 36→36（8 对里 6 对步数完全不变） |
| csvpipe | 8 | **0.850×** | 1.020 | 4/8 | 120→70 |
| 合池 | 16 | **1.022×** | 1.110 | 7/16 | 156→106 |

三条读数：

1. **合池无效果。** 几何均值 1.022×、中位数 1.110×、7/16 更快，落在空转带里；家族之间方向相反（debugfix 慢 23 %、csvpipe 快 15 %），正是 PPX2 那次「跨家族符号翻转」的重演，**任何单家族结论都不能引**。
2. **在 debugfix 上「少一个往返」根本没发生**：8 对里有 6 对步数一模一样（4→4、5→5），当步数不变而总时长变长时，多出来的是**输出**（如 p5 3947→5331、p8 3511→5855 token）。原因很直接：`def15` 的系统提示已经要求并行，再在第 1 步喊一次「一次发齐」，模型会把开局写成一份更大的计划式调用，**没省往返，只多写了几千 token**。
3. **csvpipe 的 0.850× 不是杠杆，是尾部救援**：两对（p2、p7）的 `def15` 跑崩成了 21 步（483 s）和 23 步（369 s），而对应的 `open15` 只走 6 步和 4 步；刨掉这两对，剩下 6 对是几何均值 1.36×。也就是说 `open15` 的收益全部来自「让本来会绕圈的那一跑不绕圈」，这是方差，不是速度——它同时说明 `def15` 在 csvpipe 上有不可忽略的长尾（8 对里 2 对超过 19 步，而中位数是 9–14 步）。

结论：`openBatch` **保持默认 `false`**，不进入默认路径。附带一条可用的观察：csvpipe/debugfix 这类的长尾与几何均值同样重要，后面报告「更快多少」时应同时给更快对数与最慢那一对。

## 提前收尾线（finishLineSteps: 8）：步数砍掉 33 %，墙上时间只动 4 %（FL6，4 对）

`finishLineSteps` 的意思是「到了第 N 步就对模型说：现在开始收敛到最终交付」，本意是砍掉多余的收尾轮次。在 migrate 家族只跑 4 对（`def15` vs `fl8`），因为这条臂动的是回合结构，先看形状再看倍数：

| 对 | `fl8/def15` | 步数 | 输出 token |
| --- | --- | --- | --- |
| p1 | 1.288 | 11→7 | 13 224→14 418 |
| p2 | 1.003 | 14→8 | 19 590→16 436 |
| p3 | 0.632 | 12→10 | 23 180→17 708 |
| p4 | 1.031 | 12→8 | 14 827→17 320 |
| 合计 | **0.917**（几何 0.958，中位 1.017） | **49→33 = 0.674×** | 70 821→65 882 = 0.930× |

这条结果推翻了一个容易写错的因果：**步数不是杠杆，是伴随变量**。4 对里步数平均砍掉 33 %，墙上时间只降 8 %（合计）/ 4 %（几何），因为被砍掉的那些步并没有消失——它们的内容被塞进了保留下来的步里，于是单步输出变大（p1、p4 的输出反而涨 9–17 %），而墙上时间 ≈ 输出 token（本文件前面测的 9.45 s/1k）。

回看主口径就清楚了：`off → def15` 是**步数 0.827× 且输出 0.818× 同时下降**，那才是 0.823× 的来源；单独压缩步数（`fl8`）不降输出，所以不降时间。这条也解释了为什么 BUD3/BUDL 的预算扳机在 ledger 上能到 0.53× 而在 codeimpl 上是 1.4×：它压的是「继续写」这件事本身，不是「多一轮」这件事。

`finishLineSteps` 保持默认 `0`（不启用）。它作为「回合结构」旋钮的定位保留，但速度证据不支持它进默认路径。

## 整文件改写的「只发改动行」提醒（preferPatch）：中位数 0.926×，几何 1.104×（PP8，8 对）

机制最直白的一条候选：`write` 一个 2000 字符以上的整文件，工具参数里的文件正文本身就是输出 token；提醒一次「改已有文件只发改动行（edit hunk）」，落盘内容不变，省的是解码量。codeimpl 家族 8 对 `def15` vs `pp15`（唯一差别是 `preferPatch: true`），并用会话转录确认提醒在 8/8 条 `pp15` 跑里都真的发了、`def15` 里 0 次：

| 对 | `pp15/def15` | 输出 token | 步数 |
| --- | --- | --- | --- |
| p1 | 0.651 | 13 188→9 616 | 6→7 |
| p2 | 0.870 | 20 981→16 876 | 8→5 |
| p3 | **4.549** | 8 068→31 819 | 4→8 |
| p4 | 1.520 | 15 373→22 977 | 7→13 |
| p5 | 0.561 | 12 904→7 443 | 5→6 |
| p6 | 0.907 | 27 506→21 869 | 14→15 |
| p7 | 1.169 | 10 489→16 074 | 5→11 |
| p8 | 0.945 | 18 255→17 008 | 7→8 |
| 合计/统计 | 几何 **1.104**，中位 **0.926**，5/8 更快 | 126 764→133 682 = 1.055 | 56→73 = 1.30 |

读数：

1. **机制真的生效了，而且是双向的。** 5 对里输出立降 7–42 %（p1 −27 %、p5 −42 %、p2 −20 %、p6 −20 %），说明「别发整个文件」确实省解码；但另外 3 对里提醒之后模型改成**多次小改**，步数从 4→8、7→13、5→11，输出反而涨到 3.9 倍——省下的单次正文被多出来的回合吃掉了。
2. **这是双峰分布，不是均匀位移。** 中位数 0.926×（多数对更快）与几何均值 1.104×（被一对 4.5× 拖累）同时成立；5/8 更快也成立。此时任何一个单独报数都是误导，三个必须一起出现——这也是本文件反复强调「只用几何/中位而不用比值均值」的边界：它能挡住的是均值陷阱，挡不住重尾。
3. 触发链也解释得通：提醒只在「已经写过一个大整文件之后」才发，这条路径上本来就有两种世界——一个文件改一处（赚 20–40 %），或一个文件要改多处（小改的固定开销每处再付一次）。运行时无法预知是哪一种，所以这不是可门控的杠杆。

### migrate 上重跑：同一个方向更明显（PP6，6 对）

换到 migrate（大量「同一文件多处小改」的任务）再跑 6 对，提醒同样 6/6 触发：

| 对 | `pp15/def15` | 步数 | 输出 |
| --- | --- | --- | --- |
| p1 | 0.971 | 16→8 | 26 172→27 199 |
| p2 | 2.901 | 9→33 | 13 757→27 560 |
| p3 | 1.036 | 8→11 | 18 473→18 680 |
| p4 | 2.029 | 15→16 | 16 963→31 967 |
| p5 | 0.616 | 11→12 | 21 399→15 324 |
| p6 | 1.642 | 8→15 | 14 181→23 842 |
| 合计 | 几何 **1.348**，中位 1.339，2/6 更快 | 67→95 = 1.42 | 110 945→144 572 = 1.30 |

两个家族合并（14 对）：**几何 1.203×、中位 1.100×、7/14 更快、步数 1.37×、输出 1.21×**。migrate 的 p2（9→33 步）说明小改策略在「多处改动」任务上会失控：每处都付一次完整回合的固定开销（约 32 % 的单步时间不在模型调用里），回合数一涨总时间必涨。

结论：`preferPatch` 保持默认 `false`，不进入默认路径；两族合并 1.203× 属于「明确更差」，不是噪声。若要继续，唯一有意义的方向是给它一个「本轮已经出现 ≥2 次小改就停手」的止损条件，而不是默认打开——那属于新机制，需要新的同块诊断，不在这条结论内。

## 压缩提醒的触发时点（`disciplineAfterSteps` 2 vs 4）：13 对合并 0.625×，省的是往返次数而不是正文（2026-10-28）

**问题。** `outputDiscipline: late` 那句提醒（「剩下的话只写下一步要用的」）默认第 4 步入场。之前试过改变**措辞强度**（`odt15`：全程更紧的口径，W1 12 对实测 1.213× 更慢，见上文），结论是「开局就压措辞有害」。但那是**换说法**，不是**换时点**：同一句话、同样的字数、只把入场点从第 4 步提前到第 2 步，从没测过。`disciplineAfterSteps` 就是这个旋钮。

**臂。** 两臂其余字段完全一致（`gear: auto`、`outputDiscipline: late`、`preferPatch: false`、无 `disciplineRepeatAtSteps`）；`d2` = `disciplineAfterSteps: 2`，`def15` = 用内置默认 4。所以唯一差别是那一句话在第几步注入。

**说法要收窄：它是砍掉爆走，不是普遍变短。** `migrate` 两臂逐会话步数是 `def15` [8, 9, 9, 11, 30, 32]、`d2` [10, 11, 12, 12, 14, 15] —— 中位反而从 10 升到 12，`d2` 没有一个会话失控：步子数几何 0.887× 完全来自那两个 30/32 的极端格。所以这条旋钮的正确描述是「把会走远的回合作短」，不是「每回合步骤更少」；同理`debugfix` 的 `d2` 步数 [4,4,4,4,5,5,5] 对 `def15` [4,4,5,5,6,6,8] 是收尾更齐，不是普遍减步。

**先证明它真的提前了。** 直读会话 transcript（`user/message` 里的注入文本，穷举 `prose is now the cost`）：`def15` **7/7 会话首次出现在第 4 步**，`d2` **7/7 首次出现在第 2 步**（`migrate` 块同：6/6 在第 4 步、6/6 在第 2 步）。逐格命中，不是「大概率」。这条现在由脚本可复算：`python3 /Users/apple/.local/fm/dfsign.py D2_MIG`（逐会话打印 `at_steps` 与 `steps_per_session`，注入文本按 `user/message` 里的确切 needle 定位）。

**结果（配对，treatment / baseline < 1 才快；几何/中位/合计，算术均值禁用）。**

| 块 | 家族 | 对 | 几何 | 中位 | 合计 | 赢面 | 步数几何 | 输出几何 | 范围 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `D2_DBG` | debugfix | 7 | **0.535×** | 0.446× | 0.524× | 5/7 | 0.833× | 0.549× | 0.352–1.072 |
| `D2_MIG` | migrate | 6 | **0.751×** | 0.738× | 0.713× | 4/6 | 0.887× | 0.700× | 0.229–1.724 |
| 合并 | 2 家族 | 13 | **0.625×** | 0.551× | 0.680× | 9/13 | 0.857× | 0.614× | 0.229–1.724 |

墙钟合计 1963 s → 1335 s。26 行全 `hasSessionIds`、全 `green`，逐格断言 `debugfix` 13/13、`migrate` 14/14 满，**没有一格质量下降**。

**机制：不是「正文写短了」。** 同一批 transcript 里，`debugfix` 每会话正文总量 `def15` 109 字符、`d2` 54 字符（本身就近乎为零），`migrate` 118 → 53 —— 正文差几十字节，无法解释墙钟 0.535×。真正的差别在**往返次数**：

| 块 | 臂 | 步数/会话 | 工具调用/会话 | 参数字符/会话 | 参数字符/步 |
| --- | --- | --- | --- | --- | --- |
| debugfix | `def15` | 5.43 | 9.57 | 7 359 | 1 356 |
| debugfix | `d2` | 4.43 | **5.43** | 5 526 | 1 248 |
| migrate | `def15` | 16.17 | 17.17 | 16 002 | 990 |
| migrate | `d2` | 12.33 | **12.67** | 13 574 | 1 101 |

每步的产出体量基本不变（±10 %），调用次数少了 26–43 %，与会话输出的 0.614× 同向。这与合池结论一致：速度来自**少做几遍、少复验几遍**，不是把同样的活写得更短。

再往下拆一层：墙钟 = 步数 × 每步时长，而这两项**都在降**（`debugfix` 步数几何 0.816×、每步 geo 4 801 → 3 508 ms = 0.731×；`migrate` 0.887× 与 6 793 → 5 624 ms = 0.828×，均由 transcript 的 `step/start`–`step/end` 时间戳直接算出）。但每步变短不是第三种机制：`step` 时长包含它内部的工具执行，调用数从 1.76 降到 1.23 次/步，步骤自然短。所以只有两个真正独立的量：**回合数**和**每回合里的往返数**。

按调用类别拆开，少掉的**不是验证**：`debugfix` 两臂都是 7/7 会话跑了测试（`verify` 13→8 次，第一次验证仍在第 3–4 次调用），`migrate` 两臂本来就不自测（1→0）；真正塌下去的是**改文件的反复**（`edit` 33→7 次）和**回头再读**（`migrate` 的 `inspect` 63→41 次）。也就是说提醒提前之后，模型更少「改一版→再看一眼→再改一版」，而不是「不检查就交」——隐藏测试逐格全过与此一致。

**赢在哪：切长尾，不是匀速。** 两对反向（`migrate` p2 100.66→173.54 = 1.724×、p5 225.53→342.75 = 1.520×），但赢的几对是 `def15` 走远的长尾（p3 480.37 s / 32 步 → 110.03 s / 12 步；p4 371.27 s / 30 步 → 204.70 s / 12 步）。中位 0.551× 低于几何 0.625× 也说明「多数格子明显更快、少数更慢」。所以这条旋钮的诚实描述是**压长尾**，不能写成「每格都快 38 %」。

**与 `odt15` 的 1.213× 不矛盾。** `odt15` 是全程换措辞（从第 1 步起一直紧），代价是早期步骤本来短、被压成更多往返；`d2` 是同一句话只提前到第 2 步，措辞、字数、冷却期都没变。两者是不同变量，不能互相引用。

**预注册：把默认从 4 改成 2 的通过判据（先写判据，`D2_IMPL` 数据还没出来）。** 只认 `codeimpl`（4–13 步，中等长度，是已证明受益的家族）8 对：

1. **8 对全有效**（`exit == 0 and steps >= 1 and out > 0 and hasSessionIds and not leak`），且每格 `green`。
2. **几何 ≤ 0.95×、中位 ≤ 1.00×、赢面 ≥ 5/8**：三家族（debugfix 0.535×、migrate 0.751×、codeimpl）不得出现方向翻转。`codeimpl` 若 > 1.00×，说明收益只属于「回合短」的家族，默认**不动**（维持 4），这条线判定为家族相关。
3. **三家族合并（21 对）几何 ≤ 0.85×、中位 ≤ 0.90×**。
4. **下发必须命中**：`d2` 臂 ≥ 7/8 会话在第 2 步收到（`codeimpl` 有 0 个会话不到 2 步才行；目前 `codeimpl` 16 个 `def15` 会话步数最少 4，够）。
5. 质量：任何一格 `hasSessionIds`/`green` 倒退 ⇒ 不通过（速度不能以质量为代价）。

全部满足才改默认并发版；否则保持 4，并把「时点」这条线记为家族相关、不再扫。**不因为「几何好看」就改默认**：`odt15` 当年就是被单个家族的好看数字骗过一次。

**为什么还要扫 3/6/8。** 时点如果是连续的，最优点应该能夹出来；`d3`/`d6`/`d8`/`dr8` 四个臂（12 格/块，`migrate`）正在跑，只作为曲线定位，不参与发版判据。

**曲线第一个补点 `D6_MIG`（入场 6 对 4，`migrate` 6 对）：空。** 几何 0.915×、中位 0.995×、合计 0.944×、3/6 更快、符号 p = 1.0，范围 0.593–1.546×；步数几何 1.188×（反而更多步）、输出几何 0.892×。也就是说把同一句话从第 4 步**推后**到第 6 步，什么也没发生——既没有更好也没有更坏。这与「提前有效、推后无效」的方向一致，也解释了为什么默认 4 一直在「快一点但不稳」的区间（0.72–0.93 的家族中位数）：第 4 步对短回合太晚、对长回合太晚，两头都不占优。曲线最终形状等 `D3_MIG`/`DR8_MIG` 补齐。`D8_MIG` 在 2/12 格时**主动终止**（为把发版判据块 `D2_IMPL` 提前），只留 2 行、不构成结论，重跑前会先 `scrub`。

**发版判据块 `D2_IMPL` 出来了：不通过，默认保持 4。** 只认预注册的 `codeimpl` 8 对（16 行，两臂各 8）：几何 **1.073×**、中位 **1.129×**、合计 1.194×、只有 **3/8** 更快、符号 p = 0.7266（3/8 与随机不可分，方向不显著），范围 0.374–2.688×；步数几何 **1.232×**（提前反而**多**走步）、输出几何 1.157×。5 条判据里第 2 条三项全挂（1.073 > 0.95、1.129 > 1.00、3/8 < 5/8），第 3 条（合并 21 对 0.768× / 0.787×）和第 5 条（质量 0 格倒退）通过，第 4 条下发命中（`codeimpl` `d2` 臂 8/8 在第 2 步收到，`def15` 8/8 在第 4 步）。**判决：`VERDICT: HOLD default=4`。**

**这条杠杆的「成本侧」是严格受控的（离线读 transcript）。** 提醒文本在 `def15` 与 `d2` 两臂是同一句、同样长度：三个块 42 个会话**每回合恰好注入 1 次**，每次 **318 字符**，两臂总字符数完全相同（`D2_DBG` 各 2226、`D2_IMPL` 各 2544、`D2_LED` 各 1908）；唯一差别是注入步：`d2` 一律第 2 步、`def15` 一律第 4 步（`at_steps` 逐条核对）。所以家族间那 0.5×/1.07× 的差别**全部来自时点**，不是「多给提示」换来的——这也是为什么它能保持质量零倒退。

**第三族 `D2_LED` 也跑满了（6 对 12 行，发布前就登记在 d2 的块表里）：这一族是**大幅受益**。** 几何 **0.506×**、中位 0.551×、合计 0.436×、5/6 更快、符号 p = 0.2188，范围 0.157–1.232×；步数几何 0.699×、输出几何 0.540×。两臂 12 行**全绿且 22/22 全过**（`hasSessionIds` + `green` + `valid` 全为真），所以提速不是靠少检查换来的：逐对步数 `15→11`、`13→6`、`16→25`、`6→8`、`63→13`、`10→8`——注意 p3 提前后**多**走了 9 步却仍快 0.616×，p5 把 63 步压到 13 步（1009 s → 158 s）。这样方向表就完整了：`ledger` 0.506×、`debugfix` 0.535×、`migrate` 0.751× 三族受益，`codeimpl` 1.073× 受害；受害的只有「短回合、单文件、第一步还在定位」这一类。

原因不是「提醒这句没用」，而是**同一个时点在两族里方向相反**：`debugfix` 0.535×、`migrate` 0.751×，`codeimpl` 1.073×。逐格看更清楚——`codeimpl` p4 的 `d2` 走 17 步（`def15` 8 步）、p3 1.980×、p4 2.688×，而唯一赢的 p1 是 `def15` 自己走到 5 步的那格。把 p4 的调用序列摊开：`def15` 5 步 4 次调用（`write` → `python3 - <<PY` 校验 → `ls && py_compile` → `rm -rf`），`d2` 8 步 7 次（`pwd && ls` → `write` → python 校验 **×2** → `python3 --version && python` 再校验 → `ls && find` → `rm -rf`）：在本来就最短的直线上，第 2 步的提醒**加**了探索和重复校验。所以这条旋钮的准确描述是「**把会走远的回合作短，代价是给本来就短的回合加了开销**」——家族相关的时点杠杆，默认维持 4。

## 预注册：条件入场 `disciplineEarlyCalls`（先写判据，数据未出，2026-10-28）

**为什么不是放弃提前，而是给它加条件。** `D2_IMPL` 的结论是「提前这一步本身好不好，取决于这个回合会不会走远」。那就把它明说出来：只在**回合已经显出会走远**的时候才提前。可用的预测信号必须在第 2 步之前就能读到、且是 harness 自己的机械量——用**第 1 步已经发生的工具调用次数**。基线 `def15` 的实测分布（全部来自本仓库 run 目录的 transcript）：

| 家族 | 会话 | 第 1 步调用数 | 分布 |
|---|---|---|---|
| `debugfix` | 7 | 2 | 2,2,2,2,2,2,2 |
| `migrate` | 6 | 2 | 2,2,2,2,2,2 |
| `codeimpl` | 8 | 1 | 1,1,1,1,1,1,1,1 |

**这条信号在三个家族里是干净分开的**：单文件实现任务第 1 步只发 1 次调用（8/8），多文件/调试任务发 2 次（13/13）。所以新旋钮 `disciplineEarlyCalls`（默认 0 = 不加条件，即 1.5.12 行为）：给正数 N 时，`disciplineAfterSteps` 的提前只在「**进入当前步之前**本回合已发出 ≥ N 次工具调用」时生效，否则回退到内置第 4 步。计数刻意扣掉当前步自己的调用（`turnCalls - stepCalls`），否则一步可以先付钱再买票。

**离线复算这条常量（数据未出，先验证可操作性）**：对已录下的基线会话逐条重放闸门 —— 只读第 1 步的 `tool/call` 数，预测「第 2 步触发」还是「回落到 4」，并核对基线自己的首次提醒步。`D2_DBG` 7/7 预测第 2 步、`D2_MIG` 6/6、`D2_IMPL` 0/8；三条基线全部恰在第 4 步触发（1.5.12 行为对照 26/26）。外部对照 `D2_LED`（12 行齐了）里基线的第 1 步调用数是 `[2,1,1,1,1,2]`，闸门只放 2/6 进来 —— ledger 恰恰是最喜欢提前的家族（0.506×，见上），这条闸门**故意放着它不赚**。所以这条闸门的定位是「不伤」，不是「多赚」：它只为挡住 `codeimpl` 那种「第 1 步还在定位」的回合。

**臂**：`d2g` = `outputDiscipline: late, disciplineAfterSteps: 2, disciplineEarlyCalls: 2`（其余同 `def15`）。**块**：`D2G_IMPL`（`codeimpl` 8 对）、`D2G_DBG`（`debugfix` 7 对）、`D2G_MIG`（`migrate` 6 对），基线均为 `def15`，共 21 对。**通过判据**（五条全满足才建议把默认改成 `d2g`）：

1. `D2G_IMPL` 8 对：几何 ≤ 0.95×、赢面 ≥ 5/8 —— 不得复现 1.073×。按信号分离，这一族应基本退化成 `def15`（几何 ≈ 1.00×），**退化即可，不要求它变快**。
2. `D2G_DBG` 7 对：几何 ≤ 0.80× —— 允许从 0.535× 回吐，但必须仍明显快。
3. `D2G_MIG` 6 对：几何 ≤ 0.90× —— 0.751× 的收益要留住大半。
4. 合并 21 对：几何 ≤ 0.85×、中位 ≤ 0.90×。
5. 质量零倒退；下发按条件命中：`D2G_IMPL` 8/8 仍在**第 4 步**收到，`D2G_DBG`/`D2G_MIG` 8/8 与 7/7 在第 2 步收到。

任何一条不过 ⇒ 这条线判为「时点不可救」，默认永久停在 4，不再追。

### 补测（2026-10-29，`D2G2`）：闸门没覆盖过的两族补上，方向相反

上面的 21 对只覆盖 `codeimpl`/`debugfix`/`migrate`。闸门的常数 2 在 `ledger`/`shellpipeline` 上一次都没验过，而这两族恰好是 `d2`（无闸门提前）最两极的地方（`ledger` 0.506×、`shellpipeline` 1.111×）。`D2G2` 用这两族各 3 对补齐（`def15` 为基线，12 行全部可用、丢弃 0、质量零倒退）：

```
python3 bench/suite-run.py --tasks ledger,shellpipeline --arms def15,d2g --pairs 3 --tag D2G2
python3 bench/block_score.py D2G2 def15 d2g
```

| 族 | 对 | 几何 | 逐对比值 | 备注 |
|---|---|---|---|---|
| `ledger` | 3 | **1.898×** | 2.68 / 0.95 / **2.70** | 输的两对步数 8→10、7→13，输出 12.1k→34.0k、15.9k→29.1k token |
| `shellpipeline` | 3 | **0.597×** | 0.47 / 0.69 / 0.66 | 赢的三对步数 23→19、27→10、24→17 |
| 合并（= 上节 21 对 + 6 对） | 27 | **0.816×** CI95 [0.650, 1.023] | 18/27 更快 | 中位 0.719×、步数 0.892×、输出 0.777×、符号 p=0.122 |

**判定：HOLD 不变，默认继续停在第 4 步。** 补测不是把 CI 收窄，而是把结论说得更准：同一个时点在两族上方向相反——`shellpipeline` 这种「一条命令跑完再读结果」的任务提前提醒很值（0.597×），`ledger` 这种要逐笔对账的任务提前提醒会让它重做并超写（输出 token 涨到 1.9×，单对 2.7×）。所以「第一步调了几次」不是这个时点该用的信号，**时点类旋钮到此收档**：默认 4 不再动，也不再为新时点补块。

## 预注册：`disciplineRepeatAtSteps`（第二次压缩提醒）的通过判据（2026-10-28，先写判据再跑）

**为什么是它。** 合并块里唯一被证明有效的机制，是 `outputDiscipline: late` 在第 4 步注入的那一句「剩下的话只写下一步要用的」。它**每回合只发一次**。可是合池的逐格数据显示，回合越长，赢面越小：`debugfix` 1.032×、`perfrefactor` 1.009×、`csvpipe` 0.982×，而 `shellpipeline` 0.699×、`codeimpl` 0.710× —— 长的回合里，第一次提醒之后的那些步又把输出写长了。这不是新机制，同一个机制的第二枪：同一句话，在更晚的一步再说一次，每回合仍然最多一次。

**旋钮。** `disciplineRepeatAtSteps`（默认 `0` = 不发，行为逐字节不变）。`dr8` 臂：`disciplineAfterSteps: 4`（与默认同入场点）+ `disciplineRepeatAtSteps: 8`，所以两臂的唯一差别就是第 8 步的那句话。

**为什么是第 8 步而不是更晚。** 提醒要到得了才算测到。已测 `migrate` 的 16 个 `def15` 有效会话里步数中位 13、15/16 走到第 8 步，但只有 10/16 走到第 12 步——第 12 步发不出去的会话会变成「看起来没效果」的假空。所以入场点 4 + 重复 8（间隔 4 步，中间隔着冷却期），预期 15/16 命中。

**先验证提醒真的下发。** 逐会话搜确切字符串 `still long. Keep`，`dr8` 必须 ≥1、`def15` 必须 0。任一格 0 命中 ⇒ 该格记为「未测到」而不是「更慢」。

**判据（配对，treatment / baseline < 1 才是快）：**

1. **至少 6 对有效配对**（`migrate`，同一块 `DR12_MIG`，两臂交错）。有效 = `exit == 0 and steps >= 1 and out > 0 and hasSessionIds and not leak`。
2. **几何均值 ≤ 0.95×，且中位 ≤ 1.00×**。只报几何/中位/合计比，算术均值禁用（重尾）。
3. **赢面至少 4/6**，否则即使几何更好也记为「不稳定」。
4. **输出 token 的几何均值 ≤ 0.95×** 才允许说「赢在输出变短」；如果墙钟变好而输出没变短，那就是采样噪声，判空。
5. **质量不得倒退**：`hasSessionIds` 全对，`green` 不少于对侧臂；任何一格质量下降 ⇒ 直接不通过（速度不能以质量为代价）。
6. 不通过就维持默认 `0`，并在文档里写「第二次提醒无效/有害」，不再重试第三次。

**已知上限。** 单块 n=6 的检出下限约 ±15 %（见「空转对照」一节：同臂对同臂 9 对的范围是 0.889–1.342×）。所以这里只认「几何 ≤ 0.95× 且赢面 4/6 以上」这种**远大于噪声**的结果；0.96–1.04× 一律判空。

## 结果：`D2G` 预注册判据判 **HOLD**（2026-10-28，21 对跑完）

**数字。** 21 对里 14 对更快，合并几何 **0.756×**、中位 **0.719×**、总时长 **0.685×**，质量 0 回归。分块：

| 块 | n | 几何 | 中位 | 更快 | 步数几何 | 输出几何 | 范围 | 判据 |
|---|---|---|---|---|---|---|---|---|
| `D2G_IMPL`（`codeimpl`） | 8 | 0.889× | 0.905× | 5/8 | 0.753× | 0.910× | 0.373–2.093× | 过 |
| `D2G_DBG`（`debugfix`） | 7 | **0.903×** | 0.794× | 4/7 | 0.987× | 0.731× | 0.669–1.368× | **不过（要 ≤ 0.80）** |
| `D2G_MIG`（`migrate`） | 6 | **0.495×** | 0.426× | 5/6 | 0.964× | 0.478× | 0.259–1.601× | 过 |

**判定。** 第 2 条把 `debugfix` 的门槛设在 0.80×，实测 0.903×；第 4 条合并 0.756× / 中位 0.719× 与第 3 条 `migrate` 0.495× 都远好于门槛，第 1 条 `IMPL` 0.889× 也过，但预注册规则写的是「五条全满足才建议改默认」，所以**不挑指标发版**：默认永久停在 4，这条线结束。逐格看，输的三格（`codeimpl p6` 2.093×、`migrate p3` 1.601×、`debugfix p5` 1.368×）都是本来就没有探索空间的直线任务，提前提醒让它们多走一两步；赢的三格（`migrate p1` 0.259×、`migrate p2` 0.312×、`codeimpl p3` 0.373×）都是长任务。方向按家族与长度分层，不做线拟合。

**两处与预注册的偏差，记在这里不掩盖：**

1. **判据 5 的「下发条件」原本没被脚本执行，现已补上并判 FAIL。** 预注册写的是「`D2G_IMPL` 8/8 仍在**第 4 步**收到，`D2G_DBG`/`D2G_MIG` 在第 2 步收到」。实际逐会话扫描：`def15` 8/8/6/7 全在第 4 步，`d2g` 在 `IMPL` 是**第 3 步**、在 `DBG`/`MIG` 是第 2 步。`bench/d2gscore.py` 现在自己调 `bench/d2gsign.py` 断言每个臂的命步（这两个脚本原先只在 `~/.local/fm/`，1.5.19 起随 `bench/` 一起进仓，`python3 bench/d2gscore.py` 可复现本节全部数字）（`5 delivery <block> <arm> step N`），输出里 `D2G_IMPL d2g step 4` 一行是 **FAIL**（实际 `[3]`）。`IMPL` 按设计应在 `disciplineEarlyCalls: 2` 下退化成第 4 步，实际提前到了第 3 步 —— 这解释了它为什么没像预期那样完全退化（0.889× 而不是 ≈1.00×），也说明判据 5 的「按条件命中」这一半同样没通过。原先 gate 5 只校验 `hasSessionIds`/`green`，缺口已修（见上）；判据 5 因此是「质量过、下发步位不过」，结论仍是 HOLD。
2. **`delivery audit` 探针不可用。** 报告按固定句式找完成通报，命中 0/16、2/14、0/12。这个探针在本套 fixture 上恒为 0，不能当「任务没交付」的证据；质量结论只引用 `hasSessionIds`/`green`（21 对全部 1/1）与判据 5 的 0 坏格。

### 预注册前提检查：提醒之后输出会不会「重新变长」

`disciplineRepeatAtSteps` 之所以值得一测，前提是「第 4 步的提醒压住输出，之后随着步数增长输出又爬回来」。用 `~/.dsh/fast-mode-metrics.jsonl` 按 sid 联表（会话目录名里带 tag/task/pair/arm，不用猜），取每个会话第一个 turn 的每步 `output`（可见输出 + reasoning，中位），三个 `D2G` 块读出来是：

| 块 | 臂 | 步 1… | 形状 |
|---|---|---|---|
| `D2G_IMPL` | `d2g`（第 3 步提醒） | 5162 1562 1007 1132 712 599 186 128 | 单调降，末端几乎停写 |
| `D2G_IMPL` | `def15`（第 4 步） | 2083 1681 969 1248 269 140 1910 900 364 1654 631 1866 352 68 | 提醒后在第 7、10、12 步三次反弹 |
| `D2G_MIG` | `d2g`（第 2 步） | 120 83 3568 313 393 716 384 282 1244 350 554 178 768 656 | 无单调趋势，长尾在 0.1–1.2k 波动 |
| `D2G_MIG` | `def15`（第 4 步） | 155 9080 1090 369 562 1282 1676 1491 1188 2352 462 446 125 350 | 第 4 步后先升后落，峰值在 6–10 步 |

两点结论（只对这套 fixture）：一，`def15` 的「反弹」是存在的（`IMPL` 第 7/10/12 步明显），所以重复提醒的动机不是凭空来的；二，`d2g` 在两块里都没出现同样的反弹——它的提醒本来就更早，长尾步数也少，**现有数据无法证明第 8 步再提醒一次还能压住什么**。所以 `DR8_MIG`（`disciplineRepeatAtSteps: 8`，6 对，预注册判据在文末）照跑，但预期先降为「接近 1.0× 的无操作」，跑完以数字为准。另外说清一点：这些 fixture 的 assistant 可见文字中位只有 0–200 字符，**提醒移动的主要是工具参数与 reasoning 的 token 量，不是「话多」**；`out_geo` 0.478–0.910 说的就是这个量。

## 结果：`D2` 在 debugfix 上的独立复现（`D2B_DBG`，7 对新会话）

`D2G` 判 HOLD 之后，`d2`（`disciplineAfterSteps: 2`，无条件第 2 步提醒）唯一还站得住的证据是当初 `D2_DBG` 的 0.535×。为判断那是真效应还是那 7 对的运气，用同协议重跑了一整块新会话（新 pair 编号、ABBA 交错、同一 fixture）：

| 指标 | 数值 |
|---|---|
| 对数 | 7 / 7 有效，`hasSessionIds`+`green` 21/21 格全过 |
| 几何均值 | **0.717×** |
| 中位 | 0.864× |
| 总计 | 0.633× |
| 更快 | 5 / 7（符号检验 p = 0.45，**不显著**） |
| 步骤 | `steps_geo` **1.118×**（平均步数变多） |
| 输出 | `out_geo` **0.787×**（输出确实变短） |
| 提醒步位 | `def15` 7/7 在第 4 步，`d2` 7/7 在第 2 步 |

怎么读：方向是复现的（输出变短、整体更快），但**幅度掉到 0.717×，中位只剩 0.864×，7 对里 5 对更快在统计上说明不了问题**；同时 `steps_geo` 1.118× 说明早提醒在这套 debugfix 上会让回合多走一两步——加速来自「每步写得更短」，不是「步数更少」，这和 `D2G_MIG` 的分解（`out` 0.478、`steps` 0.964）是同一套机制。合并原来的 `D2_DBG`（0.535×）与这块（0.717×），`d2` 在 debugfix 上的真实档位大约 **0.6–0.7×，且方差很大（单对 0.236–1.327×）**，所以任何「早提醒稳定快 2 倍」的说法都不成立。

## 结果：`D2B_MIG`（`d2` 在 migrate 上的新会话复现，3 对可用）

`D2` 在迁移类上的老证据只有 `D2_MIG` 的 6 对（几何约 0.75×）。同协议开一块新会话，6 对，ABBA 交错：

| 指标 | 数值 |
|---|---|
| 可用对数 | **3 / 6**（`migrate p4`–`p6` 的 12 行全是 1 步、0 字符输出、墙钟约 2 s 的空转，探测请求打空，整对作废，不参与平均） |
| 几何均值 | **0.326×** |
| 中位 | 0.322× |
| 更快 | 3 / 3 |
| 步骤 | `steps_geo` **0.575×**（18→7、11→10、13→7） |
| 输出 | `out_geo` **0.373×**（30470→9655、15107→12176、21858→4451） |

逐对：`p1` 393.3→126.7 s、`p2` 197.4→165.4 s、`p3` 355.3→45.5 s。`p1` 的基线 393 s 本身是这台机器当时负载下的长尾（同一块里 `def15` 其余行 117–280 s），所以**不把 0.326× 当成档位**，只记两点：方向在 migrate 上第三次复现（`D2_MIG` 0.75×、`D2G_MIG` 0.495×、本块 0.326×），且省下来的同样是步数与输出（长迁移里反复回看目录的调用被砍掉）。样本 3 对，符号检验没有意义，不进任何合并口径。

## 结果：`DR8_MIG`（`disciplineRepeatAtSteps: 8`，6 对）——重复提醒更慢，杠杆关闭

预注册的动机是「第 4 步提醒之后输出会反弹，所以第 8 步再说一遍」。`migrate` 上 6 对完整跑完：

| 指标 | 数值 |
|---|---|
| 对数 | 6 / 6 有效（12 格 `hasSessionIds`+`green` 全过；中途因 load ≥ 14 触发一次 ABORT，scrub 后补齐） |
| 几何均值 | **1.394×**（更慢） |
| 中位 | 1.451× |
| 更快 | 2 / 6（符号检验 p = 0.688，不显著） |
| 步骤 | `steps_geo` **1.091×** |
| 输出 | `out_geo` **1.365×**（提醒没压住，反而更长） |

逐对墙钟比：0.918 / 1.315 / 0.968 / 1.353 / 1.311 / 2.241。最差的一对 `p6`：`def15` 6 步 153 s / 13453 字符，`dr8` 15 步 343 s / 27414 字符——第 8 步那句提醒在这条长迁移上没有让模型收手，它把已完成的检查又做了一遍。

结论：**`disciplineRepeatAtSteps` 维持默认 `0`，这条杠杆关闭。** 预注册前提里「反弹存在」那半句是对的（`D2G` 的逐步骤输出确实反弹），但「再提醒一次能把反弹压回去并换来墙钟」被本块否掉；提醒本身不免费，每次注入都要占一段 prompt 与一次决策。

## 预注册：R1（压缩提醒里加一句「别重读本回合写过的文件」）

动机来自迁移族的长尾结构，不是新猜想：`D2B_MIG`、`DR8_MIG` 的逐对记录里，最慢的几对都是同一个模式——模型写完一处，随后把整份文件重新读回来确认，再进入下一次编辑；`DR8_MIG` 的最差对 `p6` 甚至把已完成的检查整段重做。读回来的内容要占工具参数与回显的输出 token，而输出 token 全部是解码时间。

实现取最小侵入：不新增消息、不改调度点，只在已有第 4 步压缩提醒的文本里追加一句「本回合已经写过的文件，按最后一次编辑和检查输出来判断，不要为了确认整份重读」。开关 `compactRereadHint` 默认 `false`，1.5.17 行为逐字不变。

臂：`bench/arms/r1.yml` = `outputDiscipline: late` + `compactRereadHint: true`，其余与 `def15` 同。基线 `def15`。

预注册判据（先写判据，再跑数据）：

- **R1_MIG**（migrate，6 对）：几何均值墙钟比 ≤ **0.85** 且至少 **4/6** 对更快 ⇒ 判「有效」。0.85–1.00 视为「方向对但不够」，> 1.00 视为「这句提醒是噪声」，两种情况杠杆关闭。
- **R1_DBG**（debugfix，7 对）：只作族间一致性参考，不作判据；若 migrate 通过而 debugfix 明显变慢（> 1.05）也要重估，因为提醒是按步数入场、与任务族无关。
- 质量门槛与既有块相同：每对必须 `hasSessionIds` 且 `green`，否则该对不计入（照旧报 n 与丢弃对）。

## 预注册：DEF（默认配置 vs 完全关闭，现代任务集上的干净对照）

`README` 一直写「默认就更快」，但这个对照在**当前任务集**上还没有一块干净的测量：带 `off` 臂的块要么是旧行格式（`V15`/`W3` 等），要么只有零星单对（`REL16` 5 对、`DBG2` 7 对，几何 0.889 / 0.958，都不是任务族内的成对结论）。所以先把判据写下来，再测。

臂：`def15`（`enabled: true, gear: auto` + 1.5.17 默认）对 `off`（`enabled: false`，插件完全退场）。任务族取长尾最明显的两族：migrate（6 对）与 debugfix（7 对）。

预注册判据：

- **DEF_MIG**：几何均值墙钟比 ≤ **0.85** 且至少 **4/6** 对更快 ⇒「默认在长迁移上更快」成立，可继续写在 `README`。0.85–1.00 ⇒ 只能写「不劣于原生」，不得写具体倍数。> 1.00 ⇒「默认更快」这句话必须从 `README` 撤回。
- **DEF_DBG**：同一门槛，族内独立判断；两族都过才算「默认普适更快」，只有一族过就按族写。
- 质量门槛照旧：`hasSessionIds` 且 `green` 才算一对；报 n 与丢弃对。这一块的墙钟是唯一可以横向引用绝对值的对照（`off` 臂是同一台机器同一时段的原生基线）。

## 预注册：D3（压缩提醒放到第 3 步）——默认时点能不能再往前挪一格

现有事实：`disciplineAfterSteps: 2`（`d2`）在 migrate 上 0.495–0.751×，但在 debugfix 与 codeimpl 上掉到 0.903–1.073×，所以默认停在 4；第 4 步（`def15`）是当前默认。第 3 步是两者中间唯一还没测过的位置——如果它保留迁移式的收益而把 `d2` 在 debugfix/codeimpl 上的损失收回来，默认时点就有值得改的方向；如果它两头都不占，默认继续停在 4，这条线彻底关闭。

臂：`d3.yml` = `outputDiscipline: late` + `disciplineAfterSteps: 3`，其余同 `def15`。

预注册判据（先写判据，再跑数据）：

- **`D3_MIG`**（6 对）：几何 ≤ **0.85** 且 ≥ 4/6 更快（与 `d2` 同门槛）。
- **`D3_DBG`**（7 对）：几何 ≤ **0.90** 且 ≥ 5/7 更快（比 `d2g` 那条 0.80 松一档，因为这次不是要它「更快」，只是要它「不掉队」）。
- **改默认只用同时满足两条**：`MIG` 过且 `DBG` 过，且 `DBG` 不比 `def15` 差到 1.00 以上；否则默认继续是 4。任何一条不过，本块都只作为「第 3 步的代价」记录，不改默认。
- 质量门槛照旧：`hasSessionIds` 且 `green` 才算一对；报 n 与丢弃对。

## 结果：`D3_MIG` 第 3 步提醒在 migrate 上 **0.509×（5 对可用）**，过 MIG 门槛；但出现一次跑到 50 步、测试没跑起来的无效跑

**数字（`def15` vs `d3`，migrate 6 对）。** 5 对可用（第 6 对 `d3` 被判 `valid=false`，见下）：几何 **0.509×**、中位 0.636×、4/5 更快、范围 0.151–1.134×；步数几何 0.622×、输出几何 0.528×。逐对：

| 对 | `def15` | `d3` | 比值 | 步数 | 输出 token |
|---|---|---|---|---|---|
| p1 | 275.6 s | 96.7 s | **0.351×** | 14→11 | 25 480→10 638 |
| p2 | 151.8 s | 96.5 s | 0.636× | 12→7 | 19 488→11 857 |
| p3 | 121.2 s | 137.5 s | 1.134× | 9→10 | 13 602→10 916 |
| p4 | 620.6 s | 93.7 s | **0.151×** | 36→11 | 40 815→9 277 |
| p5 | 199.1 s | 177.9 s | 0.893× | 20→12 | 23 771→21 142 |
| p6 | 210.0 s | 665.3 s（无效） | — | 11→**50** | 18 755→40 979 |

**判定。** 预注册 `D3_MIG` 要求几何 ≤ 0.85 且 ≥ 4/6 更快：可用 5 对几何 0.509×、4/5 更快，两条都过（若把 p6 计进去是 0.690×、4/6，也过）。所以 MIG 这一半 **PASS**，是否改默认还要等 `D3_DBG`（≤ 0.85 且 5/7）与 `D3_IMPL`。

**两处必须写清的偏差。**

1. **p6 的 `d3` 是无效跑，不是「慢」那么简单。** `valid=false` 在这一套里的定义是「测试没跑起来」：`total==0`（或 leak）。这一跑走了 50 步、40 979 token、665 s，`hasSessionIds`/`green` 仍为真但 grade 没有产出，所以按预注册的「`hasSessionIds` 且 `green` 且 `valid`」被丢弃。它同时也是**第 3 步提醒的尾部风险证据**：更早的提醒可能让模型在长迁移上更早收束、也可能让它反复试探。这一条不能当「d3 更慢 3.17×」讲，只能当「一次没交付的跑」记着。
2. **同一块里 `def15` 也出了一次 620 s / 36 步的长尾**（`valid=true`，交付正常）。两臂各一次超长尾，方向不同，说明 migrate 这一族本身是重尾分布，单看某一对没有意义——这也是继续只报几何 + 胜场、不报单对的原因。

**机制读数（沿用 9 条分解）。** 5 对里赢的四对全部同时压步数与输出（步数 0.622×、输出 0.528×），与 `d2`/`d2g` 在 migrate 上看到的「输出 token 才是主因」一致：p1 的 275.6→96.7 s 里步数贡献 −58 s、每步成本贡献 −120 s；p4 的 620.6→93.7 s 里步数贡献 −368 s。p5 是唯一「步数少了 8 步、每步反而更贵」的一对（9.8→14.6 s/步），净收益只剩 11 %——和「早提醒不是无条件赢」的既有结论吻合。

## 结果：`D2_PIPE`（`d2` 在 shellpipeline 上，4 对可用）——**没有信号，1.111×，判「未测到」**

**数字（`def15` vs `d2`，shellpipeline）。** 计划 6 对，队列在 4 对后暂停，可用 4 对、丢弃 0：几何 **1.111×**、中位 1.006×、2/4 更快、范围 0.641–2.594×；步数几何 **1.527×**、输出几何 1.047×；符号检验 p=1.000。逐对：

| 对 | `def15` | `d2` | 比值 | 步数 | 输出 token |
|---|---|---|---|---|---|
| p1 | 92.2 s | 239.1 s | **2.594×** | 7→29 | 10 129→25 071 |
| p2 | 218.7 s | 140.2 s | 0.641× | 15→13 | 21 010→13 788 |
| p3 | 219.1 s | 153.4 s | 0.700× | 19→23 | 27 113→17 137 |
| p4 | 114.0 s | 149.5 s | 1.312× | 12→15 | 12 038→14 065 |

**判定：不成立，也不反向。** 4 对里 2 对更快、几何 1.111× 由单个离群点（p1 的 7→29 步）拖出，去掉它三对几何 0.837×。n=4 太小，符号检验必然 p=1，这一块**不能写「更快」也不能写「更慢」**，只能记成「在 shellpipeline 上第 2 步提醒没有可测收益」。

**机制读数。** p1 的 2.594× 里步数贡献 +99 s、每步成本贡献 +48 s：`d2` 在第 2 步就收到「省着写」提醒后，反而在同一族里把 7 步的短流程拖成 29 步——与 `d2` 在 codeimpl/debugfix 上的既有结论（0.903–1.073×）方向一致：**早提醒在「本来就短、本来就要多验证」的形状上是净成本**。p2/p3 是反向证据（同样收到第 2 步提醒却步数不涨、输出降 35–37 %），说明签名不是家族级而是单跑级的。

**对队列的影响。** 这一族已经有 `d2g`（0.903×）与 `D2_PIPE`（1.111×，n=4）两块都不支持第 2 步，加上 `D3_MIG` 的 PASS 只覆盖 migrate，默认继续停在第 4 步；`D2_PIPE` 不再补对：现有 4 对几何 1.111×，即使补的两对都跑到 0.70×，6 对几何也只有约 0.94×，仍然写不出「更快」；再补 2 对换取一个大概率仍是 0.9× 上下的数字，成本不划算。下一步跑 `D3_DBG`（第 3 步在 debugfix 上掉不掉队），它是「默认时点能不能挪」的最后一个 gate。

## 结果：`D3_DBG`（第 3 步提醒在 debugfix 上）判 **FAIL**：6/7 更快、中位 0.876×，但几何 1.128× 被一格模型侧长尾顶穿

**数字（`def15` vs `d3`，debugfix 7 对，全部可用、丢弃 0）。** 几何 **1.128×**、中位 **0.876×**、6/7 更快、范围 0.585–7.346×；步数几何 1.321×、输出几何 1.116×；符号检验 p=0.125。逐对：

| 对 | `def15` | `d3` | 比值 | 步数 | 输出 token |
|---|---|---|---|---|---|
| p1 | 33.5 s | 32.1 s | 0.960× | 5→5 | 3 906→3 683 |
| p2 | 75.1 s | 43.9 s | **0.585×** | 6→5 | 5 956→4 317 |
| p3 | 49.5 s | 363.9 s | **7.346×** | 4→10 | 4 437→**21 743** |
| p4 | 76.8 s | 65.1 s | 0.847× | 7→6 | 5 091→4 733 |
| p5 | 69.8 s | 57.4 s | 0.822× | 4→7 | 4 967→3 757 |
| p6 | 103.7 s | 96.1 s | 0.926× | 4→4 | 6 154→6 786 |
| p7 | 40.4 s | 35.4 s | 0.876× | 4→9 | 3 908→3 235 |

**判定：FAIL，默认继续停在第 4 步。** 预注册 `D3_DBG` 要求几何 ≤ 0.90 **且** ≥ 5/7 更快；胜场过（6/7），几何不过（1.128×）。按预注册「改默认要 MIG 与 DBG 同时过」的规则，`MIG` 过、`DBG` 不过，所以 `disciplineAfterSteps` 不改，维持 4。

**p3 的 7.346× 不是 d3 的行为效应，是模型侧输出长度波动。** 该格 `llm` 361 s / 墙钟 364 s（99 % 在模型里），步数与 `def15` 同为有效、输出 21 743 token 对 4 437 token（4.9×）。全部 7 对里只有这一格输出变长，其余 6 格输出全部下降；把这 6 格单独加总几何是 **0.826×**（0.826 = 6 格几何，与胜场一致）。所以准确的说法是「第 3 步在 debugfix 上典型更快、但有一个尾部风险：更早的提醒偶尔与模型自己的长输出叠加」。**这不是把 FAIL 改判成 PASS**：预注册门禁就是几何口径，一格 4.9× 输出属于「真实会发生的分布」，不能用剔除它来通过门禁。

**方法层的发现（记下来给下一次预注册用）。** 本块和 `D3_MIG` 各出现一格 3–7× 的模型侧长尾，几何均值对这种重尾极敏感：n=7 时单格 7.35× 就能把 0.826× 抬到 1.128×。以后这类「按族看训练信号」的块，预注册应写成**中位 + 胜场为主、几何只作辅助**（例如「中位 ≤ 0.90 且 ≥ 5/7 更快，几何不设上限」），否则门禁测的是尾部分布而不是行为效应。这次不改本块的判定，只记录。

**工具修复（影响复算）。** 这一块最初被 `~/.local/fm/block_score.py` 判成「0 对可用」——它还在读旧字段 `exact`，而 1.5.18 起 runner 写的是 `hasSessionIds`。已把评分器收进仓库 `bench/block_score.py` 并改成 `row.get('hasSessionIds', row.get('exact'))`；`python3 bench/block_score.py D3_DBG def15 d3` 可复现上表。旧字段读取点只剩 `pool.py`/`report.py`/`d2gscore.py`/`dr8score.py` 已带 fallback，历史结果不需要转换。

## 结果：`DEF_MIG`（默认 `def15` vs 完全关闭，migrate，跑在 **1.5.19** 上）——**0.688×，5/5 更快**，默认在长迁移上仍然更快

**这一块的意义。** 前面的 0.823× 全池口径是在 1.5.15–1.5.17 的构建上测的；1.5.19 改了三处会**减少提示**的行为（检查复用证据不再透支、取消三态改写、`disciplineEarlyCalls` 冻结），理论上「提示更少 ⇒ 复用建议更少 ⇒ 可能变慢」。所以必须用**发布版**重新对一次 `off`（插件完全关闭）。臂：`def15` = 发布默认（`outputDiscipline: late`、第 4 步提醒、其余增强默认关），`off` = `enabled: false`。

**数字（6 对计划，5 对可用、丢弃 1）。** 几何 **0.688×**、中位 0.655×、**5/5 更快**、范围 0.566–0.850×；步数几何 **0.944×**、输出几何 **0.925×**。逐对：

| 对 | `off` | `def15` | 比值 | 步数 | 输出 token |
|---|---|---|---|---|---|
| p1 | 225.4 s | 147.7 s | 0.655× | 9→8 | 14 975→11 362 |
| p2 | 253.5 s | 143.6 s | **0.566×** | 9→9 | 16 517→15 351 |
| p3 | 221.4 s | 128.8 s | 0.582× | 9→11 | 12 913→16 334 |
| p5 | 399.6 s | 339.8 s | 0.850× | 17→13 | 27 262→21 519 |
| p6 | 147.9 s | 124.3 s | 0.840× | 10→9 | 13 402→12 867 |

丢弃的 p4：`off` 那一格 `leak: true`（53 步、46 988 token、1 149 s，测试没交付），`def15` 那一格 370 s / 11 步、`valid`。按预注册「`hasSessionIds` 且 `green` 且 `valid`」丢弃。注意这个丢弃方向是**对我们不利的**：`off` 那格是失败跑，如果硬算进去 `off` 会更慢、比值更小，所以 0.688× 不是靠丢掉不利格子得来的。

**判定：过预注册门槛。** `DEF_MIG` 要求几何 ≤ 0.85 且 ≥ 4/6 更快：可用 5 对（p4 丢弃，n=5 < 6 已在上面说明）、几何 0.688×、5/5，两条都过。结论：**发布版 1.5.19 的默认配置在长迁移上对比「完全关闭」依然快约 31 %**，且 5 对新会话方向全一致（符号检验 p=0.031）。

**机制读数：这一块的收益不是「少走步」，是「每步更省」。** 步数只降到 0.944×、输出只降到 0.925×，但墙钟到 0.688× ⇒ 每步耗时约 **0.73×**（`off` 24.6–28.2 s/步 vs `def15` 11.7–18.5 s/步）。这与全池分解（步数 0.827 × 每步 0.996 = 0.823×）**方向不同**：全池是「少走步」，这一块是「同样步数、每步的模型时间更短」。两种都成立，说明**提速来源随族变化**：migrate 上提醒把每次调用的思考/输出压短（`out` 几乎不变但落盘 token 少），全池里则是靠提前收束减少轮次。对外只报全池 0.823× 不会错，但不要把它当成「只有一条机制」。

## 结果：`DEF_DBG`（默认 vs 关闭，debugfix，1.5.19）——几何 **0.770×**，但只有 4/7 更快

7 对全部可用。几何 **0.770×**、中位 **0.760×**、**4/7 更快**、范围 0.407–1.323×；步数几何 0.794×、输出几何 0.880×；符号检验 p=1.000。

| 对 | `off` | `def15` | 比值 | 步数 | 输出 token |
|---|---|---|---|---|---|
| p1 | 36.5 s | 41.8 s | 1.147× | 4→4 | 4 047→4 773 |
| p2 | 60.4 s | 45.9 s | 0.760× | 10→4 | 5 049→6 369 |
| p3 | 34.3 s | 44.3 s | **1.294×** | 6→4 | 4 106→4 553 |
| p4 | 72.5 s | 34.1 s | **0.470×** | 4→4 | 7 191→3 849 |
| p5 | 66.5 s | 27.1 s | **0.407×** | 4→5 | 4 512→2 851 |
| p6 | 33.3 s | 44.0 s | **1.323×** | 7→5 | 4 046→5 582 |
| p7 | 79.3 s | 44.5 s | 0.560× | 6→5 | 8 408→4 447 |

**读法：几何与胜场分离。** 快的时候快很多（0.407、0.470、0.560），慢的时候慢得不多（1.147、1.294、1.323，单看都在或接近 ±20 % 噪声带），所以几何 0.770× 而胜场只有 4/7。p3/p6 有一个共同点：`def15` 步数更少但输出 token 更多（4 553 vs 4 106、5 582 vs 4 046）——第 4 步的「精简输出」提醒在这两格里像是让模型**先压缩一次、再补回来**。这是短任务上提醒本身有成本的又一条实例（与 1.5.1 里 `perfrefactor` 1.18×、`csvpipe` 1.12× 同向），不是判 FAIL 的依据：`DEF_DBG` 不承担改默认的职责，它只回答「发布版默认还快不快」。

**合池（`DEF_MIG` + `DEF_DBG`，发布版 1.5.19，12 对）。** 几何 **0.735×**（省约 26.5 %）、逐格中位 **0.708×**、9/12 更快、范围 0.407–1.323×；两族步数几何 0.886×、输出几何 0.907×。这一组是「发布版对新会话」的最干净对照（`off` = `enabled: false`，同一台机、ABBA 交错），结论：**1.5.19 减少提示之后，默认配置的提速还在，方向和量级与全池 0.823× 一致**；单块 n 小（5 和 7），对外仍只用 138 对全池口径，这组数字作为发布版复核记录。

## 提醒实际投递分布：复用建议在 951 份套件转录里 0 次

**问题。** 文档把「同一条检查原样重复时建议复用」列为提速机制之一，但这个机制只有在模型真的重复同一条检查时才出现。先量它是否出现，再决定要不要为它投入。`python3 bench/reminders.py`（默认扫 `~/.dsh/sessions/--*dsh-runs-suite-*`，共 951 份转录、含全部已完成的臂与块）按注入文案分类统计：

| 提醒种类 | 投递次数 |
| --- | --- |
| `output-discipline`（精简输出） | 754 |
| `write-and-verify`（改了 2 个文件后跑一次检查） | 84 |
| `batch-now`（一次把独立调用发齐） | 60 |
| `reuse-check`（检查原样重复，建议复用） | **0** |

951 份转录里 742 份（78 %）至少收到过一条提醒，说明投递链路本身是通的；**`reuse-check` 一条都没有**。套件任务的形状是「改文件 → 跑一次隐藏测试 → 结束」，检查不会被逐字重跑，所以复用建议没有触发条件。结论有两条：

1. 已测出的 0.823×（12 块 / 138 对）**不是**复用建议带来的，它来自精简输出、批量化与步数混合。任何把 0.823× 归因到「复用检查」的说法都不成立。
2. 复用建议的实现（按产物指纹失效、写未知即停）仍然正确，但它属于**防御性正确**而不是**已证实的加速项**：本地语料里它的收益是 0。要证明它有效，必须先构造一个会逐字重跑检查的任务形状，而不是在现有套件上再跑一个块。

下一步因此改成量**投递最多的那一条**：`outputDiscipline: late` 与 `off` 的差，就是「精简输出」这一条提醒单独的贡献（块 `LEN_MIG` / `LEN_DBG`，臂 `def15` vs `nodisc`，后者与前者只差 `outputDiscipline: false`）。

## 一步值多少钱：llm 秒数 ≈ 14.5 s × 步数 + 24 s × 千 token（R² = 0.42，n = 568 条合格行）

**为什么量这个。** 前面所有块的结论都能归结成一句「快在步数，不在每步」。这句话需要一个换算比，否则「再省一步」值多少秒说不清。对全部已完成的合格行（`valid`，568 条，跨所有块与臂）做最小二乘，被解释量是该行记录的模型耗时 `llm`（秒），解释量是 `steps` 与 `total`（千 token）：

```
llm = 14.5 × steps + 24.2 × tokens − 286.8      R² = 0.416
均值：steps 9.34，tokens 14.26，llm 193.8 s
```

（`llm` 是模型耗时秒数；`tokens` 单位是千 token；n=568 是所有已完成块的合格行。这个式子是描述性的，不是外推模型。）

读法：**在均值附近，每一步对应约 14.5 s**，每千 token 约 24 s。两点必须同时说清：①截距是 −286.8 s，且 `steps` 与 `tokens` 高度共线（长任务两样都多），所以这个线性式只在均值附近可读，不能外推成「第 20 步值 290 s」；②它给出的是**换算比**而不是因果——真正的因果证据是下一节的配对拆分（步数 0.778×、每步 1.02×）。

**换算成目标。** 在 token 不变的前提下，把总时长压到 0.90× 需要步数从 9.34 降到 8.00（−14 %）；0.85× 要到 7.33（−21 %）；0.80× 要到 6.67（−29 %）。已测的池化口径 0.823×（步数几何 0.806）正好落在这条线的位置：它**已经**吃到了约 19 % 的步数下降，而每步内部几乎没动（0.996）。

**因此剩下的空间在哪。** ①继续找「可以合并/跳过的一步」；②降低每步的固定成本本身（工具往返、等待落地），但后者不是提示词能碰的；③模型侧尾巴（单格 3–7× 的长尾）无法靠机制消除，几何均值在 n≈7 时对一格极值极度敏感——这也是前面反复出现「同形状两块给出相反结论」的原因。任何声称 0.6× 的说法都要用「步数降到 6.7 以下且质量不掉」来证明，而不是拿一次长尾反面样本充数。

## 到底省在哪：步数 0.78×、可见输出 0.53×、token 1.00×、每步时间 1.02×（15 对同题同配）

把池化口径拆成可解释的四个量，只在 **`off` 与 `def15` 各自都合格**的那 15 对上取中位数（同题、同一 pair 号，避免跨题混池）：

| 量 | `off` | `def15` | 比值 |
| --- | --- | --- | --- |
| 步数 | 9.0 | 7.0 | **0.778** |
| 可见输出字符 | 12 913 | 6 887 | **0.533** |
| 总 token（千） | 14.0 | 14.0 | 1.000 |
| 每步 llm 秒 | — | — | 1.02 |
| 总时长（秒） | 148.5 | 117.9 | **0.794** |

读法：插件把**可见输出砍掉一半**、把**步数砍掉约 22 %**，但**总 token 没动**（省下来的可见文字被推理/工具结果补上了），**每一步并没变快**。所以 0.79× 的时长收益几乎全部来自「少走几步」，与上面 14.5 s/步的固定成本换算一致：少 2 步 ≈ 省 29 s ≈ 20 %。

这一条同时排除了两种常见误判：①「插件让模型每步跑得更快」——不成立，每步时间一致；②「插件靠少写文字省 token」——可见文字确实减半，但总 token 不变，省的是**步数**，不是账单。要再往下压时间，只能继续减步数，或者在固定成本（工具往返、等待落地）上做文章。

## 逐步骤指标里的交换比：每个输出 token 约 10 ms（R² = 0.82，n = 8 917 步，本机今日单会话）

上面那块是套件里的行级换算，样本被任务长度主导。另一条独立证据来自插件自己记的逐步骤指标。**口径先说清**：`~/.dsh/fast-mode-metrics.jsonl` 目前只有本机今天的记录（同一个长会话），所以下面是「这台机器、这个网关、这个模型」的交换比，不是用户群体的统计。取 `mode: on` 的 8 917 个正常步，被解释量是该步模型耗时 `llmMs`，解释量是 `output`（输出 token）、`ttftMs`（首字延迟）：

```
llmMs ≈ 10.05 × output + 1.09 × ttftMs − 159      R² = 0.817
```

- **每个输出 token 约 10 ms**：真机中位输出 264 token ⇒ 约 2.6 s/步，占该步中位 3.69 s 的七成。这是「精简输出」这条提醒唯一能直接触碰的项，也是它为什么值钱。
- **首字延迟整体平移**：`ttftMs` 系数 1.09（不是叠加而是同步放大），中位 1.16 s。
- 只放 `output` 时 R² = 0.649，加入 `ttftMs` 跳到 0.817；再加 `cacheRead` 不再提升（0.817）——上下文的**缓存命中**不直接影响单步耗时，影响的是首字里已经算进去的那部分。

与 `src/guidance.js` 里记的旧模型（`720 ms + 6.6 ms/输出 token`，R² 0.93）相比，斜率从 6.6 涨到 10.0 ms，说明这台机器当前的解码更慢、每 token 更贵：**同样的「少写一半」在这里更值钱**。两个模型的共同结论是同一个：输出长度是插件能动的最大单项。

由此得到本轮的口径：池化 0.823× 里，步数贡献约 0.78×，输出减半贡献的主要是每步的 token 项；两者相乘才对得上实测。下一步要再压时间，只有两条路——更少步数、更少输出 token——而它们都已经在默认开关里打开，剩下是**措辞与触发**能不能让模型真的照做。这个会话本身就是反例：8 888 步里 `calls ≥ 2` 的只占 5.4 %（`1` 调用 8 128 步、`2` 调用 421 步），说明「一次把独立调用发齐」的提示在实际行为里基本没被执行——**遵从率才是下一个可量的点**，而不是再加一条提醒。

## 机制拆解：不是靠并行，是「每样都少一点」（168 对同题同配，直接数转录）

前面的换算都是行级数字。这一节换了证据来源：打开 168 对**同 tag、同任务、同 pair、只差 arm** 的会话转录（`~/.dsh/sessions/--*suite-<tag>-<task>-p<pair>-<arm>--`），直接数里面的东西。命令：

```
python3 bench/mechanism.py --arm off --arm def15          # 步数 / 调用数 / 并行率 / 只叙述步
python3 bench/textshare.py --arm off --limit 0 --json     # 全文的口径与 token 分类
```

| 每会话中位数 | off | def15 | 几何比 | 中位比 |
|---|---|---|---|---|
| assistant 消息（= 步数） | 10 | 7 | 0.806 | 0.828 |
| 工具调用数 | 11 | 9 | 0.786 | 0.815 |
| 每消息调用数 | 1.00 | 1.00 | 0.994 | — |
| 多调用消息占比 | 5.2 % | 6.3 % | — | 1.000 |
| 只叙述不带调用的消息占比 | 10.0 % | 12.5 % | — | 1.194 |
| 可见 prose 字符 | 92 | 93 | 0.804 | 1.000 |
| 工具参数字符 | 11 812 | 10 120 | 0.839 | 0.911 |
| 推理（reasoning）字符 | 47 510 | 34 778 | **0.753** | 0.815 |

三个结论，都能被上面那张表直接读出：

1. **并行不是杠杆。** 每消息调用数 1.00 vs 1.00（比 0.994），多调用消息占比 5.2 % → 6.3 %（中位比 1.000）。加不加插件，模型都是一条消息一个调用；池化 0.823× 里没有任何一份来自「一次发多个调用」。这也解释了为什么反复打磨并行提示词没有效果（见 B1 段落）。
2. **省的是「每样都少一点」，不是某一种输出。** 步数 0.83、调用 0.79、工具参数 0.84、可见 prose 0.80、推理 0.75——全都落在 0.75–0.84。没有任何一项是「砍半」，也没有任何一项反向变大。乘起来正好是实测的 0.82× 墙钟。
3. **真正的大头是推理，不是可见文字。** 每个会话中位 47.5 k 推理字符 vs 92 个可见 prose 字符——**可见文字只占总输出字符的 0.2 %**。所以「精简输出」这条提醒值钱的地方不是它写的那些字，而是它连带把模型每步的**思考**压短了 25 %（0.753）；只盯着可见文字去优化，天花板就是 0.2 %。

读法上的边界：这是本机 168 对套件转录，`off` 与 `def15` 各自只有一遍，落在 ±20 % 的诚实带内（见「诚实带」一节），所以「0.75–0.84」应读作方向与量级，不是精确系数；但「并行率不变」在这种样本量下是稳的（两条独立口径都给 1.00）。

## 纪律提醒到底占多少：同一块复测一次，方向就翻了（LEN3 vs LEN_DBG）

`def15` 与 `nodisc` 解析后的配置**只差一项**：`discipline: 'full' → 'off'`（`bench/arms/nodisc.yml`，用 `bench/arm-config.mjs --json` 对过）。这一块量的是「输出纪律」这条提醒的独立份额：

```
python3 bench/block_score.py LEN_MIG def15 nodisc
python3 bench/block_score.py LEN_DBG def15 nodisc
python3 bench/block_score.py LEN3 def15 nodisc
```

| 块 | 可用对 | wall 几何比（nodisc/def15） | 中位比 | nodisc 更快 | 符号检验 |
|---|---|---|---|---|---|
| LEN_MIG（migrate，5 对） | 4 | 1.189 | 1.275 | 2/4 | p = 1.000 |
| LEN_DBG（debugfix，7 对） | 7 | 1.790 | 1.978 | 1/7 | p = 0.125 |
| **LEN3（debugfix，7 对，复测同一块）** | 7 | **0.648** | 0.717 | **5/7** | p = 0.453 |
| 两遍合并（14 对） | 14 | 1.077 | — | 6/14 | — |

- 第一遍（`LEN_DBG`）看出「关掉纪律几乎翻倍」，第二遍（`LEN3`）同题同配、只换了一遍采样，几何比变成 **0.648**——方向相反，量级相近（1.79 与 1.54），**这不是「提醒有效」的证据，是单块采样的噪声**。
- 两遍的离群值分别落在不同手臂上：`LEN_DBG` 的 `nodisc p6` 196 s、`p4` 85 s；`LEN3` 的 `def15 p4` 165 s、`p7` 125 s。块级几何比被这一两个单元格决定，7 对远不够定方向。
- 所以**纪律提醒的独立份额目前不可判定**，不能对外报 1.54× / 1.79× 这类数字。能报的只有池化主效应（138 对 0.823×），其中「明细律」与其它提醒是同一个机制包里一起生效的，无法在单块里分离。
- 这条给「不要再加提醒时机、不要再加重复提醒」提供了同样甚至更强的支持：连唯一那条提醒的份额都测不出来，继续加同类触发点没有可验证的收益。

## 多个开关一起开：默认之上再叠任何一个都是 0（59 个块、183 对池化的消融表）

单块的 ±20 % 噪声（见上一节 LEN3）意味着「一次只测一个开关、7 对下结论」必然出假信号。已经跑过的所有块加起来其实足够回答另一个问题：**在已发布的默认配置之上，再打开某个开关，还能不能更快？** 新增 `bench/ablate.py` 把所有块的全部臂按「同块、同任务、同 pair」对上锚点臂池化，锚点优先 `off`、缺了回落到 `def15`：

```
python3 bench/ablate.py --ci 2000                      # 相对 off
python3 bench/ablate.py --ci 2000 --anchor def15       # 相对默认配置（叠开关的边际值）
```

以 `def15` 为锚（59 个块；几何比 / 95 % bootstrap CI / 快过锚点的对数 / 符号检验 p）：

| 臂 = 默认 + 打开 | n | wall 几何比 | CI95 | 中位 | 步数 | 输出 | 更快 | p |
|---|---|---|---|---|---|---|---|---|
| `d2`：明细律提前到第 2 步 | 41 | **0.695** | [0.565, 0.863] | 0.700 | 0.960 | 0.723 | 27/41 | 0.060 |
| `d2g`：同上 + 第 1 步 ≥2 次调用才提前 | 27 | **0.816** | [0.650, 1.023] | 0.719 | 0.892 | 0.777 | 18/27 | 0.122 |
| `map15` +`workspaceMap` | 23 | 0.802 | [0.648, 0.999] | 0.790 | 1.048 | 0.824 | 13/23 | 0.678 |
| `open15` +`openBatch` | 53 | 0.882 | [0.740, 1.049] | 0.911 | 0.831 | 0.915 | 30/53 | 0.410 |
| `bv15` +`batchVerify` | 39 | 0.963 | [0.813, 1.144] | 0.968 | 0.935 | 0.961 | 20/39 | 1.000 |
| `bud8` +`stepBudget:8` | 26 | 0.971 | [0.783, 1.200] | 1.110 | 0.990 | 0.973 | 11/26 | 0.557 |
| `pp16` +`preferPatch` | 34 | 0.988 | [0.803, 1.216] | 1.012 | 0.911 | 0.995 | 17/34 | 1.000 |
| `wide15` 并行宽度拉大 | 20 | 1.075 | [0.796, 1.491] | 0.991 | 1.262 | 1.076 | 10/20 | 1.000 |
| `nodisc` 关掉明细律 | 19 | 1.101 | [0.808, 1.492] | 1.110 | 0.998 | 1.049 | 8/19 | 0.648 |

- **结论 1：默认配置已经在平台上。** 除时点类两臂外，每个可选开关的 CI 都跨 1，几何比落在 0.80–1.08；把 `openBatch`、`workspaceMap`、`batchVerify`、`preferPatch`、更宽并行、步数预算一起打开，一个百分点都拿不到。所以「多开几个开关会更快」是错的——它们的收益在默认里已经兑现，或者从来就不存在。
- **结论 2：唯一在池化下站得住的杠杆是「同一句提醒的时点」。** `d2` 41 对 0.695×（CI 上限 0.863 仍 < 1），但它是任务相关的：分族 `debugfix` 0.619、`ledger` 0.506、`migrate` 0.569，`codeimpl` **1.073**、`shellpipeline` **1.111**。加条件闸门后 `d2g` 把 `codeimpl` 收到 0.889，同时把 `debugfix` 的 0.619 抬到 0.903 ⇒ 池化一度是 0.756×（21 对）。把闸门从没测过的 `ledger`/`shellpipeline` 补上后（`D2G2`，6 对）这条也塌了：27 对 **0.816×**、CI95 **[0.650, 1.023]**、18/27 更快。
- **结论 3：这两臂仍是 HOLD，不改默认。** `d2` 在 `codeimpl` 上是负收益，`d2g` 的闸门常量为 2、只在 3 个家族验过；按预注册判据（`D2_IMPL` 1.073×、3/8 更快）默认保持 `disciplineAfterSteps: 4`。这张表的作用是说明**没有别的开关可以在默认之上继续叠加**；`ledger`/`shellpipeline` 的补测（`D2G2`）已经做完，见下一节，闸门在这两族上方向相反，时点类两臂到此可以收档。

**顺带把「只挑了 12 个干净块」这个质疑也答了。** `ablate.py` 不挑块（59 个全进），默认臂对 `off` 的池化读数是 **183 对、几何 0.816×、CI95 [0.755, 0.881]、122/183 更快、符号 p < 0.0001**——与 `pool.py` 那 12 个干净块的 138 对 0.8234×（CI [0.744, 0.918]）落在同一点上，置信区间还更窄。所以主结论不是选块选出来的。

**这张表的两个诚实边界**：不同臂覆盖的任务组合不同（跑一遍命令就能看到 `tasks` 列，`d2` 含 `codeimpl:8`、`map15` 只有 `codebase/debugfix/perfrefactor`），所以行与行之间不能直接排大小；以及每个块只采样一次，`n < 20` 的行只算方向提示。

## 直接跑一次「多个开关一起开」：三开 **0.669×**（22 对，18 对更快），两开 0.742×

上一节那张消融表是**逐个**开关单独叠在默认上测的，从来没有真的跑过「一起开」的臂，所以「多开几个开关不会更快」只是推理。这一轮补上：把默认之上**唯一三个点估计小于 1** 的可选开关同时打开（`d2g` = 明细律第 2 步 + 第 1 步 ≥2 次调用才提前、`workspaceMap`、`openBatch`），和同块的默认臂按 `(任务, pair)` 配对。

```
python3 bench/suite-run.py --tasks debugfix,migrate --arms def15,combo2 --pairs 12 --tag COMBO2
python3 bench/suite-run.py --tasks debugfix,migrate --arms def15,combo3 --pairs 6  --tag COMBO3
```

`combo2` = 默认 + `d2g` + `workspaceMap` + `openBatch`；`combo3` = 默认 + `workspaceMap` + `openBatch`（不含时点类臂，用来分清增益是不是全来自那条提醒时点）。

| 臂 | n | wall 几何比 | CI95（bootstrap 5 000） | 中位 | 更快 | 符号 p | 步数中位 | 输出中位 | 隐藏测试 |
|---|---|---|---|---|---|---|---|---|---|
| `combo2`（三开） | 22 | **0.669** | **[0.554, 0.799]** | 0.687 | **18/22** | **0.0022** | 7 → 6.5 | 6 770 → 4 957（−27 %） | 22/22 绿 |
| `combo3`（两开，无时点臂） | 11 | 0.742 | [0.594, 0.909] | 0.884 | 8/11 | 0.113 | 8 → 7 | 6 223 → 6 497（+4 %） | 11/11 绿 |

分族（`combo2`）：`debugfix` 12 对 **0.766×**（10/12 更快）、`migrate` 10 对 **0.567×**（8/10 更快）。两族方向一致，没有 `d2` 那种「一开就反」的家族。

读法，以及它和上一节的矛盾怎么算：

- **叠加是真的有效，不是推理出来的。** 单个开关各自只有 0.80–0.88 且 CI 跨 1（`map15` 23 对 0.802、`open15` 53 对 0.882、`d2g` 27 对 0.816），三个一起开 22 对 CI 上限 **0.799 < 1**、符号检验 p = 0.0022。单个都测不出显著，合起来能测出，说明增益不在任何一个开关本身，而在它们**减少的是同一类东西的不同份**：少开的文件、少来回的读、少写的散文——每一份单独都不足以顶穿 ±20 % 的单轮噪声。
- **但别把它读成「把开关都打开就好」。** 表里 `bv15`、`pp16`、`bud8`、`wide15`、`nodisc` 单独叠都是 0.96–1.10，没有理由相信它们进组合会变好；这一轮只验了上面三个。
- **时点臂仍在 HOLD，不进默认。** `combo2` 比 `combo3` 多出来的那一截（0.669 vs 0.742）指向 `d2g`，但两个臂的样本不是同一批 pair，n = 22 vs 11 也不足以给「多 7 个百分点」定论；`d2g` 在 `codeimpl` 上的负收益（见上一节结论 2）没有被这一轮推翻，默认仍是 `disciplineAfterSteps: 4`。
- **诚实边界**：各一个块、两个任务族（`debugfix` 短、`migrate` 长），`combo3` 的符号检验 p = 0.113 只算方向提示；锚点臂有 2 格（`migrate` pair 2、11）判 `leak` 被剔除，剔除后仍配对 22 对，`combo2` 24 格全绿、无一格 `leak`。想复现就重跑上面两条命令，`bench/arms/combo2.yml`、`combo3.yml` 在仓库里。

## 提速有多少是从「非模型时间」里挤出来的：没有，只占墙钟 5 % 且组合臂更轻

外部评审担心的是「你把成本挪到插件自己身上，墙钟看着快了其实更重」。`wall`（任务下达到进程退出）与 `llm`（花在模型调用里的时间）两列本来就在每一行里，差值就是一切非模型开销：工具执行、宿主调度、插件每步的活。新脚本把这块单拉出来，成对看：

```
python3 bench/overhead.py --blocks COMBO2 --base def15 --arm combo2
python3 bench/overhead.py --blocks COMBO3 --base def15 --arm combo3
```

| 口径 | 默认臂 | 组合臂 |
|---|---|---|
| `combo2`（22 对）每步非模型开销 | 0.64 s | **0.38 s** |
| `combo2` 非模型开销占墙钟 | 5.9 % | 6.6 % |
| `combo2` 成对开销比 | — | 中位 **0.953×**，16/22 对更轻 |
| `combo3`（11 对）每步非模型开销 | 0.30 s | 0.39 s |
| `combo3` 成对开销比 | — | 中位 **0.940×**，8/11 对更轻 |

- **提速不来自压榨工具时间。** 非模型开销本来只占墙钟 3–8 %，就算压到 0 也只有几个百分点；实测组合臂的这块绝对值反而略轻（成对中位 0.94–0.95×，各 16/22 与 8/11 对更轻），因为总步数少了、固定开销也少收几遍。占比从 5.9 % 涨到 6.6 % 只是分母（墙钟）变小，不是开销变重。
- **每步开销没有系统性变大。** `combo2` 每步 0.38 s vs 默认（同块）0.64 s；`combo3` 是 0.39 s vs 0.30 s，方向相反但绝对值都不到 0.1 s/步，且成对中位仍是 0.94×。插件每步的活是几十毫秒级的字符串拼接与一次 `inject`，落在这两列的噪声里，解释不了 30 % 的墙钟差。
- **读法**：这一块不能用来证明快，只能用来排除「快的代价藏在别处」。墙钟比（0.687 中位 / 几何 0.669）与 `llm` 比的落点一致，说明省下来的确实是模型回合。

## 同一配置再跑一遍会差多少：块级差 4 %，任务级差 15–31 %（REP2，10 对）

外部评审提的第一条是稳定性。这一块不新增任何开关，只把 `migrate`+`debugfix` 的同一批格子**用同一套配置再跑一遍**（`REP2`，20 次运行，顺序 ABBA 与之前的块一致），看同配置的两次独立测量互相差多少：

```
python3 bench/block_score.py REP2 off def15
```

| 口径 | DEF（第一遍） | REP2（第二遍） | 两次相差 |
|---|---|---|---|
| 全部可用对（DEF 12 / REP2 10） | 0.823 几何 / 0.799 中位 | **0.791 几何 / 0.746 中位** | **4 %** |
| `debugfix` 5–7 对 | 0.770 | 0.628（5 对） | 15 % |
| `migrate` 5 对 | 0.688 | 0.997（5 对） | 31 % |
| 同一批 cell（migrate p1–p4，逐格比） | 0.655 | 0.861 | 31 % |
| 同一批 cell（debugfix p1–p5，逐格比） | 0.736 | 0.628 | 15 % |

- **块级结论可复现**：两次独立测量给 0.79 / 0.82，差 4 %；两次都是「插件更快」。
- **任务级不可复现**：`migrate` 一遍 0.688（5/5 更快）、另一遍 0.997（3/5 更快），同一批 4 个格子的逐格几何比也从 0.655 漂到 0.861。原因在单格：REP2 的 10 个格子跨度是 **0.28×–1.79×**，其中 `migrate p4` 一次 1.79×、`debugfix p1` 一次 0.28×——单格结果由那一次模型走多少步决定，不由配置决定。
- 因此文档里只对外报**池化**口径（138 对 0.823×、中位 0.799×、93/138 更快），并明确标注「单格/单块差异落在 ±20 % 内是噪声」。**任何以单块（含 n≈5–12 的块）为依据的结论都不作为决策依据**——这一条现在有了同配置重复的直接证据，不再是凭经验给的宽限。

`REP2` 的默认手臂同时复核了一件事：`def15` 在这里是**发布版源码**（`node_modules/dsh-fast-mode` 指回仓库），所以 0.791 也是「当前代码在跑」的又一次独立确认，不是旧数据的复用。

## 被截断的调用身份：146 506 次调用里 782 组会同键（1.5.24 修的就是这个）

调用身份由 `stableKey(参数)` 生成，上限 400 字符，超出即截断。身份用于两件事：判断「这一步有没有产生新观察」（重复调用、没进展的判据），以及给通过过的检查记录一个键。旧实现在截断时只留前 400 字符，于是**只在尾部不同的两条长调用会得到同一个身份**——最常见的就是同一个 heredoc 脚本改了后半段、同一个文件改了后面的内容。

本机全量普查（3 168 个会话、146 506 次工具调用，`~/.dsh/sessions` 下全部转录）：

| 量 | 值 |
| --- | --- |
| 工具调用总数 | 146 506 |
| 参数序列化 > 400 字符（被截断） | 48 293（33 %） |
| 不同 390 字符前缀组 | 47 031 |
| **同前缀对应 ≥2 种不同完整参数（旧身份下同键）** | **782 组** |

1.5.24 的修法：截断时附上整段序列化文本的 32 位 FNV-1a（8 位十六进制），长度 399 字符，上限 401 不变；不截断的短调用字节级不变。782 组里触发错误催办还需要「两条调用的结果大小也相同」（网关不给内容哈希时摘要就是字符数），所以线上未必发生过，但这条路径现在不再可能。回归测试在 `test/effort.test.js`。

## 输出里到底有多少是「说给人看的」：可见文字 1.3 %，推理字符 99.6 %

`bench/textshare.py` 现在把 `reasoning` 部分单独计数（原来只数可见文字与工具参数）：

```
python3 bench/textshare.py --limit 5
  prose 751 chars  tool args 58,084 chars  total 58,835 chars
  reasoning 218,718 chars
```

可见文字只占（可见文字 + 工具参数）的 1.28 %；把推理也算进来，**推理占模型全部发出字符的 99.6 %**。所以提速不来自「少说话」：省下来的是**步数**与**每步的推理量**，而提醒文案只是同时压这两者的手段。`bench/mechanism.py` 的报告里也多了 `prose / args / reasoning / emitted` 四行（168 对同题同配的结论：`reasoning` 几何比 0.75，`emitted` 0.82）。

## 常驻宿主的堆：把最后 6 个无界缓存封顶，并用一个「不重启的宿主」证明它是平的（1.5.34）

外部评审这一轮的建议是「先做稳定性修复，暂时不要加旋钮」。插件里能威胁稳定性的东西不多，但有一类是真的：**所有缓存都以永不重复的 id 为键**（会话 id、`callId`、`${sid}${cwd}`），而宿主的进程是活几周不重启的。1.5.32 封了 3 个，这一轮把剩下的全封了，并且补了一个黑盒浸泡脚本，让「它是平的」变成每次都能重跑的数字，而不是一句断言。

### A. 审计：插件自己持有的 8 个缓存

| 缓存 | 键 | 之前 | 现在 |
| --- | --- | --- | --- |
| `compacted`（`index.js`） | 会话 id | 200 条 FIFO | 200（改用 `trim`） |
| `mapSent`（`index.js`） | `${sid}${cwd}` | **无界** | 500 |
| `autoBudgets`（`index.js`） | 会话 id | 200 条 FIFO | 200（改用 `trim`） |
| `startedAt`（`index.js`） | `callId` | **无界**（结果没回来的调用不会被 `delete`） | 2000 |
| `warnedCaps` | `callId` | 有界 | 不变 |
| `labels`（`src/metrics.js`） | 会话 id | **无界**，且 sink 关闭时也写 | 2000，且 sink 关闭时直接不写 |
| `steps` / `open` / `pendingChunk`（`src/metrics.js`） | 步 / 会话 | **无界** | 2000 / 2000 / 1000 |
| `sessions`（`src/auto.js`） | 会话 id | 200（LRU） | 200（改用 `trim`） |

封顶全部走同一个新工具 `src/bounded.js` 的 `trim(cache, cap)`：插入顺序即淘汰顺序，返回同一个缓存以便链式使用，`cap <= 0` 时不动。之前每个缓存各写一遍淘汰循环，写法不一致是漏掉下一个缓存的原因。被淘汰不等于写错行：`labels` 里没了的会话，它的测量行记成 `mode: 'unknown'`，而不是借用别人的档位——这一条直接写成了测试（`test/bounded.test.js`）。

### B. 先修的是测量工具本身：它最初报了个假泄漏

新写的 `bench/soak.mjs` 用与 `test/plugin.test.js` 相同的假 Cordis 挂载真插件，然后扮演一个从不重启的宿主：N 个会话 × 4 轮，每轮走 `step/start` → `agent/request` → 工具调用 → `step/end`。第一次跑 4000 会话报 **+63.67 MB / 16.3 KB per session / FAIL**，而且 `--off` 一模一样——说明增长不在插件逻辑里。二分两下就定位了：

| 控制 | 4000 会话的增长 |
| --- | --- |
| `--off`（插件禁用） | +63.67 MB（同样） |
| `--no-event`（不发事件流） | +49.29 MB |
| `--no-request`（不发 `agent/request`） | **−0.55 MB** |
| `--one-id`（4000 个会话共用一个 id） | +49.30 MB（同样） |

问题在**脚本**：`ctx.fire('agent/request', …)` 没有 `await`，16000 次调用留下 16000 个 pending 的 Promise 链，堆自然涨，而且与「会话数」无关而与「调用数」有关（`--one-id` 一样涨就是这个原因）。改成 `await` 后同规模是 **−0.61 MB**。这条写进脚本头注释里：浸泡测试自己的悬挂 Promise 会伪装成插件的泄漏，先怀疑它。

（`/tmp/reqctl.mjs` 的独立控制给出同一结论：16000 次 await 过的请求 ≈ 2.43 MB，即 152 B/次，正是 `labels`；不 await 是 3 KB/次。）

### C. 封顶之后：20000 个会话、242 400 步，堆是平的

```
node --expose-gc bench/soak.mjs --sessions 20000 --metrics
sessions        20000 × 4 turns, 242400 steps
heap warm       7.26 MB
heap end        5.62 MB
growth          -1.64 MB over 20000 sessions
per session     -0.1 KB
metrics         242400 steps written, caches trimmed
```

`--metrics` 是这一轮加的口子：让测量 sink 真的写盘（写进临时文件，跑完删掉），这样 `labels`/`steps`/`open`/`pendingChunk` 四个缓存也被搅动。带它跑 4000 会话是 **−0.58 MB**，不带它跑 20000 会话是 **−1.64 MB**；阈值是 0.004 KB/会话，失败时退出码非 0，可以直接进 CI。

单点对照（`/tmp/labelctl.mjs`，20000 次 `label()`）：

| 版本 | 堆增长 |
| --- | --- |
| 1.5.33（无界，sink 关闭也写） | **2.48 MB**（124 B/会话） |
| 1.5.34（sink 关闭直接不写） | **0.01 MB** |
| 1.5.34（sink 打开，封顶 2000） | **0.01 MB**（20000 次写入后仍只留 2000 条） |

2.5 MB 不是能把宿主撑爆的量级——**这一条要诚实说清：真正的收益不是省了 2.5 MB，而是「插件里不再有随运行时长单调增长的缓存」**，而这个约束来自本机真实崩溃日志里的一条硬规则（堆内存要有上限）。上限也确实换了东西：`sink 关闭时不再记 label` 是一次纯粹的白拿。

### D. 同一轮里对已发布版本的回归复核（`V1533`，12 次运行）

改稳定性不能只看测试，还要确认「已发布的 1.5.33 源码在跑起来时没退化」。所以先跑了一班 `debugfix`+`migrate` 的 3 对 ABBA 块（`off` 与 `def15` 同块交替），用的是仓库源码（`node_modules/dsh-fast-mode` 指回仓库）：

| 口径 | `def15` | `off` |
| --- | --- | --- |
| 6 次运行合计墙钟 | 654.0 s（**0.50×**） | 1304.5 s |
| 输出 token | 60 135（0.57×） | 104 492 |
| 步数 | 45（0.46×） | 99 |
| 绿/运行 | **6/6** | 6/6 |
| 逐对更快 | 5/6 | — |

两族的逐对几何分别是 `debugfix` 0.50×、`migrate` 0.50×，与历史块同量级（这两个族本来就是插件收益最大的地方），说明封顶没有动到任何行为路径。

## 结果：`LZ1`（`lazy` 想太久就少想一点，1.6.0 新开关，三臂 36 次运行）——墙钟 **0.39× / 0.43×**，12 对里 11 对更快，判分 **216/216 全绿**

`lazy` 是 1.6.0 唯一的默认关闭开关：它不改插件其余部分，只在「这一步已经想太久」时做两件事——给对话里放一段「先按已有信息定下去」的提示，以及（`lazyEffort: false` 可关）把下一次请求的 `reasoningEffort` 降一档。三臂设计就是为了把这两半分开：

- `def15`：完全关闭（历史基准臂）；
- `lazt16`：`lazy: true, lazyEffort: false`，**只发提示文字**，一个请求字段都不写；
- `laz16`：`lazy: true`，提示文字 + 请求字段降档。

`codeimpl`（14 项判分）与 `ledger`（22 项判分）两族各 6 对，ABBA 交替，共 **36 次运行、12 对**，同一台机器连续跑完（`bench/results/suite-LZ1.jsonl`）。

| 口径 | `def15` | `lazt16`（只发文字） | `laz16`（文字 + 降档） |
| --- | --- | --- | --- |
| 12 次运行合计墙钟 | **2 795.9 s** | 1 040.9 s（**0.372×**） | 1 010.9 s（**0.362×**） |
| 逐对几何墙钟比（对 `def15`） | — | **0.389×**，95 % CI [0.252, 0.600] | **0.430×**，95 % CI [0.273, 0.677] |
| 逐对更快 | — | **11/12**，符号检验 p = 0.0032 | **11/12**，p = 0.0032 |
| 单次墙钟中位 | 174.9 s | 57.4 s | 80.0 s |
| 记录到的推理字符中位（独立于 usage 的第二通道） | 65 342 | 19 156（0.372× 几何） | 24 404（0.418× 几何） |
| 可见输出字符中位 | 21 594 | 8 310 | 9 468 |
| 步数合计 / 中位 | 116 / 8.0 | 70 / 5.5 | 58 / 4.5 |
| 判分（隐藏测试） | **216/216**，12/12 绿 | **216/216**，12/12 绿 | **216/216**，12/12 绿 |

分族看，`codeimpl` 上 `lazt16` 中位 51.7 s 对 `def15` 106.1 s、`laz16` 78.1 s；`ledger` 上 125.1 s / 239.3 s，`laz16` 85.0 s。两族方向一致，不是单族异常值撑起来的。

**这一块能说什么、不能说什么：**

- 能说：在 12 对同题同配的对照里，打开 `lazy` 后 11 对更快，几何 0.39–0.43×，判分与输出正确性没有任何一格变红；机制是「步数变少 + 每步推理字符变少」，两侧同时发生，不是纯粹少说话。
- 能说：**收益主要来自提示文字那一半**。把两臂直接对打：`laz16 / lazt16` 的逐对几何是 **1.106×**（95 % CI [0.682, 1.792]，12 对里 5 对更快）——也就是说「降 `reasoningEffort`」这一半在这条路由上**没有测出额外收益，也没测出明显代价**。这与本文件早先的记录一致：这条路由的 usage 里 `reasoning` token 一直是 0，`reasoning_effort` 是否被上游当真从未被证明；`lazy` 的第二通道（流式推理字符）就是为这件事加的，读数显示两臂推理字符同量级（0.372× 对 0.418×）。
- 不能说：这不是「所有题都变快 2.6 倍」。样本是这两族短任务、单机、单路由，`def15` 本块的中位偏慢侧（历史上 `codeimpl` 中位 161 s、`ledger` 649 s，本块 174.9 s 跨两族），逐对设计吸收了基线漂移，但换模型/换路由后倍数会变。也不要说 `lazy` 提高质量：它只做到「没把质量弄坏」——216/216 与 `def15` 持平，不是更好。
- **这一块的臂已经不存在了，读数也只复现了一部分。** `lazy` 在 1.7.0 被整个删掉（连同改 `reasoningEffort` 的那一半），所以 `def15`/`laz16`/`lazt16` 三个 arm 现在只能从 git 历史里取回。后续用同一批家族做纯文本变体对照时（`LZ2` 的 `v2 / v1` 几何 **0.699×**、`LZ3` 几何 **0.822×** 但中位 1.014×），都没有复现出这里的 0.39–0.43×，只落在 0.70–0.82× 一带；所以本块的 0.39× 只能当**一次性读数**引用，不能当「这个提示的稳定收益」。引用它时必须同时说这两件事。
- 一块**退役 0.9.x 教训**的验证：降档不跨轮、不跨会话、每步最多一次、每轮最多两次、工具报错后两步内不降、地板 `low`、从不升档，且每个请求都以「用户此刻选的档位」为基准重新计算（`agent/request` 读的是刚解好的 `reasoningEffort`）。所以用户中途换档，下一次请求就是用户的档，不会像 0.9.x 那样把会话钉在低档上。

**降档确实在真实运行里发生了（机器证据，不是推理）：**跑完后翻宿主的计量文件 `~/.dsh/fast-mode-metrics.jsonl`，`laz16` 臂的 8 个会话里有 **15 个步行**同时带着 `effort` 与 `effortBase`，全部是 `effort: 'low'` 对 `effortBase: 'medium'`——也就是用户选的是 `medium`，那一步因为推理字符超量被降了一档，行里两列同现就是证据（未降档的请求会显式清掉 `effortBase`，不会把降档归因到后来每一步）。同一批行里 `reasoningChars` 有非零读数（41 / 226 / 347 / 537 / 569 字符），说明第二测量通道也在工作，不是靠 provider 那个恒为 0 的 `reasoning` token。`lazt16` 与 `def15` 臂在这些会话里没有一条 `effortBase`——开关关掉就是真关掉。

## 结果：`LZ2`（`lazy` 的梯子文本 v1 / v2 / v3 三版对照，36 次运行）——v2 对 v1 **0.699×**，判分 648/648

`LZ1` 的结论是「收益来自提示文字那一半，降 `reasoningEffort` 那一半没测出额外收益」，所以下一个杠杆就是**文字本身**。这一块把 `lazy` 的梯子文本做成三个 bench 变体（`lazyVariant: v1|v2|v3`，隐藏字段，不进面板），三臂都开 `lazy: true` 且都关降档（`lazyEffort: false`），只看文字：

- `v1`：1.6.0/1.6.1 已发布的那份（762 字符，五条梯子 + 四类「绝不省略」）。
- `v2`：`v1` 加两条——「不要重述任务或先解释计划」「下一步已经清楚就直接做，不要再权衡一遍不打算采用的方案」（973 字符）。
- `v3`：同样规则的精简重写（629 字符），测「更短的提示能不能保住收益」。

`codeimpl` 6 对 + `ledger` 6 对，ABBA 交替，36 次运行，全部 `green`，判分 **648/648**（三臂同题同判，`bench/results/suite-LZ2.jsonl`）。

| 臂 | 墙钟合计 | 墙钟中位 | 步数合计 | 推理字符合计 | 输出 token 中位 |
|---|---|---|---|---|---|
| `lz2v1` | 1073.0 s | 80 s | 68 | 416 873 | 10 936 |
| `lz2v2` | **727.8 s** | **41 s** | 63 | **268 806** | **6 802** |
| `lz2v3` | 1132.5 s | 71 s | 68 | 401 247 | 8 884 |

逐对（各 12 对）：`v2 / v1` 几何 **0.699×**，中位 0.597×，**9/12 更快**，输出 0.715×，步数 0.934×；`v3 / v1` 几何 0.997×，中位 0.848×，7/12 更快，输出 0.935×，步数 0.966×。机制和 `LZ1` 一致：`v2` 的推理字符是 `v1` 的 **0.645×**——省的是每一步里想的时间，不是步数。

**但这一块不能单独下结论。** 12 对里有两处大幅反向（`codeimpl` 第 2 对 2.15×、第 5 对 2.03×，`ledger` 第 2 对 4.41×），符号检验 9/12 并不显著，所以按预先约定再跑一块**换族**的确认块。

## 结果：`LZ3`（确认块：`debugfix` + `migrate` + `stategraph`，30 次运行）——几何 0.822×，**中位 1.014×**，短任务全慢

同一对臂（`lz2v1` 对 `lz2v2`），三族各 5 对，15 对 30 次运行，判分 **370/370** 全绿（`bench/results/suite-LZ3.jsonl`）。

| 臂 | 墙钟合计 | 墙钟中位 | 步数合计 | 推理字符合计 | 输出 token 中位 |
|---|---|---|---|---|---|
| `lz2v1` | 1459.5 s | 85 s | 108 | 429 822 | 10 011 |
| `lz2v2` | 1070.8 s | 53 s | 88 | **310 316** | **6 737** |

逐对几何 **0.822×**、中位 **1.014×**、7/15 更快。分族看才是这块的重点：

- `debugfix`（小任务，5 对）：**5/5 全慢**，1.08× / 1.21× / 1.22× / 1.22× / 1.23×。多出来的两句话在这种 2–3 步的任务上是纯成本。
- `migrate`（5 对）：0.46× / 0.60× / 0.53× 快，1.44× / 1.56× 慢，几何约 0.94×。
- `stategraph`（长任务，5 对）：0.32× / 0.51× / 0.81× / 0.49× / 1.01×，几何约 0.58×——长的、工具多的任务上收益最大（单次最长的三对合计从 690 s 降到 305 s）。

**决定：不换默认文本，`v1` 保持发布态。** 两轮合起来（27 对）几何约 0.765×，但分布是「长任务大赚、短任务倒亏」，而且 `LZ3` 的中位没有改善（1.014×）。把它设成默认等于拿所有短任务去换长任务，这一版不做这个交易。`lazyVariant` 保留在 schema 里（默认 `v1`、不进面板）仅仅是为了这些实验能被重跑；`v3` 的结论是「更短并不更好」（0.997×），也一并留档。

**质量口径：** 两块的判分与对照臂逐题完全一致（648/648、370/370），没有出现「更快但错得更多」的题；这两块不能用来宣称质量提升，只能说明换文本没把质量弄坏。
