2026/10/13 收录

Redesigning source control at Anthropic

#Scion#version control#monorepo#git#agentic coding#developer infrastructure
/

在 Anthropic,编码已不再是构建系统或开展研究的瓶颈。Claude 能够自主地迭代研究循环、对复杂程序进行微观优化,并以我们前所未见的速度和规模从零开始构建新服务。如今,真正的瓶颈是那些为人类规模而构建的开发者基础设施:源代码管理、构建系统和持续集成(CI)。

2026 年 5 月,Anthropic 平均每天合并约 2500 次代码变更;而到了 9 月,我们每天合并的代码变更已超过 30000 次。我们预计这种增长速度短期内不会放缓;对代码合并的需求几乎可以说是无限的,而我们每消除一个瓶颈,就会立即带来代码产出量的进一步提升。

2026 年 1 月至 9 月期间,每天合并到我们单一代码仓库(monorepo)的变更数量。

为了支撑这一增长,我们构建了 Scion——一个自研的版本控制系统,并在五个月内将整个 monorepo 从 git 完整迁移过来。在同一时间段内,我们还构建并上线了代码审查界面(命名为 Antique)、代码搜索、一套完善的源代码控制 IAM(Identity and Access Management)系统、管理并合入大量已审查代码的新方法等等。本文将重点介绍我们是如何构建 Scion 的。这是一份有趣的案例研究:Claude 的代码编写能力既是导致我们陷入这一局面的原因,也是我们能够快速构建一个高质量、可扩展系统来应对这一局面的关键。

扩展我们的单代码仓库(Monorepo)

在介绍我们构建的内容之前,有必要先说明我们的工作负载有多特殊。毫不意外,Anthropic 使用 Claude 编写软件的频率远超几乎所有其他公司,因此我们往往最先发现那些为人类规模而设计的工具在何处开始不堪重负。源代码管理(Source Control)就是其中一个例子。虽然我们预计短期内不会有多少组织遇到这些问题,但我们猜测,其他使用 AI 编程工具的团队不久后也会感受到同样的压力,这正是我们想分享所构建的方案与经验的原因。

Git 是一个去中心化的系统:每一个克隆都是一个完整、自包含的仓库,能够计算差异、读取历史与 blame 信息,并在完全离线的情况下检出任意版本。对于绝大多数仓库来说,这都是正确的设计,也正是 Git 成为标准的重要原因之一。

随着我们的单仓库(monorepo)不断增长,它越来越不适合这一设计空间。仅我们主分支的历史记录就接近 100 GB,而压缩后的工作副本超过 25 GB。我们每天为持续集成(CI)执行大约 2500 万次代码检出,这还不包括开发者环境、智能体沙箱(agent sandbox)和研究任务。如果简单粗暴地拉取,每天的 git 流量将超过 600 PB。

Git 提供了处理仓库部分内容的工具,例如浅克隆(shallow clone)、稀疏检出(sparse checkout)和 promisor 远程仓库(promisor remote)。除此之外,还有一些处理大型单体仓库(monorepo)的常见替代方案,比如内部 git 镜像和保存在 Blob 存储中的仓库快照(seed)。这些方法我们都用过——在最高峰时,我们运营着数百个内部 git 镜像来应对负载。这些做法为我们争取了时间,但随着合并频率的增加,它们变得越来越难以维护。每次刷新之间,seed 距离 main 分支的最新提交越来越远,而且要让 diff 和 blame 这类依赖历史记录的操作在部分克隆(partial clone)上正常工作,需要投入的精力也越来越多。

扩展 git 检出(checkout)的典型配置。

退一步看,我们发现,和大多数公司一样,我们把 git 当作一个集中式系统在用,只有一份权威副本;但实际上我们的几乎所有客户端都是智能体(agent)和作业(job),而它们本质上是始终在线的。git 的那些让它在分布式、离线开发场景中表现优异的特性,反而随着仓库不断增长,严重制约了我们的扩展能力。于是我们反问自己:如果从一开始设计源码管理时就把「智能体始终在线」这一事实考虑在内,它会是什么样子?

面向智能体规模的源码管理原则

