intertwingly

It’s just data

There Is No Server


At noon today, Pacific time, I'll open my RubyConf talk on the Main Stage in Las Vegas by asking a few hundred people to take out their phones. The opener doesn't need the room — you can beat them to it:

  1. Open rubys.github.io/roundhouse/blog — phone is fine.
  2. Create an article. Anything.
  3. Open the same URL in a second tab, side by side, and create another. Watch it appear in both.

You just wrote to a database. Everyone reading this post is writing to a database. Whose server is that?

Check the URL bar: github.io. Static file hosting. Nobody's server. You are running the entire Rails application — router, controllers, Active Record, SQLite — alone, in your tab. The cross-tab sync is Turbo Streams, with Action Cable's role played by a BroadcastChannel out of a SharedWorker. Close the tab and reopen it: your article is still there, persisted in the browser's origin-private file system. At noon, the room will do a few hundred Rails deployments in the first ninety seconds of the talk. You just did yours early.

The talk

The slides are at rubys.github.io/rubyconf-2026, and they ship with the full speaker notes — the complete act-by-act script, not bullet-point shorthand. Press S for speaker view, or open ?script to read the whole talk as a short document. Anyone not in the room can read exactly what the room will hear.

One thing about those slides: the deck is a static page published at the same origin as the demos, so the demos embedded in the slides aren't screenshots or recordings — they're the same live applications you just opened, sharing the same SharedWorker and the same database. The presentation about serverless Rails has no server either. (The demos track the live site and keep improving, so what you see may be slightly ahead of what the room sees.)

The title's debt to The Matrix is deliberate, and it runs deeper than the title: digital rain falls behind the title slide, and the characters falling in it are the blog's own Article model. If you're in the room at noon, there's also a costume — the Keymaker, the man who cuts keys to every door in a system he doesn't control, which is as good a job description for a ten-target compiler as I could ask for. Hence the key at the top of this post.

The argument, compressed: those eight lines of Article model that open Act III say what, not how. has_many says nothing about query plans; validates says nothing about the runtime. Rails is to web applications what SQL is to data — a declarative spec — and once you take that seriously, the compiler is the query planner and the deployment target is the execution plan. Your browser is one target. Rust is another. So are TypeScript, Crystal, Elixir, Go, Python, C#, Kotlin, Swift — and typed, annotated Ruby that Matz's ahead-of-time compiler, Spinel, takes to a native binary. Every commit, CI boots all of them and DOM-diffs every URL against real Rails; eleven of twelve configurations must come back empty to merge.

This isn't a new idea. It's the first Futamura projection, from 1971: specialize an interpreter to one program, and the interpreter melts away — what remains is a compiled program. Rails is the interpreter. Your app is the program. The weird part was pointing fifty-year-old theory at has_many.

The talk doesn't argue this so much as demonstrate it, with tooling that is itself the title restated. The playground is the compiler — roundhouse compiled to WebAssembly — loaded into the slide, retargeting the blog to Rust or Elixir or Go as you type. The studio closes the loop from spec to consequence: edit a validates, and the running app's error message changes and its test suite goes red, live, in a static page. The IDE is Monaco over the same wasm analyzer, preloaded with all of Mastodon. The blog you deployed at the top of this post is a Rails app with no server; these three are the toolchain with none. Even the query planner runs in your tab.

See for yourself

The flourish at the center of Act II is one you can reproduce at home, and it's the strongest receipt that a real Rails app is running in your tab. You'll want Chrome or Edge for this — chrome://inspect is Chromium-only.

  1. Open the blog and create an article, so the worker is alive.

  2. In another tab, go to chrome://inspect/#workers and click inspect under the rubys.github.io shared worker. A DevTools window opens.

  3. In the Sources panel, hit Cmd/Ctrl-P and type articles_controller.rb. That's your first thing to notice: the file tree holds a Rails application — controllers and models under app/, by their real paths, in Ruby.

  4. Set a breakpoint in the create action, go back to the blog, and submit the New Article form. Execution stops in Ruby — call stack, locals, params, the works.

  5. Resume, then in the DevTools console:

    await new app.Article({title: "hello", body: "from the console"}).save()

    Every open tab of the blog updates instantly.

The trick has no trick: the compiler emits JavaScript with source maps that carry the original .rb sources into the bundle, so Chrome debugs the Ruby you wrote, mapped onto the JavaScript you're actually running.

The numbers

