intertwingly

It’s just data

The Rungs


For fifteen years the answer to "what are you building it in" has quietly stopped being a language and started being a framework. Nobody chooses Ruby; they choose Rails. Not Elixir but Phoenix, not JavaScript but React or Next. The language became the substrate you stand on and mostly stop thinking about, and the framework became the thing you actually decide, hire for, argue about, and identify with. That migration — attention moving up, from language to framework — is old news. What I want to do here is notice that it didn't stop, that it has been climbing a ladder one rung at a time, and that the rung we're stepping onto now is the same motion it always was. I don't know how far the ladder goes. But I think I can see what kind of ladder it is, and that turns out to be enough to say something useful.

The motion has a shape, and the shape is the point: at each rung, something that used to be hand-authored ceremony at the boundary below becomes inferred, absorbed, and invisible. You once wrote loops; the framework's idioms made the loop invisible. That's the whole story, repeated at rising altitude.

There's a second thing to notice, and it's the one that makes this worth saying now rather than at any point in the last decade. Every rung above the first is reached by building a tool — an analyzer, a compiler, a multi-target emitter — that reads the whole program and makes that rung's ceremony disappear. And until very recently, building that tool was an institutional act: a funded team, a standards body, a vendor, years. That cost is why the ladder stalled where it did. We didn't run out of ideas about where abstraction should go next; we ran out of organizations willing to build the compilers it would take to get there. What changed — the actual inflection the rest of this is circling — is that the tool which makes each rung reachable stopped requiring an institution and started requiring a person and an afternoon. The ladder isn't new. The fact that almost anyone can now build the rung they're standing under is what's new. Watch it climb, and watch what each step cost to reach.

Rung one: the framework replaces the language

This rung is behind us, so I'll be brief, but it's worth standing on for a moment because it sets the direction of travel. The center of gravity moved from language to framework, and once it did, the framework started carrying things the language used to. Rails doesn't just give you idioms; it encodes decisions — naming, structure, the shape of a request — that a language leaves to you. The framework became where the meaning lives. Everything above depends on that having already happened, because you can only compile a framework if the framework is the thing that knows what the program means. Rails was already typed, it turned out, precisely because Rails — not Ruby — had been carrying a type system in its conventions for twenty years. That's rung one paying off a decade later: the framework accumulated the semantics, and the semantics were waiting to be read.

Rung two: the framework wants to become a compiler

Tom Dale called this in 2017 — frameworks were turning from runtime libraries into optimizing compilers, and the bytes shipped to the browser would bear less and less resemblance to the source. He was right, and you can see how right on the JavaScript side: Svelte is a compiler wearing a framework's clothes, React's server components are a compile-time split, the build step quietly ate responsibilities the runtime used to own. Dale's advice to anyone who wanted to matter was "learn how compilers work."

Here is where I'd extend him, and where the extension is partly my bet rather than settled fact. Dale's compilers compressed the implementation — same language in, optimized same language out, for the machine's benefit. The rung I think we're actually on is wider: the framework reads the whole application as one analyzable artifact and emits from a typed intermediate representation, and what gets compiled isn't just the templates but the entire program's structure — its routes, its associations, its data dependencies. The difference between minifying a view and typing an entire Rails app by whole-program inference is the difference between a tool that compresses and a tool that understands. The industry has the first half. The second half is what I've been building as a sample of one, and I won't pretend it's proven at scale. But the direction is Dale's, and the direction is right: the framework wants to be a compiler, and it wants to be a compiler over the whole app, not just the parts that were already easy.

One thing changed since 2017 that matters more than any of this. Dale said "learn how compilers work" because writing one was a serious, scarce undertaking — the province of specialists and funded teams. That stopped being true. The compiler I lean on most, I could not have written by hand; I authored the thing it had to satisfy and let an agent build the rest. So Dale's imperative inverts. It's no longer "learn to write compilers." It's "compilers are cheap to commission now, so the scarce skill moves to deciding what to compile, from what, to what." That inversion is what makes the higher rungs reachable at all. A motion that required an institution at every step would have stalled. This one doesn't.

