dsh-fast-mode 1.5.11 → 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 +394 -41
- package/CHANGELOG.md +21 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/docs/REFERENCE.en.md +2 -0
- package/docs/REFERENCE.md +2 -0
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/auto.js +59 -3
- package/src/config.js +39 -0
- package/src/effort.js +16 -0
- package/src/map.js +3 -1
- package/src/parallel.js +10 -1
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
|
-
> **这一节是历史记录,数字已被后面的
|
|
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.
|
|
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`
|
|
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` |
|
|
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
|
-
|
|
|
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-
|
|
1459
|
+
## 合池的自助区间与逐任务稳定性(2026-10-28,12 块 138 对)
|
|
1460
1460
|
|
|
1461
|
-
合池点估计 **0.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1484
|
-
| `stategraph` |
|
|
1485
|
-
| `shellops` | 13 | 0.683 | 0.896 | 0.
|
|
1486
|
-
| `codeimpl` |
|
|
1487
|
-
| `migrate` | 15 | 0.739 | 0.849 | 0.53–1.
|
|
1488
|
-
| `
|
|
1489
|
-
| `
|
|
1490
|
-
| `perfrefactor` | 14 | 0.961 | 1.140 | 0.
|
|
1491
|
-
| `debugfix` | 15 | 0.976 | 1.287 | 0.77–1.
|
|
1492
|
-
| `csvpipe` | 13 | 0.983 | 1.014 | 0.54–1.
|
|
1493
|
-
|
|
1494
|
-
-
|
|
1495
|
-
- **中位好看但不稳的**:`codebase` 中位 0.586× 但均值 0.898×、四分位 0.51–1.
|
|
1496
|
-
-
|
|
1497
|
-
- **不许跨块引用的行**:`debugfix` 在 PPX2 这一块里是
|
|
1498
|
-
- **输出口径**:合池输出 0.
|
|
1499
|
-
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
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 对全池的成分统计;结论只用来说方向(正文 ≪ 参数、单调用步占多数),不要当成全池精确占比引用。
|
|
1510
1514
|
|
|
1511
|
-
|
|
1515
|
+
## `batchVerify` 复测块 BV8(2026-10-28):codeimpl 1.478× 变慢、shellpipeline 0.956× 无差,合并 1.188× —— **淘汰**
|
|
1512
1516
|
|
|
1513
|
-
|
|
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 |
|
|
1530
|
+
|
|
1531
|
+
16 对两臂 32 格全绿(各 15/15 或对应断言全过),没有一格因质量或崩溃作废。
|
|
1532
|
+
|
|
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
|
|
|
@@ -1523,6 +1547,8 @@ node bin/experiment.mjs stop # end it; later sessions g
|
|
|
1523
1547
|
| 再加 `shellpipeline`+`shellops` | 50/132 | 0.8763× |
|
|
1524
1548
|
| 再加 `migrate`+`codebase` | 76/132 | 0.8414× |
|
|
1525
1549
|
| **全开(当前默认)** | 132/132 | **0.8247×** |
|
|
1550
|
+
|
|
1551
|
+
三个独立估计量一致,不是尾部样本撑出来的:逐对中位 **0.8128×**(CI95 0.7095–0.8830)、几何均值 **0.8232×**(CI95 0.7530–0.8971)、总输出 token 比 **0.8221×**;132 对里 89 对更快,符号检验 p = 0.0001(`python3 bench/pool.py --ci 2000` 一行可复算)。
|
|
1526
1552
|
| 全开但 `debugfix`/`perfrefactor` 关 | 103/132 | 0.8235× |
|
|
1527
1553
|
|
|
1528
1554
|
读法两条,都是负结果,但有用:
|
|
@@ -1593,3 +1619,330 @@ python3 bench/suite-run.py --tasks debugfix,ledger,codeimpl --arms off,def15,bud
|
|
|
1593
1619
|
4. **方差预期**:负载高时单格方差变大,所以结论继续用「同块总和比 + 逐对中位数 + 符号检验」,且**每家族 ≥ 2 个同块有效对**才算判定;只有 1 对的行一律写「未判定」。
|
|
1594
1620
|
|
|
1595
1621
|
这一条替换掉此前「`load` 持续 ≥ 8 就整块作废」的规则(那版规则在 20:44–20:53 的 NX1_IMPL 上刚把一个块砍到只剩 1 行,而阈值 8 恰好等于本机空闲基线,等于永久停机)。
|
|
1622
|
+
|
|
1623
|
+
## 拆开看:快的那 18% 全部来自「输出 token 变少」,不是「每一步变快」
|
|
1624
|
+
|
|
1625
|
+
132 对干净配对逐对分解,恒等式先验证:`几何均值(wall) = 几何均值(步数) × 几何均值(每步墙钟)`,残差 0.0000。
|
|
1626
|
+
|
|
1627
|
+
- 几何均值:**步数 0.827 × 每步 0.996 = 0.823×**(逐对中位 0.818 × 0.999)。
|
|
1628
|
+
- 也就是说:**每步并没有变快**(0.996×,等于没变),省下来的是**回合数**;而回合数省掉之后,每步的输出反而变长,两者相乘才是那 18%。
|
|
1629
|
+
- 每步墙钟 ≈ 每步的输出 token:`corr(log llm/step, log out/step) = 0.949`(n = 132 对)。块内回归 `wall ≈ 9.45 s / 1k 输出 token`。模型侧解码速度是模型自己的,不归本插件管,所以**插件能动的那一项只有「总共写多少 token」**。
|
|
1630
|
+
- 反过来验证:`corr(log 墙钟比, log 输出比) = 0.940`(n = 145 对,含未入池块),逐对 `wall` 比的 sd 0.52、`out` 比的 sd 0.46 —— **方差几乎全部由输出长度决定,不是负载**。所以之前「负载让区间变宽」的说法只对了一半:负载抬绝对墙钟,但逐对方差的主因是模型每次写多写少。
|
|
1631
|
+
|
|
1632
|
+
逐家族(同块逐对中位,n 见括号;`每步`=wall/步 的比值,`输出/步`=out/步 的比值):
|
|
1633
|
+
|
|
1634
|
+
| 家族 | 步数 | wall | 每步 | 输出/步 |
|
|
1635
|
+
|---|---|---|---|---|
|
|
1636
|
+
| codebase (11) | 1.000 | 0.586 | 0.806 | 0.835 |
|
|
1637
|
+
| stategraph (10) | 1.085 | 0.667 | 0.639 | 0.671 |
|
|
1638
|
+
| shellops (13) | 0.769 | 0.683 | 1.152 | 1.275 |
|
|
1639
|
+
| codeimpl (15) | 0.778 | 0.709 | 0.860 | 0.960 |
|
|
1640
|
+
| migrate (15) | 0.778 | 0.739 | 0.940 | 1.022 |
|
|
1641
|
+
| ledger (14) | 0.591 | 0.812 | 1.252 | 1.445 |
|
|
1642
|
+
| shellpipeline (12) | 0.807 | 0.844 | 1.027 | 0.932 |
|
|
1643
|
+
| perfrefactor (14) | 1.000 | 0.961 | 0.981 | 0.914 |
|
|
1644
|
+
| debugfix (15) | 1.000 | 0.976 | 0.972 | 0.986 |
|
|
1645
|
+
| csvpipe (13) | 0.923 | 0.983 | 1.039 | 1.040 |
|
|
1646
|
+
|
|
1647
|
+
两条互斥的省钱路子,家族各走一条:`codebase`/`stategraph` **步数不减甚至变多,但每步写得少**(每步 0.64–0.81);`ledger`/`shellops` **步数砍掉四到六成,但每步写得多**(每步 1.15–1.25)。混合池 0.823× 就是把这两类平均后的结果;把它们混在一起比较「哪个机制强」没有意义。
|
|
1648
|
+
|
|
1649
|
+
**一个被否掉的猜想**:曾怀疑「每步变便宜」来自提示词缓存命中,因为注入文本若放在 prompt 尾部、前缀稳定,cacheRead 会变高。实测否掉:live 遥测 446 个 on 步里 `corr(log cacheRead, log llmMs/输出) = −0.026`(缓存命中不预测每 token 延迟),且 461 行遥测里**没有任何一个会话同时有 on 与 off 步**(0 个),所以「on 的 cacheRead 中位 66k vs off 34k」是上下文长度不同导致的混淆量,不可引用。每 token 时间基本是常数,这也解释了为什么 `每步` 完全跟着 `输出/步` 走(0.949)。
|
|
1650
|
+
|
|
1651
|
+
**这条拆解给出的天花板**:0.60× 需要总输出少 40 %,即模型在同样的任务里少写 40 % 的 token(少写解释、少写重复复验、少贴长 diff)。那不是调度能做到的,是**拿任务质量换的**,与「速度不能以牺牲任务质量为代价」直接冲突。所以声明:在「不动请求体、不动推理档、不牺牲正确率」的约束下,**可达区间是 0.80–0.85×**,逐家族最优 0.59–0.71×;再往下必须走被明令禁止的杠杆(提高 max_tokens 压缩比、砍推理档、跳过复验)。这条写在这里,是为了以后不再有人拿 0.60× 当目标返工。
|
|
1652
|
+
|
|
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
|
|
1663
|
+
|
|
1664
|
+
「快了 18%」必须配一条质量口径,否则就是拿正确率换的。同一批 132 个有效配对逐对核对隐藏断言:
|
|
1665
|
+
|
|
1666
|
+
- 断言通过数:`off` **1960/1960(100.00 %)**,`def15` **1959/1960(99.95 %)**,差 **−0.05 个百分点 = 1 条断言**。
|
|
1667
|
+
- 逐对看:131/132 对两臂断言数完全相同;唯一不同的是 `W1 / ledger / pair 1`,`def15` 21/22、`off` 22/22。
|
|
1668
|
+
- 产物精确性 `exact`(入口文件 / 输出与期望逐字节一致):两臂都是 132/132。
|
|
1669
|
+
- 结论只能说到这里:**在这 10 个家族、132 个任务上,速度不是用正确率换来的**;1 条断言的分差在统计上无法与噪声区分(也无法证明「绝对无损」),所以对外表述用「断言对等,1/1960 差异」而不是「零质量损失」。
|
|
1670
|
+
|
|
1671
|
+
另外记一条运行可靠性的事实,避免以后误读:同批数据里有 **`off` 6 行、`def15` 3 行**是零输出的退化运行(`out=0`、`steps` 为 1、任务只做了一半),按有效单元规则已经全部排除,不参与任何比值。这类行的出现与宿主负载相关,不是某一个臂的固有缺陷;它们也解释了为什么 `suite-run.py` 必须按「有效行」而不是「有行」来判断进度(见 `bench/scrub.py`)。
|
|
1672
|
+
|
|
1673
|
+
## 机制判定:`shapeGate` 没有可测收益(6 对,3 个家族)
|
|
1674
|
+
|
|
1675
|
+
1.5.10 之前 `shapeGate` / `outputBudget` 在 `resolveConfig()` 里被丢掉了,所以旧块其实全跑默认配置;1.5.10 修好接线后重新测了 `shapeGate`(块 `GATE3_DBG` / `GATE3_CSV` / `GATE3_IMPL`,每块 2 对、三臂 `off` / `def15` / `gateonly` 同块交错):
|
|
1676
|
+
|
|
1677
|
+
| 家族 | 对 | 逐对 `gateonly/def15` 墙钟 | 几何均值 |
|
|
1678
|
+
| --- | --- | --- | --- |
|
|
1679
|
+
| debugfix | 2 | 0.889 / 2.246 | 1.413 |
|
|
1680
|
+
| csvpipe | 2 | 1.303 / 0.337 | 0.663 |
|
|
1681
|
+
| codeimpl | 2 | 1.269 / 0.978 | 1.114 |
|
|
1682
|
+
| **合计** | **6** | — | **1.014**(中位 1.123,`gateonly` 3/6 更快;输出 token 几何 0.949) |
|
|
1683
|
+
|
|
1684
|
+
判据在跑之前就写死了:同一家族先看逐对方向是否一致,再合池;只要逐对方向随机就不给结论。这里 6 对里 3 对更快、3 对更慢,几何 1.014 与 1.0 无法区分,输出 token 只少 5 % 也没换来墙钟 ⇒ **`shapeGate` 不是速度杠杆**,与上面的分解一致(省时只能靠少输出/少轮次,而 gate 的输出变化不稳定)。因此它保持默认关闭;它的价值只在「形态约束」本身,不在速度。
|
|
1685
|
+
|
|
1686
|
+
这也顺带说明 1.5.10 那个接线 bug 的影响面:旧块里 `bud8`/`sg15`/`gateonly`/`gate15` 的分支数据一律标 **untested**,不能当机制证据;上面这张表是修复后的第一次真测量。
|
|
1687
|
+
|
|
1688
|
+
## 反例:`outputDiscipline: true`(从第 1 步就压输出)比默认 `late` 慢 21 %
|
|
1689
|
+
|
|
1690
|
+
默认值 `outputDiscipline: 'late'` 是「短任务不付提示成本、第 4 步之后提醒一次」。把规则提前全程生效(实验臂 `odt15`)在同一批 12 个块内配对里(`W1`,`codeimpl`/`csvpipe`/`debugfix`/`ledger`/`migrate`/`shellpipeline` 各 2 对):
|
|
1691
|
+
|
|
1692
|
+
- 几何均值 **1.213×**(即慢 21 %),`odt15` 只赢 4/12 对。
|
|
1693
|
+
- 输的方式不是「话变多」而是 **步数变多**:`debugfix` 4→12 步、`ledger` 8→26 步、`shellpipeline` 19→31 步;输出 token 也跟着涨(`ledger` 2.32×、`shellpipeline` 1.84×)。
|
|
1694
|
+
- 机制解释与主结论一致:把「少写过程」当常驻文风后,模型会改用更多轮次去「确认」,而轮次和输出是绑在一起的(`steps` 0.827 × 每步输出 ≈ 墙钟)。
|
|
1695
|
+
|
|
1696
|
+
所以默认值不改成 `true`,也不做成「从第一步就提醒」。这条和 `shapeGate` 的空结果一起,构成「不是所有听起来合理的省 token 开关都真省时间」的两个反例——它们都是**先测后定**,不是按直觉定的。
|
|
1697
|
+
|
|
1698
|
+
## `outputBudget`:同一个 8000 token 上限,长任务省一半、短任务慢一半(6 对,3 个家族)
|
|
1699
|
+
|
|
1700
|
+
1.5.10 修好接线后测 `outputBudget: 8000`(实验臂 `bud8`,块 `BUD3_DBG` / `BUD3_IMPL` / `BUD3_LEDGER`,每块 2 对,`def15` 与 `bud8` 同块交错):
|
|
1701
|
+
|
|
1702
|
+
| 家族 | 对 | 逐对 `bud8/def15` 墙钟 | 几何均值 | 步数变化 | 输出变化 |
|
|
1703
|
+
| --- | --- | --- | --- | --- | --- |
|
|
1704
|
+
| ledger | 2 | 0.531 / 0.976 | **0.720** | 18→5、16→13 | 0.45×、0.89× |
|
|
1705
|
+
| debugfix | 2 | 1.220 / 1.124 | 1.171 | 5→6、6→4 | 1.31×、0.91× |
|
|
1706
|
+
| codeimpl | 2 | 1.466 / 1.403 | **1.434** | 5→8、4→6 | 1.94×、1.90× |
|
|
1707
|
+
| **合计** | **6** | — | 1.066(`bud8` 2/6 更快) | — | — |
|
|
1708
|
+
|
|
1709
|
+
读法:预算本身不是速度杠杆,它是一个**收尾扳机**。`ledger`(22 个隐藏断言、本来要写 3–5 万 token)在 8000 处被提醒一次,直接砍到 5 步、输出 0.45×,墙钟 0.53×——真的省。`codeimpl` 相反:任务只需要约 1.2 万 token,预算在它还没写出实现时就把「收掉闭环」的意见插进去,结果多出 2 步重开(4→6 / 5→8),输出和墙钟一起涨 40 %。
|
|
1710
|
+
|
|
1711
|
+
所以默认值保持 0(关闭),并且不按家族静态开:判据是「这一轮是否已经长到值得收」,不是一个固定 token 数。据此把预算改成 **延迟生效**(`outputBudgetAfterSteps`,默认 0 = 立即),实验臂 `budlate` = `outputBudget 8000` + `outputBudgetAfterSteps 6`,正在跑 `BUDL_DBG`/`BUDL_IMPL`/`BUDL_LEDGER` 各 2 对。
|
|
1712
|
+
|
|
1713
|
+
## 报价更新:并入 NX1 三块后 138 对(2026-10-27 夜)
|
|
1714
|
+
|
|
1715
|
+
`NX1_IMPL` / `NX1_PIPE` / `NX1_SG` 三块是 1.5.10 修线之后的 `off` vs `def15` 同块配对(每块 2 对、ABBA 交错),与旧的 9 块口径相同,因此并入 `bench/pool.py` 的 `CLEAN`,合池从 132 对变 **138 对**(`python3 bench/pool.py --ci 4000`):
|
|
1716
|
+
|
|
1717
|
+
| 估计量 | 132 对(旧口径) | 138 对(并 NX1) |
|
|
1718
|
+
| --- | --- | --- |
|
|
1719
|
+
| 墙钟总比 | 0.8247×(CI95 0.747–0.914) | **0.8234×(CI95 0.742–0.913)** |
|
|
1720
|
+
| 逐格中位 | 0.8128×(0.710–0.883) | **0.7994×(0.703–0.885)** |
|
|
1721
|
+
| 几何均值 | 0.8232×(0.753–0.897) | **0.8248×(0.753–0.900)** |
|
|
1722
|
+
| 步数总比 / 输出总比 | 0.8085× / 0.8221× | **0.8062× / 0.8179×** |
|
|
1723
|
+
| 更快对数 / 符号检验 | 89/132,p=0.0001 | **93/138,p=0.0001** |
|
|
1724
|
+
| 断言(`off` / `def15`) | 1960/1960 / 1959/1960 | **2038/2038 / 2037/2038** |
|
|
1725
|
+
|
|
1726
|
+
三点读法:
|
|
1727
|
+
|
|
1728
|
+
1. **点估计没动**(0.8247 → 0.8234,差 0.13 个百分点,远小于区间宽度 0.18),所以对外仍是「约 1.21× 快、约 17–18 % 省时」,区间照旧报 CI95,不报单点。新增的 6 对确实更偏快(中位从 0.813 掉到 0.799),但样本量只涨 4.5 %,不足以支撑新报价。
|
|
1729
|
+
2. **唯一的断言差异还是那一个**:`(W1, ledger, 1)` 的 `def15` 21/22 对 `off` 22/22;其余 137 对断言计数完全一致,`exact` 138/138。质量措辞仍是「断言对等」,不是「零质量损失」。
|
|
1730
|
+
3. 三个估计量互相不一致的程度(0.799 / 0.823 / 0.825)正好说明为什么发布口径必须并列报,而不能挑最小的那个当中位卖点。
|
|
1731
|
+
|
|
1732
|
+
## 空转对照:开关根本没触发时,同一对能差多少(9 对,纯分析,不新增格子)
|
|
1733
|
+
|
|
1734
|
+
这一轮 mechanism 实验里有 9 对是**结构上必然空转**的:实验臂多开的那一路开关,在那一跑里一次都没触发,所以两臂的行为完全一样,它们测到的比值就是**测量本身的噪声加偏差**。判定依据不是猜的,是逐跑数会话日志里的注入条数(`zstd -dc session.v4.jsonl.zstd | grep -c`):
|
|
1735
|
+
|
|
1736
|
+
| 块 | 任务 | 逐对 `arm/def15` | 为什么是空转 |
|
|
1737
|
+
| --- | --- | --- | --- |
|
|
1738
|
+
| `BUD3_DBG` | debugfix p1 / p2 | 1.220 / 1.124 | `outputBudget 8000` 从未触发(输出 2526、5137 token < 8000),`Auto schedule` 计数 1/1 |
|
|
1739
|
+
| `BUDL_DBG` | debugfix p1 / p2 | 1.342 / 1.123 | 同上,延迟到第 6 步的预算也没触发,计数 1/1 |
|
|
1740
|
+
| `BUDL_IMPL` | codeimpl p1 | 1.151 | 8119 token 只在最后一步越过阈值,没有下一步可提醒,计数 1/1 |
|
|
1741
|
+
| `GATE3_DBG` | debugfix p1 | 0.889 | `shapeGate` 没有抑制任何条注入,计数 1/1 |
|
|
1742
|
+
| `GATE3_CSV` | csvpipe p1 | 1.303 | 同上,计数 1/1 |
|
|
1743
|
+
| `GATE3_IMPL` | codeimpl p1 / p2 | 1.269 / 0.978 | 同上,计数 1/1 |
|
|
1744
|
+
|
|
1745
|
+
9 对的分布:**几何均值 1.146×、中位 1.151×、范围 0.889–1.342×、7/9 偏向「实验臂更慢」**。也就是说同一份代码、同一份提示词、同一个任务,换一次跑就能差 ±20 %,最坏一头一尾差 1.51 倍。
|
|
1746
|
+
|
|
1747
|
+
这条数字的用处是把「不可判定」从口头判断变成可引用的边界:
|
|
1748
|
+
|
|
1749
|
+
1. **单家族 n=2 的裁决没有信号**。`BUD3_DBG` 报的 `bud8` 慢 17 %、`GATE3_DBG` 报的 `gateonly` 慢 41 %,都落在空转对的范围里——那两块的「结论」本来就不该写成结论。之前 `shapeGate` 合池 1.014×(3/6 胜)也是同一回事:6 对里 4 对根本没抑制过注入。
|
|
1750
|
+
2. **超过 ±20 % 才算有东西**。真正触发了开关的对给出的是 0.531×(ledger)、1.466× / 1.403×(codeimpl),幅度超出空转带,可以当机制证据;幅度在带内的只能进合池,不能单独成段结论。
|
|
1751
|
+
3. **偏差是方向性的,不是纯随机**:9 对里 7 对偏向同一边(实验臂慢)。因此逐对比较里出现系统性慢半拍的可能来自那一跑的位置/负载,而不是开关本身——这也是块内交错 + 只报合池比值的理由,不是「再补几对就能救单家族结论」。
|
|
1752
|
+
|
|
1753
|
+
## 把预算推迟到「回合已经长了」以后:0.956×,但赢面没了(BUDL,6 对)
|
|
1754
|
+
|
|
1755
|
+
`outputBudgetAfterSteps: 6` 的设计假设是:BUD3 里 8000 token 的伤害只发生在短任务(那里预算在第 2–3 步就烧起来,提前喊收尾),而收益发生在真正长的手上任务。于是同一条上限改成「第 6 步起才生效」,再跑三块 `off` 无关的 `def15` vs `budlate` 同块配对:
|
|
1756
|
+
|
|
1757
|
+
| 块 | 家族 | 逐对 `budlate/def15` | 预算触发 | 几何均值 |
|
|
1758
|
+
| --- | --- | --- | --- | --- |
|
|
1759
|
+
| `BUDL_DBG` | debugfix | 1.342 / 1.123 | 0 / 0(输出 4300、5769 token < 8000) | 1.228×(空转) |
|
|
1760
|
+
| `BUDL_IMPL` | codeimpl | 1.151 / 0.839 | 0 / 1 | 0.983× |
|
|
1761
|
+
| `BUDL_LEDGER` | ledger | 0.918 / 0.994 | 1 / 1 | **0.956×** |
|
|
1762
|
+
| 合池 | — | — | 2/6 触发 | **1.048×** |
|
|
1763
|
+
|
|
1764
|
+
结论三条:
|
|
1765
|
+
|
|
1766
|
+
1. **推迟消除了收益,没消除噪声。** 同一家族 ledger,BUD3 里 `bud8` 是 0.720×(一对 0.531),推迟后只剩 0.956×——因为这一跑的 `def15` 基线本来就只走 10 和 13 步(BUD3 那两跑是 18 和 16 步),长回合的收益场景没有出现。可见「长任务」不是任务标签给的,是同一标签下跑出来的步数给的,**用步数当扳机等于用上一跑的结果赌这一跑的形状**。
|
|
1767
|
+
2. **代码实现类的伤害没有随推迟消失**:`BUDL_IMPL` 里触发的那一对 0.839×,没触发的 1.151×,两对之间差 37 %,再次落进空转带。
|
|
1768
|
+
3. 合池 1.048×(n=6,2 对空转)落在上面 9 对空转对照的 0.89–1.34 带内 ⇒ **判定为「无效果」**,而不是「略慢」。因此 `outputBudget` 家族到此收口:上限不是速度杠杆(BUD3 已证),推迟触发也不是补救(BUDL 已证),`outputBudget` / `outputBudgetAfterSteps` 两个开关**保持默认 0 / 0**,只作为可分家族复现的实验旋钮保留。
|
|
1769
|
+
|
|
1770
|
+
顺带一个测量警告:同样是 `ledger` + `def15`,BUD3 某跑 830 s / 18 步,BUDL 某跑 717 s / 10 步——**同一任务同一条臂的步数能差 80 %**。所以跨块比较绝对时间没有意义,只有块内交错配对的比值能用。
|
|
1771
|
+
|
|
1772
|
+
## 开局批量化(openBatch)提示:16 对里 1.022×,家族之间符号相反(OPEN8,纯诊断)
|
|
1773
|
+
|
|
1774
|
+
机制假设:`def15` 的系统提示已经说了「能并行就并行」,但**没有说「第一件事就把这一轮的调用一次发齐」**。`openBatch` 只在第 1 步补这一句,等于直接砍掉一个往返。为了让 n 足够大,选两个短任务家族各跑 8 对(`def15` vs `open15`,同块交错,共 16 对 32 格)。指令确认在每一条 `open15` 跑里都出现了(`grep` 会话转录,8/8 命中),所以这是「开关真的触发了」的对照,不是空转。
|
|
1775
|
+
|
|
1776
|
+
| 家族 | n | 几何均值 | 中位数 | 更快 | 步数(和对和) |
|
|
1777
|
+
| --- | --- | --- | --- | --- | --- |
|
|
1778
|
+
| debugfix | 8 | **1.228×** | 1.213 | 3/8 | 36→36(8 对里 6 对步数完全不变) |
|
|
1779
|
+
| csvpipe | 8 | **0.850×** | 1.020 | 4/8 | 120→70 |
|
|
1780
|
+
| 合池 | 16 | **1.022×** | 1.110 | 7/16 | 156→106 |
|
|
1781
|
+
|
|
1782
|
+
三条读数:
|
|
1783
|
+
|
|
1784
|
+
1. **合池无效果。** 几何均值 1.022×、中位数 1.110×、7/16 更快,落在空转带里;家族之间方向相反(debugfix 慢 23 %、csvpipe 快 15 %),正是 PPX2 那次「跨家族符号翻转」的重演,**任何单家族结论都不能引**。
|
|
1785
|
+
2. **在 debugfix 上「少一个往返」根本没发生**:8 对里有 6 对步数一模一样(4→4、5→5),当步数不变而总时长变长时,多出来的是**输出**(如 p5 3947→5331、p8 3511→5855 token)。原因很直接:`def15` 的系统提示已经要求并行,再在第 1 步喊一次「一次发齐」,模型会把开局写成一份更大的计划式调用,**没省往返,只多写了几千 token**。
|
|
1786
|
+
3. **csvpipe 的 0.850× 不是杠杆,是尾部救援**:两对(p2、p7)的 `def15` 跑崩成了 21 步(483 s)和 23 步(369 s),而对应的 `open15` 只走 6 步和 4 步;刨掉这两对,剩下 6 对是几何均值 1.36×。也就是说 `open15` 的收益全部来自「让本来会绕圈的那一跑不绕圈」,这是方差,不是速度——它同时说明 `def15` 在 csvpipe 上有不可忽略的长尾(8 对里 2 对超过 19 步,而中位数是 9–14 步)。
|
|
1787
|
+
|
|
1788
|
+
结论:`openBatch` **保持默认 `false`**,不进入默认路径。附带一条可用的观察:csvpipe/debugfix 这类的长尾与几何均值同样重要,后面报告「更快多少」时应同时给更快对数与最慢那一对。
|
|
1789
|
+
|
|
1790
|
+
## 提前收尾线(finishLineSteps: 8):步数砍掉 33 %,墙上时间只动 4 %(FL6,4 对)
|
|
1791
|
+
|
|
1792
|
+
`finishLineSteps` 的意思是「到了第 N 步就对模型说:现在开始收敛到最终交付」,本意是砍掉多余的收尾轮次。在 migrate 家族只跑 4 对(`def15` vs `fl8`),因为这条臂动的是回合结构,先看形状再看倍数:
|
|
1793
|
+
|
|
1794
|
+
| 对 | `fl8/def15` | 步数 | 输出 token |
|
|
1795
|
+
| --- | --- | --- | --- |
|
|
1796
|
+
| p1 | 1.288 | 11→7 | 13 224→14 418 |
|
|
1797
|
+
| p2 | 1.003 | 14→8 | 19 590→16 436 |
|
|
1798
|
+
| p3 | 0.632 | 12→10 | 23 180→17 708 |
|
|
1799
|
+
| p4 | 1.031 | 12→8 | 14 827→17 320 |
|
|
1800
|
+
| 合计 | **0.917**(几何 0.958,中位 1.017) | **49→33 = 0.674×** | 70 821→65 882 = 0.930× |
|
|
1801
|
+
|
|
1802
|
+
这条结果推翻了一个容易写错的因果:**步数不是杠杆,是伴随变量**。4 对里步数平均砍掉 33 %,墙上时间只降 8 %(合计)/ 4 %(几何),因为被砍掉的那些步并没有消失——它们的内容被塞进了保留下来的步里,于是单步输出变大(p1、p4 的输出反而涨 9–17 %),而墙上时间 ≈ 输出 token(本文件前面测的 9.45 s/1k)。
|
|
1803
|
+
|
|
1804
|
+
回看主口径就清楚了:`off → def15` 是**步数 0.827× 且输出 0.818× 同时下降**,那才是 0.823× 的来源;单独压缩步数(`fl8`)不降输出,所以不降时间。这条也解释了为什么 BUD3/BUDL 的预算扳机在 ledger 上能到 0.53× 而在 codeimpl 上是 1.4×:它压的是「继续写」这件事本身,不是「多一轮」这件事。
|
|
1805
|
+
|
|
1806
|
+
`finishLineSteps` 保持默认 `0`(不启用)。它作为「回合结构」旋钮的定位保留,但速度证据不支持它进默认路径。
|
|
1807
|
+
|
|
1808
|
+
## 整文件改写的「只发改动行」提醒(preferPatch):中位数 0.926×,几何 1.104×(PP8,8 对)
|
|
1809
|
+
|
|
1810
|
+
机制最直白的一条候选:`write` 一个 2000 字符以上的整文件,工具参数里的文件正文本身就是输出 token;提醒一次「改已有文件只发改动行(edit hunk)」,落盘内容不变,省的是解码量。codeimpl 家族 8 对 `def15` vs `pp15`(唯一差别是 `preferPatch: true`),并用会话转录确认提醒在 8/8 条 `pp15` 跑里都真的发了、`def15` 里 0 次:
|
|
1811
|
+
|
|
1812
|
+
| 对 | `pp15/def15` | 输出 token | 步数 |
|
|
1813
|
+
| --- | --- | --- | --- |
|
|
1814
|
+
| p1 | 0.651 | 13 188→9 616 | 6→7 |
|
|
1815
|
+
| p2 | 0.870 | 20 981→16 876 | 8→5 |
|
|
1816
|
+
| p3 | **4.549** | 8 068→31 819 | 4→8 |
|
|
1817
|
+
| p4 | 1.520 | 15 373→22 977 | 7→13 |
|
|
1818
|
+
| p5 | 0.561 | 12 904→7 443 | 5→6 |
|
|
1819
|
+
| p6 | 0.907 | 27 506→21 869 | 14→15 |
|
|
1820
|
+
| p7 | 1.169 | 10 489→16 074 | 5→11 |
|
|
1821
|
+
| p8 | 0.945 | 18 255→17 008 | 7→8 |
|
|
1822
|
+
| 合计/统计 | 几何 **1.104**,中位 **0.926**,5/8 更快 | 126 764→133 682 = 1.055 | 56→73 = 1.30 |
|
|
1823
|
+
|
|
1824
|
+
读数:
|
|
1825
|
+
|
|
1826
|
+
1. **机制真的生效了,而且是双向的。** 5 对里输出立降 7–42 %(p1 −27 %、p5 −42 %、p2 −20 %、p6 −20 %),说明「别发整个文件」确实省解码;但另外 3 对里提醒之后模型改成**多次小改**,步数从 4→8、7→13、5→11,输出反而涨到 3.9 倍——省下的单次正文被多出来的回合吃掉了。
|
|
1827
|
+
2. **这是双峰分布,不是均匀位移。** 中位数 0.926×(多数对更快)与几何均值 1.104×(被一对 4.5× 拖累)同时成立;5/8 更快也成立。此时任何一个单独报数都是误导,三个必须一起出现——这也是本文件反复强调「只用几何/中位而不用比值均值」的边界:它能挡住的是均值陷阱,挡不住重尾。
|
|
1828
|
+
3. 触发链也解释得通:提醒只在「已经写过一个大整文件之后」才发,这条路径上本来就有两种世界——一个文件改一处(赚 20–40 %),或一个文件要改多处(小改的固定开销每处再付一次)。运行时无法预知是哪一种,所以这不是可门控的杠杆。
|
|
1829
|
+
|
|
1830
|
+
### migrate 上重跑:同一个方向更明显(PP6,6 对)
|
|
1831
|
+
|
|
1832
|
+
换到 migrate(大量「同一文件多处小改」的任务)再跑 6 对,提醒同样 6/6 触发:
|
|
1833
|
+
|
|
1834
|
+
| 对 | `pp15/def15` | 步数 | 输出 |
|
|
1835
|
+
| --- | --- | --- | --- |
|
|
1836
|
+
| p1 | 0.971 | 16→8 | 26 172→27 199 |
|
|
1837
|
+
| p2 | 2.901 | 9→33 | 13 757→27 560 |
|
|
1838
|
+
| p3 | 1.036 | 8→11 | 18 473→18 680 |
|
|
1839
|
+
| p4 | 2.029 | 15→16 | 16 963→31 967 |
|
|
1840
|
+
| p5 | 0.616 | 11→12 | 21 399→15 324 |
|
|
1841
|
+
| p6 | 1.642 | 8→15 | 14 181→23 842 |
|
|
1842
|
+
| 合计 | 几何 **1.348**,中位 1.339,2/6 更快 | 67→95 = 1.42 | 110 945→144 572 = 1.30 |
|
|
1843
|
+
|
|
1844
|
+
两个家族合并(14 对):**几何 1.203×、中位 1.100×、7/14 更快、步数 1.37×、输出 1.21×**。migrate 的 p2(9→33 步)说明小改策略在「多处改动」任务上会失控:每处都付一次完整回合的固定开销(约 32 % 的单步时间不在模型调用里),回合数一涨总时间必涨。
|
|
1845
|
+
|
|
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,26 @@
|
|
|
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
|
+
|
|
12
|
+
## 1.5.12
|
|
13
|
+
|
|
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)。
|
|
15
|
+
- **新旋钮 `disciplineAfterSteps`(默认 `0`,行为不变)**。整个插件里唯一被测出真实效果的机制,是 `outputDiscipline: late` 在第 4 步注入一次「省着写」的提醒;它的**注入步数**此前是写死的常量。现在可配:`0` = 用内建默认 4,正数 = 覆盖(`src/config.js` 校验 0–40,`src/effort.js` 只在 `gear: auto` 时才带出去,`src/auto.js` 用 `disciplineAt` 判步)。**这只是把已有常量变成可扫参数,不是新机制**:默认路径逐字节相同,因此不需要新标签重跑合池。`bench/arms/d2.yml`、`d3.yml` 是它的消融臂;数据尚未跑出,本版**不对提速幅度作任何声明**。
|
|
16
|
+
- **`preferPatch` 淘汰(保留默认 `false`)**:它让整文件写入改发 hunk,同块 `pp15` 对 `def15` 跑了两个家族 —— `codeimpl` 8 对几何 1.104×(0.651 / 0.870 / 4.549 / 1.520 / 0.561 / 0.907 / 1.169 / 0.945),`migrate` 6 对几何 1.348×(0.971 / 2.901 / 1.036 / 2.029 / 0.616 / 1.642),合并 14 对 **几何 1.203×、中位 1.100×、7/14 更快、步数 1.37×、输出 1.21×**。提醒确实下发了(逐会话搜确切字符串,`pp15` 8/8 与 6/6、`def15` 0 次),所以问题不在接线而在机制:把一处改动拆成多次小写,每次都要付一次完整回合的固定开销(单步时间约 32 % 不在模型调用里)。双峰分布(0.561 到 4.549)说明它不是稳定杠杆。
|
|
17
|
+
- **`openBatch` 淘汰(保留默认 `false`)**:合并 16 对几何 1.022×、中位 1.110×、7/16 更快 —— 空到负,W6 的 0.77× 再未复现。
|
|
18
|
+
- **延迟输出预算帽 `BUDL` 为空**:每家族几何 `debugfix` 1.228×(两对都空)、`codeimpl` 0.983×、`ledger` 0.956×,合并 6 对 1.048×。`outputBudget` / `outputBudgetAfterSteps` 维持 `0`。
|
|
19
|
+
- **`finishLineSteps` 的步数不是杠杆**:`fl6` 臂把收尾提醒提前到第 8 步,4 对里步数合计 49→33(**0.674×**),但墙钟 **0.958×**(4/4 里 1 对更快)—— 步数与墙钟脱钩,所以「砍步数」不能当提速证据。
|
|
20
|
+
- **空构造对照校准了判定门槛**:同一个默认臂对同一个默认臂跑 9 对(两边配置完全相同),几何 **1.146×**、中位 1.151×、范围 0.889–1.342×、7/9 在 1 以上。结论:单家族 n=2 的胜负全是噪声,只有超过 ±20 % 的差才算机制;偏差是方向性的(后跑的一臂更慢),来自位次与负载。
|
|
21
|
+
- **测试**:263 个用例全绿(`node --test test/*.test.js`,10.5 s),新增 `disciplineAfterSteps` 的「按配置步数提前触发、`0` 回落到内建 4」用例,以及 `batchVerify` 的「第二次未验证写入才触发、每回合一次、新回合可再触发」用例。
|
|
22
|
+
- **版本**:`1.5.12`。`1.5.11` 是元数据/版本对齐发版(无行为变更),未单列条目。
|
|
23
|
+
|
|
3
24
|
## 1.5.10
|
|
4
25
|
|
|
5
26
|
- **接线审计(先说结论:1.5.9 有两条机制从未真正生效)**。按 schema 的 23 个键逐个对照 `resolveConfig()` 返回的 policy,发现 `shapeGate`、`outputBudget` 在 `src/config.js` 里声明、却**没有**被 `resolveConfig` 带出去:钩子读到的永远是 `undefined`,`bud8`、`sg15`、`gateonly`、`gate15` 四个臂跑的都是 `def15` 的行为。所以这些块的数字**不是“机制没用”,而是“机制没被测到”**——一律标 **untested**,不写成胜负;`BENCHMARK.md` 里受影响的表格行、G 段与 SG2 的对照结论已就地撤回。这两个键现在真的接上了,并有传播测试(`test/effort.test.js`)防回归。
|
package/README.en.md
CHANGED
|
@@ -40,21 +40,21 @@ No model switch, no `reasoningEffort` rewrite, no request-field injection (`serv
|
|
|
40
40
|
|
|
41
41
|
## Measured
|
|
42
42
|
|
|
43
|
-
**Toggle it on and the headline is: about 18% faster in total, and
|
|
43
|
+
**Toggle it on and the headline is: about 18% faster in total, and 25–30% faster on the shapes it fits.** That is every clean block with an `off` anchor pooled (12 blocks, 138 valid pairs, 10 task families; recompute with `python3 bench/pool.py`): total wall clock **0.823×** (18% less), output tokens 0.818×, steps 0.806×, per-cell median 0.799×, faster in 93 of 138 pairs (sign test p = 0.0001). Bootstrapping those 138 pairs 2000 times puts the total ratio at a 95% interval of **[0.742, 0.913]** (9% to 26% faster), the per-cell median at [0.703, 0.885], and the geometric mean at 0.825× (interval 0.753–0.900) — "about 18%" is a point estimate, not a floor. The only quality statement at the same denominator: hidden assertions matched cell for cell across the 138 pairs (`off` 2038/2038, default 2037/2038, the one difference being a 21/22 cell) — that is **assertion parity**, not "zero quality loss". Per-family stability and sign tests are in [BENCHMARK.md](BENCHMARK.md), section "合池的自助区间与逐任务稳定性", which also explains why per-family numbers cannot be quoted across blocks.
|
|
44
44
|
|
|
45
45
|
**When to turn it on, and when not to.** On: mid-length tasks with several artifacts and repeated re-checks — `codeimpl` 0.663x (faster in 13 of 15 pairs, sign test p = 0.007) and `shellpipeline` 0.749x (p = 0.039), with `stategraph` 0.708x, `shellops` 0.748x, `codebase` 0.808x and `migrate` 0.822x on the same side but with too few pairs to stand alone. Off: one-to-five-step repairs and data reshaping — `debugfix` 1.032x (output actually 1.29x longer) and `perfrefactor` 1.009x, where the injected reminder cannot pay for itself. `ledger` 0.915x and `csvpipe` 0.982x are a wash. The switch is the lightning bolt per conversation; it remembers your last choice and applies from the next request.
|
|
46
46
|
|
|
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`
|
|
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
|
|
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
|
@@ -40,21 +40,21 @@
|
|
|
40
40
|
|
|
41
41
|
## 实测
|
|
42
42
|
|
|
43
|
-
**打开小闪电,排头的一句结论:整任务总量约快 18 %,适合的形状快
|
|
43
|
+
**打开小闪电,排头的一句结论:整任务总量约快 18 %,适合的形状快 25–30 %。** 这是把**所有**有 `off` 锚的干净块一起加总的结果(12 个块、138 对有效配对、10 个任务家族,`python3 bench/pool.py` 可复算):总墙钟 **0.823×**(省 18 %)、输出 token 0.818×、步数 0.806×、逐格中位 0.799×、138 对里 93 对更快(符号检验 p = 0.0001)。对这 138 对自助重抽 2000 次,总比 95 % 区间是 **[0.742, 0.913]**(即省 9 %–26 %),逐格中位区间 [0.703, 0.885],几何均值 0.825×(区间 0.753–0.900)——「约 18 %」是点估计、不是下限承诺。质量的同口径结论只有一句:138 对里两臂隐藏断言逐格完全相同(`off` 2038/2038、默认 2037/2038,唯一差的是一格 21/22),这是**断言对等**,不是「零质量损失」。逐家族稳定性与符号检验见 [BENCHMARK.md](BENCHMARK.md) 的「合池的自助区间与逐任务稳定性」一节,那里也写了为什么逐家族数字不能跨块引用。
|
|
44
44
|
|
|
45
45
|
**什么时候开、什么时候别开(按实测形状,不按任务名)。** 开:有多份产物、要反复跑同一份检查的中长任务——`codeimpl` 0.663×(15 对里 13 对更快,符号检验 p = 0.007)、`shellpipeline` 0.749×(p = 0.039),`stategraph` 0.708×、`shellops` 0.748×、`codebase` 0.808×、`migrate` 0.822× 也在同一侧但样本撑不起单列结论。别开:1–5 步的小改修与数据整形——`debugfix` 1.032×(输出反而长 1.29×)、`perfrefactor` 1.009×,提醒本身的注入成本收不回来。`ledger` 0.915×、`csvpipe` 0.982× 打平,开不开都行。开关就是每会话的小闪电,默认记住上次选择,下一个请求生效。
|
|
46
46
|
|
|
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`
|
|
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
|
|
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
|
|
package/docs/REFERENCE.en.md
CHANGED
|
@@ -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/index.js
CHANGED
|
@@ -95,7 +95,7 @@ const TAG = '[fast-mode]'
|
|
|
95
95
|
* Shipped identity of this build. Traced into the debug log so a stale install is
|
|
96
96
|
* visible at a glance, and pinned by a test against `package.json`.
|
|
97
97
|
*/
|
|
98
|
-
export const VERSION = '1.5.
|
|
98
|
+
export const VERSION = '1.5.12'
|
|
99
99
|
const GLOBAL_AGENT = '__global__'
|
|
100
100
|
|
|
101
101
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fast-mode",
|
|
3
|
-
"version": "1.5.
|
|
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, 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)
|
|
@@ -127,6 +139,16 @@ export function notePolicy(state, agent, policy) {
|
|
|
127
139
|
// A token budget counts decode work, which is what the wall clock is actually
|
|
128
140
|
// made of; 0 is the off switch, exactly like stepBudget.
|
|
129
141
|
entry.outBudget = stepCount(policy?.outputBudget, 0, 0)
|
|
142
|
+
entry.outBudgetAfterSteps = stepCount(policy?.outputBudgetAfterSteps, 0, 0)
|
|
143
|
+
// 0 keeps the built-in entry step; a positive value moves the one reminder that
|
|
144
|
+
// has a measured effect (see BENCHMARK: compress-output) earlier or later.
|
|
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)
|
|
130
152
|
// 0 is a value, not "unset": it is how a caller asks for no hard stop at all.
|
|
131
153
|
entry.turnStepBudget = stepCount(policy?.stepBudget, AUTO_LIMITS.turnStepBudget, 0)
|
|
132
154
|
entry.finishLineSteps = stepCount(policy?.finishLineSteps, AUTO_LIMITS.finishLineSteps, 0)
|
|
@@ -225,6 +247,10 @@ function entryOf(state, agent) {
|
|
|
225
247
|
lateDiscipline: false,
|
|
226
248
|
/** One compression reminder per turn, never more. */
|
|
227
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,
|
|
228
254
|
/** Hard stop: the step count that ends the turn with "report now". */
|
|
229
255
|
turnStepBudget: AUTO_LIMITS.turnStepBudget,
|
|
230
256
|
/** Soft stop: one "no more scope" reminder at this step, 0 = off. */
|
|
@@ -277,6 +303,7 @@ export function noteTurn(state, agent) {
|
|
|
277
303
|
entry.stopped = false
|
|
278
304
|
entry.stepProgress = false
|
|
279
305
|
entry.disciplineSent = false
|
|
306
|
+
entry.disciplineRepeatSent = false
|
|
280
307
|
entry.finishLineSent = false
|
|
281
308
|
entry.outBudgetSent = false
|
|
282
309
|
entry.openingSent = false
|
|
@@ -882,7 +909,21 @@ function directiveFor(entry, stats) {
|
|
|
882
909
|
summary: 'auto: group the verification of this edit batch',
|
|
883
910
|
}
|
|
884
911
|
}
|
|
885
|
-
|
|
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
|
|
926
|
+
if (entry.lateDiscipline && !entry.disciplineSent && stats.turnSteps >= disciplineAt) {
|
|
886
927
|
// Late engagement: the prompt carried no prose rules, so the only cost of a
|
|
887
928
|
// short task was its own work. By this step the task is demonstrably long,
|
|
888
929
|
// and every remaining step re-pays its output tokens in decode time.
|
|
@@ -898,8 +939,23 @@ function directiveFor(entry, stats) {
|
|
|
898
939
|
summary: 'auto: compress output for the rest of the turn',
|
|
899
940
|
}
|
|
900
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
|
+
}
|
|
901
956
|
const outBudget = entry.outBudget
|
|
902
|
-
|
|
957
|
+
const budgetArmed = entry.steps >= (entry.outBudgetAfterSteps || 0)
|
|
958
|
+
if (outBudget > 0 && budgetArmed && !entry.outBudgetSent && stats.turnOutTokens >= outBudget) {
|
|
903
959
|
// Evidence that the turn is expensive, not that it is long: the counter is
|
|
904
960
|
// the provider's own usage. A three-step turn that wrote 30 000 tokens is a
|
|
905
961
|
// cost problem the step counter cannot see, and a twenty-step turn that
|
package/src/config.js
CHANGED
|
@@ -128,6 +128,15 @@ export const Config = z.object({
|
|
|
128
128
|
'输出 token 预算 / Output token budget: 本轮累计输出 token(上游 usage 的 outputTokens)达到该值时,调度器提醒一次「收掉当前闭环、跑一次证明它的检查、汇报」;0 = 关闭(默认)。触发的是真实解码量而非步数:三步写出三万 token 的任务很贵,二十步只写两千 token 的任务不贵。属于成本上限,不是完成度证据——默认关闭,避免在仍在推进的任务上提前收尾。',
|
|
129
129
|
),
|
|
130
130
|
|
|
131
|
+
outputBudgetAfterSteps: z
|
|
132
|
+
.number()
|
|
133
|
+
.step(1)
|
|
134
|
+
.min(0)
|
|
135
|
+
.max(80)
|
|
136
|
+
.default(0)
|
|
137
|
+
.description(
|
|
138
|
+
'输出预算生效步数 / Budget arm delay: 只有本轮已经跑了这么多步之后,`outputBudget` 才允许提醒一次;0 = 立即生效(沿旧行为)。逐家族实测中同一个 8000 token 预算在长任务(ledger)上省 47 % 墙钟,在短任务(codeimpl/debugfix)上因为提前收尾反而慢 12–47 %;这条延迟让预算只对「已经变长」的轮次起作用。',
|
|
139
|
+
),
|
|
131
140
|
openBatch: z
|
|
132
141
|
.boolean()
|
|
133
142
|
.default(false)
|
|
@@ -142,6 +151,36 @@ export const Config = z.object({
|
|
|
142
151
|
'整文件改写提醒 / Prefer hunks: 一次整文件 write(参数 ≥ 2000 字符)之后,提醒一次「改已有文件时只发改动的行(edit hunk),不发整个文件」。落盘内容相同,省的是工具参数的输出 token(约占输出字符 28%)。只发一次,不改完成判定,不改复验。',
|
|
143
152
|
),
|
|
144
153
|
|
|
154
|
+
disciplineAfterSteps: z
|
|
155
|
+
.number()
|
|
156
|
+
.step(1)
|
|
157
|
+
.min(0)
|
|
158
|
+
.max(40)
|
|
159
|
+
.default(0)
|
|
160
|
+
.description(
|
|
161
|
+
'压缩指令的入场步 / Compression entry step: `outputDiscipline: late` 时,第几步才提醒「剩下的话只写下一步要用的」;0 = 用内置值(4)。这条提醒是本插件唯一被证明有效的机制(回合变少 + 每步输出变短),所以它的入场点值得单独调:太早会在任务还没展开时就把话掐掉,太晚则前几步的散文已经花掉。0 保持旧行为。',
|
|
162
|
+
),
|
|
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
|
+
|
|
145
184
|
workspaceMap: z
|
|
146
185
|
.boolean()
|
|
147
186
|
.default(false)
|
package/src/effort.js
CHANGED
|
@@ -250,6 +250,22 @@ export function resolveConfig(raw, overrides) {
|
|
|
250
250
|
scheduler: gear.directive && config.scheduler !== false,
|
|
251
251
|
/** Optional ceiling on decoded output per turn, from upstream usage; 0 = off. */
|
|
252
252
|
outputBudget: gear.directive ? Math.max(0, Math.floor(number(config.outputBudget, 0))) : 0,
|
|
253
|
+
/** Steps a turn must have run before the output budget may fire; 0 = immediately. */
|
|
254
|
+
outputBudgetAfterSteps: gear.directive
|
|
255
|
+
? Math.max(0, Math.floor(number(config.outputBudgetAfterSteps, 0)))
|
|
256
|
+
: 0,
|
|
257
|
+
/** Step at which the late compression reminder fires; 0 = built-in default. */
|
|
258
|
+
disciplineAfterSteps: gear.directive
|
|
259
|
+
? Math.max(0, Math.floor(number(config.disciplineAfterSteps, 0)))
|
|
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,
|
|
253
269
|
/** One "batch the opening step" reminder per turn, for short tasks. */
|
|
254
270
|
openBatch: gear.directive && config.openBatch === true,
|
|
255
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
|
-
|
|
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
|
-
|
|
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 } })
|