Agents are the new compilers

I haven’t written a line of code by hand in almost six months. Not in Go. Not in TypeScript. Not in Python.

That probably sounds either visionary or alarming depending on who you are; let me be specific about what I mean. I haven’t typed out source code — but I’ve written a lot of specifications and validation rules, I’ve spent a great deal of time thinking about what correctness looks like and how to verify it, and I’ve designed my share of interfaces and system boundaries. In some sense, I’ve written more code than I used to — it’s just not code that gets compiled into binaries.

The machine code — the actual generated functions, the loops, the variable assignments — all of that comes from agents now. And the weird thing is, I’ve stopped caring what it looks like — only what it does and how it’s put together.

I haven’t read the code they generate in maybe two months — not the way I used to. I still review it, but I’m looking for different things. Is there a single source of truth for this data, or did it get copied into three places that will drift? Is this the kind of pattern that turns one bug into four bugs in four files? Is it reaching into something it has no business touching? What I’ve stopped doing is reading an implementation and asking whether it’s how I would have written it. That question stopped being interesting.

That should sound familiar, because it’s almost exactly what happened to compiled languages.

The compiler was always there

I don’t know about you, but I remember a time when you had to care what your compiler generated. You read the assembly output, you understood what the optimizer was doing (or failing to do), and you made decisions about algorithm tradeoffs based on what machine code would actually hit the CPU.

I got out of my college assembly class by finding an endianness bug in the simulator we used to run our assembly programs. My instructor took a look and decided that someone who was debugging the simulator probably didn’t need to sit through a course on assembly. I’ve never been sure whether that was a compliment or just easier than putting up with me all semester.

That was a reasonable way to spend an afternoon back then. Today I’d have to work fairly hard to construct a situation where I care about byte order — it’s still there, quietly, in every network protocol and file format I touch, but it belongs to somebody else now.

That’s not because you were more thorough; it’s because the tooling didn’t give you a better option.

Then it did. Performance profilers got better, tracing frameworks became standard, and unit testing got cheap enough that you could run your code a million times under real conditions and measure whether it was fast, instead of squinting at assembly and guessing. That made the assembly inspection obsolete.

The compiler’s job changed too. People stopped trying to write “efficient code for humans to understand” and started writing “code that describes intent clearly” — because now the compiler’s job wasn’t just to produce working binaries; it was to produce fast binaries from clear specifications. GCC, LLVM, and half a dozen others competed not on whether they could compile, but on how well they could optimize black-box code. Some are aggressive, some are conservative, and all of them are legitimate choices for different tradeoffs.

Nobody argues about this anymore; you don’t get religious about GCC vs. LLVM the way you might have argued about compilers in 1995. They’re tools with different tradeoffs, and you measure the output rather than the process.

This is where we are with code generation agents, except we’re not in 1995 — and while we may have stopped arguing about compilers, we do still argue about emacs and vim. I gave emacs an honest try and mostly liked it, but I’ve since ditched it: my ( and ) keys wore off and I could no longer tell which was which. I give it a year before the agents start in on that one too. Vim will win, of course.

Agents aren’t all the same either

The compilers you use today are shockingly different from each other. GCC and Clang both turn C into binaries, but they make dramatically different decisions about inlining, vectorization, when to unroll loops. Rust’s compiler does whole-program optimization, while Go’s prioritizes fast compilation over aggressive optimization, and JIT compilers — Java, PyPy, JavaScript engines — observe runtime behavior and recompile hot code as they go. All of them are correct, and all of them produce different machine code.

You don’t care which compiler you use because you can:

  1. Measure the output — run benchmarks, profile memory, trace execution.
  2. Test it — unit tests verify behavior, not implementation.
  3. Specify intent — write code that’s clear about what you want, not how the compiler should do it.

