跳到主要内容

舰队级 CODEOWNERS:当作者是 AI Agent 时的评审路由

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的 CODEOWNERS 文件编码了一个古老到没人记得当初为何制定的假设:代码变更(diff)的作者知道自己身处谁的领地。一名修改计费服务的工程师深知自己进入了计费业务的领地。他们有目的地将变更范围限制在自己团队的目录内,如果必须触碰其他团队的代码,他们会亲自或在 Slack 上打个招呼,在发出评审请求之前先进行沟通。基于路径的评审路由之所以奏效,是因为人类作者会预先为自己的变更选择路由。

Agent 不会这样做。如果要求一个 Agent “迁移所有弃用的日志调用”,它会在一个下午横扫整个代码库,并生成一个涉及 400 个目录的 diff。GitHub 尽职尽责地查询 CODEOWNERS,匹配每一个通配符(glob),并同时向 40 个团队发出评审请求。

没有人询问过这些团队是否想要这种变更。没有人预警过变更即将到来。而且请求批准的“作者”并不是一个可以随时抓来了解背景信息的同事——它是一个代表其他部门某人行事的机器人账号,而那个人甚至可能都不知道哪些团队收到了通知。

GitHub 上超过五分之一的代码评审现在都有 Agent 的参与。这些评审底层的路由层是为一个“作者身份意味着领地意识”的世界设计的。那个世界已经一去不复返了,而且这种失效并非体面的退场:它表现为包含上万个文件的代码修补(codemod)因等待上百个批准而停滞不前,以及 40 个团队的队列被那些并非人类意愿产生的变更所堵塞。

基于路径所有权中的隐藏契约

CODEOWNERS 看起来像一张路由表,但它实际上只是契约的一半。可见的一半规定:/payments/ 下的变更需要 @payments-team 的批准。不可见的一半规定:任何在 /payments/ 下发起变更的人都明白自己正在进入支付领地,有正当理由出现在那里,并相应地限制了变更范围。

人类作者会下意识地遵守这部分不可见的契约。他们的变更集中在自己熟悉的、由自己团队拥有的代码周围,这意味着大多数 PR 只匹配一两个所有权规则,并进入预期的队列。跨团队变更足够少,因此沟通成本(如打招呼、设计文档、会议)可以按需逐案支付。

即使在纯人类创作的情况下,这一契约在大规模协作中也开始瓦解。大型单体仓库(Monorepos)已经深受无主文件(在某些审计中,大约八分之一的文件不匹配任何所有权规则)、导致单一变更通知多个团队的重叠通配符,以及当唯一能审批共享文件的人在度假时产生审批僵局的困扰。团队将大型单体仓库中的评审路由描述为“收件箱转盘赌”。

Agent 并不只是在瓦解契约,而是直接废除了它。Agent 没有领地意识。它专注于完成任务,跨越任务所需的任何文件,其天然的变更形态是横向的——即对整个代码库进行浅层切片——而非 CODEOWNERS 所适应的纵向、局部于团队的形态。每个通配符都匹配。每个团队都被通知。路由表完美执行,却产生了一个没人想要的结果。

这让本已超负荷的评审系统雪上加霜。2025 年的 DORA 报告发现,AI 的采用现在与交付吞吐量呈正相关,但仍与不稳定性相关——产生更多的变更失败和返工——正是因为代码生成的加速超过了校验的加速。对高 AI 采用率团队的工程指标研究显示,PR 数量几乎翻了一番,而评审时间增长了约 90%,且 PR 规模急剧膨胀。在机群(fleet)到来之前,评审就是瓶颈。而机群将瓶颈变成了路由失效。

现在作者是一对搭档:按发起人路由,而非按路径

更深层的问题在于,“作者”不再仅指代一方。每一个由 Agent 生成的 diff 都有两个主体:编写代码的 Agent 和启动它的发起人。Git 已经可以表示这种拆分——提交作者(commit author)与提交者(committer)、Co-authored-by:Generated-by: 尾注、PR 上的机器人身份——正因如此,新兴的实践将这些尾注视为取证审计追踪。最近关于 Agent 责任归属的工作将其界定为一个身份问题:对于在你的系统中运行的任何 Agent,你必须能够追溯到部署它的账号,否则你就会面临责任缺位。

评审路由应该使用这一身份,但目前并非如此。CODEOWNERS 仅根据一个信号进行路由——哪些路径发生了变化——因为对于人类作者来说,路径代表了一切:意图、范围和责任。而对于“委托人-代理人”对来说,路径几乎说明不了什么。真正关键的信号是发起人(sponsor):哪个工程师发起了这项任务、基于什么指令、过往记录如何。

具体而言,基于发起人的路由改变了三个决策:

  • 谁先评审。 发起人在其他人被通知之前先行评审。GitHub 官方关于 Agent PR 的指导非常明确:启动 Agent 的人负责验证 Agent 是否捕捉到了他们的意图,编辑描述,并在请求他人时间之前评审 diff。在发起人批准之前,来自 Agent 的评审请求应被视为未发送。
  • 合并后的责任人。 当变更在三周后引发回归问题时,“是 Agent 干的”不能作为答案。发起人的名字应该出现在 blame 层中——且是可查询的,这样事故响应者可以从一行错误代码一跳(one hop)找到具体的人,而不是通过机器人账号找到任务 ID 再找到一个已经离职的人。
  • 代码变更接受多大程度的审查。 一个在该领域有 100 次成功的 Agent 辅助合并记录的发起人,与一个第一次指派机群处理从未触碰过代码的新手,其风险是不同的。人类评审员对每个人的信任是逐渐建立的;发起人的信任度应根据“人机协作”的表现来积累,而路由应读取该信任级别。

