重新设计 Anthropic 的源代码管理

Redesigning source control at Anthropic

中文翻译 · 仅供学习参考

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

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

柱状图:2026 年 1 月至 9 月 monorepo 每日合并变更数的增长趋势
2026 年 1 月至 9 月,我们 monorepo 每日合并的变更数量。

为了支撑这样的增长,我们构建了 Scion——一套自研的版本控制系统,并在五个月内将 monorepo 从 git 完全迁移过来。在同一时间段内,我们还构建并上线了代码评审界面(称为 Antique)、代码搜索、一套功能丰富的源代码管理 IAM 系统,以及管理和合入大量已评审代码的全新方式,等等。本文将重点介绍我们如何构建 Scion。它构成了一个很有意思的案例研究:Claude 的代码编写能力既是让我们陷入这种局面的原因,也是我们能够快速构建出一套高质量、可扩展系统来应对局面的手段。

monorepo 的增长

在探讨我们构建了什么之前,需要先说明我们的工作负载有多么特殊。不出所料,Anthropic 使用 Claude 编写软件的程度远远超过几乎所有其他公司,因此我们往往最先察觉为"人类尺度"设计的工具在何处开始吃力。源代码管理只是其中一个例子。尽管我们预计短期内不会有太多组织遇到这些问题,但我们猜测,用不了多久,其他使用 AI 编码工具的团队也会遭遇同样的压力点——这正是我们想分享所构建的东西和所学经验的原因。

Git 是一个去中心化的系统:每一个克隆(clone)都是完整、自包含的仓库,即使完全离线也能计算 diff、查看历史与 blame、检出(checkout)任意版本。对于绝大多数仓库而言,这是正确的设计,也是 git 能够成为事实标准的重要原因。

随着 monorepo 的增长,它越来越不适配这一设计空间。仅主分支(main branch)的历史就有近 100 GB,一份压缩后的工作副本(working copy)超过 25 GB。我们每天为 CI 执行约 2,500 万次代码检出——这还没有计入开发者环境、智能体沙箱和研究任务。如果按朴素方式拉取,这意味着每天超过 600 PB 的 git 流量。

Git 提供了一些只处理仓库切片的工具,例如浅克隆(shallow clone)、稀疏检出(sparse checkout)和 promisor remote。除此之外,针对大型 monorepo 还有常见的变通方案,比如内部 git 镜像和保存在 blob 存储中的仓库快照(种子,seed)。这些我们都用过——在高峰期,我们运行了数百个内部 git 镜像来应对负载。这为我们争取了时间,但随着合并率的上升,它们的运维变得越来越困难。两次刷新之间,种子会落后 main 分支尖端越来越远;要让 diff、blame 这类依赖历史的操作在部分克隆上继续正常工作,需要投入越来越多的精力。

架构图:扩展 git 检出的典型设置,含镜像与快照层
扩展 git 检出的典型架构。

退一步审视,我们注意到:和大多数公司一样,我们把 git 当作只有一个权威副本的集中式系统来使用,但我们的客户端几乎全是在线运行的智能体和作业。git 在分布式离线场景下的种种优秀特性,随着仓库的增长反而切实阻碍了我们的扩展能力。于是我们自问:如果从一开始就基于"智能体始终在线"这一事实来设计源代码管理,它会是什么样子?

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

随着智能体编码(agentic coding)的兴起,关于软件开发方式的许多惯常假设都在演变。构建 Scion 时我们拥抱了这些变化,从而得以在若干核心原则上采取与 git 根本不同的思路。面向超大型 monorepo 的源代码管理并非没有先例,最著名的是 Google 的 Piper 和 Meta 的 Sapling,因此我们在构建 Scion 时得以借鉴它们来之不易的经验。我们的原则是:

客户端始终在线。 Git 假设客户端会离线工作,因此需要仓库的完整副本。既然我们的客户端几乎全是智能体和作业,我们便围绕"服务器始终可达"来设计 Scion 的客户端与协议,让客户端在需要数据时才去拉取。

