跳到正文
原文
Hacker News· rafiss·· 2 天前精选AI 评分67

Cockroach Labs 用医院流程管理编码智能体,五个月合并超千个 PR

Five months treating bugs like patients and coding agents like a medical team

AI 导读

Cockroach Labs 把自家编码流水线改造成教学医院式的多智能体协作系统 MOLT Sinai,由规划 Agent、住院医师、评审主治等角色通过 GitHub Issues 流转协作,五个月内合并了 1238 个 PR、产出超百万行代码。

推荐理由

把多智能体编码流程类比成教学医院,给出 GitHub Actions 上角色化管线的拆解与五个月实际跑出来的吞吐与质量权衡。

正文 · AI 翻译

在四月的一个周三傍晚到周五下午之间,Cockroach Labs 用于将数据库迁移到 CockroachDB 的工具 MOLT,获得了从 IBM Db2 数据源进行迁移的能力。

Db2 是最早一批商业化的关系型数据库之一,拥有丰富的 SQL 方言、复杂的类型系统以及原生的线路协议。在 MOLT 中支持它意味着要新增一个 schema 转换器、一条用于将行拉出并导入 CockroachDB 的 Fetch 路径、一条用于在数据加载完成后进行比对的 Verify 路径、一个 vendor 进来的 ANTLR 文法、一个用于 CI 的 Docker 镜像,以及超过一万行的测试 fixture。我们在 2024 年为 MOLT 工具添加 Oracle 支持时,等量的工作耗时 9 个月,消耗了约 16 万美元的工程时间。 

Db2 只花了不到两天时间,而且没有任何一行代码是由人编写的。token 账单为 4172 美元:快了 164 倍,便宜了 38 倍。

这一切都始于一条描述该需求的 GitHub issue:

Support Db2 LUW for convert/fetch/verify #526

接着,一个规划 agent 读取了这条 issue,认为它整体过大,无法一次性处理,于是将其分解为十五条带显式依赖图的子 issue:先是基础模块,然后是类型系统,再是行迭代器,然后是 Fetch,再是 Verify,再是 Convert,再是 CI,最后是测试数据。其中有两条子 issue 在自身的拆解过程中也被认为过大,因而被再次分解。在此过程中,这些 agent 还针对自身的工作另外提了十几条 issue:fixture 缺失、一个类型映射 bug 以及一个隔离级别的修复。等到父 issue 关闭时,其下已开出三十二条子 issue,合并了二十七个 pull request,审阅者共将工作打回五十五次,九条 issue 被升级,其中两条升级到了人工处理。以 PostgreSQL(我们测试最充分的方言)为基准衡量的测试覆盖率达到了一致水平。

但这一切究竟是如何发生的?是什么在驱动这一切,更为重要的是,是什么在确保最终产出的是高质量成果?在为 Db2 支持开出 issue 之前,我们首先搭建了一个我们称之为 MOLT Sinai 的“编码医院”。MOLT 是 CockroachDB 迁移工具集 的名字,而一旦我们决定以医院为模型来设计流水线,MOLT Sinai 这个混成词便让人无法抗拒。这是因为 Mount Sinai(西奈山医院)既是多伦多也是纽约(我们所在的两座城市)的一所知名教学医院。这个名字被沿用了下来,相关术语也被一并沿用:issue 就是病人,合并就是出院,负责人是各科主任。

Agent 快速写出大量代码,这本身在业界已屡见不鲜,因为软件工厂正在整个行业中不断涌现(例如 OpenAI 的 这家)。我们这个案例的独特之处在于:横亘在那些 agent 与 main 分支之间的究竟是什么,我们为什么把它建成医院的形态,以及当我们在真实工作中将其运行了五个月之后,究竟发生了什么。

为什么是教学医院Copy Icon

编码 agent 面临的问题在于:尽管它们完全有能力编写代码,但要信任它们产出经过充分测试、可维护且符合预期范围的生产级代码,仍然很难。Agent 运行起来相对便宜,但当它们的代码最终要落到客户手中时,这种便宜并不能带来多少安慰。我们宁愿让 agent 运行得更久(哪怕慢上 10 倍),也不愿把错误的数据落到客户向 CockroachDB 的迁移里。速度固然重要,但质量重要得多得多。

