dsh-fast-mode 1.6.1 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BENCHMARK.md CHANGED
@@ -5,6 +5,86 @@
5
5
  > 1.4.4 把「第 10 步提醒收尾」设为默认时确实更快(下节 14 对中位 1.86×、正确性零损失),但它对**仍在推进的长任务**同样开火,这是 1.5.0 撤回该默认的原因。**以下 1.4.4 及更早的数字全部是历史与消融证据,不是 1.5.0 的速度声明**;`fl10` 就是那条退役默认的消融臂。1.5.0 默认在隔离环境下的干净重基线见下一节「1.5.1」(10 家族、30 个单元、总墙钟 0.72×;复现块 W5、W7 总量各 0.73×,对照块 W8b 逐格中位 0.717×;W3+W5+W7 合计 65 个有效单元 0.74×)。
6
6
  >
7
7
  > **口径(1.5.1 起)**:一个单元只在 `exit == 0 && 步数 ≥ 1 && 输出 > 0 && 判分完成 && 未泄漏` 时计数;运行崩溃的单元整格作废并点名,不算红、也不算快。所以「全绿」一律指**有效单元全绿**(1.5.1 29/29、W5 19/19、W7 17/17)。
8
+ > **1.7.0**:`lazy` 与它改 `reasoningEffort` 的那一半已删除,换成三个默认关闭的纯提示开关(`probeFirst` / `actBeforePlan` / `repeatNudge`)。第一次实测见下节「块 DP17」:三开 12 对 **0.647×**、CI95 [0.501, 0.893]、判分全绿;单开三臂都还没到显著。
9
+
10
+ ## 块 DP19(2026-11-01):1.7.1 的回归复核——`actBeforePlan` 多带一条长度提醒后,`debugfix`+`migrate` 8 对 **0.701×**,判分 16/16
11
+
12
+ 1.7.1 把「本回合思考写太长」的提醒合进了 `actBeforePlan`(不新增开关,面板仍七行)。要回答的问题是:这个臂在 `DP17` 的两族旧任务上曾经是 `debugfix` 0.582×、`migrate` 0.996×,多一个行为之后会不会退化。
13
+
14
+ **方法。** `debugfix`、`migrate` 各 4 对、共 8 对;两臂 `off` 对 `ab17`,ABBA 交替先后,16 次运行,判分 16/16 绿、零失败、零泄漏(`bench/results/suite-DP19.jsonl`)。
15
+
16
+ | 臂 | 汇总墙钟比 | CI95 | 更快 | 单对范围 |
17
+ |---|---|---|---|---|
18
+ | `ab17`(1.7.1,两个行为) | **0.701×** | **[0.516, 0.930]** | 6/8 | 0.33 / 1.24 |
19
+
20
+ 分家族(逐对几何,括号里是同一族在 `DP17` 的 1.7.0 读数):`debugfix` **0.756×**(4/4 更快;`DP17` 0.582×)、`migrate` **0.649×**(2/4 更快;`DP17` 0.996×)。思考字符几何均值 `off` 28275 → `ab17` **17834**;步数 9.27 → 7.62。
21
+
22
+ **提醒确实在真实运行里发出了(翻会话记录数的,不是从代码推的):** 4 个 `ab17` 会话各收到 1–2 条长度提醒(每回合上限 2),4 个 `off` 会话 0 条;同批会话里每回合思考字符 5350–120897,4000 的门槛确实被越过。
23
+
24
+ **结论与局限。** 加长度提醒没有让这个臂在旧任务上退化:点估计 0.701×、CI 上界 0.930,比 `DP17` 同期两族(0.582× / 0.996×)没有变差的方向证据。但只有 8 对、`migrate` 只有 2/4 更快、单对区间 0.33–1.24×,**不足以说它更快**,更不足以把两个行为各自的贡献拆开——那需要一个只带长度提醒的独立 arm,本轮没做,也不打算为了拆账再加一个面板开关。
25
+
26
+
27
+ ## 块 DP18(2026-11-01):三族复核 1.7.0 的三臂,`trio17` 9 对 **0.491×**,CI95 [0.314, 0.788],判分全绿
28
+
29
+ `DP17` 只覆盖两族短任务,所以在三族更长的任务上复核同一批 arm。**这一块量的是 1.7.0 的代码**——长度提醒(1.7.1)还没上,所以 `ab17` 在这里只代表「先动手再解释」一个行为。
30
+
31
+ **方法。** 家族 `codeimpl`(14 项判分)、`ledger`(22 项判分)、`stategraph`;每族 3 对、共 9 对;四臂 `off` / `pf17` / `ab17` / `trio17` 按对轮转,同对内交替先后;共 36 次运行,全部单元判分与 `off` 逐格一致、零失败、零泄漏。
32
+
33
+ | 臂 | 汇总墙钟比 | CI95 | 更快 | 单对范围 |
34
+ |---|---|---|---|---|
35
+ | `pf17` | 0.719× | [0.492, 0.999] | 7/9 | 0.21 / 1.56 |
36
+ | `ab17` | 0.643× | [0.427, 0.920] | 6/9 | 0.21 / 1.39 |
37
+ | `trio17` | **0.491×** | **[0.314, 0.788]** | **8/9** | 0.18 / 1.97 |
38
+
39
+ 分家族(逐对几何):`codeimpl` 上 `pf17` 0.953× / `ab17` 0.784× / `trio17` 0.869×;`ledger` 上 0.490× / 0.979× / **0.331×**;`stategraph` 上 0.797× / **0.346×** / 0.411×。思考字符(几何均值)`off` **71275** → `pf17` 59930 / `ab17` 44617 / `trio17` **32463**;步数 12.86 → 7.21 / 8.98 / 6.87。
40
+
41
+ **与 `DP17` 合池(21 对):** `pf17` 0.809× [0.643, 1.002]、`ab17` **0.708×** [0.558, 0.898]、`trio17` **0.575×** [0.444, 0.748]。两块方向一致:这三个臂不是「只对短任务有效」。
42
+
43
+ **读法。** 三点:
44
+
45
+ 1. **`ledger` 与 `stategraph` 是这次收益来源。** `trio17` 在这两族 0.331× / 0.411×,`ab17` 在 `stategraph` 0.346×;思考字符从 71275 降到 32463,方向仍然是「少想,不少验证」——判分 36/36 与 `off` 相同。
46
+ 2. **`ab17` 在 `ledger` 上不赚(0.979×)。** 与 `DP17` 的 `migrate`(0.996×、步数 11.0 → 15.7)同类:需要先列清单的任务被「先动手」推着绕路。所以它保持默认关,按任务形状自己试。
47
+ 3. **`trio17` 在两块里都是唯一 CI 不跨 1 的臂**(`DP17` 0.647×、`DP18` 0.491×、合池 0.575×)。三开不是三个平均值,组合在两个方向上都不亏。
48
+
49
+ **局限。** 每族只有 3 对(单对区间 0.18–1.97×),比 `DP17` 的 6 对更薄;原计划 5 对,为了「同一时刻只跑一个块、不长时间占满 CPU」的纪律减到 3 对,代价是区间更宽。所以对外仍只说合池的 `trio17` 0.575× 与两块各自的点估计,不拿单族数字做承诺。`repeatNudge` 在 `DP17` 里只被触发 3 次(详见该块局限),这一块没再单独量它。
50
+
51
+ ## 块 DP17(2026-11-01):三个纯提示侧开关,读一次事实 / 先动手 / 重复提醒;三开 12 对 **0.647×**,CI95 [0.501, 0.893],判分全绿
52
+
53
+ 1.7.0 删掉了 `lazy` 与它改 `reasoningEffort` 的那一半(依据是 `LZ1` 里两臂对打 1.106×、CI 跨 1,见下文 `LZ1` 块),换成三个**只改提示、不改任何请求字段**的开关。这一块就是它们的第一次真测量。
54
+
55
+ **这一块量的是 1.7.0 的代码**(1.7.1 给 `actBeforePlan` 补了按思考长度的提醒,所以本块的 `ab17` 只代表「先动手再解释」一个行为)。
56
+
57
+ **方法。** 家族 `debugfix`、`migrate`;每族 6 对、共 12 对;五个 arm 配置(`off`、`pf17`=`probeFirst`、`ab17`=`actBeforePlan`、`rn17`=`repeatNudge`、`trio17`=三开)按对轮转,同对内的 `off` 与各臂交替先后。共 60 次运行,全部单元的判分与 `off` 逐格一致,零失败。比值 = 该臂墙钟 / 同对 `off` 墙钟,家族与合池都用逐对几何平均;CI 是 4000 次 bootstrap 的 2.5 / 97.5 分位。
58
+
59
+ | 家族 | 开关 | 墙钟比 | 更快 | 步数(几何均值) | 思考字符(几何均值) |
60
+ | --- | --- | --- | --- | --- | --- |
61
+ | `debugfix` | `pf17` | 0.993× | 3/6 | 4.8 / 5.3 | 7329 / 8249 |
62
+ | `debugfix` | `ab17` | **0.582×** | **6/6** | 4.2 / 5.3 | **3152 / 8249** |
63
+ | `debugfix` | `rn17` | 1.038× | 4/6 | 5.0 / 5.3 | 8792 / 8249 |
64
+ | `debugfix` | `trio17` | **0.632×** | **6/6** | 4.8 / 5.3 | 4241 / 8249 |
65
+ | `migrate` | `pf17` | 0.785× | 4/6 | 10.6 / 11.0 | 45499 / 61560 |
66
+ | `migrate` | `ab17` | 0.996× | 2/6 | 15.7 / 11.0 | 52268 / 61560 |
67
+ | `migrate` | `rn17` | 0.978× | 3/6 | 10.5 / 11.0 | 52945 / 61560 |
68
+ | `migrate` | `trio17` | **0.663×** | 4/6 | 11.3 / 11.0 | **34783 / 61560** |
69
+
70
+ | 合池(12 对) | 墙钟比 | CI95 | 更快 | 最快 / 最慢单对 |
71
+ | --- | --- | --- | --- | --- |
72
+ | `pf17` | 0.883× | [0.663, 1.176] | 7/12 | 0.31 / 1.99 |
73
+ | `ab17` | 0.761× | [0.578, 1.032] | 8/12 | 0.44 / 2.12 |
74
+ | `rn17` | 1.007× | [0.839, 1.227] | 7/12 | 0.62 / 1.90 |
75
+ | `trio17` | **0.647×** | **[0.501, 0.893]** | **10/12** | 0.30 / 2.44 |
76
+
77
+ **读法。** 三点必须一起说清:
78
+
79
+ 1. **`actBeforePlan` 是这次真正对着「思考链过长」的那一刀。** `debugfix` 上它 0.582×、6/6 更快,思考字符(几何均值)8249 → 3152(**0.38×**),步数(几何均值)5.3 → 4.2。也就是说收益确实来自「少想」,不是少做验证——同族判分逐格相同。
80
+ 2. **它不是通用加速。** `migrate` 上它 0.996×、2/6 更快,步数反而 11.0 → 15.7:该族要先列清单再动手,硬推「先动手」会绕路。所以它保持默认关,按任务形状自己试。
81
+ 3. **`repeatNudge` 单开等于没变(1.007×),但也不拖后腿。** 单开 12 对里 7 对更快、CI 完全盖住 1;放进三开组合后合池 10/12 更快、0.647×,是这一版唯一 CI 不跨 1 的臂。它的价值在「组合里不亏」,不在单开收益;这一点如实写,不做成「重复提醒有效」。
82
+
83
+ **局限。** 每族 6 对、两族合计 12 对,`trio17` 的单对区间是 0.30–2.44×,尾部有反向格;`ab17` 的合池 CI 上界 1.032 仍然擦过 1。所以对外只说 `trio17` 这一条 0.647×(CI 不跨 1),单开三臂一律写成「方向一致、尚未单独显著」。更宽的三族复核(`codeimpl`/`ledger`/`stategraph`)已跑完,见上一节「块 DP18」。
84
+
85
+ **`repeatNudge` 的 1.007× 是「几乎没触发」的读数,不是「提醒无效」的读数。** 把 `rn17` 那 12 个会话的记录翻出来数原文:12 个会话里只有 2 个收到过提醒(合计 3 条),而且全部来自「连续结果内容相同」那条分支(观察到相同条数 3、7、7);「同一调用本回合已跑 ≥2 次」这条分支一次都没满足。离线回放同一批会话:`stale` 分支在 5 次 `debugfix` 关闭态会话里会触发,`migrate` 里 0 次;每回合 `signature` 最多重复次数均值 1.18、`digest` 最长连击均值 2.76。也就是说阈值对着「极端打转」而不是「日常长链」——所以 1.7.1 才在 `actBeforePlan` 里补了按思考长度触发的第二条提醒,而不是继续调这个阈值猜测。
86
+
87
+
8
88
 
