2025 年初,我写过《Claude 是编译器吗》?当时我的回答是:我不知道。
现在我相当确定答案是“不,那是个范畴错误,它比编译器更好。”但这需要稍微展开解释一下。
计算机程序出了名的错综复杂又吹毛求疵。程序运行在极高的精度层面上。CPU 里没有“含糊其辞”这种指令。而与此同时,高层目标又是极度欠规范的。
用一种高度简化的视角来看,软件是分层构建的,每一层都在增加规范、隐藏“不必要”的细节。愿景变成战略,产品计划变成编码计划,代码变成二进制。每一步都由不同的角色来处理:高管、副总裁、产品经理、架构师、工程师、编译器。
关键在于,每一步都涉及做出大量决策。这正是提升规范程度的含义所在。(这就是为什么我招聘工程师的两个关键指标之一就是判断力。另一个是协作精神。)
最底层,从源代码到二进制,就是编译器所做的事。编译器会做出大量决策!内联、寄存器分配、是发出警告还是直接拒绝整个程序。而这些决策至关重要:它们驱动着性能、系统稳定性、可预测性和故障模式。编译器工程师的工作,就是设法让编译器持续做出好的决策。
一个优秀且值得信赖的编译器,让软件工程师不必再操心这些决策。大多数工程师对编译器的工作原理知之甚少;他们也不需要了解,就能高效工作。
2025 年,我们身处这样一个世界:我们用 LLM 来生成规模较小的代码片段。在这种心智模型中,编码智能体或许会作为软件工程师与传统编译器之间的一个新层嵌入其中。它将自然语言“编译”为代码,替工程师做出决策。它的价值与其可靠性以及它能做出的决策规模成正比。
问题在于,这种高度风格化的世界观是错误的。抽象会泄漏,各层之间会相互摩擦。而且即便它们不摩擦,我们反正也会在上面戳出窟窿。
跨层工作极具价值;机械同理心很重要。
帝国大厦之所以能在不到一年内、且低于预算(!!)建成,部分原因就在于系统性地跨层工作。例如,在决定外墙铬镍钢覆层时:
无论是建筑师、建造方还是分包商,都觉得自己没有能力在没有充分协商的情况下处理这个复杂的施工技术问题。因此,在充分的初步讨论之后,召开了一次全体会议,参会者包括业主、建筑师和建造方、轧制材料的各分包商、负责加工制造的金属工人和负责安装的工人,以及将在各个制备阶段检测所有板材的检验员。
这话说出来的时候,听起来确实再明显不过。
然而在实践中,我们却系统性地在这件事上失败。我只能想象那些金属工人的喜悦——他们有机会引导设计走向某种不至于让人干起活来又慢又痛苦的东西。
我们失败的部分原因,是根本不知道什么才值得去问。最优秀的 executives 对自身行业有着深厚的了解,这是有原因的。我还怀疑,其中一部分原因是轻视(“一个流水线金属工人能告诉我什么?”)。但很大一部分原因还在于沟通和组织开销。层级的存在是有原因的——信息隐藏使组织得以扩展。
Claude 比编译器更强,因为它可以纵向贯穿整个技术栈工作。LLM 如今能谈战略、产品、架构、代码和机器码。它(还?)无法像一位经验丰富、专注的人类那样做好大多数单项任务,但它能全部做,而且无需安排会议或请求许可。
这里有一个具体的例子。
exe.dev 的 VM 有不错的域名:vm-name.exe.xyz。当我们启动一个新 VM 时,我们会添加一条或三条 CNAME 记录。很简单,对吧?
但我们的 VM 启动得很快,快到即使我们在创建 VM 之前就创建了 DNS 记录,用户仍然得坐在那里等待 DNS 传播,而这偶尔要花上几分钟,而不是几秒钟。
我们做了最显而易见的事:自己写了一个 DNS 服务器,这样 DNS 就总能立即与事实来源保持一致。日子过得很好。
但延迟很重要,所以我们增加了区域。就这样,DNS 再次成了最长的短板,因为所有 DNS 都是从 Oregon 提供的。此外,部署还会导致短暂的 DNS 中断。要解决这个问题,我们现在需要的只是一个地理上分布式但完全一致的 DNS 服务器。
面对难题时,一个明智的工程师会怎么做:作弊。我们用 vibe 工程搞出了一个针对我们特定需求量身定制的分布式 DNS 服务器。
目标很明确:降低远离 Oregon 的用户的延迟,并提高正常运行时间的韧性。但其余的就不明确了。从我们想要的确切行为(尤其是在各种故障条件下),到它如何契合我们的整体公司规划,再到最能实现这些目标的架构,一直到精细的实现细节,我们都得自己摸索清楚。
我们当面敲定了最高层面的战略和架构决策。我们要做一个相当通用的 DNS 服务器,再叠加我们特定的行为调整,采用中心辐射模型,使用仅追加的复制策略,并在边缘实现持久化。
剩下的就是把它真正做出来。
我让 LLM 研究分布式 DNS 系统的标准设计,给我讲解 DNS 的内部机制和古怪之处,指出历史上的安全缺陷,探索替代实现策略(AXFR/IXFR?算了),调研开源方案,推演故障模式,并规划测试策略。
当我有了一个看起来有前景的初步设计草图后,我让多个并发的智能体循环去构建整个东西,包括测试和对抗性代码审查。它们提出了大量问题——涵盖各个细节层次,从重大结构性方案一直到行级代码问题。在我回答这些问题(或者撤回那些后来让我后悔的回答)的过程中,我慢慢把自己学到的东西转化为非常简洁的书面指导,把被证明重要的决策固化下来。
然后我让新的智能体去比较已完成的各个实现,寻找有意思的偏差。令人震惊的是,有那么多重要决策智能体从未询问过,而是直接做了——而且做得各不相同。
这里有个例子。复制采用了相当显而易见的做法:通过请求自最后已知条目以来的所有内容来追赶进度,然后对新条目进行长轮询。有一个丑陋的变数:数据库回滚。虽然罕见,但确实会发生,而且它们破坏了“仅追加”的约定。
智能体注意到了这一点,并以截然不同的方式解决了它。我最终确定的设计是给每一行都加一个“时间线”字段,就像在问“你生活在哪条时间线上?”这些值是随机生成的,而每一个“自第 N 行以来的条目”的同步请求都会带上边缘服务器为第 N 行记录的时间线值。如果时间线不匹配,我们就知道历史已被篡改,于是回退到一次完整的干净重新同步。
不同智能体构建的系统之间也存在明显的风格差异。Claude 和 Codex 都认同 Claude 构建的系统更优雅,而 Codex 构建的系统更周全。
我逐一梳理了已识别出的主要分歧清单,进行了实验,然后补充了更多书面指导。
计划扔掉一个;反正你终究会扔。
—— Fred Brooks
如果你计划扔掉一个,你会扔掉两个。
—— Craig Zerouni
等到我准备构建一个 keeper 时,我已经积累了一份疤痕组织文档,它在经验上足以引导一个智能体完成大多数重要决策,涵盖每一层,从高层目标到架构,再到偶尔的低层细节,比如承重并发缓存的数据类型的确切形态。
最终的系统包括单元测试、端到端测试、用于降低生产上线风险的影子模式,以及一套由智能体编写、也供智能体使用的简洁文档套件。
这累计花了我大约一周的注意力。我读过的实际代码量少得可以忽略不计。
在那之后,我向团队展示了这个解决方案。我计划启动服务器然后去度假。当同事们连珠炮般向我提问——“X 是怎么工作的?在条件 Y 下会发生什么?”——我发现自己都能自信地回答。(而且我确实去度了那个假。一个月后的 DNS 事故数量:0。)
Claude 在这里不只是一个编译器。我从未把一项任务交出去,让一个智能体做出一堆决策来把它付诸实践。那是 vibe-coding。
相反,Claude 是一种垂直整合的资源,一个多重编译器。它跨技术栈工作的能力加速并增强了我做出不同层级大量决策的能力,包括判断哪些决策是重要的。(大多数单行代码都达不到这个标准。)那是 vibe-engineering。
我想说,在所有真正重要的方面,我是理解这段代码的。当然,如果现在让我手动编辑它,会有一个陡峭的学习曲线。但我不会需要这么做。更重要的是,我能够对系统进行推理,与同事分享观点,并指导智能体开展未来的工作。而且有一个持久的产物,它凝聚了设计中那些核心的、有意的方面——这些方面重要到值得被记录下来,贯穿所有层级,因此应当经受住缺陷修复和代码更迭而存续下去。
这个时代的问题之一是:软件工程师需要理解他们所工作的系统的哪些方面?
精心选择的层级提供了理解。物理学的基本定律看似包罗万象,但在解释为什么在事故中坐公交车比坐小汽车更安全这件事上,它们不如经典力学。
有些软件层级正在消亡,因为它们提供的是便利,而非额外的洞见。(抱歉,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.