~non-deterministic/engineering
2026-05-16

The first non-deterministic abstraction layer

#philosophy#patterns#abstraction

The history of software is, in many ways, the history of abstraction. We moved from fragile hardware concerns to instruction sets, then to assemblers, compilers, and high-level languages. Later, we built frameworks, platforms, and whole generation pipelines on top. At each step, we traded some control for speed and reach; most of the time that trade was a net benefit, and over time most people learned to accept it.

What those layers had in common was determinism. Maybe not perfect elegance, maybe not perfect ergonomics, but a stable contract: same input, same output. If things broke, we could inspect what happened. We could usually trace generated code back to the original source, step through execution, and follow failures down the stack until something concrete gave way.

People are saying that AI is just one more elevation in the abstraction ladder. We get the opportunity to replace our archaic and rigid programming languages with English, a language that is very expressive and the opposite of rigid … meaning vague?

If the new “source” is natural language and the “compiler” is a probabilistic model, the typical abstraction contract changes. Give the same prompt twice and you may not get the same implementation. Sometimes you do, sometimes you don’t, and the reason why is rather opaque.

Now, you can absolutely persist the generated code and treat that as the real artifact. In practice, that is what serious teams already do. While some might suggest we don’t even need to do that — just commit the specs and regenerate everything on the fly always, isn’t it elegant? — I trust most reasonable engineers will wisely ignore that pitch. But if we still need to preserve and review the generated output like traditional source, then natural language is not replacing source code so much as producing drafts for it. That’s useful — very useful even — but it’s not abstraction; it falls in a different category.

One could argue that the readability of the generated code doesn’t matter much anymore, because the model will be smart enough to maintain it for us. Maybe. AGI is, after all, only six months away, so presumably this concern will resolve itself any day now. In the meantime, we still have to ship software, and to do so we have to read the diff.

There is also the ambiguity problem. Natural language is flexible by design. That’s great for humans coordinating under uncertainty, but terrible as a precise executable contract. Any engineer who has worked closely with product teams knows this story: the spec says one thing, five reasonable people interpret it five slightly different ways, and production chooses yet another one at 2 a.m.

Of course, our good old abstractions were never flawless either. Pretty regularly, bloated code generation tools collapsed under complexity. Sometimes, performance work forced us down a layer. Rarely, we had to resort to inline assembly we swore we’d never touch again. But most of the time, with languages like Go, Rust, Java, or C#, if used for the right job, the abstraction is still exact enough that dropping lower is the exception, not the rule.

With LLMs, dropping lower is the plan. We generate, inspect, edit, test, and often rewrite. And yes, it would be genuinely shocking to learn that engineers have occasionally had to debug an AI-generated mess — a thing I am told happens approximately never, except in every codebase I’ve touched in the past year. Apparently only the Claude Code codebase doesn’t need human review and debugging anymore, though that would explain the state of that software.

If that sounds like I am being negative on AI, I should be clear: I’m not. I’m negative on the narrative around it, and on some of the louder players in the space, but the technology itself impresses me deeply and I think it will reshape our craft like very few things before it. I just think the current discourse often confuses powerful generation with better abstraction and also implies the end of software engineering. IDEs also brought with them powerful code generation capabilities, and that has not killed software engineering, quite the opposite in fact.

My working thesis is simple: AI coding belongs on the tooling ladder, not the language ladder. It is a massive upgrade to the IDE/autocomplete lineage — arguably the biggest one yet — but it still depends on deterministic layers beneath it. The durable artifacts remain human-readable code, tests, and explicit system behavior.

I can certainly understand people who believe we will eventually execute directly from specs and skip most handwritten code. Maybe they will be right. But from where I stand today, the practical path looks more conservative: let models accelerate implementation while deterministic languages remain the foundations on which we rely to ship and maintain software.

At least, that’s the lens I’m using for now — and I’ll keep testing it against reality as the tools get better (which, to be fair, is happening at an absurd pace).