在智能体编程(agentic coding)的浪潮下,许多关于软件开发方式的既有认知正在被重塑。在构建 Scion 的过程中,我们欣然拥抱了这些变化,这使得我们在若干核心原则上采取了与 git 截然不同的方法。大型单一代码库(monorepo)的源代码管理已有先例,其中最著名的是 Google 的 Piper)和 Meta 的 Sapling,2,因此我们在构建 Scion 时借鉴了它们来之不易的经验教训。我们的原则如下:

客户端始终在线。 Git(Git)假定客户端处于离线工作状态,需要仓库的完整副本。由于我们的客户端几乎全都是智能体(agent)和任务(job),我们围绕一个始终可达的服务器来设计 Scion 的客户端和协议,让客户端在需要时才去获取数据。

客户端与服务器的不对称性。 在 git 中,每个客户端都能充当功能完整的服务器。而在 Scion 中,服务器负责回答有关文件历史和 blame 的问题、创建提交、执行合并(merge)和变基(rebase)等操作。客户端则维护一个有界的本地对象缓存、将工作副本文件布局到磁盘上、监视文件系统变化,并为用户提供一种符合人体工学的方式与服务器协作。客户端自身几乎不进行计算,但作为交换,它在本地需要存储的数据要少得多。

一个全面的源代码管理服务可以驱动许多新颖的应用场景。在使用 Git 时,CI 作业或工具如果需要查询仓库相关信息——比如某一行最后是由谁修改的,或者两个提交之间有什么关系——就必须维护一份本地仓库副本,或者寄希望于供应商提供的专用 API 能给出答案。而在 Scion 中,客户端所使用的所有源代码管理查询同样通过 API 暴露出来,因此在许多情况下,作业根本不需要本地仓库。许多内部工具,例如我们的自动化二分(bisect)和回滚(revert)流水线,如果没有这种低成本的、可编程的源代码管理访问方式,根本无法在大规模场景下实际使用。

一切都必须能够水平扩展(horizontally scalable)。在业务高峰期,我们运行着数百个 git 镜像(git mirror),这对资源容量造成了巨大压力;而且一旦仓库规模超过了镜像的磁盘或内存限制,系统便无法继续扩展。而在 Scion 中,仓库的数据是分布式存储的,每个组件都可以独立扩展;系统中任何地方——包括服务器上——都不存在仓库的完整实体副本。由于没有类似 packfile(git 中用于压缩打包对象的机制)的东西,因此也就不需要进行任何仓库维护。

提交必须是廉价的、增量的、无锁的。 在 git 中,你先在本地提交,然后推送来分享你的工作成果。而在 Scion 中,每次提交、变基(rebase)或合并(merge)都发生在服务器上,因此无需推送,但这些操作都必须足够快。创建提交以及更新其下游数据结构的开销必须与变更的规模成正比,而不是与整个仓库的规模成正比,否则服务器将无法承受。Scion 的开发流程不鼓励使用引用(指向提交的指针,例如分支和标签),除非是主引用或发布分支,因为全局状态——即每个服务器都必须达成一致的状态——是一个迫在眉睫的可扩展性隐患。

工作副本是可选的,并且在设计上就是稀疏的。 由于我们的代码仓库非常庞大,而且每天为了 CI 需要检出代码数千万次,我们希望尽可能避免检出操作。以 Scion 为例,一个 CI 任务如果需要某个提交与其合并基(merge base)之间的差异,可以直接向服务器请求。写入操作也是如此:许多写入工作负载也可以跳过检出,完全通过 API 来完成。Scion 的协议围绕稀疏检出(sparse checkout)和虚拟化检出(virtualized checkout)设计——前者只包含客户端请求的目录,后者则按需物化文件,因此当任务确实需要磁盘上的文件时,客户端只需为实际读取的内容付出开销。

Scion 的工作原理

了解这些原则在构建 Scion 的过程中是如何实际结合在一起的,是很有帮助的。这些经验同样适用于其他源代码控制系统,或者更广泛地说,适用于软件开发生命周期的其他阶段。

数据结构

