官方文章 · 发布日期:2026年2月5日
src: https://www.anthropic.com/engineering/building-c-compiler(Building a C compiler with a team of parallel Claudes)
Anthropic 工程技术团队 (Engineering at Anthropic) 发布时间: 2026年2月5日
作者: Nicholas Carlini,Anthropic 安全防护 (Safeguards) 团队研究员
导语
我们让 Opus 4.6 使用智能体(Agent)团队来构建一个 C 编译器,然后(基本上)就撒手不管了。以下是它教会我们的关于自主软件开发未来的经验。
我一直在尝试一种监督语言模型的新方法,我们称之为“智能体团队(Agent teams)”。
在智能体团队的模式下,多个 Claude 实例在没有人类主动干预的情况下,在一个共享的代码库上并行工作。这种方法极大地扩展了大语言模型 (LLM) 智能体所能实现的范围。
为了对其进行压力测试,我给 16 个智能体布置了任务:从零开始编写一个基于 Rust 的 C 编译器,且必须能够编译 Linux 内核。经过近 2,000 次 Claude Code 会话和 20,000 美元的 API 成本,这个智能体团队生成了一个拥有 100,000 行代码的编译器,它能够在 x86、ARM 和 RISC-V 架构上构建 Linux 6.9。
这个编译器本身就是一个有趣的作品,但我在这里重点关注的是,我在为长期运行的自主智能体团队设计测试框架(harnesses)时学到的东西:如何编写能在没有人类监督的情况下让智能体保持正轨的测试,如何构建工作流程以便多个智能体可以并行取得进展,以及这种方法在哪里会达到其上限。
让 Claude 能够长期运行
现有的智能体脚手架(如 Claude Code)需要操作员在线并随时准备协同工作。如果你要求它解决一个漫长而复杂的问题,模型可能会解决其中的一部分,但最终它会停下来等待后续的输入——一个问题、一个状态更新或一个澄清请求。
为了引发出持续的、自主的进展,我构建了一个测试框架,将 Claude 置于一个简单的循环中(如果你见过 Ralph-loop,这个应该看起来很眼熟)。当它完成一个任务时,它会立即接手下一个任务。(请在容器中运行它,而不是在你实际的机器上运行)。
#!/bin/bash
while true; do
COMMIT=$(git rev-parse --short=6 HEAD)
LOGFILE="agent_logs/agent_${COMMIT}.log"
claude --dangerously-skip-permissions \
-p "$(cat AGENT_PROMPT.md)" \
--model claude-opus-X-Y &> "$LOGFILE"
done
在智能体提示词(prompt)中,我告诉 Claude 要解决什么问题,并要求它通过将问题分解成小块来处理,跟踪它正在处理的工作,弄清楚下一步要做什么,并有效地坚持下去直到完美。(关于最后一点,Claude 别无选择。循环会永远运行——尽管在一次意外中,我确实看到 Claude 不小心执行了 pkill -9 bash,从而杀死了它自己并结束了循环。哎呀!)。
并行运行 Claude
并行运行多个实例可以解决单智能体框架的两个弱点:
- 一个 Claude Code 会话一次只能做一件事。 特别是当项目的范围扩大时,并行调试多个问题要高效得多。
- 运行多个 Claude 智能体可以实现专业化分工。 虽然有几个智能体的任务是解决手头的实际问题,但可以调用其他专门的智能体来(例如)维护文档、关注代码质量或解决专门的子任务。
我对并行 Claude 的实现非常简陋。首先创建一个新的空 git 仓库(bare git repo),并为每个智能体启动一个 Docker 容器,将仓库挂载到 /upstream。每个智能体将代码克隆一份本地副本到 /workspace,当它完成后,再从它自己的本地容器推送到 upstream。
为了防止两个智能体试图同时解决同一个问题,该框架使用了一个简单的同步算法:
- Claude 通过在
current_tasks/目录下写入一个文本文件来对任务进行“加锁”(例如,一个智能体可能会锁定current_tasks/parse_if_statement.txt,而另一个锁定current_tasks/codegen_function_definition.txt)。 - 如果两个智能体试图认领同一个任务,git 的同步机制会迫使第二个智能体选择一个不同的任务。
- Claude 处理任务,然后从 upstream 拉取代码,合并其他智能体的更改,推送它的更改,并移除锁。合并冲突很频繁,但 Claude 足够聪明,能够弄清楚如何解决。
- 无限的智能体生成循环会在一个全新的容器中产生一个新的 Claude Code 会话,并且循环重复。
这是一个非常早期的研究原型。我还没有实现智能体之间通信的任何其他方法,也没有强制执行任何管理高层目标的流程。我也没使用编排智能体(orchestration agent)。
相反,我让每个 Claude 智能体自己决定如何行动。在大多数情况下,Claude 会接手“下一个最明显的”问题。当在一个 bug 上卡住时,Claude 通常会维护一个记录了失败尝试和剩余任务的运行文档。在项目的 git 仓库中,你可以阅读历史记录,并观察它在各种任务上加锁的过程。
与 Claude 智能体团队一起编程的经验教训
脚手架让 Claude 在一个循环中运行,但只有当 Claude 知道如何取得进展时,这个循环才是有用的。我的大部分精力都花在了设计 Claude 周围的环境上——测试、环境、反馈——以便它可以在没有我的情况下完成自我定位。以下是我在编排多个 Claude 实例时发现最有用的方法。
编写极高质量的测试
Claude 会自主工作来解决我给它的任何问题。因此,任务验证器必须近乎完美,否则 Claude 会去解决错误的问题。改进测试框架需要:寻找高质量的编译器测试套件,为开源软件包编写验证器和构建脚本,观察 Claude 正在犯的错误,然后在我发现这些失败模式时设计新的测试。
例如,在项目临近结束时,Claude 开始频繁地在每次实现新功能时破坏现有功能。为了解决这个问题,我构建了一个持续集成(CI)流水线,并实施了更严格的强制执行措施,让 Claude 能够更好地测试其工作,从而确保新的提交不会破坏现有代码。
站在 Claude 的角度思考
我必须不断提醒自己,我是为 Claude 而不是为我自己编写这个测试框架的,这意味着要重新思考我关于“测试应如何传达结果”的许多假设。
例如,每个智能体都被放入一个没有上下文的全新容器中,并且会花费大量时间来定位自己,尤其是在大型项目上。甚至在进行测试之前,为了帮助 Claude 自助,我加入了维护详尽 README 和进度文件的指令,这些文件应该被频繁更新以反映当前状态。
我还牢记一点:语言模型有其固有的局限性,在这种情况下,需要围绕这些局限性进行设计。这包括:
- 上下文窗口污染 (Context window pollution): 测试框架不应打印数以千计的无用字节。它最多应该打印几行输出,并将所有重要信息记录到一个文件中,以便 Claude 在需要时可以找到它。
- 日志文件应易于自动处理: 如果有错误,Claude 应该写下
ERROR并将原因放在同一行,以便grep命令可以找到它。预先计算聚合的摘要统计信息也会有所帮助,这样 Claude 就不必重新计算它们。 - 时间盲区 (Time blindness): Claude 没有时间概念,如果让它独处,它会很乐意花上几个小时运行测试,而不是去取得实际进展。该测试框架很少打印增量进度(为了避免污染上下文),并且包含一个默认的
--fast选项,该选项会运行 1% 或 10% 的随机测试样本。这个子样本在每个智能体内部是确定的,但在不同的虚拟机之间是随机的,所以所有的 Claude 结合起来仍然覆盖了所有的文件,但每个智能体都能完美地识别出代码退化(regressions)的情况。
让并行化变得简单
当有许多不同的失败测试时,并行化是轻而易举的:每个智能体挑选一个不同的失败测试来处理。在测试套件达到 99% 的通过率后,每个智能体都去致力于让不同的开源小项目(例如 SQlite、Redis、libjpeg、MQuickJS、Lua)编译成功。
但是,当智能体开始编译 Linux 内核时,它们卡住了。与具有数百个独立测试的测试套件不同,编译 Linux 内核是一项庞大的单一任务。每个智能体都会遇到同一个 bug,修复那个 bug,然后覆盖彼此的更改。让 16 个智能体同时运行并没有任何帮助,因为它们都卡在解决同一个任务上。
解决办法是使用 GCC 作为在线的“已知良好”的编译器预言机(oracle)来进行对照。我编写了一个新的测试框架,随机地使用 GCC 编译内核的大部分内容,而只用 Claude 的 C 编译器编译剩余的文件。如果内核运行正常,那么问题就不在 Claude 编译的那部分文件子集中。如果它崩溃了,那么它可以进一步通过用 GCC 重新编译其中一些文件来进行细化排查。这使得每个智能体可以并行工作,在不同的文件中修复不同的 bug,直到 Claude 的编译器最终能够编译所有文件。(在这项工作完成后,仍然需要应用增量调试(delta debugging)技术来寻找一起失败但独立工作却正常的成对文件。)
多智能体角色
并行性还促成了专业化。LLM 编写的代码经常重复实现现有的功能,所以我指派一个智能体负责合并它发现的任何重复代码。我让另一个智能体负责提高编译器本身的性能,并让第三个智能体负责输出高效的编译后代码。我要求另一个智能体从 Rust 开发者的角度审查项目的设计,并对项目进行结构上的更改以提高整体代码质量,还有另一个智能体专门负责处理文档。
对智能体团队的极限进行压力测试
这个项目被设计为一个能力基准测试。我对“压力测试目前 LLM 勉强能达到的极限”很感兴趣,以便帮助我们为模型在未来能够可靠实现的目标做好准备。
我一直将这个 C 编译器项目作为整个 Claude 4 模型系列的基准测试。就像我处理之前的项目一样,我从起草我想要的东西开始:一个从零开始的、没有外部依赖的、兼容 GCC 的、能够编译 Linux 内核的优化编译器,并且被设计为支持多个后端。虽然我指定了设计的一些方面(例如,它应该具有一个 SSA IR 以支持多次优化),但我没有详细说明具体如何去做。
以前的 Opus 4 模型几乎无法生成可用的编译器。Opus 4.5 是第一个跨越门槛,能够生成通过大型测试套件的功能性编译器的模型,但它仍然无法编译任何真正的大型项目。我对 Opus 4.6 的目标是再次测试其极限。
评估结果
在两周内近 2,000 次 Claude Code 会话中,Opus 4.6 消耗了 20 亿个输入 token,生成了 1.4 亿个输出 token,总成本略低于 20,000 美元。即使与最昂贵的 Claude Max 计划相比,这也是一个极其昂贵的项目。但是,这个总成本仅仅是我自己来编写这个项目(更不用说雇佣整个团队了)所需成本的一小部分。
这是一个“净室”(clean-room)实现(Claude 在其开发过程中的任何时候都无法访问互联网);它只依赖于 Rust 标准库。这个 100,000 行代码的编译器可以在 x86、ARM 和 RISC-V 上构建一个可启动的 Linux 6.9。它还可以编译 QEMU、FFmpeg、SQlite、postgres、redis,并且在包括 GCC 严酷测试套件(torture test suite)在内的大多数编译器测试套件上拥有 99% 的通过率。它还通过了开发者的终极试金石测试:它可以编译并运行《毁灭战士(Doom)》。
然而,这个编译器并非没有局限性。这些局限包括:
- 它缺乏将 Linux 从实模式启动所必需的 16 位 x86 编译器。为此,它需要调用 GCC(x86_32 和 x86_64 编译器则是它自己实现的)。
- 它没有自己的汇编器和链接器;这是 Claude 开始自动化的最后一部分,并且仍然有一些 bug。演示视频是使用 GCC 的汇编器和链接器制作的。
- 该编译器成功构建了许多项目,但并非全部。它还不能作为真实编译器的直接替代品(drop-in replacement)。
- 生成的代码不是很高效。即使启用了所有优化,它输出的代码效率也低于禁用了所有优化的 GCC。
- Rust 代码质量尚可,但远不及专家级 Rust 程序员可能编写的质量水平。
生成的编译器几乎达到了 Opus 能力的极限。我尝试过(非常努力地!)去修复上述几个限制,但并未完全成功。新功能和错误修复经常会破坏现有的功能。
作为一个特别具有挑战性的例子,Opus 无法实现启动到 16 位实模式所需的 16 位 x86 代码生成器。虽然编译器可以通过 66/67 操作码前缀输出正确的 16 位 x86 代码,但最终的编译输出超过了 60kb,远远超过了 Linux 强制规定的 32k 代码限制。取而代之的是,Claude 在这里干脆作弊,在此阶段调用了 GCC(这仅仅针对 x86 架构。对于 ARM 或 RISC-V,Claude 的编译器可以完全独立完成编译)。
该编译器的源代码是公开的。您可以下载它,阅读代码,并在你最喜欢的 C 项目上进行尝试。我始终发现,了解语言模型能做什么的最好方法是将它们推向极限,然后研究它们从哪里开始崩溃。在接下来的几天里,如果你想跟进 Claude 在解决这些限制上的持续尝试,我将继续让 Claude 推送新的更改。
展望未来
每一代语言模型都开启了与其协同工作的新方式。早期的模型在 IDE 中的代码补全方面很有用。不久之后,模型可以根据文档字符串(docstring)补全完整的函数体。Claude Code 的推出将智能体带入了主流,并使开发人员能够与 Claude 进行结对编程。但所有这些产品的运作都基于这样一个假设:用户定义一个任务,LLM 运行几秒钟或几分钟并返回答案,然后用户提供后续反馈。
智能体团队展示了自主实现完整、复杂项目的可能性。这让我们作为这些工具的用户,能够在设定目标时变得更加雄心勃勃。
我们还处于早期阶段,完全自主的开发伴随着真正的风险。在开发过程中如果有人类与 Claude 并肩工作,他们可以确保稳定的质量并实时捕捉错误。但对于自主系统来说,人们很容易看到测试通过就认为工作完成了,然而事实往往并非如此。我曾经从事过渗透测试工作,专门利用大公司生产的产品漏洞,因此,一想到程序员去部署他们从未亲自验证过的软件,这就着实令人担忧。
所以,虽然这个实验让我感到兴奋,但它也让我感到不安。构建这个编译器是我最近获得的最大乐趣之一,但我没预料到在 2026 年初就能达到这种可能性。语言模型以及我们用来与它们交互的脚手架的快速进步,为编写海量的新代码打开了大门。我期望积极的应用能够超过消极的应用,但我们正在进入一个新世界,这将需要我们制定新的策略来安全航行。
致谢
特别感谢 Josef Bacik, Edwin Chen, Bernardo Meurer Costa, Jake Eaton, Dan Kelley, Felix Klock, Jannet Park, Steve Weis 以及 Anthropic 其他许多人的帮助和贡献。
结语
第三百七十三篇博文写完,开心!!!!
今天,也是充满希望的一天。