客户端—服务器不对称。 在 git 中,每个客户端都具备成为完整服务器的能力。而在 Scion 中,由服务器负责回答文件历史与 blame 相关的问题、创建提交(commit)、执行合并(merge)和变基(rebase)等操作;客户端则维护一个容量有界的本地对象缓存、在磁盘上排布工作副本文件、监测文件系统变化,并为用户提供与服务器交互的友好方式。它自身几乎不具备独立计算能力,但作为交换,它在本地需要的数据要少得多。

一套全面的源代码管理服务可以支撑许多新颖的应用。 使用 git 时,需要向仓库提问的 CI 作业或工具——比如"这一行最后是谁改的"或"这两个提交是什么关系"——要么自己维护一份本地仓库副本,要么指望厂商专属 API 能给出答案。而在 Scion 中,客户端使用的所有源代码管理查询都通过 API 对外暴露,因此很多情况下作业完全不需要本地仓库。许多内部工具——例如我们的自动 bisect(二分定位)和自动 revert(回退)流水线——如果没有这种廉价、可编程的源代码管理访问方式,根本无法大规模实用化。

一切组件都必须可以水平扩展。 在高峰期我们运行了数百个 git 镜像,容量已相当吃紧;一旦仓库超出镜像的磁盘或内存上限,就再也无法扩展。在 Scion 中,仓库数据是分布式的,每个组件都可独立扩展;任何地方——哪怕在服务器端——都不存在完整物化的仓库副本。也没有 packfile(git 的压缩对象包)的对应物,因此无需任何仓库维护。

提交必须廉价、增量且无锁。 在 git 中,你先在本地提交,再 push 分享成果。在 Scion 中,每次提交、变基或合并都发生在服务器端,因此无需 push,但每一个操作都必须够快。创建提交并更新其下游数据结构的成本,必须与变更的大小成正比,而不是与仓库整体大小成正比,否则服务器跟不上。Scion 的开发生命周期不鼓励使用 ref(指向提交的指针,如分支和标签),main ref 和发布分支除外——因为需要所有服务器达成一致的全局状态,是悬在头上的扩展难题。

工作副本是可选的,且生来就是稀疏的。 由于 monorepo 庞大,加上每天为 CI 检出代码数千万次,我们希望尽可能避免检出。在 Scion 中,比如一个需要某提交与其合并基(merge base)之间 diff 的 CI 作业,可以直接向服务器索要。写操作同理:许多写入型工作负载也可以跳过检出,完全通过 API 完成。Scion 的协议围绕稀疏检出(只包含客户端请求的目录)和虚拟化检出(按需物化文件)构建,因此当作业确实需要磁盘上的文件时,客户端只需为它实际读取的内容付出代价。

Scion 是如何工作的

了解这些原则在构建 Scion 时如何落地实践,会很有帮助。这些洞见同样适用于其他源代码管理系统,或者更广泛地说,适用于软件开发生命周期的其他环节。

数据结构

在 Scion 中,工作单元是"变更集"(changeset),即写前日志(write-ahead log)中的一条记录。变更集存储了你在 git 提交中预期的大部分信息。但 git 提交存储的是指向树(tree)的指针,而变更集存储的是相对于其第一父提交发生变化的文件列表[1],外加指向 blob 存储中压缩文件内容的内容寻址指针。变更集在服务器端创建,并即时在所有地方可用。

示意图:Scion 中变更集的数据结构形态
Scion 中变更集的结构。

这种结构让我们能以极高的吞吐量接收变更集:没有需要接收的 packfile,没有加锁或跨服务器协调,也没有需要移动的 ref。严格来说 Scion 也支持 ref,但由于创建和获取提交都不涉及 ref,移动一个 ref 只是对单个 Spanner 行做一次比较并交换(compare-and-swap),且与变更集的创建彼此独立。