9
89
  ## 提醒投递的稳定性(2026-10-29):压缩重入会让同一轮收到两条
10
90
 
@@ -1715,7 +1795,7 @@ python3 bench/suite-run.py --tasks debugfix,ledger,codeimpl --arms off,def15,bud
1715
1795
 
1716
1796
  判据在跑之前就写死了:同一家族先看逐对方向是否一致,再合池;只要逐对方向随机就不给结论。这里 6 对里 3 对更快、3 对更慢,几何 1.014 与 1.0 无法区分,输出 token 只少 5 % 也没换来墙钟 ⇒ **`shapeGate` 不是速度杠杆**,与上面的分解一致(省时只能靠少输出/少轮次,而 gate 的输出变化不稳定)。因此它保持默认关闭;它的价值只在「形态约束」本身,不在速度。
1717
1797
 
1718
- **全语料复核宽度引导本身(251 个已录会话,离线读 transcript,0 成本)。** 上面测的是 `shapeGate` 这个开关;再往下问一层:**请求形状判定的宽度建议,到底有没有让模型真的并行**?做法是把每个 run 目录的会话 transcript 反解出来,取第一条 `user/message` 文本喂给 `classifyEntry()`,再数第 1 步真实的 `tool/call` 事件数。结果:251 个会话里判定为宽(width>1)只有 **24** 个,而这 24 个里 **22 个第 1 步只发了 1 次调用**(只 2 个 ≥2);反过来判为 native 的 227 个里也只有 2 个第 1 步 ≥3 次。按 (任务, 对) 把 `off` 与开档臂配对看,**24 对里 23 对第 1 步调用数完全相同**,剩下 1 对是关闭档更多:宽度建议在语料里**没有可测行为效果**。原因不是判定错了,而是这套基准语料是「读一个仓库、迭代修改」的串行型任务,本来就不该扇出(这也和前面 90.7% 的步是单调用互相印证)。所以:收益**不来自批量**,只来自步数与输出长度;宽度引导作为「形态约束」保留,但不能再被当成提速机制来讲。
1798
+ **全语料复核宽度引导本身(251 个已录会话,离线读 transcript,0 成本)。** 上面测的是 `shapeGate` 这个开关;再往下问一层:**请求形状判定的宽度建议,到底有没有让模型真的并行**?做法是把每个 run 目录的会话 transcript 反解出来,取第一条 `user/message` 文本喂给 `classifyEntry()`,再数第 1 步真实的 `tool/call` 事件数。结果:251 个会话里判定为宽(width>1)只有 **24** 个,而这 24 个里 **22 个第 1 步只发了 1 次调用**(只 2 个 ≥2);反过来判为 native 的 227 个里也只有 2 个第 1 步 ≥3 次。按「任务、对」把 `off` 与开档臂配对看,**24 对里 23 对第 1 步调用数完全相同**,剩下 1 对是关闭档更多:宽度建议在语料里**没有可测行为效果**。原因不是判定错了,而是这套基准语料是「读一个仓库、迭代修改」的串行型任务,本来就不该扇出(这也和前面 90.7% 的步是单调用互相印证)。所以:收益**不来自批量**,只来自步数与输出长度;宽度引导作为「形态约束」保留,但不能再被当成提速机制来讲。
1719
1799
 
1720
1800
  这也顺带说明 1.5.10 那个接线 bug 的影响面:旧块里 `bud8`/`sg15`/`gateonly`/`gate15` 的分支数据一律标 **untested**,不能当机制证据;上面这张表是修复后的第一次真测量。
1721
1801
 
@@ -2562,4 +2642,46 @@ metrics 242400 steps written, caches trimmed
2562
2642
  - 能说:在 12 对同题同配的对照里,打开 `lazy` 后 11 对更快,几何 0.39–0.43×,判分与输出正确性没有任何一格变红;机制是「步数变少 + 每步推理字符变少」,两侧同时发生,不是纯粹少说话。
2563
2643
  - 能说:**收益主要来自提示文字那一半**。把两臂直接对打:`laz16 / lazt16` 的逐对几何是 **1.106×**(95 % CI [0.682, 1.792],12 对里 5 对更快)——也就是说「降 `reasoningEffort`」这一半在这条路由上**没有测出额外收益,也没测出明显代价**。这与本文件早先的记录一致:这条路由的 usage 里 `reasoning` token 一直是 0,`reasoning_effort` 是否被上游当真从未被证明;`lazy` 的第二通道(流式推理字符)就是为这件事加的,读数显示两臂推理字符同量级(0.372× 对 0.418×)。
2564
2644
  - 不能说:这不是「所有题都变快 2.6 倍」。样本是这两族短任务、单机、单路由,`def15` 本块的中位偏慢侧(历史上 `codeimpl` 中位 161 s、`ledger` 649 s,本块 174.9 s 跨两族),逐对设计吸收了基线漂移,但换模型/换路由后倍数会变。也不要说 `lazy` 提高质量:它只做到「没把质量弄坏」——216/216 与 `def15` 持平,不是更好。
