.offrails/README.md

Rust off Rails

The opinionated web stack for the AI era.

Rust off Rails is a web framework for AI coding agents. It ships no library you depend on: it's a set of Markdown files that tell agents how to build a full-stack Rust web app (one blessed stack, one directory layout, one way to do each thing), plus the app template those files describe, as real, tested files the agent copies instead of retyping (template/).

The convention lives in the prose; the code is copied from the template and its parts, then written by the agent for everything app-specific. Humans still write none of it.

Standing on giants

We take the best ideas of two great ecosystems and bring them together in one stack.

  • From Ruby on Rails:
    • convention over configuration;
    • generators (new, scaffold), Active Record-style models and migrations;
    • Rails 8 authentication;
    • the Solid trifecta: jobs, cache and pub/sub in the database, no Redis;
    • Turbo 8-style page-refresh broadcasts;
    • mailer previews, bin/rails tasks, and Kamal-ready Docker with /up.
  • From Elixir and Phoenix:
    • Oban persistent jobs and the Oban Web dashboard;
    • Swoosh's local mailbox;
    • phx.gen.auth magic links and the "check your mailbox" hint;
    • LiveDashboard-style dev tools;
    • Channels, Presence and PubSub;
    • multi-node clustering and "let it crash" supervision;
    • Ecto's explicit data layer (no callbacks);
    • the pending-migrations page.
  • And also: htmx, Inertia.js, Tailwind, shadcn-style owned components, and end-to-end types from Rust to TypeScript.

RAILS_MAP.md maps every Rails concept to its Off Rails equivalent, plus a Borrowed from Phoenix / Elixir section.

The deal

Rails gave you…Rust off Rails gives your agent…
A gem with 300k lines of Ruby~50 Markdown files and an app template the agent copies and owns
rails generate scaffoldgenerators/scaffold.md: a recipe the agent runs
Convention over configurationConvention over deliberation: the agent never debates which crate to use
"Optimized for programmer happiness"Optimized for agent correctness: compiler-checked everything
MagicNo magic. Explicit code is cheap when you don't type it.

Rules of the framework

  1. All code is written by AI. No exceptions. Humans write intent: specs in Markdown, interview answers and feedback. Agents write every line of Rust, SQL, HTML, CSS, TypeScript, config and scripts. If a human edits code anyway, the agent treats the edit as a spec change, records it, and owns that code from then on.
  2. Strong opinions, aimed at agents. Every decision an agent would otherwise improvise (crate choice, file location, naming, error handling, testing) is already made in these docs.
  3. Everything Rails has, mapped. Active Record, Action Controller, Action View, Active Job, Action Mailer, Action Cable, Active Storage, Solid Cache/Queue/Cable, generators, bin/rails, credentials, Kamal. See RAILS_MAP.md.

Using it

Install (one line)

curl -fsSL https://rustoffrails.org/install.sh | sh

It checks for Rust (and offers rustup), installs the cargo tools the framework uses as prebuilt binaries, downloads and SHA-256-verifies the framework into ~/.offrails/, installs the Claude Code plugin if claude is on your PATH, and adds an offrails command. No sudo, no telemetry, and it's safe to re-run. Add --dry-run to see the plan first. INSTALL.md explains every step, with a manual equivalent.

offrails new bookshelf "people track the books they've read"   # starts Claude Code (or Codex) with /offrails:new
offrails doctor                                                # prerequisites and versions
offrails update                                                # latest release

Or by hand: Claude Code plugin

/plugin marketplace add rust-off-rails/rustoffrails      # or a local checkout: /plugin marketplace add /path/to/rustoffrails
/plugin install offrails@offrails

Then, in an empty directory:

/offrails:new bookshelf: people track the books they've read and review them

The agent interviews you first (accounts? who owns data? htmx or React? where will it run?), using recommended defaults so you can just say "you decide". It confirms a plan, then builds. The skills also trigger on plain requests ("make me a new Rust web app…").

