obsidian.md - Spec-driven development doesn't work if you're too confused to write the spec - deontologician - Obsidian Publish
We’ve gone from AI auto-complete to agentic coding to spec driven development and a new argument has popped up about the future of programming. It goes something like: source code is the new assembly language. Engineers will stop caring about it, move up a level of abstraction, and be judged on outcomes rather than code quality.
It’s an attractive story because it’s partially right. But it conflates two very different kinds of work that happen to live in the same files, and the conflation makes it hard to think clearly about what is actually changing.
The case for not Caring about Source Code
Section titled “The case for not Caring about Source Code”Senior engineers like to complain about AI slop and vibecoding, but (the argument goes), they shouldn’t. In the past you had to care a lot about how the code looked in order to maintain it. But in the future, you’re never going to look at the code. You’re going to write a spec, and an agent is going to handle the low level details. All the work moves up to spec writing.
The AI bullish case has a historical analogy at its core: when high-level languages replaced assembly, [1] assembly programmers resisted. They knew things (about performance, about memory layout, about instruction pipelines) that the newcomers didn’t. And they were right that this knowledge mattered in some cases. But they were wrong that every engineer needed that knowledge.
What actually happened was that a small number of specialists continued to care about the machine-level details, compilers got good enough that most people didn’t have to, and the industry moved on. The engineers who insisted that everyone should understand assembly were fighting a losing battle against the economics of “good enough, and much faster to produce.”
The analogy to today seems obvious: A small number of strong abstraction-builders will create the tools and libraries everyone uses, and a much larger population will compose those tools together with agents without ever looking at what’s underneath. The composers will be judged on whether the software works, not on whether the code is clean. And that will be fine.
Each generation of programmers has mourned the loss of skills and craftsmanship, and each time those concerns just evaporate and the world keeps spinning. We solve the problems they worry about and for the most part the stuff we used to need to worry about doesn’t matter any more.
Source Code Isn’t Really Assembly
Section titled “Source Code Isn’t Really Assembly”The analogy has a structural problem though. Assembly language is highly constrained by the physical CPU’s capabilities[2]. But high level languages are actually an attempt to express the domain logic as succinctly and unambiguously as possible. The languages we use today may not quite be mathematics (excepting things like Lean and Haskell), but they’re much closer to “the least you have to specify in order to be unambiguous”.
Source code in a high level language is much closer to an unambiguous spec than it is to being a list of instructions for a machine. We can quibble about the details, but generally abstraction capabilities are quite high in modern languages.
So, what are we specifying in a high level language? Why can’t we kind of just hand-wave and have an agent figure out the details?
Two Kinds of Work in One Codebase
Section titled “Two Kinds of Work in One Codebase”There are two kinds of work sort of interspersed in a codebase, and we don’t really commonly distinguish which one we’re talking about when we talk about coding.
The first is domain algebra (or, if you prefer, “business logic”). This is the semantic core: the logic that expresses what the system actually does. A loan is in default if payments are 90+ days overdue. An amortization schedule is determined by the principal, rate, and term. A transaction must be atomic. This layer tends to be the tricky, problem-solving part of coding that engineers enjoy the most. The choices within it are constrained by the problem domain, the way mathematical structures are constrained by axioms.
Different engineers will express the same domain logic with different syntax, but the underlying structure is recognizable, the same way independently invented mathematical notations turn out to describe the same objects. When someone refactors this layer well, they’re discovering something true about the domain.
The second thing we do in source code is what I’ll call “representation management”. This is the accumulation of arbitrary-but-binding decisions about how domain concepts get encoded into concrete form. What are the field names? What’s the wire format? How does your internal model map to the vendor’s schema? What does the database column actually mean, given that it was named by someone who understood the domain slightly differently three years ago?
None of these choices are derivable from the domain. If you give two agents the same spec that doesn’t lock these arbitrary choices down, they will build incompatible implementations. The domain doesn’t care whether you call it loan_amount or principal_balance, whether your rate is a decimal or basis points, whether addresses are nested inside applications or alongside them. But once you’ve made these choices, they’re commitments. They’re in a database. They’re in a partner’s integration. They’re facts about the world that must be recorded and managed.
A lot of modern coding is just bookkeeping these representations. Maybe unsurprisingly, a lot of the times I’ve noticed vibecoding go poorly is when the machine just changes these representations and doesn’t consider how to migrate existing data to a new format. If it’s not part of the spec, it’s not locked down. And if it’s tedious and you don’t want to think about it, it’s not going in the spec!
In a greenfield system, most of the work is domain algebra. In a mature system that integrates with a dozen external partners, representation management can dominate. The Google joke about every engineer just mapping protobufs to other protobufs is a joke about representation management eating the world.
Back to the Title of This post
Section titled “Back to the Title of This post”So, ok. Representation management is tedious, and you have to bookkeep it. What about the domain algebra? We can just write the spec for that and have it turn it into code right?
Well no. Usually we are actually confused about the domain logic. Usually, we don’t actually understand it until we work through. You go to formalize the relationships and invariants, and the act of making them precise and consistent teaches you things about the domain that you didn’t understand when you started.
The machine pushes back on you: this rule contradicts that one, this entity doesn’t fit cleanly into the structure you assumed, this edge case reveals that your model was wrong. The difficulty at this level isn’t that there are a lot of letters to type. It’s that you have to refine your thinking as you write it down. A high-level fuzzy idea becomes a specific set of relationships, semantics, and rules, and they all have to cohere.
Spec-driven development doesn’t save us here, it’s just an opening of a conversation. Any spec unambiguous enough to work is already close enough to code that we could just express it as code. Writing that spec is a discovery process. You learn what you’re building by trying to make a computer understand it.
Where Specs Go next
Section titled “Where Specs Go next”Really, my guess is we’ll get better at writing unambiguous specs. Despite what I said above, writing specs on vibes and getting actionable results from it is going to push us to demand more from our programming languages. We’ll make more formally provable statements (and then have agents prove them). We’ll figure out better ways to split out ambiguity. We’ll write more property tests (and have agents get them to pass). We’ll find better ways to abstract out the representation layer from our specs because it’s clearly straightforward and not important at the high level. There’s still a lot of juice to be had here at the programming language level.
- I mean, I dunno. I’m not a historian. This is a vibes-level historical reconstruction. I would be curious if this is way off base though
- This isn’t true. Microcode exists. The point remains that assembly is more constrained by the machine than by ergonomics for developers working in a domain.