2645
+ - **这一块的臂已经不存在了,读数也只复现了一部分。** `lazy` 在 1.7.0 被整个删掉(连同改 `reasoningEffort` 的那一半),所以 `def15`/`laz16`/`lazt16` 三个 arm 现在只能从 git 历史里取回。后续用同一批家族做纯文本变体对照时(`LZ2` 的 `v2 / v1` 几何 **0.699×**、`LZ3` 几何 **0.822×** 但中位 1.014×),都没有复现出这里的 0.39–0.43×,只落在 0.70–0.82× 一带;所以本块的 0.39× 只能当**一次性读数**引用,不能当「这个提示的稳定收益」。引用它时必须同时说这两件事。
2565
2646
  - 一块**退役 0.9.x 教训**的验证:降档不跨轮、不跨会话、每步最多一次、每轮最多两次、工具报错后两步内不降、地板 `low`、从不升档,且每个请求都以「用户此刻选的档位」为基准重新计算(`agent/request` 读的是刚解好的 `reasoningEffort`)。所以用户中途换档,下一次请求就是用户的档,不会像 0.9.x 那样把会话钉在低档上。
2647
+
2648
+ **降档确实在真实运行里发生了(机器证据,不是推理):**跑完后翻宿主的计量文件 `~/.dsh/fast-mode-metrics.jsonl`,`laz16` 臂的 8 个会话里有 **15 个步行**同时带着 `effort` 与 `effortBase`,全部是 `effort: 'low'` 对 `effortBase: 'medium'`——也就是用户选的是 `medium`,那一步因为推理字符超量被降了一档,行里两列同现就是证据(未降档的请求会显式清掉 `effortBase`,不会把降档归因到后来每一步)。同一批行里 `reasoningChars` 有非零读数(41 / 226 / 347 / 537 / 569 字符),说明第二测量通道也在工作,不是靠 provider 那个恒为 0 的 `reasoning` token。`lazt16` 与 `def15` 臂在这些会话里没有一条 `effortBase`——开关关掉就是真关掉。
2649
+
2650
+ ## 结果:`LZ2`(`lazy` 的梯子文本 v1 / v2 / v3 三版对照,36 次运行)——v2 对 v1 **0.699×**,判分 648/648
2651
+
2652
+ `LZ1` 的结论是「收益来自提示文字那一半,降 `reasoningEffort` 那一半没测出额外收益」,所以下一个杠杆就是**文字本身**。这一块把 `lazy` 的梯子文本做成三个 bench 变体(`lazyVariant: v1|v2|v3`,隐藏字段,不进面板),三臂都开 `lazy: true` 且都关降档(`lazyEffort: false`),只看文字:
2653
+
2654
+ - `v1`:1.6.0/1.6.1 已发布的那份(762 字符,五条梯子 + 四类「绝不省略」)。
2655
+ - `v2`:`v1` 加两条——「不要重述任务或先解释计划」「下一步已经清楚就直接做,不要再权衡一遍不打算采用的方案」(973 字符)。
2656
+ - `v3`:同样规则的精简重写(629 字符),测「更短的提示能不能保住收益」。
2657
+
2658
+ `codeimpl` 6 对 + `ledger` 6 对,ABBA 交替,36 次运行,全部 `green`,判分 **648/648**(三臂同题同判,`bench/results/suite-LZ2.jsonl`)。
2659
+
2660
+ | 臂 | 墙钟合计 | 墙钟中位 | 步数合计 | 推理字符合计 | 输出 token 中位 |
2661
+ |---|---|---|---|---|---|
2662
+ | `lz2v1` | 1073.0 s | 80 s | 68 | 416 873 | 10 936 |
2663
+ | `lz2v2` | **727.8 s** | **41 s** | 63 | **268 806** | **6 802** |
2664
+ | `lz2v3` | 1132.5 s | 71 s | 68 | 401 247 | 8 884 |
2665
+
2666
+ 逐对(各 12 对):`v2 / v1` 几何 **0.699×**,中位 0.597×,**9/12 更快**,输出 0.715×,步数 0.934×;`v3 / v1` 几何 0.997×,中位 0.848×,7/12 更快,输出 0.935×,步数 0.966×。机制和 `LZ1` 一致:`v2` 的推理字符是 `v1` 的 **0.645×**——省的是每一步里想的时间,不是步数。
2667
+
2668
+ **但这一块不能单独下结论。** 12 对里有两处大幅反向(`codeimpl` 第 2 对 2.15×、第 5 对 2.03×,`ledger` 第 2 对 4.41×),符号检验 9/12 并不显著,所以按预先约定再跑一块**换族**的确认块。
2669
+
2670
+ ## 结果:`LZ3`(确认块:`debugfix` + `migrate` + `stategraph`,30 次运行)——几何 0.822×,**中位 1.014×**,短任务全慢
2671
+
2672
+ 同一对臂(`lz2v1` 对 `lz2v2`),三族各 5 对,15 对 30 次运行,判分 **370/370** 全绿(`bench/results/suite-LZ3.jsonl`)。
2673
+
2674
+ | 臂 | 墙钟合计 | 墙钟中位 | 步数合计 | 推理字符合计 | 输出 token 中位 |
2675
+ |---|---|---|---|---|---|
2676
+ | `lz2v1` | 1459.5 s | 85 s | 108 | 429 822 | 10 011 |
2677
+ | `lz2v2` | 1070.8 s | 53 s | 88 | **310 316** | **6 737** |
2678
+
2679
+ 逐对几何 **0.822×**、中位 **1.014×**、7/15 更快。分族看才是这块的重点:
2680
+
2681
+ - `debugfix`(小任务,5 对):**5/5 全慢**,1.08× / 1.21× / 1.22× / 1.22× / 1.23×。多出来的两句话在这种 2–3 步的任务上是纯成本。
2682
+ - `migrate`(5 对):0.46× / 0.60× / 0.53× 快,1.44× / 1.56× 慢,几何约 0.94×。
2683
+ - `stategraph`(长任务,5 对):0.32× / 0.51× / 0.81× / 0.49× / 1.01×,几何约 0.58×——长的、工具多的任务上收益最大(单次最长的三对合计从 690 s 降到 305 s)。
2684
+
2685
+ **决定:不换默认文本,`v1` 保持发布态。** 两轮合起来(27 对)几何约 0.765×,但分布是「长任务大赚、短任务倒亏」,而且 `LZ3` 的中位没有改善(1.014×)。把它设成默认等于拿所有短任务去换长任务,这一版不做这个交易。`lazyVariant` 保留在 schema 里(默认 `v1`、不进面板)仅仅是为了这些实验能被重跑;`v3` 的结论是「更短并不更好」(0.997×),也一并留档。
2686
+
2687
+ **质量口径:** 两块的判分与对照臂逐题完全一致(648/648、370/370),没有出现「更快但错得更多」的题;这两块不能用来宣称质量提升,只能说明换文本没把质量弄坏。
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.1(`actBeforePlan` 补上第二个信号:本回合思考已写多长;仍然只改提示词,不加面板行)
4
+
5
+ 1.7.0 用 `repeatNudge` 去抓「没新信息还在想」,但本语料里模型很少把同一个调用原样重发第三遍:`DP17` 十二个会话只发出 3 条提醒,全部来自「连续结果相同」那条分支,「同调用 ≥2 次」一次都没满足。所以第二个信号换成本来就记在会话里的东西——**这一回合已经写了多少字符的思考**。
6
+
7
+ - **判定与文本。** `assistant/message` 事件里的 `reasoning` 块按字计数(`src/usage.js` 的 `readReasoningChars`),按回合累加(`noteReasoning`);到 `REASONING_CHARS`(4000)字符时,在下一步之前给一条通知,之后每再翻一个 4000 的台阶可以再来一条,每回合上限 `MAX_REASONING_NUDGES`(2)。文本写明它看到的字数并给两个出口:`You have written about N characters of reasoning in this turn. Past that length, more thinking about facts you already have rarely changes the next action: take the action the evidence supports, or read the one fact that is still missing.` 触发条件只有长度本身,所以文本**不断言**「没有新信息」。
8
+ - **它挂在 `actBeforePlan` 上,没有新开关。** 面板固定七行,加第四行会破坏「七行」这个说法;这条和「先动手再解释」是同一个方向(把计划和推理压到能动作为止),所以合在一个开关里。代价必须说清:`ab17` 从此同时量两个行为,读数只能按「两个行为一起」解释。
9
+ - **仍然一个请求字段都不碰。** 字数只读不写,`agent/request` 照旧原样返回 `resolved`。路由不吐 reasoning 块时读回 0,开关在那条路由上保持沉默——默认关闭的东西该有的失败方式。
10
+ - **`DP18` 复核的是 1.7.0 的读数**(长度提醒还没上):`codeimpl`/`ledger`/`stategraph` 三族各 3 对、四臂轮转、36 次运行,`trio17` **0.491×** [0.314, 0.788]、8/9 更快,`ab17` **0.643×** [0.427, 0.920]、6/9,`pf17` **0.719×** [0.492, 0.999]、7/9;与 `DP17` 合池 21 对后 `ab17` 0.708× [0.558, 0.898]、`trio17` 0.575× [0.444, 0.748](18/21 更快)。思考字符几何均值 `off` 71275 → `ab17` 44617 / `trio17` 32463,步数 12.86 → 8.98 / 6.87,判分全绿。计划里的 5 对减到 3 对是资源纪律(同一时刻只跑一个块、不长时间占满 CPU),理由与代价写进 `BENCHMARK.md`。
11
+ - 测试 334 → **337 全绿**(`test/deliberation.test.js` +2、`test/usage.test.js` +1)。动 `src/deliberation.js`、`src/usage.js`、`index.js`、两个测试文件。
12
+
13
+ - **`DP19` 回归复核(1.7.1 的代码,`off` 对 `ab17`,`debugfix`+`migrate` 各 4 对、16 次运行):** `ab17` **0.701×** [0.516, 0.930]、6/8 更快、判分 16/16 绿;`debugfix` 0.756×(4/4,1.7.0 时 0.582×)、`migrate` 0.649×(2/4,1.7.0 时 0.996×),思考字符几何均值 28275 → 17834、步数 9.27 → 7.62。提醒在真实运行里发出过:4 个 `ab17` 会话各 1–2 条(每回合上限 2),`off` 0 条;同批每回合思考字符 5350–120897。结论只到「加了它没让这个臂变差」,8 对不足以说更快,也没拆开两个行为各自的贡献。
14
+
15
+ ## 1.7.0(删掉 `lazy` 与它改 `reasoningEffort` 的那一半;换成三个默认关闭的纯提示侧开关:`probeFirst`「不确定时先查一次」、`actBeforePlan`「先动手再解释」、`repeatNudge`「重复无进展时提醒」;12 对 5 臂 60 次运行里三开 0.647×,判分全绿)
16
+
17
+ 这一轮的两件事是同一个决定的两面:**插件从此不碰任何请求字段**,**以及**把「别想太久」换成不改答案的做法。
18
+
19
+ - **`lazy` 整个删掉,不是关掉。** `src/lazy.js`、`test/lazy.test.js`、`lazy`/`lazyEffort`/`lazyVariant` 三个配置键、面板第六行、`bench/arms/laz*.yml`、以及 `agent/request` 里那处唯一的 `{ ...resolved, reasoningEffort }` 全部消失。删的原因是上一轮自己测出来的:`LZ1` 里只发文字是 0.389× [0.252, 0.600],文字加降档是 0.430× [0.273, 0.677],两臂直接对打 1.106× [0.682, 1.792]——**置信区间跨过 1,请求侧那一半没有测出任何额外收益**;而且它是全插件唯一会改变答案而不只是改变时延的机制。加上这条路由在所有 9678 行测量里 `reasoning` 用量都报 0,参数是否被上游当真从来没有被证明过。删掉之后「默认契约」升级成绝对承诺:**`agent/request` 原样返回 `resolved`,仓库里不存在任何构造 `reasoningEffort:` 的代码**,不存在「某个开关打开时会改」的例外。
20
+ - **三个新开关都是纯提示侧、默认关闭、逐会话、上屏可见。**
21
+ - `probeFirst`「不确定时先查一次」:入场步一条通知——拿不准的代码库事实去读一次(先 grep 再 read,先读区间再读全文),不要用推理去猜。
22
+ - `actBeforePlan`「先动手再解释」:第一个工具调用之前最多一行计划;下一步已经清楚就直接做。
23
+ - `repeatNudge`「重复无进展时提醒」:回合进行中才说话,条件是**本地证据**——同一个调用(工具名 + 稳定的 arguments 键)本回合已经跑过 ≥2 次,或最近三条相邻结果内容完全一致;提醒里写明观察到的次数。每回合上限 2 条,同一份证据不重复提醒,**报错结果永不算作循环、并清掉陈旧计数**(失败是信息,不是原地打转)。
24
+ - probe 与 act 共用**一条**通知和一条 `GUARD`(「信任边界、数据丢失、安全、可访问性上绝不跳过验证」),两个都开也只花一条消息。
25
+ - **机制在 `src/deliberation.js`,有界且不可能污染工具结果。** `createProgressWatch`/`beginTurn`/`noteCall`/`planRepeatNudge` 加常量 `MAX_REPEAT_NUDGES = 2`、`REPEAT_CALLS = 2`、`STALE_RESULTS = 2`;内存上限 `RUN_MAX = 64` 个会话、每回合 `HISTORY_MAX = 8` 次调用;全部调用都包在既有的 catch 里,检测失败只会丢一条提醒,不会改变任何一步工具结果。
26
+ - **实测(`DP17`,`debugfix` + `migrate`,每族 6 对共 12 对、五个 arm 配置按对轮转,共 60 次运行;比值 = 该臂墙钟 / `off` 墙钟,逐对几何平均,括号是 95 % bootstrap CI):**
27
+
28
+ | 开关 | 汇总墙钟比 | 更快 | 测试 |
29
+ |---|---|---|---|
30
+ | `probeFirst` | 0.883× [0.663, 1.176] | 7/12 对 | 全绿 |
31
+ | `actBeforePlan` | 0.761× [0.578, 1.032] | 8/12 对 | 全绿 |
32
+ | `repeatNudge` | 1.007× [0.839, 1.227] | 7/12 对 | 全绿 |
33
+ | 三个全开(`trio17`) | **0.647× [0.501, 0.893]** | **10/12 对** | 全绿 |
34
+
35
+ 分家族看差别很大:`debugfix` 上 `actBeforePlan` 0.582×、6/6 更快,思考字符(几何均值)8249 → 3152(**这正对着「思考链过长」这个现象**);`migrate` 上它 0.996×、步数反而(几何均值)11.0 → 15.65,说明该族需要先规划,硬推会绕路。`probeFirst` 在 `migrate` 上 0.785×(思考字符 61560 → 45499),在 `debugfix` 上 0.993×。`repeatNudge` 单开等于没变,但它在组合里不拖后腿——三开 0.647× 是本次唯一置信区间不跨过 1 的臂。**所有臂、所有对,判分与 `off` 一致,零失败。** 更宽的三族验证(`DP18`)跑在 `codeimpl`/`ledger`/`stategraph` 上,结果补齐后写进 `BENCHMARK.md`。
36
+ - **`repeatNudge` 为什么单开等于没变:它基本没被触发。** 把 `rn17` 那 12 个会话的记录翻出来数提醒原文:12 个会话里只有 2 个各收到过提醒(`debugfix-p2` 一条、`p5` 两条),合计 **3 条**,而且全部来自「连续结果内容相同」那条分支(观察到的相同条数是 3、7、7);「同一调用本回合已跑 ≥2 次」这条分支**一次都没满足**。原因不是逻辑坏了,是阈值对着「极端打转」而不是「日常长链」:本语料里模型很少把同一个调用原样重发第三遍。所以 1.007× 的读数其实是「几乎没发生」的读数,不能当成「提醒没用」。这一条如实写在这里,下一轮要改就改阈值本身(不加开关、不加面板行),并用新的臂重测。
37
+
38
+ - **没有动的东西:** 默认值一个没改(`enabled: false` 仍是出厂态)、`d2`/`d2g`/`dr8` 仍然 HOLD、没有新增提醒时机或预算旋钮、没有新增任何请求字段或系统提示段落。
39
+ - 测试 338 → **334 全绿**(删 15 条 lazy 测试,新增 `test/deliberation.test.js` 10 条 + `test/plugin.test.js` 9 条集成)。动 `index.js`(删流式 reasoning 块、入场接线、`agent/request` 原样返回、版本号)、`src/deliberation.js`(新)、`src/config.js`(三键换三键,32 键)、`src/effort.js`、`src/state.js`、`client.js`(三行面板 + 两个白名单 + 四个写路径)、`src/lazy.js` 与 `test/lazy.test.js`(删除)、`test/{client,config,effort,metrics,plugin,state}.test.js`、`bench/arm-config.mjs`、`bench/suite-run.py`、`bench/arms/{pf17,ab17,rn17,trio17}.yml`(新)、`package.json`、两份 README、`docs/REFERENCE.md`、`BENCHMARK.md`。
40
+
41
+ - **`DP18` 补齐(三族复核,1.7.0 的臂):** `codeimpl`/`ledger`/`stategraph` 各 3 对、36 次运行,`trio17` **0.491×** [0.314, 0.788]、8/9 更快,`ab17` 0.643× [0.427, 0.920],`pf17` 0.719× [0.492, 0.999],判分全绿;与 `DP17` 合池 21 对:`ab17` 0.708× [0.558, 0.898]、`trio17` 0.575× [0.444, 0.748]。三个 arm 的方向在两族短任务(`DP17`)与三族长任务(`DP18`)上一致,`trio17` 是唯一在两块里 CI 都不跨 1 的臂。详见 `BENCHMARK.md` 的「块 DP18」。
42
+
43
+ ## 1.6.2(修面板:`lazy` 那张卡以前点不开;标签改成能看懂的说法;lazy 的梯子文本做了三版对照,不换默认)
44
+
45
+ - **「过度思考时降档」以前点不开,是真的坏了。** `client.js` 里浏览器侧的 `sessions` 列有两处白名单:`sanitiseChoices` 和 `withOwn`,它们认识 `enabled/gear/discipline/stepBudget/map/batch` 六个字段,唯独没写 `lazy`。于是点击「打开」时写出去的会话条目里根本没有这个 key,宿主读回来是「没有条目」,面板再读一遍又画成「关」——看起来就是按钮没有反应。宿主侧 `src/state.js` 一直是认识 `lazy` 的,缺的只是浏览器这一半。两处都补上,并加了一条跨侧回归测试(`test/state.test.js`:浏览器序列化 → 宿主 `choiceFor` 读回 `{lazy:true}` → 浏览器 `parseChoices` 再读回 `true`;非布尔值仍被丢弃)。**旧版本里已经点过的人不会留下坏数据**,因为那次点击压根没写进去。
46
+ - **标签改名,说明手段而不是口号。** 面板第六行从「想太久就少想一点」改成 **「过度思考时降档」**(英文 `Cap over-thinking`),tooltip 写清条件与保证:「一步里推理超量时,下一个请求降一档;安全校验照旧」。旧说法是建议、听起来像文风;新说法说的是这个开关实际做的事,也交代了降的是什么。
47
+ - **`lazy` 的梯子文本做了三版对照(`lazyVariant`,只在 bench 里选,不进面板)。** `v1` 是 1.6.0/1.6.1 已经发布的那份文本;`v2` 在它后面加两条(「不要重述任务或先解释计划」「下一步已经清楚就直接做,不要再权衡一遍不打算采用的方案」);`v3` 是同样规则的精简重写(629 字符 vs 762)。两轮对照(`LZ2` 12 对 36 次运行、`codeimpl`+`ledger`;`LZ3` 15 对 30 次运行、`debugfix`+`migrate`+`stategraph`)的结论是 **不换文本**:`LZ2` 里 v2 对 v1 是 0.699×(9/12 更快),但 `LZ3` 里反过来是 0.822×、中位 1.014、7/15 更快,`debugfix` 五对**全慢**(1.08–1.23×)——短任务上多出来的两句话是纯成本。v3 在 `LZ2` 是 0.997×,等于没变。两轮判分都和对照臂完全一致(648/648、370/370),所以这不是质量换速度,是**只在长任务上划算、在短任务上倒亏**的取舍;默认仍是 `v1`,`lazyVariant` 保留为复现实验用的隐藏字段,面板上看不到。
48
+ - **没有动的东西:** 默认值一个没改(`enabled: false`、`lazy: false` 仍是出厂态)、`lazyEffort` 仍默认 `true`、`HOT_CHARS`/`NUDGE_CHARS`/降档上限全部照旧、`d2`/`d2g`/`dr8` 仍然 HOLD、没有新增任何提醒时机或预算旋钮。
49
+ - 测试 336 → **338 全绿**。动 `client.js`(两处白名单 + 标签)、`src/config.js`(`lazyVariant`)、`src/lazy.js`(`DIRECTIVES` 三版)、`src/effort.js`、`index.js`(版本号 + 传 `variant`)、`test/state.test.js`、`test/config.test.js`、`test/lazy.test.js`、`bench/suite-run.py`、`bench/arm-config.mjs`、`bench/arms/lz2v{1,2,3}.yml`(新)、两份 README、`docs/REFERENCE.md`、`BENCHMARK.md`。
50
+
3
51
  ## 1.6.1(1.6.0 的补包:npm 页面上那句「从不改 reasoningEffort」在 1.6.0 之后不准确了)
