verdict-guard
English | 中文
DeepSeek Harness 原生插件。 一个下了结论的回合,在结论拿不出可查证的东西之前不会结束。
模型: “这个过滤器不工作,这个方向到此为止。”
插件: 拦下 —— 有结论,没有证据。
模型: (去跑一遍,贴出真实输出;或者收回这句话)为什么
Agent 产出的最贵的东西不是一个改错的补丁,而是一个下错的否定结论: 「不工作」「这里没有 bug」「复现不了」。
说错的「能用」会被下一次运行抓住 —— 它留下痕迹。 说错的「不能用」不留痕迹:一个方向被悄悄关掉,而已经有答案的问题没人会再打开。 两类错误的代价并不相等,那么它们要求的证明标准就不该相同。
这个插件把这种不对称变成机制:在回合的停止边界上,它读两样东西 —— 模型准备用来收尾的那段话,以及这个回合实际做过什么。
什么算证据
| 信号 | 来源 |
|---|---|
| ``` 代码块、退出码、测试计数、HTTP 状态行 | 回答的文本 |
file.ts:88、文件路径、日志名、journalctl、pytest … | 回答的文本 |
一次「验证类」工具的结果 —— bash、read、grep、test … | 该回合的会话日志 |
第三行正是它是插件而不是 shell hook 的原因。纯文本检查只能看到模型写了什么, 会话日志才说明跑过什么。
由此得到一项 hook 做不到的检查:
> 回答里贴着命令输出 —— `` 代码块、12 passed, 0 failed`、退出码 —— > 而这个回合一个工具都没调用。那段文字只可能是写出来的,不是观察到的。 > 这种情况会被拦下,并给出比「缺引用」更明确的说明。
单独一个文件路径不会触发这条更严格的判定:路径可以是从对话前文如实回忆的, 而一段命令记录不能。
安装
dsh plugin add dsh-plugin-verdict-guard安装到此为止。包内声明了 dsh.bundle manifest,插件会以自己的 bundle 层进入 profile, 所有选项取默认值 —— 不需要手写任何一行配置。确认它进入了插件树:
dsh --profile headless --dump-config | grep -A 3 verdict-guard要改默认值,在 profile 自己的 patch 层 ($DSH_HOME/profiles/<profile>/cordis.patch.yml,它在所有 bundle 层之后应用)里加一行:
- id: verdict-guard
config:
locale: en
requireToolEvidence: false
maxInterventionsPerTurn: 2配置
| 选项 | 默认值 | 含义 |
|---|---|---|
locale | both | 启用哪套结论词表 —— en、ru、both |
requireToolEvidence | true | 结论需要一次验证类工具结果,而不是任意一次工具调用 —— 写文件不等于核对 |
verifyingTools | bash/read/grep/test… | 算作验证的工具名 |
extraVerdictPatterns | — | 追加到内置结论词表的正则 |
extraEvidencePatterns | — | 追加到内置证据词表的正则 |
verdictPatterns | — | 整体替换内置结论词表 |
evidencePatterns | — | 整体替换内置证据词表 |
maxInterventionsPerTurn | 1 | 同一回合最多被拦几次 |
maxInterventionsPerSession | 6 | 同一会话累计最多被拦几次 |
verbose | false | 以 debug 级别记录每次放行 |
空数组一律读作「没有提供」,而不是「什么都不匹配」—— 这样一来,把未设置的数组选项填成 [] 的配置,不会让插件看起来配好了却其实全部放行。
它不是什么
它不判断一个结论是否为真 —— 没有 oracle,没有验收标准,没有契约。 它只查一件事:这个回答有没有给出任何可以去查的东西。 一个错误的结论只要旁边有真实的命令输出,在这里就会放行,交给读的人去发现; 一个正确的结论如果旁边什么都没有,会被拦下。
默认情况下每个回合也只拦一次。它是出门前的一道减速带,不是必须闯过的关卡。
工作原理
一个监听器,挂在 harness 自己的停止边界 agent/turn-stopping 上:
agent/turn-stopping → 读该回合收尾的 assistant 文本
→ 读该回合的 tool/call + tool/result 事件
→ 有结论?有证据? → agent.steer(原因)steer 会让状态机重新观察待处理输入并再跑一步,这正是该边界上「监听器提出异议」的 既定做法。agent loop 本身没有被改动任何一处。
插件自己限流。 harness 在这里还没有防循环保护:loop 里写着 TODO(stop-loop-guard),而 Claude Code hook 桥接层无条件上报 stop_hook_active: false。因此一个会 steer 的监听器必须自己设上限,否则它可能让一个 回合永远关不掉。maxInterventionsPerTurn(默认 1)就是这个上限 —— 拦一次,之后无论模型说什么,回合都会结束。
兼容性
针对 0.1.0-rc.8 构建与测试。
⚠️ harness 自己那些子包的 latest dist-tag 是过期的。 @deepseek-ai/dsh-tools、-llm、-session 等把当前线发布在 next(0.1.0-rc.8)下, 而 latest 仍指向 0.0.1-rc.1。不写明范围地安装它们会混进两代不兼容的包 —— 并且部分 0.0.1-rc.1 依赖 @deepseek-ai/dsh-bash,这个包已经不存在了。 请锁定 ^0.1.0-rc.8,或用 @next 安装。
开发
npm install
npm test # 24 个测试:检测器本身,以及插件在真实 agent loop 上的行为
npm run build集成测试用脚本化的模型适配器驱动真实的 AgentLoop, 因此两种结果 —— 被拦下与正常结束 —— 都不需要 provider key 就能验证。
关于作者
作者 Sagit Nabiev,长期在生产环境里跑 agent —— 一个自信却没有依据的结论会白白吃掉一天, 这个插件就是从那里来的。如果你想为自己的 harness 或 agent 栈定制类似的东西 —— 拦截器、桥接层、插件 —— 请写信到 founder@perpreconcile.com。
许可
MIT
