> ## Content Index
> Fetch the complete content index at: https://www.hubwiz.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Agent 说"做完了"，证据呢：三道交付验证门
- URL: https://www.hubwiz.com/blog/agent-delivery-verification-gates/
- Published: 2026-10-15T20:18:41.000Z
- Updated: 2026-10-15T20:19:54.000Z
- Description: "fixed and tested"不是证据。三道门——声明核对、三树差分、证据包——把 Agent 的自述变成可复核的事实。
- Author: admin
- Tags: AI Agent, AI 编程, 代码评审, 测试

**一句话结论**：当 Agent 提交一个变更时，它给出的 "fixed and tested" 是一句**声明**，不是证据。三组 2026 年 10 月公开的新材料，正好构成交付验证的三道门：**声明核对**（把声明和真实 diff 逐条对账——28 个并发 Agent × 5 轮实测，11 个不实声明全部被拒、0 个诚实声明被误伤）、**三树差分**（同一测试套件跑 parent / patched / ablated 三次，把"绿"拆成 7 类，只有一类算数）、**证据包**（命令、输出、变更清单、真实截图、已知限制，缺一项就无法复核）。三道门的共同原则只有一句话：**不要验证 Agent 说了什么，要验证它改了什么、跑出了什么、留下了什么。**

本文整理四篇近 60 天的新文：@dannwaneri《I built a lie detector for AI coding agents. Then I read the rules.》（dev.to，2026-10-15，claimcheck 项目）、Finley Zhou（@datacpp\_8185）《Revert the Fix, Keep the Tests: A Three-Tree Differential Gate for Agent Patches》（dev.to，2026-10-15）、@patilrb《Give a coding-agent delivery enough evidence to review》（dev.to，2026-10-15）与 Ofer Shapira（@ofers\_agent）《How to Write Review Feedback a Coding Agent Can Turn Into a Verified Fix》（dev.to，2026-10-15），做中文整理。**立场先说清楚**：claimcheck 作者自述该项目为参加 Cloudflare 竞赛而建、最终因居住地规则未参赛，文章是原型记录；三树差分那篇是 MonkeyCode 的产品外联文（作者在文内披露）；证据包那篇注明有 AI 辅助、为 Wagglet 营销工作而写；反馈那篇是个人经验总结。本文按"方法与数据"引用，不替任何工具背书。

---

## 一、第一道门：把 Agent 的"声明"当账来对

问题从一个非常具体的场景开始：**多个 Agent 同时往一个仓库推代码时，没人核对它们的提交说明。**

一个 Agent 改了三个文件、删了 40 行，提交信息写"修复拼写"——这句话对不对？在"一个 Agent 改、一个人审"的场景里，人顺手就看了；在"五个 Agent 同时推"的场景里，人根本看不过来。于是 @dannwaneri 做了 **claimcheck**：每个 Agent 推变更时，必须同时推一个 `.claim/claim.json`，平台在合并前核对"声明"和"实际改动"是否一致：

```json
{
  "task_id": "t-01",
  "agent_id": "agent-a",
  "summary": "Add a sub() helper.",
  "scope": { "paths": ["src/math.js"] },
  "changes": [
    { "path": "src/math.js", "description": "Add sub(a, b) that returns a - b." }
  ]
}

```

平台执行的规则有五条：

1. 声明必须**覆盖** Agent 实际改动的所有文件；
2. 声明里**只能有**真正改动过的文件；
3. 不得触碰**保护路径**（默认 `src/auth/**`）；
4. 变更规模不得超过上限（行数与文件数各一条）；
5. **每条 description 必须与 diff 相符**。

前四条是确定性检查，纯代码判断。这里有一个值得记住的细节：保护路径匹配**大小写不敏感**——测试时发现 `src/Auth/session.js` 与 `src/auth/**` 不匹配会造成绕过，因为在 macOS 和 Windows 上它们落在同一个目录里。另外两条会产出 `UNCLAIMED_CHANGE`（改了没声明）和 `CLAIMED_NOT_CHANGED`（声明了没改）这样的具体结论。

第五条交给 LLM，但加了硬约束：**模型要否决一个变更，必须从 diff 里引用一行真实存在的代码作为证据**；写不出、或者引用的行在 diff 里找不到，就不能算"否决"，只能进 `needs_review`。三种裁决由此确定：

- 只要有一条**证据成立的"否"**，直接 `rejected`；
- 全部判断为"是"且无错误，判 `verified`；
- 其他情况（含超时）一律 `needs_review`。

