用一支并行 Claude 团队构建 C 编译器
我们让 Opus 4.6 以 agent teams 的方式构建一个 C 编译器,然后(基本上)走开了。这是它教给我们的、关于自主软件开发未来的东西。
作者:Nicholas Carlini,Anthropic Safeguards 团队研究员。
我一直在实验一种监督语言模型的新方式,我们称之为 "agent teams"(智能体团队)。
在 agent teams 中,多个 Claude 实例在一个共享代码库上并行工作,无需人类主动干预。这种方式极大扩展了 LLM 智能体能做到的事情的范围。
为了做压力测试,我给 16 个智能体布置了任务:从零开始用 Rust 写一个 C 编译器,要能编译 Linux 内核。经过近 2,000 个 Claude Code 会话和 2 万美元的 API 费用,这支智能体团队产出了一个 10 万行的编译器,能在 x86、ARM 和 RISC-V 上构建 Linux 6.9。
编译器本身就是一个有趣的工件,但我在这里想聚焦的是我学到的、为长时运行的自主智能体团队设计 harness 的经验:如何写出能在没有人类监督的情况下让智能体不跑偏的测试,如何组织工作让多个智能体能并行推进,以及这种方式的天花板在哪里。
让 Claude 长时运行
现有的智能体脚手架(比如 Claude Code)需要操作者在线陪跑。如果你交给它一个又长又复杂的问题,模型可能解决其中一部分,但最终会停下来等待继续输入——一个问题、一次状态汇报、或一个澄清请求。
为了引出持续的自主进展,我构建了一个把 Claude 卡进简单循环的 harness(如果你见过 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在智能体提示词里,我告诉 Claude 要解决什么问题,并要求它把问题拆成小块、跟踪自己正在做什么、想清楚接下来做什么,并且实际上一直干到完美为止。(最后这一点上 Claude 没得选——循环永远运行。不过有一次,我确实看到 Claude 不小心 pkill -9 bash,把自己杀了,循环随之终结。哎呀!)
并行运行 Claude
并行运行多个实例可以解决单智能体 harness 的两个弱点:
- 一个 Claude Code 会话一次只能做一件事。随着项目范围扩大,并行调试多个问题的效率要高得多。
- 多个 Claude 智能体允许专业化分工。少数智能体负责解决手头的实际问题,其他专职智能体可以被调用去(比如)维护文档、盯代码质量、或解决专门的子任务。
我的并行 Claude 实现非常简陋。创建一个新的 bare git 仓库;为每个智能体起一个 Docker 容器,把仓库挂载到 /upstream。每个智能体克隆一份本地副本到 /workspace,做完后从自己的本地容器推送回 upstream。
为了防止两个智能体同时解决同一个问题,harness 使用一个简单的同步算法:
- 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 会去解决错误的问题。改进测试 harness 的过程包括:寻找高质量的编译器测试套件、为开源软件包编写验证器和构建脚本、观察 Claude 正在犯的错误,然后在识别出这些失败模式后设计新的测试。
例如,项目接近尾声时,Claude 开始频繁地在实现新特性时破坏既有功能。为此,我搭了一条持续集成流水线,并实施了更严格的强制,让 Claude 能更好地测试自己的工作,使新提交无法破坏既有代码。
把自己放进 Claude 的鞋里
我必须不断提醒自己:这个测试 harness 是写给 Claude 用的,不是写给我用的——这意味着要重新思考许多"测试应该如何传达结果"的假设。
例如,每个智能体都被丢进一个没有任何上下文的全新容器,会花大量时间给自己定位,在大项目上尤其如此。所以在进入测试之前,为了帮 Claude 自助,我加入了维护详尽 README 和进度文件的指令,要求频繁更新当前状态。
我还时刻记着语言模型有一些固有局限,需要围绕它们做设计。包括:
- 上下文窗口污染:测试 harness 不应该打印成千上万字节的无用输出。它最多打印几行,并把所有重要信息记录到文件里,Claude 需要时自己去找。日志文件要易于自动化处理:如果有错误,Claude 应该写
ERROR并把原因放在同一行,这样grep能找到。预先计算聚合统计也有帮助,Claude 就不必重算。 - 时间失明:Claude 感知不到时间,放着不管的话,它会开开心心花好几个小时跑测试而不是推进工作。harness 以较低频率打印增量进度(避免污染上下文),并内置一个默认的
--fast选项,随机抽 1% 或 10% 的样本来跑。这个子样本对每个智能体是确定性的、但跨 VM 是随机的,于是 Claude 整体上仍覆盖所有文件,而每个智能体都能精确识别回归。
让并行变容易
当存在许多彼此独立的失败测试时,并行化是平凡的:每个智能体挑一个不同的失败测试去干。测试套件通过率达到 99% 之后,每个智能体转去让一个不同的小型开源项目(如 SQLite、Redis、libjpeg、MQuickJS、Lua)编译通过。
但当智能体们开始编译 Linux 内核时,它们卡住了。与几百个独立测试的套件不同,编译 Linux 内核是一个巨大的单一任务。每个智能体都会撞上同一个 bug、修那个 bug、然后互相覆盖对方的改动。跑着 16 个智能体也没用,因为每一个都卡在同一个任务上。
解法是把 GCC 用作在线的已知正确编译器 oracle 来对照。我写了一个新的测试 harness:随机地用 GCC 编译内核的大部分文件,只用 Claude 的 C 编译器编译剩下的文件。如果内核能工作,问题就不在 Claude 负责的那批文件里;如果坏了,还可以把其中一些文件换回 GCC 编译来进一步收窄。这让每个智能体得以并行工作、在不同文件里修不同的 bug,直到 Claude 的编译器最终能编译所有文件。(这套跑通之后,还需要用 delta debugging 技术找出那些"单独编译没问题、放在一起就失败"的文件对。)
多种智能体角色
并行还带来专业化。LLM 写的代码经常重复实现既有功能,所以我指派一个智能体专门合并它找到的重复代码。另一个负责提升编译器自身的性能,第三个负责让编译产物更高效。我还让一个智能体从 Rust 开发者的视角批评项目设计、做结构性调整以提升整体代码质量,再让一个去写文档。
压测智能体团队的极限
这个项目被设计为一个能力基准。我感兴趣的是压测 LLM 今天勉强能够到的极限,以帮助我们为模型将来能稳定做到的事情做准备。
我一直把这个 C 编译器项目当作跨整个 Claude 4 系列的基准。像此前的项目一样,我先起草了我想要什么:一个从零开始的优化编译器、零依赖、GCC 兼容、能编译 Linux 内核、并且设计上支持多后端。我指定了设计的某些方面(例如它应该有 SSA IR 以支持多个优化 pass),但没有展开任何"怎么做"的细节。
此前的 Opus 4 系列模型勉强能产出一个能用的编译器。Opus 4.5 是第一个跨过门槛的——能产出通过大型测试套件的可用编译器,但仍然无法编译任何真实的大项目。我对 Opus 4.6 的目标是再次试探极限。
评估
在两周内的近 2,000 个 Claude Code 会话里,Opus 4.6 消耗了 20 亿输入 token、生成了 1.4 亿输出 token,总成本略低于 2 万美元。即便与最贵的 Claude Max 订阅相比,这也是一个极其昂贵的项目。但这个总数只是我亲自做这件事所需成本的一个零头——更不用说一整个团队了。
这是一次洁净室实现(开发全程 Claude 没有互联网访问),只依赖 Rust 标准库。这个 10 万行的编译器能构建可启动的 Linux 6.9(x86、ARM、RISC-V),还能编译 QEMU、FFmpeg、SQLite、Postgres、Redis,在多数编译器测试套件上通过率 99%——包括 GCC torture 测试套件。它也通过了开发者的终极试金石:能编译并运行 Doom。
不过,这个编译器并非没有局限。包括:
- 它缺少从实模式启动 Linux 所需的 16 位 x86 编译器。这一步它调用 GCC(x86_32 和 x86_64 编译器是它自己的)。
- 它没有自己的汇编器和链接器;这是 Claude 最后才开始自动化的部分,仍然有些 bug。演示视频是用 GCC 的汇编器和链接器产出的。
- 编译器能成功构建许多项目,但不是全部。它还不是真实编译器的即插即用替代品。
- 生成的代码效率不高。即便开启所有优化,其输出也不如关闭全部优化的 GCC。
- Rust 代码质量尚可,但远达不到专家级 Rust 程序员的水准。
这个编译器已经近乎触到 Opus 能力的极限。我试过(很努力地!)修复上述几个局限,但没有完全成功。新特性和 bug 修复频繁破坏既有功能。
一个特别有挑战性的例子:Opus 无法实现启动进 16 位实模式所需的 16 位 x86 代码生成器。虽然编译器可以经由 66/67 操作码前缀输出正确的 16 位 x86,但编译产物超过 60kb,远超 Linux 强制的 32k 代码上限。于是 Claude 在这里干脆"作弊",调用 GCC 完成这一阶段。(只有 x86 如此;ARM 和 RISC-V 上,Claude 的编译器可以完全自己编译。)
编译器源码已公开。下载它、通读代码、拿你喜欢的 C 项目试试。我一贯发现,理解语言模型能做什么的最好方式,是把它们推到极限,然后研究它们从哪里开始崩坏。接下来几天我会继续让 Claude 推送新变更——如果你想跟进 Claude 对这些局限的持续攻坚的话。
展望
每一代语言模型都开启与它们协作的新方式。早期模型适合 IDE 里的 tab 补全。不久后,模型能从 docstring 补全整个函数体。Claude Code 的发布把智能体带入主流,让开发者能与 Claude 结对编程。但这些产品都建立在同一个假设上:用户定义一个任务,LLM 跑几秒或几分钟返回答案,然后用户给出后续指示。
Agent teams 展示了自主实现整个复杂项目的可能性。这让我们这些工具的使用者可以对目标更有野心。
我们仍处早期,完全自主的开发伴随真实的风险。当人类陪着 Claude 开发时,他们能保证稳定的质量、实时抓住错误。对自主系统而言,很容易看到测试通过就以为工作完成了——而事实很少如此。我以前做渗透测试,专门利用大公司产品里的漏洞,所以"程序员部署自己从未亲自验证过的软件"这个念头,是一个真实的担忧。
所以,这个实验让我兴奋的同时,也让我不安。构建这个编译器是我最近最开心的经历之一,但我没料到这件事在 2026 年这么早就接近可能。语言模型与我们用来与之交互的脚手架的快速进步,打开了写出海量新代码的大门。我预期正面应用会压过负面,但我们正在进入一个需要新策略来安全航行的新世界。
致谢
特别感谢 Josef Bacik、Edwin Chen、Bernardo Meurer Costa、Jake Eaton、Dan Kelley、Felix Klock、Jannet Park、Steve Weis 以及 Anthropic 上下许多人的协助与贡献。