跳到正文

重新设计 Anthropic 的源代码管理

本文目录文章开头

在 Anthropic,编码已不再是构建系统或开展研究的瓶颈。Claude 能够自主迭代研究循环、对复杂程序进行微优化,并以前所未有的速度和规模从零开始构建新服务。如今的瓶颈在于所有为人类规模而构建的开发者基础设施:源代码控制、构建和 CI。

2026 年 5 月,Anthropic 平均每天合并约 2,500 处代码变更;到 9 月,我们每天合并超过 30,000 处。我们预计这种增长速度短期内不会放缓;合并代码的潜在需求几乎是无限的,我们每消除一个瓶颈,都会立即带来代码产出量的进一步增长。

2026 年 1 月至 9 月每日合并到我们 monorepo 的变更数。

为了支持这种增长,我们构建了 Scion——一个内部版本控制系统,并在五个月内将我们的 monorepo 完全从 git 迁移过来。我们还在同一时间段内构建并发布了一个代码审查界面(名为 Antique)、代码搜索、一套丰富的源代码控制 IAM 系统、管理并落地大量已审查代码的新方法等等。在本文中,我们将重点介绍我们如何专门构建了 Scion。这是一个有趣的案例研究:Claude 编写代码的能力既是我们陷入这种境地的原因,也是我们能够快速构建一个高质量、可扩展的系统来应对它的手段。

我们的 monorepo 不断增长

在探讨我们构建了什么之前,我们应该先承认我们的工作负载有多么不同寻常。毫不意外,Anthropic 使用 Claude 编写软件的频率远超几乎所有其他公司,因此我们往往最先看到为人类规模构建的工具开始捉襟见肘。源代码控制只是其中一个例子。虽然我们预计许多组织在短期内不会遇到这些问题,但我们怀疑其他使用 AI 编码工具的人不久后也会经历同样的痛点,这就是为什么我们想分享我们构建的东西以及我们的经验教训。

Git 是一个去中心化系统:每个克隆都是一个完整、自包含的仓库,可以计算差异、读取历史和 blame,并且可以完全离线检出任何修订版本。对于绝大多数仓库来说,这是正确的设计,这也是 git 成为标准的很大一部分原因。

随着我们的 monorepo 增长,它越来越不符合那个设计空间。仅我们主分支的历史就接近 100 GB,而一个压缩后的工作副本超过 25 GB。我们每天为 CI 执行约 2500 万次代码检出,这还不包括开发人员环境、智能体沙盒和研究任务。如果天真地进行获取,每天的 git 流量将超过 600 PB。

Git 提供了处理仓库切片的工具,例如浅克隆、稀疏检出和 promisor 远程仓库。除此之外,还有针对大型 monorepo 的标准变通方法,比如内部 git 镜像和保存在 blob 存储中的仓库快照(seeds)。我们全都用过——在高峰期,我们运行着数百个内部 git 镜像来应对负载。这为我们争取了时间,但随着合并率的提高,它们变得越来越难维护。Seeds 在刷新之间离主分支的顶端越来越远,而且要让依赖历史记录的操作(如 diff 和 blame)在部分克隆上正常工作,需要越来越多的细心维护。

扩展 git 检出的典型设置。

退一步来看,我们注意到,与大多数公司一样,我们把 git 当作一个只有一份权威副本的集中式系统来使用,但我们的几乎所有客户端都是代理和作业,它们按定义就是在线的。使 git 在分布式、离线工作中表现出色的特性,实际上正在损害我们随着仓库增长而扩展的能力。于是我们问自己:如果我们从一开始就以代理始终在线这一事实为出发点来设计源代码控制,它会是什么样子?

面向代理规模的源代码控制原则

在代理式编码(agentic coding)中,关于软件开发方式的许多常见假设正在演变。我们在构建 Scion 时拥抱了这些变化,这使我们能够在几个核心原则上采取与 git 截然不同的方法。超大型 monorepo 的源代码控制已有先例,尤其是 Google 的 Piper 和 Meta 的 Sapling,2 因此我们能够在构建 Scion 时借鉴他们来之不易的经验教训。我们的原则是:

