dsh-fast-mode 1.5.12 → 1.5.13

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 CHANGED
@@ -57,7 +57,7 @@ W1 出现过唯一一格门禁违规:`ledger` p1 的 `def15` 红在 `test_roun
57
57
 
58
58
  ## 全量池化复核(2026-10-27):7 个干净块合池 120 对,总量 **0.80×**,「26–28 %」撤回
59
59
 
60
- > **这一节是历史记录,数字已被后面的 9 块 132 对口径取代**(见文末「合池的自助区间与逐任务稳定性(2026-10-27,含 PPX2 与 SG2)」:0.825×、CI95 0.747–0.914)。留在这里是为了说明「26–28 %」为什么被撤回,不要引用本节表格里的家族数字。
60
+ > **这一节是历史记录,数字已被后面的 12 块 138 对口径取代**(见文末「合池的自助区间与逐任务稳定性(2026-10-28,12 块 138 对)」:0.8234×、CI95 0.7418–0.9133)。留在这里是为了说明「26–28 %」为什么被撤回,不要引用本节表格里的家族数字。
61
61
 
62
62
  之前对外说的是「1.5.1 那一块 0.72×、W5 0.73×、W7 0.73×,三块合并 65 个有效单元 0.74×」。那三块是**挑出来的**:它们都恰好是默认臂表现好的块。样本扩大以后,把**所有**有 `off` 锚的干净块一起加总——不再挑块、不再排除任何任务——数字变差,所以 26–28 % 撤回,改成约 **20 %**。
63
63
 
@@ -224,8 +224,8 @@ W9 会给出实测量,但先把「最多能省多少」算出来,免得把
224
224
  2. **输出里约 23 % 是工具调用参数**。对 134 个 `def15` 有效格的会话做统计:`tool/call` 参数字符数 ÷ 3.6 再除以该格输出 token,中位 **23.1 %**(均值 25.9 %)。剩下约 77 % 是推理与正文。
225
225
  3. **这部分参数基本不可压**。15 个 `def15` 会话里 153 次整文件 `write` 合计 716 468 字符;其中**写到本会话里已经写过的同一路径**(即真正的整文件重写浪费)只有 5 876 字符,占 0.8 %。也就是说参数是在**创建交付物本身**,不是重复劳动。据此预登记:`preferPatch` 的上限约等于输出的 0.2 %,W9 的预期是「与默认无差异」,若出现更大的差就是噪声。
226
226
  4. **精确重复的工具调用也很少**:373 次调用里同名同参重复 8 次(2.1 %),「别重复读同一文件」这类机制同样没有余量。
227
- 5. **还剩下的唯一大块是重复验证**:1534 次调用里 187 次是跑测试/校验类命令(12.2 %),145 个会话里 25 个出现 ≥3 次。合并这类步骤正是 `batchVerify` 的目标——它因为同一个 liveness 缺陷在 W5 里零下发,**至今未被真正测过**。
228
- 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 条。
227
+ 5. **重复验证这块已测完,方向不成立**:1534 次调用里 187 次是跑测试/校验类命令(12.2 %),145 个会话里 25 个出现 ≥3 次,合并这类步骤正是 `batchVerify` 的目标。修好投递后 BV8 的 16 对给出合并 1.188×、步数 1.229×(见「`batchVerify` 复测块 BV8」)——提醒没有把验证合并掉,反而多出往返。这条线关闭。
228
+ 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 证明「提醒合并验证」是反向的(步数上升),所以这条算术约束目前没有已验证的实现路径。
229
229
 
230
230
  ### D. W9(`preferPatch` 单格复核):4 个单元后中止,只作「提醒确实下发」的凭证
231
231
 
@@ -348,7 +348,7 @@ W10(10 个有效单元)+ W11(13 个有效单元)合并,23 个单元:
348
348
  | 工具结果截断 | 未开跑,已测余量 | 无余量 | 236 条工具结果中位 1.2 kB、最大 18 kB、单会话中位 26 kB |
349
349
  | 丢弃旧工具结果(上下文裁剪) | 未实现,先测余量 | 无余量 | 193 步里 `corr(上下文 totalTokens, 每步输出)` = **0.067**,每步输出中位 663 token 与上下文长度基本无关 |
350
350
  | `workspaceMap` | 0.927×(23 单元) | 尾部变慢,不进默认 | 输出 −10 % 但步数 +10 % |
351
- | `batchVerify` | 未测(liveness 缺陷已修) | 待测 | 测试/校验调用占 12.2 %,但步数项只占总墙钟 6 % |
351
+ | `batchVerify` | 合并 16 对 **1.188×** | 淘汰(BV8,2026-10-28) | 下发 8/8 治疗会话;步数 1.229× 反而变多,codeimpl 1.478×、shellpipeline 0.956× |
352
352
 
353
353
  最后一行是这一轮算清的账:`batchVerify` 合并的是「重复验证的步」,而步数项只占 1.62 s × 319 步 ≈ 6 % 墙钟(输出项 97 %)。即使把它压到零,总量也只能到 0.94× 左右,**它不是通往 0.67× 的路**。
354
354
 
@@ -944,7 +944,7 @@ D 的反对意见是“每个子任务太浅,所以扇出当然亏”。E 把
944
944
  | 1.5.1 默认臂整任务墙钟 **0.72–0.74×**,输出 **0.76×**、步数 **0.59×**,有效单元零红 | 1.5.1 重基线 + W3 + W5 + W7(65 个有效单元合并 0.742×),W8b 逐格中位 0.717× | 强(四块同向,门禁全绿) |
945
945
  | 墙钟 ≈ 0.97 × 输出 token(bench 实测斜率 13.5 ms/token,步数项 6 %) | W8b `off` 臂 19 格回归,逐格残差中位 18 % | 强 |
946
946
  | 输出里 23 % 是工具调用参数,而这部分基本是**在建交付物**(整文件重写的重复量只有 0.8 %) | 134 个 `def15` 格参数占比统计 + 15 会话 153 次 `write` 逐路径比对 | 强 |
947
- | 还剩的杠杆是**重复验证步**(12.2 % 的调用是跑测试类命令,25 个会话里出现 ≥3 次) | 145 个 `def15` 会话的调用分类 | 中(尚未有干净的臂测过 `batchVerify`) |
947
+ | ~~还剩的杠杆是**重复验证步**~~(已否证:BV8 合并 1.188×、步数 1.229×) | 145 个 `def15` 会话的调用分类 + BV8 16 对 | 已测,方向不成立 |
948
948
  | 0.67× 需要推理再降 15 %(等价于步数 0.585× → 约 0.50×) | W8b-C 的算术:参数项压不动,只能压推理 | 强(算术),达成方式**未验证** |
949
949
 
950
950
  一句话:**在这台自建网关的 `company/deepseek-v4.1-flash` 上,拧 `reasoning_effort` 不会让生成变快,因为这条路由根本不思考;1.5.1 的默认臂已经把整任务墙钟压到 0.72–0.74×,而墙钟几乎等于输出 token、输出里 23 % 是必须写出来的交付物,所以不碰推理档位的前提下,剩下的空间只够再挤「重复验证的步数」。**
@@ -1456,17 +1456,15 @@ node bin/experiment.mjs stop # end it; later sessions g
1456
1456
  - **速度**:墙钟合计 **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×。
1457
1457
  - **读法**:单对、n=5,噪声带比合池 0.798× 的总口径宽,**不用这块替换总量结论**;它只用于回答「发版这一步有没有把质量或方向弄坏」。质量答案是没坏;速度方向在这一块落进噪声(0.97×)。
1458
1458
  - 原始结果 `bench/results/suite-REL16.jsonl`;`python3 bench/report.py --files bench/results/suite-REL16.jsonl --baseline off` 可复算。
1459
- ## 合池的自助区间与逐任务稳定性(2026-10-27,含 PPX2 与 SG2)
1459
+ ## 合池的自助区间与逐任务稳定性(2026-10-28,12 块 138 对)
1460
1460
 
1461
- 合池点估计 **0.825×** 是 132 对有效单元的比值加总,不是承诺值。给这 132 对做 2000 次自助重抽(`python3 bench/pool.py --ci 2000`)后:**墙钟总比 95 % 区间 [0.747, 0.914]**、逐格中位 0.813×(区间 [0.710, 0.883])、89/132 对更快。`bench/results/suite-*.jsonl` 里每行都带 `valid` 判定,合池只吃 `exit==0 && steps>=1 && out>0 && exact && !leak` 的单元。
1461
+ 合池点估计 **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 && exact && !leak` 的单元(`bench/pool.py` 的 `valid()`)。
1462
1462
 