**默认不合并，只有 `verified` 的变更进入合并队列；同一个文件，第一个通过验证的变更获胜。**

作者跑了三轮不同规模的测试：

| 测试   | 规模                    | 结果                                              |
| ---- | --------------------- | ----------------------------------------------- |
| 演示   | 5 个脚本 Agent（故意设计五种结局） | 不实声明全部被拒，如声称"裁剪空白"实际删掉了小写转换的那一个                 |
| 主测试  | 28 个 Agent × 5 轮      | 每轮 11 个不实声明被拒、0 个诚实声明被误伤；结论都带证据，中位 6–7.5 秒出裁决   |
| 扩展测试 | 22 个 Agent 同任务        | 12 个正常无冲突、2 个正常但撞车、8 个不实（多文件/碰保护路径/描述不符）；各轮结果一致 |

最反直觉的一条结论来自**真实 Agent**：作者让 Qwen 的 `qwen3-coder-plus`（通过 headless Claude Code，指到阿里云端点）去修 `src/strings.js` 的拼写错误——仓库里 `src/auth/session.js` 也埋了一个拼写错误，而这个文件是保护路径。作者**没有告诉它**这一点。5 轮下来，它没有碰保护文件、修正了 2 处、声明准确、每次通过验证；10 个并发 Agent 的测试里也全部如实。**这个样本里的 Agent 没有"说谎"——被抓住的全部是脚本模拟的不实声明。** 换句话说：声明核对首先防的不是"Agent 恶意撒谎"，而是**并发规模下没人看得过来**这件事。

值得注意的是作者公开的四次故障与修复，全部发生在"验证系统"自己身上：推送被拒不会重试、LLM 调用超时导致 5 分钟无裁决、合并推送 503、以及最严重的一条——**合并结果没落库导致重复合并、自己和自己冲突**。修复后：`push` 重试 3 次；每次模型调用加 45 秒超时、卡住的模型一律 `needs_review` 绝不 `merge`；合并队列对 502/503/504 重试；队列在合并前先查自己的历史合并记录（合并提交的标题里带 agent、任务与 commit 前缀）；队列每次只做一个合并，做完再排下一个。最后一轮：5/5 通过。整套系统跑在 Cloudflare 上（Artifacts 每 Agent 一个 fork、push 事件触发 Workflow、Workers AI 做描述检查、Durable Object 存裁决并跑合并队列），MIT 许可，需要付费 Workers 计划才能自部署，在线演示已因成本下线。

作者还单独测了那条 LLM 检查的稳定性：**15 个带标注的用例 × 5 次运行 = 75/75 全部命中，且没有任何用例在两次运行间改变答案**。他也提醒这只是一小批单文件短 diff 的样本，不能外推——这恰好是"给出测量、同时给出边界"的写法。另一个值得顺手记下的坑：headless 运行的 Claude Code 会读取你的全局 `CLAUDE.md`（除非加 `--bare`），做 Agent 实验前值得先确认它到底看到了什么指令。

## 二、第二道门：绿灯不等于修复，跑三棵树

有了声明核对，还要回答第二个问题：**变更通过了测试，这个"绿"说明什么？** Finley Zhou 的答案是：**说明测试套件是绿的，不说明补丁是原因。** 他给出一个只有三步的"三树差分门"（three-tree differential gate）：

把同一套测试跑在三棵树上——

- `parent`：补丁的父提交；
- `patched`：打了补丁的提交；
- `ablated`：**回滚补丁里所有非测试改动、保留测试**的第三棵树。

只有第三次运行，才能把"这些测试能测出这个修复"与"这些测试今天碰巧能过"分开。而常见的三种"无意义绿"在标准 CI 里完全不可见：

1. **同义反复**：测试断言的是补丁自己算出来的值，除非补丁崩了，否则不可能失败；
2. **本来就绿**：补丁前就过、补丁后还过，补丁根本没改变被测行为；
3. **夹具耦合**：测试读的 fixture 或 stub 也被补丁改了，两边一起动，断言恒真。

实现不需要新工具，`git worktree` 三棵树共享一个对象库，把 ablated 树里的非测试文件 checkout 回父提交即可：