客户端始终在线。Git 假设客户端离线工作,并且需要仓库的完整副本。由于我们几乎所有的客户端都是代理和作业,我们围绕一个始终可达的服务器来设计 Scion 的客户端和协议,并让客户端在需要时获取数据。

客户端-服务器不对称。在 git 中,每个客户端都有能力成为一个功能齐全的服务器。在 Scion 中,服务器负责回答关于文件历史和 blame 的问题、创建提交、执行合并和变基(rebase)等操作。客户端维护一个有界的本地对象缓存、在磁盘上布置工作副本文件、监控文件系统变化,并为用户提供一种符合人体工学的方式来与服务器协作。它自身几乎无法进行任何计算,但作为交换,它在本地所需的数据要少得多。

全面的源代码控制服务可以驱动许多新颖的应用。使用 git 时,需要向仓库提问的 CI 作业或工具(例如某行代码最后由谁修改,或两个提交之间有何关联)必须维护一份仓库的本地副本,或者寄希望于厂商特定的 API 能提供答案。使用 Scion 时,客户端使用的所有源代码控制查询也都通过 API 暴露,因此在许多情况下,作业根本不需要本地仓库。许多内部工具(例如我们的自动化二分(bisect)和回滚(revert)流水线)如果没有这种低成本、可编程的源代码控制访问方式,在规模上是无法实现的。

一切都必须能够水平扩展。在我们的高峰期,我们运行着数百个 git 镜像,这严重消耗了容量,并且一旦仓库超出了镜像的磁盘或内存限制,就无法再扩展。在 Scion 中,仓库的数据是分布式的,每个组件都可以独立扩展;任何地方都没有仓库的完全物化副本,即使在服务器上也是如此。没有 packfile(git 的压缩对象包)的等价物,因此不需要进行仓库维护。

提交必须是廉价的、增量的、无锁的。在 git 中,你在本地提交,然后推送以共享你的工作。在 Scion 中,每次提交、变基或合并都发生在服务器上,因此没有东西需要推送,但这些操作中的每一个都必须快速。创建提交及其更新下游数据结构的成本必须随变更的大小而扩展,而不是随仓库的总体大小而扩展,否则服务器将无法跟上。Scion 的开发生命周期不鼓励使用 refs(指向提交的指针,如分支和标签),除了主 ref 或发布分支之外,因为每个服务器都必须达成一致的全局状态是一个迫在眉睫的扩展性问题。

工作副本是可选的,并且在设计上就是稀疏的。因为我们的 monorepo 非常庞大,而且我们每天要为 CI 检出代码数千万次,所以我们希望尽可能避免检出。使用 Scion,例如需要一个提交与其合并基点之间差异的 CI 作业,可以向服务器请求它。写入也是如此:许多写入工作负载也可以跳过检出,完全通过 API 运行。Scion 的协议以稀疏检出(sparse checkouts)为中心,它们只包含客户端请求的目录,以及虚拟化检出(virtualized checkouts),它们按需实例化文件,因此当作业确实需要磁盘上的文件时,客户端只需为其实际读取的内容付费。

Scion 的工作原理

了解这些原则在构建 Scion 时如何在实践中结合在一起是很有帮助的。这些见解可以应用于其他源代码控制系统,或者更广泛地说,应用于软件开发生命周期的其他阶段。

数据结构

在 Scion 中,工作单元是一个“变更集”(changeset),它是预写日志(write-ahead log)中的一个条目。变更集存储了你在 git 提交中期望的大部分内容。但是,git 提交存储的是指向树的指针,而变更集存储的是相对于其第一个父级的已更改文件列表,1以及指向 blob 存储中压缩文件内容的内容寻址指针。变更集在服务器端创建,并且立即在所有地方可用。

Scion 中变更集的形状。

