验证门 本节摘要:Agent 不被允许给自己的工作打「完成」。验证门(verification gate)读范围契约、反馈日志、规则报告、diff,回答一个问题:这个任务真的完成了吗?门说否,任务就没完成——无论聊天怎么说。诊断很经典,Agent 太容易宣布成功,三种失败形状主导:「看起来不错」(模型读自己的 diff 判它对)、「测试通过」(说得很自信,却无测试真跑的记录)、「验收达标」(验收标准被松到意为「任何像完成的东西」)。工作台的修法是一道验证门,读 Agent 已产出的工件,做裁决:门是确定性的,在版本控制里,接进 CI,Agent 贿赂不了它。
本节摘要:Agent 不被允许给自己的工作打「完成」。验证门(verification gate)读范围契约、反馈日志、规则报告、diff,回答一个问题:这个任务真的完成了吗?门说否,任务就没完成——无论聊天怎么说。诊断很经典,Agent 太容易宣布成功,三种失败形状主导:「看起来不错」(模型读自己的 diff 判它对)、「测试通过」(说得很自信,却无测试真跑的记录)、「验收达标」(验收标准被松到意为「任何像完成的东西」)。工作台的修法是一道验证门,读 Agent 已产出的工件,做裁决:门是确定性的,在版本控制里,接进 CI,Agent 贿赂不了它。本节定义门检查什么(全部验收命令跑了且退出零、范围无禁用写、无越界写、全部 block 规则通过、无 null 退出码、动过的文件匹配 allowed),区分
warn(注释裁决)与block(阻止passed: true),并立铁律门必须确定性而非概率性——同样的工件集每次产出同样裁决,无 LLM judge(LLM judge 属审查者第 39 节,那里目标是定性评估而非状态)。门发一份报告、一条路径(outputs/verification/<task_id>.json,CI 消费同一路径,多门多路径会分叉真相之源),且拒绝无例外——block 发现不能被 Agent 覆盖,只能被人覆盖,带记录的override_reason与overridden_by用户 id,覆盖是签名变更而非 Agent 决定。本节用标准库实现verify(task_id, artifacts)纯函数,跑三个场景(干净通过、范围蔓延、缺验收),并给五条把门从「又一个 lint」抬到「决定边」的生产模式:纵深防御(pre-commit→CI→pre-tool authz→pre-merge 多层)、确定性检查为主模型 judge 只管细微(Anthropic Hybrid Norm:可验证奖励答「解了吗」、LLM 量表答「可读/安全/合规吗」)、签名覆盖日志非 Slack 线程、覆盖率底线为一等检查(防 Agent 偷删失败测试让报告保持绿)、--strict模式把 warn 升 block(发布分支/阻断 PR/事故分诊时)。读完本节,你能把规则、范围、反馈收口成一道 Agent 无法绕过的闸。
对应原课程:Phase 14 · Lesson 38 ·
verification-gates(原英文phases/14-agent-engineering/38-verification-gates/docs/en.md)。前置:第 33 节(规则)、第 36 节(范围)、第 37 节(反馈)。
阅读完本节,你应当能够:
verification_report.json。Agent 太容易宣布成功。三种失败形状主导:
工作台的修法是一道验证门,读 Agent 已产出的工件,做裁决。门确定性。门在版本控制里。门接进 CI。Agent 贿赂不了它。
这是第 26 节「成功幻觉」的终极防线,也是第 31~37 节所有面的收口——指令、状态、范围、反馈全部汇进这一道闸,它给出一个不可贿赂的「完成」判定。
| 检查 | 源工件 | 严重度 |
|---|---|---|
| 全部验收命令跑了 | feedback_record.jsonl |
block |
| 全部验收命令退出零 | feedback_record.jsonl |
block |
| 范围检查无禁用写 | scope_report.json |
block |
| 范围检查无越界写 | scope_report.json |
block 或 warn |
| 全部 block 严重度规则通过 | rule_report.json |
block |
| 反馈里无 null 退出码 | feedback_record.jsonl |
block |
动过的文件匹配 scope.allowed_files |
两者 | warn |
warn 发现注释裁决;block 发现阻止 passed: true。
门必须对同样的工件集每次产出同样裁决。无 LLM judge。LLM judge 属审查者那侧(第 39 节),那里目标是定性评估,而非状态。
门每个任务收尾发一份 verification_report.json,写在 outputs/verification/<task_id>.json。CI 消费同一路径。多门多路径会分叉真相之源。
block 严重度发现不能被 Agent 覆盖。它们只能被人覆盖,带记录的 override_reason 与 overridden_by 用户 id。覆盖是签名变更,不是 Agent 决定。
原课程 code/main.py 实现:
verify(task_id, artifacts) -> VerdictReport 纯函数。def load_artifacts(task_id): return { "diff": git_diff_vs_main(), "scope_report": json.load(open(f"outputs/scope/{task_id}.json")), "rule_report": json.load(open("rule_report.json")), "feedback": read_jsonl("feedback_record.jsonl"), }
def verify(task_id, artifacts): findings = [] # 1) 验收命令跑了且退出零 for cmd in artifacts["scope_report"]["acceptance_criteria"]: rec = find_feedback(artifacts["feedback"], cmd) if rec is None: findings.append(block("ACCEPT-RAN", f"{cmd} 未跑")) elif rec["exit_code"] is None: findings.append(block("ACCEPT-NULL", f"{cmd} 无退出码")) elif rec["exit_code"] != 0: findings.append(block("ACCEPT-FAIL", f"{cmd} 退出 {rec['exit_code']}")) # 2) 范围 if artifacts["scope_report"]["forbidden_hit"]: findings.append(block("SCOPE-FORBIDDEN", str(...))) if artifacts["scope_report"]["off_scope"]: findings.append(warn("SCOPE-OFF", str(...))) # 3) block 规则 for r in artifacts["rule_report"]: if r["severity"] == "block" and not r["passed"]: findings.append(block("RULE-" + r["slug"])) passed = not any(f.severity == "block" for f in findings) return VerdictReport(task_id, passed, findings)
def emit(verdict): path = f"outputs/verification/{verdict.task_id}.json" atomic_write(path, json.dumps(asdict(verdict), indent=2)) # 一份报告一条路径 return verdict.passed # CI: 非 True 即拒合并
运行 python3 code/main.py 会输出三份裁决报告,各存脚本旁。
四加一条模式,把门从「又一个 lint」抬到「决定边」。
纵深防御,非单门。 pre-commit 钩子 → CI 状态检查 → pre-tool authz 钩子 → pre-merge 门。每层确定性,所以一层失败被下一层抓。microservices.io 2026-03 的 playbook 说得明白:pre-commit 钩子不可绕过,因为与模型侧技能不同,它不依赖 Agent 听指令。验证门坐在 CI / pre-merge 层。
确定性检查防御,模型 judge 只管细微。 Anthropic 2026 Hybrid Norm 配对:可验证奖励(单元测试、模式检查、退出码)答「代码解决问题了吗?」——LLM 量表答「代码可读、安全、合规吗?」。门跑第一类;审查者(第 39 节)跑第二类。混了会塌掉信号。
签名覆盖日志,非 Slack 线程。 每次覆盖在 outputs/verification/overrides.jsonl 发一行:时间戳、发现码、原因、签名用户、当前 HEAD 提交。运行时拒绝任何缺签名的覆盖;审计轨迹 git 跟踪。这是覆盖策略与覆盖戏剧之间的线。
覆盖率底线为一等检查。 一个 coverage_report.json 喂一个 coverage_floor(默认 80%)检查。门在测得覆盖低于底线、或低于上次合并的底线超 1 个百分点时失败。没这检查,Agent 会偷删失败的测试,验证报告保持绿。
--strict 模式把 warn 升 block。 对发布分支、阻断 PR、或事故后分诊,--strict 让每条警告硬失败。标志按分支 opt-in;非全局默认,因为「全严格」腐蚀日常流。
💡 设计要点:验证门是工作台流的决定边——其他所有面都在它上游。它的力量来自三件事:确定性(同工件同裁决,LLM 贿赂不了)、单一真相路径(一份报告 CI 与人同读)、签名覆盖(block 只能被人带理由覆盖,是审计变更非 Agent 决定)。这与第 31 节「验证是 fails closed 的函数」、第 26 节「重探查真实状态查成功幻觉」完全合流——门就是把这两条纪律落成一道不可绕过的闸。
| 运行时 | 门如何接 |
|---|---|
| CI 步骤 | verify_agent job 对 Agent 最终工件跑门;合并保护无 passed: true 即拒 |
| Pre-handoff 钩子 | Agent 运行时在生成交接文档前调门;无绿裁决即无交接 |
| 手动分诊 | 操作员在 Agent 声称成功而人怀疑时读报告 |
门是工作台流的决定边。其他所有面都在它上游。
原课程 outputs/skill-verification-gate.md:把门接进特定项目——哪些验收命令喂它、哪些规则 block 严重度、哪些越界写被容忍、覆盖审计日志怎么存。
coverage_floor 检查:测试命令必须产至少 80% 覆盖报告。决定哪个工件带底线。--strict 模式,把每条 warn 升 block。记录 strict 模式是正确默认的情况。time_since_last_human_touch 检查:人击键 60 秒内编辑的文件免越界标记。outputs/verification/<task_id>.json,CI 与人同读,多门多路径分叉真相。下一节,我们给通过验证门的产物加一道定性关卡——审查者 Agent:一个独立工人,用不同角色(只读建造者产物、只写审查报告),用 LLM 量表回答「可读、安全、合规吗」,把「建造者批自己作业」变成「同行评审」。