1463
- 新增的 off 锚定块 **SG2**(4 家族 × 1 对 × 3 臂,`bench/results/suite-SG2.jsonl`)合进来是 **1.235×(4 对,2/4 更快)**,把合池从 0.809× 抬到 **0.825×**。它也是第一块把 `ledger`(2.024×,输出 34.1k vs 18.7k token)和 `perfrefactor`(1.427×)的反向样本算进分母的块——抬上去的那 1.6 个点就是把这两类任务如实计入的代价。
1464
-
1465
- > **SG2 的 `sg15` 臂作废(1.5.10)**:`sg15` 打开的是 `shapeGate`,而这个字段当时根本没传进策略对象(见文末审计),所以 `sg15` 与 `def15` 是**同一条策略**。同块里两者 1037.5 s vs 1030.3 s 的 0.8 % 差值正是「同一策略跑两遍」应有的噪声;据此写下的「`sg15` 比 `def15` 更差 1.295× ⇒ `shapeGate` 继续默认关」**没有证据支持,撤回**。SG2 里 `off` 对 `def15` 的 4 对仍然有效,1.235× 与合池 0.825× 不受影响。
1463
+ 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` 列是字节窗口的产物)。
1466
1464
 
1467
1465
  | 块 | n | 墙钟总比 | 更快 |
1468
1466
  |---|---|---|---|
1469
- | `W1` | 11 | 0.710× | 7/11 |
1467
+ | `W1` | 11 | 0.711× | 7/11 |
1470
1468
  | `W3` | 29 | 0.721× | 23/29 |
1471
1469
  | `W5` | 20 | 0.793× | 11/20 |
1472
1470
  | `W6` | 19 | 0.931× | 11/19 |
@@ -1475,42 +1473,68 @@ node bin/experiment.mjs stop # end it; later sessions g
1475
1473
  | `REL16` | 5 | 0.967× | 3/5 |
1476
1474
  | `PPX2` | 8 | 0.912× | 6/8 |
1477
1475
  | `SG2` | 4 | 1.235× | 2/4 |
1476
+ | `NX1_IMPL` | 2 | 0.874× | 1/2 |
1477
+ | `NX1_PIPE` | 2 | 0.545× | 2/2 |
1478
+ | `NX1_SG` | 2 | 1.115× | 1/2 |
1478
1479
 
1479
- 合池输出比 **0.822×**、步数比 **0.809×**、逐格中位 **0.813×**。
1480
+ `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×。剔除它们再算不是「更好的口径」,是挑样本。
1480
1481
 
1481
1482
  | 任务 | n | 中位墙钟 | 均值 | 四分位区间 | 更快 | 符号检验 p |
1482
1483
  |---|---|---|---|---|---|---|
1483
- | `codebase` | 11 | 0.586 | 0.898 | 0.51–1.49 | 7/11 | 0.549 |
1484
- | `stategraph` | 10 | 0.667 | 0.741 | 0.57–0.86 | 8/10 | 0.109 |
1485
- | `shellops` | 13 | 0.683 | 0.896 | 0.55–1.38 | 9/13 | 0.267 |
1486
- | `codeimpl` | 15 | 0.709 | 0.754 | 0.62–0.90 | 13/15 | **0.007** |
1487
- | `migrate` | 15 | 0.739 | 0.849 | 0.53–1.28 | 11/15 | 0.118 |
1488
- | `ledger` | 14 | 0.812 | 0.908 | 0.44–1.29 | 8/14 | 0.791 |
1489
- | `shellpipeline` | 12 | 0.844 | 0.835 | 0.66–0.97 | 10/12 | **0.039** |
1490
- | `perfrefactor` | 14 | 0.961 | 1.140 | 0.69–1.53 | 7/14 | 1.000 |
1491
- | `debugfix` | 15 | 0.976 | 1.287 | 0.77–1.92 | 8/15 | 1.000 |
1492
- | `csvpipe` | 13 | 0.983 | 1.014 | 0.54–1.50 | 8/13 | 0.581 |
1493
-
1494
- - **稳的**:`codeimpl`(13/15 更快,p = 0.007,中位 0.709×)与 `shellpipeline`(10/12,p = 0.039)。这两个家族的收益同时出现在墙钟和步数上,是唯一能对外说「这个形状有稳定收益」的两行。
1495
- - **中位好看但不稳的**:`codebase` 中位 0.586× 但均值 0.898×、四分位 0.51–1.49×,p = 0.549 —— 靠少数长格拉低中位,不能说成「普遍快 41 %」。`shellops` 同理(中位 0.683×、均值 0.896×、p = 0.267)。
1496
- - **加了 SG2 之后方向变坏的**:`ledger` 中位 0.812× 但均值 0.908×、四分位 0.44–1.29×(SG2 里 2.024×);`debugfix` 中位 0.976×、均值 1.287×(输出 1.29×);`perfrefactor` 中位 0.961×、均值 1.140×。这三类不是「快得少」,是**平均在变慢**,默认指引写「先试一次,复验变多就关」。
1497
- - **不许跨块引用的行**:`debugfix` 在 PPX2 这一块里是 **0.651×(2/2 更快)**、`perfrefactor` 是 **0.687×(2/2 更快)**,与混池方向相反。块内 n=2 撑不起任何引用,写在这里只是提醒别拿单块当结论。
1498
- - **输出口径**:合池输出 0.822×,与墙钟同向 —— 速度来自少写、少复验,不是来自更快地写同样多的东西。`debugfix` 输出 1.29× 是唯一明显变长的家族。
1499
- - 开销口径:这些区间是**墙钟**的自助区间;块内两臂逐对交替跑,所以不含跨时段机器漂移。块间不可比,合池只做同 `(tag, task, pair)` 的配对。
1500
-
1501
- ## DBG2:三个慢家族的定向复测(2026-10-27,未进合池)
1502
-
1503
- 原计划 3 家族 × 3 对 × 2 臂 = 18 格,只跑完 14 格(`csvpipe` 第 3 对卡在 18 分钟未收),**7 对可用**:`debugfix` 3 对、`perfrefactor` 2 对、`csvpipe` 2 对。这一块从 13:21 跑到 14:2x,机器 load 5–8(Spotlight 重启后重建索引),**墙钟被拉长约 2–4 倍,不满足合池的负载前提**,所以只作方向参考、不进 `CLEAN`:
1504
-
1505
- | 家族 | n | 墙钟总比 | 逐对 |
1506
- |---|---|---|---|
1507
- | `debugfix` | 3 | 1.021× | 1.059 / 1.060 / 0.943 |
1508
- | `perfrefactor` | 2 | 1.207× | 1.291 / 1.122 |
1509
- | `csvpipe` | 2 | 1.783× | 3.298 / 0.268 |
1484
+ | `codebase` | 11 | 0.586 | 0.898 | 0.51–1.29 | 7/11 | 0.549 |
1485
+ | `stategraph` | 12 | 0.667 | 0.964 | 0.59–0.72 | 9/12 | 0.146 |
1486
+ | `shellops` | 13 | 0.683 | 0.896 | 0.58–1.33 | 9/13 | 0.267 |
1487
+ | `codeimpl` | 17 | 0.709 | 0.778 | 0.62–0.90 | 14/17 | 0.013 |
1488
+ | `migrate` | 15 | 0.739 | 0.849 | 0.53–1.00 | 11/15 | 0.118 |
1489
+ | `shellpipeline` | 14 | 0.775 | 0.794 | 0.59–0.88 | 12/14 | 0.013 |
1490
+ | `ledger` | 14 | 0.812 | 0.908 | 0.45–1.22 | 8/14 | 0.791 |
1491
+ | `perfrefactor` | 14 | 0.961 | 1.140 | 0.72–1.36 | 7/14 | 1.000 |
1492
+ | `debugfix` | 15 | 0.976 | 1.287 | 0.77–1.70 | 8/15 | 1.000 |
1493
+ | `csvpipe` | 13 | 0.983 | 1.014 | 0.54–1.48 | 8/13 | 0.581 |
1494
+
1495
+ - **稳的(两行)**:`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)。
1496
+ - **中位好看但不稳的**:`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)。
1497
+ - **平均在变慢的**:`debugfix` 中位 0.976×、均值 1.287×(输出 1.29×)、`perfrefactor` 中位 0.961×、均值 1.140×、`csvpipe` 中位 0.983×、均值 1.014×。这三类不是「快得少」,是**平均在变慢**;默认指引写的是「先试一次,复验变多就关」。
1498
+ - **不许跨块引用的行**:`debugfix` 在 PPX2 这一块里是 0.651×(2/2 更快)、`perfrefactor` 是 0.687×(2/2 更快),与合池方向相反;`ledger` 在 SG2 里 2.024×。块内 n=2 撑不起任何引用,写在这里只是提醒别拿单块当结论——参考「空转对照」:同一策略跑两遍,9 对里也能差到 0.889–1.342×。
1499
+ - **输出口径**:合池输出 0.8179×,与墙钟同向 —— 速度来自少写、少复验,不是来自更快地写同样多的东西。`debugfix` 输出 1.29× 是唯一明显变长的家族。
1500
+ - **不能说的**:0.8234× 是点估计,95 % 区间上界 0.9133 距今只有 8.7 个百分点;这不是「下限承诺」。也**不能说「省了 26 %」**(1/0.8234 的反算,会被读成承诺)、不能说「思考 token 变少」(遥测里 `reasoning` 恒为 0,这里没有可测的量)。
1501
+ - 开销口径:这些区间是**墙钟**的自助区间;块内两臂逐对交替跑,所以不含跨时段机器漂移。合池只做同 `(tag, task, pair)` 的配对,跨块只聚合比值、不聚合原始秒数。
1502
+
1503
+ ### 输出里究竟有什么:97–99 % 是工具参数,不是正文(2026-10-28,抽样)
1504
+
1505
+ 为什么「再压正文」这条路走不远,看构成就清楚了。从留存下来的 run 目录里抽 40 个 `def15` 会话,把每条助手消息拆成「可见正文」和「工具调用参数」两部分:正文 6,267 字符,工具参数 518,773 字符,**正文只占输出字符的 1.2 %**。输出的大头是 `bash` 命令、`write`/`edit` 的文件内容和补丁,也就是产物本身。
1506
+
1507
+ 同一批会话里再看步的形状(60 个 `def15` run 目录、528 个 on-step):**479/528 = 91 % 的步只发一个工具调用**,其中 353 个是 `bash`;只有 8 % 的步发了两个。`bash` 调用共 409 次,76.5 % 自己就带 `&&`、管道或分号,命令长度中位 344 字符、p90 3,045 字符 —— 也就是说「一步一个」不是模型不会合并,而是每一步本来就是一条已经串好的长命令。
1508
+
1509
+ 所以 `def15` 的输出降到 0.8179× 不可能是「正文写短了」:正文总量太小,压到 0 也只有 1 % 量级。它只能是**步数 0.8062× 的副产品**(每步产物量近似不变,少一步就少一份命令或一份写入),也就是**同一件活少做一遍**,不是「同样多的活写得更省」。注意这里说的是**输出的成分**,不是提醒的措辞:把整套规则提前全程生效(`odt15`,W1 的 12 对)实测 1.213× 更慢,那是「早期步骤也被强行压缩」的成本。提前**触发时点**、措辞不变是另一条线(`d2`),正由 D2 块在测(`debugfix` 7 对已出:几何 0.535×,见「压缩提醒的触发时点」一节)——两者不能混为一谈。
1510
+
1511
+ 反面对照来自 `batchVerify` 的 codeimpl 块(同块两臂,8 对):`def15` 4.5 步/会话、参数 11,593 字符/会话;`bvl15` 6.9 步/会话、14,339 字符/会话,非 `bash` 调用从 1.2 次/会话涨到 2.9 次/会话。多出来的正是复验步和随之而来的写盘 —— 与那一臂 1.478× 的墙钟对得上,不是提醒文本本身的开销。
1512
+
1513
+ 口径限制:这些数字来自**还留在磁盘上的 run 目录抽样**,不是 138 对全池的成分统计;结论只用来说方向(正文 ≪ 参数、单调用步占多数),不要当成全池精确占比引用。
1514
+
1515
+ ## `batchVerify` 复测块 BV8(2026-10-28):codeimpl 1.478× 变慢、shellpipeline 0.956× 无差,合并 1.188× —— **淘汰**
1516
+
1517
+ `batchVerify` 是最后一个「修好 liveness 缺陷后再测一次」的默认关闭机制。W4 曾筛出 0.76×,但 W8b 的逐会话检索证明那一臂**一条提醒都没下发**(20 个会话命中 0),所以 0.76× 不是它的效果;W5 同样零下发。修复投递后第一次真测就是这两块。
1518
+
1519
+ **跑之前定的判据**:合并两个家族共 16 对(`codeimpl` 8 对 + `shellpipeline` 8 对),两臂全绿,**且**合并几何均值 ≤ 0.90× 才考虑翻默认;单家族变快、另一家族变慢的「家族分裂」不算机制,只照实记录。
1520
+
1521
+ **臂差**:`bvl15` = `def15` + `batchVerify: true`。两臂的 `outputDiscipline` 都是默认的 `late`,所以 `batchVerify` 是唯一有效差异。
1522
+
1523
+ **下发核对(精确文案,不用泛词)**:`8/16 sessions delivered`,命中恰好是 8 个 `bvl15` 会话(每会话 2–6 次),8 个 `def15` 会话全为 0 —— 机制在这两块里确实运行了。
1524
+
1525
+ | 家族 | n | 几何均值 | 中位 | 总量 | 更快 | 符号检验 p | 步数 | 输出 | 区间 |
1526
+ |---|---|---|---|---|---|---|---|---|---|
1527
+ | `codeimpl` | 8 | **1.478×** | 1.332× | 1.282× | 2/8 | 0.289 | 1.379× | 1.474× | 0.622–5.965 |
1528
+ | `shellpipeline` | 8 | **0.956×** | 0.941× | 1.039× | 5/8 | 0.727 | 1.096× | 0.922× | 0.449–2.010 |
1529
+ | 合并 | 16 | **1.188×** | 1.148× | — | 7/16 | — | 1.229× | 1.166× | 0.449–5.965 |
1510
1530
 
