# 参考手册：原理、配置与边界

本手册描述 **dsh-fast-mode 1.7.28** 的实际行为（默认配置下的注入行为与 1.5.0 一致；1.5.14 起多了右键面板，1.7.0 起为七行、1.7.12 起为八行；1.7.26 起收回三行未验证的（现为五行，只数设置项；闪电本身就是开关，不占行），这些都只影响本会话；1.7.1 给其中一行加了一条按思考长度的提醒；1.7.5 在标题行右侧加了三档并行宽度，行数仍为七行；1.7.6 让省略标记同时报出字符数与缺失的行区间；1.7.7 把闪电挪到模型选择器旁的 list 槽并修正页脚计数的读取通道，1.7.12 把「截短工具输出」做成第八个面板开关，1.7.26 把「不确定先查证」「先执行后解释」「重复时换方法」从面板收回、只留配置键；1.7.27 在保守档下把被 gate 掉的四行置灰；1.7.28 把三档改成无状态预设：点一下载入该档的开关值，高亮由当前开关值反推），对应仓库里的 `index.js`、`src/` 与 `client.js`。它不是宣传页：凡是能测量的地方给出数字，凡是没测量的地方直接说没测量。

**默认契约：Fast 模式不改变思考强度、thinking 预算或模型。** `agent/request` 钩子只做一件事——`await next()`，读一下请求携带的 `reasoningEffort`、路由名记进测量，然后原样返回。用户选的是哪一档（会话里的 UI 选择，或者 profile 里 agent-default-model 固定下来的那一档），每个请求就用哪一档。

**1.7.0 起这句话没有例外。** 任何模式、任何面板开关下，插件都不写 `reasoningEffort:`、不写 thinking 预算、不加任何 provider 专用字段；`agent/request` 的返回值就是 `next()` 交回来的那个对象，一个字段都不动。1.6.0 那个「唯一例外」（`lazy` 的请求侧降一档）已在 1.7.0 **连同代码一起删掉**，理由留在「六、三个逐会话可点的提示开关」末尾的历史段。

快的部分只来自三件事：更少的往返、更少的冗余输出、更少的卡顿等待。下面逐条说明。

## 交互细节：闪电开关与会话归属

- **左键是开关，右键是面板。** 会话输入区右侧的控制槽（`conversation.input.right`，list 槽，按 `id`/`order` 与别的控件并排）里有一个闪电按钮：关闭时空心，开启时实心。左键切换开关，再点一下切回；这一下只写当前会话。没用单值槽（`conversation.input.activity`）是因为它只渲染第一个注册者，会和计划按钮、语音麦克风互相顶掉。
- **面板在右键里。** 右键闪电打开面板，标题是「更快」，标题行右侧是三档（保守 / 标准 / 激进），下面 5 行，5 行都只影响当前会话："精简输出"（`outputDiscipline`）、"步数上限"（`stepBudget`，循环 0/15/30）、"开局附文件清单"（`workspaceMap`）、"批量读文件"（`openBatch`）、"截短工具输出"（`compactResults`）。5 行都把选择写进 `sessions` 列里本会话的条目，不碰共享默认值，下一个回合生效；只读或写入中时整块禁用，写失败回滚。行内 tooltip：`compactResults`「只留头尾和报错行，长输出不再整段进上下文」。1.7.26 起面板不再出现 `probeFirst`/`actBeforePlan`/`repeatNudge` 三行：这三个都只往提示里加一句话，没测出能压过测量噪声的增益，留着开关会让人以为开了就是快，配置键仍在、老会话里已写的值照样生效。1.7.28 起三档是无状态预设：点一下把该档的开关值一次性写进本会话条目（保守全关；标准只开精简输出；激进再开开局附文件清单、批量读文件、截短工具输出），面板不记录「当前是哪档」，高亮由当前开关值反推，手动改过任何一个开关就不再高亮任何一档；步数上限不归任何档管。选「保守」档时 `directive: false` 会关掉这四行（文件清单不受影响），面板把它们的值显示为关并置灰，点击不写入；没有条目的会话沿用配置里的默认值（`outputDiscipline`/`stepBudget`/`workspaceMap`/`openBatch`/`compactResults`/`probeFirst`/`actBeforePlan`/`repeatNudge`）。面板不暴露 `disciplineAfterSteps`、`disciplineEarlyCalls`、`shapeGate`、`finishLineSteps`、`shellParallel`、`disciplineRepeatAtSteps` 这些未证实或已判 HOLD 的旋钮。
- **会话之间互不影响，这是硬约束。** 每个会话的 `enabled`、`gear`、`outputDiscipline`、`stepBudget`、`workspaceMap`、`openBatch`、`probeFirst`、`actBeforePlan`、`repeatNudge` 都从 `sessions` 列里自己那一条解析：A 会话 `更快 + 精简输出`、B 会话 `更快 + 不写过程` 同时成立，C 会话没点过就沿用配置默认值。面板和闪电只写本会话的条目，从不写共享默认值；条目最多保留 200 个，按 `ts` 淘汰最旧的，被淘汰的会话回到默认值。
- **按钮只表达一个问题：这个会话开不开。** `aria-pressed` 就是状态，tooltip 写清"仅本会话 · 点击关闭 · 右键设置"。为什么快、快在哪里、单次调用上限这类说明放在插件介绍（`meta.description` 与配置项描述）里，不再占用输入区。
- **实时行引用的是被执行的数字**，而不是产品形容词：
  - 开启、看门狗有上限：`自动调度 · 并行 2–8 · 单次调用 ≤ 2 分`
  - 开启、看门狗关闭（`stallGuard: false`，或 `toolTimeoutMs` 为负数）：`自动调度 · 并行 2–8 · 单次调用不限时`
  - 关闭：`已关闭 · 完全回到原生行为`
- **按钮只写它确定的事。** 一次点击只写 `enabled`、`gear`（恒为 `auto`）与 `sessions` 列里本会话的条目，并带上读到的 revision。它不写思考强度，也不写并行宽度：宽度是 host 端按步变化的内部状态，界面展示不了，也就不假装展示。
- **写路径。** 点开关会用一次 `settings/update` 同时提交 `enabled`、`gear`、`sessions` 三个字段，并带上它读到的 `revision`；先乐观更新，再在短时间内轮询确认。被拒绝（revision 过期、profile 只读）就整体回滚并显示原因，按钮不会卡死；读取失败显示错误，点击可重试。写入的 `gear` 永远是常量 `auto`。

## 怎么做的

### 一、不变的部分：模型、供应商、思考强度

1.0.0 把整套"降档"机制**删掉了，而不是关掉了**：`floor`、`ladder`、`aliases`、`deepAfter`、按步降档的阶梯、`ludicrous → off` 的特殊行为，以及按错误/延迟自选 effort 的启发式，都随代码一起消失。在配置里继续写这些字段不会报错（schemastery 会原样保留未声明的键），但**没有任何效果**。

请求钩子的完整逻辑是：

1. `const resolved = await next()`；
2. 读 `resolved.reasoningEffort`、路由名，写进测量标签；
3. `return resolved`——同一个对象、同一个字段集合，不构造新对象，也不写回任何档位。