过去一年里出现了若干自动化智能体编程的尝试,其中许多都是为吞吐量优化的。例如 Gas Town,它会针对一个代码库并行运行许多智能体,并配上一层合并处理机制来解决冲突。当瓶颈在于代码编写速度时,这是一种不错的设计。还有 Cursor 的这个 重建 SQLite 的实验,速度之快以至于他们需要编写自己的版本控制系统来处理变更量级。这是一个有趣的实验,但它显然不是为向用户交付高质量数据库代码而设计的。在绝大多数早期智能体自动化实验中,速度是主要的驱动因素。

我们的出发点不同。我们构建一个 分布式 SQL 数据库,以及一套将工作负载迁移到该数据库的工具。迁移工具中的一个细微 bug 可能会在数据进入一个本应将正确性置于首位的数据库时损坏数据。因此,我们关心的问题不是“每小时能合并多少个 PR?”,而是“这些 PR 中有多少是我们自己合并后会觉得难堪的?”经过这样的重新定义,结论很明显:为吞吐量优化的智能体群并不合适。

于是我们寻找这样一种模型:它在管理由能力参差不齐的员工所做的高风险工作方面有长期经验,有强制的交接、强制的二次确认,以及关于交接草率时会出现什么问题的文献。我们最终想到了教学医院。

这个比喻给了我们这些角色:

围绕这个核心,分布着你进入医院(或看过 The Pitt)后会见到的其他角色:一位每三十分钟巡视一次寻找滞留病人的护士长,一位在主分支崩溃时可以锁定医院内所有工作流的感染控制智能体,一位撰写每周流程复盘并运行事后复盘会议的安全部门,以及一个提出新工作的研究部门。

我们并不声称这比智能体群更便宜,也不声称它比单个智能体更简单。然而,它确实能比这两者产出更高质量的产品。当你身处数据库业务时,你愿意为这种质量买单。正如你将在下面看到的,它也带来了教学医院的一些令人遗憾的方面(官僚作风、等待时间和成本),但我们认为这些是值得的权衡。 

它是如何构建的Copy Icon

医院里的所有流程都跑在 GitHub Actions 上。每个病人的状态都保存在其 issue 的标签中,以及医院创建的一些临时文件里,用来跟踪每个病人的进展。

Each stage is a GitHub workflow that starts when its “label in” lands on the issue. When the agent finishes, it writes a “label out”, and that label is the next stage's “label in”. Dashed arrows send work back: a rejected plan to workup, a red CI run or requested changes to treatment, and a stuck stage up to the Chief.

每个阶段都是一个 GitHub 工作流,在它的“label in”标签落到 issue 上时启动。当智能体完成时,它会写入一个“label out”标签,而那个标签就是下一阶段的“label in”。虚线箭头将工作送回:被拒绝的计划回到检查阶段,红色的 CI 运行或请求的修改回到治疗阶段,卡住的阶段则上交给主治医生。

未显示:四个部门按各自的时间表在此流程之外运行。护士长重新运行停滞的阶段,感染控制在主分支崩溃时锁定医院,研究和安全部门提交新的 issue 和策略 PR,这些都像其他病人一样通过主治医生进入流程。

为某个 issue 打上标签会触发一个 GitHub 工作流,该工作流会加载一份角色专属的提示,使智能体像医院的员工一样工作。完成工作后,智能体会在包含该 issue 病历的文件中添加一条结构化备注,并打上流中的下一个标签。备注中带有一个类似 <!-- SINAI:TREATMENT_PLAN --> 的标记,以便后续的智能体能够查找并解析它。

几条基本的医院规则被编码在技能中,提供了大部分安全保障:

没有方案不写代码,没有审查不立方案。在 Fellow 修复任何问题之前,它必须复现问题,进行鉴别诊断(用医院术语来说),开展各种有针对性的调查来缩小范围,并发布一份治疗方案:诊断结果、待修改的文件、要新增的测试、风险以及待解问题。一位 Review Attending 是另一个智能体实例,运行的是以"挑刺"而非"修复"为核心构建的提示,它会阅读方案并批准或驳回。只有在此之后,治疗才会开始。我们加入这道关卡,是因为一个自信地实施错误修复的智能体,比一个停下来检查自己方案的智能体,浪费的时间和令牌要多得多。