SkillRails equivalentWhat it does
/offrails:new [name] [pitch]rails newInterview → plan → app skeleton → planned features, green
/offrails:feature [request]—Spec first, then code. With no argument: build what changed in product/
/offrails:scaffold Book title:string …rails g scaffoldFull resource in the app's frontend mode
/offrails:generate job|mailer|channel|authentication|model|migration|task …rails g …Any other generator recipe
/offrails:checkbin/rails test + CIDefinition of Done: fmt, clippy, tests on SQLite and Postgres, frontend checks
/offrails:doctor [--fix]rubocop + reviewAudits the app against every convention in these docs
/offrails:deploy [fly|kamal|railway|render]kamal setupDeploy config, verified image; confirms before anything outward-facing
/offrails:upgraderails app:updateMoves the app's vendored .offrails/ to the plugin's version and migrates conventions

The plugin also installs hooks. They only act inside Off Rails apps (directories with .offrails/):

  • Edit guard: blocks edits to .offrails/ (read-only) and web/src/types/ (generated by ts-rs).
  • Convention check: after each edit, flags raw SQL outside the kernel, banned crates or packages, and /:id routes.
  • Stop gate: the agent can't end a turn while cargo fmt --check or cargo check fails.
  • Session start: orients the agent and notices when the app's framework version is behind the plugin's.

Each app still vendors the docs into .offrails/, pinned to a version, so the app keeps working with any agent, with or without the plugin.

With Codex, Cursor, or any agent that reads AGENTS.md

Read /path/to/rustoffrails/AGENTS.md and make me a new app called "bookshelf".

Generated apps carry AGENTS.md and .offrails/, so later sessions need no setup.

Then

Describe features in plain language, or write them as specs in product/:

Scaffold Book with title:string, author:string, published_on:date, rating:integer.
Users must be logged in to create books. Send the owner an email when a book gets a review.

The template and its parts

A new app is a copy of template/app/: a complete, compiling app (htmx) with the kernel, security headers, CSRF, sessions, Host allowlist, rate limiting, log redaction, SQLite and Postgres, /dev tools, Tailwind, CI, Docker and the kernel tests, renamed and green in minutes instead of an hour of generated Markdown. On top of it go the parts the product needs: the mailer and dev mailbox, jobs and their dashboard, accounts (password, magic link, TOTP), realtime with presence, and a LiveDashboard-style dev dashboard. They're the tested samples from rust-off-rails/samples (screenshots of every screen there). Your agent proposes the ones your app needs, you confirm, and later needs are one command: cargo offrails add-part <name>. Once copied, the app owns the code (guides/samples.md).

The generators (offrails-gen: new, add-part, list-parts, generate, destroy, snippets) are a small standard-library Rust tool, so they work the same on Linux, macOS and Windows. Features start from a snippet (small tested code to copy: a feed, slugs, Markdown, a pager, a theme toggle; template/snippets) or a generator (cargo offrails g resource posts title:string), both optional, both the app's once written: small files per feature, one marked line per shared spot. Pages are built from a component kit (Askama macros, light and dark, branded from one brand.toml) in five layouts, and cargo offrails prove screenshots and checks every page and writes docs/proof/PROOF.md, tying each acceptance criterion to its passing tests and pages.

Field tests: real apps, ported by agents

We test the framework by having agents port well-loved open-source apps to it. Each port puts the docs, the template and the parts up against a product with years of real-world design behind it, and every rough edge an agent hits goes back into the framework.

  • fizzyer: a Rust port of Fizzy, 37signals' kanban-style tracker. Repository coming soon.
  • A FreeScout port: a Rust port of FreeScout, the open-source help desk. In progress; repository coming soon.
  • uptime-offrails: a Rust port of Uptime Kuma, the self-hosted uptime monitor. Repository coming soon.

The ports are built by different agents on purpose: GPT-6 Luna (high), Claude Sonnet 5.5 (high and medium), Meta's Muse Wonder 1, and smaller open models such as GLM 5.3 Flash through opencode. The goal is a framework that makes smaller, cheaper agents productive too, not only the biggest ones.

They're for education and testing first. We also plan to run them ourselves, mostly to save server resources. Each will be open source under its original project's license, and contributions are welcome. They're independent ports, not affiliated with or endorsed by the original projects; credit for each product's design goes to its authors.

Repository layout

