linkage18
donecheck
A plugin for DSH that checks whether your work has actually been completed. It automatically loops over the model’s responses to check for errors or shortcomings.
- Stars
- 1
- Language
- JavaScript
- Created
- Aug 15, 2026
- Updated
- Aug 15, 2026
Introduction
DoneCheck(完工核查)
让 LLM 代理不再"自以为做完"——自动复查-修复循环插件,面向 DeepSeek Harness (DSH) 动态插件运行时。
LLM 代理最常见的隐性失败不是"答错",而是提前自我终止:它认为自己已经完成任务、改完了代码,实际上没有。DoneCheck 在每次回答结束后自动执行一轮带实际操作结果的复查,发现问题就带着工具权限迭代修复,直到满足要求或达到轮次上限——并全程把"做了什么、为什么停"讲清楚。
项目对外名称:DoneCheck;代码内部标识符沿用 doublecheck。
适用模型:特别适合 deepseek-flash 这类轻量高速模型——速度快但更容易"自以为做完",复查-修复闭环正好把完成度兜住;对更强模型同样有效,收益随模型可靠性递减。
核心特性
- 复查看实据:复查员能看到回答全文 + 实际操作记录(改了什么文件、命令输出、失败信息),判定的是"实际完成度",不是"两遍回答像不像";
- 修复是真步骤:修复轮是正式会话消息,模型保留工具权限——能真改代码、真跑命令,而不是嘴上说改;
- 多轮迭代闭环:复查-修复循环最多 4 轮,满足要求或达到上限即停止,并明确说明结束原因;
- 来源可追溯:每轮修复新增/修改的内容按轮次标记(
【复查A2补充】…),一眼看出哪段是哪一轮补的; - 判定可靠:复查输出 JSON 契约,三级解析回退,无法解析时保守处理——宁可不动,不乱动;
- 零侵入:不修改会话日志、不污染模型上下文、不干扰正常回合;任何失败都静默兜底。
背景:它解决什么问题
代理模型在工具型任务(改代码、跑命令、写文档)上经常犯一个隐蔽错误:任务只完成了一部分,模型却宣布完成。典型表现:
- 改了 A 文件,B 文件的要求被遗漏;
- 声称"测试通过",实际没跑;
- 输出里写着"已实现",代码里根本没有。
为什么"让它再想一遍"不够?因为独立重答看不到实际操作结果——闭卷重考只能发现"问题没覆盖",发现不了"代码没改到位、命令没跑通"。DoneCheck 的核心转变:复查员带着实际结果(工具调用/输出的累积记录)去核对完成度。
工作原理
总体流程
回合结束前(agent/turn-stopping 钩子,串行等待)
↓
取最新回答 A_k + 原始问题 + 实际操作记录(工具调用/结果,跨轮自动累积)
↓
复查调用(独立模型调用,JSON 契约判定)
├─ completed=true 且 problems=[] → 结束(原因:满足要求)
├─ problems 非空 且 轮次 < 4 → 触发修复轮
│ 输入 = 原始问题 + 历轮回答全文(较老轮次截断)+ 问题清单
│ 执行 = agent.steer 注入真实步骤(模型保留工具权限)
│ 输出 = A_{k+1},新增/修改内容标【复查Ak补充】
│ → 回合再次进入 turn-stopping → 复查 A_{k+1} → 循环
└─ 用完 4 轮 → 结束(原因:达到上限,最后一轮提示"务必一次解决")
↓
报告卡:判定链(A1 未完成 → A2 完成)、修复轮数、结束原因、问题清单、复查原文
复查协议(Review Protocol)
| 要素 | 说明 |
|---|---|
| 输入 | 【原始问题】+【待复查的回答】+【执行工具的实际记录】(工具调用名/参数/结果,按执行顺序,含失败标记) |
| 判定契约 | 严格 JSON:{"completed": true, "problems": []} 或 {"completed": false, "problems": ["问题1(引用原文证据)", ...]} |
| 检查要点 | ① 每条要求是否真正完成(代码改到位?命令跑通?结果符合要求?)② 是否存在"自认为完成实际没做完"③ 有无遗漏要求 |
| 长消息锚点 | 原始问题超长(转储型粘贴)时拆"开头/结尾"两段,优先以结尾最后提出的请求为核对目标,开头若含完整需求清单也一并核对 |
| 解析回退 | JSON → 旧行格式 → 保守(不判定、不误触发修复,原文展示给用户) |
修复协议(Fix Protocol)
| 要素 | 说明 |
|---|---|
| 触发条件 | 复查发现具体问题(problems 非空) |
| 执行方式 | agent.steer 注入一条真实用户消息 → 模型在真实会话步骤中修复(完整历史 + 工具权限) |
| 输入 | 问题清单 + 历轮回答全文(最新轮完整、较老轮次截断,总量 12000 字预算省 token)+ 原始问题 |
| 输出要求 | ① 解决全部问题并重新核对所有要求 ② 本轮新增/修改内容标 【复查A{k}补充】 ③ 原本正确内容保持原样 |
| 终止条件 | 复查判定"完成且无问题"(原因:满足要求);或达到 MAX_FIX_ROUNDS 上限(原因:达到上限) |
多轮状态机(跨 turn-stopping)
DoneCheck 在回合关闭边界上驱动循环:每次 agent.turn-stopping 触发时,插件检查"当前最新回答"→ 复查 → 需要修复则 steer 一条修复消息并记住进度(历轮回答、历轮问题清单、修复轮数);回合继续运行,模型产出 A_{k+1} 后再次进入 turn-stopping → 复查新回答 → 继续或收尾。每回合最多修复 4 轮,steer 后立即标记该回合,绝不重复触发、绝不死循环。
设计决策(为什么这么做)
| 设计决策 | 理由 |
|---|---|
| 复查是独立模型调用(不进会话日志) | 不污染模型上下文,后续轮次不受复查报告影响;报告只经 RPC 展示在卡片 |
| 复查看实际操作记录,而非只重答 | "自以为做完"发生在操作层——代码改没改到位只有看实际结果才知道 |
| 修复走真实会话步骤(steer) | 正式消息、正确落日志、可被后续对话引用;模型保留工具权限,能真动手 |
| 每回合最多 4 轮 | 成本上限明确;最后一轮提示"务必一次解决",把预算用在刀刃上 |
| JSON 判定契约 + 保守解析 | 判定确定可机读;解析失败宁可不修,绝不凭猜测触发修复 |
| 报告经 turnTail 卡片展示 | 复查结论留在对话流中,用户随时可展开看原文;报告为内存态,刷新丢失(回答本身不受影响) |
| 触发门槛(≥60 字或含代码) | 短回复/闲聊不复查,成本可控 |
| 判官路由可配置 | 默认同模型(零成本);配置更强模型可突破"同模型盲区共享"限制 |
架构总览
┌─ Host 半段(host.js)────────────────────────────┐
│ agent/turn-stopping 监听(回合关闭前) │
│ → 取回答/问题/操作记录 │
│ → llm.stream 复查(JSON 契约) │
│ → agent.steer 修复轮(真实步骤,最多 4 轮) │
│ → 报告存入内存 Map(上限 200 条) │
│ harness.handle('doublecheck/report') 提供 RPC │
└──────────────────────────────────────────────────┘
│ host.call(package-private RPC)
┌─ Client 半段(client.js)────────────────────────┐
│ conversation.chat.turnTail chain 条目(priority -1)│
│ → select 同步判断回合有收尾文本回答 │
│ → 拉取报告 → 渲染复查卡片(判定/轮数/原因/清单/原文)│
│ → 回合产生文件时顺带渲染文件行(不丢功能) │
└──────────────────────────────────────────────────┘
用到的 DSH 扩展点:agent/turn-stopping(serial 事件)、llm.stream(模型调用)、agent.steer(真实步骤注入)、harness.handle/host.call(Client-Host RPC)、conversation.chat.turnTail(对话尾部 Slot)、会话事件日志(读取工具调用/结果记录)。
兜底路径(安全网)
| 场景 | 兜底行为 |
|---|---|
| 复查调用失败(API 错误/网络) | 捕获 → 不触发修复,回合正常关闭,卡片"无法判定" |
| 复查输出无法解析 | 三级回退(JSON → 行格式 → 保守),不误触发修复,原文展示 |
| 修复轮被取消/异常 | 状态机保留,回合安全关闭 |
| 用户中断回合(signal abort) | 立即中止复查/修复,干净退出 |
| 短回复/无文本回答 | 跳过(≥60 字或含代码才触发) |
| 判官路由配置无效 | 回退同模型 + 警告日志 |
| llm 服务/请求头缺失 | 直接跳过 |
| 会话销毁 | 清理全部内存状态(reports/turnState) |
| 报告超过 200 条 | 淘汰最旧 |
| 长消息问题锚点 | 拆"开头/结尾"两段,优先以结尾最后提出的请求为核对目标 |
原则:宁可不动,不乱动——任何失败都不抛错、不干扰回合、不误触发修复。
效果示意(复查卡片)
自动复查 复查温度 0.2 · 修复 1/4 轮 复查发现:未完成
A1 未完成 → A2 完成
结束原因:满足要求
已生成修订后的最终回答(见上方)
标记说明:第 k 轮修复新增/修改的内容标【复查Ak补充】
最近一轮复查发现的问题:
▶ 查看详情
配置(host.js 顶部常量)
| 常量 | 默认 | 说明 |
|---|---|---|
REVIEW_TEMPERATURE | 0.2 | 复查温度(要严格,不要创造性) |
MAX_FIX_ROUNDS | 4 | 最大修复轮数(A2~A5) |
TRIGGER_MIN_CHARS | 60 | 触发门槛:回答长度下限(含代码块时不受限) |
JUDGE_PROVIDER / JUDGE_MODEL | 空 | 独立判官路由;留空 = 用回答模型自己当判官 |
MAX_ANSWER_CHARS | 8000 | 复查输入中回答全文上限 |
MAX_TOOL_LOG_CHARS | 8000 | 复查输入中操作记录上限 |
安装与使用
DoneCheck 是 DSH(DeepSeek Harness)的动态 Cordis 插件:
- 在 DSH 会话中调用
cordis_define:plugin.kind = "new",idPrefix任意 3-6 位小写字母; code.host=host.js文件内容,code.client=client.js文件内容;cordis_run激活(Client 半段需授权);- 之后每次回答自动复查,无需任何手动操作。
注意:这是动态插件代码(
cordis_define的函数体),不是独立可运行的 npm 包。若要以包形式分发,需按 DSH 插件包规范(packages/下的 cordis 插件)改装——纯运行时逻辑可直接复用。
开发规范
本仓库采用 Conventional Commits + 轻量 Git Flow(详见 CONTRIBUTING.md):
- 提交信息格式
<type>(<scope>): <subject>,由.githooks/commit-msg强制校验; - 克隆后启用钩子:
git config core.hooksPath .githooks(commit-msg 格式校验 + pre-commit 语法检查); - 分支:
main常绿 +feature/*、bugfix/*、hotfix/v<版本>-*; - 版本:语义化版本,当前发布
v1.0.0。