2月4号那天晚上,我刷到Anthropic官方的一篇技术博客。刚看完第一段,我就愣住了。故事是这样的:Anthropic的研究员Nicholas Carlini做了一个实验。他同时启动了16个Claude Opus的AI实例,给它们一个看起来不可能的任务——用Rust从零开始编写一个C编译器,而且这个编译器要能够成功编译Linux内核。然后,他就真的走开了。
两周之后,这16个AI智能体“交付”了一个包含10万行代码的编译器。它能跑。它能编译Linux 6.9的内核。支持x86、ARM、RISC-V三个主流架构。还能顺利编译QEMU、FFmpeg、PostgreSQL、Redis这些大型开源项目。在GCC的torture(严酷)测试套件上,通过率达到了99%。
甚至能编译并运行《Doom》——Carlini说,这是“开发者的终极试金石”。
我盯着屏幕看了很久。这不是什么概念验证的demo,也不是玩具项目。这是一个真实的、可用的、复杂的系统级软件。而且在整个过程中,人类几乎没怎么参与具体的开发工作。
它到底是怎么做到的?

我的第一反应是:这不可能吧?让AI自己写10万行代码?那代码结构得乱成什么样?
然后我开始仔细阅读Carlini的博客。慢慢地,我发现整个实验的设计比我想象的要精妙得多。
第一层:如何让AI“持续工作”?
传统的AI代码助手使用模式是:你问一个问题,它回答一部分,然后停下来等待你的下一步指令。在这种模式下,你必须全程在线,像个监工一样不断发出命令。
Carlini要解决的第一个核心问题就是:如何让Claude在不需要人类时刻监督的情况下,自己持续干活?
他的方案简单而直接——写了一个无限循环的Bash脚本:
while true; do
claude --dangerously-skip-permissions \
-p "$(cat AGENT_PROMPT.md)" \
--model claude-opus-X-Y &> "$LOGFILE"
done
这个脚本会不停地启动新的Claude会话。当Claude完成一个任务后,它不需要等待人类输入,而是直接读取相同的提示词文件,开始下一个任务。
这里有一个很微妙的设计:在提示词(prompt)里,Carlini告诉Claude要把大问题分解成小块,追踪自己的工作进度,自己决定下一步做什么,并且要“一直做到完美为止”。
我琢磨了一下,这个设计其实挺“狠”的。因为Claude被困在这个无限循环里,它别无选择,只能不断地工作。Carlini甚至在博客中提到,有一次Claude不小心执行了 pkill -9 bash 命令,把自己杀死了,循环才得以终止。
第二层:一个不够,那就十六个
但单个智能体有一个致命问题——它一次只能做一件事。
想象一下你在编译一个大项目时发现了50个bug。如果只有一个人,你得一个一个地修。但如果有16个人,每个人负责几个不同的bug,效率就完全不同了。
所以,Carlini构建了一个多智能体(Multi-Agent)系统。具体设计如下:
- 创建一个空的Git仓库作为“上游”代码库。
- 每个智能体(Agent)运行在独立的Docker容器里。
- 每个智能体克隆一份上游代码到自己的
/workspace 目录。
- 智能体完成工作后,将改动推(push)回上游仓库。
但这里出现了一个大问题:16个智能体同时工作,如何避免它们干重复的事情?
Carlini用了一个朴素但极其有效的方案——文件锁。
智能体在开始一个任务之前,先在 current_tasks/ 目录下创建一个锁文件。例如,一个智能体锁定了 current_tasks/parse_if_statement.txt,另一个则锁定 current_tasks/codegen_function_definition.txt。如果两个智能体尝试锁定同一个任务,Git的同步机制会强制后一个智能体选择其他任务。
智能体完成任务后,会拉取(pull)上游的最新更新,合并其他智能体的改动,推送(push)自己的代码,最后删除锁文件。合并冲突很常见,但Claude足够聪明,能够自己解决这些冲突。
我觉得这个设计非常克制。Carlini没有构建复杂的中央调度系统,也没有所谓的“总指挥”智能体。每个Claude自己决定做什么——大多数情况下,它们会选择“下一个最显而易见的问题”去解决。
但一些关键问题仍需厘清
读到这里,我脑子里冒出了一堆疑问。
问题一:Claude如何知道自己做对了没有?
如果没有人类逐行检查代码,Claude如何判断自己编写的编译器是正确的?
Carlini花了巨大的精力来设计测试系统。他说:“Claude会自主地解决我给它的任何问题。所以,任务验证器必须接近完美,否则Claude只会解决错误的问题。”
他做了以下几件事:
- 寻找高质量的C/C++编译器测试套件。
- 为各种开源软件包编写验证器和构建脚本。
- 仔细观察Claude常犯的错误类型,然后专门设计新的测试来捕获这类失败模式。
例如,在项目后期,Claude开始频繁地在实现新功能时破坏已有功能。为此,Carlini搭建了一个CI(持续集成)流程,强制要求每个新提交(commit)都不能破坏现有的任何测试。
问题二:Claude的“视角”和人类不一样
Carlini反复强调:他必须站在Claude的认知角度去设计测试系统,而不是按照人类的习惯。
这里有两个细节设计,我觉得特别有意思:
上下文窗口污染:测试系统不能打印几千行没用的调试信息。它最多只打印几行关键信息,把详细的日志记录到文件中,Claude需要时再去找。如果有错误发生,应该在同一行清晰地标注“ERROR”和原因,这样Claude用 grep 命令就能快速定位。
时间盲:Claude感知不到时间的流逝。如果不加以限制,它会“愉快地”花几个小时去运行一个完整的测试集,而不是推进项目进度。因此,Carlini让测试系统以较低的频率增量打印进度(避免污染上下文),并且增加了 --fast 选项。在这个模式下,只运行1%或10%的随机样本测试。这个样本对每个智能体来说是确定性的,但在不同虚拟机之间是随机的,因此所有智能体仍然能覆盖所有测试文件,每个智能体也能完美识别出代码的回归(regression)。
我琢磨了很久这两个设计。Carlini本质上不是在“训练”Claude,而是在“重塑”环境——让环境的信息呈现方式主动去适配Claude的认知特点。
问题三:编译Linux内核这种“巨型任务”如何并行?
前期工作很顺利。当有几百个独立的测试失败时,每个智能体可以挑选不同的测试去修复。当测试通过率达到99%后,每个智能体可以负责让一个不同的小项目(如SQLite、Redis、libjpeg等)能够编译通过。
但到了编译整个Linux内核这一步,问题来了。
Linux内核不是几百个独立的测试,它是一个单一的、巨型的任务。所有智能体都会撞上同一个bug,修复完这个bug后,它们的改动可能会互相覆盖。16个智能体此时没有带来帮助,因为它们都卡在了同一个地方。
Carlini的解决方案非常巧妙:使用GCC作为“在线的、已知正确的编译器预言机”。
他编写了一个新的测试系统:随机选择内核中的大部分文件用GCC编译,只有剩下的少部分文件用Claude的编译器来编译。如果内核能成功启动并运行,说明问题不在Claude负责编译的那部分文件里。如果内核崩溃了,就进一步细化范围,重新选择一些文件改用GCC编译。
这样,每个智能体就可以并行工作,修复不同文件里的不同bug,直到Claude的编译器最终能够编译所有的内核文件。
读到这里,我突然明白了一件事:Carlini不是在“监督”Claude写代码,而是在设计一个让Claude能够自我验证、自我迭代的闭环系统。
专业化分工:并非所有智能体都在写编译器
还有一个很有意思的点:Carlini让不同的智能体扮演了不同的角色。
大型语言模型生成的代码经常重复实现已有的功能,所以他让一个智能体专门负责合并重复代码。让另一个智能体专注于提升编译器本身的性能。第三个负责优化编译后代码的输出效率。他还让一个智能体扮演“代码审查者”的角色,从资深Rust开发者的视角来批评项目的整体设计,并提出结构性改进建议。另一个智能体则专门负责维护和更新文档。
这种分工方式让我想到了一个高效的人类开发团队。但区别在于,这些智能体之间没有明确的实时沟通机制,它们只通过Git仓库的代码和提交历史来进行无声的“对话”。
成果与局限
数据显示:
- 接近2000次独立的Claude会话。
- 总耗时两周。
- 消耗了约20亿个输入token,生成了1.4亿个输出token。
- 总成本不到2万美元。
- 最终产出:10万行Rust代码。
这个编译器是一个“净室实现”——Claude在整个开发过程中没有联网搜索,仅仅依赖于Rust标准库。
它能做到的事:
- 编译并成功启动Linux 6.9内核(x86、ARM、RISC-V架构)。
- 编译QEMU、FFmpeg、SQLite、PostgreSQL、Redis等大型项目。
- GCC torture测试套件99%的通过率。
- 编译并运行经典游戏《Doom》。
但它也有明确的局限:
- 缺少16位x86代码生成器(启动Linux内核的实模式需要),这部分最终调用了GCC来完成。
- 没有自己的汇编器和链接器,演示视频中使用了GCC的工具链。
- 并非所有C项目都能编译成功,还不能作为GCC/Clang的完全替代品。
- 生成的代码运行效率很低——即使开启所有优化选项,其性能也不如关闭所有优化的GCC。
- Rust代码质量尚可,但远未达到专家级Rust程序员的水准。
Carlini坦言:“生成的编译器几乎达到了Claude Opus模型能力的极限。我(努力!)尝试修复上述几个局限,但未能完全成功。新增功能和修复bug常常会破坏已有的功能。”
他举了一个特别困难的例子:Claude无法实现启动实模式(real mode)所需的16位x86代码生成器。虽然编译器可以通过操作码前缀输出形式正确的16位代码,但编译后的代码体积超过了60KB,远超Linux内核强制的32K限制。因此,Claude在这里“作弊”了,直接调用GCC来处理这个编译阶段。
这件事为何令人深思
读完整个项目介绍,我的情绪很复杂。
Carlini在博客的最后说了这样一段话:
“这个实验让我兴奋,但也让我感到不安。构建这个编译器是我最近最有趣的经历之一,但我没想到这在2026年初就接近成为可能。我曾经做过渗透测试,攻击大公司产品的漏洞,而程序员部署他们从未亲自验证过的软件这个想法,是个真实的担忧。”
我理解他的感受。
从技术角度看,这个项目展示了一种多智能体系统设计的范式:
- 自主循环:让智能体能够持续工作,无需人类随时监督。
- 文件锁协调:采用简单但有效的机制实现多智能体间的任务同步。
- 环境适配:重塑信息呈现方式,以适应LLM的认知特点。
- 测试驱动:用高质量、自动化的测试系统替代人类的微观监督。
- 角色专业化:让不同的智能体承担互补的专门职责。
这套设计思路可以推广到其他大型软件项目。想象一下,一个创业团队可以投入2万美元,让16个AI智能体在两周内构建出一个复杂系统的原型。或者,一个开源项目可以让AI智能体持续修复bug、改进性能、维护文档。
但另一方面,当AI能够自主产出10万行代码,而人类无法进行逐行审查时,我们如何保证其质量和安全性?测试通过并不等于没有漏洞。生成的代码可能包含微妙的安全问题,这些问题在常规测试中并不会暴露。
Carlini提到了他以前做渗透测试的经历。我想他担心的是:如果未来有大量软件由AI自主生成,整个软件的攻防局面会发生什么变化?AI生成的代码会不会存在一些系统性的、可预测的弱点?
思考:我们打开了什么样的魔盒?
这个项目让我重新思考“编程”这件事的本质。
传统上,编程是这样的:人类设计算法,编写代码,调试,测试,部署。在这个过程中,人类深度参与每一个环节,理解每一行代码的含义。
现在,编程开始变成这样:人类定义目标和测试框架,AI生成代码,进行自我验证和迭代。人类可能只看最终结果和整体架构,而不再理解每一个具体的实现细节。
这并不是说AI将取代程序员。Carlini的角色已经变了——他不再是在写代码,而是在设计一个能让AI高效工作的系统。这需要对AI的能力边界、认知特点、协作方式有更深刻的理解。这可能比写代码本身更具挑战性。
但我确实感到某种不安。当软件的复杂度超过单个人类能够完全理解的程度时,我们如何保证它的正确性?过去我们依赖代码审查、自动化测试、形式化验证。但如果代码是AI生成的,长达10万行,没有人逐行读过,并且通过了所有测试,我们还能信任它吗?
Carlini将这个编译器的代码完全开源了。他鼓励大家下载、阅读代码,并在自己喜欢的C项目上尝试。他说:“我一直发现,理解语言模型能做什么的最好方法,就是把它们推到极限,然后研究它们在哪里开始崩溃。”
或许,这才是我们面对强大新技术时应有的态度。不是恐惧,也不是盲目乐观,而是认真地研究、找到其能力的边界、理解其局限,然后负责任地使用它。
写在最后
这个项目最让我印象深刻的,不是最终的结果——尽管一个能编译Linux内核的编译器已经足够惊人——而是Carlini所展示的那种设计思维。
他没有试图让Claude“像人类一样编程”。相反,他坦然接受了Claude的特点:上下文窗口有限、感知不到时间、会产出重复代码、在某些任务上存在能力极限。然后,他精心设计了一个环境,在这个环境里,Claude的这些特点不再成为障碍,甚至可以被巧妙地利用起来。
这让我想到一个类比:人类本身不会飞,但我们设计了飞机。我们没有试图让自己长出翅膀,而是理解了空气动力学,造出了适合在空中高效移动的工具。
也许,我们与AI的协作也是同样的道理。不是让AI变成人类,而是去理解AI运行的“物理规律”,并在此基础上,设计出让AI能够最大限度发挥其价值的系统。
Carlini说他没想到这在2026年初就接近可能。说真的,我也没想到。
魔盒已经打开。我们回不去了。现在真正的问题是:我们要如何负责任地、创造性地使用这些新能力?
GitHub项目地址:https://github.com/anthropics/claudes-c-compiler
Carlini的技术博客原文:https://www.anthropic.com/engineering/building-c-compiler
在技术快速迭代的今天,保持对前沿探索的好奇与审慎的思考同样重要。欢迎在云栈社区与我们继续探讨AI与系统软件开发的最新趋势与实践。