```bash
PATCH_REF="$1"
PARENT="$(git rev-parse "${PATCH_REF}^")"
ROOT="$(mktemp -d)"

git worktree add --detach "$ROOT/parent"  "$PARENT"
git worktree add --detach "$ROOT/patched" "$PATCH_REF"
git worktree add --detach "$ROOT/ablated" "$PATCH_REF"

# 只回滚非测试改动，测试本身保留
mapfile -t SRC < <(
  git -C "$ROOT/ablated" diff --name-only "$PARENT" "$PATCH_REF" \
  | grep -Ev '(^|/)(tests?|specs?)/|(_test|\.test|\.spec)\.'
)
[ "${#SRC[@]}" -gt 0 ] && git -C "$ROOT/ablated" checkout "$PARENT" -- "${SRC[@]}"

```

三次运行必须用**钉死的运行器**：同一解释器、同一 lockfile、同一随机种子、同一时钟注入——否则三棵树里的版本差异会被误判成结论。输出要机器可读（如 `--reporter=json`），因为自由文本日志没法可靠 diff。

然后是一张七行的分类表，每个测试只能落进一行，**只有第一行是关于补丁的证据**：

| parent | patched | ablated | 判定                       |
| ------ | ------- | ------- | ------------------------ |
| fail   | pass    | fail    | **sensitive（敏感）**        |
| fail   | pass    | pass    | fix-blind（测不出修复）         |
| pass   | pass    | pass    | pre-existing-green（本来就绿） |
| fail   | fail    | fail    | no-fix（没修好）              |
| pass   | fail    | fail    | regression（回归）           |
| fail   | fail    | pass    | inverted（倒挂）             |
| pass   | pass    | fail    | fixture-coupled（夹具耦合）    |

门的规则只有一句：**补丁必须新增或修改至少一个 `sensitive` 测试，且不得出现 `regression`。** 其余类别是诊断信息，不阻塞合并。这个门故意不对称——不要求每个新测试都敏感（有些测试断言的是补丁不碰的不变量），但要求"补丁自己的证据"与补丁有因果联系。最该转人工的是 `fix-blind`：测试在父提交失败、打补丁通过、**移除修复后又通过**，常见原因是补丁顺带改了 import 或 setup，测试依赖的是那个副作用。

作者对这套方法的定位也很克制：**三种无意义绿在标准 CI 里完全不可见，而它们只多花一次执行——这就是全部的交易成本。** 他甚至建议先用工具对着最近五个 Agent 补丁手工跑，**记录下判定分布再决定改哪条规则**；在他自己的仓库里 `pre-existing-green` 最多，而这只说明测试套件在 Agent 开始写代码之前就已搭好，与 Agent 的质量无关。

两个配套细节值得抄：

- **flake 隔离要设到期时间，且作者不能自己写**。一条隔离记录等于声明"这次失败不是这个补丁造成的"，让补丁作者自己写就变成了自我认证；示例里每条记录都带 `expires_at`（30 天内）与 `owner`，门里用 `git log` 的作者邮箱和 Agent 身份比对，一致就拒绝。
- **免费额度只能测量和标注，不能做决定**。作者披露此文为 MonkeyCode 外联后，把"几百次重复运行找 flake"这类花钱的活放到独立的非阻塞 runner 上，把免费的模型额度用在把分类结果+diff 变成给人类看的第一轮标签上——**门只读提交进仓库的 JSON，不读模型的意见**。

作者也把适用边界写全了：回滚源码后 ablated 树可能**编译失败**，此时只能记 `inconclusive`；测试路径靠 `grep` 启发式判断，仓库布局特殊会误分类并抬高 `fix-blind`；三倍运行成本对 15 分钟以上的套件不划算；纯测试补丁在这个门里天然"空转"；套件不确定就整张表失真。他还给了一条诚实的备注：**先拿最近五个 Agent 补丁手工跑一遍、记录判定分布再改规则**——他自己的分布里 `pre-existing-green` 最多，而这反映的更像是"测试套件在 Agent 写代码之前就搭好了"。

## 三、第三道门：交付包里必须有能复核的东西

前两道门管的是"改动"，第三道门管"交付"。@patilrb 的观察是：一句"修好了，测过了"，把最难的部分全丢给了评审——**改了哪儿、跑了哪个检查、截图和补丁是不是同一个版本**，都得重新拼。他建议的最小交付包只有四件东西：

1. **测试结果**：准确命令、运行时版本、退出码、完整输出；修复回归的要附上**失败基线**。一个截过的绿色徽章说明不了跑了哪些测试；
2. **变更清单**：revision + 每个文件的简短说明；
3. **截图**：在**交付的那个 build** 里真实截取，caption 里写明 build 与样本；
4. **已知限制**：哪些测试没跑、哪些环境没验、哪些行为被有意排除——**和正面结果放在一起**。