4
52
 
5
53
  - 1.6.0 加了 `lazy`(默认关闭,唯一会改 `reasoningEffort` 的路径),但 `package.json` 的 `description` 还是旧话「Never changes reasoning effort, the model, or the request body」,npm 首页会照抄这句。这一版只改这一句:**默认**不改推理强度、模型与请求体,例外是默认关闭的 `lazy`(想太久时降一档)。行为、默认值、测试零改动(336 全绿)。
package/README.en.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  <p align="center">
11
11
  <b>DeepSeek Harness's own fast mode: a few configs that set how fast a task runs.</b><br>
12
- Never changes the model, and by default not its reasoning effort either; it cuts redundant round trips and output through execution-strategy hints — the gain is fewer steps and fewer output tokens, not deleted narration. The one exception is the off-by-default `lazy` switch: one rung down when a turn keeps over-thinking, and the switch is yours.
12
+ Never changes the model, its reasoning effort, or any request field; it cuts redundant round trips and output through execution-strategy hints — the gain is fewer steps and fewer output tokens, not deleted narration. The switches added in 1.7.0 are all prompt-side and off by default; the switch is yours (1.7.1 adds a length-triggered notice inside `actBeforePlan`, still no request field touched).
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -40,11 +40,11 @@ A step costs round-trip overhead × steps + decode time × output tokens + tool
40
40
  - **Output discipline.** Asks the model to skip call previews, restatements and progress reports, never to shorten the final answer. Honest scope: visible assistant prose is only **1.18 %** of the characters in (prose + tool arguments) (def15, 341 sessions / 3,327 messages / 3,569 calls, 51,366 prose vs 4,315,096 argument characters; excludes reasoning and tool results; recompute with `python3 bench/textshare.py --limit 0`), so the speedup cannot be attributed to deleted narration — it comes from fewer round trips and fewer output tokens (fewer repeat edits, re-reads and verification passes). That is not the same claim as "nothing about the work changes".