在 Scion 中,工作单元是「变更集(changeset)」,它是预写日志(write-ahead log)中的一条记录。变更集存储的内容大多与 git 中的提交类似。但 git 提交存储的是指向树(tree)的指针,而变更集存储的是一份相对于其第一个父提交发生变更的文件列表,1 以及指向 blob 存储中已压缩文件内容的内容寻址(content-addressed)指针。变更集在服务端创建,并能即时在所有地方可用。

Scion 中变更集(changeset)的形态。

这种结构使我们能够以极高的吞吐量接收变更集(changeset),因为无需接收打包文件(packfile)、无需加锁或跨服务器协调,也无需移动引用(ref)。Scion 在技术上确实支持引用,但由于创建和获取提交(commit)的过程都不涉及引用,因此移动引用只是对单个 Spanner 行执行一次比较并交换(compare-and-swap)操作,与创建变更集是分开进行的。

变更集只记录了发生变更的内容,因此仅凭它自身,无法快速回答诸如"谁最后编辑了某一行"或"哪些提交影响了某个文件"这样的问题。为了回答这些问题,Scion 会基于变更集构建额外的数据结构,包括用于文件溯源(file blame)和路径历史(path history)的专用结构,以及用于提交图(commit graph)的跳表(skip list)。我们将所有不属于变更集直接组成部分的数据统称为"派生数据(derived data)",并通过一种通用机制来计算全部派生数据。每种类型的派生数据都是递归定义的,因此某个变更集的派生数据值是由其父变更集的对应值计算得出的。

这种机制有三大好处。第一,当新的数据结构能让某些操作更快时,我们可以轻松地添加新的派生数据类型。第二,对于每种类型,我们可以选择立即计算还是延迟处理,从而避免生成不需要的数据。第三,由于客户端从不接触这些数据结构的内部实现,我们可以在不改动客户端的情况下对它们进行优化。随着系统的不断扩展,我们已经多次添加新的派生数据类型,例如用于执行副本检测或基于路径的文件搜索的新索引。

扩展 Scion

一个小型的 Scion 服务器集群为客户端所需的任何操作提供源代码控制(source control)API,例如获取变更集、计算差异(diff)、查找合并基(merge base)以及获取二进制大对象(blob)。所有源代码控制数据都以内容寻址(content-addressed)的方式存储,因此数据一旦存入便不会再改变,任何服务器都可以对其进行缓存,而无需与其他服务器协商。这样一来,这些服务器具备水平扩展(horizontally scalable)能力,且几乎不需要相互协调。它们本质上是大型缓存,同时还能即时计算派生数据,并将结果写回 Spanner。

Scion 的众多消费方不再需要各自维护本地代码仓库(repository)了。过去,获取并维护仓库对 CI 任务或需要向源代码控制提问的工具来说是一项不小的维护负担,尤其是涉及提交祖先关系和 blame 归属的查询。在迁移早期,在我们拥有自己的客户端、仓库格式或协议之前,Scion 只回答这类「元数据」查询。获取文件内容和物化工作副本(working copy)是数据量大得多的操作,它们需要一套专门的设计。

Scion 的 scn 客户端在几个重要方面偏离了 git 的 fetch(获取)协议,因此能够支持一种高度可扩展的架构,所需的 CPU 资源远少于 git 服务器。Scion 的协商机制不会告诉服务器客户端已经拥有什么;相反,它完全围绕客户端想要的树(tree)和数据块(blob)来运作。从根树开始,客户端会检查本地存储中缺失的树并递归地请求它们,然后对数据块重复这一过程。每个请求都是针对某个特定且永不改变的对象,因此任何缓存都可以直接响应,这种架构与传统的分层缓存非常相似。整个过程既高效又支持断点续传,因为不存在单个多 GB 大小的 packfile(打包文件)响应。稀疏检出(sparse checkout)甚至虚拟化检出(virtualized checkout)都不需要特殊支持,因为客户端根本不会请求它不想要的树。