1511
- 读法:`debugfix` 在负载下基本持平(三条全在 1.0 上下),`perfrefactor` 两条都慢 12–29 %,`csvpipe` 两条方向完全相反(一条跑飞成 3.3×、一条 0.27×)——后者说明这个家族的方差大过机制本身,任何单点数字都不能引用。**结论与混池一致:`debugfix`/`perfrefactor`/`csvpipe` 默认不承诺收益,指引写「先试一次」。**
1531
+ 16 对两臂 32 格全绿(各 15/15 或对应断言全过),没有一格因质量或崩溃作废。
1512
1532
 
1513
- **什么时候开、什么时候别开**(按形状,不按任务名,README 同款):开在多产物、反复复验的中长任务;`debugfix`/`perfrefactor`/`csvpipe` 这类短改修和数据整形**先用一次,看它是否在替你复验**——混池里它们是负的,所以给的是「先试再定」,不是「都会快」。合池的 0.825× 是全部样本的加总口径,别拿它单点承诺某个任务。
1533
+ **三个结论**:
1534
+
1535
+ 1. **这是「变慢 + 家族分裂」,不是机制**:合并 1.188×、只有 7/16 更快、步数 1.229× 反而变多。文案要求「先把这批改动做完,再跑一次覆盖全部改动的检查」,模型实际做的是**多跑几轮**而不是少跑几轮 —— 两家族的步数都升(1.379×、1.096×),输出也同步升。这一族的边界在这里:提醒不能把「重复验证」合并掉,只能改变模型对进度的解释,代价是更多往返。
1536
+ 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×。
1537
+ 3. **默认保持 `batchVerify: false`**,并把「重复验证步」这条线关掉:W8b 算过即使把它压到零总量也只到 0.94× 左右(步数项只占 6 % 墙钟),现在实测连这个方向都不成立 —— 强行合并带来的是更多步。以后不再为这一族开臂。
1514
1538
 
1515
1539
  ## 按场景开关值不值:132 对上的反事实算账(2026-10-27,纯分析,不新增格子)
1516
1540
 
@@ -1626,7 +1650,16 @@ python3 bench/suite-run.py --tasks debugfix,ledger,codeimpl --arms off,def15,bud
1626
1650
 
1627
1651
  **这条拆解给出的天花板**:0.60× 需要总输出少 40 %,即模型在同样的任务里少写 40 % 的 token(少写解释、少写重复复验、少贴长 diff)。那不是调度能做到的,是**拿任务质量换的**,与「速度不能以牺牲任务质量为代价」直接冲突。所以声明:在「不动请求体、不动推理档、不牺牲正确率」的约束下,**可达区间是 0.80–0.85×**,逐家族最优 0.59–0.71×;再往下必须走被明令禁止的杠杆(提高 max_tokens 压缩比、砍推理档、跳过复验)。这条写在这里,是为了以后不再有人拿 0.60× 当目标返工。
1628
1652
 
1629
- ## 质量对等:132 对里断言 1959/1960 对 1960/1960
1653
+ ## 质量对等:138 对里断言 2037/2038 对 2038/2038(2026-10-28 更新)
1654
+
1655
+ > 本节表格与结论已按 **12 块 138 对**现行口径重算;下面「132 对」的那段历史正文保留,只用来说明当时的口径。
1656
+
1657
+ - 断言通过数:`off` **2038/2038(100.00 %)**,`def15` **2037/2038(99.951 %)**,差 **1 条断言**。
1658
+ - 逐对看:137/138 对两臂断言数完全相同;唯一不同的还是 `W1 / ledger / pair 1`(`def15` 21/22,`off` 22/22),即 `RX` 那格题面欠明确的历史样本,不是新出现的回归。
1659
+ - 产物精确性 `exact`:两臂都是 138/138。
1660
+ - 结论只能说到这里:**在这 10 个家族、138 个任务上,速度不是用正确率换来的**;1 条断言的分差在统计上无法与噪声区分(也无法证明「绝对无损」),所以对外表述用「断言对等,1/2038 差异」而不是「零质量损失」。
1661
+
1662
+ ## 质量对等(历史口径):132 对里断言 1959/1960 对 1960/1960
1630
1663
 
1631
1664
  「快了 18%」必须配一条质量口径,否则就是拿正确率换的。同一批 132 个有效配对逐对核对隐藏断言:
1632
1665
 
@@ -1811,3 +1844,105 @@ python3 bench/suite-run.py --tasks debugfix,ledger,codeimpl --arms off,def15,bud
1811
1844
  两个家族合并(14 对):**几何 1.203×、中位 1.100×、7/14 更快、步数 1.37×、输出 1.21×**。migrate 的 p2(9→33 步)说明小改策略在「多处改动」任务上会失控:每处都付一次完整回合的固定开销(约 32 % 的单步时间不在模型调用里),回合数一涨总时间必涨。
1812
1845
 
1813
1846
  结论:`preferPatch` 保持默认 `false`,不进入默认路径;两族合并 1.203× 属于「明确更差」,不是噪声。若要继续,唯一有意义的方向是给它一个「本轮已经出现 ≥2 次小改就停手」的止损条件,而不是默认打开——那属于新机制,需要新的同块诊断,不在这条结论内。
