核验日期:2026-07-08
内容来源:Anthropic Engineering 官方技术博客。本文先作为 Claude Code 专区后台草稿入库,发布前应由内容人员复核标题、摘要、图片、来源链接与是否需要补充本站实践说明。
用一组并行 Claude 构建 C 编译器
我们让 Opus 4.6 使用智能体团队构建一个 C 编译器,然后基本放手不管。它让我们看到了自主软件开发的未来。
来源:https://www.anthropic.com/engineering/building-c-compiler
发布日期:2026-02-05
作者 Nicholas Carlini 是我们 Safeguards 团队的研究员。
我一直在试验一种监督语言模型的新方法,我们称之为“智能体团队”。
使用智能体团队时,多个 Claude 实例会在共享代码库上并行工作,无需人类主动干预。这种方法显著扩大了 LLM 智能体可实现工作的范围。
为了对它进行压力测试,我给 16 个智能体布置了一项任务:从零编写一个基于 Rust 的 C 编译器,并且能够编译 Linux 内核。经过近 2,000 个 Claude Code 会话和 20,000 美元 API 成本,这个智能体团队产出了一个 100,000 行的编译器,可以在 x86、ARM 和 RISC-V 上构建 Linux 6.9。
$!
/$
这个编译器本身就是一个有趣的产物,但本文重点是我在为长时间运行的自主智能体团队设计运行框架时学到的东西:如何编写测试,让智能体在没有人类监督的情况下保持方向;如何组织工作,让多个智能体并行取得进展;以及这种方法在哪里会触及上限。
让 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
复制
在智能体提示词中,我会告诉 Claude 要解决什么问题,并要求它通过把问题拆成小块、跟踪自己正在处理什么、弄清楚接下来要做什么来推进,实际上一直继续直到完美为止。(关于最后一点,Claude 没有选择。循环会永远运行,尽管有一次我确实看到 Claude 不小心执行了 pkill -9 bash,从而杀死自己并结束循环。哎呀!)
并行运行 Claude
并行运行多个实例可以解决单智能体运行框架的两个弱点:
- 一个 Claude Code 会话一次只能做一件事。尤其当项目范围扩大时,并行调试多个问题效率高得多。
- 运行多个 Claude 智能体可以实现专门化。当少数智能体负责解决手头的实际问题时,其他专门智能体可以被调用来(例如)维护文档、关注代码质量,或解决专门子任务。
我的并行 Claude 实现非常简陋。先创建一个新的 bare git 仓库,然后为每个智能体启动一个 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 会话,然后循环重复。
这是一个非常早期的研究原型。我还没有实现智能体之间的任何其他通信方式,也没有强制执行任何管理高层目标的流程。我没有使用编排智能体。
相反,我把如何行动的决定交给每个 Claude 智能体。大多数情况下,Claude 会拿起“下一个最明显”的问题。卡在某个 bug 上时,Claude 通常会维护一份运行中的文档,记录失败方法和剩余任务。在该项目的 git 仓库中,你可以阅读历史记录,观察它如何锁定各类任务。
使用 Claude 智能体团队编程的经验
脚手架会让 Claude 在循环中运行,但只有当 Claude 能判断如何取得进展时,这个循环才有用。我的大部分精力都花在围绕 Claude 设计环境上,包括测试、环境和反馈,使它无需我介入也能定位自身状态。下面是我在编排多个 Claude 实例时发现最有帮助的方法。
编写极高质量的测试
Claude 会自主努力解决我给它的任何问题。因此,任务验证器必须接近完美,否则 Claude 会解决错误的问题。改进测试运行框架需要寻找高质量编译器测试套件,为开源软件包编写验证器和构建脚本,观察 Claude 正在犯什么错误,然后在识别出这些失败模式后设计新测试。
例如,在项目后期,Claude 开始经常在实现新功能时破坏已有功能。为了解决这个问题,我构建了持续集成流水线,并实施更严格的约束,使 Claude 能更好地测试自己的工作,确保新提交不会破坏现有代码。
站在 Claude 的角度
我必须不断提醒自己,我是在为 Claude 编写这个测试运行框架,而不是为自己编写。这意味着要重新思考许多关于测试应如何传达结果的假设。
例如,每个智能体都会被放进一个没有上下文的全新容器中,并会花大量时间定位自身状态,尤其是在大型项目上。甚至在进入测试之前,为了帮助 Claude 帮助自己,我加入了指令,要求它维护详细的 README 和进度文件,并频繁更新当前状态。
我也始终考虑到语言模型的固有限制,在这个案例中,需要围绕这些限制进行设计。这些限制包括:
- 上下文窗口污染: 测试运行框架不应打印数千字节的无用内容。最多只应打印几行输出,并把所有重要信息记录到文件中,让 Claude 在需要时可以找到。日志文件应易于自动处理:如果有错误,Claude 应该写 ERROR,并把原因放在同一行,这样 grep 就能找到。预先计算汇总统计数据也有帮助,这样 Claude 不必重新计算。
- 时间盲区: Claude 无法感知时间,如果放任不管,会乐意花几个小时运行测试,而不是取得进展。运行框架会以较低频率打印增量进展(避免污染上下文),并包含默认
--fast选项,用于运行 1% 或 10% 的随机样本。该子样本对每个智能体是确定性的,但在不同 VM 之间是随机的,因此 Claude 仍然覆盖所有文件,同时每个智能体都能精确识别回归。
让并行变得容易
当有许多不同失败测试时,并行化很简单:每个智能体选择一个不同的失败测试来处理。测试套件达到 99% 通过率后,每个智能体开始尝试让一个不同的小型开源项目(例如 SQlite、Redis、libjpeg、MQuickJS、Lua)能够编译。
但当智能体开始编译 Linux 内核时,它们卡住了。不同于包含数百个独立测试的测试套件,编译 Linux 内核是一个巨大的单体任务。每个智能体都会遇到同一个 bug,修复同一个 bug,然后互相覆盖彼此的更改。运行 16 个智能体没有帮助,因为每个都卡在同一个任务上。
修复方法是使用 GCC 作为运行时可用的已知正确编译器判定器进行对比。我写了一个新的测试运行框架,随机用 GCC 编译内核的大部分文件,只用 Claude 的 C Compiler 编译剩余文件。如果内核能工作,说明问题不在 Claude 编译的文件子集中。如果它坏了,就可以进一步通过用 GCC 重新编译这些文件中的一部分来缩小范围。这让每个智能体可以并行工作,修复不同文件中的不同 bug,直到 Claude 的编译器最终能够编译所有文件。(这成功后,仍然需要应用 delta debugging 技术,找出单独编译能工作、一起编译会失败的文件对。)
多种智能体角色
并行也支持专门化。LLM 编写的代码经常重新实现已有功能,因此我给一个智能体分配任务,让它合并发现的重复代码。我让另一个负责改进编译器自身性能,第三个负责输出高效的编译后代码。我要求另一个智能体从 Rust 开发者视角批判项目设计,并对项目做结构性改动以改善整体代码质量,另一个则处理文档。
压力测试智能体团队的极限
这个项目被设计为一个能力基准。我想压力测试 LLM 目前勉强能做到什么,从而帮助我们为未来模型将能可靠做到什么做好准备。
我一直使用 C Compiler 项目作为整个 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 方案相比,这也是一个极其昂贵的项目。但这个总额只是我自己产出这些内容所需成本的一小部分,更不用说一整个团队了。
这是一个洁净室实现(开发过程中 Claude 从未拥有互联网访问权限);它只依赖 Rust 标准库。这个 100,000 行编译器可以在 x86、ARM 和 RISC-V 上构建可启动的 Linux 6.9。它还可以编译 QEMU、FFmpeg、SQlite、postgres、redis,并且在包括 GCC torture test suite 在内的大多数编译器测试套件上达到 99% 通过率。它也通过了开发者的终极试金石:它可以编译并运行 Doom。
不过,这个编译器并非没有限制。包括:
- 它缺少启动 Linux 脱离 real mode 所需的 16 位 x86 编译器。为此,它会调用 GCC(x86_32 和 x86_64 编译器是它自己的)。
- 它没有自己的 assembler 和 linker;这些是 Claude 最后才开始自动化的部分,仍然有些 bug。演示视频是用 GCC assembler 和 linker 生成的。
- 这个编译器可以成功构建许多项目,但不是全部。它还不能直接替代真正的编译器。
- 生成代码效率不高。即使启用所有优化,它输出的代码也比关闭所有优化的 GCC 更低效。
- Rust 代码质量合理,但远达不到专家 Rust 程序员可能产出的质量。
最终得到的编译器已经接近 Opus 能力的极限。我曾非常努力地尝试修复上述几个限制,但并未完全成功。新功能和 bug 修复经常破坏已有功能。
一个特别有挑战性的例子是,Opus 无法实现启动到 16 位 real mode 所需的 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 内部许多其他人的协助和贡献。