在集群(fleet)中,客户端通过套接字(socket)与一个名为 scion-daemon 的守护进程(daemon)通信,该守护进程为整个节点存储对象。我们的大多数节点都需要访问单一代码仓库(monorepo),同一节点上的任务会复用大部分相同的对象。每个守护进程从一个定期刷新的快照(snapshot)启动,之后只与其所在区域的树(tree)和数据块(blob)缓存服务器通信,这些服务器称为 scion-cache。这些缓存服务器直接从内存中以守护进程将要消费的磁盘格式流式传输对象,因此无需花费时间计算客户端缺失的对象或执行增量压缩(delta compression)。当某个缓存缺少某个对象时,它会向权威的 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 预览版和Claude Tag在内部上线。这两项发布既放大了对 Scion 的需求, 也显著加快了我们的构建速度。

这里有一个格外引人注目的例子。今年早些时候,我们基本靠手写完成了 git 到 Scion 的摄取管道(ingestion pipeline)第一版原型。我们原本觉得它不错——直到我们拿到了 Claude Mythos Preview 的一个早期内部快照。我们让它重写整个管道,在没有任何额外指示的情况下,它产出的新版本内存占用减少到原来的二十分之一,速度提升了四倍,同时还完整保留了所有关于摄取顺序和数据结构的隐式不变量(implicit invariants)。

2026 年 1 月至 9 月期间,Scion 与 Antique 代码库每周合并的变更数量。

我们在构建和运维 Scion 的过程中积累的经验,为「智能体规模(agent-scale)」软件的构建方式提供了一些有意思的启示。

小团队快速开发将成为常态。 一个七人团队同时构建了 Scion 和 Antique(我们的代码审查工具,光是它就值得单独写一篇博客文章),更不用说还有一打相关系统,并在五个月内完成了全部迁移。在此期间,每天合并的代码量增长了 10 倍。在过去,开发和部署这样一个源代码控制系统(source control system)需要一家公司投入数年时间进行迭代迁移,并配备更多的工程师。我们认为,这种快速、灵活的迁移方式在未来将会更加普遍。

工程师的价值丝毫未减,但他们的工作正在发生变化。所有参与这套系统开发的工程师现在都会认为,Claude 在编写代码方面比他们更出色。但我们的工程师仍然提供了无比宝贵的高层级系统组件间关系的理解,以及设计层面的权衡判断——针对我们想要解决的问题和需要达到的规模,做出明智的选择。我们常常发现,工程师的角色就是对 Claude 说"不"。智能体(Agent)有时会为了满足某个本来就不该被允许的模式而违反核心系统约束。这在我们的迁移过程中尤为常见。例如,有多个系统试图通过线性读取提交日志来暴力查找某个变更何时合入 main 分支,而不是去询问我们的代码审查工具。Anthropic 还大量使用对抗性审查(adversarial review),虽然这种方式在很多方面极其有效,但也可能过于短视,因此需要经验丰富的工程师引导 Claude 避免因假设性的担忧而对系统的某些部分进行过度工程化。例如,其中一次审查对信任另一个内部服务表现得过于防御性,导致 Claude 添加了过多的验证逻辑和复杂度。我们的团队成员仍然会审查系统的每一个变更请求,但他们现在关注的是变更的*意图,而不是逐行阅读每一行代码。*

我们得以大幅提升优化程度和质量。 得益于 Claude 能够以远超我们自身速度编写海量代码的能力,Scion 成为我们有史以来测试最完善、防护最严密的代码库之一。即便时间紧迫,我们仍为所遇到过的每一个非平凡的基础设施交互和源码控制交互构建了封闭式模拟(hermetic simulation),还实现了模糊测试(fuzzing)和复杂的速率限制。Claude 还轻而易举地发现了一些重要的优化点——例如,它对祖先关系查询做了一项算法优化,而这个查询恰恰被我们的某个索引遗漏了——这类工作通常需要专家花费数天甚至数周进行分析和调优……而 Claude 第二天又把同样的事情重做了一遍。

在原生 Scion 代码库中观察到的性能提升。

