dsh-fast-mode 1.6.0 → 1.6.2
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 +41 -0
- package/CHANGELOG.md +12 -0
- package/README.en.md +1 -1
- package/README.md +2 -2
- package/client.js +9 -6
- package/docs/REFERENCE.md +7 -6
- package/index.js +2 -2
- package/package.json +2 -2
- package/src/config.js +8 -1
- package/src/effort.js +1 -0
- package/src/lazy.js +26 -1
package/BENCHMARK.md
CHANGED
|
@@ -2563,3 +2563,44 @@ metrics 242400 steps written, caches trimmed
|
|
|
2563
2563
|
- 能说:**收益主要来自提示文字那一半**。把两臂直接对打:`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
2564
|
- 不能说:这不是「所有题都变快 2.6 倍」。样本是这两族短任务、单机、单路由,`def15` 本块的中位偏慢侧(历史上 `codeimpl` 中位 161 s、`ledger` 649 s,本块 174.9 s 跨两族),逐对设计吸收了基线漂移,但换模型/换路由后倍数会变。也不要说 `lazy` 提高质量:它只做到「没把质量弄坏」——216/216 与 `def15` 持平,不是更好。
|
|
2565
2565
|
- 一块**退役 0.9.x 教训**的验证:降档不跨轮、不跨会话、每步最多一次、每轮最多两次、工具报错后两步内不降、地板 `low`、从不升档,且每个请求都以「用户此刻选的档位」为基准重新计算(`agent/request` 读的是刚解好的 `reasoningEffort`)。所以用户中途换档,下一次请求就是用户的档,不会像 0.9.x 那样把会话钉在低档上。
|
|
2566
|
+
|
|
2567
|
+
**降档确实在真实运行里发生了(机器证据,不是推理):**跑完后翻宿主的计量文件 `~/.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`——开关关掉就是真关掉。
|
|
2568
|
+
|
|
2569
|
+
## 结果:`LZ2`(`lazy` 的梯子文本 v1 / v2 / v3 三版对照,36 次运行)——v2 对 v1 **0.699×**,判分 648/648
|
|
2570
|
+
|
|
2571
|
+
`LZ1` 的结论是「收益来自提示文字那一半,降 `reasoningEffort` 那一半没测出额外收益」,所以下一个杠杆就是**文字本身**。这一块把 `lazy` 的梯子文本做成三个 bench 变体(`lazyVariant: v1|v2|v3`,隐藏字段,不进面板),三臂都开 `lazy: true` 且都关降档(`lazyEffort: false`),只看文字:
|
|
2572
|
+
|
|
2573
|
+
- `v1`:1.6.0/1.6.1 已发布的那份(762 字符,五条梯子 + 四类「绝不省略」)。
|
|
2574
|
+
- `v2`:`v1` 加两条——「不要重述任务或先解释计划」「下一步已经清楚就直接做,不要再权衡一遍不打算采用的方案」(973 字符)。
|
|
2575
|
+
- `v3`:同样规则的精简重写(629 字符),测「更短的提示能不能保住收益」。
|
|
2576
|
+
|
|
2577
|
+
`codeimpl` 6 对 + `ledger` 6 对,ABBA 交替,36 次运行,全部 `green`,判分 **648/648**(三臂同题同判,`bench/results/suite-LZ2.jsonl`)。
|
|
2578
|
+
|
|
2579
|
+
| 臂 | 墙钟合计 | 墙钟中位 | 步数合计 | 推理字符合计 | 输出 token 中位 |
|
|
2580
|
+
|---|---|---|---|---|---|
|
|
2581
|
+
| `lz2v1` | 1073.0 s | 80 s | 68 | 416 873 | 10 936 |
|
|
2582
|
+
| `lz2v2` | **727.8 s** | **41 s** | 63 | **268 806** | **6 802** |
|
|
2583
|
+
| `lz2v3` | 1132.5 s | 71 s | 68 | 401 247 | 8 884 |
|
|
2584
|
+
|
|
2585
|
+
逐对(各 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×**——省的是每一步里想的时间,不是步数。
|
|
2586
|
+
|
|
2587
|
+
**但这一块不能单独下结论。** 12 对里有两处大幅反向(`codeimpl` 第 2 对 2.15×、第 5 对 2.03×,`ledger` 第 2 对 4.41×),符号检验 9/12 并不显著,所以按预先约定再跑一块**换族**的确认块。
|
|
2588
|
+
|
|
2589
|
+
## 结果:`LZ3`(确认块:`debugfix` + `migrate` + `stategraph`,30 次运行)——几何 0.822×,**中位 1.014×**,短任务全慢
|
|
2590
|
+
|
|
2591
|
+
同一对臂(`lz2v1` 对 `lz2v2`),三族各 5 对,15 对 30 次运行,判分 **370/370** 全绿(`bench/results/suite-LZ3.jsonl`)。
|
|
2592
|
+
|
|
2593
|
+
| 臂 | 墙钟合计 | 墙钟中位 | 步数合计 | 推理字符合计 | 输出 token 中位 |
|
|
2594
|
+
|---|---|---|---|---|---|
|
|
2595
|
+
| `lz2v1` | 1459.5 s | 85 s | 108 | 429 822 | 10 011 |
|
|
2596
|
+
| `lz2v2` | 1070.8 s | 53 s | 88 | **310 316** | **6 737** |
|
|
2597
|
+
|
|
2598
|
+
逐对几何 **0.822×**、中位 **1.014×**、7/15 更快。分族看才是这块的重点:
|
|
2599
|
+
|
|
2600
|
+
- `debugfix`(小任务,5 对):**5/5 全慢**,1.08× / 1.21× / 1.22× / 1.22× / 1.23×。多出来的两句话在这种 2–3 步的任务上是纯成本。
|
|
2601
|
+
- `migrate`(5 对):0.46× / 0.60× / 0.53× 快,1.44× / 1.56× 慢,几何约 0.94×。
|
|
2602
|
+
- `stategraph`(长任务,5 对):0.32× / 0.51× / 0.81× / 0.49× / 1.01×,几何约 0.58×——长的、工具多的任务上收益最大(单次最长的三对合计从 690 s 降到 305 s)。
|
|
2603
|
+
|
|
2604
|
+
**决定:不换默认文本,`v1` 保持发布态。** 两轮合起来(27 对)几何约 0.765×,但分布是「长任务大赚、短任务倒亏」,而且 `LZ3` 的中位没有改善(1.014×)。把它设成默认等于拿所有短任务去换长任务,这一版不做这个交易。`lazyVariant` 保留在 schema 里(默认 `v1`、不进面板)仅仅是为了这些实验能被重跑;`v3` 的结论是「更短并不更好」(0.997×),也一并留档。
|
|
2605
|
+
|
|
2606
|
+
**质量口径:** 两块的判分与对照臂逐题完全一致(648/648、370/370),没有出现「更快但错得更多」的题;这两块不能用来宣称质量提升,只能说明换文本没把质量弄坏。
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.2(修面板:`lazy` 那张卡以前点不开;标签改成能看懂的说法;lazy 的梯子文本做了三版对照,不换默认)
|
|
4
|
+
|
|
5
|
+
- **「过度思考时降档」以前点不开,是真的坏了。** `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`;非布尔值仍被丢弃)。**旧版本里已经点过的人不会留下坏数据**,因为那次点击压根没写进去。
|
|
6
|
+
- **标签改名,说明手段而不是口号。** 面板第六行从「想太久就少想一点」改成 **「过度思考时降档」**(英文 `Cap over-thinking`),tooltip 写清条件与保证:「一步里推理超量时,下一个请求降一档;安全校验照旧」。旧说法是建议、听起来像文风;新说法说的是这个开关实际做的事,也交代了降的是什么。
|
|
7
|
+
- **`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` 保留为复现实验用的隐藏字段,面板上看不到。
|
|
8
|
+
- **没有动的东西:** 默认值一个没改(`enabled: false`、`lazy: false` 仍是出厂态)、`lazyEffort` 仍默认 `true`、`HOT_CHARS`/`NUDGE_CHARS`/降档上限全部照旧、`d2`/`d2g`/`dr8` 仍然 HOLD、没有新增任何提醒时机或预算旋钮。
|
|
9
|
+
- 测试 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`。
|
|
10
|
+
|
|
11
|
+
## 1.6.1(1.6.0 的补包:npm 页面上那句「从不改 reasoningEffort」在 1.6.0 之后不准确了)
|
|
12
|
+
|
|
13
|
+
- 1.6.0 加了 `lazy`(默认关闭,唯一会改 `reasoningEffort` 的路径),但 `package.json` 的 `description` 还是旧话「Never changes reasoning effort, the model, or the request body」,npm 首页会照抄这句。这一版只改这一句:**默认**不改推理强度、模型与请求体,例外是默认关闭的 `lazy`(想太久时降一档)。行为、默认值、测试零改动(336 全绿)。
|
|
14
|
+
|
|
3
15
|
## 1.6.0(一个新开关 `lazy`「想太久就少想一点」:动态收思考强度 + 压过度思考,默认关闭;12 对 36 次运行实测 0.39–0.43×,判分 216/216 全绿)
|
|
4
16
|
|
|
5
17
|
按这一轮的要求,只开**一个**新开关,不动任何默认值。它的理念照抄 [ponytail](https://github.com/dietrichgebert/ponytail) 的核心:先读懂再动手,然后从「最小能work的那一级」开始做,不为想象中的未来写代码;但**读完之前不许偷懒**,也不许省掉信任边界校验、安全、数据丢失处理、可访问性。`lazy` 就是把这条用在「思考」上:不是让它不读、不想,而是想够了就停。
|
package/README.en.md
CHANGED
|
@@ -109,7 +109,7 @@ The bolt is the switch: one click writes on/off, per conversation, restored on r
|
|
|
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
|
-
- **
|
|
112
|
+
- **Cap over-thinking** (`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.
|
|
113
113
|
|
|
114
114
|
All six rows take effect from the next request, per conversation. No dropdown, no multiplier estimate.
|
|
115
115
|
|
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 下降,不是删旁白。唯一的例外是默认关闭的 `lazy`:过度思考时收一档,开关在你手里。
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
- **最多几步**:`15` / `30` / 不限,默认不限。到上限只先汇报,不截断任务。
|
|
111
111
|
- **先列文件清单**:开启后,回合的第一次工具调用之前先收到一份工作目录清单(相对路径 + 字节数 + 可见的测试入口),省掉开头的 `ls`/`find` 探路。默认关;23 个配对实测几何 0.927×,尾部变慢、步数 +10%,没有达到进默认的门槛,只作为可试的开关。
|
|
112
112
|
- **一次读多个文件**:把互不依赖的读取合成一批发出,少几个来回。默认关;单独开测不到显著,和上一项一起开才有(见下)。
|
|
113
|
-
-
|
|
113
|
+
- **过度思考时降档**(`lazy`):一轮里思考文字明显超量时,先给一次"先找最省的一级、不扩范围、不砍校验"的提示,再把下一个请求的思考强度降一档。默认关;它动的是答案而不只是时延。
|
|
114
114
|
|
|
115
115
|
六项都写在本会话的条目里,对下一个回合生效;另一个会话开不开、写成什么样,互不影响。没有下拉,没有倍数估算。
|
|
116
116
|
|
package/client.js
CHANGED
|
@@ -104,8 +104,8 @@ window.__ModuleLoader__.load({
|
|
|
104
104
|
'panel.mapTitle': '开局把目录里的文件交给模型,省掉先翻一遍目录',
|
|
105
105
|
'panel.batch': '一次读多个文件',
|
|
106
106
|
'panel.batchTitle': '把互不依赖的读取合成一批发出,少几个来回',
|
|
107
|
-
'panel.lazy': '
|
|
108
|
-
'panel.lazyTitle': '
|
|
107
|
+
'panel.lazy': '过度思考时降档',
|
|
108
|
+
'panel.lazyTitle': '一步里推理超量时,下一个请求降一档;安全校验照旧',
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
const en = {
|
|
@@ -131,8 +131,8 @@ window.__ModuleLoader__.load({
|
|
|
131
131
|
'panel.mapTitle': 'Hands the directory listing to the model up front, skipping a lookup',
|
|
132
132
|
'panel.batch': 'Read several files at once',
|
|
133
133
|
'panel.batchTitle': 'Batches independent reads into one call, fewer round trips',
|
|
134
|
-
'panel.lazy': '
|
|
135
|
-
'panel.lazyTitle': '
|
|
134
|
+
'panel.lazy': 'Cap over-thinking',
|
|
135
|
+
'panel.lazyTitle': 'One rung lower on the next request when a step over-reasons; safety checks stay',
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
const CSS = [
|
|
@@ -327,6 +327,7 @@ window.__ModuleLoader__.load({
|
|
|
327
327
|
if (Number.isFinite(entry.stepBudget) && entry.stepBudget >= 0) clean.stepBudget = Math.floor(Number(entry.stepBudget))
|
|
328
328
|
if (typeof entry.map === 'boolean') clean.map = entry.map
|
|
329
329
|
if (typeof entry.batch === 'boolean') clean.batch = entry.batch
|
|
330
|
+
if (typeof entry.lazy === 'boolean') clean.lazy = entry.lazy
|
|
330
331
|
if (Number.isFinite(entry.ts)) clean.ts = Number(entry.ts)
|
|
331
332
|
if (
|
|
332
333
|
clean.enabled === undefined
|
|
@@ -335,6 +336,7 @@ window.__ModuleLoader__.load({
|
|
|
335
336
|
&& clean.stepBudget === undefined
|
|
336
337
|
&& clean.map === undefined
|
|
337
338
|
&& clean.batch === undefined
|
|
339
|
+
&& clean.lazy === undefined
|
|
338
340
|
) continue
|
|
339
341
|
out[sessionId] = clean
|
|
340
342
|
}
|
|
@@ -383,7 +385,7 @@ window.__ModuleLoader__.load({
|
|
|
383
385
|
*
|
|
384
386
|
* @param {Record<string, any>} map
|
|
385
387
|
* @param {string|undefined} sessionId
|
|
386
|
-
* @param {{enabled?: boolean, gear?: string, discipline?: boolean|'late', stepBudget?: number, map?: boolean, batch?: boolean}} patch
|
|
388
|
+
* @param {{enabled?: boolean, gear?: string, discipline?: boolean|'late', stepBudget?: number, map?: boolean, batch?: boolean, lazy?: boolean}} patch
|
|
387
389
|
*/
|
|
388
390
|
function withOwn(map, sessionId, patch) {
|
|
389
391
|
const next = { ...map }
|
|
@@ -397,6 +399,7 @@ window.__ModuleLoader__.load({
|
|
|
397
399
|
if (Number.isFinite(patch.stepBudget) && patch.stepBudget >= 0) entry.stepBudget = Math.floor(Number(patch.stepBudget))
|
|
398
400
|
if (typeof patch.map === 'boolean') entry.map = patch.map
|
|
399
401
|
if (typeof patch.batch === 'boolean') entry.batch = patch.batch
|
|
402
|
+
if (typeof patch.lazy === 'boolean') entry.lazy = patch.lazy
|
|
400
403
|
next[sessionId] = entry
|
|
401
404
|
return next
|
|
402
405
|
}
|
|
@@ -610,7 +613,7 @@ window.__ModuleLoader__.load({
|
|
|
610
613
|
*
|
|
611
614
|
* @param {{settings: {update: (ns: string, patch: object, revision?: number) => Promise<any>}}} remote
|
|
612
615
|
* @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]
|
|
616
|
+
* @param {{entry?: {enabled?: boolean, gear?: string, discipline?: boolean|'late', stepBudget?: number, map?: boolean, batch?: boolean, lazy?: boolean}}} [options]
|
|
614
617
|
* @returns {Promise<boolean>} whether the write was accepted
|
|
615
618
|
*/
|
|
616
619
|
async function write(remote, patch, options = {}) {
|
package/docs/REFERENCE.md
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
**默认契约:Fast 模式不改变思考强度、thinking 预算或模型。** `agent/request` 钩子默认只做一件事——`await next()`,读一下请求携带的 `reasoningEffort` 记进测量,然后原样返回。用户选的是哪一档(会话里的 UI 选择,或者 profile 里 agent-default-model 固定下来的那一档),每个请求就用哪一档。
|
|
6
6
|
|
|
7
|
-
**唯一的例外是 1.6.0 起逐会话可点的 `lazy`,默认关闭。** 它只在「上一请求流出的思考字符明显超量」时把**下一个请求**降一档(最低 `low`,从不升档,每轮最多两次,工具报错后两步内不降),而且每个请求都从用户当次的选择重新读起,所以下一次请求就恢复;它不改模型、不写 thinking 预算、不加任何 provider 字段,`lazyEffort: false` 时一个字段都不写。见下面"六、`lazy
|
|
7
|
+
**唯一的例外是 1.6.0 起逐会话可点的 `lazy`,默认关闭。** 它只在「上一请求流出的思考字符明显超量」时把**下一个请求**降一档(最低 `low`,从不升档,每轮最多两次,工具报错后两步内不降),而且每个请求都从用户当次的选择重新读起,所以下一次请求就恢复;它不改模型、不写 thinking 预算、不加任何 provider 字段,`lazyEffort: false` 时一个字段都不写。见下面"六、`lazy`:过度思考时降档"。
|
|
8
8
|
|
|
9
9
|
快的部分只来自三件事:更少的往返、更少的冗余输出、更少的卡顿等待。下面逐条说明。
|
|
10
10
|
|
|
11
11
|
## 交互细节:闪电开关与会话归属
|
|
12
12
|
|
|
13
13
|
- **一个图标就是开关。** 会话输入区的活动槽(`conversation.input.activity`)里只有一个闪电按钮:关闭时空心,开启时实心。点一下切换,再点一下切回;这一下只写当前会话。没有档位选择器,没有箭头,没有"选择 gear"的二级菜单。
|
|
14
|
-
- **右键是面板。** 右键闪电打开六行,六行都只影响当前会话:"更快"(`enabled`)、"少写过程"(`outputDiscipline`)、"最多几步"(`stepBudget`,循环 0/15/30)、"先列文件清单"(`workspaceMap`)、"一次读多个文件"(`openBatch`)、"
|
|
14
|
+
- **右键是面板。** 右键闪电打开六行,六行都只影响当前会话:"更快"(`enabled`)、"少写过程"(`outputDiscipline`)、"最多几步"(`stepBudget`,循环 0/15/30)、"先列文件清单"(`workspaceMap`)、"一次读多个文件"(`openBatch`)、"过度思考时降档"(`lazy`)。六行都把选择写进 `sessions` 列里本会话的条目,不碰共享默认值,下一个回合生效;只读或写入中时整块禁用,写失败回滚。没有条目的会话沿用配置里的默认值(`enabled`/`gear`/`outputDiscipline`/`stepBudget`/`workspaceMap`/`openBatch`/`lazy`)。面板不暴露 `disciplineAfterSteps`、`disciplineEarlyCalls`、`shapeGate`、`finishLineSteps`、`shellParallel`、`disciplineRepeatAtSteps` 这些未证实或已判 HOLD 的旋钮。
|
|
15
15
|
- **会话之间互不影响,这是硬约束。** 每个会话的 `enabled`、`gear`、`outputDiscipline`、`stepBudget`、`workspaceMap`、`lazy` 都从 `sessions` 列里自己那一条解析:A 会话 `更快 + 少写过程`、B 会话 `更快 + 不写过程` 同时成立,C 会话没点过就沿用配置默认值。面板和闪电只写本会话的条目,从不写共享默认值;条目最多保留 200 个,按 `ts` 淘汰最旧的,被淘汰的会话回到默认值。
|
|
16
16
|
- **按钮只表达一个问题:这个会话开不开。** `aria-pressed` 就是状态,tooltip 写清"仅本会话 · 点击关闭 · 右键设置"。为什么快、快在哪里、单次调用上限这类说明放在插件介绍(`meta.description` 与配置项描述)里,不再占用输入区。
|
|
17
17
|
- **实时行引用的是被执行的数字**,而不是产品形容词:
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
|
|
139
139
|
上限是 200 个会话,超出时淘汰 `ts` 最旧的条目——这是内存与配置文件体积的边界,不是权限模型。host 端的实现(`src/state.js`)和浏览器里的副本(`client.js`)是两份代码,测试用同一组 fixture 锁住它们的读写一致。
|
|
140
140
|
|
|
141
|
-
### 六、`lazy
|
|
141
|
+
### 六、`lazy`:过度思考时降档(1.6.0,默认关闭)
|
|
142
142
|
|
|
143
143
|
Ponytail 那句"最好的代码是没写的代码"在这里只借一半:**先读懂,再省**。这一节是插件里唯一会改动 `reasoningEffort` 的路径,所以它默认关闭、逐会话可点、并且每个请求都从用户当次的选择重新读起。
|
|
144
144
|
|
|
@@ -147,7 +147,7 @@ Ponytail 那句"最好的代码是没写的代码"在这里只借一半:**先
|
|
|
147
147
|
- **提示侧(`lazy: true` 时始终生效)。** 每轮第一步发一条 notice:先找最省的一级(能不能不做 → 有没有现成 helper → 标准库/平台 → 已装的依赖 → 一行 → 最小实现),确认必须写才写;不许扩范围;不许砍信任边界校验、数据丢失处理、安全与可访问性;结尾用一行说清跳过了什么、风险在哪。文本在 `src/lazy.js` 的 `DIRECTIVE`,`lazyEffort: false` 也照样发。
|
|
148
148
|
- **effort 侧(`lazy: true` 且 `lazyEffort !== false`)。** 账本记两件事:上一步流出的思考字符(`agent/assistant-stream` 的 `reasoning-delta`)与本轮累计。上一步 ≥ 1600 字符,或本轮累计越过 4000 / 8000 字符,就把**下一个请求**降一档(`src/lazy.js` 的 `EFFORT_RUNGS` 顺序,最低到 `low`,从不升档);每轮最多降两次、每步最多降一次(重试不会连降),工具结果报错后两步内不降——报错通常说明该想得更多,不是更少。
|
|
149
149
|
|
|
150
|
-
为什么不会像 0.9.x 那样把会话"卡"在低档:`agent/request` 每次都从 `resolved.reasoningEffort`(用户当下的选择)重新读基准,降档只作用于它自己那一次返回值,所以用户切回 High 之后的第一个请求就是 High;面板里关掉 `lazy` 同样立刻恢复。测量口径上,`lazyEffort: false` 是"只发文字、一个字段都不写"的对照臂,用来分辨收益来自提示词还是 provider 真的更低档;`agent/assistant-stream` 记的 `reasoningChars` 是第二条独立通道(不依赖 provider 报 `reasoning_tokens`)。两块数字见 `BENCHMARK.md` 的 `LZ1` 块(12 对、36 次运行):「只发文字」臂逐对几何墙钟 **0.389×**(95 % CI [0.252, 0.600],11/12 更快,p = 0.0032),「文字 + 降档」臂 **0.430×**(CI [0.273, 0.677],11/12),推理字符 0.372× / 0.418×,两臂判分 **216/216 全绿**。两臂直接对打 `laz16 / lazt16` = 1.106×(CI 跨 1),所以诚实的结论是:**收益主要来自提示文字那一半;降 `reasoningEffort` 这一半在这条路由上没有测出额外收益,也没测出代价。** 样本是两个短任务族、单机、单路由,别把它读成「所有题都快 2.5 倍」。
|
|
150
|
+
为什么不会像 0.9.x 那样把会话"卡"在低档:`agent/request` 每次都从 `resolved.reasoningEffort`(用户当下的选择)重新读基准,降档只作用于它自己那一次返回值,所以用户切回 High 之后的第一个请求就是 High;面板里关掉 `lazy` 同样立刻恢复。测量口径上,`lazyEffort: false` 是"只发文字、一个字段都不写"的对照臂,用来分辨收益来自提示词还是 provider 真的更低档;`agent/assistant-stream` 记的 `reasoningChars` 是第二条独立通道(不依赖 provider 报 `reasoning_tokens`)。两块数字见 `BENCHMARK.md` 的 `LZ1` 块(12 对、36 次运行):「只发文字」臂逐对几何墙钟 **0.389×**(95 % CI [0.252, 0.600],11/12 更快,p = 0.0032),「文字 + 降档」臂 **0.430×**(CI [0.273, 0.677],11/12),推理字符 0.372× / 0.418×,两臂判分 **216/216 全绿**。两臂直接对打 `laz16 / lazt16` = 1.106×(CI 跨 1),所以诚实的结论是:**收益主要来自提示文字那一半;降 `reasoningEffort` 这一半在这条路由上没有测出额外收益,也没测出代价。** 样本是两个短任务族、单机、单路由,别把它读成「所有题都快 2.5 倍」。 提示文本后来又做了两轮对照(`LZ2` 12 对、`LZ3` 15 对,都是 `v1`/`v2`/`v3` 之间的比较):`v2` 在长任务上能到 0.699×,但短任务上倒亏(`debugfix` 五对全慢),合并中位 1.014×,所以**默认文本仍是 `v1`**,`lazyVariant` 只作为复现实验的隐藏字段保留。
|
|
151
151
|
|
|
152
152
|
不改的东西:模型、`thinking` 预算、`output_config`、provider 专用字段、以及档位词汇本身(路由声明的档位名不认识时原样放过,不猜)。
|
|
153
153
|
|
|
@@ -181,8 +181,9 @@ Ponytail 那句"最好的代码是没写的代码"在这里只借一半:**先
|
|
|
181
181
|
| `sessions` | `''` | 插件自动维护的会话级覆盖,见上一节。**不要手改。** |
|
|
182
182
|
| `outputDiscipline` | `'late'` | 输出纪律。`late`(默认)前 4 步不写任何输出约束,第 4 步起由调度器提醒一次;`true` 全程生效;`false` 关闭输出纪律,仍注入并行与有界调用规则。 |
|
|
183
183
|
| `openBatch` | `false` | 开局批量:每轮第一步发一次「把已知要做的独立调用放进同一块、shell 命令用 `&&` 串起来」。短任务的钱几乎全是每步固定成本(首字延迟 ~0.5 s/次),少一次往返就省一次;只影响第一轮第一步,长任务可忽略。旧批次里它不优于默认,但那些数字已因测量缺陷撤回(运行目录在仓库内、指标按字节偏移切片),尚无干净重测。保持在 `false`,但 1.5.33 起进面板(「一次读多个文件」),逐会话可开;和 `workspaceMap` 一起开 11 对 0.742×,再加 `disciplineAfterSteps: 2` 三开 22 对 0.669×〔CI 0.554–0.799〕,单独开仍测不到显著。 |
|
|
184
|
-
| `lazy` | `false` |
|
|
184
|
+
| `lazy` | `false` | 过度思考时降档:每轮第一步给一次"先找最省的一级、不扩范围、不砍校验"的提示,并在思考字符明显超量时把下一个请求降一档思考强度(最低 `low`,从不升档,每轮最多两次,报错后两步内不降)。默认关闭:它是唯一会改变答案而不只是时延的开关。1.6.0 起进面板("过度思考时降档"),逐会话可点。 |
|
|
185
185
|
| `lazyEffort` | `true` | `lazy` 的 effort 半边。设为 `false` 时只发提示文字,一个请求字段都不写——这是分辨"提示词有效"与"provider 真降档"的对照臂。 |
|
|
186
|
+
| `lazyVariant` | `'v1'` | **实验字段,面板不暴露。** 选 `lazy` 提示文字的三个版本:`v1` 是 1.6.0/1.6.1 发布的那份(762 字符);`v2` 在同一份后面加「不要重述任务或先解释计划」「下一步已经清楚就直接做」(973 字符);`v3` 是同样规则的精简重写(629 字符)。`LZ2`(12 对)里 `v2` 对 `v1` 是 0.699×,`LZ3`(15 对)换族确认只有 0.822×、中位 1.014×、`debugfix` 五对全慢,所以**默认仍是 `v1`**;`v3` 是 0.997×。留着它只为让那两块实验能被重跑,见 `BENCHMARK.md`。 |
|
|
186
187
|
| `shapeGate` | `false` | 形状闸门:本轮累计调用 ≥ 4 且其中写入类(不在只读名单里的工具)占比 ≥ 60 % 时,**整轮不再注入任何提醒**,行为退回原生。判断只用已有的工具名,不发请求、不加请求体字段。它给的是「不适合就别管我」的退路:写密集型的任务没有可并行的调用,每条提醒都只是一次它用不上的往返。显式设了 `stepBudget` 时该收尾线优先于闸门——「别加速」不等于「无视我叫停」。默认关,因为它会同时关掉本可以救回长尾的收尾提醒。 |
|
|
187
188
|
| `preferPatch` | `false` | 整文件写入之后提醒一次「改已有文件时只发改动的行(edit hunk),不要重发整个文件」。本步 `write` 的正文 ≥ 2000 字符才武装,之后一步发一次,每轮最多一次;文本明确要求结果与原文件逐字节相同,不限制写什么、也不影响何时结束。默认关闭:它只作用于模型的输出形式(工具调用参数),正确性由文件内容决定、与发送形式无关;实测是否真省墙钟见 `BENCHMARK.md` 的 `pp16` 臂。 |
|
|
188
189
|
| `compactRereadHint` | false | 压缩提醒里多一句「本回合写过的文件不要为确认整份重读」;不新增消息。实验旋钮,默认关 |
|
|
@@ -397,6 +398,6 @@ dsh-fast-mode-experiment stop # 结束实验
|
|
|
397
398
|
node --test test/*.test.js
|
|
398
399
|
```
|
|
399
400
|
|
|
400
|
-
当前
|
|
401
|
+
当前 338 个测试在 Node 24.16.0 上全部通过(338/338,0 失败)。测试覆盖:调度器宽度与指令、预算优先级、提示段的开关条件、`sessions` 列在 host 与浏览器两份实现间的一致性、notice 来源校验、计量字段、分配哈希、报告与实验 CLI 的参数解析,`lazy` 的账本与降档边界(15 条单元测试)、以及一条扫描源码、确认 `reasoningEffort` 只有那一处受控赋值的回归测试。
|
|
401
402
|
|
|
402
403
|
文档入口:`README.md`(中文)、`README.en.md`(English)、`BENCHMARK.md`(测量方法与历史数据)、`CHANGELOG.md`(每个版本删了什么、加了什么)。
|
package/index.js
CHANGED
|
@@ -99,7 +99,7 @@ const TAG = '[fast-mode]'
|
|
|
99
99
|
* Shipped identity of this build. Traced into the debug log so a stale install is
|
|
100
100
|
* visible at a glance, and pinned by a test against `package.json`.
|
|
101
101
|
*/
|
|
102
|
-
export const VERSION = '1.6.
|
|
102
|
+
export const VERSION = '1.6.2'
|
|
103
103
|
const GLOBAL_AGENT = '__global__'
|
|
104
104
|
|
|
105
105
|
/**
|
|
@@ -632,7 +632,7 @@ export function apply(ctx, rawConfig) {
|
|
|
632
632
|
lazyTurnKey.set(sid, key)
|
|
633
633
|
trim(lazyTurnKey, 200)
|
|
634
634
|
}
|
|
635
|
-
const plan = lazyBeginStep(lazyWatch, sid, { firstStep: fresh })
|
|
635
|
+
const plan = lazyBeginStep(lazyWatch, sid, { firstStep: fresh, variant: cfg.lazyVariant })
|
|
636
636
|
if (plan && plan.directive) notices.push(createNoticeMessage(plan.directive, LAZY_SUMMARY))
|
|
637
637
|
if (plan && plan.nudge) notices.push(createNoticeMessage(plan.nudge, LAZY_NUDGE_SUMMARY))
|
|
638
638
|
if (plan && initial.debug) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fast-mode",
|
|
3
|
-
"version": "1.6.
|
|
4
|
-
"description": "DeepSeek Harness fast mode: same thinking effort
|
|
3
|
+
"version": "1.6.2",
|
|
4
|
+
"description": "DeepSeek Harness fast mode: fewer round trips at the same thinking effort. Adaptive parallel tool fan-out, output discipline that never trims the answer, a cancellable stall guard, and one off-by-default switch (lazy) that steps a stalled turn one rung down. By default it never changes reasoning effort, the model, or the request body. Provider-agnostic, one dependency (@deepseek-ai/schemastery).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/regrevia/dsh-fast-mode.git"
|
package/src/config.js
CHANGED
|
@@ -166,7 +166,14 @@ export const Config = z.object({
|
|
|
166
166
|
.default(false)
|
|
167
167
|
.volatile()
|
|
168
168
|
.description(
|
|
169
|
-
'
|
|
169
|
+
'过度思考时降档 / Cap over-thinking(面板「过度思考时降档」): 每轮第一步给一次「先找最省的一级、不扩范围、不砍信任边界校验/数据丢失处理/安全/可访问性」的提示;当上一步流出的思考字符 ≥ 1600、或本轮累计超过 4000/8000 时,把下一个请求的思考强度降一档(最低 low、从不升档、每轮最多两次、工具报错后两步内不降)。每个请求都从用户当下的选择重新读基准,所以下一次请求就恢复。默认关闭:它是唯一会改变答案而不只是时延的开关。',
|
|
170
|
+
),
|
|
171
|
+
|
|
172
|
+
lazyVariant: z
|
|
173
|
+
.union(['v1', 'v2', 'v3'].map(name => z.const(name)))
|
|
174
|
+
.default('v1')
|
|
175
|
+
.description(
|
|
176
|
+
'实验开关,不进面板:lazy 的梯子文本用哪一版。v1 是 1.6.x 已发布的文本,v2 在它后面加「不重述任务、行动已经清楚就直接做」,v3 是同样规则的精简重写。只用于同一块里对照三版文本,默认 v1;选它不会改变回答以外的任何东西,普通用户不需要动。',
|
|
170
177
|
),
|
|
171
178
|
|
|
172
179
|
lazyEffort: z
|
package/src/effort.js
CHANGED
|
@@ -285,6 +285,7 @@ export function resolveConfig(raw, overrides) {
|
|
|
285
285
|
*/
|
|
286
286
|
lazy: gear.directive && config.lazy === true,
|
|
287
287
|
lazyEffort: gear.directive && config.lazy === true && config.lazyEffort !== false,
|
|
288
|
+
lazyVariant: String(config.lazyVariant || 'v1'),
|
|
288
289
|
/**
|
|
289
290
|
* The stall guard, the one *hard* bound this plugin owns: it cancels a single
|
|
290
291
|
* tool call at its deadline. On by default; `false` or a negative
|
package/src/lazy.js
CHANGED
|
@@ -47,6 +47,31 @@ export const DIRECTIVE = `Work like a lazy senior developer: the best step is th
|
|
|
47
47
|
- Never cut these to save code or time: validation where input crosses a trust boundary, error handling that protects data, security, accessibility, and the deliverable the user asked for.
|
|
48
48
|
- End the turn with one line: what you skipped, and the risk that carries.`
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Bench variants of the ladder text, selected by `lazyVariant`. The measured
|
|
52
|
+
* block compares them in one run, so the control stays the released text.
|
|
53
|
+
*/
|
|
54
|
+
export const DIRECTIVES = Object.freeze({
|
|
55
|
+
v1: DIRECTIVE,
|
|
56
|
+
v2: `Work like a lazy senior developer: the best step is the one not taken.
|
|
57
|
+
|
|
58
|
+
- Read before deciding. Trace the real flow of the code the change touches, then decide. Being lazy about the solution never means being lazy about the reading.
|
|
59
|
+
- Once a step fully does the job, stop planning it. No option, abstraction, or "for later" code nobody asked for.
|
|
60
|
+
- Take the first rung that works: what this repo already has, then the platform or a tool you already hold, then one line, then the minimum that works.
|
|
61
|
+
- Never cut these to save code or time: validation where input crosses a trust boundary, error handling that protects data, security, accessibility, and the deliverable the user asked for.
|
|
62
|
+
- End the turn with one line: what you skipped, and the risk that carries.
|
|
63
|
+
- Do not restate the task or re-explain the plan before acting: the reader already has it.
|
|
64
|
+
- If the next action is already clear, take it now. Do not spend a step weighing alternatives you are not going to use.`,
|
|
65
|
+
v3: `Work like a lazy senior developer: the best step is the one not taken.
|
|
66
|
+
|
|
67
|
+
- Read the flow the change touches before you decide; never be lazy about the reading.
|
|
68
|
+
- Take the first rung that works: what the repo already has, the platform, a tool you hold, one line, then the minimum.
|
|
69
|
+
- Once the step fully does the job, stop planning it: no option, abstraction, or code nobody asked for.
|
|
70
|
+
- Never cut these: validation at a trust boundary, error handling that protects data, security, accessibility, and the deliverable.
|
|
71
|
+
- Do not restate the task. If the next action is clear, take it. End with one line: what you skipped and the risk.`,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
|
|
50
75
|
const DIRECTIVE_SUMMARY = 'lazy mode: first rung that works, no scope beyond the task'
|
|
51
76
|
|
|
52
77
|
/** Reasoning rungs a route may name, weakest first. */
|
|
@@ -156,7 +181,7 @@ export function beginStep(watch, sid, where) {
|
|
|
156
181
|
const fresh = newTurn()
|
|
157
182
|
fresh.step = step
|
|
158
183
|
watch.runs.set(sid, fresh)
|
|
159
|
-
return { directive: DIRECTIVE }
|
|
184
|
+
return { directive: DIRECTIVES[where?.variant] || DIRECTIVE }
|
|
160
185
|
}
|
|
161
186
|
// The previous step's streamed volume is the evidence the next request acts on.
|
|
162
187
|
run.hot = run.stepChars >= HOT_CHARS
|