That third point is the key. When you write if (a < b) { swap(a, b); }, you’re not instructing the compiler on the best way to generate a conditional branch. You’re describing the intent: “if a is less than b, put them in the other order.” The compiler is free to unroll it, vectorize it, hoist it, inline it, or decide it’s dead code and delete it — because the intent is clear and measurable.

We’re evaluating code-generation agents exactly backwards. People ask “did the agent write good code?” the same way someone back then might have asked “did the compiler generate good assembly?” — and it was the wrong question then too. The agent generated some code. What actually matters is: does it do what we wanted?

And here’s the thing: you can answer that question without reading the agent’s output the way you used to. You review for architecture rather than implementation, you run the tests, you measure performance, and you watch how it behaves in production. Your code review process checks for correctness and safety, not “is this implementation idiomatic?”

The agent is now the compiler. It takes specifications — sometimes in English, more often in structured format — and produces code in whatever language makes sense for the job. Some agents are aggressive and some are conservative; some prefer clarity, others performance. The choice of agent is like the choice of compiler — but it’s not the only choice you’re making.

And the choice of agent isn’t even the interesting variable. The same model, pointed at the same specification, produces wildly different code depending on how it’s driven — what’s in the system prompt, what it’s allowed to see, whether it has a dependency graph and semantic search or just grep, whether it runs the tests before handing you the result. Those are compiler flags. -O0 and -O2 invoke the same compiler over the same source, and the machine code that comes out the other side can be dramatically different.

This is the part I’ve become most confident about, and it’s also the part I can least prove. Working through a structured pipeline — idea, design, plan, blueprint, build — rather than dropping a prompt on a bare model produces solutions that are more complete and need far less rework, and it burns noticeably fewer tokens getting there. Not because the model is smarter, but because most of what a model wastes tokens on is rediscovering context that something else already knew: what’s in the codebase, what the constraints are, what was already decided and why. Give it that up front and the optimization pass gets cheap. I don’t have a controlled benchmark for this — I have a week of building four unrelated projects, exclusively on the largest model available to me, and finishing it at not even half my usage limit, at a time when running out of budget is a common enough complaint to be a genre. Take it for what it’s worth.

But wait, libraries

For decades, code reuse happened through libraries. You built something, packaged it, published it, and other people used it. This was obviously better than everybody reimplementing the same thing. It’s still better for certain cases. But it came with a permanent cost: you had to adapt to someone else’s API. Their design decisions became constraints on yours. When the library didn’t quite fit, you could fork it, monkey-patch it, or write adapter code around it. All expensive.

What if that’s about to become optional?

If you can give an agent a clear specification — “here’s what this component should do, here’s what correctness means, here are the rules it has to follow” — and that agent can generate code that fits your system right now, adapted to your constraints, integrated with your existing code… why would you use a library?

Not a generic library, anyway.

We’ve already seen hints of this. Code generation is how gRPC works: you write a .proto file specifying your service boundary, and the generator produces clients and servers in ten languages. The specification is the important thing; the generated code is interchangeable.

Same with database schema migrations. Same with API definitions and OpenAPI generators. Same with configuration file generators and test scaffolding.

The pattern is always the same: specify the intent, generate the implementation. The generated code is not a liability you’re stuck with forever; it’s an artifact you regenerate when the specification changes.

Now imagine applying that to a logging library. Instead of pulling in a massive dependency and building your application around its architecture, you give an agent a specification:

“I need structured logging. I want JSON output. When an error occurs, include the stack trace. Metrics should go through this interface. Performance matters — I want near-zero overhead in the hot path. Here are examples of what ‘correctness’ looks like in my codebase. The logger needs to work in Go and TypeScript and needs to interop seamlessly between them over gRPC.”

An agent could generate that code in a few minutes, tailored specifically to your constraints. When you find a bug, you don’t open an issue on someone else’s repo and wait for a release. You add a validation rule to the specification, re-run the generator, and the fix exists across every language and every place it’s used.