41
41
  - **Stall watchdog.** Shell/script calls get a 120–300 s adaptive cap; a stuck call is cancelled and "change the approach" is written back into the context.
42
42
 
43
- No model switch, no request-field injection, and no `reasoningEffort` byte written by default (`lazy` is the one off-by-default exception) (`service_tier`, `max_tokens`, anything like them). While the mode is on, the model's input carries this plugin's own scheduling guidance and reminders — that is the mechanism; switch the mode off and they are gone. DeepSeek's official API, an OpenAI-compatible relay, a self-hosted gateway, Anthropic routes — all work the same.
43
+ No model switch and no request-field injection of any kind: `agent/request` returns unchanged, not one byte of `reasoningEffort` is written, no exceptions (`service_tier`, `max_tokens`, anything like them). While the mode is on, the model's input carries this plugin's own scheduling guidance and reminders — that is the mechanism; switch the mode off and they are gone. DeepSeek's official API, an OpenAI-compatible relay, a self-hosted gateway, Anthropic routes — all work the same.
44
44
 
45
45
  ## Three boundaries
46
46
 
47
- - **The explicit promise**: the plugin does not change the model, does not inject request fields, and by default does not rewrite `reasoningEffort`. With `lazy` on it moves at most one rung down, never up, never below `low`, at most twice per turn, and every request re-reads your own choice first. That is where quality degradation would most likely come from, and by default the plugin stays out of it.
47
+ - **The explicit promise**: the plugin does not change the model, does not inject any request field, and does not rewrite `reasoningEffort`. Since 1.7.0 that has no exception: the old "one rung down" half was removed together with `lazy`, because it measured no extra gain and was the only mechanism that could change an answer.
48
48
  - **Honest mechanism**: execution-strategy hints cut redundant round trips and output. The evidence is falling steps and output tokens (whole-pool default arm: 0.806× steps, 0.818× output); no claim that the gain comes from deleting narration.
49
49
  - **Quality boundary**: quality is judged by hidden tests and real evaluations, and behaviour is not claimed to be fully equivalent. When the quality gate fails, quality wins: experimental knobs ship off, and the default is only adopted from batches where both arms' hidden assertions match cell by cell.
50
50
 
@@ -103,19 +103,21 @@ Legacy `fast` / `turbo` / `max` / `ludicrous` values are read as `auto` with a d
103
103
 
104
104
  ## The switch
105
105
 
106
- The bolt is the switch: one click writes on/off, per conversation, restored on reload. Right-click it for a six-row panel — the switch itself and five settings:
106
+ The bolt is the switch: one click writes on/off, per conversation, restored on reload. Right-click it for a seven-row panel — all seven affect this conversation only:
107
107
 
108
108
  - **Less narration** — master switch for the step-4 reminder; with it off, long tasks take about 2.7x longer and write about twice as much. On by default.
109
109
  - **Step cap** — `15` / `30` / off, off by default. At the cap it reports instead of truncating the task.
110
110
  - **Workspace inventory** — before the turn's first tool call, one inventory notice (relative paths + byte sizes + visible test entry points) so the turn does not open with `ls`/`find`. Off by default; over 23 pairs it measured 0.927x geometric with a slower tail and 10% more steps, so it stays a switch to try rather than a default.
