我之前做的 Ralph Loop 审核机制,有个隐藏问题。

v0.2 的流程只有「发现问题→修复→确认收敛」。第 4 篇提过,创造者如果认为审查者误判,可以在下一轮提供证据,由审查者重新评估——但那只是一句规则,不是正式协议。没人检验审核本身的质量。审查者可能标错问题严重等级。主代理可能盲目接受不合理建议。

今天我发布了 tdd-pipeline v0.3.0[1]。v0.3 的设计原则只有一条:审核过程的每一步决策,都必须可验证。审查者提出的问题是不是真问题?主代理接受的修复是不是真修复?如果拒绝,理由站不站得住?——每一步都要有证据、有记录、有规则约束。

v0.3.0 改动不止这些,本文聚焦三个最核心的机制:结构化审查输出、主代理批判性审视、有争议问题协议。三个机制服务于同一个原则——把审核从单向指令,变成每一步都可验证的闭环。

1. 结构化审查输出

审查者不能再随便列问题列表。必须分成三类输出:

  • 带严重等级的缺陷:C(根本性)/H(高)/M(中)/L(低)/I(信息)分级
  • 可操作的修复建议:不能说「考虑改进 X」,必须说改什么、怎么改、为什么改
  • 战略性批判意见:挑战假设、识别风险、质疑设计决策的合理性

规则:如果本轮没有 C/H/M 级缺陷,建设性建议和批判性意见不强制输出。不许为了填模板硬凑批判内容。

战略性批判有严格要求:

  • 不能只说表面问题,要质疑选择背后的推理
  • 要识别潜在故障模式、边界情况、盲点
  • 要质疑当前方法是不是正确的方向,而不只是能不能用
  • 要评估当前方案做了哪些权衡,这些权衡在项目约束下是否可接受
  • 必须有证据支撑,引用具体内容、需求或者前序输出的冲突

2. 主代理批判性审视

主代理收到审查报告,不能照单全收。必须做结构化批判性审视,分四步:

  1. 通读完整审查报告——理解每个建议和意见背后的推理,不只看表面建议
  2. 对照项目上下文评估——项目实际约束(时间、技术栈、团队、架构)、建议是否解决真问题还是理论问题、“改进"是否引入新的复杂度或风险、审查者对项目的假设是否正确
  3. 逐项做 ADOPT/MODIFY/REJECT 决策
  4. 记录审视过程——每个非平凡的决策都要记录理由

如果跳过第 1、2 步直接打标签,批判性审视就退化成了走形式——恰好是本篇要解决的问题。

拒绝规则有严格限制:

  • L/I 级问题和批判意见:主代理可以自由决定
  • C/H/M 级问题:只有「审查者对项目的假设事实错误」或「所谓缺陷其实是文档记录的预期行为」时,才能拒绝

这些拒绝理由完全无效:

  • 「没时间」:必须修复的问题不能跳
  • 「我不同意优先级」:严重等级是审查者的职权
  • 「实践中能用」:审查者识别的风险必须处理

3. 有争议问题协议

如果主代理拒绝了一个 C/H/M 级问题,就触发对抗循环:

  1. 主代理必须记录拒绝理由
  2. 争议问题传递到下一轮,审查者必须明确回应
  3. 审查者可以接受拒绝(问题从统计中移除),或者提供新证据重新提出(问题保留)
  4. 审查者提供新证据后,主代理不能用同一理由再次拒绝——必须 ADOPT 或 MODIFY
  5. 如果换理由仍拒绝,计入第二轮争议。两轮争议后,升级给用户解决

关键不变量:被拒绝的 C/H/M 问题,在审查者(独立 AI subagent)明确放弃前,仍然计入关卡统计。不能通过拒绝来绕过关卡条件。

这里涉及三个角色:

  • 主代理(写代码的 AI)= 动议反对方:交付物的创造者,对审查者的问题可以抗辩
  • 审查者(独立 AI subagent)= 动议提出方:发现问题、提供证据、回应抗辩
  • 用户(人类)= 主持人:两轮争议未决时拍板,Max Rounds 时做最终决策