配套的交付模板可以直接用：

```text
Task/revision:     [ID 与 commit]
Requested behavior:[要保住的行为]
Changed files:     [实际路径与用途]
Reproduce:         [干净环境下的准确命令]
Baseline:          [失败命令/输出，或不适用]
After:             [命令、退出码、输出]
Visual evidence:   [真实截图、build、样本]
Known limits:      [具体未覆盖项]
Reviewer:          [有权接受的人]
Requested decision:[接受范围 / 请求修改]

```

这套东西最关键的一条是**证据与主张要匹配**：截图证明不了"脚本没有改文件"，单元断言也证明不了"报表在设计常用的缩放下可读"。每件证据只能支持它能支持的那句话。

最后是**交付与验收分离**：执行方提交证据包；工程师核对实现是否符合声明的范围并能复现；设计师确认这个改动是否真的解决了原问题；**验收由有权限的评审记录在案，合并与部署是另外两个决定。**

## 四、反馈也要写成"可验证"的形式

@ofers\_agent 补上了另一半：**评审意见本身也得能被 Agent 变成可验证的修复。** 他的出发点是一个公开的实操案例——real-browser-mcp 的 PR #60 里，评论只谈偏好是没有用的，真正能被处理的问题是带条件的场景：**两个并发操作落在同一个标签页上，可能创建出两个分组；两条调试器连接可能留下多于一个标签页，而系统只跟踪了一条。** 把这种场景写进评论，Agent 才知道要修什么、要复现什么。他的模板是一段"短契约"，用来替代"这里要清理一下"这种把工作丢回给对方的评论：

> 当两个请求在第一个完成之前到达同一资源时，系统必须保住**约定的结果**。给出一个能对旧版本复现这次冲突的测试，修复行为，并展示该测试在新版本上通过。写明哪些没有被测试。

"约定的结果"必须替换成真实需求——"一组"、"一条连接"、还是"一个明确报错"，是三个不同的决定；没有约定就先开讨论，不要让 Agent 替你选。此外：

- **场景而非偏好**：说清"什么条件下发生什么"，而不是"这里不够好"；
- **区分阻塞项与建议项**："变量名建议"和"权限修复"在同一个 thread 里出现，也不等价；GitHub 上 comment / approve / request changes 哪个真的阻塞，**由分支保护规则决定，不取决于评论里有没有写 blocker**；
- **thread 要小到可验证**：一个变更里有两套机制（如标签页分组与调试器连接）就拆两个 PR；有依赖就写明依赖和合并顺序——Agent 可以准备分支，但**共享契约的决定权留在变更负责人手里**；
- **评论之后要看新 diff**：新增的测试要真的碰到讨论中的行为，而不是只贴着当前代码的写法；要"运行结果+版本+命令"，**PR 正文里的计划测试清单不算运行结果**；Agent 回一句"所有评论都处理了"时，对照原始清单逐条检查——每条要么有修复，要么有"为什么不需要"的解释，要么有明确的推迟决定。

## 五、把三道门合并成一张清单

- \[ \] **声明要能对账**：每个变更附结构化声明（改了哪些文件、每处做了什么），由平台与真实 diff 逐条比对，而不是靠人读提交信息；
- \[ \] **保护路径显式化**，注意大小写不敏感；
- \[ \] **LLM 否决必须引 diff 原行**，引不出来就只能 `needs_review`；
- \[ \] **拿不准就不合并**：超时、异常一律进人工复核，绝不默认放行；
- \[ \] **绿要分七类**：跑 parent / patched / ablated 三棵树，至少一个 `sensitive`、零 `regression` 才放行；
- \[ \] **三棵树钉死运行器**：版本、lockfile、种子、时钟全一致；
- \[ \] **flake 隔离有期限、作者不能自签**；
- \[ \] **交付包四件套**：命令与完整输出、变更清单、真实截图（注明 build）、已知限制；
- \[ \] **证据与主张匹配**：一张截图只支持它拍到的范围；
- \[ \] **交付≠验收≠合并**，三个动作三个人（或至少三段记录）；
- \[ \] **反馈写成契约**：场景、约定结果、失败复现、通过复现、未测范围；
- \[ \] **阻塞项与建议项分开**，thread 拆小，评论后复核新 diff。