它不写 thinking 预算，不换模型，也不添加任何 provider 专用字段。因此常备的"更快"杠杆仍是三个：并行扇出（减少往返）、输出纪律（减少过程文字）、卡顿看门狗（减少工具等待）。1.7.0 起另有三条**纯提示**的逐会话开关（先查一次、先执行后解释、重复时提醒），它们只往会话里加文字，同样不碰请求；见"六、三个逐会话可点的提示开关"。思考深度和解码速度不在常备杠杆里——前者是用户的契约，后者任何提示词都做不到。

### 二、自适应调度器：`gear: auto`

`auto` 是唯一面向用户的档位。它按会话自己的历史，每步重新决定两件事：这一步要不要提速（以及多宽），和单次工具调用最多等多久。**只在证据表明这一步是“多个互不依赖的读”时才提速，其余保持原生。**

常量都在 `src/auto.js` 的 `AUTO_LIMITS` 里：

| 名字 | 值 | 含义 |
|---|---|---|
| `parallelBase` / `parallelMax` | 4 / 8 | 首次探测、最宽（`1` 是"原生"的正式写法，调度可以给到 1） |
| `widenAfterSteps` | 2 | 进入"按表现调宽度"的步数门槛 |
| `stallFloorMs` / `stallCeilMs` | 120000 / 300000 | 看门狗预算的夹取区间（2–5 分钟） |
| `stallMultiplier` | 3 | 用最近延迟的 p90 乘 3 作为预算 |
| `noProgressWarn` | 6 | 连续无进展达到这个步数，提醒一次"换个做法"；只提醒，不结束回合 |
| `turnStepBudget` | 0 | 调度器自己的硬步数上限；0 = 关闭，这是默认 |
| `finishLineSteps` | 0 | 可选软收尾线；0 = 关闭，这是默认。`10` 是 1.4.4 的退役默认值，现在只作消融臂 |
| `outputBudget` | 0 | 可选输出 token 预算：本轮累计 `outputTokens`（上游 usage）到线提醒一次「收掉当前闭环、跑一次证明它的检查、汇报」；0 = 关闭，这是默认 |
| `disciplineAfterSteps` | 4 | 迟到输出纪律从第几步起生效 |
| `disciplineRepeatAtSteps` | 0 | 同一句压缩提醒在第几步再发一次；0 = 不发（每回合仍然最多两次）。未证实，默认关 |
| `disciplineEarlyCalls` | 0 | 给正数 N 时，`disciplineAfterSteps` 的提前只对「进入当前步之前本回合已发出 ≥ N 次调用」的回合生效，否则回落到内建 4；0 = 无条件（1.5.12 行为） |
| `waitWindow` | 32 | 参与 p90 的最近调用数 |
| `directiveCooldown` | 3 | 两条调度指令之间的最小步距 |

宽度按下面的顺序算，命中即停：

1. 错误数 ≥ 3：**1**（原生）。错误证据压过入口分类：一直在报错的回合不该被催着批处理；
2. 本轮已经有步数、且入口请求分类给了宽度：**那个宽度**（1–8）。这是本轮第一步唯一可用的信号，所以排在"还没观测过"的探测之前；
3. 会话里还没有任何调用：**4**（只做一次低成本探测）；
4. 连续无进展 ≥ 3 步：**1**（原生，整段提示不渲染）；
5. 上一步是纯读、≥ 2 个调用、且每个调用的目标互不相同（“独立读块”）：**8**；
6. 连续 ≥ 2 步纯读、且上一步 ≥ 2 个调用：**8**；
7. 轮内已走 ≥ 1 步、上一步纯读且 ≥ 3 个调用：**8**；
8. 轮内 ≥ 2 步、上一步 ≥ 2 个调用：**8**；
9. 新的一轮请求、历史里没有并行形状：**1**（原生）；
10. 其余：**1**。

**宽度 1 就是原生。** 提示段在宽度 ≤ 1 时整段渲染为空，看门狗仍按自己的预算工作。所以写密集、强依赖、或一直在报错的回合，不会被催着批处理，也不用为这段提示付输入 token。

**“独立读块”是程序化判据**：这一步所有调用都在 `SAFE_CALLS` 里（`bash` 故意不在），且参数签名两两不同（没有两个调用读同一个目标）。这排除了“看起来批了、实际是链”的形状——两个调用读同一路径时，后者很可能依赖前者的结果。

"无进展"按**结果内容**判，不按字符数、也不按调用数：每个完成调用按（工具名, 参数）记一条签名，结果摘要变了、上次失败这次成功、或这是没见过的签名，都算这一步有进展；同一个签名原样成功重跑不算。所以 `completed 10 / 99` → `11 / 99` 这种等长状态轮询仍算进展（内容变了），而一步里把同一条命令原样重跑八次只算一条签名，这一整步依然是无进展。判定单位是**步**：一步里只要有一个调用带来新结果，这一步就算有进展。

看门狗预算：取最近 32 次工具调用时长的 p90，乘以 3，夹在 120–300 秒之间。还没有样本时 p90 为 0，夹取后就是起点 120 秒，随会话变慢而放宽、变快而收紧。

调度指令：连续无进展达到 `noProgressWarn`（6）步，发一条"换个做法"——它只要求换方法，不要求停下。默认没有任何按步数触发的结束语：`stepBudget` 设成正数时到线才发一次"按现状汇报"，`finishLineSteps` 设成正数时到线才发一次"别再扩大范围"。两条指令之间至少隔 3 步。指令以 producer-owned notice 的形式插进消息列表（source 是 `plugin:fast-mode`），模型读得到，会话记录里也看得到。

**检查结果复用（`reuse-check`）。** 一个带参数的调用成功之后，结果会按签名存下来，连同当时的产物版本号（epoch）。如果模型随后把同一条检查原样再跑一遍，而中间没有发生任何写操作，调度器就不重复催它，而是提醒一句："这个检查在当前文件上已经通过了，可以直接依赖它的结果，把剩下的要求做完。"写、编辑、`apply_patch`/`patch`，以及带重定向或常见改写命令（`sed -i`、`git apply`、`npm`、`make` 等）的 shell 调用都会让 epoch 加一，一次加一就让所有存下来的检查立即失效——"通过"因此永远不会被错当成"改了代码之后还通过"。它只是**建议**：插件不返回缓存结果、不跳过任何调用、也不结束回合，模型仍可选择重跑。 需要说明的是，本地 951 份套件转录里这条建议**一次都没投递过**（`python3 bench/reminders.py`）：那些任务的检查不会被逐字重跑。它属于防御性正确的机制，不是已测出的加速来源。

计数口径：`turnSteps` 按轮计数。`index.js` 在 `session/event` 的 `turn/start` 上调用 `noteTurn` 清零每轮计数，会话级的"试过什么、卡了多久"记忆则保留——这正是卡住时循环要重读的东西。默认没有按步数的停止线；自己把 `stepBudget` 设成正数时，那条硬上限是**每轮**的，不是整段会话的总预算。

### 三、提示词引导：`fast-mode:execution-speed`