That’s not a pipe dream. That’s the same pattern Konstruct uses internally for components between the LLM daemon (Go) and the workspace daemon (Go) and the TypeScript desktop application. Instead of writing hand-crafted protobuf definitions and then fighting impedance mismatches in the client code, the specification is the source of truth and the code is generated from it, so when the generator improves or the spec gets clarified, you just regenerate.

The tools change, not the concept

This doesn’t mean source code libraries disappear entirely. Open-source frameworks — the things that define the boundaries of your system (Spring, Django, React, Kubernetes) — those matter because they define the shape of whole categories of software. You’re not regenerating your entire web framework from a spec.

But the library-of-utility-functions layer? The “well, everyone needs this pattern, so here’s a package”? That’s increasingly a bet that your specification for the library is so good that everyone will accept your design decisions as-is. That bet is weakening.

What makes this work is tooling. Specifically:

  1. Semantic search — when you find a bug, “search for all code that implements logging in the same pattern” isn’t a grep query, it’s a query. The agent can find the code semantically, understand what it’s doing, and apply fixes consistently.

  2. Graph traversal — “what breaks if I change this interface?” becomes answerable without reading every file. The code graph tells you the blast radius. The agent can apply changes with confidence.

  3. Specification as contract — the protobuf definition, the schema, the formal spec — those become the source of truth, not the implementation.

  4. Validation — clear rules for what “correct” means, expressed in ways agents can check. Not “this looks reasonable” but “this passes all the tests and matches the spec.”

This is what we’ve had with compilers for twenty years: specify what you want, choose the tool that matches your constraints, measure the output, and trust the tests.

The job changes

Here’s what I spend my time on now:

  • Specifications. Detailed, clear descriptions of what systems should do. What are the inputs? What are the valid outputs? What happens when things go wrong?
  • Validation rules. How do you know it’s correct? What tests matter? What tradeoffs are acceptable?
  • Architecture and boundaries. Where do things talk to each other? What can change independently? What must stay consistent?
  • Code review. Did the agent do what we asked? Does the behavior match the spec? Are there edge cases we missed?

I’m almost never asking “is this code good?” in the sense of “does it follow our style guide?” or “did they use the right design pattern?” Those are compiler problems now.

This is actually more work than writing code. It requires clarity about intent in ways hand-written code doesn’t. You can’t get away with implicit assumptions or “well, in practice everyone does it this way.” The specification has to be precise enough that code can be generated from it and precise enough that you can validate the generated code against it.

But “more work” isn’t the same as “more hours.” Typing has a hard ceiling — the theoretical maximum of a day spent writing code by hand is your words-per-minute times the minutes you were awake for, and nobody hits that number anyway. Supervision doesn’t work like that. The job starts to look like running a team of junior engineers: you’re holding things inside the guardrails, catching the thing that’s about to go sideways, being clear about what “done” means. That’s real work and it’s tiring in a different way. But you can do it for three things at once, and none of them are waiting on your hands.

And that precision is valuable. It forces you to think hard about what you’re actually building rather than just how to build it, it makes systems more legible to the next person (or the next agent) who has to modify them, and it makes testing easier, because now you’re testing against explicit specs instead of implicit expectations.

We’re not done transitioning

We’re still in the era where people ask “why didn’t the agent use async/await instead of callbacks?” the way people used to ask “why didn’t the compiler vectorize this loop?”

Eventually that question will stop making sense. You’ll run your tests, measure performance, and see whether it does what the spec says — and then you’ll either approve the generated code or send it back with a note about what the spec was missing.

The compiler agent is just a tool. The specification is what matters.

And when that clicks — when you build systems around clear specifications and let agents generate code that fits your constraints instead of adapting yourself to someone else’s library — that’s when the job actually gets interesting. Because now you’re not writing code; you’re designing systems, validating behavior, and steering architecture.

Which is, I’d argue, what we should have been doing all along; we just spent a few decades typing instead.

Filed under: technology, software, ai, agents