111
111
  - **Read several files at once** — batches independent reads into one call. Off by default; the measured gain shows up when it is stacked with the inventory switch (below), not alone.
112
- - **Think less when it stalls** (`lazy`) — when a turn keeps pouring out reasoning, one notice asks for the cheapest rung that works and one rung less thinking on the next request. Off by default; it can change the answer, not only the timing.
112
+ - **Read one uncertain fact first** (`probeFirst`) — at turn entry, one notice: if a codebase fact is uncertain, read it once instead of reasoning about it. Off by default; 0.883x over 12 pairs (7/12 faster).
113
+ - **Act before explaining** (`actBeforePlan`) — at most one line of plan before the first action; act as soon as the next step is clear. Since 1.7.1 it also carries a length-triggered notice: once this turn has written 4000 characters of reasoning, the next step learns how much it wrote and gets two exits — act on the evidence, or read the one fact still missing (at most two per turn). Off by default; 0.761x over 12 pairs (8/12 faster) with reasoning characters 8249 → 3152 on `debugfix`; 0.643x over 9 pairs (6/9 faster) on three families in `DP18`, 0.708x [0.558, 0.898] pooled over 21 pairs.
114
+ - **Nudge on repetition** (`repeatNudge`) — when the same call has already run twice this turn, or three results in a row are identical, one line says how many times it repeated and asks for a different approach or a conclusion. Off by default; 1.007x alone, but it does not drag the three-switch combination down.
113
115
 
114
- All six rows take effect from the next request, per conversation. No dropdown, no multiplier estimate.
116
+ All seven rows take effect from the next request, per conversation. No dropdown, no multiplier estimate.
115
117
 
116
- **Turn several on at once?** Yes, but only the measured pair. Defaults plus inventory plus batched reads is **0.742x** over 11 pairs; adding the step-2 reminder timing (not a panel row, still on HOLD) reaches **0.669x** over 22 pairs, CI95 [0.554, 0.799], 18/22 faster, hidden tests green. Each switch alone lands at 0.80–0.88 with a confidence interval that crosses 1 — the gain comes from each one cutting a different share of the same cost, not from any single switch. The other knobs (`batchVerify`, `preferPatch`, step budget, wider fan-out) all land at 0.96–1.10; do not stack them.
118
+ **Turn several on at once?** Yes, but only the measured pair. Defaults plus inventory plus batched reads is **0.742x** over 11 pairs; adding the step-2 reminder timing (not a panel row, still on HOLD) reaches **0.669x** over 22 pairs, CI95 [0.554, 0.799], 18/22 faster, hidden tests green. Each of those two alone lands near 0.93x with a confidence interval that crosses 1 — the gain comes from each one cutting a different share of the same cost, not from any single switch. The other knobs (`batchVerify`, `preferPatch`, step budget, wider fan-out) all land at 0.96–1.10; do not stack them.
117
119
 
118
- `lazy` is not on that scale: it can change the answer, not only the timing, so even though the controlled block says it is faster (`LZ1`: 11 of 12 pairs faster, geometric 0.39–0.43×, 216/216 graded assertions green) it stays off by default and joins no default combination. The text-only arm's readings (`lazyEffort: false`) are in the same `LZ1` block of [BENCHMARK.md](BENCHMARK.md).
120
+ `probeFirst` / `actBeforePlan` / `repeatNudge` are the 1.7.0 additions; 1.7.1 adds a length-triggered notice inside `actBeforePlan` (no new switch, the panel stays seven rows). All three on (`trio17`) is the only arm whose interval stays below 1 in both blocks: **0.647x** [0.501, 0.893], 10/12 faster over 12 pairs in `DP17`; **0.491x** [0.314, 0.788], 8/9 faster over 9 pairs in `DP18`; **0.575x** [0.444, 0.748], 18/21 faster pooled over 21 pairs, grading green. `repeatNudge` alone is 1.007x and fired only 3 times across twelve sessions — that is "it almost never happened", not "it does not work"; `actBeforePlan` alone is 0.761x / 0.643x in the two blocks. None of them changes an answer or writes a request field, so they stay off by default.
119
121
 
120
122
  The idea draws on `dsh-batch-tool-calls` and `dsh-ops` (batched calls, bounded execution), but their widths are static while this width comes from the conversation's own call evidence; do not install all three. Context-compaction plugins coexist.
121
123
 
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  <p align="center">
11
11
  <b>DeepSeek Harness 自己的快速模式:用几项配置控制任务的运行速度。</b><br>
12
- 默认不改模型、不改 reasoningEffort;用执行策略提示减少冗余回合与输出——收益来自步数与输出 token 下降,不是删旁白。唯一的例外是默认关闭的 `lazy`:想太久时收一档,开关在你手里。
12
+ 默认不改模型、不改 reasoningEffort、不改任何请求字段;用执行策略提示减少冗余回合与输出——收益来自步数与输出 token 下降,不是删旁白。几个 1.7.0 新开关都是纯提示侧,默认关,开关在你手里(1.7.1 给 `actBeforePlan` 加了一条按思考长度的提醒,同样不碰请求字段)。
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -40,11 +40,11 @@
40
40
  - **输出纪律**:提示模型不预告调用、不复述结果、不写进度报告,但**不缩短最终答案**。口径要说清:实测里可见助手正文只占「正文+工具参数」字符的 **1.19%**(def15 全量 385 会话 / 3,659 消息 / 4,007 调用,正文 57,101 / 参数 4,757,921 字符;不含 reasoning 与工具返回;`python3 bench/textshare.py --limit 0` 可复算),所以提速不能归因于删旁白——它来自回合数与输出 token 变少(少重复编辑、少回头读、少验证往返),这和「内容不受影响」不是一回事。
41
41
  - **卡顿看门狗**:给 shell/脚本类调用设 120–300 秒自适应上限,卡住的调用被取消并把“换做法”写回上下文。
42
42
 
43
- 没有模型切换、没有请求字段注入;`reasoningEffort` 默认一个字节都不写(`lazy` 是唯一、默认关闭的例外);开着时模型输入里会多出本插件自己的调度提示与提醒(这是机制本身),关掉后连这些也不存在。DeepSeek 官方 API、OpenAI 兼容中转、自建网关、Anthropic 协议 —— 走哪条路由都成立。
43
+ 没有模型切换、没有任何请求字段注入:`agent/request` 原样返回,`reasoningEffort` 一个字节都不写,没有例外;开着时模型输入里会多出本插件自己的调度提示与提醒(这是机制本身),关掉后连这些也不存在。DeepSeek 官方 API、OpenAI 兼容中转、自建网关、Anthropic 协议 —— 走哪条路由都成立。
44
44
 
45
45
  ## 三条边界
46
46
 
47
- - **明确承诺**:不改模型、不注入请求字段、默认不改 `reasoningEffort`。打开 `lazy` 时它只降一档、从不升档、最低 `low`、每轮最多两次、下一个请求就从你的选择重新读起;降智风险最容易发生的地方,默认一个字段都不碰。
47
+ - **明确承诺**:不改模型、不注入任何请求字段、不改 `reasoningEffort`。1.7.0 起这条没有例外——上一版那半个「降一档」因为测不出额外收益、且是全插件唯一会改答案的机制,已经连同 `lazy` 一起删除。
48
48
  - **机制诚实**:靠执行策略提示减少冗余回合与输出。证据是步数与输出 token 下降(全池默认臂 0.806× 步数、0.818× 输出);不宣称收益来自「删旁白」。
49
49
  - **质量边界**:任务质量以隐藏测试判分与真实评测为准,不宣称行为完全等价。质量门槛不过就优先保质量:实验性旋钮默认关闭,默认配置只在两臂隐藏断言逐格相同的批次里采用。
50
50
 
@@ -103,20 +103,21 @@
103
103
 
104
104
  ## 开关
105
105
 
106
- 闪电就是开关:开/关即写,按会话独立,重启按会话恢复。右键闪电是一块六行的面板,六行都只影响当前会话:
106
+ 闪电就是开关:点一下即开/关,按会话独立,重启按会话恢复。右键闪电是一块七行的面板(闪电自己就是开关,不占行),七行都只影响当前会话:
107
107
 
108
- - **更快**:这个会话开不开快模式,和点闪电是同一件事。
109
108
  - **少写过程**:第 4 步那句「省着写」提醒的总开关,关掉后输出会变长、耗时变慢(长任务约 2.7×),默认开。
110
109
  - **最多几步**:`15` / `30` / 不限,默认不限。到上限只先汇报,不截断任务。
111
110
  - **先列文件清单**:开启后,回合的第一次工具调用之前先收到一份工作目录清单(相对路径 + 字节数 + 可见的测试入口),省掉开头的 `ls`/`find` 探路。默认关;23 个配对实测几何 0.927×,尾部变慢、步数 +10%,没有达到进默认的门槛,只作为可试的开关。