提示段的名字与顺序是固定的：`fast-mode:execution-speed`，`order: 520`，紧挨着计划策略和其他跨领域执行引导。它按请求渲染，内容随会话的实时宽度变化：

- 关闭，或宽度 ≤ 1：整段不注册（渲染成空字符串被丢弃）；
- 开启且宽度 > 1：依次是批量规则、扇出目标、可选的预取、有界的调用方式、输出纪律。

几个值得注意的细节：

- **扇出目标里的数字就是调度器算出的宽度。** `auto` 下提示段和看门狗共用同一个 `autoSchedule` 结果，所以模型读到的数字和被执行的数字不会漂移。
- **预取只在宽度 ≥ 4 时出现。** 宽度不够时多取一个调用只会挤占别的工作。
- **有界调用那一段只在 `stallGuard !== false` 时出现**，因为它描述的规则和看门狗是一套东西。
- **输出纪律有两档**：`light` 给旧档位 `fast` 用，`full` 给 `auto`/`turbo`/`max`/`ludicrous` 用。两档要求模型不预告调用、不复述结果、不写进度报告；最后一步的答案仍然要完整写出。`full` 只多一句"不需要文字时只发调用"。**它不会让答案变短，更不会让模型跳过检查。** 口径要说清：可见正文只占「正文+工具参数」字符的 1.19 %（def15 全量 385 会话；不含 reasoning 与工具返回；`python3 bench/textshare.py --limit 0` 可复算），所以它的价值不在"删掉的字"本身，而在它连带把每步的推理压短（池化 168 对里 `reasoning` 字符 0.75×、`emitted` 0.82×）。把这一项单独关掉（`bench/arms/nodisc.yml`）的份额目前**测不出来**：同一块（debugfix 7 对）跑两遍，`nodisc/def15` 一次 1.79×、一次 0.648×，方向相反、符号检验都不显著，块级几何比由一两个离群单元格决定（见 `BENCHMARK.md` 的 LEN3 一节）。能报的只有池化主效应 0.823×，其中明细律与其它提醒在一个机制包里一起生效，无法在单块里分离；同样不能由此宣称"工作内容不受影响"。要判断「在默认之上再打开某个开关还有没有用」，用 `python3 bench/ablate.py --ci 2000 --anchor def15`：它把全部块按同块、同任务、同 pair 池化成每个开关相对默认的几何比与 95 % CI（当前读数：所有可选开关的 CI 都跨 1，叠上去拿不到收益）。
- **`parallel: 1` 是"开着但不要求扇出"的正式写法**：开关仍然生效于看门狗，提示段整体消失。

成本是明码标价的：`auto` 下这一段约 2.7 千字符，每个请求都要付这部分输入 token。这是有意的交换——用输入 token 换更少的往返。

### 四、卡顿看门狗：单次调用的天花板

它挂在 `tools/execute` 这个 waterfall 上，遵守文档允许的唯一改法：只替换 `exec.signal`，让调用体与一个计时器赛跑，超时就 abort 并把失败结果交还给模型；取消后它会分两段等被取消的调用体真的落地（先等宽限，预算够大时最多 5 秒、预算小时按预算缩放且下限 250 ms，再等一段**退出确认**，两段合计上限 15 秒），并把 `quiesced` / `confirmed` / `waitedMs` 记进观测。`confirmed: false` 意味着**没看到进程真的退出**：此时失败文案不会说「已取消」，而是说命令可能仍在运行、先别动它可能写的文件、用 `pgrep -fl` / `ps -o pid,stat,command -p` 确认后再重试。无论结果如何，调用方原来的 signal 都会在 `finally` 里恢复。

它只作用于 **shell / 脚本类**工具（名字匹配 `bash|shell|sh|zsh|exec|pwsh|powershell|cmd|script|monitor|terminal`）。问用户、等后台任务、等子代理这类"本来就要等"的工具被显式豁免：`ask_user_question`、`wait_agent`、`job_output`、`job_list`、`task_board_run`、`workflow`、`subagent`、`subagent_fork`、`run_code`。

预算优先级从高到低：

1. `enabled: false` → 0。总开关是最外层：关掉之后即使 `stallGuard` 还是默认的 `true`、或者另设了 `toolTimeoutMs`，也不会再取消任何调用。1.5.0 之前这里会继续落到档位表，于是"关了还在取消"；
2. `stallGuard: false` → 0（完全不看门狗）；
3. `toolTimeoutMs < 0` → 0（同上，用数值表达）；
4. `toolTimeoutMs > 0` → 这个固定值，所有档位一致；
5. 否则按档位表取初始值：

   | 档位 | 初始上限 |
   |---|---|
   | `safe` | 0（不看门狗） |
   | `auto` | 120 秒，随后由调度器接管 |
   | `turbo` | 180 秒 |
   | `max` | 120 秒 |
   | `ludicrous` | 90 秒 |
   | `fast` | 300 秒 |
   | 未知名 | 300 秒 |

6. 在 `auto` 下，如果初始值非 0 且没有显式 `toolTimeoutMs`，就用调度器按最近 p90 算出的实时预算覆盖上表。

被取消时返回的是正常的工具失败结果（`isError: true`），正文告诉模型"这次调用跑了 N 秒被取消了"，并给三条具体建议：把服务/监听放到后台、把长命令写进文件再执行、把全量扫描收窄成一次窄探测。最后一句明确：不要原样重发同一条命令，它还会被取消。

### 五、开关状态跟着会话走

状态分两层，都在配置的同一个 `sessions` 列里：

- **共享字段 `enabled` / `gear`** 是默认值，也是最近一次选择。新会话没有自己的条目时沿用它。
- **`sessions` 列**是每个会话自己的条目：`{"<sessionId>": {"enabled": true, "gear": "auto", "ts": 1730000000000}}`。读取时逐字段优先于共享字段；写入时两者在同一次 patch 里更新，所以不会出现"开关已经切了、档位还没跟上"的中间态。

上限是 200 个会话，超出时淘汰 `ts` 最旧的条目——这是内存与配置文件体积的边界，不是权限模型。host 端的实现（`src/state.js`）和浏览器里的副本（`client.js`）是两份代码，测试用同一组 fixture 锁住它们的读写一致。

### 六、三个逐会话可点的提示开关（1.7.0 起，默认全关；1.7.1 给 `actBeforePlan` 补了第二个信号）

三条都只在提示侧：一个字都不写进请求，也不改调用。它们改变的是模型这一轮怎么开始（先读还是先想、先做还是先解释），以及在明显原地打转时被告知事实；面板逐会话打开，配置默认 `false`。

1.7.1 之后 `actBeforePlan` 一个开关带两个同方向的行为：回合开始的计划压缩，以及回合中途的思考长度提醒（见本节末尾）。面板的行数随后只在 1.7.12 因压缩开关从七行变八行；`actBeforePlan` 本身没有新增配置键。

**共用一条消息。** `probeFirst` 与 `actBeforePlan` 在本轮第一步合并成一条 notice，两个都关时不发；两个都开也只有一条消息、一句共同 GUARD（`src/deliberation.js` 的 `GUARD`）：`None of this cuts validation at trust boundaries, data-loss handling, security, or accessibility.`

