Pencils Down, Notation Up
I watched the opening keynote at Rails World 2026 expecting a talk on "Shaping the Future of Ruby on Rails." What I found instead was important and needed to be said, but it wasn't quite what that slot on the agenda had led me to expect.
The message I heard was that LLMs are a game changer (I agree), and that we are at an exciting point in history (again, agreed). The keynote closed with:
So take the white pill, take the optimism pill, lean in. [...] realize there is only one choice, and that is for you to embrace the future with optimism, with gusto, with full acceleration. The black pill is for fucking losers. Don't be a loser.
There is only one choice, he said. I'd like to argue there's at least one more. I'd like to see a world where you can believe both the promises on the Ruby on Rails website and the vast majority of the premise of "Pencils Down." I think the gap between those two is much narrower than the keynote made it sound, and DHH himself described exactly where it is.
The drill bit
Two days after the keynote, DHH wrote:
Rust is a good prompt compilation target for the moment, but so is C++. And soon assembler. Then microcode. Myopic to think we're going to stop the agentic drill bit until it reaches computing bedrock.
I agree with more of that than he might expect. There's a source, a translation step and a target, and the target will keep moving down. HEY's new backend is a good example of where that makes sense. It's a mail server, with an existing reference implementation to check against, and the hard parts are protocol edge cases and throughput. I can see the logic of Rust there.
The post leaves one question unasked, though: what sits at the top of the drill? What do we keep, edit and trust as the source of truth?
A thought experiment
I have a concrete way to ask that question. Using Roundhouse and Matz's Spinel compiler, I compiled Basecamp's own ONCE Campfire, unmodified, down to C. The drill has already reached that layer: the result is a single file of 202,000 lines, and it builds into a Docker image with no Ruby in it. Download it and look for yourself; it's pack/src/campfire.c.
So here's the question. Give an agent a change to make to Campfire. Does it do better editing the Rails app or the C?
LLMs can probably handle both. The results won't be the same:
| Campfire in Rails | Campfire in C | |
|---|---|---|
| size | ~60k tokens | ~4M tokens |
| fits in an agent's context? | yes, all of it | no, ~70× too large |
lines mentioning boosts |
20, in 13 files | 1,220 |
lines mentioning involvement |
28, in 9 files | 593 |
The sizes aren't quite like for like. The Rails column counts only Campfire's app code, while the C also contains the compiled framework. But an agent editing the C has to work inside all of it, and the framework never mentions boosts. Every one of those 1,220 lines comes from the app.
Nobody would choose to maintain the C. That's what makes it a compilation target. But it's worth being precise about why, because the same reasons apply to anything else we might put at the top of the drill.
What a source of truth needs
It has to fit in context. Agents read far more than they write. Every change starts with loading the relevant code, and an agent makes mistakes about the code it didn't see. The Rails app fits in context whole; the C is only ever seen in slices.
It has to say each thing once. Changing how boosts work is about twenty coordinated edits in Rails and over a thousand in C. Each edit that's missed or slightly wrong is a bug, and in C it's often a memory-safety bug rather than an exception. The agent is also the future maintainer, so it's the one that pays for the mess.
It has to keep the intent. Those 1,220 lines of C exist because of has_many :boosts and a few declarations next to it. The C doesn't say so. The decision was erased when the code was compiled. Changing the decision in Rails is a one-line edit. Doing the same in the C means working backwards to the decision first, which is decompiling rather than maintenance.
It has to produce the same result every time. Compile the same Rails source twice and you get the same C. Regenerate from the same prompt twice and you get two different programs, each of which has to be verified again. The further down the drill goes, the less anyone can read the output, so verification carries more of the weight.
It helps if the model has seen it before. A conventional Rails app looks like thousands of others in the training data, so the agent's guesses are usually right. Generated C is a dialect the model has never seen.
None of these reasons depend on a human reading the code. The Rails homepage promises "token-efficient code that's easy for agents to write and beautiful for humans to review." With pencils down, the second half matters less. The first half matters more than ever.
What about the prompt?
The obvious reply is that nobody maintains the C: you maintain the prompt and regenerate. That's the real disagreement, and it comes down to the same five tests.
For the output to be the Campfire rather than a Campfire, the prompt has to pin down every association, validation, callback and route. It has to say each of them once, compactly, in a form that produces the same result every time. A prompt that precise is a program. At that point you've written a domain-specific language for web applications with a database, and we already have one, refined over twenty years, that every model has read.
has_many :comments was always a specification. Rails is the notation; what runs it at the bottom is a separate question.
You don't have to give up the numbers
The keynote's case for Rust was performance: 99% less CPU, 95% less memory. It's fair to ask whether keeping Rails at the top means giving that up.
It doesn't have to. Compiling a Rails reference app to Rust, I measure 85–140× Rails' throughput in 96% less memory. Compiling Campfire to a native binary, it's 7–8× the throughput of Campfire as its own Dockerfile deploys it, in 92% less memory, with 6–14× lower tail latency. (Blog benchmarks, Campfire benchmarks.) These are different apps from HEY, measured differently, so I'm not claiming a head-to-head. But they're the same order of magnitude, from unmodified Rails source.
The most telling number is a smaller one. The emitted code running on plain CRuby is already 6–10× faster than Rails. Most of the cost was never Ruby. It was deciding, on every request, what the app's declarations mean: has_many :comments, the routes, the callbacks, the views. Decide that once, at compile time, and most of the cost goes away.
Pencils down, notation up
That's the talk I was hoping to hear in that slot. Not "Rails or agents," and not "Rails or Rust," but what Rails becomes when agents write the code: the most compact, precise and conventional specification of a web application, whatever it ends up compiled to.
Let the drill go as deep as it can. Just keep the notation at the top.