不要即兴发挥。如果治疗过程中范围发生变化,Fellow 会停下来,发布修订后的方案,并回到方案审查环节。技能文件里的指示只有两个词:"不要即兴发挥。"

不走捷径,尤其在测试方面。智能体不得通过禁用、跳过、削弱或修改测试来让测试通过。如果一个测试失败,那它就是正确的,除非另有证明。如果智能体真心认为测试有误,那对测试的修改必须单独作为一个提交,单独证明其合理性,并与修复分开审查。同样,一个虽然通过但实际上并未真正覆盖所改之处的测试,比完全没有测试更糟糕。所有代码审查都以禁用该改动并确认所有新测试都失败作为开始。如果不是这样,那这些测试要么超出范围,要么是错误的。

不要胡乱挣扎。当 Fellow 卡住时,它不会更用力地尝试。它会撰写一份 I-PASS(病情严重程度、患者摘要、行动清单、情境意识、综合交接)交接单,这是一种直接借鉴自教学医院的结构化格式,然后升级给一位 Attending。接收方智能体在开始工作之前,必须先回写它对接单内容的理解。关于 I-PASS 的研究显示,在医院中如果医生和护士以标准化方式书写患者信息,轮班期间的不良事件可减少 50%。我们的智能体遵循同样的规范。

出院护士要审查审查者。合并之前的最后一道关卡并不会重新审查代码。它核查的是审查是否合规:是否存在批准、审查模板是否填写完毕、没有未解决的讨论线程、CI 是否为绿、提交历史是否干净。它会发布一份清单,列出它核查的所有内容;如果有任何一项无法打勾,出院就会失败,那一项保持未勾选状态并附上说明。设立这一规则的原因是,当你将控制权交给智能体时,你要确保它们正在按指令行事,而有时候它们并没有。

在打扰 Chief 之前先查看先例。人类 Chief 做出的每一条具有普适性的决策,都会被记录到一个仅可追加的先例日志中。想要升级的智能体必须先阅读该日志,如果存在适用的先例,它就会遵循该先例并引用它,而不是去升级。只有人类才能创建先例。五个月以来,这已经显著减少了智能体向 Chief 升级的频率。 

它交付的内容Copy Icon

当我们开始研发 MOLT Sinai 时,我们创建了一个镜像仓库,MOLT 工具就是在这个仓库中构建的。随后,这个镜像成为了一个试验场,让我们能够检验“医院”模型的有效性。以下是 4 月 21 日至 9 月 11 日期间该仓库中发生的事情:

在不到 5 个月的时间里,“医院”流水线成功提交了超过 100 万行代码,这些代码被拆分成足够小的变更,可以让 agents 有信心地进行审查。

你可能已经注意到上表中的一个奇怪数字:1299。在该仓库中创建的 issue 中,有近一半是由“医院”为“医院”自己创建的:分解出的子任务、Follower 从计划中剥离出来的后续工作、研究部门提出的提案、安全部门提出的整改措施。流水线会自行产生待办列表。由人来决定哪些工作被纳入,但从第二个月起,人就不再是工作的主要来源了。

虽然 Db2 支持是我们放入“医院”的第一个大型功能,但它仅仅是一次实验。在它自主运行的第一周结束后,我们开启了“人工审批模式”,该模式要求每一次合并都经过人工的审核。只有在这种模式下,我们才能确保代码质量足以交付给客户。有了这一额外的安全保障层,我们开始着手处理真正创建“医院”的目标:构建 Migration Assistant,这是一款基于 AI 的工具,可引导用户完成从 Postgres 到 CockroachDB 的完整迁移,包括 schema 转换、数据加载与校验,以及 routine 转换。Migration Assistant 目前已开放 Postgres 迁移的预览版,其代码几乎全部由“医院”员工编写。

它的成本Copy Icon