**`probeFirst`「不确定先查证」。** 何时发：打开的回合，第一步，一次。文本（`PROBE_TEXT`，逐字）：``When you are unsure of a fact about the codebase — a file's contents, a signature, a test's expectation — read it once instead of reasoning about it. One call, the cheapest that answers the question: `grep` before `read`, a range before the whole file. Guessing costs a step and is usually wrong; looking costs the same step and settles it. Read again when a result surprises you, before theorising about why it surprised you.`` 它不指定查哪个文件、不替代判断，也不强制任何调用。

**`actBeforePlan`「先执行后解释」。** 何时发：同一个回合第一步、同一条消息里。文本（`ACT_TEXT`）：`Do not restate the task and do not re-explain the plan: the reader already has it. Before your first tool call, one line of plan is enough; when the next action is already clear, take it now and explain afterwards only if the result is not self-evident.` "最多一行计划"是建议措辞，不是机器截断：插件不数行数、不拦截调用。

**`repeatNudge`「重复时换方法」。** 何时发：轮中，只在有证据时。触发二选一——同一个调用（工具名 + 稳定参数，即 `signature`）本轮已运行 ≥ `REPEAT_CALLS`（2）次；或连续结果内容相同，即连续第 3 次相同（`STALE_RESULTS` = 2，比较结果 `digest`）。文本随实际次数走：`The call you are about to reason about has already run N times in this turn and returned what you have. If the answer did not change, a different call is what changes it — not more thinking about this one.`；或 `The last N calls returned the same content. Nothing new is arriving from that direction: decide with what you have, or ask a different question.` 上限：每轮最多 `MAX_REPEAT_NUDGES`（2）次；同一证据（`signature` + `digest`）只提醒一次；错误结果**不算**循环，并且重置「结果没变」的连击——报错是需要多想一会儿的情形。实现 `src/deliberation.js`（`createProgressWatch` / `beginTurn` / `noteCall` / `planRepeatNudge`），观察表上限 `RUN_MAX` = 64 个会话、每会话保留最近 `HISTORY_MAX` = 8 次调用；全部在既有 `catch` 内，异常不会改变工具结果。

**`actBeforePlan` 的第二个信号：思考长度（1.7.1）。** 触发不看工具调用，只看这一回合已经写了多少字符的思考。读数来自 `assistant/message` 事件里的 `reasoning` 块，由 `src/usage.js` 的 `readReasoningChars` 求和（块缺失或没有 `text` 就是 0——路由不吐思考块时这个开关保持沉默），按回合累加进 `noteReasoning` 的 `reasonChars`；`beginTurn` 在新用户消息时清零。阈值 `REASONING_CHARS`（4000）字符，第 n 次提醒的门槛是 `REASONING_CHARS × (n + 1)`，每回合上限 `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.`` 因为触发条件只是长度，这段文本**不说**「没有新信息」——它给的是「按已有证据动手」或「去读那一个还缺的事实」两个出口。它不写任何请求字段，也不改变 `agent/request` 的返回值。

**它们不做什么。** 不写请求字段（含 `reasoningEffort`）、不改模型与 thinking 预算、不重写答案、不强制工具调用、不减少校验。单独开的墙钟收益很小、并且只有 `actBeforePlan` 接近显著：`debugfix` + `migrate` 共 12 对、5 臂 60 次运行里，`probeFirst` 逐对几何 0.883×〔CI 0.663–1.176〕、7/12 更快，`actBeforePlan` 0.761×〔0.578–1.032〕、8/12 更快（`debugfix` 一族 0.582×、6/6），`repeatNudge` 1.007×〔0.839–1.227〕基本没变；三条一起开才是 0.647×〔0.501–0.893〕、10/12 更快——这一条 CI 不跨 1。所以面板给的是「按任务形状试」，不是「单开就快」的保证。

**推荐怎么开（1.7.1 之后）。** 大部分时候三条全开。四块实测里只有三开这一臂的 CI 每一块都不跨 1：`DP17` 12 对 0.647×〔0.501–0.893〕、`DP18` 三族 9 对 0.491×〔0.314–0.788〕、`DP19` 发布前 `ab17` 8 对 0.701×〔0.516–0.930〕、`DP20` 发布版本上 9 对 0.372×〔0.240–0.563〕；合池 30 对 0.505×〔0.401–0.635〕、26/30 更快、30/30 绿、438/438 项。只开一个时按症状选：拿不准就去读一次（`probeFirst`，21 对 0.809×，最保守），思考链太长（`actBeforePlan`，`debugfix` 0.582×），`repeatNudge` 单开无收益、只在组合里无害。实现类任务（`codeimpl`）偶尔多绕一步（`DP20` 有一对 1.04×），所以是「大多数时候」，不是无条件。

**历史：`lazy` 为什么被删（1.6.0 → 1.7.0）。** `lazy`、`lazyEffort`、`lazyVariant`、`src/lazy.js` 与请求侧的 `reasoningEffort` 改写已全部删除，不是默认关闭。证据：`LZ1` 12 对 36 次运行里，「只发文字」逐对几何墙钟 0.389×〔CI 0.252–0.600〕，「文字 + 降档」0.430×〔CI 0.273–0.677〕，两臂直接对打 1.106×〔CI 0.682–1.792〕——区间跨 1，降档这一半没测出可测量的收益；而它是唯一会改变答案而不只是时延的机制，路由又在全部 9678 行计量里报 reasoning usage 0，所以「真的降了档」从来无法被证实。留一个不可验证、还可能改答案的旋钮不值得，于是连同代码删掉。

## 配置

放在 profile 的 `cordis.patch.yml` 里：

```yaml
- id: fast-mode
  config:
    enabled: true
    gear: auto
    # parallel: 4            # 可选：初始并行目标，auto 会随后续步骤调整
    # outputDiscipline: late   # 或 true / false
    # stepBudget: 0            # 单轮步数硬上限，0 = 不限（默认）
    # finishLineSteps: 0       # 可选软收尾线，0 = 关闭（默认）
    # outputBudget: 0          # 输出 token 预算，0 = 关闭（默认）
    # stallGuard: true
    # toolTimeoutMs: 0
    # reassertAfterCompaction: true
    # metrics: true