变更集只记录发生了什么变化,因此单凭它无法快速回答"某一行最后由谁编辑""哪些提交影响过某个文件"这类问题。为了回答这些问题,Scion 会从变更集构建额外的数据结构,包括用于文件 blame 和路径历史的专用结构,以及提交图(commit graph)的跳表(skip list)。我们把所有不直接属于变更集的部分统称为"派生数据"(derived data),并通过一个通用机制来计算它们。每种派生数据都以递归方式定义,因此它在某个变更集上的值,由其在该变更集各父提交上的值计算而来。

这一机制有三大好处。其一,当新的数据结构能让某个操作更快时,我们可以轻松新增一种派生数据。其二,对每种派生数据,我们可以选择立即计算还是推迟处理,从而避免物化用不上的数据。其三,由于客户端永远看不到这些数据结构的内部实现,我们可以在不改动客户端的前提下优化它们。随着系统成长,我们已经多次新增派生数据类型,例如用于副本检测(copy detection)或基于路径的文件搜索的新索引。

扩展 Scion

一小批 Scion 服务器为客户端所需的任何操作提供源代码管理 API,例如获取变更集、计算 diff、查找合并基、获取 blob 等。所有源代码管理数据都是内容寻址的,因此任何数据一旦存储便永不改变,任何服务器都可以缓存它而无需与其他服务器核对。因此,这些服务器可以水平扩展且几乎不需要协调。它们本质上是大缓存,同时还能即时计算派生数据并把结果写回 Spanner。

Scion 的大量使用方不再需要各自维护本地仓库。对于需要向源代码管理提问的 CI 作业或工具而言——尤其是关于提交祖先关系和 blame 的问题——拉取和维护仓库曾是一项沉重的维护负担。在迁移早期,我们还没有自己的客户端、仓库格式或协议时,Scion 只回答这类"元数据"查询。而获取文件内容、物化工作副本是数据密集得多的操作,需要单独设计。

Scion 的 scn 客户端在几个重要方面偏离了 git 的拉取协议,因而适合一种高可扩展架构,其 CPU 消耗远低于 git 服务器。Scion 的协商(negotiation)不告诉服务器客户端已拥有什么,而是只围绕客户端想要的树和 blob 展开。从根树(root tree)出发,客户端检查本地存储中缺失的树并递归请求,然后对 blob 重复同样的过程。每个请求针对的都是一个永不改变的具体对象,因此任何缓存都能应答,整个架构非常接近传统的分层缓存。这个过程也高效且可断点续传,因为不存在单个数 GB 的 packfile 响应。稀疏检出甚至虚拟化检出都无需特殊支持——客户端不请求它不想要的树就是了。

在集群中,客户端通过套接字与守护进程 scion-daemon 通信,后者为所在节点存储对象。我们的多数节点都需要访问 monorepo,而同一节点上的作业会复用大部分相同的对象。每个守护进程从一个定期刷新的快照启动,之后只与本区域的树和 blob 缓存服务器(称为 scion-cache)通信。这些缓存服务器直接从内存、以守护进程将要消费的磁盘格式流式传输对象,因此无需花时间计算客户端缺失的对象或执行 delta 压缩。当缓存缺失某个对象时,它会询问权威的 scion-server,后者按需从 Spanner 或云存储读取。

scion-daemon、scion-cache 和 scion-server 对该协议响应完全相同的 RPC,因此客户端无需了解缓存拓扑。理论上任何一层都可以与其他层直接通信,但我们严格强制分层,以隔离故障域。我们要避免这样一种情形:某个区域 scion-cache 的故障压垮上游的 scion-server,进而拖垮整个系统。

架构图:Scion 的检出架构,包含 daemon、cache 与 server 三层
Scion 的检出架构。

这套系统的每一层都保持着超过 99% 的缓存命中率,且不需要长期存活的状态,因此需要时可以轻松扩容。就 CPU 和内存需求而言,我们的 Scion 基础设施占用现在只有原先 git 镜像基础设施的 1/200。CI 和研究任务检出代码的速度快了两个数量级(以秒计而非以分钟计),为整个集群释放了相当可观的容量;仅 CI 一项就获得了 25% 的 CPU 容量收益。客户端和缓存都无需执行仓库维护,这大大简化了作业的生命周期管理。

构建 Scion 过程中的收获

