重新设计 Anthropic 的源代码控制系统
Anthropic 每天的合并次数涨了十倍以上,于是团队用五个月把基于 git 的方案换成了自研的版本控制系统 Scion,并给出了各项操作到底快了多少的数字。
中文
复制

随着我们每天的合并次数增长十倍以上,源代码控制成了我们最大的开发瓶颈。这里我们分享如何在五个月内迁移到一套自研的版本控制系统。
在 Anthropic,写代码已不再是构建系统或开展研究的瓶颈。Claude 能够自主推进研究循环、对复杂程序做微优化、从零搭建新服务,速度和规模都是我们前所未见的。如今的瓶颈,是为人类规模而建的那一整套开发者基础设施:源代码控制、构建和 CI。2026 年 5 月,Anthropic 平均每天合并约 2,500 个代码变更;到 9 月,我们每天合并超过 30,000 个。我们不认为这个增速会在短期内放缓;合并代码的潜在需求几乎是无限的,而我们每消除一个瓶颈,代码产出量立刻就会再上一个台阶。

为了支撑这样的增长,我们自研了 Scion 这一内部版本控制系统,并在五个月内把 monorepo 从 git 完整迁移过来。同一时期,我们还构建并上线了代码评审界面(名为 Antique)、代码搜索、一套功能完善的源代码管理 IAM 系统、管理并合入大量已评审代码的新方法等等。本文只聚焦 Scion 本身是怎么做出来的。这是个挺有意思的案例:Claude 写代码的能力,既是最初把我们推入这个处境的原因,_也_是我们能迅速搭出一套高质量、可扩展系统来应对它的原因。
monorepo 的增长
在展开我们做了什么之前,先要说明我们的负载有多特殊。不出意外,Anthropic 用 Claude 写软件的程度远超几乎所有其他公司,所以我们常常是最早撞上「为人类规模设计的工具开始吃紧」的那批人。版本控制只是其中一个例子。我们不认为很多组织在近期就会遇到这些问题,但用 AI 编码工具的其他人撞上同样的压力点,大概也不会太久,这也是我们想分享自己做了什么、学到了什么的原因。
Git 是去中心化系统:每个 clone 都是一个完整、自包含的仓库,可以计算 diff、读取历史和 blame、检出任意版本,全程离线。对绝大多数仓库来说,这是正确的设计,也是 git 成为标准的重要原因。
随着我们的 monorepo 增长,它越来越不落在这一设计区间内。仅主分支的历史就接近 100 GB,_压缩后_的工作副本也超过 25 GB。我们每天为 CI 执行的代码检出量在 2500 万次量级,这还没算开发者环境、agent 沙箱和研究任务。如果天真地全量拉取,每天会是 600 PB 以上的 git 流量。
Git 提供了一些只操作仓库一部分的工具,比如 shallow clone、sparse checkout 和 promisor remote。除此之外,大型 monorepo 还有一些常规变通做法,比如内部 git mirror,以及存放在 blob 存储里的仓库快照(seed)。这些我们全都用过——高峰时期我们运行着几百个内部 git mirror 来扛住负载。这为我们争取了时间,但随着合入速率上升,它们越来越难运维。两次刷新之间,seed 离 main 的顶端越来越远,而要让 diff、blame 这类依赖历史的操作在部分克隆上正常工作,需要投入的精力也越来越多。