And it isn't only me, which is the part that should make you take it seriously rather than treat it as one retiree's anomaly. While I was teaching a Rails app to compile itself into nine languages, someone else was writing — from scratch, in dependency-free C, no LLVM and no Apple runtime — a complete Swift compiler, SwiftUI runtime, Foundation bridge, and Metal shader compiler that runs entirely in a browser tab, retargeting Apple's platform to WebAssembly and canvas. (The project, MiniSwift, reports seventy-odd thousand lines of C and several hundred passing tests; I'm taking those figures from its author, the way I'd want you to take mine.) Different language, different stack, no knowledge of my work nor I of theirs until after — and the same move: a single person building the kind of whole-language compiler that used to require a vendor, and using it to turn a platform boundary into a build target. Two doors, one room. When the cost of building the tool that makes a rung reachable falls far enough, you don't get one person climbing. You get strangers arriving at the same rung from opposite sides, having never coordinated. That's what an inflection looks like from the ground.

Rung three: the framework becomes full-stack

This rung is not speculative either; it's where the mainstream is standing right now. Next, Remix, SvelteKit, Phoenix LiveView — the defining move of the last several years has been frameworks swallowing both sides of the client/server boundary into a single programming model. You write something that looks like one program, and the framework arranges for part of it to run there and part of it to run here. The boundary didn't go away, but the framework took over the job of negotiating it.

I'm aligned with the industry on this rung and differ only on method, which is worth being precise about because the method is where the next rung hides. The mainstream unifies the boundary by coordination — LiveView holds a stateful connection across it, server components coordinate a serialization protocol over it. The boundary is still there; the framework is just very good at talking across it. The alternative, the one whole-program compilation opens, is to unify by elimination: if both sides are compiled from one source into one artifact, there is no wire to coordinate, because there is no gap. You can watch the difference most clearly where it has historically hurt the most — system tests. The reason a decade of Capybara-and-Selenium tests were slow, brittle, and full of false negatives is that they were asserting determinism across a boundary that is nondeterministic by construction: a browser, a network hop, JavaScript timing, a server. DHH concluded the concept had failed. I'd say the separation failed, and the tooling was merely where the cost showed up. Collapse the stack into one artifact running in one process and the boundary isn't crossed more reliably — it's gone, and the class of bug that lived on it is gone with it. The tests that DHH gave up on run in tens of milliseconds with zero flakiness, not because the runner got better but because the seam they were fighting no longer exists.

So rung three, told honestly: the industry is already here on the destination — full-stack is the consensus — and the only live question is whether you reach it by getting very good at the boundary or by removing it. Both work. One of them makes a whole category of problem stop existing.

Rung four: the targets stop being exercises for the student

Here is the rung we're stepping onto, and here is where I stop reporting and start guessing, so I'll mark it plainly. For most of its life, a Rails application has had exactly one place to run: a server. Everything else — the browser as a first-class offline client, the phone as a native app, the edge as a worker with a tight memory cap — has been an exercise left for the student, or worse, an explicit bridge you hand-author and maintain forever. React Native rebuilds your client in a foreign stack and asks you to keep two apps in sync. Hotwire Native wraps a webview and asks you to write, by hand, the bridge between the native shell and the web content — the most visible, most fragile, most ceremonial code in the whole arrangement.

The rung says: those stop being destinations you port to and become targets you compile to. Browser, mobile, edge, cloud — build flags, not projects. And the reason this isn't just "more output formats is nice" is that each surviving target earns its place by removing a specific boundary and returning a specific, measurable benefit. The browser target isn't a curiosity; it's an offline-first deployment Rails couldn't otherwise reach. The mobile target isn't a checkbox; it's on-device native code with no bridge to maintain. The edge target fits a memory budget no Rails process fits. And running underneath several of them, the emitted code turns out to be the monomorphic, resolved-shape program a JIT was always built for — so the same compilation that buys you reach also buys you speed, an order of magnitude of it, as a side effect of having stripped the interpretive generality away.