我们开始构建 Scion 的时间,大致与 Claude Mythos Preview 和 Claude Tag 在内部可用的时间相同。这两个发布一方面放大了对 Scion 的需求,另一方面也切实加快了我们构建它的速度。

有一个格外醒目的例子。今年早些时候,我们基本以手工方式写出了 git 到 Scion 摄取(ingestion)流水线的第一版原型。我们觉得它还不错——直到我们拿到 Claude Mythos Preview 的早期内部快照。我们让它重写整个流水线,在没有任何额外指示的情况下,它产出了一个内存占用降低 20 倍、速度快 4 倍的版本,同时完整保留了围绕摄取顺序和数据结构的所有隐式不变量。

柱状图:2026 年 1 月至 9 月 Scion 与 Antique 代码库每周合并变更数
2026 年 1 月至 9 月,Scion 和 Antique 代码库每周合并的变更数量。

构建和运营 Scion 的经验,为"构建智能体规模的软件会是什么样子"提供了一些有趣的启示。

小团队的快速开发将变得更加普遍。 一个七人团队构建了 Scion 和 Antique(我们的代码评审工具,单是它就够我们再写一篇完整的博客文章),外加半打相关系统,并在五个月内完成了全面迁移——而在此期间,每日合并的代码量还增长了 10 倍。放在过去,开发和部署这样一套源代码管理系统,需要一家公司投入更多的工程师、历经数年的迭代式迁移。我们认为,这种快速、敏捷的迁移在未来会越来越常见。

工程师的价值丝毫未减,但他们的工作正在改变。 参与构建这套系统的所有工程师如今都认为,Claude 写代码比他们写得更好。但我们的工程师依然提供了无可替代的价值:他们对系统间高层组件与设计层面权衡的深刻理解,让我们能够围绕要解决的问题和已知需要达到的规模做出明智选择。很多时候,我们发现工程师的角色是对 Claude 说"不"。智能体有时会为了满足某种本就不该被允许的模式而违反核心系统约束,这在我们的迁移过程中尤其常见。例如,有多个系统试图通过线性读取提交日志来暴力查找某个变更何时合入 main,而不是去询问我们的代码评审工具。Anthropic 也大量采用对抗式评审(adversarial review),这种方式虽然在许多方面极为有效,但也可能缺乏全局视野,因此需要资深工程师引导 Claude,不要为了应对假想中的问题而过度设计系统的某些部分。例如,曾有一次评审对信任另一个内部服务过于防御,导致 Claude 添加了过多的校验和复杂度。团队成员仍会评审该系统的每一个变更请求,只是他们现在审视的是变更的意图,而不是逐行阅读代码。

我们在优化和质量上实现了大幅提升。 凭借 Claude 以远超我们的速度编写海量代码的能力,Scion 成为我们经手过的测试最完善、防御最严密的代码库之一。即便时间线如此紧张,我们仍为遇到过的每一种非平凡的基础设施和源代码管理交互构建了封闭式(hermetic)仿真,还有模糊测试(fuzzing)和精细的速率限制。Claude 还顺手发现了重要的优化——例如对祖先关系查询的一处算法优化,它绕过了我们的某个索引——这要是交给专家,得花上数天甚至数周的分析与调优……而第二天它又照此重来了一遍。

图表:原生 Scion 仓库上观测到的性能提升数据
在原生 Scion 仓库上观测到的性能提升。

我们为新型 monorepo 安全打下了基础。 在一个庞大的 monorepo 中管理安全性、安保与合规,与管理体量和规模同等重要。我们从一开始构建 Scion 时就把这些记在心上。它支持对仓库的细粒度访问控制,还可以对单个目录应用权限,从而在不丧失 monorepo 优势的前提下保护最敏感的代码。此外,我们构建的代码评审工具可以在批准(approval)上表达复杂的语义约束,这让我们能够区分"仅注释改动"这类简单变更与敏感的逻辑变更,让人工评审者得以聚焦于影响最重大的变更。