自今年 4 月 MOLT Sinai 开始运行以来,它在 Claude tokens 上的花费略高于 13.5 万美元,平均每个“病人”(即 GitHub issue)约为 84 美元。Issue 通常在“医院”待上 1 到 2 天,其中大部分时间花在了等待人工审查上。就 LLM 模型而言,随着模型的进步,“医院”也在不断演进。

Cost over time by workflow type

起初,“医院”在流水线的每个阶段都使用 Opus 模型,但随着 Fable 的引入,现在它使用 Fable 进行规划,由 Fable 生成的方案来选择实施阶段所使用的模型。当 Sonnet 或 Opus 模型在实施过程中遇到困难时,可以升级调用 Fable 模型。这有助于降低成本,同时在必要时仍然能够调用更昂贵(也更强)的模型。

如上图所示,每进行一次模型切换,都需要做些工作来调优 agent 的技能以优化成本。我们每隔几周就会进行一轮优化,这也是上图呈现锯齿状的原因之一。还有其他原因,例如某段时间内工作的组合(UI 任务成本更高,因为 agent 必须处理截图和其他视觉数据)以及我们 CI 环境中的一些怪癖,但这些细节过于琐碎,不适合在入门博客文章中展开。

“医院”自我构建Copy Icon

当我们创建 MOLT Sinai 时,我们把最初的工作流、技能和提示直接构建到了镜像仓库中。不久之后,我们意识到我们所构建内容的价值,并预判公司里的其他人也会希望使用它。为了让这种使用成为可能,我们将 "Sinai" 代码(实际的医院流水线)从 MOLT Sinai 仓库(医院流水线的第一个使用者)中拆分出来,做成一个独立的仓库,让其他人如果想使用医院模型,可以将其嵌入到自己的仓库中。如今,Sinai 仓库约有 1,570 次提交,其中大约 1,340 次——占 85%——都是由流水线自身提交的。 

我们最初并没有这样的计划。框架会出 bug,我们像对待其他任何问题一样把它们送进医院处理。每三十分钟查房一次的 Charge Nurse 工作流之所以存在,是因为一个 agent 在 GitHub Actions 标签事件中撞上了竞态条件,并把这个问题提成了 issue,随后另一个 agent 设计并交付了修复。这同时也是快速迭代医院模型最高效的方式——随着几乎每一位"患者"都顺利出院,整个医院模型已变得更加健壮、高效,也更易使用。

如今,Sinai 模型已经在 Cockroach Labs 的四个仓库中落地,另有数个仓库计划在未来几个月里采纳它。

指令变成了遗留代码Copy Icon

每个 agent 角色都由一份技能文件来定义:一份包含流程、规则、模板和禁止行为的 Markdown 文档。这些文档的编写方式与知识库通常的做法如出一辙——每当出现问题,就加上一条规则。

今年七月,我们对它们进行了审计。二十五个技能文件,总计略多于 100,000 词。其中大约 23,000 词,23%,被标记为可以在不改动任何 gate、命令、模板或 sentinel 的前提下删除。重复最多的模式是同一条规则在多处被复述:仅这一类就在七十项审计发现中累计占了六千词。其中有一个文件的冗余度高达百分之四十七。

这些冗余给我们带来了实实在在的金钱和时间成本。医院里的"护士"一次会同时加载多份技能。Discharge Nurse 在每一次运行开始时,都会先加载约 21,800 词——大约 29,000 个 token——的指令,之后才会去读取它本来要检查的 PR 内容。基础的 hospital-protocol 技能会被全部十五个 agent 加载,因此这一处的冗余代价最高。

我们怀疑这种情况并非 MOLT Sinai 所独有。我们的提示和代码一样腐化了:我们一直往里加规则,却从不删除,直到文件长到没人能从头读到尾。我们之所以注意到这个问题,还是因为 token 账单。目前我们还没有为此编写 linter,但很可能会很快开发一个。 

经验教训Copy Icon

围绕 MOLT Sinai(以及 Sinai 模型)展开五个月的工作让我们收获良多。以下是其中一些值得一提的部分。

有效之处Copy Icon

