Anthropic 详解自研版本控制系统 Scion 的设计与迁移
Redesigning source control at Anthropic
Anthropic 介绍其自研版本控制系统 Scion,并在五个月内把单体仓库从 git 完整迁移过去。Scion 按客户端始终在线、客户端与服务端不对称、数据分布式存储等原则设计,配套代码评审工具 Antique 等系统由七人团队完成;其基础设施的 CPU 和内存占用比此前的 git 镜像方案小 200 倍,CI 检出代码从分钟级降到秒级。
Anthropic 公开自研版本控制系统 Scion 的设计取舍,为智能体大规模写代码后的工程基础设施提供一份可迁移的参考。
在 Anthropic,编码已不再是构建系统或开展研究的瓶颈。Claude 能够自主迭代研究循环、对复杂程序进行微优化,并以我们前所未见的速度和规模从零构建新服务。如今的瓶颈,是为人类规模而构建的所有开发者基础设施:源代码控制、构建和 CI。
2026 年 5 月,Anthropic 平均每天合并约 2,500 次代码变更;截至 9 月,我们每天合并超过 30,000 次。我们预计这一增长速度短期内不会放缓;合并代码的潜在需求几乎是无限的,而我们每消除一个瓶颈,都会立即带来代码产量的进一步增长。
为了支撑这一增长,我们构建了 Scion——一套内部版本控制系统,并在五个月内将我们的 monorepo 从 git 完全迁移过来。我们还在同一时期构建并上线了代码审查界面(名为 Antique)、代码搜索、一套丰富的源代码控制 IAM 系统、管理并落地大量已审查代码的新方法等等。本文将重点介绍我们是如何专门构建 Scion 的。这是一个有趣的案例研究:Claude 编写代码的能力既是最初让我们陷入这一处境的原因,也是我们能够快速构建一套高质量、可扩展的系统来应对它的原因。
扩展我们的 monorepo
在探讨我们构建了什么之前,我们应该承认我们的工作负载有多么不寻常。不出所料,Anthropic 使用 Claude 编写软件的程度远超几乎所有其他公司,因此我们常常最先看到为人类规模构建的工具开始吃紧的地方。源代码控制只是其中一个例子。虽然我们预计近期内不会有太多组织遇到这些问题,但我们怀疑用不了多久,其他使用 AI 编码工具的公司也会经历同样的压力点,这就是为什么我们想分享我们构建了什么以及学到了什么。
Git 是一个去中心化系统:每个克隆都是一个完整、自包含的仓库,可以计算差异、读取历史和 blame,并在完全离线的情况下检出任意修订版本。对于绝大多数仓库来说,这是正确的设计,也是 git 成为标准的重要原因之一。
随着我们的 monorepo 增长,它越来越不符合这一设计空间。仅我们主分支的历史就接近 100 GB,而压缩后的工作副本也超过 25 GB。我们每天为 CI 执行约 2,500 万次代码检出,这还不算开发者环境、智能体沙箱和研究任务。如果朴素地拉取,那将是每天超过 600 PB 的 git 流量。
Git 提供了处理仓库部分内容的工具,例如浅克隆、稀疏检出和 promisor 远程。除此之外,针对大型 monorepo 还有标准的变通方案,比如内部 git 镜像和保存在 blob 存储中的仓库快照(种子)。这些我们都用过——在高峰期,我们运营着数百个内部 git 镜像来应对负载。这为我们争取了时间,但随着合并率上升,它们变得越来越难以运营。种子在两次刷新之间离主分支顶端越来越远,要让依赖历史记录的操作(如 diff 和 blame)在部分克隆上正常工作,也需要越来越多的维护。
退一步来看,我们注意到,和大多数公司一样,我们把 git 当作一个集中式系统来使用,只有一份权威副本,但我们的客户端几乎全都是按定义始终在线的 agent 和作业。git 之所以在分布式、离线场景下表现出色的那些特性,恰恰在仓库不断增长时削弱了我们的扩展能力。于是我们问自己:如果从一开始就把 agent 始终在线这一事实纳入源码控制的设计之中,它会是什么样子?
面向 agent 规模的源码控制原则
在 agentic 编程中,许多关于软件开发方式的常见假设正在发生变化。在构建 Scion 时,我们拥抱了这些变化,这让我们得以在若干核心原则上采取与 git 截然不同的方法。面向超大型 monorepo 的源码控制已有先例,尤其是 Google 的 Piper 和 Meta 的 Sapling,2 因此我们得以借鉴它们来之不易的经验来构建 Scion。我们的原则是:
客户端始终在线。git 假设客户端离线工作,并且需要仓库的完整副本。由于我们的客户端几乎全都是 agent 和作业,我们围绕一个始终可达的服务器来设计 Scion 的客户端和协议,客户端在需要时才获取数据。
客户端-服务器不对称。在 git 中,每个客户端都能成为完整的服务器。在 Scion 中,服务器负责回答关于文件历史和 blame 的问题、创建提交、执行合并和 rebase 等等。客户端维护一个有界的本地对象缓存,在磁盘上布置工作副本文件,监视文件系统变化,并为用户提供与服务器交互的顺手方式。它自身能做的计算很少,但作为交换,它在本地需要的数据也少得多。
一个全面的源码控制服务可以支撑许多新颖的应用。在 git 中,CI 作业或工具如果需要查询仓库的某个问题,比如某一行最后是谁改的、两个提交之间是什么关系,就必须维护一份仓库的本地副本,或者指望厂商特定的 API 能给出答案。在 Scion 中,客户端使用的所有源码控制查询也都通过 API 暴露出来,因此在许多情况下,作业根本不需要本地仓库。许多内部工具,比如我们自动化的 bisect 和 revert 流水线,如果没有这种低成本、可编程的源码控制访问方式,就无法在规模上实际运行。
一切都必须能水平扩展。在高峰期,我们运行着数百个 git 镜像,这给容量带来了显著压力,而且一旦仓库超出了镜像的磁盘或内存上限,就不再能扩展。在 Scion 中,仓库的数据是分布式的,每个组件都可以独立扩展;任何地方都没有仓库的完整物化副本,即使在服务器上也没有。不存在 packfile(git 压缩的对象包)这样的东西,因此不需要进行仓库维护。
提交必须廉价、增量且无锁。在 git 中,你在本地提交,然后推送以共享工作成果。在 Scion 中,每次提交、变基或合并都发生在服务器上,因此无需推送,但其中每一项操作都必须足够快。创建提交并更新其下游数据结构的开销必须随变更规模扩展,而不是随仓库整体规模扩展,否则服务器将无法承受。Scion 中的开发生命周期不鼓励使用 ref(指向提交的指针,例如分支和标签),主 ref 或发布分支除外,因为每台服务器都必须达成一致的全局状态是一个迫在眉睫的扩展性问题。
工作副本是可选的,并且按设计就是稀疏的。由于我们的 monorepo 非常庞大,而且我们每天要为 CI 检出代码数千万次,我们希望尽可能避免检出。例如,在 Scion 中,需要获取某个提交与其合并基之间差异的 CI 任务可以直接向服务器请求。写入也是如此:许多写入工作负载也可以跳过检出,完全通过 API 完成。Scion 的协议以稀疏检出和虚拟化检出为中心:稀疏检出只包含客户端请求的目录,虚拟化检出按需生成文件,因此当任务确实需要磁盘上的文件时,客户端只需为实际读取的内容付出代价。
Scion 如何工作
理解这些原则在构建 Scion 时如何在实际中结合是很有帮助的。这些洞见可以应用于其他源代码控制系统,或者更广泛地应用于软件开发生命周期的其他阶段。
数据结构
在 Scion 中,工作单元是“变更集”(changeset),即预写日志中的一条记录。变更集存储了你在 git 提交中预期的大部分内容。但 git 提交存储的是指向树的指针,而变更集存储的是相对于其第一个父提交发生变更的文件列表,1以及指向 blob 存储中压缩文件内容的内容寻址指针。变更集在服务器端创建,并可立即在所有地方使用。
这种结构使我们能够以非常高的吞吐量接受变更集,因为无需接受 packfile,没有锁定或跨服务器协调,也没有需要移动的 ref。Scion 在技术上支持 ref,但由于创建和获取提交都不涉及 ref,移动 ref 只是对单个 Spanner 行执行比较并交换,且与创建变更集分开进行。
变更集只记录发生变更的内容,因此仅凭它自身无法快速回答诸如谁最后编辑了某一行或哪些提交影响了某个文件之类的问题。为了回答这些问题,Scion 会基于变更集构建额外的数据结构,包括用于文件追溯(blame)和路径历史的专用结构,以及用于提交图的跳表。我们将所有不直接属于变更集的内容称为“派生数据”,并通过一个通用机制计算全部派生数据。每种派生数据都递归定义,因此某个变更集的派生数据值由该变更集各父提交的对应值计算得出。
这个机制有三个好处。第一,当新的数据结构能让某些操作更快时,我们可以轻松添加一种新的派生数据类型。第二,对于每种类型,我们可以选择是急切计算还是延迟处理,从而避免物化我们不需要的数据。第三,由于客户端永远看不到这些数据结构的内部细节,我们可以在不改变客户端的情况下对它们进行优化。随着系统的发展,我们已经多次添加新的派生数据类型,例如用于执行复制检测或基于路径的文件搜索的新索引。
扩展 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-servers 并导致整个系统宕机的情况。
该系统的每一层都保持超过 99% 的缓存命中率,且不需要长期存活的状态,因此按需扩容很容易。就 CPU 和内存需求而言,我们的 Scion 基础设施占用现在比之前的 git 镜像基础设施小 200 倍。CI 和研究任务检出代码的速度快了两个数量级(以秒计而非分钟),这在整个集群中释放了相当可观的容量;仅 CI 就实现了 25% 的 CPU 容量收益。客户端和缓存都不需要执行仓库维护,这简化了我们任务的生命周期管理。
构建 Scion 的经验
我们开始构建 Scion 的时间,与 Claude Mythos Preview 和 Claude Tag 在内部可用的时间大致相同。这两个版本的发布同时放大了对 Scion 的需求 ,并显著加快了我们构建它的速度。
这里有一个特别引人注目的例子。今年早些时候,我们基本上手写了 git-to-Scion 摄取流水线的第一版作为原型。我们本以为它不错——直到我们拿到了 Claude Mythos Preview 的早期内部快照。我们让它重写整个实现,在没有任何进一步指示的情况下,它产出的版本内存占用减少了 20 倍、速度提升了 4 倍,同时保留了围绕摄取顺序和数据结构的全部隐式不变量。
我们在构建和运营 Scion 过程中学到的东西,为“智能体规模的软件”构建可能是什么样子提供了一些有趣的启示。
小团队快速开发将变得更加普遍。一个七人团队同时构建了 Scion 和 Antique(我们的代码审查工具,完全可以另写一篇博客文章来介绍),更不用说还有六个相关系统,并在五个月内完全迁移到这些系统上。我们完成这一切的同时,每天合并的代码量增长了 10 倍。过去,开发和部署这样一个源代码控制系统需要公司花费数年时间进行迭代迁移,并投入多得多的工程师。我们认为这种快速、灵活的迁移在未来会变得更加普遍。
工程师的价值丝毫未减,但他们的工作方式正在改变。参与该系统开发的所有工程师如今都会认为,Claude 在编写代码方面比他们更强。但我们的工程师仍然提供了对高层级系统间组件和设计层面权衡的宝贵理解,围绕我们想要解决的问题以及我们已知需要达到的规模做出明智选择。我们经常发现,工程师的角色就是告诉 Claude“不行”。智能体有时会为了满足某种本就不该被允许的模式而违反核心系统约束。这在我们迁移过程中尤为常见。例如,有多个系统试图通过线性读取提交日志来暴力查找某次变更何时合并到 main,而不是询问我们的代码审查工具。Anthropic 还大量使用对抗性审查,尽管这在许多方面极为有效,但也可能视野狭隘,因此需要熟练的工程师引导 Claude 不要为了应对假设性的担忧而对系统部分过度设计。例如,其中一次审查对信任另一个内部服务过于防御,导致 Claude 添加了过多的验证和复杂性。我们的团队成员仍然审查该系统的每一个变更请求,但他们现在审视的是变更的意图,而不是逐行阅读代码。
我们得以大幅提升优化和质量。得益于 Claude 能够以远超我们自身速度编写海量代码,Scion 是我们参与过的最经过充分测试和防护的代码库之一。即便在紧张的时间线内,我们也能够为我们遇到过的每一种非平凡基础设施和源代码控制交互构建封闭式模拟,以及模糊测试和复杂的速率限制。Claude 还随手发现了重大优化——例如,针对祖先查询的一项算法优化,该查询漏掉了我们的一个索引——这本需要专家花费数天或数周的分析和调优……然后第二天它又把这一切重做了一遍。
我们为新形式的 monorepo 安全奠定了基础。在庞大的 monorepo 中管理安全性、安保和合规性,与管理其规模和体量同样重要。我们从一开始就在构建 Scion 时考虑了这些。它支持对仓库进行丰富的访问控制,并能够将权限应用于单个目录,以保护最敏感的代码,同时不失去 monorepo 的优势。此外,我们构建的代码审查工具能够对审批表达复杂的语义约束,这让我们能够区分仅注释变更与敏感逻辑变更等简单事项,从而使人工审查者能够专注于最重要的变更。
实现更高水平的运营卓越是有可能的。我们的团队大量使用 Claude Tag,这帮助我们监控系统的每个组件。例如,我们创建了 Slack 频道,让 Claude 专注于单个组件的健康状况。我们称这些 Claude 为“超级粉丝”。我们有 scion-daemon 超级粉丝、scion-cache 超级粉丝、Antique 合并队列超级粉丝等等。它们可以访问我们的遥测数据和日志,表现得像一位经验丰富的工程师,除了刷新图表和寻找异常之外无事可做。我们所有系统也都有传统告警,但我们的超级粉丝能发现细微而新颖的问题,相比之下这些告警显得过于单一。
我们正在彻底重新思考我们的先验假设。这个项目促使我们认真审视其他为人类规模设计的工作流程。例如,一个规范的合并队列在智能体规模下并不太合理。当数百甚至数千个变更可以在测试运行时合并,CI 究竟意味着什么?我们未能有效复制行业标准的合并队列全局仓库锁,因此转而投入合并后和部署前的自动化测试。这确实意味着并发变更可能冲突,但 Claude 通常能比人类发现冲突更快地修复它们。
为更广泛的生态系统做贡献
我们认为 Scion 只是解决能力日益增强的 AI 智能体所引发的扩展性问题的一部分答案。我们分享这些细节,是作为团队如何应对类似挑战的一个示例,尽管我们并不期望我们的解决方案或我们的考量适用于所有情况。
Scion 绝不是 git 在通用场景下的合适替代品。我们在此概述的概念只对真正庞大的单体仓库有意义,这类仓库要么有数万名工程师,要么有成群的智能体大量编写代码。此外,Anthropic 的独特之处在于,我们为内部使用消耗 token 的能力远远超出任何其他企业现实中愿意支出的水平。我们这样做既是为了满足自身的软件开发需求,也是为了测试 Claude 的能力。
实际上,很难将 Scion 以一种对其他组织广泛有用的形式提供出来。它与 git 差异显著,意味着公司需要迁移开发生命周期的每一个环节,而且它需要自己的代码审查、代码搜索和 CI。我们这些组件的内部版本高度定制化,因此其他组织难以采用,我们担心试图构建一个迎合所有人的公开版本实际上并不会很有用。
不过,我们预计这类问题在未来六到十二个月内会变得更加普遍,而让每家使用 git 的公司都独自面对类似挑战,将是对工程资源的低效利用。鉴于此,我们认为最有影响力的事情是分享我们的思考,让其他开发者能够在不偏离整体模型的前提下,将 Scion 中最成功的部分应用到 git 上。git 服务器不必与 git 客户端以相同方式工作;存在能带来显著扩展性提升的新型服务器架构。Git 平台可以提供像 Scion 那样全面的服务端 RPC,甚至可能以标准化的方式跨多个代码托管平台提供,让 git 客户端学会与之通信。协商协议也不必与今天完全一样,而且 git 中各种稀疏和浅克隆特性可以被设计成良好组合。
最后,我们想承认,git 社区花了近二十年时间为数百万开发者扩展该平台。我们的贡献要狭窄得多:分享我们对智能体软件开发如何与这些系统交互的早期看法,以便我们能与 GitHub、GitLab 和开源社区合作,支持开发者不断变化的需求。随着软件开发生命周期与 AI 指数级增长相交汇,我们同舟共济。
来源:Anthropic Research · anthropic.com