混合 PR 队列:审查者吞吐量已成为瓶颈约束
在过去的二十年里,制约理论(Theory of Constraints)在软件交付中的答案始终如一:瓶颈在于编写代码。我们围绕这一假设构建了一切——结对编程、IDE 自动补全、更快的 CI、更小的微服务,所有这些都是为了让更多的代码通过固定宽度的审阅管道。接着,编程 Agent 出现了,管道的生产端拓宽了 5–10 倍,而审阅管道的宽度却纹丝不动。一位过去每周提交 3 个 PR 的资深工程师,现在正监督着一群在一个下午就能提交 30 个 PR 的智能体。团队的交付速度不再取决于编写代码的速度,而是取决于人类阅读代码的速度。
这并非未来的问题。据测量,在某些样本中,PR 审阅时间的中位数同比增长了 441%,并且在未经任何审阅的情况下就被合并的 PR 增加了 31%——这并非出于政策规定,而是因为审阅者已经放弃了跟上进度。Stripe 每周交付超过一千个由 Agent 生成的 PR。在一项基准测试中,特性分支(feature-branch)的吞吐量同比增长了 59%,而主分支(main-branch)的吞吐量却下降了 7%——代码正在被编写,但没有被发布,因为它们卡在了审阅环节。
当这种情况发生时,人们往往倾向于将其视为工具问题,并寻求 AI 审阅工具。这在边缘情况下有所帮助,但它忽略了结构性的转变:编程 Agent 计划首先是一个审阅流程计划,其次才是代码生成计划。如果你在不重新设计队列的情况下部署 Agent,你只是构建了一个待办事项生成器。
两种失败模式其实是同一种失败
混合 PR 队列会在两个相反的方向上失败,而团队往往在两者之间摇摆。
第一种失败是 晚上 11 点的橡皮图章。审阅者面对四十个待处理的 diff,知道必须在每日站会前清理完毕,于是开始批准那些他们根本没读过的东西。“已批准”不再是质量信号,而成了吞吐量信号。审阅者在私下里很坦诚,但在审计记录中却在作假。团队的名义审阅 SLA 看起来很健康,直到复盘(post-mortem)显示,由于一个在 48 秒内被批准的 900 行 PR,导致 Bug 上线。
第二种失败是 坟场队列。同一个团队,同样的输入。资深工程师拒绝敷衍了事,因此 PR 会在队列中躺上三到七天,等待真正的审阅。作者转而去处理其他事情,diff 变得陈旧,合并冲突堆积如山,Agent 必须带着新的分支重新输入提示词。实际吞吐量降到了 Agent 出现之前的水平,因为审阅队列已经变成了一个没人愿意涉足的、自我强化的泥潭。
这些看起来像是相反的问题,但其实是从不同团队文化中看到的同一个问题。它们都是因为将 Agent 编写的和人类编写的 PR 喂进了一个为人类产出节奏而设计的队列 ,且使用了单一的审批标准,没有区分一行代码的依赖项升级与支付流程的重构。
必须落地的标注纪律
在任何流程变更生效之前,队列必须是 可读的。一个盯着 PR 列表的审阅者需要无需打开 diff 就能知道他们面对的是什么对象。以下三类元数据极具价值:
- 作者类别 (Author class)。
agent-authored: <agent-id>告诉审阅者这段代码是由 Claude Code、Codex 还是你的内部 Agent 生成的,以及是哪一个。不同的 Agent 有不同的失败模式,审阅者会根据 Agent 调整注意力,就像他们对待合作过的初级开发人员一样。 - 监督状态 (Supervision state)。
supervised-by: <human>告诉审阅者在进入队列之前是否已经有人类检查过该 diff。一个作者已经读过的 Agent PR 与一个没人读过的 Agent PR 是完全不同的审阅对象。 - 风险等级 (Risk tier)。这不需要追求完美。即使是粗略的标签——
tier: trivial | feature | critical-path——也会改变审阅者的行为。“琐碎”等级只需要 30 秒的粗略浏览。“关键路径”等级则需要像对待人类作者一样接受严格审查。
标签的命名并不如标签的存在本身重要。那些试图把所有这些都记在脑子里的团队,不出一个季度就会陷入“橡皮图章”式的失败。而那些将这些信息放在结构化追踪信息(trailer)或 PR 模板中的团队,则会拥有一个人类可以在一分钟内扫描完毕的队列。
针对等级设置 SLO,而非针对 PR 设置 SLO
一旦队列分了级,审阅 SLO 也必须随之分级。当产出是人类节奏时,一刀切的“PR 在 24 小时内完成审阅”目标是可以忍受的。有了 Agent 之后,它变成了一种将所有事情推向“橡皮图章”极端的情况,因为达到 SLO 的唯一方法就是缩短每个 PR 的审阅时间。
一个可行的方案:
- 琐碎等级 (Trivial tier):分钟级合并。在 CI 提供充足信号的情况下自动批准,并尽可能合并到单个快速通道 PR 中(例如,将所有补丁升级合并为一个 Dependabot PR,而不是六个)。这一等级的重点是让这些 PR 完全脱离人类的注意力,而不是让人类更快地审阅它们。
- 特性等级 (Feature tier):数小时内完成首次审阅。完整的人类审阅,允许 Agent 辅助分类。这是审阅者发挥实际判断力的地方,队列规模应足够小,以便他们能投入真正的时间。
- 关键路径等级 (Critical-path tier):明确指定的审阅者,没有橡皮图章路径,无论 CI 状态如何,都不允许 Agent 自动合并。鉴权边界、支付流程、数据删除,以及任何涉及与客户签订合约的内容。这一等级在结构上保持精简,并获得在 PR 总量较少时所拥有的那种关注度。
核心洞见在于,琐碎等级 并不是特性等级的快速版——它是一个有着不同审批标准的、不同的审阅对象。团队犯的错误是将所有 PR 在更快的时钟周期下视为同一种东西,这压缩了原本 应该花在真正重要的 PR 上的时间。
推理踪迹让代码审查变得可控
当 Agent 提交一个 PR 时,审查者面临一个在人类编写代码时无需面对的问题:模型是真的理解了需求,还是只是生成了与需求模式匹配的代码?这两者完全不同,而 200 行的代码差异(diff)并不能告诉你到底是哪一种。
结构性的解决方法是要求 Agent 的 PR 描述必须包含推理踪迹(reasoning trace)和验证记录。这样,审查者审计的是过程 —— “Agent 是否通过合理的方法解决了正确的问题,并验证了结果?” —— 而不是从头开始重新推导正确性。第一项工作只需 30 秒,而第二项可能需要 30 分钟。这就是可扩展队列与不可扩展队列之间的本质区别。
追踪过程不需要太复杂。通常三点就足够了:Agent 理解的目标是什么(以便审查者立即发现任务框架是否错误)、它考虑并拒绝了哪些替代方案(以便审查者知道它是否思考过更简单的路径)、以及它运行了什么来验证结果(以便审查者知道“测试通过”是指通过了针对此行为的测试,还是仅仅因为整个测试套件是绿色的)。如果缺少这三点中的任何一点,PR 应该在提交给人类审查者之前就退回给 Agent。
这种方法存在真实风险:推理踪迹可能是“自信地胡说八道”。模型可能会生成一个听起来很合理的思维链,通过错误的逻辑得出正确的答案,或者虚构一个从未真正发生过的验证步骤。推理踪迹并不是证据 —— 它是一个结构化的对象,供审查者对照 diff 进行抽查。如果运用得当,它可以将审查时间缩短一个数量级;如果盲目信任,它不过是一个更高级的橡皮图章。
“CI 通过” Oxygen 并不是你想象中的那道关卡
混合队列的压力最终会迫使团队走向最危险的妥协:让 Agent 在 CI 亮绿灯后自动合并自己的 PR。这看起来像是显而易见的解决办法 —— 测试通过了、Lint 通过了、类型检查也通过了,为什么还需要人类介入?但这种策略会在六个月后让你后悔莫及。
问题在于,CI 的设计初衷从来不是为了给 Agent 编写的代码做合并把关。CI 是为了验证人类编写的代码没有破坏人类已经想到要测试的功能。当作者和测试作者是同一个模型时,CI 亮绿灯只能说明变更在内部是一致的,并不能说明该变更符合需求。发现这一点的团队通常都经历过相同的惨痛教训:功能上线了,客户报告无法工作,本应捕获该问题的测试并不存在,而 Agent 的 PR 描述对缺失的场景只字未提,因为 Agent 根本没想过要为它没打算实现的行为编写测试。
解决方法不是放弃自动合并,而是诚实地面对自动合并的意义。对于琐碎的层级(已知兼容性范围内的依赖更新、Lint 自动修复、生成器是确定性的代码刷新),CI 是一个合理的关卡。对于任何涉及业务逻辑的代码,CI 是一个必要关卡,但绝不是一个充分关卡,合并按钮必须留在人类手中。