模型做了超出预期的事。每个角色的技能文件都以几句话来框定该角色,我们发现这几句话最有价值。Fellow 的开头是:"你是一名 Fellow,正在对一名入院患者(GitHub issue)进行诊断性检查。你的工作是彻底调查,并产出一份其他 Fellow 可执行的书面治疗方案。"Review Attending 的开头是:"你是与治疗该病例的 Fellow 不同的另一个智能体实例。你的工作是发现缺陷、安全问题、回归风险,以及与既定方案的偏离。"这奠定了基础,因为底层 LLM 非常清楚教学医院的运作方式、鉴别诊断是什么、为什么交接班有结构,以及第二意见意味着什么。当你让 LLM 扮演某个角色时,它会做得非常出色。例如,一位 Review Attending 在一个已经经过多轮审查的 PR 中发现了一行 docstring 的缺陷,并拒绝自行将其打回重写,而是写道:"我把这个问题提请您裁断,而不是在患者已经疲惫不堪的时候,仅因一处 docstring 的小修改就单方面把病例打回 Fellow。"在另一个案例中,一位 Attending 在决定是否重新拆分一个 Chief 已经裁定过的问题时写道:"一个前提已被结果推翻的、由 Chief 做出的决定,应返还给其所有者,而不是绕开它自行变通。"这些句子都没有直接出现在技能文件中。相反,是智能体在扮演角色时自己写出来的。 

先审方案,再看代码。被审查过的方案被拒的概率不到 10%,但一旦被拒,这就是系统中价值最高的审查,因为它把返工前移到了最早的阶段。例如,一项遥测修复的方案审查——要从我们采集到的日志中移除客户的主机名和数据库名——发现了一个信息仍可能被泄露的小缺口。如果这个问题没在方案审查阶段被发现,几乎不可能在代码审查中被发现,那么该问题要么会带着这个缺口上线,要么就需要再开一个 issue 来解决。在规划阶段就发现缺陷,能省下未来堆积如山的工作。

拆分,且设有硬性上限。如果检查评估认为改动将超过 1,000 行代码(含测试),Fellow 必须将其拆分为带有依赖关系图的子 issue。任何子 issue 本身若会超过 1,000 行,则再次拆分。设定这个上限的目的,是让每一次改动都足够小,使审查者——无论是智能体还是人类——都能在脑子里装下全部内容。Db2 冲刺从一个入院病例拆分出了 32 个 issue。成千上万行代码不可能由单个智能体(或人类)进行有说服力的审查,因此"医院"通过拆分来降低工作难度。

记录。每个医院病例都完整记录了它所产生的全部内容:检查方案;住院医师在动手写代码之前所写的复现测试,以及那次测试失败的过程记录;治疗方案及其每次修订;每一次 I-PASS 交接;每一份附带理由的审查结论;还有每个阶段的临床记录,注明所用模型、token 数、耗时与费用。这些记录存放在一个配套的归档分支中,与代码一并合并,从而使 PR 的 diff 保持只含代码,因此更便于审查。五个月后,我们仅凭工单讨论串就能轻松复现任意一份(共 1,238 份)PR 合并的原因,安全部门的每周审查之所以能够开展,也完全仰赖这条记录轨迹。严谨的记录工作,最终被证明是医院隐喻赋予我们的最强大工具之一。

我们已改进的,以及仍需改进的Copy Icon

模型有时做得过多。让各角色各司其职的同一套提示,也让它们变得官僚起来。一名住院医师对一个工单写出的方案仅仅是"关闭为重复;PR #691 已经发布了修复"。方案审查确认了此言不虚,却仍然将其上转,理由是"审查-主治-方案协议中记录的三种结果(通过 / 驳回 / 上转)并不包含'关闭为重复'"。一个已经确认的重复工单本应交由人类处理(这一点我们后来已经解决)。一处仅仅一行的 UI 文案修改——把副标题里的 "Validating" 改成 "Verifying"——却经历了五轮返工,一路闹到主任那里,就为了争论"低风险 PR 是否可以在不稳定的 CI 跑绿之前就合并"(智能体们常常在 CI 报错时仍试图合并,辩称那不是它们造成的)。一份代码审查在代码本身毫无缺陷的情况下阻断了 PR,理由仅仅是 PR 描述里有个小笔误。再有,当审查者留下非阻断性的细枝末节意见时,流水线又倾向于开新工单去跟踪它们(这一点也已修复),把一条小意见变成了一个新"病人"。这些问题我们已部分解决,但有些则是一个遵循自身规则、把质量置于首位之系统所必然要付出的代价。