退一步看,我们注意到,和大多数公司一样,我们把 git 当作集中式系统来用,只有一份权威副本;但我们的客户端几乎全是 agent 和 job,而它们按定义就是始终在线的。git 之所以在分布式、离线场景下表现出色,那些特性恰恰在仓库不断变大时拖累了我们的扩展能力。于是我们问自己:如果从一开始就把 agent 始终在线这一事实纳入源码控制的设计,它会是什么样子?
agent 规模下的源码控制原则
在 agentic coding 的背景下,关于软件开发方式的许多常见假设正在改变。构建 Scion 时我们接受了这些变化,这让我们得以在几条核心原则上采取与 git 根本不同的做法。面向超大型 monorepo 的源码控制已有先例,尤其是 Google 的 Piper 和 Meta 的 Sapling,因此我们在构建 Scion 时能够借鉴它们来之不易的经验。我们的原则是:
客户端始终在线。 git 假设客户端离线工作,并且需要一份完整的仓库副本。由于我们的客户端几乎全是 agent 和 job,Scion 的客户端与协议围绕一个始终可达的服务器来设计,客户端在需要时才去取数据。
客户端与服务器不对称。 在 git 中,每个客户端都能充当一个完整的服务器。在 Scion 中,服务器负责回答关于文件历史和 blame 的问题、创建 commit、执行 merge 和 rebase 等等。客户端维护一个有界的本地对象缓存,把工作副本文件落到磁盘上,监听文件系统变化,并为用户提供一套顺手的服务器操作方式。它自身能计算的东西很少,但换来的好处是本地所需的数据量小得多。
一套完整的源码控制服务可以支撑许多全新的应用。 在 git 中,一个 CI job 或工具如果想查询仓库的某个问题,比如某一行最后是谁改的、两个 commit 之间是什么关系,就必须维护一份本地仓库副本,或者指望厂商特定的 API 能给出答案。在 Scion 中,客户端用到的所有源码控制查询都通过 API 暴露出来,因此很多情况下 job 根本不需要本地仓库。许多内部工具,比如我们自动化的 bisect 和 revert 流水线,如果没有这种廉价、可编程的源码控制访问方式,在规模上根本不可行。
一切组件都必须能横向扩展。 在高峰期,我们运行着几百个 git 镜像,容量被压得很紧;当仓库的体积超出镜像的磁盘或内存上限后,这套做法就再也扩不动了。在 Scion 中,仓库数据是分布式的,每个组件都能独立扩展;任何地方都不存在仓库的完整物化副本,服务器上也没有。这里没有 packfile(git 用来打包对象的压缩文件)的对应物,因此不需要做仓库维护。
提交必须廉价、增量、无锁。 在 git 里,你在本地提交,然后 push 出去分享成果。在 Scion 里,每一次提交、rebase 或 merge 都发生在服务器上,所以没有东西需要 push,但这些操作每一个都必须足够快。创建一次提交、更新其下游数据结构的开销,必须随改动的大小扩展,而不是随仓库的整体规模扩展,否则服务器根本扛不住。Scion 的开发流程不鼓励使用 ref(指向提交的指针,例如分支和标签),只有 main ref 或发布分支例外,因为所有服务器都必须达成一致的_全局_状态,是一个迟早会爆的扩展性问题。
工作副本是可选的,而且天生稀疏。 我们的 monorepo 太大,加上 CI 每天要检出代码几千万次,所以只要能避免检出,我们就想避免。在 Scion 中,一个 CI 任务如果需要某个提交与其 merge base 之间的 diff,可以直接向服务器要。写操作也一样:很多写入负载同样可以跳过检出,完全通过 API 完成。Scion 的协议围绕稀疏检出和虚拟化检出设计:前者只包含客户端请求的目录,后者按需物化文件。因此当任务确实需要文件落到磁盘上时,客户端只为真正读取的内容付出代价。
Scion 如何工作
了解这些原则在构建 Scion 时是如何落到实处的,会很有帮助。这些经验也适用于其他源码管理系统,或者更广泛地说,适用于软件开发周期的其他阶段。
数据结构
在 Scion 中,工作单位是“changeset”,即预写日志中的一条记录。changeset 存储的内容,大多与 git 中的一次 commit 相当。但 git commit 存的是指向 tree 的指针,changeset 存的则是相对于其第一个父节点发生变化的文件列表,1 以及指向 blob 存储中压缩文件内容的内容寻址指针。changeset 在服务端创建,随处可用,且立即可用。