This is also where I'm contemplating pruning, and that impulse is the tell that the rung is real rather than a demo. Exploring a wide spread of targets was how I proved the architecture was target-agnostic. Keeping all of them would be how I proved I'd lost the plot. So the spread collapses by principle: the Ruby family survives because it's nearly free — same language, marginal emit cost, and it buys the runtime and deployment spread. TypeScript survives because it's the universal client substrate that every browser, edge, and test tier rides on. Kotlin and Swift survive because they are the only way to reach on-device mobile as first-class native code rather than a wrapped webview. Free, universal, uniquely-enabling — three different reasons, each load-bearing. The targets that clear none of those bars did their job by demonstrating target-independence, and can retire.

What I suspect happens next is that the target goes invisible the way Erlang went invisible behind Elixir. Elixir didn't hide the BEAM by wrapping it; it became a better surface over the same substrate, and you reach down for Erlang only when you specifically mean to. If the same holds here, you won't choose Swift. You'll say you're building for iPhone, and the compiler will choose Swift, and the choice will be as invisible as the type annotations nobody writes anymore. And the bridge — the hand-authored, painfully visible bridge that Hotwire Native makes you maintain — becomes inferable for exactly the same reason the types did: the compiler that reads the whole app already knows which interactions are client and which are server. The information the bridge encodes by hand is information the analysis already holds. Make it invisible.

I want to be honest that this last move is the one I've made twice and not yet a third time. Types went invisible; I can show you that. Targets went invisible; I can show you that too. The bridge going invisible is a prediction — but it's the third instance of a pattern that held the first two times, and the pattern is specific: take something hand-authored at a boundary, recover it by whole-program inference, and let it disappear into the layer below. I believe the bridge because I've watched the same mechanism eat types and targets. That's not proof. It's the kind of confidence you're allowed to have when you're standing on a rung you climbed to.

Rung five: the compiler answers questions

The four rungs so far are a story about humans writing less. For fifteen years that was the whole benefit — invisible ceremony is ceremony you don't have to hold in your head. But there's a cost to rising abstraction that the human story politely ignores: every rung makes the system harder to reason about from outside, because more of it is implicit. The newcomer — the new hire, the contributor, and now the model — faces a program where the types aren't written, the bridge isn't written, the target isn't chosen in any one place. The information is all there, recovered by the compiler, but it was recovered in order to emit, and then it evaporated. Ask the running program what type comment is and there's no one to ask.

This is the rung the present moment forces, because the entity now doing much of the authoring is an agent, and an agent cannot read your mind about implicit structure any better than a new hire can — worse, it will confidently guess. Every Rails tool that tries to help it today either boots the whole application and reflects at runtime, or gives up and applies heuristics. There has been no way to ask a Rails codebase a sound, static question — what is the type here, can this be nil, where is this used, will this construct even lower to the target — and get an answer that's correct where it fires and honest where it can't.

But the compiler from rungs two through four already computes exactly those answers; it just throws them away after emitting. Keep them. Expose the whole-program analysis not only as a front-end that emits code but as a backend that answers queries — over a language server for the human's editor, over a tool protocol for the agent. The same inference that made the framework a compiler makes the framework legible, on demand, to whatever is building on it. The agent stops hallucinating the type and calls for it. The guardrail that an annotation used to provide — and that inference made invisible — becomes available again the instant something needs to check its work, except now nobody had to write it.

That's the rung the AI-native moment actually needs, and it's the one the framework-as-compiler story doesn't reach on its own. Rising abstraction spent fifteen years making the implicit invisible. The agent needs a way to make it explicit again on request — not by writing the ceremony back down, but by asking the compiler that already knows. Invisible to author, visible to interrogate. The compiler is how you get both.

What the ladder is actually made of

Step back and the rungs are one motion seen again and again: the place where software gets defined keeps rising, and at each level, ceremony that was hand-authored at the boundary below gets recovered by analysis and made invisible. Language to framework. Framework to whole-app compiler. Compiler to full-stack. Full-stack to all-targets. And then, when the implicit becomes too much to reason about, the compiler turns around and answers questions about what it made invisible. At every step, a category of explicit work — idioms, types, boundary coordination, target selection, bridges — stops being something you write and starts being something the layer beneath you infers. That's the ladder. It's not a prediction about frameworks; it's a fifteen-year-old habit of abstraction, still climbing, and the rungs I'm calling speculative are just the ones my feet haven't fully settled on.

