Rails as a specification; deployment is a build flag.
Roundhouse reads Ruby source — specifically, Rails applications — and produces standalone projects in other target languages. The deployment target (Rust or Swift binary, TypeScript bundle, Crystal or Go service, Elixir OTP app, Kotlin/JVM or C#/.NET service, Python project, browser bundle, or Spinel-compiled Ruby) becomes a compiler flag rather than a runtime choice.
A roundhouse is the circular hub in a rail yard where engines rotate and route onto different tracks. That's the pipeline shape: one Ruby source at the center, analyzed and dispatched to one of N target tracks.
For the case for doing this at all — the constraints that push successful Rails apps off CRuby, and the option value of preserving the choice — see WHY.md.
Three things, from one binary and one analysis. Each has a door in the user guide.
Analyze. No type annotations are involved anywhere. Rails was
already typed: has_many :comments is a type declaration, and the
framework's conventions carry type information that was simply never
written down. Roundhouse recovers it by whole-program inference —
which class an association returns, which columns a model has and
what they deserialize to, whether a find_by came back nil — from
unmodified source, without booting the app or touching a database. A
pass over Mastodon (1,173 files, all 337 controllers, HAML views
included) takes about 1.5 seconds. That inference is a product in its
own right: roundhouse check for a terminal or a CI gate, an
LSP server for your editor, an
MCP server for your agent — types, nil-safety,
static N+1 findings, and the full request trace for any action — and
an in-browser IDE that
analyzes a folder on your disk without uploading it. Static, deep and
annotation-free is a cell of the Ruby tooling space nobody else
occupies: ruby-lsp is static but stops at names; ruby-lsp-rails and
Tidewave are deep but need a running app; Sorbet and Steep are static
and deep but you pay in annotations. →
check · editor ·
agent · IDE
Transpile. The same analysis fed to a dozen emitters: a
standalone project in Rust, Go, TypeScript, Crystal, Elixir, Kotlin,
Swift, Python, C#, or Ruby, with its own tests and no Rails at
runtime. The way we know the output is correct is a conformance
oracle: the same URL fetched from Rails and from each target must
produce the same response — emitted tests, a differential compare
against live Rails (DOM node for DOM node, JSON value for value), and
browser end-to-end tests for what a static diff can't reach — and
every target on the list passes it on every push. →
--target · targets
· what of Rails comes through ·
verifying
Compile. The Ruby shape compiled ahead of time to one native binary by Spinel, Matz's AOT Ruby compiler: one executable, one SQLite file, no interpreter. Among the compiled targets it has the closest behavior to Rails by a distance, and will for the foreseeable future, because it runs the framework runtime itself rather than a translation of it. Basecamp's Campfire runs this way — every page and every cable frame compared against live Rails on every push, and a Docker archive you can run in minutes. → Spinel
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/rubys/roundhouse/releases/latest/download/roundhouse-installer.sh | sh
roundhouse --version
roundhouse check --continue /path/to/your/rails/appReleases are dated snapshots with binaries for macOS on Apple silicon
and Linux x86-64; RELEASES.md says what each one
contains. The binaries cover most needs, and cargo build --release --bin roundhouse from a checkout is always there for the rest —
docs/guide/install.md has both paths and
the prerequisites.
- IDE — the analyzer in a browser tab, preloaded with the Rails Guides store, the blog, Lobsters, Campfire and Mastodon; open folder… for your own app.
- Campfire — the compiled product, as a Docker archive.
- Browse — what every emitter produces from the blog fixture, updated on each push.
- Bench — throughput, memory and latency across the live targets on a fixed box, against Rails as it ships.
- Demo — the fixture every target is built and tested against, and its three test layers.
Rails is, operationally, an interpreter for your application — routes, associations, validations and templates are data it consults on every request. Every decision whose answer cannot differ between requests, Roundhouse makes once at transpile time; only the per-request residue survives to runtime. On the blog fixture, serving the HTML index on a fixed Linux x86 server (July 2026 round):
| configuration | req/sec |
|---|---|
| Rails on CRuby+YJIT | 326 |
| Rails on JRuby | 1,066 |
| Roundhouse emit on CRuby+YJIT | 3,292 |
| Roundhouse emit on JRuby | 24,172 |
Stripping the interpretive layers is worth ~10× on the same interpreter; the static, monomorphic Ruby that remains is the input the JVM JIT was built for, worth a further ~7×; the compiled targets go further still. These are ratios from a CPU-bound microbenchmark of a small fixture, and the live numbers with their environment capture are on the bench page. The long-form versions:
- Conformance vs Comprehension — the project, and the conformance-oracle methodology behind it
- Live Types for Rails — the inference as a live type checker: LSP, MCP, and the competitive landscape
- An IDE You Don't Install — Mastodon analyzed in a browser tab
- Campfire Chats — a real product, compiled
- The Ruby JRuby Was Built to Run — the 2×2 experiment the table above is the current round of
- Numbers Without Conclusions — benchmark methodology, and what the numbers are and aren't evidence of
Using it: the user guide — one page per door
above, starting at install — and
RELEASES.md.
Working on it: DEVELOPMENT.md (build, test, the
bin/rh workflow runner, debugging tools, repo map),
AGENTS.md (the invariants not to break), and
docs/ — the architecture: the compiler's
inputs, the pipeline (analyze, lower,
emit, runtime, verification), and the working plans.
BETS.md is why this attempt is shaped differently from
its predecessors; WHY.md is why do it at all.
- railcar — the Crystal-based predecessor; taught us which bets were worth keeping and where the shape needed to change.
- ruby2js — transpiles Ruby to JavaScript; originator of the filter/escape-hatch pattern for per-app transformations.
- Juntos — ruby2js extension that transpiles entire Rails apps; validated the multi-target ambition against Basecamp's Writebook.
Issues and discussion are welcome. Architecture is still forming — a quick conversation before a PR is usually the most helpful path.
Dual-licensed under either of
at your option.
runtime/spinel/tep/ carries code that began as
tep 0.8.1 by Ori Pekelman,
MIT-licensed; see runtime/spinel/tep/NOTICE. That directory ships
into every emitted spinel tree, under runtime/tep there.