返回 文章 build CMS 文章

用并行 Claude 智能体团队从零构建 C 编译器

16 个 Claude 并行协作,用 2 万美元和两周时间从零写出能编译 Linux 内核的 C 编译器。

LLM智能体并行计算C编译器自主编程
成长分 / 100 80 综合收获、行动、留存与影响

用并行 Claude 智能体团队从零构建 C 编译器
为什么值得读了解一种全新的监督语言模型方法——智能体团队,让多个 Claude 实例在共享代码库上并行自主工作。

获取设计长时间运行自主智能体运行框架的实战经验,包括测试、环境与反馈机制。

关键洞察
  1. 智能体团队模式通过并行运行多个 Claude 实例,解决了单智能体框架需要人类持续干预和无法并行推进的弱点。
  2. 任务验证器必须近乎完美,否则 Claude 会解决错误的问题;测试框架需要为 Claude 而非人类设计。
  3. 并行化实现了专业化,例如指派不同 agent 负责合并重复代码、优化性能、改进代码质量或编写文档。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:12677

作者 Nicholas Carlini,我们 Safeguards 团队的研究员。

我一直在试验一种监督语言模型的新方法,我们称之为“智能体团队”(agent teams)。

借助智能体团队,多个 Claude 实例可以在同一个代码库上并行工作,无需人类主动干预。这种方式大幅扩展了 LLM 智能体能够完成的任务范围。

为了对它进行压力测试,我让 16 个智能体从零开始编写一个基于 Rust 的 C 编译器,要求能够编译 Linux 内核。经过近 2,000 次 Claude Code 会话、花费 20,000 美元 API 费用,这支智能体团队产出了一个 10 万行代码的编译器,可以在 x86、ARM 和 RISC-V 上构建 Linux 6.9。

这个编译器本身就是一件有意思的成果,但本文的重点是我在设计长时间运行的自主智能体团队所用的 harness(运行框架)时学到的东西:如何编写测试,让智能体在没有人类监督的情况下不偏离轨道;如何组织工作,让多个智能体能够并行推进;以及这种方法的天花板在哪里。

让 Claude 长时间运行

Claude Code 这类现有的智能体脚手架需要操作者在线并随时配合工作。如果你让它解决一个漫长而复杂的问题,模型可能会解决其中一部分,但最终会停下来等待后续输入,比如一个问题、一次状态更新或一个澄清请求。

为了促成持续的自主推进,我搭建了一个 harness,把 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

在智能体提示词中,我告诉 Claude 要解决什么问题,并要求它把问题拆成小块,记录自己正在做什么,想清楚下一步做什么,并且一直持续到做到完美为止。(最后这一点上,Claude 没有选择,因为循环会永远运行。不过有一次,我确实看到 Claude 不小心执行了 pkill -9 bash,把自己杀掉,循环也随之结束。哎呀!)

让 Claude 并行运行

并行运行多个实例,可以弥补单智能体 harness 的两个弱点:

  • 一个 Claude Code 会话同一时间只能做一件事。尤其是随着项目规模扩大,并行调试多个问题要高效得多。
  • 运行多个 Claude 智能体还可以实现分工。一部分智能体负责解决实际要解决的问题,其他专门的智能体则可以被调用来(比如)维护文档、留意代码质量,或解决专门的子任务。

我实现的并行 Claude 非常简陋。先创建一个新的 bare git 仓库,然后为每个智能体启动一个 Docker 容器,把该仓库挂载到 /upstream。每个智能体把本地副本克隆到 /workspace,完成工作后,再从自己的本地容器推送到上游。

为避免两个智能体同时去解决同一个问题,harness 使用了一个简单的同步算法:

  1. Claude 通过向 current_tasks/ 写入一个文本文件来对任务“加锁”(例如,一个智能体可能锁定 current_tasks/parse_if_statement.txt,另一个则锁定 current_tasks/codegen_function_definition.txt)。如果两个智能体试图认领同一个任务,git 的同步机制会迫使第二个智能体另选一个。
  2. Claude 完成任务后,会先从上游拉取、合并其他智能体的改动,再推送自己的改动并移除锁。合并冲突很常见,但 Claude 足够聪明,能自己搞定。
  3. 无限的智能体生成循环会在一个全新的容器中启动一个新的 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% 的随机抽样。这个子样本在单个智能体内是确定的,但在不同虚拟机之间是随机的,因此 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,总成本略低于 20,000 美元。即使与最贵的 Claude Max 套餐相比,这也是一个极其昂贵的项目。但这个总额只是我自己完成这件事所需成本的一小部分,更不用说一整个团队了。

这是一次洁净室(clean-room)实现(Claude 在开发过程中任何时候都没有互联网访问权限);它只依赖 Rust 标准库。这个 10 万行的编译器可以在 x86、ARM 和 RISC-V 上构建可启动的 Linux 6.9。它还能编译 QEMU、FFmpeg、SQlite、postgres、redis,并在大多数编译器测试套件上取得 99% 的通过率,包括 GCC torture 测试套件。它还通过了开发者的终极试金石:能编译并运行 Doom。

不过,这个编译器也并非没有局限。这些局限包括:

  • 它缺少 16 位 x86 编译器,而这是让 Linux 脱离实模式启动所必需的。对此它会调用 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 运行几秒或几分钟后返回一个答案,然后用户再给出后续指令。

智能体团队展示了自主实现完整的复杂项目的可能性。这让我们作为这些工具的用户,可以对自己的目标更有野心。

我们仍处于早期,完全自主的开发伴随着真实的风险。当人类在开发过程中与 Claude 在一起时,可以确保质量一致,并实时发现错误。对于自主系统,人们很容易看到测试通过就认为工作已经完成,而事实上这很少是真的。我曾从事渗透测试,利用大公司所生产产品中的漏洞,因此想到程序员部署自己从未亲自验证过的软件,我确实感到担忧。

所以,尽管这个实验令我兴奋,也让我感到不安。构建这个编译器是我最近玩得最开心的事情之一,但我没有想到在 2026 年初就能做到这个程度。语言模型以及我们与之交互的脚手架的快速进步,为编写海量的新代码打开了大门。我预计积极的应用将超过消极的,但我们正在进入一个新世界,需要新的策略才能安全地驾驭它。

致谢

特别感谢 Josef Bacik、Edwin Chen、Bernardo Meurer Costa、Jake Eaton、Dan Kelley、Felix Klock、Jannet Park、Steve Weis,以及 Anthropic 内外许多其他人的协助与贡献。