```

`enabled`、`gear`、`parallel`、`sessions` 四个字段是 `.volatile()`：它们会出现在 Web UI 的插件页表单里，并且修改能直接作用到运行中的 fiber。其余字段是组合级的排障开关，只在 patch 里生效，不占表单。

| 字段 | 默认值 | 说明 |
|---|---|---|
| `enabled` | `false` | 总开关。关闭时完全回到原生行为：不注入提示段、不看门狗、不换档。默认关闭是为了让"关掉就是关掉"可验证。 |
| `gear` | `'auto'` | 调度方式。面板写三档：`safe`（保守，2 路并行、不附输出纪律）、`auto`（标准，出厂默认，4 路并行为起点、按最近几步自动收放）、`aggressive`（激进，8 路并行）。标准与激进在并行宽度与提示强度之外，面板上还差三个开关（见「面板在右键里」一节）；**保守档是例外：它声明 `directive: false`，`resolveConfig` 因此把宽度压到 1、提示段整段消失，并把输出纪律、批量工具、截短工具输出、步数上限一起关掉**——面板上这四行在保守档下置灰并显示为关，避免写进会话却毫无效果的死开关。非面板来源（配置或实验脚本）写 `fast`/`turbo`/`max`/`ludicrous` 会被解析成 `auto` 并打一条弃用日志；**会话条目里 pinned 的 `fast` 是激进档的兼容别名**，解析为 `aggressive`。 |
| `parallel` | 由档位推导，`auto` 下为 4 | 并行上限的初始值，1–8。`auto` 只把它当起点；设为 1 表示不注入批量提示段。 |
| `sessions` | `''` | 插件自动维护的会话级覆盖，见上一节。**不要手改。** |
| `outputDiscipline` | `'late'` | 输出纪律。`late`（默认）前 4 步不写任何输出约束，第 4 步起由调度器提醒一次；`true` 全程生效；`false` 关闭输出纪律，仍注入并行与有界调用规则。 |
| `openBatch` | `false` | 开局批量：每轮第一步发一次「把已知要做的独立调用放进同一块、shell 命令用 `&&` 串起来」。短任务的钱几乎全是每步固定成本（首字延迟 ~0.5 s/次），少一次往返就省一次；只影响第一轮第一步，长任务可忽略。旧批次里它不优于默认，但那些数字已因测量缺陷撤回（运行目录在仓库内、指标按字节偏移切片），尚无干净重测。保持在 `false`，但 1.5.33 起进面板（「批量读文件」），逐会话可开；和 `workspaceMap` 一起开 11 对 0.742×，再加 `disciplineAfterSteps: 2` 三开 22 对 0.669×〔CI 0.554–0.799〕，单独开仍测不到显著。 |
| `probeFirst` | `false` | 逐会话提示开关：拿不准的代码事实（文件内容、签名、测试期望）先读一次，而不是用推理猜；`grep` 先于 `read`，先读范围再读整文件。本轮第一步发一条 notice，一句，且只在打开时发。不指定读哪个文件、不强制调用、不改请求。1.7.0 起进面板（「不确定先查证」）。 |
| `actBeforePlan` | `false` | 逐会话提示开关：不复述任务、不重讲计划；第一个工具调用之前最多一行计划，下一步已经清楚就直接做。与 `probeFirst` 共用同一条 notice 与同一句 GUARD，两个都开也只有一条消息。"最多一行"是建议措辞，不做机器截断。1.7.0 起进面板（「先执行后解释」）。 |
| `repeatNudge` | `false` | 逐会话提示开关：只在有证据时发一条轮中证据行——同一个调用（工具名 + 稳定参数）本轮已运行 ≥ 2 次，或连续 3 次结果内容相同；句子给出实际次数，每轮最多 2 次，同一证据只提醒一次，错误结果不算循环并重置连击。实现 `src/deliberation.js`，不写请求字段。1.7.0 起进面板（「重复时换方法」）。 |
| `shapeGate` | `false` | 形状闸门：本轮累计调用 ≥ 4 且其中写入类（不在只读名单里的工具）占比 ≥ 60 % 时，**整轮不再注入任何提醒**，行为退回原生。判断只用已有的工具名，不发请求、不加请求体字段。它给的是「不适合就别管我」的退路：写密集型的任务没有可并行的调用，每条提醒都只是一次它用不上的往返。显式设了 `stepBudget` 时该收尾线优先于闸门——「别加速」不等于「无视我叫停」。默认关，因为它会同时关掉本可以救回长尾的收尾提醒。 |
| `preferPatch` | `false` | 整文件写入之后提醒一次「改已有文件时只发改动的行（edit hunk），不要重发整个文件」。本步 `write` 的正文 ≥ 2000 字符才武装，之后一步发一次，每轮最多一次；文本明确要求结果与原文件逐字节相同，不限制写什么、也不影响何时结束。默认关闭：它只作用于模型的输出形式（工具调用参数），正确性由文件内容决定、与发送形式无关；实测是否真省墙钟见 `BENCHMARK.md` 的 `pp16` 臂。 |
| `compactResults` | `false` | 工具结果截断：结果正文超过 `compactMaxChars` 时只留头尾两段并写明省略了多少字符与行区间（`… [fast-mode: elided 4210 chars, lines 88–412] …`）；shell 类输出还会把被省略区间里的报错行连同行号带进同一条标记（`; kept 2 matching lines 121:FAIL …`），默认最多 12 条。只改模型看到的文本，不改工具、不改文件、不改请求；错误结果与已含该标记的结果原样通过。默认关——它会改变模型所见，不能静默开启。实现 `src/compact.js`；配置里默认关，1.7.12 起面板也有逐会话的「截短工具输出」，两边写的是同一个键。 |
| `compactMaxChars` | `8000` | `compactResults` 的阈值，200–200000。低于阈值的结果一字不改。 |
| `readCache` | `false` | 读取缓存：同一文件在 `mtime`/大小/inode 三项都没变时，第二次整文件 `read` 直接返回上次结果的原文，省掉一次磁盘读与一次结果传输；带 `offset`/`limit` 的切片读、错误结果、超过 200000 字符的文件都不缓存也不命中。默认关，命中与未命中的文本逐字节相同。实现 `src/readcache.js`。 |
| `compactRereadHint` | false | 压缩提醒里多一句「本回合写过的文件不要为确认整份重读」；不新增消息。实验旋钮，默认关 |
| `workspaceMap` | `false` | 每会话一次工作区清单：在第一次工具调用之前，由 harness 自己走一遍会话的工作目录（深度 2、最多 `mapEntries` 条、跳过 `node_modules`/`.git`/`dist` 等），把「相对路径 + 字节数」和它能看到的测试入口（`npm test` 脚本、`tests/`、pytest 配置、`go`/`cargo`/`make`）作为一条通知交给模型。它只替代「目录里有什么」这一轮探索（W8b 记录里这类调用占 35.7 %），不隐藏任何东西、不限制读什么、也不影响何时结束；清单一字未改地给模型，读哪个文件仍由它自己判断。默认关闭：它是一个提示，不是加速证明，实测见 `BENCHMARK.md` 的 `map15` 臂。 |
| `mapEntries` | `60` | `workspaceMap` 的条数上限，10–200。清单正文另有 ~1.4 kB 的上限，超出时给「前 N 条，共 M 条」并保留测试入口那行。 |
| `stepBudget` | `0` | 单轮步数硬上限。`0` = 不限，这是默认：步数只说明成本，不说明任务做没做完，所以默认不按步数催停。设成正数时，到线只发一次「按现状汇报」，不阻断工具调用——硬截断会把任务切在编辑中途。 |
| `outputBudget` | `0` | 输出 token 预算：本轮累计 `outputTokens`（上游 `usage.outputTokens`，不是估算）达到该值时提醒一次「收掉当前闭环、跑一次证明它的检查、汇报」，每轮最多一次。`0` = 关闭，这是默认。它触发的是真实解码量而不是步数——三步写出三万 token 的任务很贵，二十步只写两千 token 的任务不贵；但 token 数同样是成本而非完成度证据，所以默认不启用。 |
| `finishLineSteps` | `0` | 可选软收尾线：第 N 步提醒一次「不再扩大范围、不重跑已通过的检查、只修还在报的错」。`0` = 关闭，这是默认。**`10` 是 1.4.4 的退役默认值，现在只作消融臂，且不建议开启**：它在旧批次里是测到最快的配置（那些数字已因测量缺陷撤回），但按门禁不合格——`ledger` p1 上基线与默认都绿、它红（21/22，重跑 5 次全绿，1/6 失败率），因此仍然关闭。7 家族 14 对的 1.86× 是在 1.4.4 那个默认上测的逐对中位，不适用于本版；它省的是重复验证，但会在「测试通过后又改了代码」的任务上压掉必要复验，所以退回关闭。数据和边界见 `BENCHMARK.md`；1.5.0 自身默认的干净重基线是 10 家族 30 个完整单元：**0.72× 墙钟 / 0.74× 输出 / 0.71× 步数**，逐格墙钟中位 **0.64×**、隐藏测试 60/60 全绿，见该页 `## 1.5.1`。 |
| `stallGuard` | `true` | 是否给单次 shell/脚本类调用加上限。`auto` 下预算在 2–5 分钟之间自适应。 |
| `toolTimeoutMs` | `0` | 单次调用上限，单位毫秒。`0` = 自适应（见 `stallGuard`），负数 = 关闭看门狗，正数 = 所有档位统一用这个值。 |
| `guidance` | `true` | 是否向模型注入提示段。关掉后调度、宽度计算、看门狗照常，只是不写任何提示文字——用来把“提示词的效果”和“harness 层面的效果”分开测。 |
| `shellParallel` | `false` | 是否注册 `shell_parallel` 工具（一次调用内跑多个独立 shell 作业）。默认关闭：2026-10-24 实测 6 个 5 秒作业，原生 9.1 s vs 开工具 26.3 s（0.35×）——模型本来就会用 `&` + `wait` 自并行，多出的工具诱发复核步骤。 |
| `reassertAfterCompaction` | `true` | 上下文被压缩后的下一次请求，补一条极短 notice，重申批量与有界调用两条规则。提示段本身不受影响。 |
| `metrics` | `true` | 是否写计量文件。关闭则不落任何文件。 |
| `metricsFile` | `~/.dsh/fast-mode-metrics.jsonl` | 计量文件路径，每步一行 JSONL；超过 8 MB 轮转成 `.1` 再新开一份；`DSH_FAST_MODE_METRICS_MAX_BYTES` 可在基准窗口内临时放大上限，避免按字节偏移读取时被轮转打断。 |
| `benchmarkFile` | `~/.dsh/fast-mode-bench.json` | 实验分配文件路径。文件不存在或 `enabled` 不为真时不启用分配。 |