这种结构让我们能以极高的吞吐量接收 changeset,因为无需接收 packfile,没有锁定或跨服务器协调,也不用移动 ref。Scion 在技术上支持 ref,但创建和获取 commit 都不涉及 ref,因此移动一个 ref 只是对 Spanner 中单行做一次 compare-and-swap,与创建 changeset 相互独立。
changeset 只记录变更内容,因此单靠它无法快速回答诸如某一行最后由谁编辑、哪些 commit 影响过某个文件之类的问题。为了回答这些问题,Scion 会基于 changeset 构建额外的数据结构,包括用于 file blame 和路径历史的专用结构,以及用于 commit graph 的 skip list。我们把所有不直接属于 changeset 的部分称为“derived data”,并统一通过一套通用机制计算。每种 derived data 都递归定义,因此某个 changeset 上的取值由其各父 changeset 上的取值计算得出。
这套机制有三个好处。第一,当新的数据结构能让某些操作更快时,我们可以轻松加入新的 derived data 类型。第二,对每种类型,我们可以选择立即计算还是推迟计算,从而避免物化不需要的数据。第三,由于客户端看不到这些数据结构的内部细节,我们可以在不改变客户端的前提下优化它们。随着系统发展,我们已多次加入新的 derived data 类型,例如用于 copy detection 或基于路径的文件搜索的新索引。
Scion 的扩展
一小组 Scion 服务器即可为客户端所需的任何操作提供 source control API,例如获取 changeset、计算 diff、查找 merge base、拉取 blob。所有 source control 数据都按内容寻址,因此数据一旦存储就不再改变,任何服务器都可以直接缓存,无需与其他服务器核对。因此,这些服务器可以横向扩展,几乎不需要协调。它们本质上就是大型缓存,同时还能即时计算 derived data 并把结果写回 Spanner。
Scion 的众多使用者不再需要各自维护本地仓库。过去,拉取并维护仓库对 CI 任务或需要向版本控制提问的工具来说是一项沉重的维护负担,尤其是涉及提交祖先关系和 blame 的查询。迁移初期,在我们拥有自己的客户端、仓库格式和协议之前,Scion 只负责回答这类“元数据”查询。拉取文件内容和生成工作副本是数据密集程度高得多的操作,需要单独设计。
Scion 的 scn 客户端在几个重要方面偏离了 git 的 fetch 协议,也因此适合一种高度可扩展的架构,所需 CPU 远少于 git 服务器。Scion 的协商不会告诉服务器客户端已有什么,而是只针对客户端想要的 tree 和 blob 进行操作。从根 tree 开始,客户端在本地存储中检查缺失的 tree 并递归请求,然后对 blob 重复同样的过程。每个请求针对的都是某个永不改变的具体对象,因此任何缓存都能应答,整个架构与传统多级缓存非常相似。这个过程也很高效、可断点续传,因为不存在单个数 GB 的 packfile 响应。稀疏检出乃至虚拟化检出都不需要特殊支持,因为客户端根本不会请求它不想要的 tree。
在集群中,客户端通过 socket 与守护进程 scion-daemon 通信,后者为整个节点存储对象。我们大多数节点都需要访问 monorepo,同一节点上的任务会复用大部分相同的对象。每个守护进程从一个定期刷新的快照启动,之后只与所在区域的 tree 和 blob 缓存服务器通信,这些服务器称为 scion-cache。缓存服务器直接从内存中以守护进程将要消费的磁盘格式流式发送对象,因此无需花时间计算客户端缺失的对象或做增量压缩。当缓存缺少某个对象时,它会向权威的 scion-servers 请求,后者按需从 Spanner 或云存储读取。
scion-daemon、scion-cache 和 scion-server 在该协议下响应完全相同的 RPC,因此客户端无需了解缓存拓扑。理论上,任意一层都可以与另一层通信,但我们严格强制分层,以隔离故障域。我们要避免的情况是:某个区域的 scion-cache 出现故障,压垮上游的 scion-server,进而拖垮整个系统。

这套系统的每一层缓存命中率都保持在 99% 以上,而且不需要长期存活的状态,因此需要扩容时很容易扩展。按 CPU 和内存需求计算,我们的 Scion 基础设施占用比之前的 git 镜像基础设施小了 200 倍。CI 和研究任务检出代码的速度快了两个数量级(从几分钟缩短到几秒),在整个集群中释放出相当可观的容量;仅 CI 一项就实现了 25% 的 CPU 容量收益。客户端和缓存都不需要做仓库维护,这让我们对任务的生命周期管理简单了不少。
构建 Scion 的经验
我们开始构建 Scion 的时间,大致与 Claude Mythos Preview 和 Claude Tag 在内部上线的时间相同。这两个版本的发布同时放大了对 Scion 的需求,也大幅加快了我们的构建速度。
有一个例子特别能说明问题。今年早些时候,我们基本靠手写完成了 git 到 Scion 摄取流水线的第一版原型。我们本以为它已经不错了——直到拿到 Claude Mythos Preview 的早期内部快照。我们让它把整个东西重写一遍,没有给任何额外指示,它拿出的版本内存占用降低了 20 倍、速度快了 4 倍,同时保留了摄取顺序和数据结构方面所有隐含的不变量。

在构建和运营 Scion 的过程中,我们积累了一些经验,对于 agent 规模的软件开发会是什么样子,这些经验带来了一些有意思的启示。
小团队快速开发会越来越常见。 一支七人团队同时构建了 Scion 和 Antique(我们的代码审查工具,完全可以再写一篇博客专门讲它),此外还有六个相关系统,并在五个月内完成了向这些系统的全面迁移。这期间,我们每天合并的代码量还增长了 10 倍。放在过去,开发和部署这样一个源代码控制系统,一家公司要投入多得多的工程师,经历多年的迭代迁移。我们认为这种快速、灵活的迁移未来会越来越常见。
工程师的价值没有降低,但他们的工作正在改变。 参与这个系统的所有工程师现在都会认为,Claude 在_写_代码方面比自己更强。但工程师仍然提供了不可替代的东西:对系统间高层组件的理解,以及对设计层面取舍的判断,围绕我们想解决什么问题、需要达到什么规模做出有依据的选择。很多时候,我们发现工程师的角色就是对 Claude 说“不”。Agent 有时会为了迎合某种本不该被允许的模式而违反系统的核心约束。这在我们迁移过程中尤其常见。比如,有多个系统试图通过线性读取 commit log 来暴力查找某个变更何时合入 main,而不是去问我们的代码审查工具。Anthropic 也大量使用对抗式审查,这种方式在很多方面极其有效,但也可能视野狭隘,因此需要熟练的工程师引导 Claude,避免它为了应对假设性的担忧而对系统某些部分过度设计。比如,有一次这样的审查对信任另一个内部服务表现得过于防备,结果 Claude 添加了过多的校验和复杂度。我们的团队成员仍然会审查系统的每一个变更请求,但现在他们看的是变更的_意图_,而不是逐行读代码。
我们因此得以大幅改进优化与质量。 凭借 Claude 能以远超我们自身速度编写海量代码的能力,Scion 成为我们参与测试过的测试最充分、防御最严密的代码库之一。即便时间表如此紧张,我们仍为遇到过的每一项非平凡基础设施与源码控制交互构建了封闭模拟,还做了模糊测试和精细的限流。Claude 还顺手发现了重大的优化点——比如针对祖先查询的一处算法优化,我们此前漏掉了一个索引——这类工作若由专家来做,需要数天甚至数周的分析与调优……而它第二天又把这一切重做了一遍。