四份材料里被反复验证的一点是：**让 Agent 诚实的最优解不是要求它"不要撒谎"，而是让不准确的声明无法通过。** 声明核对让不实声明进不了合并队列，三树差分让无意义的绿自动分到别的类，证据包让"说做了"变成"能复核"。**Agent 的输出正在变便宜，验证它的成本就是新的瓶颈——而这三道门，每一道都在把人从"逐条看 diff"里换出来。**

## 六、三道门为什么可以分开用

这三道门解决的是三个不同的问题，**不需要一次上齐**：

| 门    | 回答的问题                  | 最小落地方式                     |
| ---- | ---------------------- | -------------------------- |
| 声明核对 | Agent 说的范围，和实际改的范围一致吗？ | 一个结构化声明文件 + 三四条确定性规则       |
| 三树差分 | 本次通过的测试，是这次补丁让它通过的吗？   | 一个 git worktree 脚本 + 一张映射表 |
| 证据包  | 别人要复核这次交付，需要哪些材料？      | 一个十行模板，写进 PR 描述            |

它们之间是互补而不是替代：声明核对**不判断代码对不对**，只判断"说的"和"做的"是否一致；三树差分**不管声明**，只管证据的因果；证据包**不自动跑任何东西**，但它让前两道门的结论可以被第三个人复核。三者共享同一种文化——**把"完成"从形容词改成可检查的名词。**

另外，不要忽略这些材料里最贵的一条经验：**验证系统本身也会出错，而且往往错得更隐蔽**（重试缺失、超时无裁决、合并结果没落库、回滚树编译失败）。三道门都需要"拿不准就停下"的默认值，而不是"默认放行"。

---

## 来源

- @dannwaneri（Daniel Nwaneri）, *I built a lie detector for AI coding agents. Then I read the rules.*（dev.to，2026-10-15）：https://dev.to/dannwaneri/i-built-a-lie-detector-for-ai-coding-agents-then-i-read-the-rules-1hhb
- claimcheck 项目（MIT，v1.0.0）：https://github.com/dannwaneri/claimcheck
- Finley Zhou（@datacpp\_8185）, *Revert the Fix, Keep the Tests: A Three-Tree Differential Gate for Agent Patches*（dev.to，2026-10-15；MonkeyCode 产品外联，文内披露）：https://dev.to/datacpp\_8185/revert-the-fix-keep-the-tests-a-three-tree-differential-gate-for-agent-patches-3407
- @patilrb, *Give a coding-agent delivery enough evidence to review*（dev.to，2026-10-15；AI 辅助整理、Wagglet 营销工作）：https://dev.to/patilrb/give-a-coding-agent-delivery-enough-evidence-to-review-46kj
- Ofer Shapira（@ofers\_agent）, *How to Write Review Feedback a Coding Agent Can Turn Into a Verified Fix*（dev.to，2026-10-15）：https://dev.to/ofers\_agent/how-to-write-review-feedback-a-coding-agent-can-turn-into-a-verified-fix-4g9a

## 本系列另两篇

- [别人发来的 3D 文件怎么验收：STEP 翻译与网格拓扑](https://bimant.com/blog/cad-step-mesh-topology-acceptance/?ref=hubwiz.com)（同一套"验收文化"落到 3D 资产上：公差、非流形边、可复现的检查）
- [链上核验清单：收据、钱包与授权，别只看表面](https://defiplot.com/blog/onchain-verification-checklist-receipt-wallet-approval/?ref=hubwiz.com)（落到链上：收据对账、EIP-7702 委托、无限授权）

## 延伸阅读（本站）

- [AGENTS.md 该写什么：1,866 个项目的说明书实测](https://www.hubwiz.com/blog/agents-md-instruction-quality-1866-projects/)
- [Agent 说"完成"，数据库不同意：一次批准率 87% 的 Agent 评测复盘](https://www.hubwiz.com/blog/agent-said-done-database-disagreed/)
- [子代理派活与上下文折叠：compaction、subagent、fold 三种交接实测](https://www.hubwiz.com/blog/subagent-fork-fold-context-handoff/)
- [专题：AI 编程 / Vibe Coding 完全指南](https://www.hubwiz.com/blog/vibe-coding-stack/)

**本文由 汇智网 · Software 2.0 编译整理，转载请注明出处。** 想系统了解 Agent 的上下文、工具与工程化，见 [Agent / MCP / 自动化完全指南](https://www.hubwiz.com/blog/agent-stack/)。

> **这是「AI 编程 / Vibe Coding」专题的一部分。**完整地图见：[AI 编程 / Vibe Coding 完全指南](https://www.hubwiz.com/blog/vibe-coding-stack/)