AGENTS.md          entry point for agents: read this first
interview.md       the questions agents ask before building
doctrine.md        the philosophy, rewritten for machines
RAILS_MAP.md       every Rails concept and its Off Rails equivalent
stack.md           the blessed crates (the "Gemfile")
structure.md       directory layout and naming conventions
cli.md             the app binary and the bin/rails equivalents
workflow.md        how an agent works: spec → code → green → done
guides/            one guide per framework component
generators/        recipes that replace `rails generate`
blueprints/        files copied into every new app (AGENTS.md, spec template…)
template/          the app template (app/: a complete, tested blank app) and its parts (parts/: the samples), copied by offrails-gen
.claude-plugin/    Claude Code plugin + marketplace manifests
skills/            /offrails:* skills (thin entry points into the docs above)
hooks/             convention guards (no-ops outside Off Rails apps)
evals/             `claude plugin eval` cases: does the framework steer agents right?
tools/             offrails-gen/ (new, add-part, list-parts: Rust, any OS), offrails (the CLI), brand/ (favicons + social card), browser-smoke/ (kernel pages), release/, ci/
install.sh         the one-line installer (served at rustoffrails.org/install.sh); INSTALL.md explains it
VERSION, CHANGELOG.md   framework version; apps record theirs in .offrails/VERSION

Developing the framework

claude --plugin-dir .                       # load this checkout instead of the installed plugin
claude plugin validate . --strict           # manifests, skills, hooks
claude plugin eval . --runs 1               # run the eval cases

Every doc change goes under ## Unreleased in CHANGELOG.md the moment it's made, with App migration notes written for an agent. Each release section keeps the shape described at the top of CHANGELOG.md (What's new, Breaking changes, Upgrading, Full list of changes): it becomes the GitHub Release notes. Apps detect drift by content fingerprint (hooks/fingerprint.sh), not version alone, so even unreleased edits show up as an upgrade.

Releasing:

  1. Rename Unreleased to the new version.
  2. Bump VERSION, .claude-plugin/plugin.json and tools/offrails-gen/Cargo.toml (+ its Cargo.lock) together, and commit (release.sh refuses a mismatch: offrails-gen's version is in Cargo's fingerprint, so an app's cached cargo offrails binary is rebuilt for the new release).
  3. tools/release/release.sh --dry-run to check, then tools/release/release.sh. It builds a reproducible rustoffrails.tar.gz (the whole framework, skills and hooks included), install.sh and checksums.txt, tags v<version>, and creates the GitHub release with gh. install.sh downloads releases/latest/download/…, so a release is all it takes.
  4. Publish the release's install.sh at https://rustoffrails.org/install.sh (the website serves it).
  5. claude plugin tag --push for the plugin marketplace.

Installer tests: tools/ci/test-install.sh (local shells + clean Linux containers; --full also runs the real rustup + cargo-binstall path).

Template tests: TEST_DATABASE_URL=postgres://… tools/ci/test-template.sh (generates the bare app, every part, parts added one by one, every generator and every snippet; the Definition of Done on SQLite and Postgres and the memory budget). Parts come from the samples repo: tools/sync-parts.sh <samples checkout> <ref>, never edited here.

Installed plugins update only when the version changes.

The stack in one breath

Axum on hyper (the binary is the web server: HTTP/1.1 + HTTP/2, no app server or nginx needed) · Tokio · SeaORM (SQLite locally, Postgres in production, same code) · Tailwind · a frontend mode chosen at birth:

  • htmx (default): Askama (compile-time checked templates) + htmx. No Node. Optional React or Svelte islands.
  • inertia: React or Svelte pages with Rails-style server routing, via a generated Inertia adapter. The default when you want a JS UI.
  • spa: a Vite SPA (React Router or SvelteKit in SPA mode) + a JSON API.
  • api: no UI, for mobile backends and public APIs (in progress).

UI_LIBRARY: react | svelte picks the JS library. Props and API types are generated from Rust by ts-rs, so the type chain runs from the database to the browser.

Plus a database-backed job queue, cache and pub/sub (the "Solid trifecta") · lettre · object_store · tracing · one binary, one Docker image.

Yes, the acronym is still RoR. That's on purpose, and it's a thank-you.

License

Licensed under either of Apache License, Version 2.0 or MIT license, at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Security issues: see SECURITY.md. Contributing: see CONTRIBUTING.md.