dsh-llm-verifier 0.3.1 → 0.4.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.
Files changed (97) hide show
  1. package/README.md +133 -9
  2. package/lib/auto-B5V49e-A.js +1517 -0
  3. package/lib/auto-B5V49e-A.js.map +1 -0
  4. package/lib/{caller-DJd_FlrQ.js → caller-DR_C7ueP.js} +154 -20
  5. package/lib/caller-DR_C7ueP.js.map +1 -0
  6. package/lib/caller.js +2 -2
  7. package/lib/client.js +804 -29
  8. package/lib/client.js.map +1 -1
  9. package/lib/core.js +226 -3
  10. package/lib/core.js.map +1 -1
  11. package/lib/index.js +1044 -1349
  12. package/lib/index.js.map +1 -1
  13. package/lib/replay.js +157 -0
  14. package/lib/replay.js.map +1 -0
  15. package/lib/types/auto.js +1 -1
  16. package/lib/types/auto.js.map +1 -1
  17. package/lib/types/caller.d.ts +65 -0
  18. package/lib/types/caller.d.ts.map +1 -1
  19. package/lib/types/caller.js +113 -5
  20. package/lib/types/caller.js.map +1 -1
  21. package/lib/types/client-i18n.d.ts +163 -6
  22. package/lib/types/client-i18n.d.ts.map +1 -1
  23. package/lib/types/client-i18n.js +137 -26
  24. package/lib/types/client-i18n.js.map +1 -1
  25. package/lib/types/client.d.ts +30 -0
  26. package/lib/types/client.d.ts.map +1 -1
  27. package/lib/types/client.js +94 -9
  28. package/lib/types/client.js.map +1 -1
  29. package/lib/types/config.d.ts +33 -0
  30. package/lib/types/config.d.ts.map +1 -1
  31. package/lib/types/config.js +17 -2
  32. package/lib/types/config.js.map +1 -1
  33. package/lib/types/core.d.ts +84 -0
  34. package/lib/types/core.d.ts.map +1 -1
  35. package/lib/types/core.js +222 -2
  36. package/lib/types/core.js.map +1 -1
  37. package/lib/types/criteria.d.ts +33 -0
  38. package/lib/types/criteria.d.ts.map +1 -0
  39. package/lib/types/criteria.js +67 -0
  40. package/lib/types/criteria.js.map +1 -0
  41. package/lib/types/decisions.d.ts +84 -0
  42. package/lib/types/decisions.d.ts.map +1 -0
  43. package/lib/types/decisions.js +209 -0
  44. package/lib/types/decisions.js.map +1 -0
  45. package/lib/types/engine.d.ts +54 -1
  46. package/lib/types/engine.d.ts.map +1 -1
  47. package/lib/types/engine.js +175 -12
  48. package/lib/types/engine.js.map +1 -1
  49. package/lib/types/index.d.ts +1 -0
  50. package/lib/types/index.d.ts.map +1 -1
  51. package/lib/types/index.js +402 -39
  52. package/lib/types/index.js.map +1 -1
  53. package/lib/types/replay.d.ts +88 -0
  54. package/lib/types/replay.d.ts.map +1 -0
  55. package/lib/types/replay.js +136 -0
  56. package/lib/types/replay.js.map +1 -0
  57. package/lib/types/router.d.ts +29 -5
  58. package/lib/types/router.d.ts.map +1 -1
  59. package/lib/types/router.js +271 -45
  60. package/lib/types/router.js.map +1 -1
  61. package/lib/types/statistics.d.ts +10 -1
  62. package/lib/types/statistics.d.ts.map +1 -1
  63. package/lib/types/statistics.js +35 -7
  64. package/lib/types/statistics.js.map +1 -1
  65. package/lib/types/top-logprobs.d.ts +36 -0
  66. package/lib/types/top-logprobs.d.ts.map +1 -1
  67. package/lib/types/top-logprobs.js +41 -14
  68. package/lib/types/top-logprobs.js.map +1 -1
  69. package/package.json +6 -1
  70. package/scripts/eval-replay.mjs +118 -0
  71. package/src/auto.ts +1 -1
  72. package/src/caller.test.ts +62 -1
  73. package/src/caller.ts +124 -4
  74. package/src/client-i18n.ts +137 -25
  75. package/src/client.test.ts +21 -0
  76. package/src/client.tsx +145 -7
  77. package/src/config.test.ts +32 -0
  78. package/src/config.ts +49 -2
  79. package/src/core.test.ts +95 -0
  80. package/src/core.ts +217 -2
  81. package/src/criteria.test.ts +60 -0
  82. package/src/criteria.ts +76 -0
  83. package/src/decisions.test.ts +150 -0
  84. package/src/decisions.ts +244 -0
  85. package/src/engine.test.ts +131 -0
  86. package/src/engine.ts +167 -10
  87. package/src/index.test.ts +349 -8
  88. package/src/index.ts +383 -38
  89. package/src/replay.test.ts +82 -0
  90. package/src/replay.ts +155 -0
  91. package/src/router.test.ts +217 -5
  92. package/src/router.ts +286 -47
  93. package/src/statistics.test.ts +49 -1
  94. package/src/statistics.ts +44 -7
  95. package/src/top-logprobs.test.ts +28 -0
  96. package/src/top-logprobs.ts +56 -8
  97. package/lib/caller-DJd_FlrQ.js.map +0 -1