关于旧名字：schema 仍然接受 `fast`、`turbo`、`max`、`ludicrous`（否则旧 patch 会加载失败），但用户配置里出现它们时，`resolveConfig` 会改成 `auto` 并在加载日志里警告"这个名字以前会降低思考强度，现在跑自动调度，强度保持不变"。实验脚本的按会话 override 是例外，它保留原样（见"按会话分配档位"）。

关于 `safe`：它是实验对照组，不是给用户选的档位。它声明了 `directive: false`，因此 `resolveConfig` 把宽度压到 1、提示段整段消失、看门狗预算为 0——也就是"插件在场但不干预"。

## 与压缩类插件共存的细节

本插件管的是**执行**：一次请求里能并行几个调用、模型写多少过程文字、单次调用等多久。压缩类插件管的是**输入历史**：消息列表多大、多少旧图片被搬走。两者作用在不同层，字段不重叠，可以叠加。

三条规则让它们真的能一起用：

1. **不抢同一个字段。** 本插件不碰 `reasoningEffort`、`thinking`、`output_config`，也不改任何请求字段；压缩器与它没有共享字段——所以没有"两个组件同时钳制 effort"的问题。
2. **压缩步骤不需要特殊照顾。** 1.0.0 没有"按住某一步"的概念；历史折叠发生在请求发出之前，本插件看到的只是一个普通请求。
3. **摘要调用碰不到。** 压缩器自己的摘要请求往往跑在它自己的进程或 HTTP 层里，不经过 `agent/request`，本插件既不影响它，也不被它影响。

### billion-context

**兼容且互补。** billion-context 以 DSH bundle（`bili-native`）安装，关掉宿主平面 `compaction-basic` 的 auto，并通过 patch HTTP 层拦截模型流量（`globalThis.fetch`），不走 `agent/request`，也不读写 `reasoningEffort`。它和本插件没有共享字段，不需要为对方改配置。

唯一值得看一眼的是它自己的 `compress.outputSteering.effortRouting`：那个开关会在"机械延续"的轮次把 effort 往低压。它不再是本插件的竞争对手（本插件根本不碰 effort），但它会破坏"强度保持你选的那档"这个承诺。如果你在意这个承诺，把它关掉：

```yaml
- id: bili-native
  config:
    compress:
      outputSteering:
        enabled: true
        effortRouting: false    # 强度由用户选择，不由压缩器改写
```

另外两点是事实，不是建议：billion-context 自己的摘要调用跑在它的代理进程里，不经过 harness，本插件碰不到；它会关掉 `compaction-basic` 的自动压缩，所以**别再叠第二个压缩器**。

### 历史图片自动压缩 / 网关适配插件

`image-guard`、`intellif-litellm-adapter` 这类插件修的是另一种失败：在请求发出前重写消息列表（搬走老图片、强制再压一轮、重试 `too_many_images` 的 400），以便穿过网关的图片数量 / 像素 / 上下文上限。它们和本插件不共享任何字段，不需要改配置，也不需要把工具名交给本插件"保护"——1.0.0 没有这样的名单。

同一 profile 里**只留一个图片削减者**（billion-context 也会在服务端折叠媒体），否则两次压缩互相踩。

### `dsh-batch-tool-calls` 与 `dsh-ops`：不能和本插件同时启用

这两个不是"配置一下就能共存"的邻居，是**功能重叠**：同时开会让同一件事被收费两次，有时还会互相打架。

- **`dsh-batch-tool-calls`** 注册的是它自己的批量调用提示段。本插件注册的是它的超集：按会话生效的宽度、随表现变化的并行目标、输出纪律、有界调用规则、压缩后补一条提醒。两个都开着，同一份提示要付两遍钱，而且两段文字可以给出不一致的并行目标。
- **`dsh-ops`** 用自己的工具替掉 shell 与文件工具（`ops_bash`、`ops_grep`、`ops_glob`、`ops_inspect_local_file`、`ops_replace` …），而且只带 Windows 二进制。本插件的引导段讲的是"用那个 shell 做事"——给调用设上限、把服务放后台、别用 heredoc；装了 `dsh-ops` 就等于有两个 shell、两套规则，要么重复要么冲突。

本插件加载时会检测这两个（在 profile 的 `package.json` / `cordis.patch.yml` 里找包名，另外用一次实时的 `ops_bash` 工具探测），命中就在会话里**只说一次**冲突，不阻塞加载。修法只有一条：**删掉其中一个**。