But a ladder of rising abstraction has a graveyard at its base, and intellectual honesty means looking at it. The history of our field is full of boundaries that were declared about to dissolve and didn't. Distributed objects promised the network boundary would vanish and it didn't, because the network is a physical fact — latency and partial failure are real, and no abstraction smooth enough to hide them was ever fast enough to use. The fourth-generation languages promised the language boundary would melt into declarative specification, and mostly it didn't. So "boundaries dissolve" is not a law, and anyone selling it as one is selling something.

The discipline — the only thing that separates this from the graveyard — is telling an artifact boundary from an essential one. Some boundaries are accidents of tooling that couldn't see the whole program: the front-end and back-end of a small app are the same program, artificially split because the framework couldn't read both halves at once. Those dissolve, and the rungs above are the dissolving. But some boundaries are essential complexity wearing a code-boundary's clothes, and those do not dissolve — they were only hidden. Collapse the client/server code boundary and you do not abolish latency, partial failure, offline conflict resolution, or the deploy skew between a phone app and the server it left behind weeks ago. You surface them. The honest version of rung four is not "the boundaries go away." It's "removing the artificial boundary exposes the real ones that were hiding behind it, and those still need policy, judgment, and explicit decision." I know this because every place I've removed a code boundary, the genuinely hard problems — who's authorized to call this, which version of the contract is the phone running, how do two offline edits reconcile — are exactly what poke through afterward. The compiler made the easy boundary vanish and handed me the hard one in better focus. That's not failure. That's the abstraction doing its actual job: not eliminating the difficulty, but moving it to where it's real and refusing to let ceremony hide it.

So I don't know how far the ladder goes, and I'm suspicious of anyone who claims to. But the AI-native moment lets me ask a sharper question than "how far," which is: who is the invisibility for now? For fifteen years the answer was the human — less to write, less to carry. But if an agent is doing much of the authoring, the calculus changes, and it changes in a way I find clarifying rather than alarming. Ceremony that's inferred instead of written is ceremony the agent cannot get wrong: it can't mistype what it never typed, can't desync a bridge it never wrote, can't misconfigure a target it never chose. Every rung that makes something invisible to the author also removes a thing the agent could have botched — and rung five hands back the ability to check the rest. That is not a small reframing of safety. The way you keep an agent honest is not to review every line it writes; it's to give it a layer that's correct by construction beneath it and a compiler that will answer, soundly, when something needs verifying.

Follow that far enough and the ladder points somewhere specific. If the language is a substrate you don't think about, and the framework's idioms are invisible, and the types are inferred, and the boundaries are compiled away, and the targets are chosen for you, and the agent generates the implementation against a compiler that certifies it — then what is left for a person to actually author? The answer the whole climb has been converging on is: the definition of correct. The spec. The conformance gate. The oracle that says this output is right and that one isn't, against which everything below — generated, inferred, compiled — is measured and replaced at will. That's the top of this ladder as far as I can see it: the human climbs until the only thing still hand-authored is the statement of what counts as done, and the entire stack beneath it becomes derivation. Not because we decided implementation didn't matter, but because, rung by rung, we made it something the layer below could infer or generate — and the one thing no layer below can infer is what you were trying to build in the first place.

I can't prove that's where it ends; it's the view from a rung, not a summit. What I'll claim is narrower and, I hope, more durable: the motion is old, it is still moving, it climbs by making boundary-ceremony invisible through whole-program inference, and it has just acquired an engine. The thing that makes each rung reachable used to require an institution and now requires a person and an afternoon — and the thing being authored, as you climb, keeps shrinking toward the single irreducible act of saying what correct means. That's what the inflection is, underneath the noise. Not that the machine writes the code. That the cost of building the tools that make code invisible fell to nothing, the ladder started moving again after years of being stuck, and the part left for us turns out to be the part that was always the point.


Roundhouse is open source: dual-licensed MIT / Apache-2.0. Issues and discussion welcome.