我们为新型 monorepo 安全打下了基础。 在庞大的 monorepo 中管理安全、安保与合规,和管理它的体量与规模同样重要。我们从一开始就把这些考虑进了 Scion 的设计。它支持对仓库做细粒度的访问控制,也能把权限施加到单个目录上,从而在保护最敏感代码的同时不放弃 monorepo 的好处。此外,我们构建的代码审查工具可以对审批表达复杂的语义约束,让我们能区分仅改注释这类简单变更和敏感的逻辑变更,使人工审查者可以专注于影响最大的改动。
运营水平可以再上一个台阶。 我们团队大量使用 Claude Tag,它帮我们监控系统的每一个组件。比如我们建了一些 Slack 频道,让 Claude 只盯住某一个组件的健康状况,别的什么都不管。我们把这些 Claude 叫做“superfan”。我们有 scion-daemon superfan、scion-cache superfan、Antique merge queue superfan,等等。它们能访问我们的遥测和日志,表现得就像一个经验丰富的工程师,除了刷新图表、寻找异常之外无事可做。我们所有系统也都有传统告警,但 superfan 能发现那些细微而新颖的问题,相比之下传统告警显得十分单薄。
我们正在彻底重估自己的预设。 这个项目促使我们认真审视其他为人类规模设计的工作流。比如,一个正经的 merge queue 在 agent 规模下就没那么说得通了。当成百上千个变更可以在测试运行期间合并时,CI 到底意味着什么?我们一直没能有效复现业界标准的 merge queue 全局仓库锁,于是转而投入合并后与部署前的自动化测试。这确实意味着并发变更可能冲突,但 Claude 修复这些冲突的速度,通常比人类发现它们还快。
为更广泛的生态做贡献
在我们看来,Scion 只是应对规模化难题的答案之一——能力越来越强的 AI agent 让这个问题愈发突出。我们分享这些细节,是把它当作团队应对类似挑战的一个示例,并不指望我们的方案或考量适用于所有情况。
Scion 绝不是 git 的通用替代品。这里描述的概念只对真正超大规模的 monorepo 才有意义——要么有数万名工程师,要么有成群的 agent 在大量写代码。而且 Anthropic 的情况很特殊:我们为内部用途消耗 token 的能力,远远超出任何其他企业现实中愿意花的钱。我们这么做,既是为了满足自身的软件开发需求,也是为了测试 Claude 的能力。
从实际角度看,很难把 Scion 做成对其他组织普遍有用的形态。它与 git 差异很大,意味着公司需要迁移开发生命周期的每一个环节,而且它需要自己的代码审查、代码搜索和 CI。我们内部这些组件都是高度定制的,其他组织很难采用;我们也担心,做一个试图讨好所有人的公开版本,实际上并不会有多大用处。
不过我们预计,这类问题在未来 6 到 12 个月内会变得更加普遍,让每一家用 git 的公司各自面对相似的挑战,是对工程资源的低效使用。因此,我们认为最有影响力的事情是分享我们的思路,让其他开发者能把 Scion 中最成功的部分应用到 git 上,而不必脱离整体模型。git 服务器不必和 git 客户端以同样的方式工作;已经存在能带来显著扩展性提升的新型服务器架构。git 平台可以提供像 Scion 那样全面的服务端 RPC,甚至可以在各个 forge 之间以标准化的方式提供,让 git 客户端学会与之通信。协商协议也不必和今天完全一样,git 中各种 sparse 和 shallow 特性也可以组合得很好。
最后,我们要感谢 Git 社区近二十年来不断拓展这一平台,惠及数百万开发者。相比之下,我们的贡献要有限得多:分享我们对 agentic 软件开发如何与这些系统交互的初步观察,以便与 GitHub、GitLab 及开源社区携手,支持开发者不断变化的需求。当软件开发生命周期与 AI 的指数级发展交汇,我们都在同一条船上。