这一切并不意味着取消基于路径的所有权。团队仍然需要控制进入其领地的内容。这意味着路径匹配只是路由的一个输入,而不是全部算法——核心问题从“哪些文件改变了”转变为“谁在主导,他们是否有权修改这些文件”。

自主预算:回答“在哪个评审层级”的所有权规则

一旦发起者(Sponsor)成为一等公民级别的路由输入,第二个升级紧随其后:所有权规则不应仅指定“谁”来批准,而应指定 AI 代理编写的变更在目录树的每个部分中获得“多少自主权”。将其称为自主预算(Autonomy budget)——一种按目录声明的、代理 diff 必须通过的评审层级。

以下是各团队正在趋同的一种方案:

  • 生成区域和机械化区域——lockfile、生成的客户端、仅格式化的变动:代理 diff 在 CI 通过且发起者批准后即可合并。无需团队评审。
  • 标准应用代码:代理 diff 首先接受自动化评审(针对重复代码、规避 CI 的行为和幻觉正确性进行机器检查),然后由所属团队的一名成员评审“决策判断”,而非每一行代码。
  • 核心承重区域——身份验证、计费、迁移或任何安全敏感的内容:完整的团队评审,显著标记代理作者身份,无论发起者的过往记录如何,都不允许自动合并。

这并非虚构。Cloudflare 的内部评审编排已经根据风险等级对每个合并请求进行分类——微小变更由两个自动化评审员处理,中等规模变更由四个处理,大型或安全敏感变更则由七个以上处理——在一个月内运行了 13.1 万次评审,中位成本不到一美元,耗时不到四分钟。层级逻辑已经存在并发挥作用;大多数组织尚未做到的是将其接入“所有权”,使层级成为“领地”的属性而非全局设置。

自主预算还为团队提供了一个目前欠缺的调节拨盘。今天,团队对抗代理流量的唯一杠杆是社交手段——抱怨直到人们停止让代理指向你的目录。有了所有权级别的预算,一个刚刚被坑过的团队可以降低其层级一个季度,而一个淹没在琐碎审批中的团队可以提高门槛,并将机械化层级委托给 CI。DORA 的研究发现,AI 会放大其所在的任何工程系统,这一点在这里具有双面性:拥有强大自动化质量闸门的团队可以安全地接受高程度的代理自主权;没有这些闸门的团队在制定所有权规则时不应自欺欺人。

Codemod 死锁,以及为什么采样优于签核

现在,让这一切变得紧迫的失败模式:全仓库扫荡。代理执行一个涉及万个文件的机械化迁移——重命名 API、更换日志库、在各处强制执行 lint 规则。在笼统的 CODEOWNERS 规则下,该 diff 需要其涉及的所有团队批准。上百个审批,每个都在具有自身延迟的队列中,任何一个审批都能让整个变更陷入停滞。与此同时,diff 正在腐烂:等待的每一天,代码库都在变动,代理(或其发起者)不得不重新变基并再次将上百个队列翻个遍。

死锁之所以存在,是因为笼统的规则无法区分“一个决策应用了一万次”与“一万个决策”。机械化的 codemod 属于前者。值得评审的是这种转化——这个重写规则正确吗?——以及它被忠实执行的证据。这种评审只需一个团队花费一小时。独立评审每个应用点需要一百个团队花费一周时间,且不会产生额外的安全性,因为第 63 位评审员查看其团队的 15 个重命名调用点时,并不会掌握第一位评审员所不知道的信息。

出路在于像评审数据流水线一样评审 codemod,而不是像评审手工补丁那样:

  • 一次性批准转化。指定的负责人(平台团队或被迁移 API 的所有者)评审重写规则及其测试证据,并对“模式”进行签核。
  • 通过采样验证应用。在各团队的领地内随机抽样变更点;如果样本干净且各处 CI 均为绿色,则剩余点继承审批结果。这是统计学上的信心,而非逐个团队的签核。
  • 给受影响团队一个否决窗口,而非审批门槛。通知每个所属团队并设定截止日期:在 N 天内提出异议,否则变更将落地到你的目录。沉默意味着对机械化层级的同意;但对于核心承重层级,沉默绝不代表同意。

将扫荡拆分为按团队划分的 PR(常见的权宜之计)实际上更糟:它成倍增加了协调面,保证了迁移在完成一半时落地,并让代码库处于迁移本该消除的混合状态中。评审单元必须改变,因为创作单元已经改变了。

在机群改写契约之前,先重写它

令人不安的总结:CODEOWNERS 仍然在回答“谁必须批准这些文件的变更”,但代理打破了让这成为正确问题的假设。现在的正确问题是:谁在发起这次变更、该发起者的代理在领地内获得了什么自主权,以及这是一个决策还是一万个决策——而今天的工具原生无法回答其中任何一个,因此答案存在于部落规范、机器人账户约定和临时豁免中。

利用现有工具,你就能完成大部分工作。要求每个代理 PR 在 trailers 中携带其发起者的身份,并将无发起者的代理 PR 视为垃圾邮件。让发起者的自我评审成为合并阻断检查,而非礼貌性建议。将自主权层级编码为目录之间分支保护的差异,即使手段简陋也行。在第一个万级文件扫荡冲击你的组织之前,建立一条具有模式级批准和采样验证的 codemod 通道,因为在死锁期间设计它会非常痛苦。

将评审路由视为基础设施(版本化、负载均衡、感知驾驶者是谁)的团队,将消化机群规模的创作并将其转化为吞吐量。而那些继续假装每个 PR 作者都是知道自己在谁的地盘上的真实人类的团队,将会发现一个完美执行其规则的路由表仍然会导致整个工程组织死锁。机群已经在提交拉取请求了。而它撞上的所有权文件,是为别人准备的。

References:Let's stay in touch and Follow me for more thoughts and updates