package/README.md CHANGED
@@ -8,13 +8,14 @@
8
8
 
9
9
  `dsh-llm-verifier` 为 DeepSeek Harness(DSH)引入了一套**独立的裁判复核机制**。在主 Agent 负责生成代码、执行命令与工具交互的同时,Verifier 会收集当前任务目标、各候选方案过程以及真实的终端执行结果,交由你在设置中指定的独立 DSH 模型进行仲裁:评估哪个方案更可靠、当前任务的实际完成进度、以及是否存在未发现的潜在错误。
10
10
 
11
- 插件提供四个显式工具,并支持可配置的宿主级自动会话验收:
11
+ 插件提供五个显式工具,并支持可配置的宿主级自动会话验收:
12
12
 
13
13
  - `verifier_compare`:对两个候选执行过程进行成对比较(Pairwise Comparison);
14
14
  - `verifier_select`:在多个候选方案中通过锦标赛机制选出最优解,供 Best-of-N / 多候选编排器直接调用;
15
15
  - `verifier_track`:评估任务在已有检查点(Checkpoint)的完成度与进展,供 Goal / Workflow 等长任务编排器直接调用;
16
+ - `verifier_best_of_n`:**唯一会自己生成候选的工具**——用当前会话模型并行起草 N 份完整候选,再用独立裁判排序,并额外给出与验收门控同源的**绝对分**(见[关键交付物的 best-of-N](#关键交付物的-best-of-n));
16
17
  - `verifier_current_session`:显式提取当前 DSH 会话记录,进行脱敏并执行复核;
17
- - **四工具自动路由**:智能或严格策略在 `agent/turn-stopping` 生命周期边界按阶段调度 `select → compare → track → current_session`。第一阶段只信任 Workflow 的版本化候选协议与发生真实变化的 Todo 快照;普通 Subagent 输出必须经第二阶段的证据引用分类,避免把不同子任务误当候选;
18
+ - **四类自动路由**:智能或严格策略在 `agent/turn-stopping` 生命周期边界按阶段调度 `select → compare → track → current_session`(`verifier_best_of_n` 永不自动触发)。第一阶段只信任 Workflow 的版本化候选协议与发生真实变化的 Todo 快照;普通 Subagent 输出必须经第二阶段的证据引用分类,避免把不同子任务误当候选;
18
19
  - **自动验收门控**:候选选择与进度检查完成后,宿主运行同一会话验收逻辑;未通过时以插件 steering 反馈要求 Agent 修复并重新验证,而不是依赖模型是否主动想起工具。
19
20
 
20
21
  ## 安装与启用 (Installation & Usage)
@@ -204,7 +205,7 @@ T = 明确且无可争议的失败
204
205
 
205
206
  - **聚合方式是中位数**:单个裁判时中位数就是它自己的分数,因此单裁判结果与旧版本逐位一致;两个裁判退化为平均值;三个及以上时,个别跑偏的裁判不会把结论拉走。
206
207
  - **每个裁判独立缓存与去重**:裁判身份(Provider / Model / 推理强度 / 输出上限)是评分缓存键的一部分,不同裁判既不会读到对方的缓存,也不会被合并成一次调用。
207
- - **判决透明**:四个工具(`verifier_compare` / `verifier_select` / `verifier_track` / `verifier_current_session`)的返回都新增 `judges` 数组,逐裁判给出 Provider、Model、显示名、是否成功、实际调用次数,以及该裁判自己的分数 / 排名 / 检查点分数;`verifier_compare` 与 `verifier_current_session` 还返回 `agreement`(与最终胜负一致的裁判比例,单裁判恒为 1)。
208
+ - **判决透明**:五个工具(`verifier_compare` / `verifier_select` / `verifier_track` / `verifier_best_of_n` / `verifier_current_session`)的返回都新增 `judges` 数组,逐裁判给出 Provider、Model、显示名、是否成功、实际调用次数,以及该裁判自己的分数 / 排名 / 检查点分数;`verifier_compare` 与 `verifier_current_session` 还返回 `agreement`(与最终胜负一致的裁判比例,单裁判恒为 1)。
208
209
  - **降级而不是整体失败**:个别裁判偶发失败(网络、限流、解析不出判决)时会被排除在该次聚合之外,并在 `judges[].ok=false` 与 `judges[].error` 中如实报告;只有当**整组裁判**都给不出判决时,这次验收才失败。
209
210
  - **预算按裁判数放大**:自动路由的模型调用预算按 `对局数 × 裁判数` 预留,多裁判场景请相应调大「任务/会话模型调用预算」(默认 96 / 240)。
210
211
  - **分类只走主裁判**:混合语义路由的分类请求是轻量路由动作而非判决,只使用主裁判,不随裁判数放大。
@@ -239,13 +240,19 @@ flowchart LR
239
240
  | **混合语义路由** | 结构化证据不足时,是否允许裁判模型保守分类 `compare/select/track/none`;不会生成新候选或编造检查点 |
240
241
  | **语义路由置信度** | 语义识别达到该值才执行对应工具,默认 `0.9` |
241
242
  | **最多候选数** | 自动 `select` 一次最多纳入的候选数,默认 `8` |
242
- | **每任务/每会话最多路由** | 限制 `compare/select/track` 及语义分类的自动次数。它与最终验收**共享同一个尝试计数器**(实际上限 = 本项 + 对应的「每任务/每会话最多验收」),预算按真实对局数估算(8 候选约需 108 次调用);被拒绝时写入宿主日志并区分「预算不足 / 已有 verifier 在跑 / 同一证据已路由」,strict 下额外注入一次提示 |
243
+ | **每任务/每会话最多路由** | 限制 `compare/select/track`、语义分类、团队任务闸与计划预审的自动次数。它与最终验收**各自独立计数**(路由用本项,最终验收用「每任务/每会话最多验收」,互不占用),因此被路由要求过的最终验收一定有额度可用;旧版共享一个计数器时,路由可以把最终验收的额度花光,导致 turn 在从未验收的情况下静默关闭。预算按真实对局数估算(8 候选约需 108 次调用);被拒绝时写入宿主日志并区分「预算不足 / 已有 verifier 在跑 / 同一证据已路由」,strict 下额外注入一次提示 |
243
244
  | **同时验收子 Agent** | 默认关闭;开启后 `subagent` / fork 子会话也会执行自动路由与最终验收 |
244
- | **进度完成阈值** | `track` **最新**检查点低于该值时 steering 要求继续工作,默认 `0.8`。更早的检查点只随消息展示:每个任务的第一个检查点都是"刚写完计划",用"任一检查点低于阈值"判定会让继续分支恒真(实测 5/5) |
245
+ | **进度完成阈值** | `track` **最新**检查点低于该值时 steering 要求继续执行,否则改为"准备最终交付证据;最终会话验收是强制的"。默认 `0.684` = 判官标尺上 **N("偏成功")的起点**(进度分 = 字母序号 ÷ 19,N = 13/19 ≈ 68.4%),与提示词里"N–S = leans YES"的档位描述自洽;旧的 0.8 要求 Q 以上,等于"说得再肯定也不算过"。更早的检查点只随消息展示:每个任务的第一个检查点都是"刚写完计划",用"任一检查点低于阈值"判定会让继续分支恒真(实测 5/5)。**最新检查点达到阈值时还会置一个"下个边界直接进最终验收"的偏好**:steering 文案不变,但下一停止边界会跳过新的自动路由(不再为同一句提示重复购买 `track`);偏好由最终验收的预约消耗,验收失败后路由自动恢复 |
245
246
  | **单项/总证据字符上限** | 自动候选与检查点脱敏后的单项、整次路由输入硬上限,默认 `20000 / 60000`;同时约束语义路由提示词的总长度(只保留预算内最新证据)。截断标记本身也计入上限,因此截断后的条目不会被判定为超限。候选与检查点都按**数量分摊**总字符预算(单项仍受 2 万字符上限),自动 `track` 还最多渲染**最近 6 个**检查点(更早的在首个检查点中注明已省略),因此 8 个长候选或几十个检查点都不会让整条决策被上限整条丢弃 |
246
247
  | **任务/会话模型调用预算** | 分类、compare/select/track 与最终验收共享的估算请求预算,默认 `96 / 240`(按真实对局数估算:8 候选的完整锦标赛约 18 场对决 × 3 标准 = 54 次调用,再加最终验收 3 标准 × 2 轮 = 6 次;默认值因此给最终验收留出余量) |
247
248
  | **通过阈值** | 自动会话验收要求证据分数达到阈值、胜过“未执行有效工作”基线,**且每一项标准各自也达到阈值**(均值会把某一项彻底失败平均掉:3 项中 1 项 0 分、2 项满分时均分 0.67 仍会越过 0.65) |
248
- | **路由评估轮次** | 自动路由每个标准的重复轮次;默认 1。`compare` 只判一对,会被**向上取整到偶数**(引擎只在奇数轮交换 A/B 位置,奇数轮等于让第一个候选固定坐在 A 位);`select` 的 ring 天然对称、pivot 轮的 A/B 槽位由引擎逐对平衡,`track` 没有位置可换,两者都保留配置值——不对症的偏差不付双倍调用 |
249
+ | **验收判据 (Rubric)** | 自动门控(最终验收、自动 compare/select/track)与未显式传 `criteria` 的工具使用的评分标准。内置 5 套:`coding`(默认,与历史内置判据**逐字相同**)、`debug`、`research`、`ops`、`writing`,每套 2–4 条窄判据。切换会同时改变验收松紧与提示词(缓存键随之失效,属预期);设置页可展开**预览**判官实际收到的提示词 |
250
+ | **判据文件 (Criteria file)** | `criteriaPreset = custom` 时读取的 Markdown 判据文件路径。格式:`## Ground Truth Note`(可选)+ `## Criteria` 下每个 `### 判据名`(可用 `{#id}` 锚定 id,否则由名称 slug 化)。文件缺失或解析失败时**退回 `coding` 判据**并在日志告警,不会让门控失效;判据文件在每次解析边界重新读取,编辑后立即生效 |
251
+ | **路由评估轮次** | 自动路由每个标准的重复轮次;默认 1。`compare` 只判一对,会被**向上取整到偶数**(引擎只在奇数轮交换 A/B 位置,奇数轮等于让第一个候选固定坐在 A 位);`select` 的 ring 天然对称、pivot 轮的 A/B 槽位由引擎逐对平衡,两者都保留配置值——不对症的偏差不付双倍调用 |
252
+
253
+ | **进度跟踪轮次** | 自动 `track` 单独的重复轮次并**取平均**,默认 `3`。判官模型不返回 logprobs 时,进度分来自显式标签通道——每次调用只采到**一个字母**(A–T 每档 5.3%),单次采样足以在相邻档位之间抖动;重复取平均即上游 `n_evaluations` 的做法。`track` 是最便宜的一类调用(一次请求、无锦标赛),因此默认值比路由轮次高 |
254
+
255
+ | **保存决策快照** | 默认开启:把每次裁判调用的**脱敏提示词 + 原始回答**限量写入本话题的 `verifier/decisions-v1.json`(每次调用最多 12 次模型调用;单条记录 ≤ 3 万字符,且**按调用数平均分配**——6 次调用的会话验收会把 6 条全部留下并各自缩窗,而不是只留下最先返回的 3 条;每话题保留最近 40 条),统计看板的「最近调用」里可逐条展开查看。它只回答"裁判为什么这么判",不参与任何判定;命中评分缓存或复用 in-flight 调用时不会伪造快照 |
249
256
  | **最终验收轮次** | 最终会话验收每个标准的重复轮次,默认 `2`:奇数轮不交换、偶数轮交换 A/B 位置,因此默认配置下位置偏好被抵消。调低到 1 会省一次调用,但会失去这层保护 |
250
257
  | **智能模式最少工具调用** | `smart` 策略需要的最少非 Verifier 工具调用数 |
251
258
  | **最大证据字符** | 自动发送给裁判的最近会话轨迹字符上限 |
@@ -271,9 +278,122 @@ flowchart LR
271
278
  > [!NOTE]
272
279
  > **多模态与图片支持**:若选定的裁判模型不支持图像输入,传入图片时将由对应 DSH Adapter 明确报错拦截,插件绝不会静默丢弃图片证据。
273
280
 
274
- ## 四工具自动调度
281
+ ## 验收判据(Rubric)
282
+
283
+ 自动门控过去对所有任务使用同一套编码判据,于是"研究问答"或"运维操作"也会被"Output Match / Error Signal Detection"这类判据打分。现在按任务类型选择判据:
284
+
285
+ | 预设 | 判据 |
286
+ |---|---|
287
+ | `coding`(默认,= 历史内置判据) | Specification Adherence / Output Match / Error Signal Detection |
288
+ | `debug` | Failure Reproduction / Root Cause / Fix Verification |
289
+ | `research` | Question Addressed / Source Grounding / Limits Stated |
290
+ | `ops` | Change Specification / Observed Result / Reversibility |
291
+ | `writing` | Brief Adherence / Structure And Clarity / Factual Grounding |
292
+ | `custom` | 由「判据文件」指定的 Markdown 文件 |
293
+
294
+ 自定义文件格式(对齐上游 `criteria/*.md` 的写法,HTML 注释会被剥离,判官看不到):
295
+
296
+ ```markdown
297
+ # 我的任务 — 验收判据
298
+
299
+ ## Ground Truth Note
300
+
301
+ 只信观测到的输出,不要信 Agent 的自我陈述。
302
+
303
+ ## Criteria
304
+
305
+ ### 最终答案正确性
306
+
307
+ 对照任务真正要求的内容、类型与格式打分;只在答案被轨迹中观测到的输出支持时给高分。
275
308
 
276
- 自动路由发生在 Agent **准备停止但尚未提交 `turn/end`** 的边界,四个工具不是互斥替代关系,而是覆盖不同阶段:
309
+ ### 经验验证 {#verification}
310
+
311
+ 看 Agent 实际运行了什么、输出了什么,而不是它声称了什么。
312
+ ```
313
+
314
+ 判据 id 由名称 slug 化(`{#id}` 可固定 id,避免改标题导致缓存键变化);同名判据会自动去重为 `_2`,因为 `compare` 按 id 归并逐项结果——重名会把两条判据悄悄合并成一行。**解析失败一律 fail closed**:没有判据、或某条判据没有正文,都会退回 `coding` 并在日志说明原因。
315
+
316
+ ## 判官自检(Probe)
317
+
318
+ 统计看板右上角的「判官自检」按钮会对**每个已配置判官**发一次真实调用(超时上限 30 秒、不重试、不计入统计),报告:
319
+
320
+ - 可达性与错误原文;
321
+ - **实际使用的评分通道**(`top-logprobs` 概率期望 / `explicit-tag` 显式标签)——能力探测是运行时静默降级的,这里才看得见。自检会**强制重新探测**该判官的能力(清掉本地能力标记并持久化),所以它给的是当场结论而不是缓存里的旧答案;如果探到该判官其实支持 logprobs,后续真实验收也会随之改走概率期望通道;
322
+ - 一次真实 A–T 判决的解析结果(证明回答**能被解析**,而不只是"能连上");
323
+ - 延迟与调用次数;
324
+ - **当前生效的判据**(来源、条数、文件路径;自定义文件不可用时显示回退原因)。
325
+
326
+ 统计看板是**全局页面**,通常没有"当前会话":自检会把能力记忆挂到**最近的一个话题**上(能力记忆本身是 per-topic 的),因此这个按钮在全局看板里同样可用;如果连一个会话都还没有,它会直接说明需要先开始一个会话,而不是抛出与诊断无关的报错。
327
+
328
+ ## 离线回放评测(`scripts/eval-replay.mjs`)
329
+
330
+ 不需要任何模型调用,回答两个问题:
331
+
332
+ ```bash
333
+ pnpm run build # 脚本导入 lib/replay.js
334
+ node scripts/eval-replay.mjs # 扫描 ~/.dsh/sessions 下所有话题
335
+ node scripts/eval-replay.mjs --dir <话题目录> --thresholds 0.5,0.65,0.8
336
+ ```
337
+
338
+ 1. **阈值扫描**:每条 `verifier_current_session` 记录都存有会话分数、空工作基线、胜者与逐项判据分数——也就是门控看过的全部信息——因此可以用**当前生效的验收规则**在每个阈值上重新判定,并区分"均分不够""逐项不达标""输给基线""无法解析"四种落选原因。
339
+ 2. **解析器回放**:决策快照保存了判官原始回答,脚本用当前解析器重新解析并报告 drift——提示词或解析逻辑的回归会在**没有模型调用**的情况下暴露出来。`top-logprobs` 通道的分数是 token 分布上的期望,快照不保存分布,因此其文本通道重解析出现 drift 属预期,仅供参考。
340
+
341
+ > 在本仓库作者机器上的真实数据里,14 次验收在 0.5–0.9 的所有阈值下**全部通过**:判官给每次会话都打了 1.00。这正是这个工具要暴露的事实——阈值调参的前提是判官能区分候选,详见 `.agents/notes/implemented/feature/2026-09-13-best-of-n-generation-side-selection.md` 记录的判别探针结论与当前通道现状。
342
+
343
+ ## 关键交付物的 best-of-N
344
+
345
+ 插件是「复核者」,默认不能改写模型输出;但**可以按需生成候选再选优**,这是唯一能把「发现失败」变成「交付更好」的手段。有两条路径:
346
+
347
+ ### 路径一:`verifier_best_of_n`(插件自己起草,显式触发)
348
+
349
+ 一次调用完成「并行起草 N 份 → 独立裁判排序 → 与门控同源的绝对分」:
350
+
351
+ ```text
352
+ task ─┬─ 生成 N 份候选(会话模型,temperature 1.0,maxTokens 4096/份,并行)
353
+ ├─ verifier_select 的锦标赛(默认 3 判据 × 2 轮)→ 相对最优 + 排名
354
+ └─ 胜者 vs 空工作基线(与最终验收同一条基线)→ 绝对分 + passesThreshold
355
+ ```
356
+
357
+ **先读这条:判别力随候选难度塌缩(实测)**
358
+
359
+ 用本仓库的判官(`antigravity/gemini-3.8-flash`,explicit-tag 通道)对两对候选各做 10 次重复、A/B 槽位各 5 次,共 60 次真实调用:
360
+
361
+ | 候选对 | 方向正确率 | 中位分数差 | 单次比较胜率 |
362
+ |---|---|---|---|
363
+ | 明显质量差(一方漏验需求) | 10/10 | 0.544 | 63.3% |
364
+ | 难分(都通过任务列出的用例,差别在 `.strip()` 的覆盖范围与证据) | 10/10 | 0.018 | 50.4% |
365
+
366
+ 方向是可靠的(A/B 换槽后完全一致),但**效果量随难度塌缩**:explicit-tag 一次只采一个字母,量化下限就是 1/19 ≈ 0.053,0.018 的中位差意味着 3 条判据里只有 1 条动了一档。真机上已经看到它的直接后果:一个三份草稿**都合格**的长任务被锦标赛判成 **0.5 / 0.5 / 0.5 全平**,排名没有任何信息量;那一次真正救场的是**绝对分**——`passesThreshold: false` 且点名 `output_match` 未达标(生成侧无法提供真实跑过的 stdout/stderr),于是 agent 不会拿着一个平局排名去发货。**这正是「相对最优 ≠ 够好」,也是基线比较不可省的原因。** **所以「让插件起草」只在预期候选之间有明显质量差时才划算**(不同思路、不同实现正是这种情形);如果候选很可能都「做了真实工作」,先花钱买判官质量(换成能走概率期望通道的判官)而不是把 N 调大。设置页的「判官自检」会回报当前实际通道。
367
+
368
+ 探针数据与结论另见 Agent Notes:`.agents/notes/implemented/feature/2026-09-13-best-of-n-generation-side-selection.md`(含三份草稿全合格时锦标赛全平的真机案例)。
369
+
370
+ - **不新增配置项**:起草模型取自 `agent.session.requestHeader().config`(就是当前会话模型),温度固定 1.0——判官的默认 0.2 会让 N 份几乎相同,工具就失去意义。
371
+ - **每份输出上限 16384 token,且「被上限截断」不算失败**:这个数字是实测的,不是拍的——第一次真机端到端验收里,4096 让**三份草稿全部截断**、工具一份都没返回;原因写在返回值的 `stats.reasoningTokens` 里:一次只有两份短草稿的运行,会话模型(`deepseek-official/deepseek-flash`)输出了 17254 token,其中 **16363 是推理 token**(约 8k/份),4096 在答案开始前就被吃光。16384 既是该实测值的约 2 倍,也是「两份草稿 + 任务」仍留在插件 24 万字符显式证据上限内的最大取值;`maxTokens` 是上限而非预留,短草稿的代价没有变化。**仍被截断的草稿会被保留并列入 `truncated`**:判官自己看得到文本没写完、会相应扣分,而直接丢弃等于白付一次生成。真正的长答案超出本工具适用范围。
372
+ - **`judges[].calls` 覆盖整次调用**:既含锦标赛也含基线比较(`ok` 同理),因为顶层 `calls` 已经把两者都算进去了,只报锦标赛会低估每个判官的真实工作量。
373
+ - **只有 `score` / `criteria` / `passesThreshold` 是绝对口径**:`scores` 是锦标赛偏好份额(`wins/counts`),**不能**和 `autoVerifyThreshold` 相比;`threshold` 回显当前生效阈值,判定规则与最终验收完全一致(胜者、均分、逐项判据)。
374
+ - **成本**(默认 3 判据、2 轮;判官数会整体放大):
375
+
376
+ | n | 生成 | 锦标赛 | 基线 | 合计模型调用 |
377
+ |---|---|---|---|---|
378
+ | 2 | 2 | 6 | 6 | 14 |
379
+ | 3(默认) | 3 | 18 | 6 | 27 |
380
+ | 4 | 4 | 36–54 | 6 | 40–64 |
381
+
382
+ - **使用门槛**:这是最贵的验证工具,只用于「最终交付物且选错代价高」的场景,并且**永不参与自动路由**;已有候选请改用 `verifier_select` / `verifier_compare`。
383
+ - **fail closed**:幸存候选少于 2 份直接报错并列出每一次失败原因,绝不静默退回第 1 份;会话尚无已记录的请求头(无法确定起草模型)时明确报错,并指向路径二。
384
+ - 费用估算把生成 token 也按**判官的单价表**折算(插件只有这一张表),因此只是估算。
385
+
386
+ ### 路径二:手写配方(不调用新工具,零额外生成费用)
387
+
388
+ 1. 用 `subagent` 并行产出 2–3 份**完整**候选(不同思路、不同实现),各自独立验证;
389
+ 2. 用 `verifier_select` 让独立裁判排序(自动路由本身也会把多个子 Agent 的产出识别为 `select` 路由);
390
+ 3. **同时看绝对分数**:best-of-N 只保证「相对最好」,不保证「够好」。候选分数低于验收阈值时,应继续修正而不是直接采纳。
391
+
392
+ 两条路径的前提都是裁判具备**区分两个都做了真实工作的候选**的能力。若判官在难分候选上退化为极值(A/T),best-of-N 等于付 N 倍生成成本换一次抛硬币——因此先把「判官自检」与难分候选的对比跑一遍再决定是否采用。
393
+
394
+ ## 四类自动路由
395
+
396
+ 自动路由发生在 Agent **准备停止但尚未提交 `turn/end`** 的边界,被路由的四个工具不是互斥替代关系,而是覆盖不同阶段(`verifier_best_of_n` 不在其中:它是显式、按需、昂贵的生成工具):
277
397
 
278
398
  ```text
279
399
  3 个以上同组真实候选 → verifier_select → steering 实施胜出候选
@@ -317,12 +437,14 @@ flowchart LR
317
437
 
318
438
  - `smart`:仅在会话出现 **Subagent / Workflow / 计划模式产物**时分类。Todo 与 Team 快照不在其列:它们是结构化 track 路由自己的输入,且结构化路由先跑——把它们算作线索只会在"结构化已经产出决策"或"已显式 track 过"的形状里多付一次分类调用;
319
439
  - `strict`:每个准备结束边界都分类,无法确认时返回 `none`;路由调用失败时注入 steering,阻止静默跳过;
320
- - `manual`:结构化与语义自动路由、最终自动验收都关闭,四个工具仍可显式调用。
440
+ - `manual`:结构化与语义自动路由、最终自动验收都关闭,五个工具仍可显式调用。
321
441
 
322
442
  候选选择产生 steering 后,Agent 必须实施胜出候选;进度未达阈值也会 steering 继续。任何自动 `compare/select/track` 成功都会设置 `finalVerificationRequired`,下一停止边界即使没有传统写入类工具也必须执行 `verifier_current_session`;只有针对最新快照且达到阈值的最终验收才能清除要求(计划预审通过**不会**设置该标记:批准计划不等于完成工作)。路由采用 reservation/commit/fail 状态,取消、过期或失败不会被误记为成功;strict 会维持阻断并 fail closed。
323
443
 
324
444
  显式调用 `verifier_current_session` **不会**无条件关闭自动门控:只有该次复核真的达到通过阈值(`winner === 'A'`、分数不低于阈值,**且每一项标准各自不低于阈值**——与自动门控同一条 `sessionAccepted` 规则,否则显式调用会变成绕过单项失败的漏洞),并且之后没有再产生实质工作(写文件、执行命令等)时,才会被当作"已完成验收"而放行。判决失败、低于阈值、结果无法解析、或通过之后又继续改动的复核,都不会解除门控——下一停止边界仍会照常验收。此外,Agent Teams 的队友会话以 `team-message` 作为任务边界,因此队友会话同样可以触发(并遵守)任务级门控;Team 任务验收与计划预审都要求裁判给出显式的 `Verdict: <A-T>` 行,解析不出来时:计划预审放行(预算耗尽时 deny 会死锁 plan mode,只能放行),**Team 任务验收在 strict 下照常阻断**,与其它自动阶段一致。
325
445
 
446
+ `track` 采用**离线**打分:判官在给较早的检查点打分时已经看到了它之后的内容,因此曲线偏乐观。门控只用**最新**检查点(它之后没有内容,不存在"未来泄露"),曲线展示时请把它读作"判官回看整段会话时的判断",而不是逐步在线估计;上游的 `ProgressTracker` 是逐步在线形态,代价是每一步一次调用,本插件不采用。
447
+
326
448
  所有自动阶段共享任务/会话级模型调用预算;分类请求以 `verifier_route_classify` 单独记录到统计。若某条决策因为超过证据上限、或语义路由引用了不存在/已失效的证据而没能执行,插件会写入宿主日志,并在统计里记下一条带 `dropped-over-budget` / `invalid-references` 判决的记录(没有模型调用,因此 `calls` 为 0),看板可以直接回答“这次为什么没有路由”。候选、步骤和标签在发送前统一执行默认脱敏、单项字符上限和总字符上限,图片证据按与显式工具相同的路径传入。
327
449
 
328
450
  ## 缓存、重试与遥测统计
@@ -333,6 +455,8 @@ flowchart LR
333
455
  - **运行统计与监控**:每次调用返回完整的统计信息,涵盖实际请求次数、重试次数(Retries)、各类 Token 消耗(输入/缓存命中/输出/推理 Token)、缓存命中与未命中次数(Cache Hits/Misses)以及费用估算;统计记录还会保存一条 `verdict` 摘要(阶段、结果、分数、基线、胜负、阈值,会话验收额外带 `criteria` 逐标准分数;`track` 额外带 `scores` 检查点曲线,`score` 取**最新**检查点而不是历史最低分;`compare` 的 `score` 是**胜方**分数并额外记录 `scoreB`,结果记为 `compared` 而不是拿一个它从未使用的阈值判"未达标";旧记录缺少这些字段时按旧样式渲染),看板的「最近调用」因此能直接显示每次判决与阈值的关系,回答"这次为什么没通过",而不是只显示"调用成功/失败"(注意:`success` 表示**模型调用没有抛错**,不等于任务通过验收);其中:
334
456
  - `topLogprobScores`:采用概率分布期望计算的评分次数;
335
457
  - `explicitTagScores`:降级采用模型显式输出标签的评分次数。
458
+ - **前缀缓存预热(省钱项)**:提示词把任务与两条轨迹放在前面、判据放在**尾部**,以最大化同一次比较内各判据之间共享的提示词前缀;并且**每个不同前缀先单独跑一次**再扇出其余调用(换位轮次的前缀与未换位不同,因此各预热一次;`track` 的重复轮次发送的是同一条提示词,先跑第一轮再扇出)。预热用的是本来就要发生的调用,只改变顺序,**不增加调用次数**,只多一次串行等待;在没有前缀缓存的后端上收益为零、损失也只是这点等待。提示词布局或预热顺序是刻意设计,改动前请先读 `core.ts` 的 `buildPairwisePrompt` 注释与 `engine.ts` 的 warm-up 段。
459
+ - **两种"缓存命中率"要分清**:看板的「评分缓存命中率」是**本地评分缓存**命中(命中即不发模型请求),「前缀缓存命中率」是**模型服务端**按 token 计费口径统计的前缀命中(`cachedInputTokens / (inputTokens + cachedInputTokens)`,DSH 报告的两者是**不相交**的计数)。前者衡量省了多少次调用,后者衡量省了多少输入 token;只优化后者不会改变任何判决,而把两者混为一谈会让人误以为省钱优化无效。
336
460
  - **能力记忆**:当某个 Provider/Model 明确拒绝 `logprobs` 参数后,插件会记住该能力探测结果,避免后续每次调用重复报错。探测结果按话题持久化到 verifier 目录的 `capabilities-v1.json`(24 小时 TTL 后自动重新探测),随话题一并删除;跨进程与会话恢复不再重复探测,首次降级并发窗口内的相同请求也会合并在途调用、不重复计费。
337
461
 
338
462
  ## 隐私与数据安全边界