对抗式审查方法论
对抗式审查方法论
学习目标
读完本章后,你将能够:
- 理解对抗式审查与事后审查的本质区别
- 应用 CLAIM → EXTRACT → DOUBT → RECONCILE → STOP 五步循环
- 设计对抗性提示,引导审查者发现问题而非验证结论
- 实现跨模型升级(Cross-Model Escalation)机制
- 识别”Doubt Theater”反模式并正确停止循环
为什么需要对抗式审查
AI Agent 在长会话中会积累上下文,这些上下文会将假设悄悄转化为”事实”而无人察觉。一个自信的答案不等于一个正确的答案。对抗式审查的核心理念是:在非平凡决策做出后、成本变高之前,用一个带着”找错”心态的新审查者来交叉检验。
对抗式审查 ≠ 事后审查:
/review是对已完成工件的终审判断;对抗式审查是飞行中的逐决策审查——在方向还可以便宜地修正时就发现问题。
什么是”非平凡决策”
对抗式审查仅适用于非平凡决策,不是每个按键都要审查。以下任一条件满足即为非平凡:
| 条件 | 示例 |
|---|---|
| 引入或修改分支逻辑 | 新增 if/else 路径、switch 分支 |
| 跨越模块或服务边界 | 两个模块之间的调用契约变更 |
| 断言类型系统无法验证的属性 | 线程安全、幂等性、顺序保证、不变量 |
| 正确性依赖未来读者看不到的上下文 | 特定于当前会话的假设 |
| 影响范围不可逆 | 生产部署、数据迁移、公开 API 变更 |
不适用:机械操作(重命名、格式化)、明确的用户指令、读取代码、一行变更的明显正确性。
五步循环
CLAIM ──→ EXTRACT ──→ DOUBT ──→ RECONCILE ──→ STOP │ │ │ │ │ ▼ ▼ ▼ ▼ ▼命名决策 隔离工件 对抗审查 分类发现 有界停止Step 1: CLAIM — 命名决策
用 2-3 行写下你做了什么决定以及为什么重要:
CLAIM: "新的缓存层在规格描述的读重负载下是线程安全的。"WHY THIS MATTERS: 此处的竞态会损坏用户数据且 QA 难以检测。如果你无法用这么紧凑的篇幅写出 CLAIM,那你有的只是一个感觉,不是一个决策。在审查前先厘清它。
Step 2: EXTRACT — 隔离最小可审查单元
新上下文的审查者需要的是工件和契约,而不是你的推理过程:
- 代码:diff 或函数本身,不是整个文件
- 决策:3-5 句提议加上必须满足的约束
- 断言:CLAIM 加上支撑它的证据(与 Step 1 的 CLAIM 块分开)
关键规则:剥离你的推理。如果你把结论交给审查者,你得到的会是对你结论的验证,而不是独立判断。单元必须小到审查者一次阅读就能装进脑子——如果是 500 行的 PR,先分解。
Step 3: DOUBT — 用对抗性提示调用审查者
审查者的提示必须是对抗性的。框架决定答案:
对抗式审查。找出这个工件的问题。假设作者过于自信。寻找:- 未声明的假设- 未处理的边界情况- 隐藏的耦合或共享状态- 契约可能被违反的方式- 可能破坏的现有约定- 意外输入下的故障模式
不要验证。不要总结。找问题,或明确声明你经过彻底检查后找不到任何问题。
ARTIFACT: <粘贴工件>CONTRACT: <粘贴契约>关键约束:
- 只传递 ARTIFACT + CONTRACT,不要传递 CLAIM。把你的结论给审查者会偏向同意。
- 审查者必须独立判断工件是否满足契约。
- 对抗性提示优先于角色的默认响应格式——如果角色的默认输出是”优点+缺点”的平衡报告,但你需要的是”只找问题”的输出,必须粘贴对抗性提示来覆盖。
Step 4: RECONCILE — 分类发现
审查者的输出是数据,不是判决。你仍然是编排者。在分类前重新阅读工件文本——橡皮图章式接受审查者的发现和直接忽略它一样是失败模式。
按以下优先级顺序分类(第一个匹配的类别胜出):
| 优先级 | 类别 | 含义 |
|---|---|---|
| 1 | 合同误读 | 审查者标记问题是因为你提供的 CONTRACT 不清楚或不完整。先修复合同,下一轮重新分类。 |
| 2 | 有效 + 可行动 | 真实问题,需要修改工件。修改后重新循环。 |
| 3 | 有效权衡 | 问题真实存在,但修复成本大于接受成本。显式记录权衡,让用户看到。 |
| 4 | 噪声 | 审查者标记了在你拥有而审查者没有的上下文中实际正确的东西。记录下来,继续前进。 |
注意:新审查者可能因为缺少上下文而出错。不要因为它是”新鲜的”就盲从。重新阅读工件,分类,然后决定。
Step 5: STOP — 有界循环,不是无限递归
满足以下任一条件时停止:
- 下一轮只返回琐碎的或已经考虑过的发现
- 已完成 3 轮循环(向用户报告,不要独自继续第 4 轮)
- 用户明确说”发布它”
如果 3 轮后审查者仍然发现实质性问题,工件可能还没准备好。向用户报告——3 轮未解决本身就是一个关于工件质量的信息。
跨模型升级
单一模型的审查者与原始作者共享盲点——一个更冷门的、不同架构的模型能捕获这些盲点。
交互式会话:始终提供选项,禁止静默跳过。
升级流程:
- 单模型审查完成后,在 RECONCILE 之前暂停并询问用户
- 如果用户选择某个 CLI 工具:先验证工具在 PATH 中,再测试它能工作(
gemini --version),再确认确切调用方式 - 只传递 ARTIFACT + CONTRACT + 对抗性提示——不传递会话上下文,不传递 CLAIM
- 注意 Shell 转义:如果工件包含引号、
$(...)或反引号,优先使用 stdin 或 heredoc,而不是内联参数 - 将输出带入 Step 4(RECONCILE)
非交互式上下文(CI、定时任务、自主循环):跨模型被跳过,且必须在输出中宣布跳过。
关键安全属性:永远不要在未经用户明确授权的情况下自动调用外部 CLI。每次调用都是独立的授权——工件、提示、标志都会变化,每次都要重新确认。
反模式:Doubt Theater
可检查的信号:在 2 轮或更多轮循环中,审查者发现了实质性问题,但零个被分类为可行动的。
你在做验证,不是在做质疑。停止并升级。
与其他质量门控的关系
| 方法 | 定位 | 与对抗式审查的关系 |
|---|---|---|
| 多维度质量门控 | 检查 BUILD/TEST/LINT 等维度是否通过 | 对抗式审查补充这些自动化检查——它捕获那些测试无法覆盖的设计决策问题 |
| 证据驱动审查 | 要求证据新鲜度,防止假阳性 | 对抗式审查本身就是生成新鲜证据的过程——一轮审查就是一轮新鲜证据 |
| TDD 的 RED 步骤 | 失败的测试是一个证伪尝试 | 当 TDD 适用时,失败的测试就是行为 CLAIM 的质疑步骤 |
| 事后审查(/review) | 对已完成工件的终审 | 互补而非替代——对抗式审查在飞行中捕获问题,事后审查做最终判断 |
检查清单
应用对抗式审查后,确认:
- 每个非平凡决策(按上述定义)在确立前被显式命名为 CLAIM
- 每个非平凡工件至少经过一次新上下文审查(TDD 的 RED 步骤可满足行为 CLAIM)
- 审查者收到的是 ARTIFACT + CONTRACT,而非 CLAIM 或推理过程
- 审查者的提示是对抗性的(“找问题”),而非验证性的(“这样好吗?”)
- 发现被分类(不是橡皮图章式接受),按优先级顺序:合同误读 / 可行动 / 有效权衡 / 噪声
- 满足停止条件(琐碎发现、3 轮循环、用户覆盖)
- 在交互式模式下,跨模型被显式提供给用户(无论工件风险高低)
- 在非交互式模式下,跨模型被跳过且跳过被宣布