2025 年初,我写了一篇《Claude 是编译器吗?》的文章。当时我的回答是:我不知道。
现在我很确定答案是“不,这是个范畴错误,它比编译器更厉害。”但这需要稍加解释。
计算机程序以极其复杂和挑剔著称。程序以极高的精度运行。不存在“挥手”这样的 CPU 指令。与此同时,高层次的目标又极度缺乏具体说明。
在一个高度模式化的世界观里,软件是按层级构建的,每一层都增加具体说明,并隐藏“不必要的”细节。愿景变成战略,产品计划变成编码计划,代码变成二进制文件。每一步都由不同的角色处理:高管、副总裁、产品经理、架构师、工程师、编译器。
关键在于,每一步都涉及做出大量决策。这就是提高具体说明程度的意义所在。(这也是我招聘工程师的两个关键指标之一是判断力的原因。另一个指标是和气。)
最底层,从源代码到二进制文件,是编译器的工作。编译器会做出大量决策!内联、寄存器分配、是发出警告还是直接拒绝程序。而这些决策至关重要:它们影响着性能、系统稳定性、可预测性和故障模式。编译器工程师的工作就是让编译器持续做出良好的决策。
一个优秀且值得信赖的编译器能让软件工程师免于做出这些决策。大多数工程师对编译器的工作原理知之甚少;他们不需要了解这些也能高效工作。
在 2025 年,我们处于一个使用大语言模型生成小块代码的世界。在这种思维模式下,编码智能体可能会作为一个新层级,插入在软件工程师和传统编译器之间。它将自然语言“编译”成代码,做出决策,从而让工程师无需操心。它的价值与其可靠性及其所能做出的决策规模成正比。
问题是,这种高度模式化的世界观是错误的。抽象会泄露,层级会摩擦。即使它们不会,我们也会故意在上面戳洞。
跨层级工作极具价值;对机械原理的感知至关重要。
帝国大厦之所以能在一年内、预算内(!!)建成,部分原因在于系统性地跨层级协同工作。例如,在决定外墙铬镍钢覆层时:
建筑师、建造商和分包商都认为,没有充分协商,谁也无法独立处理这个复杂的施工技术问题。因此,经过充分的前期讨论后,召开了一次全面会议,参会者包括业主代表、建筑师和建造商代表、负责轧制材料的分包商、负责制造和安装的金属工人,以及在各个准备阶段对所有板材进行检测的检验员。
这话说出来听着特别理所当然。
然而在实践中,我们却系统性地做不到这一点。我简直能想象那些金属工人有多高兴,因为他们有机会引导设计走向一个不会让他们干得又慢又痛苦的方向。
我们失败的部分原因在于,根本不知道哪些事情值得去问。最优秀的高管之所以对自己行业有深刻理解,是有原因的。我也怀疑其中一部分是轻视心理("一个流水线上的金属工人能告诉我什么?")。但很大一部分原因还在于沟通和组织上的开销。层级的存在是有原因的——信息隐藏使得组织能够规模化。
Claude 比编译器更胜一筹,因为它能垂直贯穿整个技术栈。大语言模型现在能讨论战略、产品、架构、代码和机器码。它(目前还?)不能像经验丰富、专注的人类专家那样出色地完成大多数单项任务,但它能完成所有这些任务,而且无需安排会议或请求许可。
这里有一个具体例子。
exe.dev 的虚拟机有漂亮的域名:vm-name.exe.xyz。当我们启动一台新虚拟机时,我们会添加一条或几条 CNAME 记录。很简单,对吧?
但我们的虚拟机启动得很快,快到即使我们在创建虚拟机之前就创建了 DNS 记录,用户仍然需要干等着 DNS 传播,有时这需要几分钟,而不是几秒钟。
我们做了显而易见的事:自己编写了 DNS 服务器,这样 DNS 总能立即与数据源保持一致。从此生活就美好了。
但延迟至关重要,因此我们增加了区域。就这样,DNS 再次成为瓶颈,因为所有 DNS 都从俄勒冈州提供服务。此外,部署操作还导致了短暂的 DNS 中断。为了解决这个问题,我们现在所需要的只是一个地理分布但完全一致的 DNS 服务器。
我们做了任何明智的工程师在面对难题时都会做的事:走捷径。我们通过“氛围工程”构建了一个针对我们特定需求定制的分布式 DNS 服务器。
目标很明确:降低远离俄勒冈州的用户的延迟,并提高正常运行时间的弹性。但除此之外,其他一切都不明确。我们必须弄清楚所有事情,从我们想要的确切行为(尤其是在各种故障条件下),到它如何融入我们公司的整体计划,再到能够最好地实现这些目标的架构,一直深入到具体的实现细节。
我们当面敲定了最高级别的战略和架构决策。我们将构建一个相当通用的 DNS 服务器,然后在其上叠加我们特定的行为调整,采用中心辐射模型,使用仅追加的复制策略,并在边缘节点实现持久化。
剩下的就是实际构建它了。
我让大语言模型研究分布式 DNS 系统的标准设计,向我讲解 DNS 的内部机制和特性,指出历史上的安全缺陷,探索替代实现策略(AXFR/IXFR?不用了,谢谢),研究开源方案,推演故障模式,并规划测试策略。
一旦我有了一个看起来有希望的初步设计草图,我就提示多个并发的智能体循环来构建整个系统,包括测试和对抗性代码审查。它们提出了大量问题——涉及各个细节层面,从主要的架构方法到逐行的代码问题。当我回答这些问题(或撤销那些让我后悔的回答)时,我慢慢地将所学到的东西转化为非常简洁的书面指导,并将那些被证明是重要的决策固定下来。
然后,我让新的智能体比较已完成的实现,并寻找有趣的差异。令人震惊的是,有多少重要的决策是智能体从未询问过就自行做出的——而且做出的方式各不相同。
这里有一个例子。复制采用了相当直观的方法:通过请求自上一个已知条目以来的所有内容来追赶,然后通过长轮询获取新条目。但有一个棘手的变数:数据库回滚。这种情况很少见,但确实会发生,并且会破坏“仅追加”的约定。
智能体们注意到了这一点,并以截然不同的方式解决了它。我最终确定的设计是给每一行一个“时间线”字段,就像“你生活在哪条时间线?”一样。这些字段是随机生成的,每个针对“自第 N 行以来的条目”的同步请求都包含边缘服务器针对第 N 行的时间线值。如果时间线不匹配,我们就知道历史记录已被篡改,并回退到完整的全新重新同步。
不同智能体构建的系统之间也存在明显的风格差异。Claude 和 Codex 都同意 Claude 创建了一个更优雅的系统,但 Codex 更全面。
我逐一处理了已识别出的主要分歧列表,进行了实验,然后添加了更多的书面指导。
然后,我将整个差异规范分析过程重复了两次。我知道我的那些格言。
计划扔掉一个;无论如何,你都会的。
—— 弗雷德·布鲁克斯
如果你计划扔掉一个,你会扔掉两个。
—— 克雷格·泽鲁尼
到我准备好构建一个最终版本时,我已经积累了一份“伤疤文档”,它经验性地足以指导一个智能体完成大多数重要决策,涵盖各个层面,从高层目标到架构,再到偶尔的底层细节,例如承载负载的并发缓存的数据类型的确切形状。
最终系统包含了单元测试、端到端测试、用于降低生产环境部署风险的影子模式,以及一套由智能体编写、为智能体服务的简洁文档套件。
这总共花费了我大约一周的精力。我阅读的实际代码量微乎其微。
在那之后,我向团队展示了解决方案。我计划启动服务器,然后去度假。当我的同事们向我提出一连串问题——“X 是如何工作的?在 Y 条件下会发生什么?”——时,我发现我可以自信地回答所有问题。(而且我确实去度假了。一个月后的 DNS 事件数量:0。)
这里的 Claude 不仅仅是编译器。我从未把任务交出去,让智能体做出一堆决策来将其付诸实践。那是氛围式编程。
相反,Claude 是一种垂直整合的资源,一个多层级编译器。它跨技术栈工作的能力加速并增强了我做出不同层级决策的能力,包括判断哪些决策是重要的。(大多数单行代码达不到这个标准。)这才是氛围式工程。
我想说,在所有重要的方面,我都理解这段代码。当然,如果现在要我手动编辑它,会有很陡的学习曲线。但我没必要这么做。更重要的是,我能对系统进行推理,与同事交流观点,并指导智能体完成未来的工作。而且,这里留下了一个持久的产物,它封装了设计中那些足够重要、值得记录的核心意图性方面,跨越所有层级,因此应该能在 bug 修复和代码更迭中存活下来。
这个时代的问题之一是:软件工程师需要对他们所工作的系统理解到什么程度?
精心选择的抽象层能提供理解。物理基本定律看似包罗万象,但在解释为什么发生事故时坐大巴比坐小车更好这件事上,它们不如经典力学。
有些软件层正在消亡,因为它们只提供便利,却不提供额外的洞察。(抱歉,Tailwind。我曾爱过你。)但那些能让我们以可理解的方式表达重要决策的软件层呢?它们会留下来。
我们正将更多注意力转移到技术栈上层,但并没有完全放弃底层。智能体并不是一张免票,可以让我们放弃对系统更深层的所有理解。Go 标准库大部分是用 Go 写的,但少数关键例程是用汇编写的。在那里你不能依赖编译器。
软件工程师正在被拉伸。这既令人兴奋又令人疲惫。不过,越来越清楚的是,在不久的将来,氛围式工程就只是……工程。
In early 2025, I wrote Is Claude a Compiler? At the time, my answer was: I don’t know.
I’m now pretty sure the answer is “no, that’s a category error, it’s better than a compiler.” But this requires a bit of unpacking.
Computer programs are notoriously intricate and finicky. A program operates at an extreme level of precision. There is no “wave hands” CPU instruction. High-level goals, meanwhile, are deeply underspecified.
In a highly stylized view of the world, software gets built in layers, each one adding specification and hiding “unnecessary” detail. Vision becomes strategy, product plans become coding plans, code becomes binaries. Each step is handled by a different role: executive, VP, PM, architect, engineer, compiler.
Critically, every step involves making lots of decisions. That’s what it means to increase the level of specification. (This is why one of my two key metrics for hiring engineers is judgment. The other is comity.)
The bottom layer, from source code to binary, is what a compiler does. Compilers make lots of decisions! Inlining, register allocation, whether to emit warnings or reject a program outright. And these decisions matter: They drive performance, system stability, predictability, and failure modes. A compiler engineer’s job is to arrange for the compiler to make consistently good decisions.
A good, trusted compiler frees a software engineer from having to make these decisions. Most engineers have little idea how compilers work; they don’t need to in order to be effective.
In 2025, we operated in a world where we used LLMs to generate smallish chunks of code. In this mental model, a coding agent might slot in as a new layer between a software engineer and a traditional compiler. It “compiles” natural language to code, making decisions so the engineer doesn’t have to. Its value is proportional to its reliability and the scale of the decisions it can make.
The thing is, this highly stylized view of the world is false. Abstractions leak and layers rub. And even if they didn’t, we’d poke holes in them anyway.
Working across layers is extremely valuable; mechanical sympathy matters.
Part of how the Empire State Building was constructed in under a year and under budget (!!) was by systematically working across layers. For example, when deciding about the exterior chrome-nickel steel cladding:
Neither architects, builders nor subcontractors felt competent to deal with this complicated technical problem of construction without full consultation. Accordingly, after full preliminary discussion, an all-inclusive meeting was called which was attended by representatives of the owner, the architects and builders, the subcontractors rolling the material, the metal workers who were to fabricate and those who were to erect it, and the inspectors who were to test all sheets at the several stages of preparation.
This sounds really obvious when you say it out loud.
And yet we systematically fail at this in practice. I can only imagine the delight of the metal workers who had an opportunity to guide the design toward something that wasn’t slow and miserable to work on.
Part of the reason we fail is ignorance of what is even worth asking about. There’s a reason that the best executives have deep knowledge of their industry. I also suspect that some of it is dismissiveness (“What could a line metalworker have to tell me?”). But a big chunk is also communication and organizational overhead. Layers exist for a reason—information hiding enables organizational scaling.
Claude is better than a compiler because it can work vertically across the stack. LLMs now talk strategy, product, architecture, code, and machine code. It can’t (yet?) do most individual tasks as well as an experienced, dedicated human, but it can do all of them, without having to schedule meetings or ask permission.
Here’s a concrete example.
exe.dev VMs have nice domain names: vm-name.exe.xyz. When we start a new VM, we add a CNAME entry or three. Easy, right?
But our VMs start fast, so fast that even if we created the DNS entries before creating the VM, our users still had to sit around waiting for DNS to propagate, which occasionally took minutes, not seconds.
We did the obvious thing: We wrote our own DNS server, so that DNS always immediately matched the source of truth. And life was good.
But latency matters, so we added regions. And just like that, DNS became the long pole again, because all DNS was served out of Oregon. Also, deployments caused tiny DNS outages. To fix this, all we needed now was a geographically distributed but fully consistent DNS server.
We did what a sensible engineer does when faced with a hard problem: cheat. We vibe-engineered a distributed DNS server tuned to our specific needs.
The goals were clear: Reduce latency for users far from Oregon and increase uptime resiliency. But the rest was not. We had to figure out everything from the exact behavior we wanted (particularly under various failure conditions), to how it fit into our overall company plans, to the architecture that could best achieve those goals, straight through down to the fine implementation details.
We hashed out the highest level strategic and architectural decisions in person. We’d make a fairly general-purpose DNS server and layer on our particular behavioral tweaks, use a hub-and-spoke model, use an append-only replication strategy, and have persistence at the edges.
All that was left was to actually build it.
I had LLMs research standard designs for distributed DNS systems, teach me about the guts and quirks of DNS, point out historic security failings, explore alternative implementation strategies (AXFR/IXFR? no thanks), research open source offerings, game out failure modes, and plan testing strategies.
Once I had an initial sketch of a design that seemed promising, I prompted multiple concurrent agent loops into building the entire thing, including tests and adversarial code review. They raised a bunch of questions—at every level of detail, from major structural approaches down to line-level code concerns. As I answered them (or reverted answers that generated regret), I slowly converted what I had learned into very terse written guidance, codifying decisions that proved to be important.
Then I asked new agents to compare the completed implementations and look for interesting deviations. It was shocking how many important decisions the agents never asked about but simply made—and made differently.
Here’s an example. Replication uses the fairly obvious approach: Catch up by asking for everything since the last known entry, and then long poll for new entries. There’s one ugly twist: database rollbacks. Rare, but they do happen, and they break the “append-only” contract.
The agents noticed this, and they solved it in wildly different ways. The design I ultimately settled on was to give every row a “timeline” field, as in “which timeline are you living in?” These are randomly generated, and every sync request for “entries since row N” includes the edge server’s timeline value for row N. If there’s a timeline mismatch, we know that history has been altered and fall back to a full clean re-sync.
There were also obvious style differences between the systems built by different agents. Claude and Codex both agreed that Claude created a more elegant system but that Codex was more thorough.
I worked through the list of major identified divergences, experimented, and then added more written guidance.
Then I repeated that entire differential spec analysis process, twice. I know my aphorisms.
Plan to throw one away; you will, anyhow.
— Fred Brooks
If you plan to throw one away, you will throw away two.
— Craig Zerouni
By the time I was ready to build a keeper, I had accumulated a scar-tissue document that was empirically sufficient to guide an agent through most of the important decisions, at every layer, ranging from high level goals through architecture down to the occasional low level detail, such as the exact shape of the data type for load-bearing concurrent caches.
The final system included unit tests, end-to-end tests, a shadow-mode for de-risking prod rollout, and a terse written-by-and-for-agents doc suite.
This cumulatively took about a week of my attention. I read a vanishingly small amount of the actual code.
At the end of that, I presented the solution to the team. I planned to launch the server and then go on vacation. As my colleagues peppered me with questions—"How does X work? What happens in condition Y?"—I found I could answer all of them confidently. (And I did go on that vacation. Number of DNS incidents a month later: 0.)
Claude wasn’t just a compiler here. I never handed off a task and let an agent make a bunch of decisions in order to reduce it to practice. That’s vibe-coding.
Rather, Claude was a vertically integrated resource, a multi-compiler. Its ability to work across the stack accelerated and augmented my ability to make a bunch of decisions at different levels, including about which decisions were important. (Most individual lines of code don’t make that cut.) That’s vibe-engineering.
I’d say that, in all the ways that matter, I understand the code. Sure, if I had to hand-edit it now, there’d be a serious learning curve. But I won't have to. And more importantly, I can reason about the system, share perspectives with my colleagues, and guide agents on future work. And there’s an enduring artifact that encapsulates the central, intentional aspects of the design that were important enough to record, across all layers, and should thus survive bug fixes and code churn.
One of the questions of this era is: What do software engineers need to understand about the systems they work on?
Well-chosen layers provide understanding. Fundamental laws of physics appear all-encompassing, but they’re inferior to classical mechanics for explaining why it’s better to be in a bus than a car in an accident.
Some software layers are dying, because they provide convenience, but not extra insight. (Sorry, Tailwind. I loved you.) But software layers that enable us to express important decisions in a comprehensible way? Those will stay.
We are shifting more of our attention up the stack, but without fully relinquishing the lower layers. Agents are not a free pass to hand off all understanding of the deeper layers of a system. Most of the Go standard library is written in Go, but a few key routines are written in assembly. You can’t rely on the compiler there.
Software engineers are being stretched. It’s exhilarating and exhausting. What’s becoming clear, though, is that in the near future, vibe-engineering is just…engineering.