dsh-fast-mode 0.0.0-stage → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BENCHMARK.md ADDED
@@ -0,0 +1,1281 @@
1
+ # 实测:快在哪儿,不快在哪儿
2
+
3
+ > **当前默认(1.5.0)**:不按步数收尾。`finishLineSteps` 与 `stepBudget` 默认都是 `0`;什么时候结束由任务状态和完成证据决定(复用已通过的检查、换方法、看门狗),不由计数器决定。
4
+ >
5
+ > 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×)。
6
+ >
7
+ > **口径(1.5.1 起)**:一个单元只在 `exit == 0 && 步数 ≥ 1 && 输出 > 0 && 判分完成 && 未泄漏` 时计数;运行崩溃的单元整格作废并点名,不算红、也不算快。所以「全绿」一律指**有效单元全绿**(1.5.1 29/29、W5 19/19、W7 17/17)。
8
+
9
+ ## 1.5.1 干净重基线(2026-10-26):10 家族 30 个单元,总墙钟 0.72×、逐格中位 0.64×、隐藏测试 60/60 全绿
10
+
11
+ 1.5.0 那一节的数字(0.86× / 0.95× / 0.85×)因两个测量缺陷整体撤回,这一节是修好缺陷之后、用当前默认(`def15`,即插件 `enabled: true` 的出厂配置)重跑的结果。**这是 1.5.x 唯一的速度声明。**
12
+
13
+ 实验:10 个家族(codebase、codeimpl、csvpipe、debugfix、ledger、migrate、perfrefactor、shellops、shellpipeline、stategraph)× 3 对 × 2 臂(`off` / `def15`),共 60 次运行、30 个完整单元。`off` 与 `def15` 都是同一台机器上的真实 DSH 会话,只是插件开关不同;每对按 ABBA 交错跑,抵消机器负载漂移。
14
+
15
+ ### A. 总量(30 个单元,60 次运行)
16
+
17
+ | 臂 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
18
+ |---|---|---|---|---|---|---|
19
+ | `off` | 3434.7 s | 1.00× | 591 244 | 1.00× | 390 | 1.00× |
20
+ | `def15`(1.5.0 默认) | 2472.5 s | **0.72×** | 439 708 | **0.74×** | 276 | **0.71×** |
21
+
22
+ 逐格(每个「家族 × 对」算一格,比较该格两臂的整任务墙钟):**中位 0.64×、均值 0.84×、90 % bootstrap 区间 [0.59, 0.79]、30 格里 24 格更快**。输出 token 逐格中位 0.69×。
23
+
24
+ 两个口径必须一起看:**总和 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×,中位只作这一批的分布记录。
25
+
26
+ ### B. 分家族(3 对加总,`def15` ÷ `off`:墙钟 / 输出 / 步数)
27
+
28
+ | 家族 | 墙钟 × | 输出 × | 步数 × |
29
+ |---|---|---|---|
30
+ | codebase | 0.55× | 0.57× | 0.71× |
31
+ | codeimpl | 0.71× | 0.71× | 0.81× |
32
+ | csvpipe | 1.12× | 0.96× | 0.83× |
33
+ | debugfix | 0.88× | 2.01× | 1.82× |
34
+ | ledger | 0.64× | 0.72× | 0.79× |
35
+ | migrate | 0.45× | 0.56× | 0.60× |
36
+ | perfrefactor | 1.18× | 1.06× | 1.00× |
37
+ | shellops | 0.57× | 0.56× | 0.41× |
38
+ | shellpipeline | 0.84× | 0.82× | 0.72× |
39
+ | stategraph | 0.64× | 0.72× | 0.68× |
40
+
41
+ 省得最多的是「同一条检查被反复重跑」的任务(`migrate`、`shellops`、`codebase`);`csvpipe`/`debugfix`/`perfrefactor` 这类短任务的收益不稳定,`debugfix` 这轮还多写了 2 倍输出——短任务本来就没有可省的重复验证,提醒本身也是成本。
42
+
43
+ ### C. 质量门禁
44
+
45
+ 隐藏测试在 agent 看不到的副本里执行,30 个单元 × 2 臂 **60/60 全绿**,两侧通过率都是 1.000,没有「基线绿而该臂红」的单元。这是有限任务上的证据,不是质保证明;发布句子照旧:「在已测的有限任务中观察到加速,未观察到这些隐藏断言上的退化。」
46
+
47
+ ### D. 这一轮修掉的测量缺陷(都会让旧数字失真)
48
+
49
+ 1. **运行目录必须在仓库外**:旧批次在 `bench/.runs/` 跑,探索中的会话能读到 `suite/<task>/hidden/test_*.py` 和 `ref/`。现在运行根是 `$TMPDIR/dsh-runs/`,判分在临时副本里做;跑批器扫描产物目录,命中 `hidden/`、`ref/`、`bench/suite/`、`MANIFEST.json` 字样的行标 `leak: true` 并丢弃。
50
+ 2. **指标不能按字节偏移切**:旧口径按文件偏移读 `~/.dsh/fast-mode-metrics.jsonl`,会把同机器其他会话的行算进来(约 12 步的运行被记成 156 步)。现在按 `session id` 白名单 + 开跑前 `pre_sids` 差集取行。
51
+ 3. **判分不能在产物目录里做**:`shellops` 臂会把自己的工作目录 `chmod` 成只读,判分脚本往里写隐藏测试时 `EACCES`,整块批次中断(W3 在 31/60 行时崩过一次,已从断点续跑)。现在判分先 `copytree` 到临时目录再跑。
52
+ 4. **fixture 改动要作废旧摘要**:`bench/MANIFEST.json` 的 `aggregate` 是整套 fixture 的 sha256,跑批器开跑前校验,对不上就拒绝运行。
53
+
54
+ ### E. 本轮顺手查清的一件事:W1 里那格「红」是 fixture 歧义,不是退化
55
+
56
+ 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 起全部用新题面。
57
+
58
+ ## 复现块 W5(2026-10-26):三臂同块对照,总量复现 0.73×,逐格中位不复现
59
+
60
+ 这一块是为了两件事:① 用另一批样本复算 1.5.1 的 `def15` 总量;② 在同一块内直接对照 `bv15`(`batchVerify: true`),因为 W4 只有单臂对基线,无法排除那一轮基线本身偏慢。
61
+
62
+ 实验:10 家族 × 2 对 × 3 臂(`off` / `def15` / `bv15`),60 次运行;`shellpipeline` p1 的 `bv15` 一行因读到 fixture 被剔除(见 `bench/README.md` 协议第 12 条),三臂齐全的完整单元 19 个。fixture 摘要 `2d7546f8…`,与 1.5.1 同。
63
+
64
+ ### A. 总量(19 个完整单元)
65
+
66
+ | 臂 | 总墙钟 | × | 总输出 × | 总步数 × | 逐格中位 | 更快格数 |
67
+ |---|---|---|---|---|---|---|
68
+ | `off` | 1855.5 s | 1.00× | 1.00× | 1.00× | — | — |
69
+ | `def15`(默认) | 1357.7 s | **0.73×** | 0.77× | 0.89× | 0.842× | 11/19 |
70
+ | `bv15` | 1550.6 s | 0.84× | 0.84× | 0.83× | 0.800× | 10/19 |
71
+
72
+ > **撤回(2026-10-26,W8b 之后)**:`bv15` 这一行不是 `batchVerify` 的效果。逐会话检索下发文案「files changed since the last run」,20 个会话里命中 **0**——这个提醒在整个块里一次都没到过模型。根因是同一步里调度结果被多个钩子读取,只有其中一个会投递提醒,只读钩子把一次性提醒标成「已用」却没显示(详见 W8b 一节的第 3、4 条)。所以 0.84× 测到的是「与默认基本等价 + 噪声」。`batchVerify` 因此回到**未测**,不是被证伪;默认仍保持 `false`,修复后重测。
73
+
74
+ ### B. 三条结论
75
+
76
+ 1. **总量复现**:`def15` 在这块里是 0.73×,与 1.5.1 的 0.72× 一致。两块样本不同(30 格与 19 格、fixture 摘要不同),总量口径仍是 0.72–0.73×,这是目前唯一稳定的速度声明。
77
+ 2. **`bv15` 不能读成「机制变慢」,也还不能读成任何结论**(见上表下的撤回说明):它在同一块里比 `def15` 慢(0.84× 对 0.73×),但那一臂实际一条提醒都没发出去。W4 筛出的 0.76× 既没被复现、也没被否定,`batchVerify` 回到未测。
78
+ 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×。
79
+ 4. **分家族同样不稳**:1.5.1 里 `shellops` 是 0.57×,本批是 1.43×;`perfrefactor` 1.18× 对 0.80×。族级数字只作分布说明,不当承诺。
80
+
81
+ ### C. 质量门禁
82
+
83
+ `off` 20/20、`def15` 20/20、`bv15` 19/19 全绿,0 红,没有「基线绿而该臂红」的单元。泄漏 1 行,已点名剔除。
84
+
85
+ ### D. 泄漏事件与治具加固
86
+
87
+ `shellpipeline` p1 的 `bv15`(墙钟 109.03 s、29 步、17 603 输出 token、隐藏测试全绿)在会话里从临时运行目录一路 `find` 到仓库,读了 `hidden/test_shellpipeline.py` 与 `ref/`,随后还 `grep` 了整个 `bench/suite/`。这一行按泄漏剔除,不计入任何比值;该格因此少一个 `bv15` 样本。
88
+
89
+ 治具随之加固(协议第 12 条):跑批器开跑前把每个任务的 `hidden/`、`ref/` 置为不可读(题面与测试模块此时已读进内存,`verify()` 从内存把测试写进临时副本判分,`seed` 复制不受影响),块结束或收到 `SIGINT`/`SIGTERM` 时恢复;仓库内 `bench/.runs/` 已删除并写进 `.gitignore`;仓库外两处历史批次归档一并置为不可读。泄漏哨兵保留,任何一块出现泄漏行都必须点名。
90
+
91
+ ## 筛查块 W6(2026-10-26,**结论已由 W7 证伪**):`openBatch` 曾以 0.77× 快过默认的 0.93×
92
+
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 的数字只作筛查记录,不当速度声明。
94
+
95
+ `openBatch` 是最后一个没在干净批次里测过的默认关闭机制:每轮只在第一步发一次「把已知要做的独立调用放进同一块、shell 命令用 `&&` 串起来」,之后不再插入任何内容(`src/auto.js` 的 `open-batch` 分支)。旧批次里它被记成 1.03× 并有一次 325 步失控,但那个批次整体撤回,所以重测。
96
+
97
+ 实验:10 家族 × 2 对 × 3 臂(`off` / `def15` / `open15` = `openBatch: true`),60 次运行;`stategraph` p2 的 `off` 一行读到 fixture 被剔除(见协议第 12 条),三臂齐全的完整单元 19 个。
98
+
99
+ ### A. 总量(19 个完整单元)
100
+
101
+ | 臂 | 总墙钟 | × | 总输出 × | 总步数 × | 逐格中位 | 更快格数 | 最长步数 |
102
+ |---|---|---|---|---|---|---|---|
103
+ | `off` | 2144.6 s | 1.00× | 1.00× | 1.00× | — | — | 35 |
104
+ | `def15`(默认) | 1997.3 s | 0.93× | 0.96× | 0.96× | 0.962× | 11/19 | 38 |
105
+ | `open15` | 1654.0 s | **0.77×** | 0.84× | **0.64×** | 0.662× | 11/19 | **23** |
106
+
107
+ 逐格均值的 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。
108
+
109
+ ### B. 结论与边界
110
+
111
+ 1. `open15` 在这一块里同时赢总量(0.77× 对 0.93×)、中位(0.662× 对 0.962×)与最长尾(23 步对 38 步),且门禁 20/20 全绿、0 红。旧批次「1.03× 墙钟 + 325 步失控」没有复现,那种失控来自被撤回的测量口径。
112
+ 2. 机制仍在约束内:一次开局提醒,不按步数结束回合、不改完成判定、不省复验(门禁全绿)、不动 `reasoningEffort`、不加请求体字段。
113
+ 3. **这只是筛查**:19 个单元、2 对、单块。`def15` 自己的总量比在两块之间从 0.73× 摆到 0.93×,说明块间波动很大;`open15` 的 0.77× 必须再拿一块独立样本同块复测(W7)才算数,不能凭这一块改默认。
114
+ 4. 分家族:`csvpipe` 0.36×、`codebase` 0.42×、`debugfix` 0.76×、`perfrefactor` 0.87× 都有收益,`stategraph` 1.06×、`shellops` 0.95× 基本打平;没有哪一族被显著拖慢。
115
+
116
+ ## 复现块 W7(2026-10-26):默认臂 0.73× 复现,`openBatch` 被证伪;7 行运行崩溃、3 个单元作废
117
+
118
+ 这一块做三件事:① 再拿一批独立样本复算 `def15` 的总量(第三块复现);② 在同块内复测 `openBatch`(W6 的 0.77×);③ 把「运行崩溃」和「机制变慢」分开——旧口径把两者算进同一个分母,会让一块里的基础设施故障看起来像门禁红灯。
119
+
120
+ 实验: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 同。
121
+
122
+ ### A. 总量(17 个有效单元)
123
+
124
+ | 臂 | 总墙钟 | × | 总输出 × | 总步数 × | 逐格中位 | 更快格数 | 最长步数 |
125
+ |---|---|---|---|---|---|---|---|
126
+ | `off` | 3724.4 s | 1.00× | 1.00× | 1.00× | — | — | 36 |
127
+ | `def15`(默认) | 2734.8 s | **0.73×** | 0.75× | 0.59× | 0.709× | 13/17 | 17 |
128
+ | `open15` | 3390.2 s | 0.91× | 0.84× | 0.67× | 0.890× | 12/17 | 29 |
129
+
130
+ 逐格均值的 90 % 区间:`def15` **0.816× [0.692, 0.948]**(不跨 1,这是三块复现里第一块总量与逐格均值同时排除「打平」的),`open15` 0.837× [0.677, 1.008]。
131
+
132
+ ### B. 结论
133
+
134
+ 1. **默认臂第三块复现**:0.73×,与 1.5.1 的 0.72×、W5 的 0.73× 一致。三块复现(W3+W5+W7)合并 65 个有效单元,总墙钟 **0.742×**、总输出 **0.758×**。
135
+ 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`。
136
+ 3. **步数压到 0.59× 但输出只到 0.75×**:`def15` 把步数从 236 压到 138(最长一格 36 步 → 17 步),每步输出反而变长(0.75 / 0.59 ≈ 1.27×)——少掉的步数里有一部分是把工作并进了更长的步骤。这解释了为什么墙钟停在 0.73× 而不是跟着步数到 0.6×:墙钟的 93 % 是解码时间,而解码量由输出 token 决定,步数只是它的代理。
137
+ 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×),只作分布说明。
138
+
139
+ ### C. 质量门禁
140
+
141
+ `off` 17/17、`def15` 17/17、`open15` 17/17 全绿,0 红,没有「基线绿而该臂红」的有效单元。作废的 3 个单元不是红:它们三臂(或其中两臂)根本没有产出会话,判分无从谈起。
142
+
143
+ ### D. 崩溃行与做法修正
144
+
145
+ 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)。
146
+
147
+ ## 对照块 W8b(2026-10-26):`def15` 逐格中位 0.717× 复现;`preferPatch` 臂因提醒未下发而作废
148
+
149
+ 这一块原本要测第四个杠杆 `preferPatch`(大整文件写入之后,提醒下一步只发改动的行)。60 次运行、20 个单元(10 家族 × 2 对 × `off` / `def15` / `pp16`),其中 `ledger` p2 的 `off` 行泄漏(读了隐藏测试,输出 53 133 token),整格作废,**59 个有效单元,有效单元零红**。fixture 摘要与 1.5.1 同。
150
+
151
+ ### A. 结果(59 个有效单元)
152
+
153
+ | 臂 | n | 总墙钟 | × `off` | 总输出 token | 总步数 | 最长步数 |
154
+ |---|---|---|---|---|---|---|
155
+ | `off` | 19 | 5138.5 s | 1.00× | 369 845 | 197 | 25 |
156
+ | `def15`(默认) | 20 | 4798.7 s | **0.934×** | 343 042 | 215 | 24 |
157
+ | `pp16`(`preferPatch`) | 20 | 4129.3 s | 0.804× | 294 156 | 197 | 26 |
158
+
159
+ 逐格稳健统计(每格取该格两臂同一对任务整任务墙钟之比):`def15/off` **中位 0.717×、几何均值 0.819×、19 格里 13 格更快**,输出逐格中位 0.823×,步数逐格中位 **1.000×**。
160
+
161
+ ### B. 结论
162
+
163
+ 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 这一块总量偏软,只作分布与门禁记录。
164
+ 2. **本块 `def15` 步数没降(逐格中位 1.000×),省下的时间来自输出长度**:与 W7「步数 0.59×、输出 0.75×」互补,说明两条路都能省墙钟,具体走哪条取决于任务形状。
165
+ 3. **`pp16` 臂作废,不计入任何结论**:`preferPatch` 在这次运行里**一次提醒都没有下发**(20 个会话里检索下发文案 `that step sent a whole file` 命中 0)。根因是 liveness 缺陷——调度结果在一个步里被多个钩子读取,其中只有一个会真正投递提醒,其余只读钩子也调用了同一个「一次性」提醒函数,把提醒标成已用却从未显示给模型。它真实的行为差异只是占掉那一步的提醒槽位、把该步的 late-pressure 提醒挤掉,所以 `pp16` 对 `def15` 的中位 0.942× 是噪声,**既不能读成 `preferPatch` 的效果,也不能读成它的反证**。
166
+ 4. 修复见 `3cb94e4`:只读调用改为 `{emit: false}`,提醒只在真正投递时消耗;回归测试与单格验证(同一格修复前 0 次下发、修复后 1 次下发)都过。修复后的筛选块 W9 用 `def15` / `pp16` 两臂在 5 个写入型家族上重测。
167
+
168
+ ### C. 机制余量预估(在 W9 跑之前登记,用来判断「还差多少」)
169
+
170
+ W9 会给出实测量,但先把「最多能省多少」算出来,免得把噪声当效果:
171
+
172
+ 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。
173
+ 2. **输出里约 23 % 是工具调用参数**。对 134 个 `def15` 有效格的会话做统计:`tool/call` 参数字符数 ÷ 3.6 再除以该格输出 token,中位 **23.1 %**(均值 25.9 %)。剩下约 77 % 是推理与正文。
174
+ 3. **这部分参数基本不可压**。15 个 `def15` 会话里 153 次整文件 `write` 合计 716 468 字符;其中**写到本会话里已经写过的同一路径**(即真正的整文件重写浪费)只有 5 876 字符,占 0.8 %。也就是说参数是在**创建交付物本身**,不是重复劳动。据此预登记:`preferPatch` 的上限约等于输出的 0.2 %,W9 的预期是「与默认无差异」,若出现更大的差就是噪声。
175
+ 4. **精确重复的工具调用也很少**:373 次调用里同名同参重复 8 次(2.1 %),「别重复读同一文件」这类机制同样没有余量。
176
+ 5. **还剩下的唯一大块是重复验证**:1534 次调用里 187 次是跑测试/校验类命令(12.2 %),145 个会话里 25 个出现 ≥3 次。合并这类步骤正是 `batchVerify` 的目标——它因为同一个 liveness 缺陷在 W5 里零下发,**至今未被真正测过**。
177
+ 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` 不动、不加请求字段的前提下,能动的只有「重复验证的步数」,即第 5 条。
178
+
179
+ ### D. W9(`preferPatch` 单格复核):4 个单元后中止,只作「提醒确实下发」的凭证
180
+
181
+ W8b 之后用 `def15` / `pp16` 在 5 个写入型家族上重测 `preferPatch`,跑到 4 个有效单元时中止(改用更有余量的 `batchVerify` 臂,见 C 第 5 条)。这 4 格全部有效、全绿:
182
+
183
+ | 格 | `def15` | `pp16` | 比值 |
184
+ |---|---|---|---|
185
+ | `csvpipe` p1 | 334 s / 9 步 / 24 900 tok | 343 s / 12 步 / 30 607 tok | 1.03× |
186
+ | `ledger` p1 | 151 s / 7 步 / 15 341 tok | 577 s / 6 步 / 33 534 tok | 3.82× |
187
+
188
+ 两条结论,都不涉及效果大小:
189
+
190
+ 1. **修复后提醒确实到过模型**:`csvpipe` p1 的 `pp16` 会话里检索到下发文案 `that step sent a whole file` 命中 1 次。W8b 的 20/20 零下发就此失效,`preferPatch` 从「因 liveness 缺陷作废」变成「已下发、效果待测」。
191
+ 2. **4 格不足以下任何比值结论**,尤其 `ledger` 那格 `pp16` 输出翻倍(15 k → 34 k tok),属于单格长尾。按 C 第 3 条的预登记,这个机制的余量上限约等于输出的 0.2 %,所以在拿到整块之前**不许把上面两个比值当效果**。
192
+
193
+ ## 1.5.0 重基线(2026-10-25,**已撤回,仅作历史**):9 家族 54 个单元,总墙钟 0.86×,总输出 0.95×
194
+
195
+ > **撤回(2026-10-26)**:本节及 TA3/TA4 三块批次的每一步数字都作废,原因是两个测量缺陷:
196
+ > ① 运行目录在仓库内(`bench/.runs/`),探索中的会话能读到 `../../suite/<task>/hidden/test_*.py` 与 `ref/`,30 个 SG 会话里确认 4 个读到了隐藏测试或参考实现;
197
+ > ② 指标按字节偏移切片,把同机器其他会话的行算进来(一次约 12 步的运行被记成 156 步)。
198
+ > 因此 `0.86× / 0.95× / 0.85×` 与所有步数比不再作为声明;隔离后的干净复测见下面的「1.5.1」。本节其余内容保留,只作为当时口径的记录。
199
+
200
+ 口径换了一次,这是本节和 1.4.4 及更早各节不能直接比数字的原因:**只看整任务完成后的总量**——每个「任务 × 对」把三臂都跑完才算一个单元,单元内比较总墙钟、总输出 token、总步数,再把 54 个单元(9 家族 × 6 对)加总。隐藏测试是门禁(runner 事后执行,agent 看不到测试文件),任何一臂红即该单元作废。
201
+
202
+ > 本文件 1.5.0 的每个数字都可以在仓库里复算:fixture、跑批器、汇总器与三块原始结果都在 [`bench/`](bench/README.md)。`python3 bench/report.py --files bench/results/suite-W3.jsonl --baseline off` 会原样打出下面的总量表、分家族表与尾部分布,并逐条列出红行与「基线绿而该臂红」的门禁违规。改任何 fixture 都会让 `bench/MANIFEST.json` 的摘要对不上,跑批器会拒绝开跑。
203
+
204
+ ### A. 三臂总量(54 个单元,162 次运行)
205
+
206
+ | 臂 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
207
+ |---|---|---|---|---|---|---|
208
+ | `off` | 7142.3 s | 1.00× | 1 196 707 | 1.00× | 1155 | 1.00× |
209
+ | `def15`(1.5.0 默认:late 文风纪律) | 6119.2 s | **0.86×** | 1 140 982 | **0.95×** | 983 | **0.85×** |
210
+ | `fl10`(退役默认:late + 第 10 步收尾线) | 5579.4 s | **0.78×** | 939 664 | **0.79×** | 831 | **0.72×** |
211
+
212
+ 总体结论:默认配置在 9 个家族上省下 14 % 墙钟、5 % 输出 token、15 % 步数;`fl10` 省得更多(22 % / 21 % / 28 %),但代价见 D 节。
213
+
214
+ ### B. 分家族(总墙钟 × / 总输出 token ×,相对 `off`)
215
+
216
+ | 家族 | 类型 | n | `off` 基线 | `def15` 墙钟/输出 | `fl10` 墙钟/输出 |
217
+ |---|---|---|---|---|---|
218
+ | `csvpipe` | 数据管道(读多写少) | 6 | 1310.3 s / 212 380 t | 0.79× / 0.85× | 0.55× / 0.57× |
219
+ | `debugfix` | 修 bug(小步) | 6 | 331.7 s / 65 562 t | 0.56× / 0.51× | 0.38× / 0.30× |
220
+ | `ledger` | 从零实现(深) | 6 | 1317.3 s / 254 050 t | 0.73× / 0.67× | 0.71× / 0.61× |
221
+ | `stategraph` | 状态机重写 | 6 | 1034.9 s / 157 600 t | 0.82× / 0.92× | 0.94× / 1.08× |
222
+ | `shellpipeline` | 终端流水线 | 6 | 780.3 s / 133 252 t | 0.87× / 0.91× | 0.72× / 0.77× |
223
+ | `shellops` | 终端运维(长命令) | 6 | 688.0 s / 100 152 t | 0.85× / 1.39× | 1.01× / 1.24× |
224
+ | `migrate` | 批量迁移(写多) | 6 | 878.0 s / 129 561 t | 1.02× / 1.34× | 0.78× / 0.78× |
225
+ | `codeimpl` | 代码实现(写多) | 6 | 631.8 s / 114 388 t | 1.09× / 1.13× | 1.03× / 0.91× |
226
+ | `perfrefactor` | 性能重构(改既有码) | 6 | 170.0 s / 29 762 t | 1.33× / 1.64× | 1.45× / 1.43× |
227
+
228
+ 读多写少和"从零写"两类赢得很稳(csvpipe、debugfix、ledger、shellpipeline、stategraph 五个家族墙钟 0.55–0.87×);**三个写文件型家族(migrate、codeimpl、perfrefactor)没有稳定加速,默认臂甚至更慢**(1.02×–1.33×)。这一点由下面的四臂消融单独确认,不靠总量掩盖。
229
+
230
+ ### C. 尾部(同一批单元,按单次运行)
231
+
232
+ | 臂 | p50 | p90 | max | 单次最大步数 |
233
+ |---|---|---|---|---|
234
+ | `off` | 122.2 s | 263.3 s | 326.8 s | 137 |
235
+ | `def15` | 105.5 s | 201.6 s | 292.7 s | 63 |
236
+ | `fl10` | 92.1 s | 178.7 s | 277.1 s | 62 |
237
+
238
+ 尾部也被压住了:p90 从 263 s 降到 202 s(`def15`)/179 s(`fl10`),单次最长运行从 137 步降到 63/62 步。**`off` 的 p90 与 max 明显被两个自己打转的基线运行撑着**(见 G 节)。
239
+
240
+ ### D. 被否掉的默认:`fl10` 仍然更快,但不作默认
241
+
242
+ `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 的速度声明**。
243
+
244
+ ### E. 四臂消融:问题不在"提示词",在"写文件"
245
+
246
+ 在 4 个写文件家族(`codeimpl`、`migrate`、`perfrefactor`、`shellops`)上,12 个完整四臂单元(48 次运行,全绿):
247
+
248
+ | 臂 | 配置 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
249
+ |---|---|---|---|---|---|---|---|
250
+ | `off` | 关闭 | 1587.2 s | 1.00× | 171 288 | 1.00× | 132 | 1.00× |
251
+ | `def15` | `guidance: true` + `outputDiscipline: 'late'` | 1633.9 s | 1.03× | 175 775 | 1.03× | 142 | 1.08× |
252
+ | `g1` | `guidance: true`,无文风纪律 | 2095.3 s | 1.32× | 210 993 | 1.23× | 139 | 1.05× |
253
+ | `s1` | 文风纪律全程静态 | 1520.7 s | 0.96× | 154 050 | 0.90× | 107 | 0.81× |
254
+
255
+ - 关掉文风纪律(`g1`)**更慢**:调度器只剩并行提示,模型在大段写入时没有被要求压缩输出,输出 +23 %、墙钟 +32 %。这一条否掉了"少提醒=更快"的直觉。
256
+ - 静态纪律(`s1`)在写文件家族有小幅正收益(0.96× / 0.90× / 0.81×),但不足以抵消它在长读任务上的提示税,所以不进默认。
257
+ - 结论:写文件家族的速度不由提示词决定,这是 1.5.0 不再加提示词、转而做 harness 层机制(复用检查、无进展诊断、token 预算)的原因。
258
+
259
+ ### F. 新增经典编程家族 `codebase`(3 臂 × 3 对,9/9 绿)
260
+
261
+ 小仓库(`inventory/` 包 + 可见测试 + README),任务是修 2 个真 bug、加 TTL、加分页;隐藏套件 18 条只调公共 API。种子状态隐藏测试 **6/18 红**(确实要干活),参考解 18/18 绿。
262
+
263
+ | 臂 | 总墙钟 | × | 总输出 token | × | 步数 | 隐藏测试 |
264
+ |---|---|---|---|---|---|---|
265
+ | `off` | 143.6 s | 1.00× | 15 299 | 1.00× | 21 | 9/9 绿 |
266
+ | `def15` | 120.6 s | **0.84×** | 12 086 | **0.79×** | 19 | 9/9 绿 |
267
+ | `fl10` | 153.2 s | 1.07× | 15 580 | 1.02× | 21 | 9/9 绿 |
268
+
269
+ 这是最接近"日常改一个真实小仓库"的场景:默认臂 0.84× 墙钟、0.79× 输出,`fl10` 反而略慢——和 D 节的结论一致,收尾线在这种"改完还要再验一次"的任务上没有便宜可占。
270
+
271
+ ### G. 对照臂:按真实 usage 的 `outputBudget`(3 家族 × 3 对,9/9 绿)
272
+
273
+ 前面说过写文件家族的问题不在提示词。这一轮就把"压输出"做成 harness 机制而不是再写一段提示:`outputBudget` 读上游 `usage.outputTokens`,本轮累计到线就发一次「收掉当前闭环、跑一次证明它的检查、汇报」,每轮最多一次、默认 `0`(关)。对照臂 `bud8` = 默认配置 + `outputBudget: 8000`,与 `off`、`def15` 同轮 ABBA,`codeimpl` / `migrate` / `codebase` 各 3 对:
274
+
275
+ | 臂 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
276
+ |---|---|---|---|---|---|---|
277
+ | `off` | 1060.8 s | 1.00× | 140 555 | 1.00× | 109 | 1.00× |
278
+ | `def15` | 749.4 s | 0.71× | 106 081 | 0.75× | 85 | 0.78× |
279
+ | `bud8` | 726.6 s | **0.68×** | 98 443 | **0.70×** | 95 | 0.87× |
280
+
281
+ 分家族(墙钟 × / 输出 ×):
282
+
283
+ | 家族 | `off` | `def15` | `bud8` |
284
+ |---|---|---|---|
285
+ | `codeimpl`(写多、单次写入最长) | 559.0 s / 81 101 t | 0.58× / 0.60× | **0.55× / 0.46×** |
286
+ | `migrate`(批量改写) | 408.8 s / 44 573 t | 0.85× / 1.02× | 0.77× / 0.99× |
287
+ | `codebase`(小仓库改 bug,单步写入短) | 93.0 s / 14 881 t | 0.82× / 0.81× | 1.13× / 1.16× |
288
+
289
+ 读法:
290
+
291
+ - **机制确实压得下输出**:在最能验证它的 `codeimpl` 上,`bud8` 相对 `def15` 再省 23 % 输出(0.60× → 0.46×)、再快 3 个百分点(0.58× → 0.55×)。这是本轮唯一一个直接针对"输出 token"的机制,方向成立。
292
+ - **它对单步写入短的任务是负担**:`codebase` 上 1.13× / 1.16×——预算线在第八千 token 才响,小任务要么响不到、要么在已经接近完成时把模型从"再验一次"推到"就这样报",于是多花一步。**这不是可以设成默认的东西**,默认仍是 `0`。
293
+ - **样本很小**:每家族 n=3、单臂、只有一个阈值(8000)。它够用来回答"这条路有没有用"(有),不够用来定"阈值该设多少"(没测 4000/16000,也没测它是否会像 `fl10` 那样压掉必要复验)。
294
+ - 27 次运行全部 `exit 0`、`green: true`,没有出现基线绿而对照臂红的单元。
295
+
296
+ ### H. 第四臂:`openBatch`(9 家族 × 6 对,53 个完整单元,0 红)
297
+
298
+ 目标里点名要测的第三臂是 `openBatch`(一次就绪的独立调用合成一步发出)。它在 `off`/`fl10` 之外单独跑了一块(9 家族 × 6 对 = 54 次运行,`open15` = 1.5.0 默认 + `openBatch: true`),再按 `(task,pair)` 与 V15 的 `off`/`fl10` 同单元配对:
299
+
300
+ | 臂 | 总墙钟 | × | 总输出 token | × | 总步数 | × |
301
+ |---|---|---|---|---|---|---|
302
+ | `off` | 6875.9 s | 1.00× | 1 169 089 | 1.00× | 1137 | 1.00× |
303
+ | `fl10` | 5491.9 s | 0.80× | 929 729 | 0.80× | 824 | 0.72× |
304
+ | `open15` | 5041.8 s | **0.73×** | 753 421 | **0.64×** | 450 | 0.40× |
305
+
306
+ 分家族(墙钟 × / 输出 ×):
307
+
308
+ | 家族 | `off` | `fl10` | `open15` |
309
+ |---|---|---|---|
310
+ | `codeimpl` | 631.8 s / 114 388 t | 1.03× / 0.91× | **0.76× / 0.68×** |
311
+ | `csvpipe` | 1310.3 s / 212 380 t | 0.55× / 0.57× | 0.68× / 0.61× |
312
+ | `debugfix` | 331.7 s / 65 562 t | 0.38× / 0.30× | 0.51× / 0.39× |
313
+ | `ledger` | 1317.3 s / 254 050 t | 0.71× / 0.61× | 0.56× / 0.45× |
314
+ | `migrate` | 611.7 s / 101 943 t | 0.97× / 0.89× | 1.04× / 1.01× |
315
+ | `perfrefactor` | 170.0 s / 29 762 t | 1.45× / 1.43× | 0.97× / 0.66× |
316
+ | `shellops` | 688.0 s / 100 152 t | 1.01× / 1.24× | 0.92× / 0.81× |
317
+ | `shellpipeline` | 780.3 s / 133 252 t | 0.72× / 0.77× | 0.78× / 0.65× |
318
+ | `stategraph` | 1034.9 s / 157 600 t | 0.94× / 1.08× | **0.69× / 0.73×** |
319
+
320
+ 尾巴:`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)。
321
+
322
+ 读法:
323
+
324
+ - **这是目前测到最快的配置**:总量 0.73× 墙钟、0.64× 输出,而且 53 个完整单元里 **0 红**(`off` 绿的单元 `open15` 全绿)。
325
+ - **输出下降不是靠少想**:它把"已经知道要做的独立调用"压进同一步,少掉的是重复的上下文重读与中间的过渡文字,所以输出 token 掉得比墙钟更多(−36 % vs −27 %)。
326
+ - **步数 0.40× 有一半是机械的**:一步发多个调用,步数必然降;所以步数不能单独当成绩,墙钟才是。墙钟也降,说明省的是真实往返。
327
+ - **它仍然治不了 `migrate`**(1.04× / 1.01×):串行改写本来就没有可并行的调用。
328
+ - **这次不是交错 ABBA**:`open15` 是单独一块跑完再按单元配对,机器负载在块间会漂移,所以下面单独做交错复核。
329
+
330
+ **交错复核(4 家族 × 3 对,12 个完整单元、24 次运行、0 红)**:`open15` 与 `def15` 只差 `openBatch` 一个键,同单元交替跑:
331
+
332
+ | 臂 | n | 总墙钟 | × | 总输出 token | × | 步数 | p50 / p90 / max |
333
+ |---|---|---|---|---|---|---|---|
334
+ | `def15` | 12 | 1724.6 s | 1.00× | 260 293 | 1.00× | 146 | 120.8 / 261.4 / 298.1 s |
335
+ | `open15` | 12 | 1185.8 s | **0.69×** | 186 964 | **0.72×** | 114 | 99.4 / 147.5 / 174.2 s |
336
+
337
+ 配对中位 **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)。
338
+ - 有一次运行 `exit -9`:那是我自己 `pkill suite-run.py` 时误杀了子进程(`hasEntry: false`、0 步、36 s),已删掉重跑同一单元,不是质量红。
339
+
340
+ ### H2. 三臂交错 ABBA:9 家族 × 6 对(171 次运行,54 个完整单元)
341
+
342
+ 这是本轮的主实验:`off` / `fl10` / `open15` 三臂在 9 个家族上真正的交错 ABBA(每臂每家族 6 对,配对顺序逐对翻转),隐藏测试逐单元门禁。171 次运行里 **54 个单元三臂全绿**可使用,另 3 个单元有红(见下),不进入任何一臂的总量。`ledger` 的 22 条断言偏紧,前 6 对里有 3 对因某臂红而整对作废,于是把它补到 9 对,才凑满 6 个完整单元(用的是 p3/p4/p5/p7/p8/p9)。
343
+
344
+ | 臂 | n | 总墙钟 | × | 总输出 token | × | 总步数 | × |
345
+ |---|---|---|---|---|---|---|---|
346
+ | `off` | 54 | 7841.3 s | 1.00× | 2 035 299 | 1.00× | 3960 | 1.00× |
347
+ | `fl10` | 54 | 5160.6 s | **0.66×** | 1 390 465 | **0.68×** | 2655 | **0.67×** |
348
+ | `open15` | 54 | 5419.4 s | **0.69×** | 1 438 884 | 0.71× | 2800 | 0.71× |
349
+
350
+ 逐对中位(对 `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` 略前。
351
+
352
+ 分家族(墙钟 × / 输出 ×):
353
+
354
+ | 家族 | n | `off` | `fl10` | `open15` |
355
+ |---|---|---|---|---|
356
+ | `codeimpl` | 6 | 884.3 s / 293 276 t | 0.68× / 0.60× | 0.73× / **0.56×** |
357
+ | `csvpipe` | 6 | 1438.3 s / 322 202 t | 0.55× / 0.64× | **0.46× / 0.51×** |
358
+ | `debugfix` | 6 | 194.8 s / 50 405 t | **0.69× / 0.78×** | 0.91× / 1.24× |
359
+ | `ledger` | 6 | 1616.9 s / 398 128 t | **0.62× / 0.66×** | 0.72× / 0.82× |
360
+ | `migrate` | 6 | 885.1 s / 210 915 t | **0.67× / 0.69×** | 0.82× / 1.07× |
361
+ | `perfrefactor` | 6 | 287.8 s / 71 076 t | 0.77× / 0.68× | **0.62× / 0.59×** |
362
+ | `shellops` | 6 | 663.1 s / 254 229 t | 0.78× / 0.55× | **0.75× / 0.37×** |
363
+ | `shellpipeline` | 6 | 910.3 s / 234 962 t | **0.52× / 0.61×** | 0.83× / 0.79× |
364
+ | `stategraph` | 6 | 960.5 s / 200 106 t | 0.87× / 1.16× | **0.63× / 0.88×** |
365
+
366
+ 尾巴(同一批单元):`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 最低、尾部最重,说明它减少的是请求数,不是单次请求的时长)。
367
+
368
+ 读法:
369
+
370
+ - **两条「少发请求」的路线都能拿掉约三分之一墙钟**,输出 token 也降三成;这不是本轮才出现的效应,前面 H 节与 V15 的方向一致,现在 9 个家族都是每臂 6 个完整单元。
371
+ - **`fl10` 按判定规则不合格**:`ledger` p1 上 `off` 绿(22/22)、`fl10` 红(21/22),按「基线绿而该臂红即不合格」的规则出局;该单元随后重跑 5 次全绿(1/6 失败率,非确定)。它仍然是消融臂,不做默认。`open15` 在 H3 的 5 家族对照里也有一次同样形式的门禁违规(`shellops` p7),所以「三臂都过门禁的更快配置」本轮不存在。
372
+ - **`off` 自己也红过两次**(`ledger` p2 与 p6,均 21/22),因此这两对按规则整对作废、不进入任何一臂的总量;`ledger` 的 6 个完整单元是补跑 p7–p9 后才补齐的。这也说明该家族的 22 条隐藏断言对模型偏紧,不是某一臂的退化。
373
+ - **补跑的两处口径**:`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` 里保留的是替换后的行。
374
+ - **结论不依赖 `ledger`**:剔掉这一族、只用其余 48 个单元重算,两臂比值不动(`fl10` 0.67× / 0.69× / 0.69×,`open15` 0.68× / 0.68× / 0.69×)。
375
+ - **两臂互补而非互相支配**:`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× 附近,再往下要靠形状判断而不是更狠的压制。
376
+
377
+ 是否把 `openBatch` 提为默认,要看它与**当前默认 `def15`** 的交错对照(下面 H3)。答案是**不提**:换对手之后它 1.03× 墙钟(输出与步数持平或略少),还出现一次基线绿而它红的单元与一次 325 步 / 5.2 倍 token 的失控。
378
+
379
+ ### H3. `openBatch` 对 1.5.0 默认(`def15`):扩到 5 家族后被推翻
380
+
381
+ 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`**:
382
+
383
+ | 臂 | n | 总墙钟 | × | 总输出 token | × | 总步数 | × |
384
+ |---|---|---|---|---|---|---|---|
385
+ | `def15`(默认) | 30 | 2707.2 s | 1.00× | 758 146 | 1.00× | 1586 | 1.00× |
386
+ | `open15` | 30 | 2785.7 s | **1.03×** | 749 801 | 0.99× | 1502 | **0.95×** |
387
+
388
+ 逐对中位 0.91×(19/30 更快)看起来还行——输出总量基本持平、步数还少 5 %——但墙钟被一个单元拖翻:
389
+
390
+ | 家族 | n | `def15` | `open15` 墙钟/输出/步数 |
391
+ |---|---|---|---|
392
+ | `csvpipe` | 6 | 898.0 s / 273 832 t | **1.18× / 1.19× / 1.12×** |
393
+ | `debugfix` | 5 | 126.6 s / 15 679 t | 0.78× / 1.34× / 1.12× |
394
+ | `perfrefactor` | 7 | 239.8 s / 77 620 t | 0.86× / 0.85× / 0.67× |
395
+ | `shellops` | 6 | 604.2 s / 147 341 t | 0.86× / 0.77× / 0.64× |
396
+ | `stategraph` | 6 | 838.7 s / 243 674 t | 1.07× / 0.92× / 1.06× |
397
+
398
+ 两个硬结果:
399
+
400
+ - **没过门禁**:`shellops` p7,`def15` 绿 14/14,`open15` 红 13/14。按「基线绿而该臂红即不合格」,`openBatch` **不合格**。
401
+ - **合批会放大失控**:`csvpipe` p5,`open15` 用了 **325 步 / 499.0 s / 188 731 t**,同单元 `def15` 是 82 步 / 133.9 s / 36 433 t——**5.2 倍输出 token**。合批把「先看清再动手」的串行检查压成一次大爆写,写错之后要整片重写。它自己没有收敛机制,因为合批只改调度、不发新约束。
402
+
403
+ 结论:`openBatch` 保持默认 `false`,只作实验旋钮,并在 REFERENCE 里写明「本版实测不优于默认、且未过隐藏测试门禁」。H 节那 12 个单元的 0.69× 是**小样本上界**,不作为声明。
404
+
405
+ ### I. 边界与两个必须披露的偏差
406
+
407
+ - **基线里有两个失控运行**:`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×**——加速的六成来自墙钟,而输出/步数上「默认更省」在剔掉基线打转后消失。本节所有总量都按"不剔任何运行"给,因为那是真实成本;但这个敏感性必须一起读。
408
+ - **机器是共享且超载的**:本轮期间同一台机器上还有另一个项目在跑基准(`run_ab.py`,约 20 load / 12 核),绝对墙钟被整体抬高,ABBA 配对与同单元加总仍然有效,但跨轮次的绝对值不可比。
409
+ - **臂集合**:`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`。
410
+ - **`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` **不合格**,只能留作消融臂;这也和它的机制一致——它压掉的正是「测试通过后的复验」。
411
+ - **隐藏测试门禁**:V15 167 行、AB2 48 行、CB1 9 行、BUD1 27 行、OPEN15 54 行,全部 `green: true`(其中 OPEN15 有 1 次 `exit -9` 是我自己 `pkill` 误杀子进程,删掉重跑,不算红),没有出现「基线绿而加速臂红」的单元。因此本轮的结论可以说成:**在已测的有限任务中观察到加速,未观察到这些隐藏断言上的退化**——不能说成"不会降智"。
412
+
413
+ ### J. 本轮之后的方向
414
+
415
+ 写文件家族既然不是提示词问题,下一步只能是 harness 机制:①复用已通过的检查(已实现,按产物版本失效);②无进展只诊断不判决(已实现);③**按真实 usage 的 `outputBudget`**——本轮已实现并作为对照臂测过(G 节:`codeimpl` 上输出再降 23 %,小任务上变慢)。默认仍为 `0`(关闭),因为在完成度不足时提前收口正是 1.4.4 犯过的错。
416
+
417
+ 本轮把三条加速路线都走完了,也都用隐藏测试门禁筛过一遍:`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`,对写密集任务放行——但那需要先做出一套可靠的形状判据,并且逐家族复测,不是再加一条提示词能解决的。
418
+
419
+ ## 1.4.4(历史/消融):收尾线定版——7 家族 14 对,中位 1.86×,输出 −52%,正确性零损失(2026-10-25)
420
+
421
+ 这一轮把“长任务的尾巴”当成单独的对象来测。上一版只证明了“时间随步数出现”,却没回答怎么在**不伤正确性**的前提下把尾巴剪掉。答案是一个 harness 层的软收尾线:第 10 步由调度器注入一次“别再扩大范围、不重跑已通过的检查、只修还在报的错”,不阻断工具、不截断步骤。
422
+
423
+ ### I. 语料:5 轮 ABBA,272 次运行,7 个家族
424
+
425
+ 7 个长/短混合家族(每家族 4–32 步、10–22 条隐藏断言、确定性评分):`csvpipe`、`ledger`、`migrate`、`stategraph`、`shellpipeline`、`debugfix`、`perfrefactor`。全部运行 `exit 0`,隐藏测试由 runner 事后执行,agent 看不到测试文件。
426
+
427
+ | 轮次 | 目的 | 臂 | 运行数 |
428
+ |---|---|---|---|
429
+ | A1 | 长任务基线 | off / on / late | 42 |
430
+ | B1 | 归因:提示段、并行宽度 | off / on / late / late-par1 / late-guid0 | 70 |
431
+ | C1 | 步数旋钮 | off / late / fl8 / fl12 / b15 | 50 |
432
+ | D1 | 跨家族确认 + 短任务守卫 | off / fl8 / b15 / combo | 56 |
433
+ | E1 | 定版候选 vs 现有默认 | off / late / fl10 | 54 |
434
+
435
+ ### J. 定版结果:`finishLineSteps: 10`
436
+
437
+ E1,7 家族、每臂 14 对、隐藏测试全绿:
438
+
439
+ | 臂 | 墙钟中位 | 变快对 | 符号检验 | 输出 token | 步数 | 正确性 |
440
+ |---|---|---|---|---|---|---|
441
+ | `fl10`(late + 第 10 步收尾线) | **1.86×** | **13/14** | **p = 0.002** | **−52 %** | **−10.0** | 14/14 绿 |
442
+ | `late`(1.4.3 默认) | 1.31× | 11/14 | p = 0.057 | −28 % | −8.0 | 14/14 绿 |
443
+
444
+ 同轮次的直接配对(`fl10` ÷ `late`,14 对):中位 **1.27×**、11/14 更快(p = 0.057)、输出 **−22 %**、步数中位 **不变**。也就是说收尾线的增益主要来自**少写**(不再重跑已通过的检查),而不是少做步骤——这正是 1.4.3 延迟模型里 decode 占 58 % 的那一块。
445
+
446
+ 分桶(按 off 组步数):
447
+
448
+ | 桶 | 对数 | `fl10` 中位 | 变快 | 输出 |
449
+ |---|---|---|---|---|
450
+ | 1–5 步 | 1 | 0.66× | 0/1 | +53 % |
451
+ | 6–10 步 | 1 | 2.51× | 1/1 | −58 % |
452
+ | 11–15 步 | 4 | **1.86×** | 4/4 | −47 % |
453
+ | 16–99 步 | 6 | **1.87×** | 6/6 | −46 % |
454
+
455
+ ### K. 被否掉的三个候选
456
+
457
+ | 候选 | 证据 | 结论 |
458
+ |---|---|---|
459
+ | 硬步数预算 `stepBudget: 15`(b15) | C1 中位 2.04×(8/10)、D1 1.26×(10/14),14/14 与 10/10 全绿 | 速度不稳、且到线直接要求“停止并报告”,在 D1 的短任务桶掉到 0.43–0.77×。保留为可配,不作默认 |
460
+ | 更早的收尾线 `finishLineSteps: 8`(fl8) | C1 1.63×(9/10,p=0.021)、D1 1.50×(7/14) | 短任务桶 6–10 步 0.44×(输出 +165 %);第 8 步对 10 步左右的任务太早 |
461
+ | 两条一起 `combo`(8 + 15) | D1 1.14×(8/14),16+ 步 1.50×(6/8) | 叠加不增益,短任务更贵 |
462
+
463
+ 并行宽度与提示段归因来自 B1:`late-guid0`(关掉提示段)0.96×,`late` 1.26×——**提示段本身是载荷**,并行宽度(`parallel: 1`,1.10×)不是。
464
+
465
+ ### L. 这一版的边界
466
+
467
+ - 只有本地 `deepseek-v4.1-flash` 网关的数据;单轮方差 0.41×–4.76×,所以所有结论都按配对中位与符号检验给,不报单次最好成绩。
468
+ - 收尾线是**建议**,不是截断:模型可以继续做。它靠的是“别再重复验证”这句话在有界上下文里真的生效,证据是输出 token 中位 −52 %。
469
+ - 短任务仍无收益:1–5 步桶 0.66×(n=1,绝对值 17 s),4 步以内结束的一轮根本到不了第 10 步,代价为零。
470
+ - 单一家族曾测到 2.68×(ledger,6 对),那是该家族的乐观值;跨 7 家族的定版数字是 **1.86×**。
471
+ - 同一 `(任务, 臂, 对)` 有补跑时按各次运行的中位聚合再配对,避免挑最有利的一次;E1 的 54 行记录压成 42 个单元后 n 仍为 14。
472
+ - **12 对 vs 14 对**:下面分桶表只覆盖 14 对中落在四个步数桶里的 12 对,另 2 对不在桶内;E1 的原始记录未提交,无法从已提交数据重算那 14 对的比值,桶表不能当作 14 对的完整分解。
473
+ - **语料与复现边界**:272 次运行里包含 C1/D1 的调参轮(不是每条都进定版结论);7 个家族 fixture 与 runner 未公开发布(只在本机 `/tmp/fm-bench`),因此外部无法逐字复跑;全部数据来自本地 `deepseek-v4.1-flash` 网关,换路由后的绝对值不可假定。
474
+
475
+ ## 1.4.3:速度随步数增长——短任务 0 收益,长任务 1.49×(2026-10-24)
476
+
477
+ 这一轮回答“A 对短任务收尾是不是收益不够”。答案是够的,只是**收益随步数出现**:短任务里没有多少可省的东西,长任务里输出 token 与步数都会被压下来。
478
+
479
+ ### G. 步数分桶:1–5 步无收益,6–10 步 1.23×
480
+
481
+ 同一份 80 对 ABBA 语料(`results.jsonl`,外部墙钟 `elapsedSec`,全部运行 `exit 0`)按 off 组步数分桶:
482
+
483
+ | 桶 | 对数 | 中位加速 | p25–p75 | 变快对 |
484
+ |---|---|---|---|---|
485
+ | 1–5 步 | 47 | **1.00×** | 0.82–1.20 | 23/47 |
486
+ | 6–10 步 | 33 | **1.23×**(符号检验 p = 0.035) | 0.99–1.43 | 23/33 |
487
+ | 11 步以上 | 0 | — | — | — |
488
+
489
+ `corr(输出 token 变化, 加速) = −0.77`:快的那些对,都是模型写得少。输出 token 中位 831 → 756。这条规律解释了为什么“总体中位 1.06×”看起来弱——它把 47 对几乎没有可省空间的短任务平均进去了。
490
+
491
+ 同一份语料还证明**并发不是来源**:短任务上并行宽度早就够用,长任务上的加速与工具扇出无关。
492
+
493
+ ### H. 输出纪律 A/B:−15.4% token,正确性零损失,墙钟测不出来
494
+
495
+ 深 fixture(4 模块 / 20 测试)12 对 ABBA,24 次运行全部 `exit 0`、测试文件未改:
496
+
497
+ | 组 | 墙钟中位 | 步数 | 输出 token | 测试 |
498
+ |---|---|---|---|---|
499
+ | 关 | 29.9 s | 9.0 | 4558 | 96/96 |
500
+ | 开 | 25.2 s | 9.0 | 3858 | 96/96 |
501
+
502
+ token **−15.4%**,未达预设的 20% 门槛;配对中位 1.13×,但只有 7/12 对更快(p = 0.774),配对 token 变化中位 −14.5%、仅 7/12 对下降(最差一对 +47%)。单看这一项,**在 n=12 上与 ±30% 的任务内噪声分不开**。验收条件“token 降 ≥20% 或判负即结案”由此触发的结论是:不作为独立卖点,正确性零损失这一条成立。
503
+
504
+ ### I. 长任务 A/B:16.5 步 → 10.5 步,墙钟 1.49×,隐藏测试 22/22 全对
505
+
506
+ fixture 是“从零实现一个模块”——`ledger.py`,6 个函数,规范写清但边界很多(会计括号负数、货币符号、千位逗号、`Decimal` 精度、ROUND_HALF_UP、规则优先序、并列取 ASCII 最小、报表排版)。**隐藏测试**放在模型看不到的目录,跑完由 runner 复制进去执行,22 条断言;写不出来就是 0 分,等于把“正确性”做成可观测量而不是主观判断。8 对 ABBA、16 次运行:
507
+
508
+ | 组 | 墙钟中位 | 步数中位 | 输出 token 中位 | 隐藏测试 | 绿 |
509
+ |---|---|---|---|---|---|
510
+ | 关 | 215.2 s | **16.5** | 32 718 | 22.0/22 | 8/8 |
511
+ | 开 | 144.5 s | **10.5** | 24 274 | 22.0/22 | 8/8 |
512
+
513
+ - 墙钟 **1.49×**(配对中位 1.56×,均值 1.66×;5/8 对更快,p = 0.29——赢的三对赢得大,2.3–2.7×,输的三对只输 5–24%)。
514
+ - **步数 8/8 对全部下降**(符号检验 p = 0.008):这是最干净的一条,机制也直接——少写过程就少一步“先说明再动手”,少一次重复验证。
515
+ - 输出 token **−25.8%**(配对中位 −38.6%,5/8 对下降),这次越过了 20% 门槛。
516
+ - **正确性 16/16 次运行、22/22 全对,两个臂都一样**。这是本轮最重要的质量证据:省下来的是过程文字,不是实现质量。
517
+
518
+ 对照实验:上一版“12 个 bug 的修复型 fixture”只花 4–6 步就被模型一次改完,结果 **0.94×、输出 +11%**——它压根不是长任务,只是“短任务 + 更多文件”。它反过来印证 G 的分桶:步数不够,收益就是零甚至是负的。
519
+
520
+ ### J. 结论与产品含义
521
+
522
+ - 可辩护的说法:**6 步以上约 1.2×;真长任务(16 步量级)1.49×,步数 −36%,输出 −26%,正确性不变;≤5 步为 0**。
523
+ - 因为收益随步数出现,**“短任务不介入、长任务自动介入”有实测依据**,`outputDiscipline: 'late'` 由此实现:系统提示里不放文风规则(短任务零成本),第 4 步由调度器注入一次压缩提醒(`src/auto.js` 的 `compress-output` 指令,每轮一次)。
524
+
525
+ ### J. 迟到介入 A/B:长任务 2.68×,正确性同样零损失
526
+
527
+ 同一 fixture、同一 runner,6 对 ABBA(12 次运行),比较 `off` 与 `outputDiscipline: 'late'`:
528
+
529
+ | 组 | 墙钟中位 | 步数中位 | 输出 token 中位 | 隐藏测试 | 绿 |
530
+ |---|---|---|---|---|---|
531
+ | 关 | 184.2 s | 10.5 | 30 178 | 22.0/22 | 6/6 |
532
+ | late | **68.6 s** | **7.5** | **14 480** | 22.0/22 | 6/6 |
533
+
534
+ - 墙钟 **2.68×**(配对中位 2.82×;4/6 对更快,输的两对 0.77×/0.79×)。
535
+ - 步数 5/6 对下降,中位 −3 步;输出 token **−52.0%**(配对中位 −59.6%,4/6 对下降)。
536
+ - 正确性仍是 12/12 次运行、22/22 全对。n=6 是 pilot,只能当方向;但它与上一节 8 对的 1.49× 同向,且“全程纪律”那一版在同一 fixture 上是 1.49×——迟到介入没有变慢。
537
+ - **短任务对照**(上一版 4–6 步 fixture,6 对):late 13.5 s vs 关 17.6 s(中位 1.31×,配对中位 1.26×,4/6 对更快,步数不变)。这条落在 ±30% 的任务内噪声里,只能说“短任务没有付出代价、也没赚到”,与用户设想的“短任务不介入”一致;全程纪律版在同一 fixture 上是 0.94×,两者都在噪声内、方向相反,正好说明这个 fixture 量不出东西。
538
+ - 因此**默认值改为 `late`**:短任务不付提示税,长任务在第 4 步拿到同一句压缩指令。
539
+
540
+ ## 1.4.2:把“并发”量出来——子代理是真并行,shell 扇出工具是负收益(2026-10-24)
541
+
542
+ 这一轮只问两个问题:harness 层面到底还有多少并发没被用上?我们为此加的提示段和工具,究竟赚还是亏?
543
+
544
+ ### A. 子代理并发:4 路,跨度 21 秒(串行应 80 秒)
545
+
546
+ 四个真实子代理,每个只做 `sleep 20`,各自把开始/结束时间戳追加到同一个日志:
547
+
548
+ | 探针 | 开始 | 结束 |
549
+ |---|---|---|
550
+ | agent1 | +0 s | +20 s |
551
+ | agent2 | +0 s | +21 s |
552
+ | agent3 | +1 s | +21 s |
553
+ | agent4 | +0 s | +20 s |
554
+
555
+ 整段跨度 **21 s**,串行基线 80 s → **约 3.8×**。四个子代理的启动在 1 秒内完成,说明限制不在调度器,而在**模型是否愿意把工作拆给子代理**。这条与早前 k=4 的并发测量(4 个相同请求并发 6.5 s vs 串行 23.2 s = 3.6× 吞吐,单步 llm 只涨 14%)互相独立、结论一致。
556
+
557
+ ### B. `shell_parallel` 工具:工具本身有效,产品化是负收益(3 对 ABBA)
558
+
559
+ fixture 是 6 个互不依赖的作业,每个 `sleep 5`(`tp`),同题三对、顺序轮转、六次运行全部 `exit 0`:
560
+
561
+ | 组 | 墙钟 | 步数 | 工具内时间 |
562
+ |---|---|---|---|
563
+ | on p1 | 26.3 s | 7 | 15.3 s |
564
+ | off p1 | 16.0 s | 6 | 5.2 s |
565
+ | on p2 | 54.5 s | 7 | 45.5 s |
566
+ | off p2 | 8.9 s | 3 | 5.1 s |
567
+ | on p3 | 15.2 s | 6 | 5.2 s |
568
+ | off p3 | 9.1 s | 3 | 5.1 s |
569
+ | **中位** | **26.3 s** | 7 | 15.3 s |
570
+ | **中位** | **9.1 s** | 3 | 5.1 s |
571
+
572
+ **0.35×(on / off)**。会话日志给出了确切机制,两条都不是“工具写错了”:
573
+
574
+ 1. **工具确实是并发的**:6 个 5 秒作业在 5.1 秒内跑完(`tool/call` +1.4 s → `tool/result` +6.5 s)。
575
+ 2. **基线本来就会并行**:off 组一次 `bash` 里就写了 `for d in job-0*; do (cd $d && python3 slow.py) & done; wait`,模型自己把六个作业并行起来了。
576
+ 3. **多出来的工具诱发复核**:on 组先跑 `shell_parallel`,再 `cat slow.py`、再 `date; ls done.txt`、再跑第二遍;其中一次落进串行 `for` 循环验证(+22.1 s → +52.4 s,正好 30 s)。六次工具调用、七步,墙钟翻倍还多。
577
+
578
+ 结论:**默认关闭**(`shellParallel: false`),提示段也只在工具真的注册时才提它的名字(否则模型会去调一个不存在的工具)。需要时一个开关打开。
579
+
580
+ ### C. 一次把自己证伪的三臂实验(诚实记录)
581
+
582
+ 我本想用 `guidance: false` 做“有提示段 / 无提示段 / 关插件”三臂对照,跑了 3 任务 × 6 对 = 54 次(全部 `exit 0`)。结果出来后复核代码才发现:**`resolveConfig` 没有把 `guidance` 透传出去**,三臂里其实有两臂配置完全相同。
583
+
584
+ | 任务 | off | 名义 notext | 名义 text |
585
+ |---|---|---|---|
586
+ | t2 | 5.6 s | 5.5 s | 5.6 s |
587
+ | t3 | 3.9 s | 7.5 s | 5.2 s |
588
+ | tp | 9.7 s | 17.6 s | 16.0 s |
589
+ | **总体中位** | **5.6 s** | **7.5 s** | **5.6 s** |
590
+
591
+ 于是这组数据不能当 A/B 用,但它**变成了一次噪声标定**:两个完全相同的配置,在每任务 6 对的样本下给出了 **0.75× 和 1.01×** 两种“效应”。也就是说,本套小任务上 n=6 的每任务中位数**噪声达 ±30%**——任何小于这个幅度的单任务差异都不该被当成结论。我此前“notext 1.01× / text 0.88×”的说法**作废**(`guidance` 开关当时并未生效)。
592
+
593
+ ### D. 子代理扇出做真实工作:小任务上是 0.25×(2 对 pilot)
594
+
595
+ A 证明了“并发原语能用”,但没用它做任何事。这一组把真实工作交给子代理:fixture 有 4 个模块,每个模块 120 行 `key=value`,规则写在各自 `NOTES.md`(能被 7 整除的 key 是 hot),要求算 hot 条数、最大 cold 值并汇总成表。两臂都用 `off`,唯一差别是提示里是否明确要求“为每个模块开一个子代理并行分析”。两对 ABBA、四次运行全部 `exit 0`:
596
+
597
+ | 臂 | 墙钟 | 步数 | sum(llm) | sum(tool) | 输出 token |
598
+ |---|---|---|---|---|---|
599
+ | 自己串行做 p1 | 10.9 s | 6 | 9.9 s | 0.2 s | 1798 |
600
+ | 子代理扇出 p1 | 49.4 s | 27 | 54.2 s | 30.8 s | 9130 |
601
+ | 子代理扇出 p2 | 23.2 s | 30 | 66.0 s | 0.7 s | 10054 |
602
+ | 自己串行做 p2 | 7.4 s | 5 | 6.4 s | 0.1 s | 1348 |
603
+ | **中位,串行** | **9.2 s** | 5.5 | 8.2 s | 0.2 s | 1573 |
604
+ | **中位,扇出** | **36.3 s** | 28.5 | 60.1 s | 0.7 s | 9592 |
605
+
606
+ **0.25×(扇出 / 串行)**,而且不是调度器的问题:`sum(llm)` 60 s 是**四个子代理各自的解码之和**,它们确实在并行跑(单次跨度只有 23–49 s);真正的代价是
607
+
608
+ 1. 每个子代理都要付一次完整启动 + 系统提示 + 至少 4–5 步(读 data、读 notes、算、写报告),4 个孩子就是 4 份固定成本;
609
+ 2. 子代理的报告天生啰嗦:输出 token 从 ~1.5 K 涨到 ~9.6 K,**6 倍**;
610
+ 3. 父会话还要额外花步数去派发与等待(步数 5.5 → 28.5)。
611
+
612
+ **这说明扇出的盈亏线不在“并发度”,在“子任务本身的深度”。** 每个子任务只有一两千 token 级别的活时,拆出去必亏;只有当单个子任务本身就要跑十几步、几千 token 时,扇出才可能赢。真实任务大多落在前者。
613
+
614
+ ### E. 深子任务扇出:仍然是 0.60×(2 对 pilot,含会话取证)
615
+
616
+ 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`,差别只在提示是否要求“每模块各开一个子代理”。
617
+
618
+ | 臂 | 墙钟 | 步数 | sum(llm) | 输出 token | 正确性 |
619
+ |---|---|---|---|---|---|
620
+ | 串行 p1 | 20.4 s | 11 | 18.8 s | 3253 | **4/4** |
621
+ | 扇出 p1 | 45.8 s | 42 | 93.0 s | 13817 | **4/4** |
622
+ | 扇出 p2 | 50.7 s | 44 | 127.5 s | 20004 | **0/4** |
623
+ | 串行 p2 | 37.3 s | 14 | 35.6 s | 6931 | **4/4** |
624
+ | **中位,串行** | **28.8 s** | 12.5 | 27.2 s | 5092 | 4/4、4/4 |
625
+ | **中位,扇出** | **48.2 s** | 43 | 110.3 s | 16910 | 4/4、0/4 |
626
+
627
+ **0.60×(扇出 / 串行),而且一次还做错了。** 会话取证说明问题不在“有没有并行”:
628
+
629
+ - 扇出臂确实开了 **4 个子会话**,四个 `createdAt` 相差 **3 毫秒**——派发是**同一瞬间**发生的,调度器没有排队;
630
+ - 但每个孩子耗了 **44–81 个事件(约 15–25 步)** 才算完一个模块,而串行臂用 **11–14 步** 就把四个模块全做完了;
631
+ - 输出 token 5.1 K → 16.9 K(3–4 倍),`sum(llm)` 27 s → 110 s(四个孩子各自的解码之和);
632
+ - 父会话在派发后还要读四份报告、写汇总,步数 12.5 → 43。
633
+
634
+ **换句话说:把活切成四份之后,每一份的代价几乎和整份一样大。** 子代理的启动、系统提示、以及“自己重新摸清这个模块”的探索成本,与切片大小基本无关;切片越小,摊销得越差。这是子代理架构的固定成本,不是插件能改的。
635
+
636
+ 结论:**扇出在这套 harness 上已经两次被证伪**——浅子任务 0.25×(D),深子任务 0.60× 且有一次结果错误(E)。并发原语是真的(A:4 路 21 s;E:4 个孩子 3 ms 内派出),但“把活交给更多 agent”不是提速手段,反而放大 token 与出错面。
637
+
638
+ ### F. “重复调用就短路”这条机制:先做人群统计,然后否掉(1198 个会话日志)
639
+
640
+ 思路本来是纯程序化的、不碰提示词:如果某一步又调了**同一个工具、同样的参数**,就把上一步的结果直接回灌,省掉一次执行。在动手之前先数了一下它到底能命中多少——把仓库里所有 bench 会话日志(**560 个会话目录、1198 个日志、7168 次工具调用**)全扫了一遍:
641
+
642
+ | 判重口径 | 命中率 | 命中的工具 |
643
+ |---|---|---|
644
+ | 严格(参数逐字节相同) | **10 / 7168 = 0.1%** | `shell_parallel` 8 次(就是这个已经被关掉的工具)、`list_agents` 2 次 |
645
+ | 宽松(去 `cd … &&`、压空白、去 `\| head`) | **314 / 7168 = 4.4%** | `bash` 300、`grep` 4、`shell_parallel` 8 |
646
+
647
+ 而且命中是**极度集中**的:4.4% 里绝大部分来自 74 个会话(占 1198 的 **6%**),典型形态是「同一形状的复核命令连发 5 次」。抽查最集中的那个会话(t3-on-p2),七次调用里六次是:
648
+
649
+ ```
650
+ 1..6 bash | python3 gen.py ← 同一个生成脚本,连跑六遍
651
+ 7 write
652
+ ```
653
+
654
+ 三条否掉理由:
655
+
656
+ 1. **量太小**:严格口径 0.1%,宽松口径 4.4%,且集中在 6% 的会话里;对总体墙钟的期望收益是毫秒级——这些重复是 `ls`/`cat`/`date`/`python3 gen.py` 这类**便宜调用**,不是重跑 5 秒作业那种贵调用。
657
+ 2. **省的不是那 0.5 秒**:短路能省的是**工具执行时间**,省不掉**那一步的 LLM 开销**(模型仍然要生成这次调用、仍然要等一次 TTFT)。而整套测量的结论恰恰是每步 `TTFT ≈ 0.5 s` 是主要固定成本——这一步该由既有的步数预算/停滞看门狗去砍,不是由结果缓存去砍。
658
+ 3. **会引入正确性风险**:命中最多的 `python3 gen.py` 是**有状态**的命令(重新生成数据文件)。把它的第二次调用短路成“上次的结果”,等于把一个会改变现场的命令当成纯读——这是会静默写坏状态的优化。
659
+
660
+ 结论:**不做**。同时说明一件事——`shell_parallel` 那个工具的 8 次重复,是它自己诱发出来的,这也是它被关掉的第三个理由。
661
+
662
+ ### 这一轮的净结论
663
+
664
+ - 唯一有量级的杠杆是**并发**:子代理 3.8×、k=4 吞吐 3.6×,都不需要改提示词。
665
+ - 提示段和 `shell_parallel` 都没有带来可测收益;后者在本 fixture 上稳定更慢,已改默认关闭。
666
+ - 小任务上的 `1.06×`(76 对)仍是仓库里唯一的大样本结论,且它落在 CI 下沿——按上面的噪声标定,它应当被读作“几个百分点”,不是“快一截”。
667
+ - **扇出到子代理已经两次证伪**:浅子任务 **0.25×**(D),深子任务 **0.60×** 且两次里有一次算错(E)。取证显示四个孩子在 3 毫秒内同时派出——并行是真的,亏在“切片后每一片的固定成本几乎和整片一样大”。这条线到此为止。
668
+ - “重复调用短路”也被否掉(F:严格 0.1%、宽松 4.4%,且都是便宜调用与有状态命令)——它省的是执行时间,省不掉那一步 0.5 s 的 TTFT。
669
+ - 时间构成的实测比例是:**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 过),而不是并行、不是缓存。
670
+ - 因此不再做 `fanout` 工具,也不在提示里推子代理。插件能碰的只剩“每步少一次派发、每步少解码一点”,而上界由 76 对样本给出:**中位 1.06×**。产品的诚实定位是“小幅提速 + 卡死看门狗 + 每步计量”,不是倍数级加速。
671
+
672
+ ## 1.4.1 修正:扩大样本后,1.4.0 的 1.86× 被证伪(76 对)
673
+
674
+ 1.4.0 发布时的每任务数字只有 5 对,我把 t1/t3 扩到 15 对(另加两个新形状:t9 写扇出、t10 读写混合,各 8 对)之后,结论变了:
675
+
676
+ - t1:5 对时 1.86× → **15 对时 0.85×**(升级后再测,判为“逐项输出”走原生,回到 **1.02×**)。原因是“一次抓全”把八次读并成一步,但那一步要一次解码整张 per-file 表,输出 token 从 1069 涨到 1197,比省下的轮次贵。
677
+ - t3:5 对时 1.77× → **15 对时 1.03×**;步数确实从 4.7 降到 3.5、`llmMs` 从 4578 降到 3752,但墙钟只有 ~8% 的改善,中位数被几个慢跑抵消。
678
+ - t9(6 模块独立改写,写扇出):**0.98×(8 对)**。宽块里写六个独立文件是安全的,但没有变快——模型本来就会在一次里写多个。
679
+ - t10(10 日志聚合报告,读写混合):**1.11×(8 对)**,方向为正但不显著。
680
+
681
+ 合并 76 对:**中位 1.06×,CI 0.996–1.155×,45/76 对变快,均值 1.15×**。也就是说,在本套任务和这条网关上,fast mode 的真实收益是“几个百分点 + 更大的方差”,不是“明显快一截”。
682
+
683
+ 为什么:一步的墙钟 ≈ `720 ms + 6.6 ms × 输出 token + 预填充`;把 N 步压成 1 步,省下 N×0.7 s 的固定开销,却常常让那一步多解码几百 token(≈2 s)。只有“步数下降且单步输出不涨”的形状能净赚,t3 的 shell 扇出是唯一稳定的例子。
684
+
685
+ 1.4.1 因此只留一个行为改动:**逐项输出形状(`one object keyed by file`)判原生**,避免 0.85× 的回归;同时保留 shell 扇出与聚合命令。
686
+
687
+ ## 1.4.0:形状分类 + 一次抓全 + 聚合命令(43 对,2026-10-24)
688
+
689
+ 三项调度改动,全部是程序化并行,不改请求体、不改 `reasoningEffort`:
690
+
691
+ 1. **请求形状分类**(1.3.0 起):串行/shell/聚合/扇出,第一步就决定宽窄,不再有新会话 4 宽探测。
692
+ 2. **一次抓全**:已知的目标集合必须在第一个块里抓完,不许为决定读什么而先读一个。
693
+ 3. **聚合命令**:目标超过约十个或是求和/计数/求交集时,用一条 `rg -c` / `grep -r` / 短脚本,而不是把每个文件都读进上下文;shell 扇出(每目录一个作业)也允许同一块。
694
+
695
+ 口径同前:真实 headless 启动,`company/deepseek-v4.1-flash`,`reasoningEffort: medium`,ABBA 配对,每对同一任务同一 fixture 的两份独立副本;质量 120/120 全部 PASS;表中墙钟是各臂均值。
696
+
697
+ | 任务(形状) | 墙钟:关 → 开 | 加速比 | 95 % CI |
698
+ |---|---|---|---|
699
+ | t1:8 文件抽摘要 | 14.9 s → 8.8 s | 1.86× | 0.86–3.77 |
700
+ | t2:40 个独立文件计数 | 5.8 s → 6.0 s | 0.98× | 0.82–1.34 |
701
+ | t3:6 目录逐个求和(shell 为主) | 8.5 s → 6.6 s | 1.77× | 0.41–2.19 |
702
+ | t4:12 模块审计报告 | 9.1 s → 11.0 s | 1.03× | 0.55–1.19 |
703
+ | t5:12 模块 × 5 文件查热符号 | 6.8 s → 7.6 s | 1.07× | 0.43–1.35 |
704
+ | t6:24 个日志文件按级别求和 | 8.7 s → 5.6 s | 1.43× | 1.00–2.57 |
705
+ | t7:18 节点依赖链(必须串行) | 4.4 s → 4.3 s | 1.06× | 0.76–1.30 |
706
+ | t8:16 个 manifest 找重复依赖 | 6.9 s → 5.4 s | 1.29× | 0.98–1.61 |
707
+
708
+ - 43 对合并:**中位加速比 1.09×(10 000 次 bootstrap CI 1.03–1.33×)**,30/43 对变快,均值 1.26×;平均步数 5.5 → 4.7。
709
+ - CI 首次不跨 1,但每任务只有 5–8 对,单任务数字只能看方向;t1/t3 的 1.8× 来自“抓全 + shell 扇出”,t2/t4/t7 基本是设计的无操作对照组(聚合、串行、必须串行)。
710
+ - 输出 token 均值反而略降(884 → 767/次运行),因为输出纪律同时生效;输入 token 上升,是本版有意用 token 换轮次。
711
+
712
+ ## 1.3.0 pilot:请求形状分类(32 对)
713
+
714
+ `src/shape.js` 从用户消息本身判断形状:串行词(chain / one by one / 依次 / 逐个)或 shell 词(bash / python3 / 执行)→ 原生;扇出词(independently / every file / across / per file / 每个 / 所有)或 ≥4 个指名目标 → 8;其余原生。新会话不再有 4 宽探测。
715
+
716
+ - 8 任务 × 4 对 ABBA = 64 次运行;质量 63/64(唯一失败在**对照组** t8,与插件无关)。
717
+ - 32 对合并:**中位加速比 1.08×(10 000 次 bootstrap CI 0.88–1.21×),18/32 对变快**,均值 1.11×。
718
+ - 分任务(各 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×。
719
+ - 与 1.2.1 的 80 对相比,点估计 0.96× → 1.08×,t1/t4 不再倒挂;但 CI 仍跨 1,**不构成显著结论**,只是从“有代价”变成“看不出代价、方向为正”。
720
+
721
+ ## 1.2.1:8 任务 80 次运行(相对值,2026-10-24)
722
+
723
+ 这是当前版本唯一权威的一组数据:**8 个多文件任务 × 5 对 ABBA 配对 = 80 次真实 headless 运行**,同 provider(`company/deepseek-v4.1-flash`)、同路由、同 `reasoningEffort: medium`,两臂只差 `fast-mode.enabled`。每次运行使用全新工作目录,确定性 `verify.py` 判定,**80/80 全部 PASS**(每任务两臂各 5/5)。
724
+
725
+ 口径(和 README 一致,务必按这个读):
726
+
727
+ - **墙钟**:进程启动到 headless 回合结束的端到端秒数,含模型延迟、工具等待与插件开销。
728
+ - **加速比** = `墙钟(关) / 墙钟(开)`,大于 1 才是变快;1.28× 即省 22 % 时间。95 % CI 用 10 000 次配对 bootstrap 得到,跨过 1 表示与关闭不可区分。
729
+ - 只报相对值;`ms = 720 + 6.63 × 输出tok` 那类绝对模型是 1.0.0 之前的数据,已不作为本版依据。
730
+
731
+ | 任务(形状) | 对数 | 质量 | 墙钟 关 → 开 | 加速比 | 95 % CI | 输出 token 比 |
732
+ |---|---|---|---|---|---|---|
733
+ | t1 8 文件抽摘要 | 5 | 5/5 + 5/5 | 8.6 s → 11.3 s | 0.78× | 0.43–1.30 | 0.96 |
734
+ | t2 40 独立文件计数 | 5 | 5/5 + 5/5 | 7.2 s → 6.4 s | 1.14× | 0.93–1.24 | 0.90 |
735
+ | t3 6 目录逐个求和 | 5 | 5/5 + 5/5 | 5.0 s → 6.5 s | 0.81× | 0.53–1.20 | 1.13 |
736
+ | t4 12 模块审计 | 5 | 5/5 + 5/5 | 8.7 s → 13.1 s | 0.71× | 0.55–1.21 | 1.43 |
737
+ | t5 12 模块 × 5 文件热符号 | 5 | 5/5 + 5/5 | 13.9 s → 12.2 s | 1.14× | 0.90–1.82 | 0.81 |
738
+ | t6 24 日志文件求和 | 5 | 5/5 + 5/5 | 5.7 s → 5.2 s | 1.28× | 0.68–1.59 | 0.87 |
739
+ | t7 18 节点依赖链 | 5 | 5/5 + 5/5 | 4.6 s → 5.6 s | 0.98× | 0.38–1.51 | 1.02 |
740
+ | t8 16 manifest 找重复依赖 | 5 | 5/5 + 5/5 | 6.8 s → 6.0 s | 1.04× | 0.68–1.27 | 0.90 |
741
+
742
+ 合并:**中位加速比 0.96×(CI 0.80–1.14×),19/40 对变快。**
743
+
744
+ 怎么读:
745
+
746
+ - **收益集中在"大量互不依赖的读"**:t2/t5/t6 为 1.14×–1.28×,t5 的步数比 0.62、输入 token 比 0.63。
747
+ - **摘要/审计形状会变慢**(t1/t4),说明第一步那 4 宽探测在不需要扇出的任务上仍是净成本;这是本版已知的代价,不是噪声。
748
+ - **单轮任务的先天限制**:形状判据来自已经发生的调用,而 8 个任务都在一轮内结束,只能吃到新会话那一次探测。多轮会话的第二轮会继承第一轮的形状,单轮任务没有这个机会。把宽度选择改成读请求入口形状 + 在线 bandit,是下一步。
749
+
750
+ 这份文件回答一个问题,只回答这个问题:**开了这个插件,请求真的更快了吗?**
751
+
752
+ 先把版本交代清楚,因为这份文件的大部分内容是历史:
753
+
754
+ - 实验 A、B、E、F 全部跑在 **1.0.0 之前**:E 和 F 在 0.8.0 上,A 和 B 更早。那时候每个开启的档位除了「一步多宽」,还带一套从第二步开始逐步下调 `reasoning_effort` 的阶梯。
755
+ - 1.0.0 把那套阶梯**整条删了**:`agent/request` 现在只是 await 之后原样返回请求,快速模式**永不改变思考强度**。所以下面凡是「档位更高所以更快」的表格,描述的都是一个已经不存在的东西。
756
+ - 保留它们的理由:它们正好解释了那条阶梯为什么该删(实验 B、F 直接测出降档没有用),以及 1.0.0 还剩下什么、还缺哪一块证据。
757
+
758
+ 1.0.0 能诚实说的只有一句话:**同样的思考强度,任务允许的时候更少往返。** 没有百分比。
759
+
760
+ - 已知的:每步的耗时账记得下来,而且几乎全由输出长度决定(`ms = 720 + 6.63 × 输出tok`,R² = 0.93)。这台路由**不产出思考 token**,所以减思考不是杠杆。
761
+ - 历史证据:0.8.0 的 `turbo` 臂(扇出 + 预读 + 输出纪律 + 降档阶梯)在 16 个同任务会话上比 `safe` 快 40 % —— 但那是四样东西的合力,其中降档那一条在 1.0.0 已经不存在了(实验 E)。
762
+ - 已知测不出的:干净的单文件任务上,各档位是控制组的 0.95×–1.09×,在噪声内(实验 F)。这不是插件坏了,是那类任务没有「几个互相独立的查询」可以并行。
763
+ - **未测**:1.0.0 现在这套(自适应扇出 2–8 + 卡顿取消 + 循环中断)**还没有在 1.0 规模上做过 A/B**。
764
+
765
+ ## 结论速览
766
+
767
+ | 结论 | 证据 | 强度 |
768
+ | --- | --- | --- |
769
+ | 单步耗时几乎完全由**输出 token 数**决定 | `ms = 720 + 6.63 × 输出tok`,R² = 0.93,n = 689 | 强 |
770
+ | 这条路由**不产出思考 token** | 750 行拟合数据里 `reasoning` 全为 0;同一份实验日志的 1,888 步同样全为 0 | 强 |
771
+ | 因此降 `reasoning_effort` 不可能省下解码时间 | 四个档位 ms/token 9.0–10.6;按输出长度回归后,残差都在 ±350 ms 内且符号互相矛盾(实验 B、F) | 强 |
772
+ | 档位越高、一步里的并行调用越多(**0.8.0 数据**) | 多调用步占比:关闭 2% → `turbo`/`max` 22–23% | 中 |
773
+ | 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) |
774
+ | 1.0.0 剩下的整包(扇出 + 卡顿取消 + 循环中断)快多少 | 没有测过 | **未测** |
775
+ | 公开单文件题上,各档位落在控制组的 0.95×–1.09× | HumanEval 100 会话 + AIME 39 会话(0.8.0) | 强(只证明「该形状测不出差异」) |
776
+ | 1.5.1 默认臂整任务墙钟 **0.72–0.74×**,输出 **0.76×**、步数 **0.59×**,有效单元零红 | 1.5.1 重基线 + W3 + W5 + W7(65 个有效单元合并 0.742×),W8b 逐格中位 0.717× | 强(四块同向,门禁全绿) |
777
+ | 墙钟 ≈ 0.97 × 输出 token(bench 实测斜率 13.5 ms/token,步数项 6 %) | W8b `off` 臂 19 格回归,逐格残差中位 18 % | 强 |
778
+ | 输出里 23 % 是工具调用参数,而这部分基本是**在建交付物**(整文件重写的重复量只有 0.8 %) | 134 个 `def15` 格参数占比统计 + 15 会话 153 次 `write` 逐路径比对 | 强 |
779
+ | 还剩的杠杆是**重复验证步**(12.2 % 的调用是跑测试类命令,25 个会话里出现 ≥3 次) | 145 个 `def15` 会话的调用分类 | 中(尚未有干净的臂测过 `batchVerify`) |
780
+ | 0.67× 需要推理再降 15 %(等价于步数 0.585× → 约 0.50×) | W8b-C 的算术:参数项压不动,只能压推理 | 强(算术),达成方式**未验证** |
781
+
782
+ 一句话:**在这台自建网关的 `company/deepseek-v4.1-flash` 上,拧 `reasoning_effort` 不会让生成变快,因为这条路由根本不思考;1.5.1 的默认臂已经把整任务墙钟压到 0.72–0.74×,而墙钟几乎等于输出 token、输出里 23 % 是必须写出来的交付物,所以不碰推理档位的前提下,剩下的空间只够再挤「重复验证的步数」。**
783
+
784
+ ## 方法
785
+
786
+ - **比较单位是「步」,不是会话。** 会话墙钟里大部分是人在读和打字(本机 12 轮里,生成只占 59%),拿会话时长比快慢等于在测打字速度。
787
+ - 每步记一行 JSONL:`llmMs`(`step/start` → 最后一条 `assistant/message`)、`stepMs`、`output`/`input`/`cacheRead`/`reasoning`、`calls`、`effort`、`mode`/`gear`、`route`。
788
+ - **分档不靠手点**:实验文件按 `FNV-1a(会话 id) mod 档位数` 分配,同一个会话每次进来都落在同一档;`off` 是合法的对照组。手点分档会让"我觉得这档快"悄悄决定结果。
789
+ - 关闭状态的会话也照常记录,否则没有对照组。
790
+ - **来源**:A、B、E、F 四组实验都在 1.0.0 之前跑。当时开启的档位是「宽度 + 输出纪律 + 逐步降 effort」的整包,所以表格里写的「档位」是这个整包,不是单一变量。1.0.0 的档位只剩宽度,没有 effort 这一层。
791
+
792
+ ## 实验 A:被自己的工具结果污染了
793
+
794
+ 六个会话、24 步、同一个任务。其中一个收到了 **33,763 字符**的工具结果,触发 `bigChars`,把 `signals.struggling` 置上,于是后面几步的 effort 被钉住。两组不可比,实验作废。
795
+
796
+ 它暴露的是当时的真实缺陷:一次 `git diff` 就能让降档卡住六步。1.0.0 里这条路径连同整个 effort 策略一起删了(`bigChars` / `struggling` 在代码里已经不存在),所以这个 bug 不再存在。留在这里是因为它说明「用一个观察信号直接决定策略」有多脆 —— 1.0.0 的调度器仍然读会话观察,但改的是宽度和上限,不是用户选的思考强度。
797
+
798
+ ## 实验 B:控制住变量之后
799
+
800
+ 八个独立会话,同一提示词、同一模型、同一台机器、同一分钟启动,每个三步,每步以 `BENCH-DONE` 结束。四个臂:`off` ×3、`safe` ×3、`turbo` ×1、`max` ×1 —— 共 24 步。
801
+
802
+ | 档位 | 步数 | effort(第 1→3 步) | LLM p50 | 输出 tok/步 | **ms/token** |
803
+ | --- | --- | --- | --- | --- | --- |
804
+ | `off`(对照) | 9 | `medium` / `medium` / `medium` | 1978 ms | 247 | 9.08 |
805
+ | `safe` | 9 | `medium` / `low` / `low` | 1974 ms | 227 | 9.11 |
806
+ | `turbo` | 3 | `medium` / `low` / `low` | 2262 ms | 254 | 9.01 |
807
+ | `max` | 3 | `medium` / `low` / `low` | 2216 ms | 220 | 9.47 |
808
+
809
+ 插件确实降了档 —— `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`、却一分钟都没省下来的机制,正确的处理是移除,而不是再调低一档。
810
+
811
+ ## 实验 E:0.8.0 的三个杠杆真的更快
812
+
813
+ > **先说边界:这是 0.8.0 的整包结果,不是 1.0.0 的预测。** 治疗臂 `turbo` = 扇出目标 6 + 投机预读 + 完整输出纪律 **+ 逐步降 effort 的阶梯**。−40 % 是这四样东西的合力,实验 E 没有把它们拆开;其中 effort 那一条在 1.0.0 已经不存在了。
814
+
815
+ 实验 A 和 B 只证明了 effort 不是杠杆。实验 E 检验的是 0.8.0 真正换上的东西:**更宽的步(扇出 + 预读)、更少的步(少往返)、更短的步(输出纪律)**。
816
+
817
+ - **任务有可核对的答案**:读一个仓库,报四件事 —— ① `test/` 下每个文件的 `test(` 数,② `src/` 下每个文件的行数,③ `package.json` 的版本 / 导出键数 / 依赖,④ `git log -3` 和 `git status --porcelain`。
818
+ - **16 个独立会话,两波各八个**,同一路由(`company/deepseek-v4.1-flash`)、同一提示词、同时启动。分档同样按 `FNV-1a(会话 id) mod 2` 分配 —— 没有人手点。
819
+ - 对照 `safe`(不注入 guidance,即插件 0.8.0 之前的行为);治疗 `turbo`(扇出目标 6 + 投机预读 + 完整输出纪律 + 降档阶梯)。
820
+
821
+ **结果(每会话 Σ step time = 该会话的 `stepMs` 之和)**
822
+
823
+ | 指标 | `safe` (n=4) | `turbo` (n=12) | 变化 |
824
+ | --- | --- | --- | --- |
825
+ | Σ步时,中位 | 25.9 s | **15.6 s** | **−40 %** |
826
+ | Σ步时,均值 | 25.7 s | 17.4 s | −33 % |
827
+ | 步数,中位 | 7.5 | **4.5** | **−40 %** |
828
+ | 输出 token/会话,中位 | 3,798 | **2,180** | −43 % |
829
+ | 输出 token/步 | 510 | 535 | +5 %(一步里干更多活,这是预期的) |
830
+ | 工具调用/步 | 1.41 | 1.37 | 持平 |
831
+ | Mann–Whitney U(Σ步时) | — | — | U = 46, z = 2.67, **p = 0.008** |
832
+
833
+ > `输出 token/会话` 那一行是整包的副产物(步少了,总量自然少),**不是 1.0.0 的目标**;1.0.0 明确接受更多 token,见后面「1.0.0 改了什么」。
834
+
835
+ 两波方向一致:第一波 −32 %,第二波 −40 %。单会话在 3–8 步之间,所以这个差不是某一个离群值拖出来的中位数。
836
+
837
+ **质量没掉。** 16 个会话报的 ①②③ 完全一致(唯一看着不一致的 175 vs 173,是字面 `test(` 数和真实用例数的差别,两个都对)。④ 的差异只是我一边跑一边在改 README,脏文件数从 7 变成 8。这是一个确定性的事实题,十个数字全对上了。
838
+
839
+ **诚实的读法**
840
+
841
+ - **扇出的直接证据反而没有出现**:calls/step 1.41 → 1.37,没升。这 40 % 不是「每步更宽」挣来的,而是「步更少」(7.5 → 4.5)和「每步更短」。任务只有四个查找,对照臂本来就已经一步两个调用了,没有多少可并的余地。实验 E 也没有单独测「只开扇出、不降 effort」那一臂,所以 −40 % **既不能记在扇出头上,也不能记在降档上**(B 和 F 都说降档是 0)—— 它是一个拆不开的整包。
842
+ - 两臂人数不均(4 vs 12),是 `FNV-1a` 哈希分档的运气问题,不是挑选。n 也小(16 个会话、29 vs 54 步)。
843
+ - 实验期间有 1 个**非实验**会话在同时跑(07:21–07:37),两臂平均受它干扰,但它不进统计。
844
+ - `turbo` 的并行目标是内置的 6。1.0.0 的 `auto` 改成 2–8 自适应、默认目标 4;宽度本身没有单独做过对照。
845
+
846
+ ## 实验 F:公开 benchmark 上的对照(HumanEval 100 会话,AIME 2025 39 会话)
847
+
848
+ 实验 E 只有 16 个会话,而且是一个仓库任务。为了回答"换个任务还成不成立",又跑了两组公认的公开题。**结论先写在前面:这两个 benchmark 在结构上就测不出 fan-out,所以它们能证伪的东西很少 —— 但它们证伪了"档位普遍更快"这个说法。**
849
+
850
+ **来源**:这轮同样跑在 0.8.0 上,每个开启的臂都带着降 effort 的阶梯。那条阶梯解释不了任何差异(每一行 `reasoning` 都是 0),但下面几张表里的「档位」仍然是整包,数字是历史,不是 1.0.0 的预测。
851
+
852
+ ### 设计
853
+
854
+ - **任务**:HumanEval 前 10 题(题面全文给出,实现写进各自目录的 `solution.py`,用官方 `test` 本地跑通);AIME 2025 前 10 题(最终整数写进 `answer.txt`,与官方答案比对)。
855
+ - **一题一个沙箱**:每题独立目录、独立进程、独立输出文件,互不可见;跑完逐题校验,不靠模型自述。
856
+ - **档位由 hash 分配**,不是手点:`FNV-1a(会话 id) mod 档位数`,`off` 是合法对照臂。手点会让"我觉得这档快"悄悄决定分组。
857
+ - **AB 轮转抵消时段与缓存**:每波 10 个会话同分钟启动,波内五档轮转,波与波之间重排档位。会话级取中位数,再跨会话取中位数(两级中位数,避免长尾把均值拖走)。
858
+ - **缓存变量单独看**:prompt cache 在所有臂上命中规模相同(`cacheRead` 80–85 k/会话,平的),所以"总 token"只算 `input + output`(新 token),`cacheRead` 单列 —— 否则 91–92% 的数字是缓存读,档位差异会被稀释成噪声。
859
+
860
+ 下面三张表只保留原始中位数。旧版还有"最快于 safe"和"新 token ×"两列倍率,**已经去掉**:按档位切出来的单步中位数不是加速比,token 倍率也不是 1.0.0 要下的结论。**同一档位两轮之间就能漂 ±11 %**(见下面第 2 条),所以任何一位小数上的"更快"都不该发布。
861
+
862
+ ### HumanEval(100 会话,10 波 × 10 题,每波 10/10 通过)
863
+
864
+ RUN A(第 1–6 波,60 会话):
865
+
866
+ | 档位 | n | 步数/会话 | Σ步时 中位 | 步时 中位 | 输出/步 | 新 token/会话 | cacheRead | 调用/步 |
867
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- |
868
+ | `safe` | 14 | 4.2 | 6 754 ms | 1 519 ms | 481 | 550 | 83 744 | 0.80 |
869
+ | `fast` | 11 | 4.0 | 5 369 ms | 1 342 ms | 313 | 455 | 82 816 | 0.75 |
870
+ | `turbo` | 9 | 4.2 | 6 793 ms | 1 491 ms | 440 | 650 | 84 224 | 0.79 |
871
+ | `max` | 12 | 4.2 | 5 394 ms | 1 310 ms | 346 | 430 | 83 040 | 0.80 |
872
+ | `ludicrous` | 14 | 4.1 | 5 370 ms | 1 342 ms | 315 | 442 | 80 384 | 0.84 |
873
+
874
+ RUN B(第 7–10 波,40 会话):
875
+
876
+ | 档位 | n | 步数/会话 | Σ步时 中位 | 步时 中位 | 输出/步 | 新 token/会话 | cacheRead | 调用/步 |
877
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- |
878
+ | `off` | 9 | 4.4 | 6 411 ms | 1 361 ms | 337 | 464 | 83 712 | 0.82 |
879
+ | `safe` | 12 | 4.3 | 6 064 ms | 1 364 ms | 423 | 537 | 83 776 | 0.78 |
880
+ | `turbo` | 9 | 4.0 | 5 554 ms | 1 388 ms | 341 | 466 | 85 376 | 0.78 |
881
+ | `ludicrous` | 10 | 3.9 | 5 656 ms | 1 420 ms | 339 | 469 | 84 544 | 0.85 |
882
+
883
+ ### AIME 2025(39 会话,4 波)
884
+
885
+ | 档位 | n | 步数/会话 | Σ步时 中位 | 步时 中位 | 输出/步 | 新 token/会话 | cacheRead | 调用/步 |
886
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- |
887
+ | `off` | 9 | 5 | 19 357 ms | 2 047 ms | 234 | 2 810 | 103 936 | 0.80 |
888
+ | `safe` | 4 | 4.5 | 13 494 ms | 2 105 ms | 197 | 1 396 | 93 216 | 0.78 |
889
+ | `fast` | 8 | 5 | 16 572 ms | 2 510 ms | 184 | 1 724 | 107 552 | 0.80 |
890
+ | `turbo` | 8 | 4 | 10 147 ms | 1 894 ms | 136 | 968 | 81 248 | 0.75 |
891
+ | `ludicrous` | 10 | 6 | 12 432 ms | 2 007 ms | 148 | 1 252 | 122 080 | 0.90 |
892
+
893
+ `fast` 的 Σ步时中位(16 572 ms)比 `safe`(13 494 ms)还高,只是因为 `safe` 只有 4 个会话 —— 这正是这张表不报倍率的原因。
894
+
895
+ ### 这两个 benchmark 教给我们的事
896
+
897
+ 1. **步数被任务形状钉死。** HumanEval 每题 4–5 步,AIME 4–6 步,92 % 的会话 ≤ 15 步。fan-out 的收益随"独立查询的条数"增长,而单文件题的条数是 1 —— 所以**不是档位没效果,是这个任务没有效果可给**。
898
+ 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 %)。**组间差 ≤ 组内漂移,就不下结论。**
899
+ 3. **唯一稳定的可见差异是输出长度,而它没有变成速度。** `safe` 在两组里都是最啰嗦的一臂(输出/步 481、423),其余臂更低。方向与"输出纪律"一致,但它没有穿过上面那 ±11 % 的漂移;而且 1.0.0 不卖"省 token"这件事 —— 一步变宽会读进更多上下文,token 变多是接受的代价,不是回归。
900
+ 4. **质量没有掉。** HumanEval 100 会话逐题跑官方 `test`,每波 10/10 通过;AIME 4 波分别是 9/1、9/1、8/2、7/3(通过/未通过)。没有一例因为档位而变得更差。
901
+ 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。
902
+
903
+ **所以这张表能认证什么、不能认证什么**:能认证"公开题上不掉质量、`off` 不慢";不能认证"档位让公开题变快"。fan-out 的证据仍然只有实验 E 那一个 —— 而且是 0.8.0 的整包,没有单独隔离扇出的那一臂。把两组结论放在同一页,而不是只挑好看的那组 —— 这才是可复现的意思。
904
+
905
+ 原始日志:[`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`。会话本身和逐题沙箱在跑完后已经删掉,上面每个数字都能只靠这份日志、按声明的约定(两级中位数、波内轮转)重算出来。
906
+
907
+ ## 为什么没效果:延迟模型
908
+
909
+ 把 689 步全放进去做最小二乘,`llmMs` 对输出 token 数的拟合是:
910
+
911
+ ```
912
+ ms = 720 + 6.63 × 输出tok R² = 0.93
913
+ ```
914
+
915
+ - **720 ms 的固定开销**(首字 + 排队 + 预处理),与输出长短无关。
916
+ - **6.63 ms/token** 的解码速度,约 150 tok/s。
917
+ - 输出长度解释了 93% 的耗时差异。剩下 7% 才是工具、路由、调度能碰的地方。
918
+
919
+ 于是一步的墙钟是三项之和:
920
+
921
+ ```
922
+ 墙钟 ≈ 720 ms × 步数 + 6.63 ms × 输出 token + 工具等待
923
+ ```
924
+
925
+ > **修正(1.5.1 隔离复测)**:上式的步数项在隔离基准里被高估。干净会话拟合是 `墙钟 ≈ 590 ms × 步数 + 6.0 ms × 输出 token`(TTFT 0.59 s/步,解码占 `llm` 的 93 %),即**步数几乎免费、输出 token 才是主项**;这正是后文“减少输出 token 是唯一量级杠杆”的实测依据。
926
+
927
+ 前两项来自那 689 步的拟合;第三项不在拟合里(它是 `stepMs − llmMs`),也正是长尾和卡顿待的地方 —— 1.0.0 的单次调用上限就是冲它去的。
928
+
929
+ 再看 effort 的残差(扣掉输出长度影响后的平均偏差):
930
+
931
+ | effort | 步数 | 平均输出 | 平均耗时 | 残差 |
932
+ | --- | --- | --- | --- | --- |
933
+ | `minimal` | 45 | 625 | 5356 ms | **+488 ms** |
934
+ | `low` | 451 | 534 | 4152 ms | −110 ms |
935
+ | `medium` | 87 | 402 | 3347 ms | −39 ms |
936
+ | `high` | 106 | 500 | 4328 ms | +292 ms |
937
+
938
+ 中位残差全部落在 ±350 ms 内,符号还互相矛盾(最"省"的 `minimal` 残差最正)。**这不是快慢,这是噪声。**
939
+
940
+ 直接证据更简单:750 行拟合数据里 `reasoning` 全为 0;同一份实验日志的 1,888 步也全为 0 —— 四个臂、五个档位,一个例外都没有。这条自建路由不产出思考 token,**所以降 effort 不可能省下任何解码时间:没有可省的解码。** 插件的降档逻辑没有写错,它只是在一个没有思考可省的路由上工作。
941
+
942
+ 这正是 1.0.0 **删掉**而不是**再调低一点**的原因:在一个不产出思考的路由上把 effort 重新标定一次,仍然是在改用户的问题,而不是在改回答的速度;而且改写会随请求头变成宿主的"最近使用档位",用户选的 Medium 会漂到 Low 并跨重启留在那里(0.9.x 之前确实如此,1.0.0 的 CHANGELOG 记了这条)。删掉之后,请求是纯透传。
943
+
944
+ 顺带一个反直觉的数字:输入侧几乎免费。全部 750 步里 `cacheRead` 累计 4,086 万 token,而真正新读入的 `input` 只有 2.4 万 —— 提示缓存把输入侧抹平了,所以"省输入"也不是一个有用的方向。反过来,1.0.0 让一步变宽时多读的那点输入,在这本账上很便宜。
945
+
946
+ ## 1.0.0 改了什么
947
+
948
+ 1. **删掉的**:`floor`、`decideEffort`、每步降一档、`ludicrous` 发 `reasoningEffort: 'off'`、面板上的档位选择器。`agent/request` 现在只做一件事:`const resolved = await next()` …… `return resolved`。代码里唯一还读 `reasoningEffort` 的地方是把它记进指标(记录用户选了什么),从不写回。测试里有一条扫描源码、保证不再出现 effort 改写。
949
+ 2. **留下的只有两类**,都只影响"同样的思考怎么走完":
950
+ - **让一步更宽**:`auto` 的扇出目标从 4 起,顺利两步且上一步至少 2 个调用时放到 8,报错或无进展时收窄到 2 —— 它只是"目标",模型自己决定发几个;输出纪律只收叙述、不收答案(`light` / `full`)。
951
+ - **让停不下来的停下来**:单次调用上限在 `auto` 下从 120 s 起,按本会话最近调用时长的 p90 × 3 往上抬、封顶 300 s(`safe` 不设限);到点取消调用,并把"换个做法"写回给模型。循环中断:连续 6 步没有新东西 → 警告改法;12 步 → 要求停下报告;一轮 30 步同样触发。
952
+ 3. **这套组合没有在 1.0 规模上做过 A/B。** 实验 E 的 −40 % 是 0.8.0 的整包,其中 effort 那一层已经被删了;实验 F 说明单文件题上本来就测不出。所以对 1.0.0 我们只有机制和方向,没有百分比。**任何把 −40 % 当成 1.0.0 数字的写法都是错的。**
953
+ 4. **诚实的话术**:「同样思考强度,任务允许时更少往返」。不要说"快 40 %",也不要说"省 43 % token" —— 后一句在 1.0.0 的方向上是反的。
954
+ 5. **token 是交易,不是回归。** 一步更宽意味着一次读入更多上下文,token 可能变多;1.0.0 明确接受这一点并写进了 CHANGELOG。要报 token,必须报在"同任务、同质量、同思考强度"的 A/B 上,否则报的只是任务形状的差。
955
+ 6. **下一次该测什么**:多仓库、多文件任务,每步有好几个互相独立的查询(例如一次看 5 个仓库的版本、5 个目录的测试数、5 份文件的导出)。对照用 `--gears off,safe,auto`(1.0.0 的默认是 `auto`,不是 `turbo`);指标用 calls/step、steps/session、Σ step time,并把工具等待单列。单文件题上测不出差异是**预期**,不该再拿它当反例或正例。
956
+
957
+ ## 那什么才真的更快
958
+
959
+ 延迟模型自己给出了答案:`720 ms × 步数 + 6.63 ms × 输出 token + 工具等待`。可动的量只有两个,都不是"解码速度"。
960
+
961
+ | 杠杆 | 现状(本机,1.0.0 之前的日志) | 1.0.0 动不动 |
962
+ | --- | --- | --- |
963
+ | **步数** | 关闭态每轮 12.9 步;一步固定烧掉 720 ms | 动:更宽的步 + 循环中断,目标是十几次试探并成几次 |
964
+ | **工具等待** | 4,480 s 总耗时里 1,536 s 在工具里;bash p90 5.5 s、最长一次 818 s | 动:并行摊开,加上唯一一个硬上限(单次调用到点取消) |
965
+ | 输出 token | 平均 ~500 tok/步 | 只动叙述,答案不动;**不承诺总量下降** |
966
+ | 解码速度 | ~150 tok/s | 不动(1.0.0 连 effort 都不碰) |
967
+ | 首字 | 720 ms 里的固定部分 | 不动(没有流式首字信号可测,见下) |
968
+
969
+ 旧版这里写的是"并行把 token 从 58 k 推到 142 k,这笔账是亏的"。1.0.0 不再这么算:token 是买墙钟的货币,账要记在同任务、同质量、同思考强度的 A/B 上,而那个 A/B 还没有做。能确认的只有一个方向性结果:0.8.0 的 16 个同任务会话少用 3 步、少 40 % 墙钟,代价是每步多 5 % 输出。它说明方向,不构成 1.0.0 的证据。
970
+
971
+ ## 这份实测的局限
972
+
973
+ - **一条路由**。`company/deepseek-v4.1-flash` 经自建网关;官方 DeepSeek 路由声明了 `off/low/high/max` 并有真实的思考 token,结论不能直接搬过去。
974
+ - **样本小**。实验 B 每档 1–3 个会话,够看出"没有差异",不够看出"差异是 3%"。
975
+ - **没有 1.0.0 的 A/B**。A/B/E/F 全部是 1.0.0 之前的;1.0.0 的扇出、卡顿取消、循环中断从未在同一个任务上与 `safe` 对照过。这是这份文档最大的缺口。
976
+ - **所有档位数字都是旧整包的**。实验 E 的 −40 % 和实验 F 的 0.95×–1.09× 都带着降 effort 那一层;去掉之后会变成什么样,没有测过。
977
+ - **首字是近似值**。`assistant/message` 每步只提交一次,所以 `ttftMs` 就是 `llmMs`,不是真正的流式首字。这一栏目前只能当"整步耗时"读。
978
+ - **实验 F 的臂不均、有长尾**。档位由哈希分配,`safe` 只有 4 个会话而 `off` 有 9 个;5 个 550–597 s 的超时会话能把同一档的中位数挪一倍。
979
+
980
+ ## 复现
981
+
982
+ ```bash
983
+ # 仓库里直接跑,等价于安装后的 dsh-fast-mode-experiment / dsh-fast-mode-report
984
+ node bin/experiment.mjs start --gears off,safe,auto # 1.0.0 该测的对照:关 / 只有边界 / 自适应整包
985
+ node bin/experiment.mjs status # 每档已有多少会话/步
986
+ node bin/report.mjs --since 2d # 两张表 + 相对关闭态的倍率
987
+ node bin/experiment.mjs stop # 结束,之后的会话回到插件配置
988
+ ```
989
+
990
+ - `--gears` 不传时默认是 `off,safe,fast,turbo,max`;A/B/E/F 那几轮用的就是这套固定宽度臂(`--gears safe,turbo` 是实验 E 的最小复现)。1.0.0 里实验臂会按名字原样生效(包括 `off`),`turbo`/`max` 仍是固定宽度 6/8,但**不再降 effort** —— 所以重跑得到的是"扇出 + 纪律 + 卡顿上限",数字不会等于当年的 −40 %。
991
+ - 报告的两个倍率表在**任务形状不一致**时不要读:本机那两张全量表里 `fast` 显示"模型耗时 0.89×",那是它恰好落在长输出会话里的结果,不是加速。要比就比实验 B 那种同任务、同步数的小样本,或者直接用上面的延迟模型做残差比较 —— 后者对任务形状免疫。
992
+ - 实验 E 的最小复现:`--gears safe,turbo`,然后并发跑两波、每波 8 个同提示词的会话,最后按 `gear` 分组算每会话的 `ΣstepMs` 中位数。原始行在 `~/.dsh/fast-mode-metrics.jsonl` 里(本机存在),按 `sid` 分组即可重算,不需要重跑模型。
993
+ - 0.8.0 那一轮的原始日志在 [`docs/bench-raw-2026-10-24.jsonl.gz`](docs/bench-raw-2026-10-24.jsonl.gz)(已随仓库提交),按上面的约定可以重算实验 E 和 F 的每一个数。
994
+ - 测多仓库任务时,不要直接读 `dsh-fast-mode-report` 的全量倍率表当结论;先按任务分组,再比同组内的中位数。
995
+
996
+ ---
997
+
998
+ # Measured: where it is faster, and where it is not
999
+
1000
+ > **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.
1001
+
1002
+ One question, one answer: **did the request actually get faster?**
1003
+
1004
+ Version first, because most of this file is history:
1005
+
1006
+ - 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".
1007
+ - 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.
1008
+ - 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.
1009
+
1010
+ The only honest 1.0.0 claim: **same thinking effort, fewer round trips where the task allows them.** No percentage.
1011
+
1012
+ - 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.
1013
+ - 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).
1014
+ - 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.
1015
+ - **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.
1016
+
1017
+ ## Summary
1018
+
1019
+ | Claim | Evidence | Strength |
1020
+ | --- | --- | --- |
1021
+ | Step latency is almost entirely output tokens | `ms = 720 + 6.63 × outTok`, R² = 0.93, n = 689 | strong |
1022
+ | 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 |
1023
+ | 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 |
1024
+ | Higher gears fan out more tool calls per step (**0.8.0 data**) | multi-call steps: off 2% → `turbo`/`max` 22–23% | medium |
1025
+ | 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) |
1026
+ | How much 1.0.0's remaining bundle (fan-out + stall cancellation + loop breaking) is worth | not measured | **unmeasured** |
1027
+ | 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) |
1028
+
1029
+ 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.
1030
+
1031
+ ## Method
1032
+
1033
+ - 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).
1034
+ - Each step appends one JSONL row: `llmMs`, `stepMs`, `output`/`input`/`cacheRead`/`reasoning`, `calls`, `effort`, `mode`/`gear`, `route`.
1035
+ - **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.
1036
+ - **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.
1037
+
1038
+ ## Experiment A: contaminated by its own tool output
1039
+
1040
+ 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.
1041
+
1042
+ 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.
1043
+
1044
+ ## Experiment B: variables controlled
1045
+
1046
+ 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.
1047
+
1048
+ | Gear | Steps | Effort (step 1→3) | LLM p50 | Output tok/step | **ms/token** |
1049
+ | --- | --- | --- | --- | --- | --- |
1050
+ | `off` (control) | 9 | `medium` / `medium` / `medium` | 1978 ms | 247 | 9.08 |
1051
+ | `safe` | 9 | `medium` / `low` / `low` | 1974 ms | 227 | 9.11 |
1052
+ | `turbo` | 3 | `medium` / `low` / `low` | 2262 ms | 254 | 9.01 |
1053
+ | `max` | 3 | `medium` / `low` / `low` | 2216 ms | 220 | 9.47 |
1054
+
1055
+ 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.
1056
+
1057
+ ## Experiment E: 0.8.0's three levers are actually faster
1058
+
1059
+ > **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.
1060
+
1061
+ 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)**.
1062
+
1063
+ - **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`.
1064
+ - **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.
1065
+ - 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).
1066
+
1067
+ **Results (per-session Σ step time = sum of that session's `stepMs`)**
1068
+
1069
+ | Metric | `safe` (n=4) | `turbo` (n=12) | Change |
1070
+ | --- | --- | --- | --- |
1071
+ | Σ step time, median | 25.9 s | **15.6 s** | **−40 %** |
1072
+ | Σ step time, mean | 25.7 s | 17.4 s | −33 % |
1073
+ | Steps, median | 7.5 | **4.5** | **−40 %** |
1074
+ | Output tokens per session, median | 3,798 | **2,180** | −43 % |
1075
+ | Output tokens per step | 510 | 535 | +5 % (a fatter step is the point) |
1076
+ | Tool calls per step | 1.41 | 1.37 | flat |
1077
+ | Mann–Whitney U (Σ step time) | — | — | U = 46, z = 2.67, **p = 0.008** |
1078
+
1079
+ > 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.
1080
+
1081
+ 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.
1082
+
1083
+ **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.
1084
+
1085
+ **Honest reading**
1086
+
1087
+ - **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.
1088
+ - 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).
1089
+ - One **non-experiment** session ran alongside (07:21–07:37). It hits both arms equally and is excluded from the statistics.
1090
+ - `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.
1091
+
1092
+ ## Experiment F: the same comparison on public benchmarks (HumanEval 100 sessions, AIME 2025 39 sessions)
1093
+
1094
+ Experiment E had 16 sessions and one repo task. To ask whether it generalises, two
1095
+ well-known public suites were run. **The answer up front: both are structurally
1096
+ unable to exercise fan-out — but they do falsify "every gear is faster".**
1097
+
1098
+ **Provenance**: this run is also from 0.8.0, where every enabled arm still carried
1099
+ the effort ladder. That ladder cannot explain any of the differences in either
1100
+ direction (`reasoning` is 0 in every row), but the arms are still bundles, so the
1101
+ numbers below are history, not a 1.0.0 prediction.
1102
+
1103
+ ### Design
1104
+
1105
+ - **Tasks**: the first 10 HumanEval problems (full statement given; the solution
1106
+ goes into its own `solution.py`, checked with the official `test` locally) and
1107
+ the first 10 AIME 2025 problems (final integer into `answer.txt`, compared with
1108
+ the official answer).
1109
+ - **One sandbox per problem**: own directory, own process, own output file; every
1110
+ answer is checked, never taken on the model's word.
1111
+ - **Arms are hashed, not clicked**: `FNV-1a(session id) mod gears`; `off` is a
1112
+ legal control. Clicking lets a hunch pick the groups.
1113
+ - **AB rotation cancels time-of-day and cache drift**: ten sessions start in the
1114
+ same minute per wave, gears rotate inside the wave and are reshuffled between
1115
+ waves. Per-session medians, then a median across sessions — a two-stage median,
1116
+ so a long tail cannot drag the mean.
1117
+ - **Cache is looked at separately**: `cacheRead` is flat across arms (80–85 k per
1118
+ session), so "total tokens" counts `input + output` (new tokens) only and
1119
+ `cacheRead` gets its own column. Otherwise 91–92 % of the sum is cache reads and
1120
+ the gear difference drowns.
1121
+
1122
+ The three tables keep raw medians only. The old `faster than safe` and
1123
+ `new tokens ×` multiplier columns are **gone on purpose**: a per-gear step-time
1124
+ median is not a speed ratio, and a token ratio is not the claim 1.0.0 makes. One
1125
+ arm drifts ±11 % between two runs (point 2 below), so no "faster" worth a decimal
1126
+ place can be published from them.
1127
+
1128
+ ### HumanEval (100 sessions, 10 waves × 10 problems, 10/10 passing per wave)
1129
+
1130
+ RUN A (waves 1–6, 60 sessions):
1131
+
1132
+ | Gear | n | steps/session | median Σ step ms | median step ms | out/step | new tok/session | cacheRead | calls/step |
1133
+ |---|---|---|---|---|---|---|---|---|
1134
+ | `safe` | 14 | 4.2 | 6 754 ms | 1 519 ms | 481 | 550 | 83 744 | 0.80 |
1135
+ | `fast` | 11 | 4.0 | 5 369 ms | 1 342 ms | 313 | 455 | 82 816 | 0.75 |
1136
+ | `turbo` | 9 | 4.2 | 6 793 ms | 1 491 ms | 440 | 650 | 84 224 | 0.79 |
1137
+ | `max` | 12 | 4.2 | 5 394 ms | 1 310 ms | 346 | 430 | 83 040 | 0.80 |
1138
+ | `ludicrous` | 14 | 4.1 | 5 370 ms | 1 342 ms | 315 | 442 | 80 384 | 0.84 |
1139
+
1140
+ RUN B (waves 7–10, 40 sessions):
1141
+
1142
+ | Gear | n | steps/session | median Σ step ms | median step ms | out/step | new tok/session | cacheRead | calls/step |
1143
+ |---|---|---|---|---|---|---|---|---|
1144
+ | `off` | 9 | 4.4 | 6 411 ms | 1 361 ms | 337 | 464 | 83 712 | 0.82 |
1145
+ | `safe` | 12 | 4.3 | 6 064 ms | 1 364 ms | 423 | 537 | 83 776 | 0.78 |
1146
+ | `turbo` | 9 | 4.0 | 5 554 ms | 1 388 ms | 341 | 466 | 85 376 | 0.78 |
1147
+ | `ludicrous` | 10 | 3.9 | 5 656 ms | 1 420 ms | 339 | 469 | 84 544 | 0.85 |
1148
+
1149
+ ### AIME 2025 (39 sessions, 4 waves)
1150
+
1151
+ | Gear | n | steps/session | median Σ step ms | median step ms | out/step | new tok/session | cacheRead | calls/step |
1152
+ |---|---|---|---|---|---|---|---|---|
1153
+ | `off` | 9 | 5 | 19 357 ms | 2 047 ms | 234 | 2 810 | 103 936 | 0.80 |
1154
+ | `safe` | 4 | 4.5 | 13 494 ms | 2 105 ms | 197 | 1 396 | 93 216 | 0.78 |
1155
+ | `fast` | 8 | 5 | 16 572 ms | 2 510 ms | 184 | 1 724 | 107 552 | 0.80 |
1156
+ | `turbo` | 8 | 4 | 10 147 ms | 1 894 ms | 136 | 968 | 81 248 | 0.75 |
1157
+ | `ludicrous` | 10 | 6 | 12 432 ms | 2 007 ms | 148 | 1 252 | 122 080 | 0.90 |
1158
+
1159
+ `fast`'s median Σ step time (16 572 ms) is higher than `safe`'s (13 494 ms) only
1160
+ because `safe` has 4 sessions — which is exactly why this table carries no ratio.
1161
+
1162
+ ### What these two benchmarks taught us
1163
+
1164
+ 1. **Step count is pinned by the task shape.** 4–5 steps per HumanEval problem,
1165
+ 4–6 per AIME problem, 92 % of sessions ≤ 15 steps. Fan-out pays per independent
1166
+ query, and a single-file problem has one — so this is not "the gear does
1167
+ nothing", it is "this task has nothing to give".
1168
+ 2. **`off` and every enabled gear sit inside the noise.** HumanEval RUN B: `off`
1169
+ 0.95×, `turbo` 1.09×, `ludicrous` 1.07× against `safe`; AIME spreads wider
1170
+ (0.70×–1.33×). The drift of one arm between the two runs is the same size
1171
+ (`safe` 6 754 ms in RUN A vs 6 064 ms in RUN B, ±11 %). **When the between-group
1172
+ gap is ≤ the within-group drift, no conclusion is drawn.**
1173
+ 3. **The only stable visible difference is output length, and it does not become
1174
+ speed.** `safe` is the verbose arm in both suites (481 and 423 out/step); the
1175
+ rest are lower. That is the direction output discipline predicts, but it never
1176
+ crossed the ±11 % drift above — and 1.0.0 does not sell a token saving: a wider
1177
+ step reads more context, so more tokens are an accepted cost, not a regression.
1178
+ 4. **Quality did not drop.** HumanEval ran the official `test` on all 100 sessions,
1179
+ 10/10 passing in every wave; AIME's four waves scored 9/1, 9/1, 8/2, 7/3. No
1180
+ case got worse because of the gear.
1181
+ 5. **The AIME run exposed the real wall-clock enemy, and it is not the gear.** Five
1182
+ of 39 sessions kept writing after the task board judged them timed out — 550–597 s
1183
+ and 77–132 steps each. Drop them and `ludicrous`'s median Σ step time falls from
1184
+ 12 432 to 6 633 ms while its mean falls from 178 s to 12 s; the `fast` arm's
1185
+ median falls from 16 572 to 12 840 ms. **The median survives, the mean does not —
1186
+ and these same pairs show why a median must not be published as a speed ratio**:
1187
+ five tail sessions can move one arm's median by a factor of two, so the
1188
+ 0.95×–1.09× gap between arms means nothing next to them. It is also why 0.9.0
1189
+ added a ceiling on one tool call and 1.0.0 kept it: what eats the clock is a few
1190
+ calls that never stop, not 100 ms per step.
1191
+
1192
+ **What this table can and cannot certify**: it certifies that public tasks keep
1193
+ their quality and that `off` is not slower. It does not certify that a gear makes a
1194
+ public task faster. The fan-out evidence is still Experiment E alone — and that is
1195
+ 0.8.0's bundle, with no arm that isolates fan-out. Both results stay on the same
1196
+ page instead of publishing the flattering one — that is what reproducible means
1197
+ here.
1198
+
1199
+ Raw log: [`docs/bench-raw-2026-10-24.jsonl.gz`](docs/bench-raw-2026-10-24.jsonl.gz)
1200
+ — per-step rows (`ts / sid / gear / route / ttftMs / llmMs / stepMs / calls /
1201
+ errors / input / output / cacheRead / cacheWrite`) for the 179 experiment sessions,
1202
+ including the pilot and Experiments E and F. The sessions themselves and the
1203
+ per-problem sandboxes were deleted once the run finished; every number above is
1204
+ reproducible from this log alone under the stated conventions (two-stage medians,
1205
+ in-wave gear rotation).
1206
+
1207
+ ## Why: the latency model
1208
+
1209
+ Least squares over 689 steps:
1210
+
1211
+ ```
1212
+ ms = 720 + 6.63 × outTok R² = 0.93
1213
+ ```
1214
+
1215
+ 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.
1216
+
1217
+ So one step's wall clock is three terms:
1218
+
1219
+ ```
1220
+ wall clock ≈ 720 ms × steps + 6.63 ms × output tokens + tool wait
1221
+ ```
1222
+
1223
+ 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.
1224
+
1225
+ 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.
1226
+
1227
+ 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.
1228
+
1229
+ 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.
1230
+
1231
+ 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.
1232
+
1233
+ ## What 1.0.0 changes about these conclusions
1234
+
1235
+ 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.
1236
+ 2. **Kept, and it only comes in two kinds**, both about how the same thinking gets carried out:
1237
+ - **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`).
1238
+ - **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.
1239
+ 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.**
1240
+ 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.
1241
+ 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.
1242
+ 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.
1243
+
1244
+ ## What would actually be faster
1245
+
1246
+ 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".
1247
+
1248
+ | Lever | Here (local log, pre-1.0.0) | 1.0.0 moves it? |
1249
+ | --- | --- | --- |
1250
+ | **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 |
1251
+ | **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) |
1252
+ | Output tokens | ~500 tok/step on average | only narration, never the answer; **no promise of a lower total** |
1253
+ | Decode rate | ~150 tok/s | no (1.0.0 does not even touch effort) |
1254
+ | Time to first token | the fixed part of the 720 ms | no (no streaming signal to measure — see Limits) |
1255
+
1256
+ 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.
1257
+
1258
+ ## Limits
1259
+
1260
+ - **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.
1261
+ - **Small sample.** Experiment B has 1–3 sessions per arm: enough to see "no difference", not enough to see "a 3 % difference".
1262
+ - **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.
1263
+ - **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.
1264
+ - **Time to first token is approximate.** `assistant/message` is emitted once per step, so `ttftMs` equals `llmMs`; read that column as "whole-step time".
1265
+ - **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.
1266
+
1267
+ ## Reproduce
1268
+
1269
+ ```bash
1270
+ # from the repo, equivalent to the installed dsh-fast-mode-experiment / dsh-fast-mode-report
1271
+ node bin/experiment.mjs start --gears off,safe,auto # what 1.0.0 should be compared on: off / edges only / the adaptive bundle
1272
+ node bin/experiment.mjs status # sessions and steps per arm
1273
+ node bin/report.mjs --since 2d # the two tables plus ratios against the off arm
1274
+ node bin/experiment.mjs stop # end it; later sessions go back to the plugin config
1275
+ ```
1276
+
1277
+ - 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 %.
1278
+ - 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.
1279
+ - 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.
1280
+ - 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.
1281
+ - 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.