一种全新水平的卓越运维成为可能。 我们的团队大量使用 Claude Tag,它帮助我们监控系统的每一个组件。例如,我们创建了一些 Slack 频道,指派 Claude 专注地盯着单个组件的健康状况。我们把这些 Claude 称为"超级粉丝"(superfans):我们有 scion-daemon 超级粉丝、scion-cache 超级粉丝、Antique 合并队列超级粉丝,等等。它们可以访问我们的遥测数据和日志,表现得像一位闲来无事、只顾刷新图表、搜寻任何异常的资深工程师。我们也为所有系统配备了传统告警,但相比之下,我们的超级粉丝能察觉那些让告警显得单薄的细微新异问题。

我们正在彻底反思自己的既有认知。 这个项目促使我们认真审视其他为"人类尺度"设计的工作流。例如,一个像样的合并队列(merge queue)在智能体规模下已经说不通:当测试还在运行时就有成百上千个变更合入,CI 又意味着什么?我们始终无法有效复制"全局仓库锁"这一合并队列的行业惯例,因此转而投资合入后(post-merge)与部署前(pre-deploy)的自动化测试。这确实意味着并发变更可能冲突,但通常在人类还没识别出这些冲突时,Claude 就已经把它们修好了。

反哺更广泛的生态

我们把 Scion 视为对日益强大的 AI 智能体所引发的扩展性问题的一个局部答案。我们分享这些细节,是作为一个团队如何应对类似挑战的示例——当然,我们并不期待我们的方案或我们的考量放之四海而皆准。

就通用场景而言,Scion 绝不是 git 的合适替代品。这里概述的这些概念,只对那些真正庞大的 monorepo 才有意义——要么拥有数万名工程师,要么有成群的智能体在持续不断地写代码。此外,Anthropic 的独特之处在于,我们在内部使用上可以消耗的 token 远超任何其他企业现实中所愿意花费的水平。我们这样做,既是满足自身软件开发的需要,也是为了检验 Claude 的能力。

务实地讲,很难以一种对其他组织广泛有用的形态来提供 Scion。它与 git 差异巨大,意味着企业需要迁移开发生命周期的每一个环节,而且它需要配套自己的代码评审、代码搜索和 CI。这些组件的内部版本高度定制,其他组织难以采用;我们也担心,试图讨好所有人的公开版本实际上未必有用。

不过,我们预计这类问题在未来六到十二个月内会变得愈发常见,而让每一家使用 git 的公司都独自面对类似挑战,是对工程资源的低效使用。有鉴于此,我们认为最有影响力的事情是分享我们的思考,让其他开发者能够把 Scion 中最成功的部分应用到 git 上,而不必脱离 git 的整体模型。git 服务器未必要以和 git 客户端相同的方式工作;已经存在能带来显著扩展改善的新型服务器架构。Git 平台可以像 Scion 那样提供全面的服务端 RPC,甚至可以跨代码托管平台(forge)以标准化方式提供,让 git 客户端学会与之对话。协商协议也不必保持今天的模样,git 中各种稀疏与浅克隆特性也完全可以被打磨得彼此良好配合。

最后,我们想向 git 社区致敬:他们花了近二十年时间为数百万开发者扩展这一平台。我们的贡献要狭窄得多——分享我们关于智能体软件开发如何与这些系统互动的早期观察,以便与 GitHub、GitLab 和开源(OSS)社区合作,共同支持开发者不断变化的需求。当软件开发生命周期与 AI 指数曲线交汇,我们所有人都在同一条船上。

致谢

本文由 Nick G. 与源代码管理团队撰写:S.G.、R.K.、Dan C.、Garrett H.、Ben L.、Colin M.、Joe P。感谢 Vilas J. 和 Sergiu D. 在这段雄心勃勃的旅程中对源代码管理团队的支持,感谢 Amir O. 对 Antique 的早期贡献。

脚注

  1. 相对于合并的第一父提交存储变更文件,是一种风格上的选择;实践中我们发现,在 monorepo 的主干开发(trunk-based development)模式下,人们只关心主线(main line)上提交的历史与 blame。↩