1847
+
1848
+ ## 压缩提醒的触发时点(`disciplineAfterSteps` 2 vs 4):13 对合并 0.625×,省的是往返次数而不是正文(2026-10-28)
1849
+
1850
+ **问题。** `outputDiscipline: late` 那句提醒(「剩下的话只写下一步要用的」)默认第 4 步入场。之前试过改变**措辞强度**(`odt15`:全程更紧的口径,W1 12 对实测 1.213× 更慢,见上文),结论是「开局就压措辞有害」。但那是**换说法**,不是**换时点**:同一句话、同样的字数、只把入场点从第 4 步提前到第 2 步,从没测过。`disciplineAfterSteps` 就是这个旋钮。
1851
+
1852
+ **臂。** 两臂其余字段完全一致(`gear: auto`、`outputDiscipline: late`、`preferPatch: false`、无 `disciplineRepeatAtSteps`);`d2` = `disciplineAfterSteps: 2`,`def15` = 用内置默认 4。所以唯一差别是那一句话在第几步注入。
1853
+
1854
+ **说法要收窄:它是砍掉爆走,不是普遍变短。** `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] 是收尾更齐,不是普遍减步。
1855
+
1856
+ **先证明它真的提前了。** 直读会话 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 定位)。
1857
+
1858
+ **结果(配对,treatment / baseline < 1 才快;几何/中位/合计,算术均值禁用)。**
1859
+
1860
+ | 块 | 家族 | 对 | 几何 | 中位 | 合计 | 赢面 | 步数几何 | 输出几何 | 范围 |
1861
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
1862
+ | `D2_DBG` | debugfix | 7 | **0.535×** | 0.446× | 0.524× | 5/7 | 0.833× | 0.549× | 0.352–1.072 |
1863
+ | `D2_MIG` | migrate | 6 | **0.751×** | 0.738× | 0.713× | 4/6 | 0.887× | 0.700× | 0.229–1.724 |
1864
+ | 合并 | 2 家族 | 13 | **0.625×** | 0.551× | 0.680× | 9/13 | 0.857× | 0.614× | 0.229–1.724 |
1865
+
1866
+ 墙钟合计 1963 s → 1335 s。26 行全 `exact`、全 `green`,逐格断言 `debugfix` 13/13、`migrate` 14/14 满,**没有一格质量下降**。
1867
+
1868
+ **机制:不是「正文写短了」。** 同一批 transcript 里,`debugfix` 每会话正文总量 `def15` 109 字符、`d2` 54 字符(本身就近乎为零),`migrate` 118 → 53 —— 正文差几十字节,无法解释墙钟 0.535×。真正的差别在**往返次数**:
1869
+
1870
+ | 块 | 臂 | 步数/会话 | 工具调用/会话 | 参数字符/会话 | 参数字符/步 |
1871
+ | --- | --- | --- | --- | --- | --- |
1872
+ | debugfix | `def15` | 5.43 | 9.57 | 7 359 | 1 356 |
1873
+ | debugfix | `d2` | 4.43 | **5.43** | 5 526 | 1 248 |
1874
+ | migrate | `def15` | 16.17 | 17.17 | 16 002 | 990 |
1875
+ | migrate | `d2` | 12.33 | **12.67** | 13 574 | 1 101 |
1876
+
1877
+ 每步的产出体量基本不变(±10 %),调用次数少了 26–43 %,与会话输出的 0.614× 同向。这与合池结论一致:速度来自**少做几遍、少复验几遍**,不是把同样的活写得更短。
1878
+
1879
+ 再往下拆一层:墙钟 = 步数 × 每步时长,而这两项**都在降**(`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 次/步,步骤自然短。所以只有两个真正独立的量:**回合数**和**每回合里的往返数**。
1880
+
1881
+ 按调用类别拆开,少掉的**不是验证**:`debugfix` 两臂都是 7/7 会话跑了测试(`verify` 13→8 次,第一次验证仍在第 3–4 次调用),`migrate` 两臂本来就不自测(1→0);真正塌下去的是**改文件的反复**(`edit` 33→7 次)和**回头再读**(`migrate` 的 `inspect` 63→41 次)。也就是说提醒提前之后,模型更少「改一版→再看一眼→再改一版」,而不是「不检查就交」——隐藏测试逐格全过与此一致。
1882
+
1883
+ **赢在哪:切长尾,不是匀速。** 两对反向(`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 %」。
1884
+
1885
+ **与 `odt15` 的 1.213× 不矛盾。** `odt15` 是全程换措辞(从第 1 步起一直紧),代价是早期步骤本来短、被压成更多往返;`d2` 是同一句话只提前到第 2 步,措辞、字数、冷却期都没变。两者是不同变量,不能互相引用。
1886
+
1887
+ **预注册:把默认从 4 改成 2 的通过判据(先写判据,`D2_IMPL` 数据还没出来)。** 只认 `codeimpl`(4–13 步,中等长度,是已证明受益的家族)8 对:
1888
+
1889
+ 1. **8 对全有效**(`exit == 0 and steps >= 1 and out > 0 and exact and not leak`),且每格 `green`。
1890
+ 2. **几何 ≤ 0.95×、中位 ≤ 1.00×、赢面 ≥ 5/8**:三家族(debugfix 0.535×、migrate 0.751×、codeimpl)不得出现方向翻转。`codeimpl` 若 > 1.00×,说明收益只属于「回合短」的家族,默认**不动**(维持 4),这条线判定为家族相关。
1891
+ 3. **三家族合并(21 对)几何 ≤ 0.85×、中位 ≤ 0.90×**。
1892
+ 4. **下发必须命中**:`d2` 臂 ≥ 7/8 会话在第 2 步收到(`codeimpl` 有 0 个会话不到 2 步才行;目前 `codeimpl` 16 个 `def15` 会话步数最少 4,够)。
1893
+ 5. 质量:任何一格 `exact`/`green` 倒退 ⇒ 不通过(速度不能以质量为代价)。
1894
+
1895
+ 全部满足才改默认并发版;否则保持 4,并把「时点」这条线记为家族相关、不再扫。**不因为「几何好看」就改默认**:`odt15` 当年就是被单个家族的好看数字骗过一次。
1896
+
1897
+ **为什么还要扫 3/6/8。** 时点如果是连续的,最优点应该能夹出来;`d3`/`d6`/`d8`/`dr8` 四个臂(12 格/块,`migrate`)正在跑,只作为曲线定位,不参与发版判据。
1898
+
1899
+ **曲线第一个补点 `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`。
1900
+
1901
+ **发版判据块 `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`。**
1902
+
1903
+ 原因不是「提醒这句没用」,而是**同一个时点在两族里方向相反**:`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。
1904
+
1905
+ ## 预注册:条件入场 `disciplineEarlyCalls`(先写判据,数据未出,2026-10-28)
1906
+
1907
+ **为什么不是放弃提前,而是给它加条件。** `D2_IMPL` 的结论是「提前这一步本身好不好,取决于这个回合会不会走远」。那就把它明说出来:只在**回合已经显出会走远**的时候才提前。可用的预测信号必须在第 2 步之前就能读到、且是 harness 自己的机械量——用**第 1 步已经发生的工具调用次数**。基线 `def15` 的实测分布(全部来自本仓库 run 目录的 transcript):
1908
+
1909
+ | 家族 | 会话 | 第 1 步调用数 | 分布 |
1910
+ |---|---|---|---|
1911
+ | `debugfix` | 7 | 2 | 2,2,2,2,2,2,2 |
1912
+ | `migrate` | 6 | 2 | 2,2,2,2,2,2 |
1913
+ | `codeimpl` | 8 | 1 | 1,1,1,1,1,1,1,1 |
1914
+
1915
+ **这条信号在三个家族里是干净分开的**:单文件实现任务第 1 步只发 1 次调用(8/8),多文件/调试任务发 2 次(13/13)。所以新旋钮 `disciplineEarlyCalls`(默认 0 = 不加条件,即 1.5.12 行为):给正数 N 时,`disciplineAfterSteps` 的提前只在「**进入当前步之前**本回合已发出 ≥ N 次工具调用」时生效,否则回退到内置第 4 步。计数刻意扣掉当前步自己的调用(`turnCalls - stepCalls`),否则一步可以先付钱再买票。
1916
+
1917
+ **离线复算这条常量(数据未出,先验证可操作性)**:对已录下的基线会话逐条重放闸门 —— 只读第 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` 5 个会话的第 1 步调用数是 `[2,1,1,1,1]`,闸门只放 1/5 进来 —— ledger 即使喜欢提前,这条闸门也**故意不去赚**它(该块还未跑满登记对数,此处不引用比值)。所以这条闸门的定位是「不伤」,不是「多赚」:它只为挡住 `codeimpl` 那种「第 1 步还在定位」的回合。
1918
+
1919
+ **臂**:`d2g` = `outputDiscipline: late, disciplineAfterSteps: 2, disciplineEarlyCalls: 2`(其余同 `def15`)。**块**:`D2G_IMPL`(`codeimpl` 8 对)、`D2G_DBG`(`debugfix` 7 对)、`D2G_MIG`(`migrate` 6 对),基线均为 `def15`,共 21 对。**通过判据**(五条全满足才建议把默认改成 `d2g`):
1920
+
1921
+ 1. `D2G_IMPL` 8 对:几何 ≤ 0.95×、赢面 ≥ 5/8 —— 不得复现 1.073×。按信号分离,这一族应基本退化成 `def15`(几何 ≈ 1.00×),**退化即可,不要求它变快**。
1922
+ 2. `D2G_DBG` 7 对:几何 ≤ 0.80× —— 允许从 0.535× 回吐,但必须仍明显快。
1923
+ 3. `D2G_MIG` 6 对:几何 ≤ 0.90× —— 0.751× 的收益要留住大半。
1924
+ 4. 合并 21 对:几何 ≤ 0.85×、中位 ≤ 0.90×。
1925
+ 5. 质量零倒退;下发按条件命中:`D2G_IMPL` 8/8 仍在**第 4 步**收到,`D2G_DBG`/`D2G_MIG` 8/8 与 7/7 在第 2 步收到。
1926
+
1927
+ 任何一条不过 ⇒ 这条线判为「时点不可救」,默认永久停在 4,不再追。
1928
+
1929
+ ## 预注册:`disciplineRepeatAtSteps`(第二次压缩提醒)的通过判据(2026-10-28,先写判据再跑)
1930
+
1931
+ **为什么是它。** 合并块里唯一被证明有效的机制,是 `outputDiscipline: late` 在第 4 步注入的那一句「剩下的话只写下一步要用的」。它**每回合只发一次**。可是合池的逐格数据显示,回合越长,赢面越小:`debugfix` 1.032×、`perfrefactor` 1.009×、`csvpipe` 0.982×,而 `shellpipeline` 0.699×、`codeimpl` 0.710× —— 长的回合里,第一次提醒之后的那些步又把输出写长了。这不是新机制,同一个机制的第二枪:同一句话,在更晚的一步再说一次,每回合仍然最多一次。
1932
+
1933
+ **旋钮。** `disciplineRepeatAtSteps`(默认 `0` = 不发,行为逐字节不变)。`dr8` 臂:`disciplineAfterSteps: 4`(与默认同入场点)+ `disciplineRepeatAtSteps: 8`,所以两臂的唯一差别就是第 8 步的那句话。
1934
+
1935
+ **为什么是第 8 步而不是更晚。** 提醒要到得了才算测到。已测 `migrate` 的 16 个 `def15` 有效会话里步数中位 13、15/16 走到第 8 步,但只有 10/16 走到第 12 步——第 12 步发不出去的会话会变成「看起来没效果」的假空。所以入场点 4 + 重复 8(间隔 4 步,中间隔着冷却期),预期 15/16 命中。
1936
+
1937
+ **先验证提醒真的下发。** 逐会话搜确切字符串 `still long. Keep`,`dr8` 必须 ≥1、`def15` 必须 0。任一格 0 命中 ⇒ 该格记为「未测到」而不是「更慢」。
1938
+
1939
+ **判据(配对,treatment / baseline < 1 才是快):**
1940
+
1941
+ 1. **至少 6 对有效配对**(`migrate`,同一块 `DR12_MIG`,两臂交错)。有效 = `exit == 0 and steps >= 1 and out > 0 and exact and not leak`。
1942
+ 2. **几何均值 ≤ 0.95×,且中位 ≤ 1.00×**。只报几何/中位/合计比,算术均值禁用(重尾)。
1943
+ 3. **赢面至少 4/6**,否则即使几何更好也记为「不稳定」。
1944
+ 4. **输出 token 的几何均值 ≤ 0.95×** 才允许说「赢在输出变短」;如果墙钟变好而输出没变短,那就是采样噪声,判空。
1945
+ 5. **质量不得倒退**:`exact` 全对,`green` 不少于对侧臂;任何一格质量下降 ⇒ 直接不通过(速度不能以质量为代价)。
1946
+ 6. 不通过就维持默认 `0`,并在文档里写「第二次提醒无效/有害」,不再重试第三次。
1947
+
1948
+ **已知上限。** 单块 n=6 的检出下限约 ±15 %(见「空转对照」一节:同臂对同臂 9 对的范围是 0.889–1.342×)。所以这里只认「几何 ≤ 0.95× 且赢面 4/6 以上」这种**远大于噪声**的结果;0.96–1.04× 一律判空。
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 未发版(在跑 `D2G` 判据,未 bump 版本)
4
+
5
+ - **新旋钮 `disciplineEarlyCalls`(默认 `0`,行为不变)**:与 `disciplineAfterSteps` 配合,给一个正数 N 时,提前的压缩提醒只对「进入当前步之前本回合已发出 ≥ N 次工具调用」的回合生效,否则回落到内置第 4 步。计数扣掉当前步自己的调用(`turnCalls - stepCalls`),一步不能先付钱再买票。依据是 `D2_IMPL` 的失败与三家族的信号分离(见下条);`0` 走的是 1.5.12 的逐字节相同路径。判据块 `D2G_IMPL` / `D2G_DBG` / `D2G_MIG`(21 对)正在跑,**本版不对它的效果作任何声明**。
6
+ - **`disciplineAfterSteps: 2` 的预注册判据块 `D2_IMPL` 判为 HOLD,默认维持 4**:`codeimpl` 8 对(16 行)几何 **1.073×**、中位 **1.129×**、3/8 更快、步数几何 **1.232×**(提前反而多走步)、范围 0.374–2.688×,第 2 条判据三项全挂。同一时点在 `debugfix` 是 0.535×、`migrate` 是 0.751× —— 方向按家族翻转。`codeimpl` p4 的调用序列:`def15` 5 步 4 次(`write` → python 校验 → `ls && py_compile` → `rm -rf`),`d2` 8 步 7 次(多出 `pwd && ls`、两次同样的 python 校验、`ls && find`):在本来就最短的直线上,提前提醒**加**了探索与重复校验。合并 21 对的 0.768× / 0.787× 好看,但不能压过判据 2,不引用为发版理由。
7
+ - **提前提醒的分离信号(免费、第 2 步之前可读)**:基线 `def15` 第 1 步工具调用数 —— `debugfix` 7/7 是 2、`migrate` 6/6 是 2、`codeimpl` 8/8 是 1。单文件实现任务第 1 步只发 1 次调用,多文件/调试任务发 2 次,所以「调用密度」就是「这个回合会不会走远」的机械代理。
8
+ - **`batchVerify` 淘汰(保留默认 `false`)**:`bvl15` 16 对几何 **1.188×**,把验证合并成一次并没有省下回合,反而多了一次「等这批改完」的往返。
9
+ - **`disciplineRepeatAtSteps`(默认 `0`,未证实)**:同一句话在更晚一步再提醒一次,逐格数据见 `BENCHMARK.md`,默认不发。
10
+ - **机制拆解入档 `BENCHMARK.md`**:调用/步 90.7 % 是单调用(`{1:479, 2:43, …}`)⇒「合批」这条杠杆已关闭;bash 里 inspect 占 0.171;助手文本只占输出字符 1.2 % ⇒ 文本不是杠杆;`D2` 省的是 `edit` 33→7(−79 %)与回头 `inspect`(`migrate` 63→41,−35 %),验证次数两臂 7/7 都在;每步时长几何 DBG 0.731× / MIG 0.828× 是「调用变少」的后果,不是第三机制。
11
+
3
12
  ## 1.5.12
4
13
 
5
14
  - **合池口径定为 138 对(纯数据与文档,无行为变更)**:12 个干净块、138 对有效配对、10 个家族 —— 总墙钟 **0.8234×**(95 % 区间 0.7418–0.9133)、逐格中位 **0.7994×**(0.7026–0.8850)、几何均值 0.8248×(0.7532–0.9001)、输出 0.8179×、步数 0.8062×、**93/138 对更快**、符号检验 **p = 0.0001**。质量同口径结论只有「断言对等」:`off` 2038/2038、默认 2037/2038,唯一差异是一格 21/22 —— 不写成「零质量损失」。README 与市场描述的 132 对 / 0.825× 口径一并更新;算术均值 0.872 及其区间**禁用**(区间上界越 1.0)。
package/README.en.md CHANGED
@@ -47,14 +47,14 @@ No model switch, no `reasoningEffort` rewrite, no request-field injection (`serv
47
47
  **1.5.0 ships no step-based finish line by default.** `finishLineSteps` and `stepBudget` both default to `0`: there is no "remind me at step 10" and no "stop at step 30". When to finish is decided by task state and completion evidence — repeating an already-passing check verbatim against the same artifact suggests reusing the result and spending the step on what is left; several steps with no new result suggests changing the method (a reminder only, it never ends the turn); a stuck tool is cancelled by the watchdog and awaited until it really stops. A finished task is reported normally; a reminder never votes to stop for you.
48
48
 
49
49
  **1.5.1 clean rebaseline (10 families, 30 complete units, hidden tests 60/60 green).** Totals over whole completed tasks with no runs dropped except rows that leaked fixture material: off **3434.7 s / 591 244 tokens / 390 steps**; the default arm **0.72× wall / 0.74× output / 0.71× steps**; per-cell median **0.64×**, mean 0.84×, 90% interval [0.59, 0.79]. The gain sits in tasks that otherwise re-run the same check (`migrate` 0.45×, `shellops` 0.57×, `codebase` 0.55×, `stategraph` 0.64×, `ledger` 0.64×); short tasks are unstable — `perfrefactor` 1.18×, `csvpipe` 1.12×, and `debugfix` wrote 2.01× the output — because they have no repeated verification to save and the reminder itself costs a turn. This round also fixed two measurement defects that distorted the old numbers (run directories used to live inside the repo; metrics used to be sliced by byte offset). The old `0.86×` is retracted; see `1.5.1` and `1.5.0 rebaseline (retracted)` in [BENCHMARK.md](BENCHMARK.md).
50
- **Replication block W5 (2026-10-26, 19 complete units).** Three arms compared inside one block: `off` 1855.5 s, the default arm 0.73× wall / 0.77× output / 0.89× steps, matching 0.72× from 1.5.1. The `batchVerify` arm in the same block recorded 0.84×, but its notice **was never actually delivered** in any of the 20 sessions (0 hits when searched session by session), so `batchVerify` is **untested**, not slower and not falsified; it stays off, to be retested after the fix. One row leaked (`shellpipeline` p1 `bv15` read the hidden test and the reference solution) and is named and excluded; the runner now makes `hidden/` and `ref/` unreadable for the duration of a block. See "复现块 W5" in [BENCHMARK.md](BENCHMARK.md) and protocol item 12 in [bench/README.md](bench/README.md).
50
+ **Replication block W5 (2026-10-26, 19 complete units).** Three arms compared inside one block: `off` 1855.5 s, the default arm 0.73× wall / 0.77× output / 0.89× steps, matching 0.72× from 1.5.1. The `batchVerify` arm in the same block recorded 0.84×, but its notice **was never actually delivered** in any of the 20 sessions (0 hits when searched session by session), so `batchVerify` was **untested** then — not slower and not falsified. It was later retested with the wiring fixed and **dropped**: `codeimpl` 8 pairs 1.478×, `shellpipeline` 8 pairs 0.956×, pooled 16 pairs **1.188×**, median 1.148×, only 7/16 faster, all green and the notice verifiably delivered in 8/16 sessions. See the BV8 section of [BENCHMARK.md](BENCHMARK.md). One row leaked (`shellpipeline` p1 `bv15` read the hidden test and the reference solution) and is named and excluded; the runner now makes `hidden/` and `ref/` unreadable for the duration of a block. See "复现块 W5" in [BENCHMARK.md](BENCHMARK.md) and protocol item 12 in [bench/README.md](bench/README.md).
51
51
  **Replication block W7 (2026-10-26, 17 valid units).** The default arm measured **0.73×** wall / 0.75× output / 0.59× steps (longest cell 36 steps → 17), matching 0.72× in 1.5.1 and 0.73× in W5; the three blocks pool to **0.74×** over 65 valid units. `openBatch` in the same block was 0.91× and 1.136× head-to-head against the default, so W6's 0.77× did not replicate and it is **dropped**. The block also produced 7 crash rows (`exit=1`, 1 step, 0 output at session startup) which voided 3 units, named in the doc; from this block on a unit only counts when `exit == 0 && steps >= 1 && output > 0 && grading completed && no leak`, so "all green" always means all valid units green (1.5.1 29/29, W5 19/19, W7 17/17). See "复现块 W7" in [BENCHMARK.md](BENCHMARK.md).
52
52
 
53
53
  **Screen W10 + replication W11 (2026-10-26, 23 valid units combined): `workspaceMap` only half delivered, stays off by default.** 1.5.2 adds one knob, off by default: before the first action the harness hands the model a one-shot inventory of the working tree (depth 2, ≤60 entries, dependency and build directories skipped) so it does not have to spend a step listing files itself. Both blocks compare it with `def15` inside the same block: across 23 valid units **wall 0.927×, output 0.896×, steps 1.099×**, per-cell median 0.791×, 13/23 faster, zero gate reds. Half of the mechanism is confirmed: output really is 10 % lower (0.90× and 0.89× in the two blocks). But steps rise 10 % and the tail gets worse — p90 559 s versus 369 s and max 714 s versus 510 s, with the same reverse cells in both blocks (`stategraph` at 1.4×–1.77× in each). **Because a long task must not get slower, it is not promoted**: the 1.5.2 default behaves exactly like 1.5.1, and the knob ships with its numbers. The same round accounted for the remaining headroom: repeated verification is only 6 % of wall, tool results have a 1.2 kB median, rewriting an already written file is 0.8 % — so **0.67× is not reachable inside the "do not touch `reasoningEffort`, add no request fields" constraints**. See "复现块 W11" and "0.67× 在现有约束下的可达性" in [BENCHMARK.md](BENCHMARK.md).
54
54
 
55
55
  **Pre-release re-check (REL16, 10 units)**: the then-current default was run against `off` once more, purely to ask whether shipping had damaged quality. Both arms came back **10/10 green with identical per-task pass counts** (14/14, 22/22, 18/18, 10/10, 14/14) and no leaks; wall summed to **0.967×**, per-cell median 0.999×, faster in 3/5 cells. That is one pair at n=5, a wider noise band than the pooled total, so it is reported as a re-check and does not replace the pooled 0.798× figure — see [BENCHMARK.md](BENCHMARK.md).
56
56
 
57
- **Faster configurations that failed the gate stay ablation arms only.** `fl10` (a step-10 finish reminder) is the fastest configuration measured, but it suppresses re-verification after the tests pass and once went red on a cell where both the baseline and the default were green, so it stays an ablation arm (those numbers came from a retracted batch and are kept only as a decision record). `openBatch` (one opening reminder to put independent calls in one block) beat the default 0.77× vs 0.93× in the W6 screen, but an independent W7 rerun brought it back to 0.91× and 1.136× head-to-head — **not replicated, so dropped**. `batchVerify` was 0.84× against the default inside W5 and is likewise not promoted. **The defaults therefore stay as they are**: no step-based finish line, `stepBudget: 0`, and all three mechanisms off. Per-cell data and gate results are in the `H2`/`H3` sections of BENCHMARK.
57
+ **Faster configurations that failed the gate stay ablation arms only.** `fl10` (a step-10 finish reminder) is the fastest configuration measured, but it suppresses re-verification after the tests pass and once went red on a cell where both the baseline and the default were green, so it stays an ablation arm (those numbers came from a retracted batch and are kept only as a decision record). `openBatch` (one opening reminder to put independent calls in one block) beat the default 0.77× vs 0.93× in the W6 screen, but an independent W7 rerun brought it back to 0.91× and 1.136× head-to-head — **not replicated, so dropped**. `batchVerify` was 0.84× against the default inside W5 with the notice never delivered (untested then); with the wiring fixed the BV8 block measured **1.188× slower** over 16 pairs (`codeimpl` 1.478×, `shellpipeline` 0.956× — inconsistent direction), so it is **dropped** and stays `false`. **The defaults therefore stay as they are**: no step-based finish line, `stepBudget: 0`, and all three mechanisms off. Per-cell data and gate results are in the `H2`/`H3` sections of BENCHMARK.
58
58
 
59
59
  **The retired default: `finishLineSteps: 10`.** On 7 families and 14 pairs it produced a median **1.86×**, 13/14 pairs faster (sign test **p = 0.002**), output **−52 %**, steps **−10.0**, hidden tests **14/14 green**. That line was genuinely fast, and the cost was real too: it fired at step 10 even on a long task that was still making progress. So 1.5.0 demotes it to an ablation arm (`fl10`), off by default.
60
60
 
package/README.md CHANGED
@@ -47,14 +47,14 @@
47
47
  **1.5.0 的默认不做步数收尾。** `finishLineSteps` 与 `stepBudget` 默认都是 `0`:没有“第 10 步提醒收尾”,也没有“30 步停止”。结束由任务状态和完成证据决定 —— 同一份产物上逐字重复一条已通过的检查,建议复用结果、把这一步留给剩下的工作;连续几步没有新结果,建议换方法(只是提醒,不结束回合);工具卡住,看门狗取消它并等它真正停下。任务做完就正常报告,提醒不会替你判停。
48
48
 
49
49
  **1.5.1 干净重基线(10 家族、30 个完整单元、隐藏测试 60/60 全绿)**:不剔任何运行(只看产物里有没有泄漏标记)、按整任务总量加总 —— 关闭 **3434.7 s / 591 244 token / 390 步**;默认臂 **0.72× 墙钟 / 0.74× 输出 / 0.71× 步数**;逐格中位 **0.64×**、均值 0.84×、90 % 区间 [0.59, 0.79]。收益集中在反复重跑同一条检查的任务(`migrate` 0.45×、`shellops` 0.57×、`codebase` 0.55×、`stategraph` 0.64×、`ledger` 0.64×);短任务收益不稳定,`perfrefactor` 1.18×、`csvpipe` 1.12×、`debugfix` 输出反而 2.01×——短任务本来就没有可省的重复验证,提醒本身也是成本。这一轮同时修掉了两个会让旧数字失真的测量缺陷(运行目录曾在仓库内、指标曾按字节偏移切片),旧口径的 `0.86×` 已撤回,详见 [BENCHMARK.md](BENCHMARK.md) 的「1.5.1」与「1.5.0 重基线(已撤回)」。
50
- **复现块 W5(2026-10-26,19 个完整单元)**:同一块里三臂直接对照,`off` 1855.5 s,默认臂 0.73× 墙钟 / 0.77× 输出 / 0.89× 步数,与 1.5.1 的 0.72× 一致。同块的 `batchVerify` 臂记到 0.84×,但那一臂的提醒**实际一次都没下发**(20 个会话逐条检索命中 0),所以 `batchVerify` 是**未测**、不是变慢也不是被证伪,默认保持关闭、修复后重测。这一块还有 1 行泄漏(`shellpipeline` p1 的 `bv15` 读到隐藏测试与参考解),已点名剔除;跑批器现在开跑前会把 `hidden/`、`ref/` 置为不可读,详见 [BENCHMARK.md](BENCHMARK.md) 的「复现块 W5」与 [bench/README.md](bench/README.md) 协议第 12 条。
50
+ **复现块 W5(2026-10-26,19 个完整单元)**:同一块里三臂直接对照,`off` 1855.5 s,默认臂 0.73× 墙钟 / 0.77× 输出 / 0.89× 步数,与 1.5.1 的 0.72× 一致。同块的 `batchVerify` 臂记到 0.84×,但那一臂的提醒**实际一次都没下发**(20 个会话逐条检索命中 0),所以 `batchVerify` 当时是**未测**、不是变慢也不是被证伪。**后来修好接线重测,结论是淘汰**:`codeimpl` 8 对几何 1.478×、`shellpipeline` 8 对 0.956×,合并 16 对 **1.188×**、中位 1.148×、只有 7/16 更快,全绿且提醒确实下发(8/16 会话命中)—— 所以不是没测到,是家族方向不一致、合起来更慢,默认保持 `false`,见 [BENCHMARK.md](BENCHMARK.md) 的「`batchVerify` 复测块 BV8」。这一块还有 1 行泄漏(`shellpipeline` p1 的 `bv15` 读到隐藏测试与参考解),已点名剔除;跑批器现在开跑前会把 `hidden/`、`ref/` 置为不可读,详见 [BENCHMARK.md](BENCHMARK.md) 的「复现块 W5」与 [bench/README.md](bench/README.md) 协议第 12 条。
51
51
  **筛选块 W10 + 复现块 W11(2026-10-26,合并 23 个有效单元):`workspaceMap` 只兑现一半,默认关闭。** 1.5.2 加了一个默认关闭的旋钮:会话第一次动手前,把工作目录的清单(深度 2、≤60 条、跳过依赖与构建目录)先交给模型一次,省掉它自己 `ls`/`find` 的那一步。两块同块对照 `def15`:合并 23 个单元 **墙钟 0.927×、输出 0.896×、步数 1.099×**,逐格中位 0.791×、13/23 格更快、隐藏测试零红。方向被证实一半——输出确实少 10 %(两块分别是 0.90×、0.89×);但步数反而多 10 %,而且尾部变差:两臂 p90 是 559 s 对 369 s、最慢 714 s 对 510 s,W10 与 W11 都出现同一形状的反向格(`stategraph` 两轮都 1.4×–1.77×)。**按「长任务不能变慢」的门槛它不进默认**,1.5.2 的默认行为与 1.5.1 完全相同,旋钮与数字一起公开。这一轮同时把余量算清了:重复验证只占 6 % 墙钟、工具结果中位 1.2 kB、重复写文件占 0.8 %,所以 **0.67× 在「不动 `reasoningEffort`、不加请求字段」的约束内不可达**,细节见 [BENCHMARK.md](BENCHMARK.md) 的「复现块 W11」与「0.67× 在现有约束下的可达性」。
52
52
 
53
53
  **复现块 W7(2026-10-26,17 个有效单元)**:默认臂 **0.73×** 墙钟 / 0.75× 输出 / 0.59× 步数(最长一格 36 步 → 17 步),与 1.5.1 的 0.72×、W5 的 0.73× 一致:三块合并 65 个有效单元 **0.74×**。同块的 `openBatch` 是 0.91×,对默认头对头 1.136×,W6 的 0.77× 没有复现,**淘汰**。这一块还有 7 行运行崩溃(会话启动阶段 `exit=1`、1 步、0 输出),3 个单元因此作废并点名;从这一块起判据要求 `exit == 0 && 步数 ≥ 1 && 输出 > 0 && 判分完成 && 未泄漏` 才计数,所以「全绿」一律指有效单元全绿(1.5.1 29/29、W5 19/19、W7 17/17)。详见 [BENCHMARK.md](BENCHMARK.md) 的「复现块 W7」。
54
54
 
55
55
  **发版前复核(REL16,10 个单元)**:用当时的默认再对 `off` 跑一块,只查「质量有没有被发版弄坏」。结果两臂 **10/10 全绿、逐任务通过数完全相同**(14/14、22/22、18/18、10/10、14/14),无 leak;墙钟合计 **0.967×**、逐格中位 0.999×、3/5 格更快。单对 n=5,噪声带比总量宽,所以这条只作复核,不替换合池 0.798× 的总口径,详见 [BENCHMARK.md](BENCHMARK.md)。
56
56
 
57
- **更快但没过门禁的配置只作消融臂。** `fl10`(第 10 步提醒收尾)是实测最快的配置,但它会压掉「测试通过后又改了代码」的必要复验,且出现过基线与默认都绿而它红的一格,所以只是消融臂(这些数字出自已撤回的批次,只保留决策记录)。`openBatch`(开局发一次「把独立调用并进同一块」)在 W6 筛查里以 0.77× 快过默认的 0.93×,但 W7 独立复测回到 0.91×、头对头 1.136×,**没有复现即淘汰**。`batchVerify`(改完一批文件再跑一次覆盖检查)在 W5 同块对照里记到 0.84×,但那一臂的提醒实际零下发,测到的不是机制本身,所以它算**未测**,默认保持关闭。**所以默认不变**:不按步数收尾、`stepBudget: 0`、上述机制全关。单元级数据与门禁逐条见 BENCHMARK 的 `H2`/`H3`。
57
+ **更快但没过门禁的配置只作消融臂。** `fl10`(第 10 步提醒收尾)是实测最快的配置,但它会压掉「测试通过后又改了代码」的必要复验,且出现过基线与默认都绿而它红的一格,所以只是消融臂(这些数字出自已撤回的批次,只保留决策记录)。`openBatch`(开局发一次「把独立调用并进同一块」)在 W6 筛查里以 0.77× 快过默认的 0.93×,但 W7 独立复测回到 0.91×、头对头 1.136×,**没有复现即淘汰**。`batchVerify`(改完一批文件再跑一次覆盖检查)在 W5 同块对照里记到 0.84×,但那一臂的提醒零下发,当时只能算**未测**;接线修好后在 BV8 复测块实测 16 对合并 **1.188× 更慢**(`codeimpl` 1.478×、`shellpipeline` 0.956×,方向不一致),**淘汰**,默认保持 `false`。**所以默认不变**:不按步数收尾、`stepBudget: 0`、上述机制全关。单元级数据与门禁逐条见 BENCHMARK 的 `H2`/`H3`。
58
58
 
59
59
  **已退役的默认:`finishLineSteps: 10`。** 1.4.4 用它跑出 7 家族 14 对中位 **1.86×**、13/14 更快(符号检验 **p = 0.002**)、输出 **−52 %**、步数 **−10.0**、隐藏测试 **14/14 全绿**。这条线的速度是真的,代价也是真的:它到第 10 步就要求停止扩大范围,对**仍在推进的长任务**同样开火。所以 1.5.0 把它降级为消融臂(`fl10`),默认关闭。
60
60
 
@@ -48,6 +48,8 @@ The constants live in `AUTO_LIMITS` in `src/auto.js`:
48
48
  | `outputBudget` | 0 | Optional output-token budget: at that many cumulative `outputTokens` (upstream usage) in a turn, one reminder to close the current loop and run the check that proves it; 0 = off, which is the default |
49
49
  | `finishLineSteps` | 0 | Optional soft finish line; 0 = off, which is the default. `10` is the retired 1.4.4 default and is kept only as an ablation arm |
50
50
  | `disciplineAfterSteps` | 4 | Step at which the late output discipline starts |
51
+ | `disciplineRepeatAtSteps` | 0 | Step at which the same compression reminder fires a second time; 0 = never (still at most twice per turn). Unproven, off by default |
52
+ | `disciplineEarlyCalls` | 0 | With a positive N, the `disciplineAfterSteps` early entry applies only to a turn that already made at least N tool calls before the current step, otherwise it falls back to the built-in 4; 0 = unconditional, i.e. the 1.5.12 behaviour |
51
53
  | `waitWindow` | 32 | Recent calls that feed the p90 |
52
54
  | `directiveCooldown` | 3 | Minimum steps between two scheduler directives |
53
55
 
package/docs/REFERENCE.md CHANGED
@@ -48,6 +48,8 @@
48
48
  | `finishLineSteps` | 0 | 可选软收尾线;0 = 关闭,这是默认。`10` 是 1.4.4 的退役默认值,现在只作消融臂 |
49
49
  | `outputBudget` | 0 | 可选输出 token 预算:本轮累计 `outputTokens`(上游 usage)到线提醒一次「收掉当前闭环、跑一次证明它的检查、汇报」;0 = 关闭,这是默认 |
50
50
  | `disciplineAfterSteps` | 4 | 迟到输出纪律从第几步起生效 |
51
+ | `disciplineRepeatAtSteps` | 0 | 同一句压缩提醒在第几步再发一次;0 = 不发(每回合仍然最多两次)。未证实,默认关 |
52
+ | `disciplineEarlyCalls` | 0 | 给正数 N 时,`disciplineAfterSteps` 的提前只对「进入当前步之前本回合已发出 ≥ N 次调用」的回合生效,否则回落到内建 4;0 = 无条件(1.5.12 行为) |
51
53
  | `waitWindow` | 32 | 参与 p90 的最近调用数 |
52
54
  | `directiveCooldown` | 3 | 两条调度指令之间的最小步距 |
53
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-fast-mode",
3
- "version": "1.5.12",
3
+ "version": "1.5.13",
4
4
  "description": "DeepSeek Harness fast mode: same thinking effort, fewer round trips. An adaptive parallel tool fan-out, output discipline that never trims the answer, and a cancellable stall guard. Never changes reasoning effort, the model, or the request body. Provider-agnostic, one dependency (@deepseek-ai/schemastery).",
5
5
  "repository": {
6
6
  "type": "git",
package/src/auto.js CHANGED
@@ -74,6 +74,18 @@ export const AUTO_LIMITS = Object.freeze({
74
74
  directiveCooldown: 3,
75
75
  /** Steps into a turn before compression is worth its one message. */
76
76
  disciplineAfterSteps: 4,
77
+ /** Off by default: no second compression reminder in a long turn. */
78
+ disciplineRepeatAtSteps: 0,
79
+ /**
80
+ * Off by default. When set, an early `disciplineAfterSteps` only applies to a
81
+ * turn that has already made this many tool calls; otherwise the reminder
82
+ * waits for the built-in step. The early reminder is what the turn pays for
83
+ * and what it saves depends on the turn being long, so the call density at
84
+ * that step is the prediction: measured on 21 paired sessions, a one-call
85
+ * first step is a single-file task (8/8 codeimpl) and a two-call first step
86
+ * is a multi-file or debug task (7/7 debugfix, 6/6 migrate).
87
+ */
88
+ disciplineEarlyCalls: 0,
77
89
  /** A write whose body is at least this long is a whole-file rewrite. */
78
90
  patchHintMinChars: 2000,
79
91
  })
@@ -119,7 +131,7 @@ export function noteEntry(state, agent, width) {
119
131
  *
120
132
  * @param {ReturnType<typeof createAutoState>} state
121
133
  * @param {string} agent
122
- * @param {{lateDiscipline?: boolean, stepBudget?: number, finishLineSteps?: number, outputBudget?: number, outputBudgetAfterSteps?: number, openBatch?: boolean, shapeGate?: boolean, scheduler?: boolean, fanoutWidth?: number, batchVerify?: boolean, preferPatch?: boolean}} policy
134
+ * @param {{lateDiscipline?: boolean, stepBudget?: number, finishLineSteps?: number, outputBudget?: number, outputBudgetAfterSteps?: number, openBatch?: boolean, shapeGate?: boolean, scheduler?: boolean, fanoutWidth?: number, batchVerify?: boolean, preferPatch?: boolean, disciplineAfterSteps?: number, disciplineRepeatAtSteps?: number, disciplineEarlyCalls?: number}} policy
123
135
  */
124
136
  export function notePolicy(state, agent, policy) {
125
137
  const entry = entryOf(state, agent)
@@ -131,6 +143,12 @@ export function notePolicy(state, agent, policy) {
131
143
  // 0 keeps the built-in entry step; a positive value moves the one reminder that
132
144
  // has a measured effect (see BENCHMARK: compress-output) earlier or later.
133
145
  entry.disciplineAfterSteps = stepCount(policy?.disciplineAfterSteps, 0, 0)
146
+ // The reminder above is once per turn; a long turn drifts back to prose after
147
+ // it, so an explicit step can repeat the same ask exactly once more.
148
+ entry.disciplineRepeatAtSteps = stepCount(policy?.disciplineRepeatAtSteps, 0, 0)
149
+ // 0 keeps the early reminder unconditional, which is the 1.5.12 behaviour and
150
+ // the arm that measured slower on short turns.
151
+ entry.disciplineEarlyCalls = stepCount(policy?.disciplineEarlyCalls, 0, 0)
134
152
  // 0 is a value, not "unset": it is how a caller asks for no hard stop at all.
135
153
  entry.turnStepBudget = stepCount(policy?.stepBudget, AUTO_LIMITS.turnStepBudget, 0)
136
154
  entry.finishLineSteps = stepCount(policy?.finishLineSteps, AUTO_LIMITS.finishLineSteps, 0)
@@ -229,6 +247,10 @@ function entryOf(state, agent) {
229
247
  lateDiscipline: false,
230
248
  /** One compression reminder per turn, never more. */
231
249
  disciplineSent: false,
250
+ /** Step for the optional repeat of that reminder; 0 = off. */
251
+ disciplineRepeatAtSteps: AUTO_LIMITS.disciplineRepeatAtSteps,
252
+ /** True once the repeat has been sent. */
253
+ disciplineRepeatSent: false,
232
254
  /** Hard stop: the step count that ends the turn with "report now". */
233
255
  turnStepBudget: AUTO_LIMITS.turnStepBudget,
234
256
  /** Soft stop: one "no more scope" reminder at this step, 0 = off. */
@@ -281,6 +303,7 @@ export function noteTurn(state, agent) {
281
303
  entry.stopped = false
282
304
  entry.stepProgress = false
283
305
  entry.disciplineSent = false
306
+ entry.disciplineRepeatSent = false
284
307
  entry.finishLineSent = false
285
308
  entry.outBudgetSent = false
286
309
  entry.openingSent = false
@@ -886,7 +909,20 @@ function directiveFor(entry, stats) {
886
909
  summary: 'auto: group the verification of this edit batch',
887
910
  }
888
911
  }
889
- const disciplineAt = entry.disciplineAfterSteps || AUTO_LIMITS.disciplineAfterSteps
912
+ // An early reminder is a bet on the turn being long. When the caller asks for
913
+ // the bet to be conditional, spend the turn's observed call density as the
914
+ // prediction and fall back to the built-in step when the turn still looks
915
+ // like a single-file task.
916
+ const earlyAt = entry.disciplineAfterSteps || AUTO_LIMITS.disciplineAfterSteps
917
+ // Calls the turn made before the current step: at the moment this line runs the
918
+ // step's own calls have not happened yet, so counting them would let a step pay
919
+ // for its own admission.
920
+ const callsBefore = entry.turnCalls - entry.stepCalls
921
+ const disciplineAt = entry.disciplineEarlyCalls > 0
922
+ && earlyAt < AUTO_LIMITS.disciplineAfterSteps
923
+ && callsBefore < entry.disciplineEarlyCalls
924
+ ? AUTO_LIMITS.disciplineAfterSteps
925
+ : earlyAt
890
926
  if (entry.lateDiscipline && !entry.disciplineSent && stats.turnSteps >= disciplineAt) {
891
927
  // Late engagement: the prompt carried no prose rules, so the only cost of a
892
928
  // short task was its own work. By this step the task is demonstrably long,
@@ -903,6 +939,20 @@ function directiveFor(entry, stats) {
903
939
  summary: 'auto: compress output for the rest of the turn',
904
940
  }
905
941
  }
942
+ const repeatAt = entry.disciplineRepeatAtSteps
943
+ if (entry.lateDiscipline && entry.disciplineSent && !entry.disciplineRepeatSent && repeatAt > 0
944
+ && stats.turnSteps >= repeatAt) {
945
+ // Same ask, shorter: by this step the turn has already been told once, so the
946
+ // reminder only has to re-arm the rule, not teach it.
947
+ entry.disciplineRepeatSent = true
948
+ entry.lastDirectiveStep = entry.steps
949
+ entry.lastDirectiveKind = 'compress-output'
950
+ return {
951
+ kind: 'compress-output',
952
+ text: 'Auto schedule: still long. Keep the remaining steps to the command, the file, the conclusion.',
953
+ summary: 'auto: compress output again for the rest of the turn',
954
+ }
955
+ }
906
956
  const outBudget = entry.outBudget
907
957
  const budgetArmed = entry.steps >= (entry.outBudgetAfterSteps || 0)
908
958
  if (outBudget > 0 && budgetArmed && !entry.outBudgetSent && stats.turnOutTokens >= outBudget) {
package/src/config.js CHANGED
@@ -161,6 +161,26 @@ export const Config = z.object({
161
161
  '压缩指令的入场步 / Compression entry step: `outputDiscipline: late` 时,第几步才提醒「剩下的话只写下一步要用的」;0 = 用内置值(4)。这条提醒是本插件唯一被证明有效的机制(回合变少 + 每步输出变短),所以它的入场点值得单独调:太早会在任务还没展开时就把话掐掉,太晚则前几步的散文已经花掉。0 保持旧行为。',
162
162
  ),
163
163
 
164
+ disciplineEarlyCalls: z
165
+ .number()
166
+ .step(1)
167
+ .min(0)
168
+ .max(20)
169
+ .default(0)
170
+ .description(
171
+ '提前入场的条件 / Early-entry gate: 与 `disciplineAfterSteps` 配合。给一个正数时,提前的入场只对「第 1 步已经发了这么多次工具调用」的回合生效,否则回到内置第 4 步。依据是实测:第 1 步只发 1 次调用的回合是单文件任务(codeimpl 8/8),提前提醒在那一族是 1.073× 更慢;第 1 步发 2 次的是多文件/调试任务(debugfix 7/7、migrate 6/6),那一族提前提醒是快的。0 = 不加条件(1.5.12 行为)。',
172
+ ),
173
+
174
+ disciplineRepeatAtSteps: z
175
+ .number()
176
+ .step(1)
177
+ .min(0)
178
+ .max(80)
179
+ .default(0)
180
+ .description(
181
+ '压缩指令的重复步 / Compression repeat step: `outputDiscipline: late` 的提醒每回合只发一次,第 4 步之后的长回合(10 步以上)输出会重新变长。给一个正数就按那一步再提醒一次「仍然只写下一步要用的」,仍然每回合最多一次。0 = 不发(默认,行为不变)。',
182
+ ),
183
+
164
184
  workspaceMap: z
165
185
  .boolean()
166
186
  .default(false)
package/src/effort.js CHANGED
@@ -258,6 +258,14 @@ export function resolveConfig(raw, overrides) {
258
258
  disciplineAfterSteps: gear.directive
259
259
  ? Math.max(0, Math.floor(number(config.disciplineAfterSteps, 0)))
260
260
  : 0,
261
+ /** Minimum first-step calls for an early reminder to be spent; 0 = unconditional. */
262
+ disciplineEarlyCalls: gear.directive
263
+ ? Math.max(0, Math.floor(number(config.disciplineEarlyCalls, 0)))
264
+ : 0,
265
+ /** Step for the single repeat of the compression reminder; 0 = never. */
266
+ disciplineRepeatAtSteps: gear.directive
267
+ ? Math.max(0, Math.floor(number(config.disciplineRepeatAtSteps, 0)))
268
+ : 0,
261
269
  /** One "batch the opening step" reminder per turn, for short tasks. */
262
270
  openBatch: gear.directive && config.openBatch === true,
263
271
  /** One "send hunks, not whole files" reminder after a large whole-file write. */
package/src/map.js CHANGED
@@ -51,7 +51,9 @@ function walk(root, dir, depth, out) {
51
51
  if (out.length >= MAX_ENTRIES) return
52
52
  if (SKIP.has(entry.name)) continue
53
53
  const abs = path.join(dir, entry.name)
54
- const rel = path.relative(root, abs)
54
+ // The map is read by the model, not by this shell: forward slashes keep one
55
+ // spelling on every platform instead of \\ on Windows.
56
+ const rel = path.relative(root, abs).split(path.sep).join('/')
55
57
  if (entry.isDirectory()) {
56
58
  out.push(`${rel}/`)
57
59
  if (depth < MAX_DEPTH) walk(root, abs, depth + 1, out)
package/src/parallel.js CHANGED
@@ -166,10 +166,19 @@ export function buildShellParallelTool ({ spawn, logger, pool = DEFAULT_POOL, cl
166
166
  }
167
167
  let child
168
168
  try {
169
- child = spawn('bash', ['-lc', job.command], {
169
+ // `-l` starts a login shell, which on Windows re-reads the whole Git bash
170
+ // profile for every job: measured ~850ms of pure startup per job there,
171
+ // enough to blow the parallelism budget the tool exists to prove. The
172
+ // profile contributes nothing to a job, so only POSIX keeps it.
173
+ const shellArgs = process.platform === 'win32' ? ['-c', job.command] : ['-lc', job.command]
174
+ child = spawn('bash', shellArgs, {
170
175
  cwd: jobCwd(exec, job),
171
176
  env: process.env,
172
177
  stdio: ['ignore', 'pipe', 'pipe'],
178
+ // Windows allocates a console for every spawn unless told not to, and
179
+ // that allocation is synchronous on the thread that starts the job:
180
+ // three jobs then take turns instead of overlapping.
181
+ windowsHide: true,
173
182
  })
174
183
  } catch (error) {
175
184
  finish({ label: job.label, command: job.command, exit: null, timedOut: false, error: String(error?.message ?? error), stdout: { text: '', clipped: false }, stderr: { text: '', clipped: false } })