112
111
  - **一次读多个文件**:把互不依赖的读取合成一批发出,少几个来回。默认关;单独开测不到显著,和上一项一起开才有(见下)。
113
- - **想太久就少想一点**(`lazy`):一轮里思考文字明显超量时,先给一次"先找最省的一级、不扩范围、不砍校验"的提示,再把下一个请求的思考强度降一档。默认关;它动的是答案而不只是时延。
112
+ - **不确定时先查一次**(`probeFirst`):入场时提醒一句——拿不准的代码库事实去读一次,不要用推理猜。默认关;12 对实测 0.883×(7/12 更快)。
113
+ - **先动手再解释**(`actBeforePlan`):第一次动手前最多写一行计划,动作清楚就直接做;1.7.1 起它还带一条按思考长度触发的提醒——本回合思考写到 4000 字符(约 2000 汉字)后,下一步之前告诉你写了多少,并给「按证据动手,或去读那一个还缺的事实」两个出口(每回合最多两条)。默认关;`DP17` 12 对 0.761×(8/12 更快),`debugfix` 一族 0.582×、思考字符 8249 → 3152;`DP18` 三族 9 对 0.643×(6/9 更快),合池 21 对 0.708× [0.558, 0.898]。
114
+ - **重复无进展时提醒**(`repeatNudge`):同一调用本回合已跑过两次、或连续三条结果内容完全一样时,说一句「已重复 N 次,换方法或给结论」。默认关;单开 1.007×,但在三开组合里不拖后腿。
114
115
 
115
- 六项都写在本会话的条目里,对下一个回合生效;另一个会话开不开、写成什么样,互不影响。没有下拉,没有倍数估算。
116
+ 七项都写在本会话的条目里,对下一个回合生效;另一个会话开不开、写成什么样,互不影响。没有下拉,没有倍数估算。
116
117
 
117
- **几个一起开会不会更快?** 会,但只有实测过的那几个。默认 + `workspaceMap` + `openBatch` 两开:11 对 **0.742×**;再把 `disciplineAfterSteps` 提前到 2(面板里没有这一项,属于 HOLD 的时点实验)三开:22 对 **0.669×**、CI95 [0.554, 0.799]、18/22 更快、隐藏测试全绿。单独开其中任何一个都测不到显著(0.80–0.88,置信区间全跨 1)——所以增益来自它们各自省掉的是同一类成本的不同份,不是哪个开关本身。其余开关(`batchVerify`、`preferPatch`、步数预算、更宽并行)叠上去都是 0.96–1.10,没有理由跟着一起开。细节见 [BENCHMARK.md](BENCHMARK.md) 的「直接跑一次多个开关一起开」。
118
+ **几个一起开会不会更快?** 会,但只有实测过的那几个。默认 + `workspaceMap` + `openBatch` 两开:11 对 **0.742×**;再把 `disciplineAfterSteps` 提前到 2(面板里没有这一项,属于 HOLD 的时点实验)三开:22 对 **0.669×**、CI95 [0.554, 0.799]、18/22 更快、隐藏测试全绿。单独开其中任何一个都测不到显著(置信区间全跨 1)——所以增益来自它们各自省掉的是同一类成本的不同份,不是哪个开关本身。其余开关(`batchVerify`、`preferPatch`、步数预算、更宽并行)叠上去都是 0.96–1.10,没有理由跟着一起开。细节见 [BENCHMARK.md](BENCHMARK.md) 的「直接跑一次多个开关一起开」。
118
119
 
119
- `lazy` 不与上面这些同口径:它动的是答案而不只是时延,所以哪怕测出来更快(`LZ1`:12 对里 11 对更快,逐对几何 0.39–0.43×,判分 216/216 全绿)也仍然默认关闭,不进任何默认组合。纯提示臂(`lazyEffort: false`)的读数同样在 [BENCHMARK.md](BENCHMARK.md) 的 `LZ1` 块。
120
+ `probeFirst` / `actBeforePlan` / `repeatNudge` 三个是 1.7.0 新加的纯提示侧开关,1.7.1 给 `actBeforePlan` 补了一条按思考长度触发的提醒(不新增开关、面板仍是七行)。三开(`trio17`)在两块实测里都是唯一置信区间不跨 1 的臂:`DP17` 12 对 **0.647×** [0.501, 0.893]、10/12 更快;`DP18` 三族 9 对 **0.491×** [0.314, 0.788]、8/9 更快;合池 21 对 **0.575×** [0.444, 0.748]、18/21 更快,判分全绿。单开 `repeatNudge` 等于没变(1.007×,且十二个会话里只触发 3 次——这是「几乎没发生」,不是「无效」),单开 `actBeforePlan` 两块分别 0.761× / 0.643×。它们不改答案、不写请求字段,所以默认仍然关。
120
121
 
121
122
  思路参考了 `dsh-batch-tool-calls` 与 `dsh-ops`(批量调用、有界执行),但它们的规则是静态的,这里的宽度来自会话自身的调用证据;不需要同时装。压缩类插件可以共存。
122
123
 