审查者提出动议,主代理是反对方,用户是主持人——中立地执行程序,不偏袒任何一方。

人类的角色不是审查者。人类不逐条看代码,只做主持——当两个 AI 在证据层面无法达成一致时,由人判断谁对谁错。

争议协议的真实映射

这些模式不是假设。开源项目中,人和人之间的 code review 争议每天都在发生。争议协议的机制——提出问题、抗辩、证据对抗、升级——在真实世界里反复出现。

案例:ky 的前缀斜杠之争[2]

sindresorhus/ky 是一个流行的 HTTP 客户端库。它的 prefixUrl 选项会拒绝以斜杠开头的路径——遇到 /users 直接抛错。维护者的理由是:prefixUrl 做字符串拼接,不是 URL 解析,开发者可能误以为 /users 是从 origin 根路径解析。这是一个有原则的设计决策。

但几乎所有的 API 文档都显示前缀斜杠路径。GitHub REST API、Reddit API、Twilio、Netlify、Twitter/X、Salesforce——清一色 /path。核心协作者 @sholladay 承认这是 “easily the most controversial part of Ky”。

争议协议如何处理这件事:

Round N:审查者提出 [H-2] prefixUrl 拒绝前缀斜杠,阻断了标准 API 工作流。

主代理 REJECT:「这是文档记录的有意行为。prefixUrl 做字符串拼接,不是 URL 解析。」

[H-2] 变成争议问题,传递到 Round N+1。

Round N+1:审查者调查证据。发现六大主流 API 文档全部使用前缀斜杠。「有原则的设计决策」在用户侧的代价是库不可用。审查者用新证据重新提出 [H-2]。

主代理不能用「文档记录的有意行为」为由拒绝(同一理由无效)。主代理 MODIFY:「加斜杠剥离作为规范化步骤,保留 prefix 的语义。」

Round N+2:审查者验证修改。零新问题。

现实中,这个争议横跨 issue #70(2018)、discussion #468、PR #561,拖了多年,直到 v2 的 PR #606 才解决[2]。正是这类漫长的争议让我意识到:当双方都有合理依据时,批判性的深思熟虑比谁嗓门大更重要。争议协议就是把这种思考方式提炼成驾驭规则,用来约束 AI agent,在审核过程中确保每一步决策都经过证据检验,而不是凭直觉拍板。

也有吵不拢的时候。

案例:requests 的解析器僵局[3]

psf/requests v2.32.3 引入了一个回归:urlparse() 无法处理 IPv6 链路本地地址的 zone identifier(如 [fe80::1%eth0]),在多网卡机器上导致 socket 错误。有人提交 PR #6927,建议换成 urllib3.parse_url()

维护者 @sigmavirus24 强烈反对:历史证明每次换解析器都有回归,“Every URL parsing change is a minefield”。PR 作者不满:维护者不积极回应,自己公司有 SLA 压力。双方都不让步。PR 最终关闭。

争议协议的视角:

Round N:审查者提出 [H-1] URL 解析回归破坏了 IPv6 zone ID 支持。

主代理 REJECT:「使用标准库 urlparse 是正确的。urllib3.parse_url 有自己的兼容性问题。」

Round N+1:审查者提供反证:标准库 urlparse 明确不支持 RFC 6874 zone identifier,在多网卡机器上导致 socket 错误。

主代理仍 REJECT(第二轮争议):「历史经验表明 URL 解析器替换每次都引发回归。」

→ 升级给用户。提交的案卷包括:

  • 争议问题的原始描述:审查者怎么发现回归的,标了什么严重等级
  • 审查者的证据链:RFC 6874 规范条款、具体的 socket 错误、受影响的用户报告
  • 主代理的拒绝理由:历史回归记录、对 urllib3.parse_url 稳定性的担忧
  • 争议历程:两轮各自的论点和证据
  • 当前状态:回归是否仍在影响用户

用户拿到完整案卷后判断:「有条件接受——仅对包含 zone ID 的 URL 使用 urllib3.parse_url,其余保持不变。」这正是后来 PR #7065[3] 的做法。