## 官方 API 支持

**完全支持，而且不需要任何配置。** 官方 DeepSeek API（`deepseek-official`）、官方 API 前面挂的代理和中转、自建网关（如 `litellm` + Anthropic Messages）、OpenAI 兼容端点，行为完全一样，原因很简单：

- 请求体原样进出。本插件不写 `reasoningEffort`、不写 `thinking`、不写 `output_config`，也不添加任何 provider 专用字段；路由不报 reasoning token 也不影响提示侧照常工作；
- 不做"这条路由声明了哪些档位"的过滤和降档。没有 `floor`，没有档位阶梯，没有按步降档，也没有请求侧改写；
- 路由名只被记进测量和调试日志，不参与任何决策；
- token 用量按上游实际拼写读，不假定某一家：harness 的 `inputTokens/outputTokens/…`、OpenAI Chat 的 `prompt_tokens/completion_tokens`（含 `prompt_tokens_details.cached_tokens`、`completion_tokens_details.reasoning_tokens`）、新版 OpenAI 的 `input_tokens/output_tokens`、Anthropic 的 `cache_read_input_tokens/cache_creation_input_tokens`、Google 的 `candidatesTokenCount` 都会落到同一组数字；usage 挂在 `usage`、`message.usage` 或 `response.usage` 下都能找到。网关完全不报 usage 时按 0 处理——步的时间和调用数由 harness 自己量，不靠 provider。

所以无论 provider 声明的是 `off/low/high/max`、还是 `minimal/low/medium/high`、还是只有一档、还是什么都没声明，本插件的行为都一样：它注入提示段、给 shell 调用设上限，然后把你选的 effort 原样发出去，一个字段都不改。

## 测量、实验与报告

测量留在插件里，而且**关闭态也记录**——只记录开启态的话，就没有基线可对比。

### 每个完成的步写一行

计量文件是 JSONL，每个完成的模型步一行，懒创建、失败不影响请求（写不进去就静默放弃）。版本号是 `v: 1`，字段如下：

| 字段 | 含义 |
|---|---|
| `v` | 计量格式版本，当前为 1 |
| `ts` | 这一步结束的 ISO 时间 |
| `sid` / `turn` / `step` | 会话 id、轮次、步号 |
| `mode` / `gear` | `on` 或 `off`；关闭时 gear 记为 `off` |
| `effort` | **请求实际携带的思考强度**，就是你在 UI 里选的那档，一字不改（1.7.0 起没有任何路径改它） |
| `effortBase` | 旧字段，1.6.x 用来记降档前的选择。1.7.0 的代码不再设置它，所以新行里通常没有这个键；留着只为读旧计量文件 |
| `route` | 路由/模型的标签，仅用于分组 |
| `ttftMs` | 首字延迟：步开始到第一条 assistant 消息 |
| `llmMs` | 模型耗时：步开始到最后一条 assistant 消息 |
| `stepMs` | 整步耗时，含工具等待 |
| `calls` / `errors` | 本步的工具调用数与失败数 |
| `input` / `output` / `reasoning` | 上游 usage 的输入、输出、推理 token；拿不到就不写 |
| `cacheRead` / `cacheWrite` | 上游 usage 的缓存读写 token |
| `assistantMessages` | 本步的 assistant 消息数 |

`effort` 这一列是理解请求路径的关键：它记录的是请求**实际带出去的**值，也就是你在 UI 里选的那档（选了 `high`，每一行就是 `high`）。1.7.0 起这一列只是读数，不是插件写过的证据——没有任何路径能让它和你的选择不同。另有一条独立通道 `reasoningChars`：`agent/assistant-stream` 里数出来的思考字符数，不依赖 provider 报 `reasoning_tokens`，用来判断"这步想得久不久"，与档位无关。

### 每个会话自己的开关

`sessions` 列的数据契约（也是你在导出配置时会看到的东西）：

```json
{"<sessionId>": {"enabled": true, "gear": "auto", "ts": 1730000000000}}
```

- 读取顺序：本会话条目 → 共享 `enabled`/`gear` → schema 默认值；
- 条目里缺哪个字段，就退回共享字段的那个字段，而不是整体失效；
- 写条目和写共享字段在同一次 patch 里完成；
- 上限 200 条，超出淘汰 `ts` 最旧的；`enabled` 必须是布尔、`gear` 必须是字符串、`ts` 必须是有限数，损坏的条目会被丢弃而不是让整列失效。

### 按会话分配档位

实验文件（默认 `~/.dsh/fast-mode-bench.json`）存在且 `enabled: true` 时，插件按会话 id 的 FNV-1a 哈希把会话分到不同档位，做到同一账号下随机对照：

```json
{
  "enabled": true,
  "gears": ["off", "safe", "fast", "turbo", "max"],
  "sessions": { "<sessionId>": "max" }
}
```

- `sessions` 里的显式钉子优先于哈希，用来把某个重要会话固定到某一档；
- `off` 是对照组，表示关掉插件；
- 不认识的名字被过滤掉并只警告一次；文件坏了也只警告一次，然后当作不存在；
- 文件改动最多 1 秒生效（TTL 1 秒），不需要重启。

分配结果在请求路径上**优先于**按会话的开关——这是实验期间"别手动干预"的原因：按钮显示你上次的选择，而实际执行的可能是分配到的档位。

### 报告：`dsh-fast-mode-report`

```bash
dsh-fast-mode-report                  # 默认文件，全部历史
dsh-fast-mode-report --since 2d       # 只最近两天
dsh-fast-mode-report --json           # 机器可读
dsh-fast-mode-report --file ./m.jsonl # 另一个文件
dsh-fast-mode-report --lan            # 附带局域网用量面板对照
```

输出分三块：

1. **按 `mode|gear` 分桶的主表**：会话数、步数、步/轮、步耗时 p50/p90、模型耗时 p50、首字 p50、输出 token/步、输出 token/会话、工具调用/步、多调用步数；
2. **相对关闭态的倍率表**（有基线时才出现）：模型耗时倍率、步耗时倍率、输出比例、每会话 token 比例、调用数比例。`> 1×` 表示该档位更快或更贵，看列名；
3. **口径说明**：比较单位是"模型步"而不是整个会话（会话墙钟还包含人读和打字）；模型耗时与首字的定义；倍率基于均值，样本少时会抖动。

没有关闭态样本时它不会编倍率，而是直接告诉你"去关掉跑一遍"。`--lan` 需要 `USAGE_TOKEN` 环境变量（`USAGE_BASE` 可选），只读；缺 token 或请求失败只打印一条说明，不影响本机报表。

### 实验：`dsh-fast-mode-experiment`

```bash
dsh-fast-mode-experiment start        # 写实验文件
dsh-fast-mode-experiment status       # 看进度和每档样本数
dsh-fast-mode-experiment stop         # 结束实验
```

选项：`--bench`、`--metrics`、`--gears a,b,c`、`--sessions n`（只影响 status 的提示）、`--force`（覆盖已有实验文件）。默认实验臂是 `off,safe,fast,turbo,max`——注意这些是**旧的固定宽度臂**，按会话 override 原样执行：`fast` 是 4 宽 + 轻量输出纪律，`turbo` 是 6 宽，`max` 是 8 宽，`safe` 是"在场但不干预"，`off` 是对照。想看自适应调度器本身的效果，把 `auto` 加进 `--gears`。