package/client.js CHANGED
@@ -104,8 +104,12 @@ window.__ModuleLoader__.load({
104
104
  'panel.mapTitle': '开局把目录里的文件交给模型,省掉先翻一遍目录',
105
105
  'panel.batch': '一次读多个文件',
106
106
  'panel.batchTitle': '把互不依赖的读取合成一批发出,少几个来回',
107
- 'panel.lazy': '想太久就少想一点',
108
- 'panel.lazyTitle': '想太久时先按已有信息定下去,安全校验照旧',
107
+ 'panel.probe': '不确定时先查一次',
108
+ 'panel.probeTitle': '拿不准的事实读一次,别用推理猜',
109
+ 'panel.act': '先动手再解释',
110
+ 'panel.actTitle': '最多一行计划,动作清楚就直接做',
111
+ 'panel.repeat': '重复无进展时提醒',
112
+ 'panel.repeatTitle': '重跑同一个调用或连续无新信息时提醒',
109
113
  }
110
114
 
111
115
  const en = {
@@ -131,8 +135,12 @@ window.__ModuleLoader__.load({
131
135
  'panel.mapTitle': 'Hands the directory listing to the model up front, skipping a lookup',
132
136
  'panel.batch': 'Read several files at once',
133
137
  'panel.batchTitle': 'Batches independent reads into one call, fewer round trips',
134
- 'panel.lazy': 'Think less when it stalls',
135
- 'panel.lazyTitle': 'Cuts a turn short when it keeps reasoning instead of deciding; safety checks stay',
138
+ 'panel.probe': 'Read before guessing',
139
+ 'panel.probeTitle': 'Look up the fact you are unsure of once; a guess costs more; safety checks stay',
140
+ 'panel.act': 'Act first',
141
+ 'panel.actTitle': 'At most one line of plan before the first call; act when the next step is clear',
142
+ 'panel.repeat': 'Nudge on repeats',
143
+ 'panel.repeatTitle': 'One line when a call runs again or several steps bring nothing new; safety checks stay',
136
144
  }
137
145
 
138
146
  const CSS = [
@@ -196,8 +204,12 @@ window.__ModuleLoader__.load({
196
204
  inventory: false,
197
205
  /** Batches independent reads into one call for this conversation. */
198
206
  batch: false,
199
- /** The lazy switch: this conversation may be reasoned at a lower effort when it over-thinks. */
200
- lazy: false,
207
+ /**
208
+ * The three deliberation switches: read the missing fact, one line of plan,
209
+ * one evidence line when the turn stops learning. All off unless this
210
+ * conversation turned them on.
211
+ */
212
+ probe: false, act: false, repeat: false,
201
213
  /** This conversation's step cap; 0 means no cap. */
202
214
  stepBudget: 0,
203
215
  /** The right-click panel is open. Store-backed, like every other paint. */
@@ -327,6 +339,9 @@ window.__ModuleLoader__.load({
327
339
  if (Number.isFinite(entry.stepBudget) && entry.stepBudget >= 0) clean.stepBudget = Math.floor(Number(entry.stepBudget))
328
340
  if (typeof entry.map === 'boolean') clean.map = entry.map
329
341
  if (typeof entry.batch === 'boolean') clean.batch = entry.batch
342
+ if (typeof entry.probeFirst === 'boolean') clean.probeFirst = entry.probeFirst
343
+ if (typeof entry.actBeforePlan === 'boolean') clean.actBeforePlan = entry.actBeforePlan
344
+ if (typeof entry.repeatNudge === 'boolean') clean.repeatNudge = entry.repeatNudge
330
345
  if (Number.isFinite(entry.ts)) clean.ts = Number(entry.ts)
331
346
  if (
332
347
  clean.enabled === undefined
@@ -335,6 +350,9 @@ window.__ModuleLoader__.load({
335
350
  && clean.stepBudget === undefined
336
351
  && clean.map === undefined
337
352
  && clean.batch === undefined
353
+ && clean.probeFirst === undefined
354
+ && clean.actBeforePlan === undefined
355
+ && clean.repeatNudge === undefined
338
356
  ) continue
339
357
  out[sessionId] = clean
340
358
  }
@@ -383,7 +401,7 @@ window.__ModuleLoader__.load({
383
401
  *
384
402
  * @param {Record<string, any>} map
385
403
  * @param {string|undefined} sessionId
386
- * @param {{enabled?: boolean, gear?: string, discipline?: boolean|'late', stepBudget?: number, map?: boolean, batch?: boolean}} patch
404
+ * @param {{enabled?: boolean, gear?: string, discipline?: boolean|'late', stepBudget?: number, map?: boolean, batch?: boolean, probeFirst?: boolean, actBeforePlan?: boolean, repeatNudge?: boolean}} patch
387
405
  */
388
406
  function withOwn(map, sessionId, patch) {
389
407
  const next = { ...map }
@@ -397,6 +415,9 @@ window.__ModuleLoader__.load({
397
415
  if (Number.isFinite(patch.stepBudget) && patch.stepBudget >= 0) entry.stepBudget = Math.floor(Number(patch.stepBudget))
398
416
  if (typeof patch.map === 'boolean') entry.map = patch.map
399
417
  if (typeof patch.batch === 'boolean') entry.batch = patch.batch
418
+ if (typeof patch.probeFirst === 'boolean') entry.probeFirst = patch.probeFirst
419
+ if (typeof patch.actBeforePlan === 'boolean') entry.actBeforePlan = patch.actBeforePlan
420
+ if (typeof patch.repeatNudge === 'boolean') entry.repeatNudge = patch.repeatNudge
400
421
  next[sessionId] = entry
401
422
  return next
402
423
  }
@@ -543,9 +564,15 @@ window.__ModuleLoader__.load({
543
564
  const seenBatch = own !== undefined && typeof own.batch === 'boolean'
544
565
  ? own.batch
545
566
  : value.openBatch === true
546
- const seenLazy = own !== undefined && typeof own.lazy === 'boolean'
547
- ? own.lazy
548
- : value.lazy === true
567
+ const seenProbe = own !== undefined && typeof own.probeFirst === 'boolean'
568
+ ? own.probeFirst
569
+ : value.probeFirst === true
570
+ const seenAct = own !== undefined && typeof own.actBeforePlan === 'boolean'
571
+ ? own.actBeforePlan
572
+ : value.actBeforePlan === true
573
+ const seenRepeat = own !== undefined && typeof own.repeatNudge === 'boolean'
574
+ ? own.repeatNudge
575
+ : value.repeatNudge === true
549
576
  store.phase = 'ready'
550
577
  store.error = ''
551
578
  store.ns = view.ns
@@ -556,7 +583,9 @@ window.__ModuleLoader__.load({
556
583
  store.stepBudget = seenBudget
557
584
  store.inventory = seenMap
558
585
  store.batch = seenBatch
559
- store.lazy = seenLazy
586
+ store.probe = seenProbe
587
+ store.act = seenAct
588
+ store.repeat = seenRepeat
560
589
  // A write we have not seen echoed yet wins: the Loader applies the new
561
590
  // section asynchronously, and flipping back to the old paint in that
562
591
  // window would read as a failed click.
@@ -610,7 +639,7 @@ window.__ModuleLoader__.load({
610
639
  *
611
640
  * @param {{settings: {update: (ns: string, patch: object, revision?: number) => Promise<any>}}} remote
612
641
  * @param {{enabled?: boolean, gear?: string, outputDiscipline?: boolean|'late', stepBudget?: number, workspaceMap?: boolean}} [patch]
613
- * @param {{entry?: {enabled?: boolean, gear?: string, discipline?: boolean|'late', stepBudget?: number, map?: boolean, batch?: boolean}}} [options]
642
+ * @param {{entry?: {enabled?: boolean, gear?: string, discipline?: boolean|'late', stepBudget?: number, map?: boolean, batch?: boolean, probeFirst?: boolean, actBeforePlan?: boolean, repeatNudge?: boolean}}} [options]
614
643
  * @returns {Promise<boolean>} whether the write was accepted
615
644
  */
616
645
  async function write(remote, patch, options = {}) {
@@ -634,7 +663,9 @@ window.__ModuleLoader__.load({
634
663
  if (typeof entry.stepBudget === 'number') store.stepBudget = entry.stepBudget
635
664
  if (typeof entry.map === 'boolean') store.inventory = entry.map
636
665
  if (typeof entry.batch === 'boolean') store.batch = entry.batch
637
- if (typeof entry.lazy === 'boolean') store.lazy = entry.lazy
666
+ if (typeof entry.probeFirst === 'boolean') store.probe = entry.probeFirst
667
+ if (typeof entry.actBeforePlan === 'boolean') store.act = entry.actBeforePlan
668
+ if (typeof entry.repeatNudge === 'boolean') store.repeat = entry.repeatNudge
638
669
  }
639
670
  if (typeof shared.enabled === 'boolean') {
640
671
  store.enabled = shared.enabled
@@ -646,7 +677,9 @@ window.__ModuleLoader__.load({
646
677
  if (typeof shared.stepBudget === 'number') store.stepBudget = shared.stepBudget
647
678
  if (typeof shared.workspaceMap === 'boolean') store.inventory = shared.workspaceMap
648
679
  if (typeof shared.openBatch === 'boolean') store.batch = shared.openBatch
649
- if (typeof shared.lazy === 'boolean') store.lazy = shared.lazy
680
+ if (typeof shared.probeFirst === 'boolean') store.probe = shared.probeFirst
681
+ if (typeof shared.actBeforePlan === 'boolean') store.act = shared.actBeforePlan
682
+ if (typeof shared.repeatNudge === 'boolean') store.repeat = shared.repeatNudge
650
683
  emit()
651
684
  try {
652
685
  unwrap(await withTimeout(remote.settings.update(store.ns, outgoing, store.revision), 15000, 'settings/update'))
@@ -663,7 +696,9 @@ window.__ModuleLoader__.load({
663
696
  store.stepBudget = before.stepBudget
664
697
  store.inventory = before.inventory
665
698
  store.batch = before.batch
666
- store.lazy = before.lazy
699
+ store.probe = before.probe
700
+ store.act = before.act
701
+ store.repeat = before.repeat
667
702
  store.pending = undefined
668
703
  store.writeError = describeError(error)
669
704
  emit()
@@ -879,12 +914,23 @@ window.__ModuleLoader__.load({
879
914
  emit()
880
915
  }
881
916
 
882
- /**
883
- * The one switch that changes how much this conversation thinks. It can
884
- * change the answer, not only its timing, so the row names that first.
885
- */
886
- const toggleLazy = () => {
887
- void write(remote, undefined, { entry: { lazy: !store.lazy } })
917
+ /** Read the fact instead of reasoning about it. */
918
+ const toggleProbe = () => {
919
+ void write(remote, undefined, { entry: { probeFirst: !store.probe } })
920
+ store.panelOpen = false
921
+ emit()
922
+ }
923
+
924
+ /** At most one line of plan before the first tool call. */
925
+ const toggleAct = () => {
926
+ void write(remote, undefined, { entry: { actBeforePlan: !store.act } })
927
+ store.panelOpen = false
928
+ emit()
929
+ }
930
+
931
+ /** One evidence line when the turn repeats a call or stops learning. */
932
+ const toggleRepeat = () => {
933
+ void write(remote, undefined, { entry: { repeatNudge: !store.repeat } })
888
934
  store.panelOpen = false
889
935
  emit()
890
936
  }
@@ -923,12 +969,30 @@ window.__ModuleLoader__.load({
923
969
  ),
924
970
  row(
925
971
  'menuitemcheckbox',
926
- t('panel.lazy'),
927
- t(store.lazy ? 'panel.on' : 'panel.off'),
972
+ t('panel.probe'),
973
+ t(store.probe ? 'panel.on' : 'panel.off'),
974
+ canWrite,
975
+ toggleProbe,
976
+ store.probe,
977
+ t('panel.probeTitle'),
978
+ ),
979
+ row(
980
+ 'menuitemcheckbox',
981
+ t('panel.act'),
982
+ t(store.act ? 'panel.on' : 'panel.off'),
983
+ canWrite,
984
+ toggleAct,
985
+ store.act,
986
+ t('panel.actTitle'),
987
+ ),
988
+ row(
989
+ 'menuitemcheckbox',
990
+ t('panel.repeat'),
991
+ t(store.repeat ? 'panel.on' : 'panel.off'),
928
992
  canWrite,
929
- toggleLazy,
930
- store.lazy,
931
- t('panel.lazyTitle'),
993
+ toggleRepeat,
994
+ store.repeat,
995
+ t('panel.repeatTitle'),
932
996
  ),
933
997
  react.createElement('div', { className: 'fm-panel__scope' }, t('panel.scope')),
934
998
  )