这种结构使我们能够以非常高的吞吐量接受变更集,因为没有 packfile 需要接受,没有锁定或跨服务器协调,也没有 refs 需要移动。Scion 在技术上确实支持 refs,但由于创建和获取提交都不涉及它们,因此移动一个 ref 是对单个 Spanner 行的比较并交换(compare-and-swap),它与创建变更集分开发生。

变更集只记录发生了什么变化,因此它本身无法快速回答诸如谁最后编辑了某一行或哪些提交影响了某个文件之类的问题。为了回答这些问题,Scion 从变更集构建了额外的数据结构,包括用于文件 blame 和路径历史的专用结构,以及用于提交图的跳表(skip lists)。我们将所有不是直接属于变更集的内容称为“派生数据”(derived data),并且我们通过一个通用机制计算所有这些数据。每种类型的派生数据都是递归定义的,因此它对某个变更集的值是根据其对该变更集父级的值计算得出的。

该机制有三大好处。首先,当新的数据结构能使某些操作更快时,我们可以轻松添加新类型的派生数据。其次,对于每种类型,我们可以选择是立即计算还是延迟计算,从而避免物化我们不需要的数据。第三,由于客户端从不接触这些数据结构的内部实现,我们可以在不改变客户端的情况下对它们进行优化。随着系统的增长,我们多次添加了新类型的派生数据,例如用于执行重复检测或基于路径的文件搜索的新索引。

扩展 Scion 规模

一小群 Scion 服务器为客户端所需的任何操作提供源代码控制 API,例如获取变更集、计算差异、查找合并基点以及获取 blob。所有源代码控制数据都是内容寻址的,因此数据一旦存储就不会改变,任何服务器都可以缓存它而无需与其他服务器协调。因此,这些服务器可以水平扩展,几乎不需要协调。它们本质上是大型缓存,还可以即时计算派生数据并将结果写回 Spanner。

Scion 的众多消费者不再需要维护自己的本地仓库。获取和维护仓库过去是 CI 作业或需要查询源代码控制问题(尤其是关于提交祖先关系和 blame)的工具的一项重大维护负担。在迁移早期,在我们拥有自己的客户端、仓库格式和协议之前,Scion 只回答这些“元数据”查询。获取文件内容和物化工作副本是数据密集得多的操作,它们需要自己的设计。

Scion 的 scn 客户端在几个重要方面偏离了 git 的获取协议,因此它适用于一种高度可扩展的架构,所需的 CPU 远少于 git 服务器。Scion 的协商不会告诉服务器客户端已经拥有什么;相反,它完全基于客户端想要的树和 blob 进行操作。从根树开始,客户端检查其本地存储中缺失的树并递归请求它们,然后对 blob 重复此过程。每个请求都是针对一个永不改变的特定对象,因此任何缓存都可以响应它,该架构与传统的分层缓存非常相似。该过程也是高效且可恢复的,因为不存在单个数 GB 的 packfile 响应。稀疏甚至虚拟化的检出不需要特殊支持,因为客户端根本不会请求它不想要的树。

在集群中,客户端通过套接字与守护进程 scion-daemon 通信,该守护进程为整个节点存储对象。我们的大多数节点都需要访问 monorepo,同一节点上的作业会重用大部分相同的对象。每个守护进程都从定期刷新的快照开始,然后只与其区域内的树和 blob 缓存服务器(称为 scion-cache)通信。这些缓存服务器直接从内存中流式传输对象,采用守护进程将要消费的磁盘格式,因此它们不需要花费时间计算客户端缺失的对象或执行增量压缩。当缓存缺少某个对象时,它会询问权威的 scion-servers,这些服务器会根据需要从 Spanner 或云存储中读取。

scion-daemon、scion-cache 和 scion-server 都响应此协议的相同 RPC,因此客户端无需了解缓存拓扑。理论上任何层级都可以与任何其他层级通信,但我们严格强制执行分层,以隔离故障域。我们希望避免出现这样的情况:某个区域的 scion-cache 发生故障,导致上游 scion-server 过载,进而使整个系统崩溃。

Scion 的检出架构。