我们为新型单体仓库(monorepo)安全机制奠定了基础。 在大规模单体仓库中管理安全性、安全防护与合规性,与管理其体量和规模同等重要。我们在设计 Scion 之初就将这些因素纳入考量。它支持对仓库进行细粒度的访问控制,并能将权限应用到单个目录,从而在不牺牲单体仓库优势的前提下,保护最敏感的代码。此外,我们构建的代码审查工具能够对审批施加复杂的语义约束,使我们可以区分仅修改注释这类简单变更与涉及敏感逻辑的变更,从而让人工审查者能够专注于影响最大的改动。

运营卓越可以达到全新的水平。我们团队大量使用 Claude Tag,它帮助我们监控系统的每一个组件。例如,我们创建了 Slack 频道,让 Claude 专注于某一个组件的健康状况。我们把这些 Claude 称为"超级粉丝"。我们有一个 scion-daemon 超级粉丝、一个 scion-cache 超级粉丝、一个 Antique 合并队列超级粉丝,等等。它们可以访问我们的遥测(telemetry)数据和日志,表现得就像一位经验丰富、闲来无事只能不断刷新图表、寻找任何异常的工程师。我们当然也为所有系统设置了传统告警,但我们的超级粉丝能够发现那些细微而新奇的问题,相比之下,这些告警显得过于单一了。

我们正在彻底反思我们的既有假设。这个项目促使我们重新审视那些为人类规模设计的其他工作流程。例如,一个规范的合并队列(merge queue)在智能体规模下并没有太大意义。当数百甚至数千个变更可以在测试运行期间合并时,CI(持续集成)究竟意味着什么?我们一直无法有效复刻业界在合并队列上采用的全局仓库锁(global repository lock)这一标准做法,因此我们转而投入构建合并后(post-merge)和部署前(pre-deploy)的自动化测试。这确实意味着并发变更可能产生冲突,但 Claude 通常修复这些冲突的速度比人类发现它们的速度还要快。

为更广泛的生态做出贡献

我们认为,Scion 只是应对日益强大的 AI 智能体(AI agents)所引发的扩展性问题的方案之一。我们分享这些细节,是希望为团队解决类似挑战提供一个参考范例,当然我们并不期望我们的解决方案或考量适用于所有情况。

Scion 绝非一个适合替代 git 用于日常通用开发的方案。我们在此概述的这些概念,真正适用的场景只有极其庞大的单体代码仓库(monorepo)——要么拥有数以万计的工程师,要么是成群的智能体(agent)在大规模地编写代码。此外,Anthropic 的独特之处在于,我们在内部使用上所能消耗的令牌(token)数量,远远超过其他任何企业实际愿意投入的规模。我们这样做,一方面是为了满足自身软件开发的需求,另一方面也是为了测试 Claude 的能力。

实际上,我们很难以一种对其他组织普遍有用的形式来提供 Scion。它与 git 差异很大,这意味着企业需要迁移开发流程中的每一个环节,而且它还需要配套的代码审查、代码搜索和 CI 系统。我们内部的这些组件都是高度定制的,其他组织很难直接采用;同时我们也担心,试图构建一个取悦所有人的公开版本,最终可能并不会非常实用。

然而,我们预计这类问题在未来六到十二个月内会变得更加普遍,而让每个使用 git 的公司都独自面对类似的挑战,将是对工程资源的低效利用。鉴于此,我们认为最有价值的 做法是分享我们的思路,以便其他开发者能够在不偏离整体模型的前提下,将 Scion 中最成功的部分应用于 git。git 服务器不必与 git 客户端以完全相同的方式工作;已经存在一些新颖的服务器架构,能够带来显著的扩展性提升。Git 平台可以像 Scion 那样提供全面的服务端 RPC(远程过程调用),甚至可以跨 forge 平台以标准化的方式实现,让 git 客户端学会与之通信。协商协议也不必完全保持今天的样子,git 中各种稀疏检出(sparse)和浅克隆(shallow)特性也可以被良好地组合起来。

最后,我们想说明的是,git 社区已经花费了近二十年时间,为数百万开发者扩展这一平台。我们的贡献要有限得多:分享我们对智能体(agentic)软件开发如何与这些系统交互的早期观察,以便我们能与 GitHub、GitLab 及开源社区携手合作,支持开发者不断变化的需求。当软件开发生命周期与人工智能的指数级增长相遇时,我们都在同一条船上。