The middle of the talk belongs to a claim I've been building toward all year: Ruby was never the slow part. Same app, same CRuby, same YJIT — melt only the framework, and throughput goes up 8×. At least 87% of a Rails request is the framework re-answering questions whose answers were fixed at boot. YJIT's own verdict points the same direction: +33% on the melted version, −3% on Rails — fresh Arel trees and polymorphic attribute reads every request leave the inline caches nothing to hold. Hand both versions to the JVM and the gap widens to 27×. JITs amplify static shape; Rails erases it. Language accounts for maybe 2–8×; architecture for 8–27×; they multiply.

The ladder isn't an exit from Ruby — it's Ruby's exoneration.

All of these are geomeans across the five endpoints on the project's own live benchmark page, rubys.github.io/roundhouse/bench, republished on a cron from real runs, caveats included. The digits drift as roundhouse develops — the page will have moved by the time you read this — but the conclusions don't hang on the decimals. Don't take the slide's word for it; the raw numbers are a click away.

The robots we live with

The subtitle's third promise is a story with timestamps. On April 30th I hit a Spinel miscompilation — a module assigned to a module-level attr_accessor read back as 0. Claude Code analyzed the failure and drafted the issue; I filed it as matz/spinel#126 at 02:46:53Z. Matz — working with his own agent — landed a two-stage fix by 03:39:47Z. I verified against master and reverted my workaround at 03:58:33Z. Seventy-two minutes, two humans, two agents, two compilers. Raise your hand if you've shipped a fix to someone else's compiler in 72 minutes.

The talk presents those 72 minutes as remarkable, and in April they were. Ten weeks later, that's just the cadence. In the week before this post, I filed fifteen more spinel issues — an && evaluation-order violation, a rescue frame popped before its return expression finished evaluating, String#include? truncating at NUL bytes — and all fifteen are closed, four of them inside an hour, the fastest in eighteen minutes. The flow runs both ways now, too: four pull requests of mine landed in spinel's codegen the same week, each merged within a couple of hours. April's story was issue #126; last week's carry numbers in the 2400s.

The receipt I like best from this week is one I have a photograph of. At 15:38:55Z I filed matz/spinel#2438group_by silently dropping its nil-key group, no diagnostic, the rows simply gone at runtime; the exact shape you hit threading comments by parent_comment_id and reading the roots back out of h[nil]. Nineteen minutes later Matz closed it, his agent co-authoring as always. Then I took the photo. In the foreground, GitHub still calls the commit "3 minutes ago"; in the background, Matz is on stage, a microphone being clipped to his collar. He closed my compiler bug and walked on to give his keynote.

The deck's own repository is the other half of that story: every commit is Co-Authored-By: Claude. The talk about living with the robots was assembled living with them, and git log is the checkable appendix.

What doesn't work

Candor gets its own act, because the skeptics in the room are owed one. method_missing never compiles — the answer only exists at runtime. define_method is a diagnostic today. C extensions are a wall for most targets (Spinel speaks FFI, so there it's a door). The line isn't the keyword — it's whether the answer exists before the app runs; has_many is define_method under the hood, and you watched it compile. Every unsupported construct becomes a diagnostic on the exact line, and the diagnostic list is the roadmap, not a gate. Fed all 83,000 lines of Mastodon, the analyzer types most of it and hands back a ledger of precisely what it can't yet model — you can browse that ledger yourself, in an IDE that is also just a tab.

Why any of this matters: Rails gets you to market — everyone agrees on that part. The startups that survive face the sequel: the rewrite, proverbially in Go. Types and performance — the two criticisms WIRED leveled at Ruby this year — are exactly why that rewrite happens. What if the rewrite were a build target? Go, or nine others: readable, idiomatic, yours to evolve independently. Ejection, not lock-in.

Homework

In the room, the ladder slide comes with an apology: every language on it is a live link, and clicking links is not a phone experience. This post is where that homework lands.

The long form

A thirty-minute talk compresses a year of posts. If you want the full argument behind any act:

The close of the talk quotes something I wrote three years ago about JavaScript: the JavaScript you write isn't the JavaScript you run. Ruby has now caught up to its own version of that weirdness. The Ruby you write isn't the Ruby you run. It's Rust. It's your phone. It's Matz's C. Or nothing at all — there is no server.

The last slide of the deck is titled "Continue the argument," and that's the success metric here too. The issues at rubys/roundhouse are open, the speaker notes name every claim's receipt, and the conference recording will be linked here when it's posted.