该系统的每一层都保持超过 99% 的缓存命中率,且不需要长期状态,因此在需要时可以轻松扩展。就 CPU 和内存需求而言,我们现在的 Scion 基础设施占用比之前的 git 镜像基础设施小 200 倍。CI 和研究任务检出代码的速度快了两个数量级(以秒而非分钟计),这在整个集群中释放了大量容量;仅 CI 就实现了 25% 的 CPU 容量节省。客户端和缓存都不需要执行仓库维护,这简化了我们任务的生命周期管理。

构建 Scion 的经验教训

我们开始构建 Scion 的时间,大约与 Claude Mythos Preview 和 Claude Tag 在内部可用的时间相同。这两个版本的发布同时放大了对 Scion 的需求,并显著加快了我们构建它的速度。

这是一个特别引人注目的例子。今年早些时候,我们基本上是手工编写了 git 到 Scion 摄取管道的第一个版本作为原型。我们认为它还不错——直到我们拿到 Claude Mythos Preview 的早期内部快照。我们让它重写整个东西,在没有任何进一步指示的情况下,它生成的版本内存消耗减少了 20 倍,速度快了 4 倍,同时保留了围绕摄取顺序和数据结构的所有隐式不变量。

2026 年 1 月至 9 月,Scion 和 Antique 代码库每周合并的变更。

我们在构建和运营 Scion 过程中学到的经验,为构建智能体规模的软件可能是什么样子提供了一些有趣的启示。

小团队的快速开发将变得更加普遍。一个七人团队构建了 Scion 和 Antique(我们的代码审查工具,我们完全可以就此再写一篇博客文章),更不用说还有半打相关系统,并在五个月内完全迁移到这些系统上。我们在每天合并的代码量增加 10 倍的情况下完成了所有这些工作。过去,开发和部署这样一个源代码控制系统,需要一家公司数年的迭代迁移,并且需要更多的工程师。我们认为,这种快速、灵活的迁移在未来将变得更加普遍。

工程师的价值并未降低,但他们的工作方式正在改变。所有参与这个系统开发的工程师如今都会认为 Claude 在编写代码方面比他们更强。但我们的工程师仍然提供了不可或缺的理解——关于高层系统间组件以及设计层面的权衡取舍,围绕我们想要解决的问题和已知需要达成的规模做出明智选择。我们常常发现,工程师的角色是告诉 Claude "不"。智能体有时会违反核心系统约束,去满足一个本就不该被允许的模式。这在我们的迁移过程中尤为常见。例如,有多个系统试图通过线性读取提交日志来暴力查找某次变更何时合并到 main,而不是询问我们的代码审查工具。Anthropic 也大量使用对抗性审查,虽然这在许多方面极其有效,但也可能目光短浅,因此需要熟练的工程师引导 Claude 避免因假想的担忧而对系统进行过度设计。例如,某次审查对信任另一个内部服务过度设防,导致 Claude 添加了过多的验证和复杂性。我们的团队成员仍然审查系统的每一个变更请求,但现在他们审查的是变更的意图,而不是逐行阅读代码。

我们在优化和质量方面取得了显著提升。凭借 Claude 比我们自己更快编写海量代码的能力,Scion 成为我们有史以来测试最充分、防护最严密的代码库之一。即使在紧张的时间表下,我们也能够为遇到过的每一个非平凡的基础设施和源代码管理交互构建封闭式模拟,以及模糊测试和复杂的速率限制。Claude 还轻松发现了重大的优化——例如,一个针对祖先查询的算法优化,它发现我们遗漏了一个索引——这需要专家花费数天或数周的分析和调优……然后第二天又全部重做了一遍。

图片未能保存,请查看原文。

在原生 Scion 仓库上观察到的性能提升。