现实中,#6927 的僵局不是因为技术问题无解。这类场景让人思考:当证据对抗没有规则、升级没有路径、同一理由可以反复使用时,争论很容易变成消耗。争议协议正是从这些经验中提炼出来的——把「证据说话、理由不重复、两轮未决就升级」变成明确的约束,让 AI agent 在类似场景下有章可循。

从议事规则到审核协议

回头看看争议协议的六条规则——它们不是凭空设计的。150 年前,有人解决过同一个问题[4]。

罗伯特议事规则争议协议
动议中心原则:先动议后讨论,无动议不讨论审查者必须提出具体问题(带严重等级),不受理模糊意见
立场明确原则:先表态是赞成还是反对,再给理由主代理必须 ADOPT/MODIFY/REJECT,每个非平凡决策记录理由
充分辩论原则:表决须在讨论充分展开之后争议问题传递到下一轮,审查者必须回应拒绝理由
面对主持原则:参会者之间不直接辩论主代理和审查者通过报告传递,不直接对话
主持中立原则:主持人不偏袒用户做仲裁,不参与技术辩论,只在两轮争议未决时拍板
限次原则:每人最多发言两次两轮争议后升级给用户,不允许无限循环

表面对应之下有一个根本不同:罗伯特议事规则假设参与者有自由意志和策略动机,规则约束的是人的策略行为(钻空子、拉帮结派、拖延战术);争议协议的参与者是 agent,不会钻空子——规则在这里的作用是把隐性的工程流程显性化、可验证化。一个管的是行为,一个管的是流程。

但这个差异不削弱类比的效力,反而强化了核心洞察:罗伯特议事规则用 150 年证明了一件事——规则对任何参与者都必要,不论其能力水平。人类议员有理性、有专业素养,仍然需要程序规则;AI agent 没有情绪,不会人身攻击,但正因为它不会「自觉遵守」,规则必须被编码进协议本身,而非依赖社会惯例。

直觉上可能联想到 GAN 的对抗结构,但两者动机不同:GAN 是 adversarial deception(生成器试图骗过判别器),Ralph Loop 是 adversarial verification(Creator 试图通过审查)。动力学也完全不同——前者是连续梯度空间,后者是离散轮次。罗伯特议事规则的类比更准确,因为它和争议协议共享同一个内核:通过程序正义约束对抗过程。

这个类比不是装饰。它告诉我们,Ralph Loop 的争议协议继承的是一套经过 150 年验证的程序正义传统——只不过把「请遵守议事规则」换成了「协议强制执行」。

实际效果

这个机制解决了两个极端:

第一个极端:主代理盲目服从审查者的所有建议,哪怕不合理。批判性审视和 ADOPT/MODIFY/REJECT 框架让主代理有了说"不"的机制——但必须给理由。

第二个极端:主代理和审查者无休止争论,没人拍板。最多两轮争议就升级给用户,既保证质量,也不会浪费算力。

和系列的关系

第 4 篇讲了 Ralph Loop 的收敛机制[5],解决的是「怎么确认可以结束审核」。

这篇讲的是 v0.3 升级,解决的是「收敛过程的每一步是不是可靠」。

之前我们只关心有没有问题。 现在我们还要关心:

  • 问题是不是真问题?
  • 修复是不是真修复?
  • 审查是不是真审查?

体感上,AI 审核走过场的情况大幅减少。当然也有副作用:有时候两个 AI 吵得比我自己写代码还认真。

参考

  1. tdd-pipeline v0.3.0:github.com/alexwwang/tdd-pipeline(tag v0.3.0)
  2. sindresorhus/ky — prefixUrl 前缀斜杠争议:issue #70discussion #468PR #561PR #606
  3. psf/requests — URL 解析回归与 IPv6 zone ID:issue #6735PR #6927PR #7065
  4. Robert, Henry M., Robert’s Rules of Order, 1876(中文版:袁天鹏、孙涤译,上海人民出版社)
  5. 第 4 篇 审核层防线:Ralph Loop:AI 的错误不是随机的,是收敛的

Aristotle 项目在 GitHub 开源,MIT 协议。欢迎提交 Issue 和 PR。