实验文件和计量的配合方式：`start` 后跑任务，`status` 看每档是否够样本，`stop` 结束；再用 `report --since` 看倍率。样本太少时倍率会抖，这是统计事实，不是插件行为。

## 安全边界

- **只观察，不改写。** `tools/pre-execute` / `tools/post-execute` 是纯观察者（记录开始时间和调用结果），`agent/request` 原样返回请求。除了注入提示段和三条 producer-owned notice（压缩后提醒、调度指令，以及只在本会话打开 `repeatNudge` 时才有的重复提醒），插件不往会话里写任何东西。
- **模型与档位都不可写。** 源码里没有任何 `reasoningEffort:` 赋值，也没有 0.9.x 的 `decideEffort`；`test/effort.test.js` 扫描源码，要求 `/reasoningEffort\s*:/` 命中数为 **0**（每个文件都查），并且仍然禁止 `thinking:` 与 `floor`/`effort` 这类旧字段名。所以"插件偷偷降了档"不是被禁止，而是根本没有那条代码。
- **设置只能由这次点击改，且带 revision。** 没有后台静默改配置的路径；冲突写入会失败并回滚，原因是 tooltip。
- **计量是本地的。** 只写 `metricsFile`；`--lan` 是唯一的出网路径，只读，需要显式 token。
- **看门狗只改 signal。** 调用方原来的 signal 在 `finally` 里恢复；取消后调用体的 rejection 被吞掉，不会变成未处理的拒绝。
- **每个缓存都有上限。** 插件持有的 8 个缓存全部以永不重复的 id 为键（会话 id、`callId`、`${sid}${cwd}`），而宿主进程会活几周，所以一个都不留无界：`compacted` 200、`mapSent` 500、`autoBudgets` 200、`startedAt` 2000、`warnedCaps` 有界、测量 sink 的 `labels` 2000 / `steps` 2000 / `open` 2000 / `pendingChunk` 1000、`src/auto.js` 的会话状态 200（LRU）。淘汰统一走 `src/bounded.js` 的 `trim(cache, cap)`，插入顺序即淘汰顺序。上限远高于真实并发量，所以正常使用不会淘汰活条目；真的淘汰了也不会写错行——测量行记 `mode: 'unknown'`，不会借用别人的档位。sink 关闭时 `label()` 直接不写（默认配置就是关闭）。`bench/soak.mjs` 用 20000 个会话的黑盒浸泡验证这条（−0.1 KB/会话）。

## 诚实的限制

**时延账本。** 在这套网关上实测，一步的墙钟约等于 `720 ms + 6.63 ms × 输出 token` 再加工具等待（R² = 0.93，689 步样本）。这条式子说明：减少步数、减少工具等待是真杠杆；思考强度和原始解码速度不是——前者改写答案，后者任何提示词都做不到。

**扇出有前提。** 只有一步里真的存在多个互不依赖的查询时，加宽才有收益。单文件类的基准任务（HumanEval / AIME 形态）上，本插件测到的差异在噪声内（0.95×–1.09×），没有可测量的加速——那些任务本来就没有可并行的读集。把"这个插件让所有任务快 30%"当卖点是错的。

**默认没有按步数的停止线。** 1.5.0 把 `finishLineSteps` 与 `stepBudget` 的默认值都改成 0：一个还在产出新结果的回合不会被步数计数器结束，也不会被提醒收尾——把一个做到一半的长任务按步数打断，比慢更贵。默认唯一按步数触发的循环提醒是连续 6 步无进展时的一句「换个做法」（`noProgressWarn`），它只换方法，不结束回合；1.7.0 的 `repeatNudge` 是另一条、独立的一路，默认关闭、只在有证据时发（同一个调用已跑 ≥2 次，或连续三个结果相同），两套阈值不要混着读。这些常量的数值来自小样本观测与设计推理，还缺真实会话的对照数据；`report` 与 `experiment` 是为了补这一课，不是为了证明它已经成立。

**token 可能变多，而且这是接受的交换。** 更宽的步意味着每步塞进更多工具结果，输入 token 上升；输出纪律只减少过程文字，不承诺总 token 下降。不要用"省 token"来介绍这个插件。

**提示是建议，不是强制。** 模型仍可能每步只发一个调用。真正同时执行多少个调用还受 `agentLoop.maxParallelToolCalls` 限制；插件发现目标宽度超过这个上限时会警告一次，并给出一条 `cordis.patch.yml` 的改法。

**看门狗只看 shell 类工具。** 一个卡住的非 shell 调用（比如某个自定义工具的 promise 永不 resolve）不在保护范围内。被豁免的工具按设计不设上限——等用户、等后台任务本来就要等。

**客户端看不到实时宽度。** 它只在插件介绍里写 2–8；host 端为某个会话算出的当前宽度只出现在提示段和调试日志里。

**`turnSteps` 的计数口径。** 每轮由 `turn/start` 清零；如果自己把 `stepBudget` 设成正数，那条硬上限只约束单轮，跨轮保留的是调度记忆，不是步数预算。

## 开发与测试

模块分工：

- `index.js`：组装、钩子、按会话取配置、提示段注册；
- `src/auto.js`：调度器的状态、宽度、预算、指令；
- `src/guidance.js`：模型可见的提示文本；
- `src/stallguard.js`：看门狗与预算优先级；
- `src/effort.js`：档位表与旧名字归一化（不再有 effort 阶梯）；
- `src/deliberation.js`：三个提示开关（`PROBE_TEXT` / `ACT_TEXT` / `GUARD`）、进展观察、重复提醒判定、思考长度提醒（`REASONING_CHARS` / `noteReasoning` / `planReasoningNudge`）；
- `src/config.js`：schema 与表单字段；
- `src/state.js`：`sessions` 列的解析与编码；
- `src/notices.js`：producer-owned notice 的构造与来源校验；
- `src/metrics.js`：JSONL 计量与汇总；
- `src/benchmark.js`：按会话哈希分配；
- `src/peers.js`：冲突插件探测；
- `client.js`：闪电开关、右键面板（五行，按会话写入 `sessions` 列）。

跑测试：

```bash
node --test test/*.test.js
```

当前 334 个测试在 Node 24.16.0 上全部通过（334/334，0 失败，`node --test test/*.test.js`，约 10.5 秒）。测试覆盖：调度器宽度与指令、预算优先级、提示段的开关条件、`sessions` 列在 host 与浏览器两份实现间的一致性、notice 来源校验、计量字段、分配哈希、报告与实验 CLI 的参数解析，三个提示开关的文本与进展观察（`test/deliberation.test.js`，10 条），以及一条扫描源码、确认 `reasoningEffort` 赋值出现 0 次的回归测试（`test/effort.test.js`）。

文档入口：`README.md`（中文）、`README.en.md`（English）、`BENCHMARK.md`（测量方法与历史数据）、`CHANGELOG.md`（每个版本删了什么、加了什么）。