审查需要一个熔断机制。工单 1777 被标记为紧急,而修复却只需要一行:在某条代码路径中调用一个已有函数——和相邻三条代码路径的做法一模一样。然而它竟经历了整整十一轮返工、耗时两天:先是因为注释规范,然后是 PR 描述里的一处不实陈述,再是与最初方案的小偏差,又是测试质量、两次 rebase、两次失败的工作流,以及一道人工审批关卡。任何超过三轮返工的工单都会触发一次安全审查;而在某一次安全审查窗口期内,三个复杂工单分别经历了九轮、九轮和七轮的返工,消耗了医院该窗口期总共 $1,046 预算中的约 $631。同一次安全审查记录到:代码审查流程缺少"可计数的发散熔断机制",因此发散的循环"要么很晚才上转,要么永远不上转"。该审查所提出的修复最近刚刚合并——当一份 PR 连续四轮返工每轮都冒出新的阻断性问题,或累计任意性质的实质性返工达到六轮时,审查者现在必须将其移交给主治。虽然这条规则合并时间尚短,我们也没有充分的数据来佐证其效果,但早期迹象看起来是积极的。

这家医院“产出”极高,而且并不总是好的一面。 这家医院在替自己找活儿这件事上产出极高。如前所述,几乎一半的问题是由 agent 提交的。调研部会扫描代码库并提出新功能,原本每三小时运行一次,它产生的问题量太大,我们根本来不及审阅是否准入。我们先把它限流到每天一次,然后把等待准入的提案数量上限设为十个。问题在于,它发现的问题大体上都是好的。我们经常遇到这种情况:在与交互式 Claude 会话一起排查代码库中的某个问题时,它把我们指向了几周(甚至几个月)前由这家医院提交的同一问题。即便如此,把所有这些问题都推过流程既费钱又耗时,而且价值存疑。从河流里淘到金子依然是难点,也是我们至今仍未解决的部分。

我们建了一家其实并不真正教学的“教学医院”。MOLT Sinai 完全符合你想象中两位资深工程师(合计拥有 40 多年行业经验)会造出来的东西。模型能力惊人,我们睡觉时它能持续不断地处理问题,等第二天早上我们快速审阅一遍。然后,我们就可以在开会的时候快速往医院里塞更多需要处理的问题,抽空审阅并在下班前“放行”。如今开上一整天的会,也能换来 10 个高质量合并的 PR。可是,那些日程并不紧凑、欠缺数十年经验才能积累的软件工程判断力、却又渴望深入学习如何成为更强工程师的应届生呢?模型对他们来说并不能完全满足需求。我们目前正在对医院做增强,让人类可以接手已经分诊过的问题,自己拟定方案,再交由医院的 agent 审阅。在审阅环节,他们需要为自己的决策辩护,并被引导去思考其他可能带来更全面解法的选项。类似地,当看到一份已被审阅过的方案时,人也可以选择自己去写代码(小修小补可以手写,或者在由 LLM 驱动的交互式编程会话协助下完成),从而学习如何在代码库中穿行、实时做出实现层面的取舍,并通过最终的代码评审被衡量水平。我们需要培养未来的软件工程师;不管十年后智能体编程的世界变成什么样,我们依然需要具备良好判断力和批判性思维的人。

后续步骤Copy Icon

我们持续致力于提高医院中 agent 的效率、速度和准确性。同时,我们也在努力让医院对初级工程师更具吸引力、更具教学价值、更有助于技能提升。对了,我们还在确认 IBM Db2 支持是否正确,因为在最初那次测试中,医院写出来的代码我们一份都没有审过。最后,我们正在进行一系列实验,探究至少在某些类别的问题上,让医院重新回到完全自主模式需要哪些条件。如果你有兴趣帮我们解决上述一个或多个问题,我们非常期待收到你的来信。

来源:Hacker News · cockroachlabs.com