我们为新型 monorepo 安全奠定了基础。在大型 monorepo 中管理安全性、防护和合规性,与管理其规模和体量同样重要。我们在构建 Scion 时从一开始就考虑了这些因素。它支持仓库的丰富访问控制,以及对单个目录应用权限的能力,从而在不失去 monorepo 优势的情况下保护最敏感的代码。此外,我们构建的代码审查工具能够表达复杂的审批语义约束,使我们能够区分仅注释变更这类简单情况与敏感的逻辑变更,从而让人工审查者能够专注于影响最大的变更。

运营卓越的新境界已成为可能。我们的团队大量使用 Claude Tag,这帮助我们监控系统的每个组件。例如,我们创建了 Slack 频道,让 Claude 专注于单个组件的健康状况。我们称这些 Claude 为“超级粉丝”。我们有一个 scion-daemon 超级粉丝、一个 scion-cache 超级粉丝、一个 Antique 合并队列超级粉丝,等等。它们可以访问我们的遥测数据和日志,表现得像一位经验丰富的工程师,除了刷新图表、寻找任何异常之外无事可做。我们所有的系统也都有传统告警,但我们的超级粉丝能够检测到细微而新颖的问题,相比之下,这些告警显得过于单一。

我们正在彻底重新审视我们的先验假设。这个项目促使我们非常认真地审视其他为人类规模设计的工作流程。例如,一个合适的合并队列在智能体规模下并没有太大意义。当数百甚至数千个变更可以在测试运行期间合并时,CI 到底意味着什么?我们无法有效地复制行业标准的全局仓库锁合并队列,因此,我们转而投资于合并后和部署前的自动化测试。这确实意味着并发变更可能会产生冲突,但 Claude 通常能比人类更快地修复这些冲突。

为更广泛的生态系统做出贡献

我们视 Scion 为应对日益强大的 AI 智能体所引发的扩展性问题的答案之一。我们分享这些细节是作为团队如何应对类似挑战的示例,尽管我们并不期望我们的解决方案或我们的考量适用于所有情况。

Scion 绝非适用于通用场景的 git 替代品。我们在此概述的概念只对真正庞大的 monorepo 才有意义,这些仓库要么拥有数万名工程师,要么拥有成群的智能体在大量编写代码。此外,Anthropic 的独特之处在于,我们为内部使用而消耗 token 的能力远远超过任何其他企业现实上愿意花费的水平。我们这样做既是为了满足自身的软件开发需求,也是为了测试 Claude 的能力。

实际上,很难以一种对其他组织广泛有用的形式提供 Scion。它与 git 存在显著差异,这意味着公司需要迁移开发生命周期的每一个环节,并且它需要自己的代码审查、代码搜索和 CI。我们内部的这些组件版本高度定制化,其他组织很难采用,而且我们担心,试图构建一个试图取悦所有人的公开版本实际上并不会非常有用。

然而,我们预计此类问题在未来六到12个月内将变得更加普遍,而让每一家使用 git 的公司独自面对类似的挑战,将是对工程资源的低效利用。鉴于此,我们认为最有影响力的 做法是分享我们的思考,以便其他开发者能够在不偏离整体模型的情况下,将 Scion 最成功的部分应用到 git 中。git 服务器不必以与 git 客户端相同的方式工作;已存在能够提供显著扩展性改进的新型服务器架构。git 平台可以提供像 Scion 那样的全面服务器端 RPC,甚至可能以跨代码托管平台的标准化方式实现,让 git 客户端学会与之通信。协商协议也不必与当前的完全相同,git 中的各种稀疏(sparse)和浅层(shallow)特性也可以被设计成能够良好组合。

最后,我们想要承认,git 社区已经花费了近二十年时间为数百万开发者扩展该平台。我们的贡献要狭窄得多:分享我们对智能体(agentic)软件开发如何与这些系统交互的早期看法,以便我们能够与 GitHub、GitLab 和 OSS 社区合作,支持开发者不断变化的需求。随着软件开发生命周期与 AI 指数级增长相交汇,我们都在同一条船上。

阅读显示设置

调整正文排版

当前浏览器记住选择,应用于文章正文。手机上正文宽度以屏幕可用空间为限。

图表完整性提示

普通文章遇到图表保存问题时显示提示。