为智能体写的变更建合并队列。
你的编码智能体把 PR 数量翻了一倍,交付指标却纹丝不动,于是显而易见的诊断是「评审是瓶颈」,显而易见的解法是加评审人或上一个评审机器人。两者都错,而且错得要花一个季度才发现:真正的约束是从「已批准」到「进 main」之间那条串行路径;而应对数量的标准解法——把多个 PR 打包,让一次 CI 清掉好几个——会随着智能体占比上升在算术上变得更糟,因为智能体抬高的正是被打包所放大的那个「单 PR 失败率」。按这个失败率来定批次大小,把失败定位做便宜,剩下的力气全用在「让任何东西进队列之前先把这个失败率压下去」。
数量来了;而系统是靠降低门槛把它吸收掉的。
先看遥测数据究竟说了什么,因为坊间版本——「AI 写更多代码,评审变慢」——恰恰盖住了那个真正要紧的发现。
Faros AI 的 2026 年报告取自约 22,000 名开发者、4,000 多个团队、两年时间,按每个组织自身 AI 采用率最低与最高的时期做对比。吞吐上去了。下游的一切也跟着上去了:人均 bug 数上升 54%,评审时间中位数上升约 5 倍,而事故与 PR 的比值涨了三倍不止。LinearB 的 2026 基准补上了排队的形状——智能体式 PR 等到评审人接手的时间约为 5.3 倍。
真正该盯住的是另一个数字:完全没有经过评审就合入的 PR 多了 31%。那不是瓶颈。瓶颈会把活拦住。这是一个系统发现数量吸收不了,于是绕开了自己的质量闸门——而那个涨了三倍的事故比值,就是收据。
所以对这个问题的诚实表述不是「我们得评审得更快」,而是:如今仍然对每一次变更一视同仁生效的闸门,只剩下批准与 main 之间那道自动化的了。那道闸门若慢,人们就会绕过它合入;它若又快又严,它就会去做评审已经不做的那份活。下文全部是在讲如何让它快到足以保持严格。互补的另一半——教智能体产出便于核验的变更——在补丁生成与测试驱动循环里。
打包是标准解法,而智能体负载恰恰是让它失效的东西。
合并队列做的是串行化:取下一批 PR,把它们放在 main 当前尖端加上排在它们前面的所有东西之上做测试,通过的就合入。代价是每次合并一趟 CI,而在智能体的量级上,这就是问题的全部。每一家队列产品的答案都是打包——把 b 个 PR 分成一组,跑一次 CI,全绿就一起合入。
现在把算术做一遍,因为它就是本页论证的全部。若每个 PR 在队列里独立地以概率 p 失败,那么 b 个一组通过的概率是 (1−p)b:
# Probability a batch of b passes, at per-PR queue-failure rate p p = 5% (well-tested human PRs) b = 4 -> 0.95^4 = 81.5% b = 10 -> 0.95^10 = 59.9% p = 20% (typical agent-authored mix, unrebased) b = 4 -> 0.80^4 = 41.0% b = 10 -> 0.80^10 = 10.7% # Without bisection, one failure discards the whole batch. # Expected CI runs per merged PR, b=10: # p=5% -> 1 / (10 * 0.599) = 0.17 (batching wins big) # p=20% -> 1 / (10 * 0.107) = 0.93 (batching bought you nothing)
把最后两行慢慢读。在人类的失败率上,十个一组每合入一次变更只花六分之一趟 CI。在智能体的失败率上,同样的配置每次合并要花掉将近一整趟——你为打包这套机器付了钱、多了一种「队列重置」的失败模式,然后回到了原点。
而 p 并不是你继承来的一个常数。智能体写的变更之所以 p 更高,有三个结构性原因:智能体是对着它出发时那个基线提交做的核验,而不是对着此刻真实存在的尖端;它改的文件里有些它并没有本地上下文;而它开 PR 的速度快过这棵树平静下来的速度,于是两个智能体的改动彼此冲突成了常态而非罕事。数量与 p 是一起涨的,这正是为什么那个直觉反应——量更大了,就把批次做更大——恰恰反了。
规则:批次大小是你实测的队列失败率的函数,不是队列长度的函数。按仓库、按周、按作者类型分开测 p。如果你的队列工具支持动态的 {min, max} 批次大小,这段算术说的正是那个设置——但它是按积压量来调的,不是按 p,所以上限得由你自己定。
二分定位改变的是指数,而它是唯一值得为之付费的功能。
上面的算术假定失败的一组会被整组丢弃。正是这个假定让高 p 下的打包变得毫无意义;而它也正是每一款像样的队列产品都要打破的假定。
失败时把这一组劈开、测两半、递归下去。在 b 个里定位出一个罪魁,大约多花 2·log₂(b) 趟,而不是全部重跑;健康的那些 PR 继续往前走,而不是回到队尾。再叠上测试结果缓存,那些已经作为更大一组的一部分通过过的半区根本无需重跑。
这正是各家工具真正有差别的那条轴,所以请按它来筛,而不是按宣传来筛:
- GitHub 原生合并队列会组成 merge group,当必需检查失败时,把出问题的那个 PR 移出队列。它有一个构建并发设置(分发 1–100 个
merge_group事件),以及一个控制「失败的 PR 能否被编在一个通过的 PR 后面」的选项。它免费、开箱即有,对于 CI 快、量不大的单仓库就是正确答案。它不提供批次二分定位,也不提供 monorepo 感知的分道。 - Trunk Merge Queue 把兼容的 PR 打成一组,失败时把这一组挪到一条单独的队列做二分定位,劈开重测并复用此前通过的结果。它通过构建图集成(Bazel、Nx)从一次变更实际影响到的目标推断出并行的道。
- Mergify 把旋钮直接暴露出来:批次大小可以是固定整数(1–128)或动态的
{min, max};通过临时草稿 PR 把改动与基线合起来做并行/推测式检查;有max_parallel_checks上限、优先级规则,以及把触及相同区域的改动编在一起的 scope 感知打包。 - Aviator 围绕受影响目标来搭:由一次变更触及什么推导出动态队列;乐观校验会在怀疑是 flaky 时先等一等,看后面的批次是否通过,而不是立刻重置(
use_optimistic_validation、optimistic_validation_failure_depth);并提供自托管部署选项。
照两个问题来挑,你就不会挑错:它会不会对失败的一组做二分定位,以及它能不能为彼此不可能互相影响的变更跑独立的道。其余都是配置。
在进队列之前把 p 压下去:让智能体对着它将要合上去的那个尖端做核验。
二分定位让高失败率变得可以承受。而把失败率降下来,才让它变便宜;在这一点上,编码智能体平台有一项人类工作流没有的优势:智能体还在跑,你可以叫它再来一遍。
默认的智能体循环是对着它检出的那个提交做核验的。等它的 PR 排到队首,main 已经动了——在繁忙的仓库里能动几十个提交。大多数队列失败不是坏补丁,而是对着陈旧状态测过的好补丁。
- 在入队时对着队首重新核验。在 PR 加入队列之前,把它变基到当前的推测尖端上,跑受影响的那些测试。这是一趟便宜的运行,它把一次队列失败——昂贵、串行、还打扰后面所有人——变成一次普通的分支失败。这是本手册里杠杆最高的一处改动。
- 把失败交回给智能体,而不是交给人。一次队列弹出是一个规格清楚、还附带复现的任务,正是CI 修复智能体所描述的那个循环的理想输入。把尝试次数封在两次然后上报;两次都修不好自己被弹出的智能体,产出的是一份需要人看一眼的变更。
- 给智能体的 PR 设一个改动规模上限。失败概率随触及文件数上升,与在途其他改动冲突的概率也一样。一个改了 40 个文件的智能体 PR 不是一个工作单元,而是一个队列风险源。把它拆开;同样的论证用在运行时那一侧,见影响半径。
- 绝不允许智能体绕开队列合入。管理员直接合并这条后门是存在的,智能体手里有令牌,而一次直推 main 就会让在途的每一趟推测式运行作废。请把权限撤掉,而不是为它写一条规定。
这个循环还有一条特有的告诫:一个以「让队列接受它」为奖励的智能体,会找到那条便宜的路——删掉断言、把测试标记为跳过、把类型放宽。这种失败模式不是假想,补丁生成与测试驱动循环里有专门的讨论。机械的护栏是一条 diff 层面的规则:修复队列弹出的智能体不得修改测试文件,除非这次弹出本身就是它正当更新的那个测试失败;而任何断言删除,无论如何都触发人工评审。
只把真正会互相影响的东西串行化。
一条全局队列把每一次变更都当成可能与其他任何变更冲突,这在小仓库里是真的,在大仓库里则大错特错。两个触及互不相干服务的 PR 不可能把对方弄坏,让它们排一条队是纯粹的吞吐损失——而这份损失恰恰在量最大的时候被放大。
受影响目标分道会从构建图推导出每次变更的目标,给互不重叠的变更各自一条道、各自独立合入;重叠的变更则叠进一条共享的道里一起测。前提是有一套能回答「这个 diff 影响到什么」的构建系统——Bazel、Nx、Pants、Turborepo——如果没有,诚实的近似是一张手工维护的「路径到道」的映射表。它比构建图差,但比一条全局队列好得多。
两点实务提醒。第一,道的边界是一条关于你架构的断言,而错误的断言会把两个确实会互相影响的变更放行;请保留一小组在每条道里都跑的集成检查。第二,flaky 测试是另一种失败,症状却一模一样——一片红——而在智能体的量级上,一个 1% flaky 的必需检查会不停地炸。请通过一套明确的、可追踪的流程把 flaky 测试隔离出必需集合,而不是让队列的 flake 启发式去把它们吸收掉,并把隔离清单当成有主的技术债来管。一条被调成能容忍 flaky 的队列,就是一条被调成能容忍真实失败的队列。
建造顺序,以及那个告诉你「它起作用了」的指标。
按顺序来,每一步都为下一步挣出资格:
- 先开一条完全不打包的队列。批次大小 1。你于是拿到了正确性——没有东西会在未对真实尖端测过的情况下合入——以及一个测量值:你按作者类型分开的基线 p。
- 加上入队时的变基再核验。看着 p 掉下去。这通常是单项降幅最大的一步,而代价只是改一处流水线。
- 把批次大小提到实测 p 撑得住的水平,而且仅当你的工具会做二分定位。每月重算一次;你 PR 里智能体的占比在动,p 会跟着动。
- 拆成多条道——在「合入耗时」的主导项变成排队等待、而不是 CI 时长之后再做。
- 把弹出交回智能体,封在两次尝试,并带上测试文件护栏。
每周只跟四个数字,别的都不跟:按作者类型分开的队列失败率 p、每合入一个 PR 的期望 CI 趟数、从批准到进 main 的中位时间,以及未经队列就进了 main 的变更占比。第四个是那个会悄悄坏掉的——那 31% 就住在这里,而一条变慢的队列会把它养大,且没有任何人做过这个决定。
在你掏钱买队列产品之前,先花一个下午,从手上已有的数据算出一个数字:上个月合入的那些 PR,如果是对着它们合入那一刻的 main 尖端、而不是对着它们自己的基线去测,有多大比例会失败?取五十个样本,在它们各自合上去的那个尖端上重跑受影响的测试。那个比例就是你的 p,而本页的一切都由它决定——打包对你是帮助还是纯开销、批次大小该配多少、二分定位是个锦上添花还是让整条队列可行的唯一前提。测过的团队通常落在自己猜测的二到四倍之间,而这份意外几乎全部可以归到那些「对着一个已经不存在的基线测过」的智能体变更身上。
延伸:后台编码智能体是这些数量的来源,代码评审智能体是这条队列在兜底的那道闸门,智能体产出的评审队列则是同一笔算术在人这一侧的样子。