{"slug": "rails-hyperdrive-supercharged-agentic-development-for-rails", "title": "Rails Hyperdrive: supercharged agentic development for Rails", "summary": "Evil Martians built Rails Hyperdrive, a development-only Rails engine that lets any gem in a bundle ship agent knowledge and exposes eight MCP (Model Context Protocol) tools that answer live from a booted app. The tool follows the Agents on Rails benchmark Evil Martians built for the Rails Foundation, in which Claude Opus 5 and Claude Fable 5.1 each passed 58 of 63 runs but the best models reached for the Rails API the task turned on in only 41% of runs. Rails Hyperdrive ships no knowledge of its own; the bundle decides what the agent learns, with companion gems such as layered-rails-skills@3.1.0 installing a skill, two agents, and eight commands into a project's .claude/ directory.", "body_md": "# Rails Hyperdrive: supercharged agentic development for Rails\n\nEvery session, a coding agent meets your Rails app like a stranger. It reads the Gemfile, greps `routes.rb`, trusts a `db/schema.rb` that may lag behind the migrations, and guesses the rest from training data. This burns tokens before the first line of work, and these guesses cause bugs. We’ve measured this. In [Agents on Rails](https://evilmartians.com/clients/rails-foundation), the benchmark Evil Martians built for the [Rails Foundation](https://rubyonrails.org/foundation), the leading models pass most atomic Rails tasks ([Claude Opus 5 and Claude Fable 5.1 both pass 58 of 63 runs](https://rubyonrails.org/2026/9/2/agents-on-rails-claude-fable-5-1-and-glm-5-3-flash)), yet even the best [reach for the Rails API the task turns on](https://github.com/rails/ai-evals/blob/main/methodology.md) in only 41% of runs; the rest of the time, it writes its own version. Frontier models are good at Rails. They’d be much better if someone handed them what the framework and your gems already document. That’s why Evil Martians built Rails Hyperdrive. In this article, we’ll cover all you need to know.\n\n[Rails Hyperdrive](https://github.com/rails-hyperdrive/rails-hyperdrive) is a development-only engine that lets *any* gem in your bundle ship agent knowledge, installed only when your Gemfile calls for it, with eight [MCP](https://modelcontextprotocol.io) (Model Context Protocol) tools on the side that answer live from your booted app. The engine ships no knowledge of its own. Instead, your bundle decides what your agent learns.\n\nIn this post, we’ll install Rails Hyperdrive, edit what it installs, watch an upgrade collide with those edits, hand the collision to an agent, and show gem authors how to plug in with nothing but markdown files and one gemspec line.\n\n## \n\nWe’ll start from a fresh `rails new` app (Rails 8.1, SQLite, the default Gemfile and nothing else) and add two gems to the development group: the engine itself and one *companion gem*, that is, an ordinary gem whose payload is agent knowledge.\n\nThen comes the init:\n\n``` bash\n$ bundle add rails-hyperdrive layered-rails-skills --group=development\n$ bin/rails hyperdrive:init\n      create  .mcp.json\n      append  .gitignore\n      append  Gemfile\n      insert  config/routes.rb\n      create  .hyperdrive/config.yml\n      create  .claude/skills/layered-rails/SKILL.md\n      [... 47 more skill files ...]\n      create  .claude/agents/layered-rails-planner.md\n      create  .claude/agents/layered-rails-reviewer.md\n      create  .claude/commands/layered-rails-analyze-callbacks.md\n      [... 7 more command files ...]\n      create  .hyperdrive/lock.yml\n\n        done  hyperdrive initialized\n  Mount: /_hyperdrive (in config/routes.rb)\n  Server: 8 MCP tools at http://localhost:3000/_hyperdrive/mcp\n  Installed 1 skill, 0 guidelines, 2 agents, 8 commands\n\n    layered-rails-skills@3.1.0\n      skill      layered-rails (+47 files)\n      agent      layered-rails-planner\n      agent      layered-rails-reviewer\n      command    layered-rails-analyze\n      [... 7 more commands ...]\n```\n\nThe engine has mounted an MCP server inside your dev server and it wrote `.mcp.json` so [Claude Code](https://claude.com/claude-code) picks it up automatically. The companion gem handled the other half: [`layered-rails-skills`](https://github.com/palkan/layered-rails-skills) installed an architecture skill (`SKILL.md` plus 47 supporting files) into `.claude/skills/`, where your agent loads it on demand.\n\nThe skill is Vladimir Dementyev’s [Layered Rails skill](https://github.com/palkan/skills), the coding-agent distillation of [Layered Design for Ruby on Rails Applications](https://www.packtpub.com/en-us/product/layered-design-for-ruby-on-rails-applications-9781806114221) which has been shipped as a companion by the skill’s author.\n\nBut that skill didn’t come alone. Here’s the whole `.claude/` tree after init:\n\n``` bash\n$ tree -L 3 .claude\n.claude\n├── agents\n│   ├── layered-rails-planner.md\n│   └── layered-rails-reviewer.md\n├── commands\n│   ├── layered-rails-analyze-callbacks.md\n│   ├── layered-rails-analyze-gods.md\n│   ├── layered-rails-analyze-services.md\n│   ├── layered-rails-analyze.md\n│   ├── layered-rails-archspec.md\n│   ├── layered-rails-plan.md\n│   ├── layered-rails-review.md\n│   └── layered-rails-spec-test.md\n└── skills\n    └── layered-rails\n        ├── examples\n        ├── references\n        ├── SKILL.md\n        └── workflows\n\n8 directories, 11 files\n```\n\nThe same init installed two sub-agents into `.claude/agents/`: `layered-rails-reviewer`, which checks code for layer violations, and `layered-rails-planner`, which drafts a phased adoption roadmap. It also dropped eight slash commands into `.claude/commands/`. Open one and you’ll find a few lines that route into a workflow file inside the installed skill:\n\n``` bash\n$ cat .claude/commands/layered-rails-review.md\n# /layered-rails-review\n\nStandalone code review from a layered architecture perspective.\n\n[... usage block omitted ...]\n\nRead and follow [`skills/layered-rails/workflows/review.md`](../skills/layered-rails/workflows/review.md), applied to the diff or file path(s) given as arguments.\n```\n\nThe knowledge lives in one place and the agents and commands are its entry points. To see what an entry point buys you, let’s give the demo app a deliberately fat `OrdersController#create` that sums line items, applies a promo code, sets the initial status, and logs large orders, all inline. And we’ll run the review command on it:\n\n``` bash\n$ claude -p \"/layered-rails-review app/controllers/orders_controller.rb\"\n## Layered Rails Review\n\n### Files Reviewed\n- `app/controllers/orders_controller.rb` (Presentation)\n- Context read: `app/models/order.rb`, `app/models/line_item.rb` (Domain), `app/mailers/order_mailer.rb`, `app/views/orders/*`, `db/schema.rb`, `test/models/*`\n\n### Layer Analysis\n- **Layers touched:** Presentation → Domain (direct), Presentation → Infrastructure (mailer)\n- **Data flow:** Violation detected — pricing/discount/status rules live in the Presentation layer\n[... 2 more lines omitted ...]\n\n### Findings\n\n🔴 **Critical: Business logic in controller (domain calculation)**\nLocation: `app/controllers/orders_controller.rb:13-15`\n[... the three quoted controller lines omitted ...]\n**Problem:** Three domain rules — how a total is computed, what \"SPRING10\" means (10% off, minimum $100), and what state a new order starts in — are encoded in an HTTP handler. `Order` knows none of them. Any second creation path (admin console, API controller, Rails console, import job, a future `Order.create!` in a test) produces an order with `total_cents = nil` and `status = nil`. The schema permits exactly that: both columns are nullable with no default and no validation.\n\n**Fix:** Move the rules to `Order`; the controller keeps params extraction and response.\n[... fix code, 2 warnings, and 3 suggestions omitted ...]\n\n### Summary\n[... \"Good\" list omitted ...]\n**Needs Attention:**\n1. 🔴 Total, discount, and initial status are domain rules stranded in `create` — move to `Order`\n2. ⚠️ `Order` is anemic and untested as a result\n3. ⚠️ Nil `price_cents`/` quantity` raises `NoMethodError` before validation runs\n4. 💡 Promo catalog → value object; `large?` → model predicate\n```\n\nOne fat action in, a prioritized findings list out, produced by the workflow the command points at. That’s what the artifacts are for; the skill holds the knowledge, and the commands and agents put it to work.\n\nThat’s three of the four *artifact* kinds a companion can install. The fourth, guidelines, showed up in the init summary as a zero; we’ll meet a real one later. These kinds differ in exactly one dimension, namely, who activates the content, and when:\n\n| Kind | Who activates it (and when) | Install destination | \n|---|---|---|\n| Skill | Lazy: the agent loads it when the task matches its description—costs nothing until then | `.claude/skills/` | \n| Agent | Delegated: a specialist takes a bounded job off the main session’s hands | `.claude/agents/` | \n| Command | Invoked: you type `/layered-rails-analyze` , that workflow runs now | `.claude/commands/` | \n| Guideline | Eager: short declarative rules in the agent’s context at all times | `CLAUDE.md` , via`@` -import | \n\nEverything a companion installs shows up in `git status` and nothing ends up *gitignored* except the engine’s own cache for the discover command we’ll meet later. The `.hyperdrive/` directory holds the engine’s two bookkeeping files, a settings file that’s yours and a lock that’s the engine’s; we’ll open both when an upgrade forces the question. Let’s look at what we got:\n\n``` bash\n$ git status --short\n M .gitignore\n M Gemfile\n M config/routes.rb\n?? .claude/\n?? .hyperdrive/\n?? .mcp.json\n```\n\nThe MCP server is live the moment you boot `bin/rails server`. The agent’s tools answer from the running process; they ask the router instead of grepping `routes.rb` and read the live database schema instead of trusting `db/schema.rb`. Here’s the tool list straight from the endpoint:\n\n``` bash\n$ curl -s http://localhost:3000/_hyperdrive/mcp \\\n    -H 'Content-Type: application/json' -H 'Accept: application/json' \\\n    -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}' | jq -r '.result.tools[].name'\ndescribe_app\nrun_ruby\nrun_sql\ntail_logs\nlist_models\nlocate_source\nlookup_doc\nlist_routes\n```\n\nEither half is skippable: `hyperdrive:init --skip-content` sets up the MCP server without any content, and `--skip-mcp` does the reverse.\n\nBut live introspection is table stakes. The other half is the reason this project exists.\n\n## \n\nPackage managers for agent knowledge already exist. [`npx skills`](https://github.com/vercel-labs/skills) installs any GitHub repo with a `SKILL.md` in it into your agent’s skills directory and [Claude Code plugin marketplaces](https://code.claude.com/docs/en/plugin-marketplaces) distribute versioned catalogs of skills and commands. Both leave you as the resolver: you find the knowledge, judge whether it fits your stack, and install it, whether or not you run the library the advice is about.\n\n[Laravel Boost](https://github.com/laravel/boost) goes further (it reads your `composer.json` and installs matching guidelines and skills) but the knowledge for the ecosystem’s major packages lives inside Boost itself, one curated repo maintained by the Laravel team, with per-version variants for the stack Laravel blesses: [Livewire](https://livewire.laravel.com), [Inertia](https://inertiajs.com), [Pest](https://pestphp.com).\n\nThat works because Laravel apps overwhelmingly run those defaults. Rails blesses defaults too ([omakase](https://dhh.dk/2012/rails-is-omakase.html), famously does this). But the same essay grants that “substitutions are allowed, within reason”, and the community exercises that right constantly. [RSpec](https://rspec.info) or [Minitest](https://github.com/minitest/minitest). [Sidekiq](https://sidekiq.org) or [Solid Queue](https://github.com/rails/solid_queue) or [GoodJob](https://github.com/bensheldon/good_job). [Hotwire](https://hotwired.dev) or [Inertia](https://inertiajs.com).\n\nEvery mature Rails app is a different intersection of choices, which means a single guideline pack would be wrong for most apps most of the time. An agent told about Minitest conventions in an RSpec codebase is actively worse than one given no guidance at all.\n\nSo Rails Hyperdrive ships **zero content**: no skills or guidelines go in your agent’s context window by default. What it ships is a contract. This means any gem can carry agent knowledge, declare what stack it targets, and the engine installs the intersection. Your bundle decides what your agent learns. This division of labor is convention over configuration, applied to agent knowledge. A companion gem with no manifest at all installs its content universally; a gem-root `hyperdrive.yml` manifest exists only to declare where your content deviates from “applies everywhere”. Every key is optional; you configure the exception, not the rule.\n\n## \n\nThe layered-rails-skills companion shows why gating (installing a file only when the app’s bundle calls for it) earns its place. The skill ships reference manuals for nine specific gems: [Alba](https://github.com/okuramasafumi/alba), [Action Policy](https://github.com/palkan/action_policy), [ViewComponent](https://viewcomponent.org), and six more. The companion’s rule: advice that helps you *pick* a gem installs everywhere, but a reference manual for a gem you don’t bundle is dead weight and dangling links. So the nine manuals are gated, and our bare demo app got none of them: of the skill’s 57 markdown files (some 14,700 lines of guidance) exactly 48 hit.\n\nAdd two of those nine gems:\n\n``` bash\n$ bundle add alba action_policy\n[... Bundler output omitted ...]\n$ bundle install\n[... Bundler output omitted ...]\n[hyperdrive] installed 2 artifact(s):\n  .claude/skills/layered-rails/references/gems/action-policy.md\n  .claude/skills/layered-rails/references/gems/alba.md\n[hyperdrive] 1 artifact(s) need attention — run bin/rails hyperdrive:sync\n  .claude/skills/layered-rails/SKILL.md (layered-rails-skills@3.1.0 → layered-rails-skills@3.1.0)\n```\n\nThe manuals arrived during `bundle install` itself, courtesy of a [Bundler](https://bundler.io) plugin that `hyperdrive:init` registered in the Gemfile. That’s why the transcript runs both commands: Bundler activates a newly declared plugin only on `bundle install`, so this first `bundle add` ran without it; from here on, `bundle add` triggers it too.\n\nThe plugin is additive-only and quiet by contract: it never rewrites an installed file and never fails your bundle. Anything beyond adding new files, it defers. Specifically, that’s the “need attention” line, and it’s why the arrow shows the same version on both sides: the gem didn’t move, your bundle did. Run the sync, and the skill’s entry file re-renders its gem-reference table:\n\n``` bash\n$ bin/rails hyperdrive:sync\n       force  .claude/skills/layered-rails/SKILL.md\n   [... 59 \"unchanged\" lines, lock update, and summary omitted ...]\n$ git diff .claude/skills/layered-rails/SKILL.md\n@@ -194,6 +194,8 @@ For library-specific guidance:\n\n | Gem | Purpose | Reference |\n |-----|---------|-----------|\n+| action_policy | Authorization framework | [action-policy.md](references/gems/action-policy.md) |\n+| alba | JSON serialization | [alba.md](references/gems/alba.md) |\n | archspec | Enforce layer boundaries in CI (reference `Archspec.rb` config) | [archspec.md](references/gems/archspec.md) |\n```\n\nTwo rows joined a table that already existed (the [`archspec`](https://github.com/crmne/archspec) row is ungated and was there from init) and each row lives only as long as its gem stays in the bundle. Now, `bundle remove alba` and sync again:\n\n``` bash\n$ bin/rails hyperdrive:sync\n       force  .claude/skills/layered-rails/SKILL.md\n      remove  .claude/skills/layered-rails/references/gems/alba.md\n      [... lock update and summary omitted ...]\n```\n\nThat `force` on `SKILL.md` is safe by construction. Rails Hyperdrive’s lock file (more on it shortly) records a content hash showing the file unedited, and a plain sync only overwrites files that still match what it installed. The manual and its table row are both gone, and no link in the skill dangles: your agent’s knowledge tracks your Gemfile in both directions.\n\n## \n\nInstalled skills are plain files in your repo, and you *will* edit them (that’s the point of having them locally). This raises the question every dotfile manager, Rails generator, and config framework eventually faces: *what happens when upstream ships a new version of a file you’ve changed?*\n\nTo show the upgrade story we need an upstream that moves, so let’s write a toy companion gem. Here’s its entire content, gemspec aside:\n\n```\nrails-hyperdrive-sidekiq/\n├── hyperdrive.yml\n├── skills/sidekiq-idempotency/SKILL.md   # or lib/<gem>/hyperdrive/skills/\n└── lib/rails-hyperdrive-sidekiq/hyperdrive/guidelines/jobs-sidekiq.md\n# hyperdrive.yml\ngem: sidekiq\n```\n\nThe whole gem is two markdown files and a one-line manifest saying “only for apps that bundle Sidekiq.” The top-level `gem:` gates every artifact the gem ships; per-artifact keys exist only to deviate from it. The second file is our first *guideline*, the eager kind from the taxonomy, which layered-rails-skills didn’t ship. Skills sit at the gem root, where skills.sh users and plain git clones can see them; guidelines live at Rails Hyperdrive’s conventional path under `lib/`.\n\nWe point the demo app’s Gemfile at the toy gem and sync; the skill installs, and the guideline gets wired in through a single import chain; the `@` prefix is Claude Code’s import syntax:\n\n``` bash\n$ cat CLAUDE.md\n<!-- AI instructions for this project. Managed content lives in .claude/hyperdrive/. -->\n\n@.claude/hyperdrive/index.md\n\n$ cat .claude/hyperdrive/index.md\n@guidelines/jobs-sidekiq.md\n```\n\nEager context isn’t free, and the sync says exactly what it costs:\n\n```\n       eager  1 guideline(s), ~233 tokens always in context\n```\n\nNow, let’s return to the question this section opened with. Say, we add an app-specific warning to the installed skill (something like “payment jobs in this app must check `Payment#processed_at` before charging”) and upstream ships v0.2.0 with a new section on testing. The Bundler plugin spots the collision:\n\n``` bash\n$ bundle install\nUsing rails-hyperdrive-sidekiq 0.2.0 (was 0.1.0) from source at `../gems/rails-hyperdrive-sidekiq-0.2.0`\n[hyperdrive] 1 artifact(s) need attention — run bin/rails hyperdrive:sync\n  .claude/skills/sidekiq-idempotency/SKILL.md (rails-hyperdrive-sidekiq@0.1.0 → rails-hyperdrive-sidekiq@0.2.0)\n```\n\nA plain sync refuses to touch your work. That’s the default behavior:\n\n``` bash\n$ bin/rails hyperdrive:sync\n        skip  .claude/skills/sidekiq-idempotency/SKILL.md (locally modified; run hyperdrive:sync with --merge, --sidecar, or --overwrite to reconcile)\n```\n\nYou pick the reconciliation strategy explicitly. `--merge` runs a git three-way merge between your file, the previously installed version, and the new upstream:\n\n``` bash\n$ bin/rails hyperdrive:sync --merge\n   [... 49 \"unchanged\" lines omitted ...]\n       force  .claude/skills/sidekiq-idempotency/SKILL.md\n      merged  .claude/skills/sidekiq-idempotency/SKILL.md (local edits merged with rails-hyperdrive-sidekiq@0.2.0)\n   [... 12 \"unchanged\" lines, lock update, and summary omitted ...]\n  Merged 1 file by three-way merge; a clean merge is textually non-overlapping, not verified; review with `git diff`\n```\n\nThe merged file carries both the payment warning and the new testing section, no conflict markers. Mind the footer, though: clean means textually non-overlapping, not semantically right (Git merges two contradictory edits without complaint when unchanged lines separate them) so `git diff` is the review step, not a formality.\n\nAnd merges don’t always land even that cleanly. When edits collide, or when the previously installed gem version is gone from disk (a three-way merge needs it as the base), the same command never writes a half-merged file. It falls back to sidecar delivery and names the reason:\n\n```\n     sidecar  .claude/skills/sidekiq-idempotency/SKILL.md (locally modified; new upstream delivered to .claude/skills/sidekiq-idempotency/SKILL.md.new; conflicting edits)\n```\n\nYou can also ask for that outcome directly with `--sidecar`. Your file stays untouched; the full new upstream lands next to it as `SKILL.md.new`, inert to the agent, visible in `git status`:\n\n``` bash\n$ diff .claude/skills/sidekiq-idempotency/SKILL.md .claude/skills/sidekiq-idempotency/SKILL.md.new\n25,28d24\n< > **In this app:** payment jobs must additionally check\n< > `Payment#processed_at` before charging — the gateway ledger is\n< > reconciled nightly and double charges are expensive to unwind.\n<\n67a64,78\n>\n> ## Testing for idempotency\n[...]\n```\n\nThe pair is now yours to reconcile (or an agent’s task). An agent has both full texts, so it merges them by meaning rather than by line; a prompt naming the two paths is all it takes. There’s also a way to automate that handoff. `--resolve` works like `git mergetool`: after delivery, each unresolved `.new` goes to a command you name in `.hyperdrive/config.yml`, the settings file init created next to the lock:\n\n```\n# .hyperdrive/config.yml\nresolve:\n  command: claude -p $PROMPT --allowedTools Read,Edit,Write\n```\n\nTake the collision that defeated git—upstream inserting a blockquote of its own right where our warning sits. `--resolve` delivers the sidecar and hands it straight to the command:\n\n``` bash\n$ bin/rails hyperdrive:sync --resolve\n   [... 49 \"unchanged\" lines omitted ...]\n      create  .claude/skills/sidekiq-idempotency/SKILL.md.new\n     sidecar  .claude/skills/sidekiq-idempotency/SKILL.md (locally modified; new upstream delivered to .claude/skills/sidekiq-idempotency/SKILL.md.new)\n   [... 12 \"unchanged\" lines, lock update, and eager line omitted ...]\n     resolve  .claude/skills/sidekiq-idempotency/SKILL.md via claude\n      remove  .claude/skills/sidekiq-idempotency/SKILL.md.new\n    resolved  .claude/skills/sidekiq-idempotency/SKILL.md\n   [... summary omitted ...]\n  Sidecars: 1 resolved\n```\n\nHere’s what Claude wrote, from the first hunk of the `git diff`. The two blockquotes that wanted the same spot now sit one under the other, upstream’s first; the second hunk is the new testing section at the bottom of the file.\n\n``` bash\n$ git diff .claude/skills/sidekiq-idempotency/SKILL.md\n@@ -22,6 +22,10 @@ state whether it runs once or five times.\n    change (or vanish) between enqueue and perform. Reload and bail out early\n    if the work is already done.\n\n+> **Retry caps:** jobs with external side effects should set\n+> `sidekiq_options retry: 5` and surface the terminal failure; 25 silent\n+> retries against a payment gateway is an incident, not resilience.\n+\n > **In this app:** payment jobs must additionally check\n > `Payment#processed_at` before charging — the gateway ledger is\n > reconciled nightly and double charges are expensive to unwind.\n[... second hunk omitted ...]\n```\n\nThe flags combine too: `--merge --resolve` lets Git take what it merges cleanly and sends the command only what it couldn’t.\n\nThat `$PROMPT` stands for [the prompt the agent runs with](https://github.com/rails-hyperdrive/rails-hyperdrive/blob/v0.9.1/lib/rails/hyperdrive/resolve/prompt.md.erb), rendered per sidecar from an ERB template that ships with the gem. To rewrite it, define your own:\n\n```\n# .hyperdrive/config.yml\nresolve:\n  command: claude -p $PROMPT --allowedTools Read,Edit,Write\n  prompt: .hyperdrive/resolve-prompt.md.erb\n```\n\nInside the template, the sidecar’s paths and provenance are available as methods, e.g. `<%= local %>` and `<%= remote %>`. The command line takes the same values as `$` placeholders, which is how you’d wire up a tool that expects file paths instead of a prompt:\n\n| In `command:` | In the template | Value | \n|---|---|---|\n| `$LOCAL` | `local` | Your file, with your edits | \n| `$REMOTE` | `remote` | The sidecar: the new upstream | \n| `$BASE` | `base` | The upstream your edits were made on, when that gem version is still on disk (the lock remembers which one, even while a sidecar waits); `nil` otherwise | \n| `$MERGED` | `merged` | Your file again: the path the tool must write | \n| `$SOURCE` | `source` | `<gem>@<version>` of the new upstream | \n| `$PREVIOUS_SOURCE` | `previous_source` | `<gem>@<version>` your copy is based on | \n| `$KIND` | `kind` | `skill` ,`guideline` ,`agent` ,`command` , or`skill_support` | \n| `$PROMPT` | — | The rendered template | \n\nThe `$MERGED` row is the contract. A sidecar is deleted only when the command exits 0 *and* that file changed, which is Git’s own `mergetool` default (`trustExitCode = false`). An agent that was denied every write, or that judged the merge beyond it, leaves the file untouched and gets reported as `unresolved … (command exited 0 but wrote nothing)`; that, like any other exit, leaves the live file, the sidecar, and the lock exactly as they were, and the next `--resolve` hands the sidecar off again. The engine runs nothing you didn’t name, and never during `bundle install`. Either way you end up where `--merge` left you: with a `git diff` to review.\n\nHowever a sidecar gets resolved, the lock records each upstream delivery exactly once, so nothing gets re-offered, and the file keeps showing as locally modified on future syncs—accurate, because you own it now.\n\nThe third strategy, `--overwrite`, discards your edits and restores gem-shipped content. Rails Hyperdrive tracks all of this through `.hyperdrive/lock.yml`, a git-tracked ledger—here trimmed to one delivered file:\n\n```\nversion: 3\nfiles:\n- path: \".claude/skills/sidekiq-idempotency/SKILL.md\"\n  artifact: skill\n  source: rails-hyperdrive-sidekiq@0.2.0\n  source_sha: 5efe4771dc42ace62920fb67a6335294328f5d2c0b2e84efeb6d5b14f160c498\n  installed_at: '2026-09-05T23:02:12Z'\n```\n\n`source` pins which gem delivered the file and at what version; `source_sha` is the hash of the delivered content. Comparing that hash against the file on disk is how a sync tells “unedited, safe to upgrade” from “edited, hands off” without asking you a single interactive question.\n\nThe lock holds state, and it’s the engine’s to write. Your choices live in the other file, the `config.yml` we put the resolver in; the relationship is `Gemfile` to `Gemfile.lock`. Init creates it once, with empty sections, and no hyperdrive command writes to it again.\n\nEditing is one form of ownership; rejecting is another. The `disabled:` lists in that file permanently opt out any artifact you don’t want, per kind—a slash command that clashes with one of yours, say. Their mirror, `enabled:`, opts a gem in: add a gem’s name and its skills install **even if the gem has no native Hyperdrive support**, as long as it ships them in a `skills/` directory. Bundle such a gem (its author may never have heard of Rails Hyperdrive) and hyperdrive prints a notice: it’s one line away from full adoption, its skills syncing and upgrading like everything else.\n\nAnd deletions get the same respect as edits: remove a guideline’s line from `.claude/hyperdrive/index.md`, or the import line from `CLAUDE.md` itself, and no future sync adds it back.\n\n## \n\nCompanion gems are ordinary gems on [RubyGems](https://rubygems.org), discoverable by a metadata declaration rather than a naming scheme or a separate registry. `hyperdrive:discover` asks the RubyGems search API for gems declaring the `hyperdrive_targets` metadata key, then intersects their declared targets with your lockfile:\n\n``` bash\n$ bin/rails hyperdrive:discover\n\nFound gems with rails-hyperdrive content for your stack:\n  ✓ railties 8.1.3.1   → layered-rails-skills 3.1.0  (installed)\n  ! railties 8.1.3.1   → require-profiler 0.3.1 — ships skill  (suggested)\n\nRun: bundle add require-profiler --group=development\nThen: bin/rails hyperdrive:init\n```\n\nEach line reads left to right: a gem in your lockfile, and the companion that declares it as a target. The `suggested` line is the point: [require-profiler](https://rubygems.org/gems/require-profiler), Vladimir Dementyev’s require-time profiler ([announced on this blog](https://evilmartians.com/chronicles/get-in-human-cut-rails-boot-time-with-require-profiler-and-this-guide)), isn’t a dedicated companion at all, but a regular library gem that shipped a skill and declared `hyperdrive_targets` in its gemspec metadata. This declaration alone made it discoverable to every app running the command above, with no separate gem or registry involved.\n\n## \n\nWhich brings us to you, gem authors: ship markdown, declare what it targets, and publish. That’s the whole contract, and the [companion gem docs](https://github.com/rails-hyperdrive/rails-hyperdrive/blob/main/docs/COMPANION_GEMS.md) hold all of it. The minimal recipe is a `skills/` directory plus one gemspec metadata key, `hyperdrive_targets`, the declaration that makes your gem discoverable. The `hyperdrive.yml` manifest is optional and exists only to declare deviations (gating, a custom directory layout, a command prefix).\n\nThere are three routes in:\n\n1. Your library gem ships a top-level `skills/` directory itself (the way require-profiler does): one gem, one source of truth.\n2. An existing skill repo packages itself as a gem ([the way layered-rails-skills does](https://github.com/palkan/layered-rails-skills/tree/v3.1.0) ): standalone guidance (say, an architecture skill) not tied to any specific library.\n3. Anyone publishes a dedicated companion for a library that ships nothing (the way our toy sidekiq gem did): third-party guidance, no upstream buy-in needed.\n\nAnd if two routes ever collide (like if a library grows its own skill after a third party already shipped one), same-named artifacts from different gems both install, each renamed with its source gem as a postfix, so no gem silently overwrites another.\n\n`SKILL.md` follows the [Agent Skills](https://agentskills.io/specification) contract used by [skills.sh](https://skills.sh) and [Claude Code](https://claude.com/claude-code)—frontmatter with just `name` and `description`. A plain skills.sh skill installs with zero warnings and zero modifications, because all Rails-specific behavior lives outside the content, in the manifest.\n\nAnd the compatibility runs both ways: your skill stays useful to people who never touch Rails Hyperdrive, through skills.sh or a plain git clone of your repo.\n\nThis parity extends to behaviors. layered-rails-skills doubles as a [Claude Code plugin](https://code.claude.com/docs/en/plugins), and the one real difference between the two homes is command naming: `/layered-rails:analyze` as a plugin, `/layered-rails-analyze` as flat files in `.claude/commands/`. A single manifest line (` command_prefix: layered-rails`) and one ERB helper (` canonical_render?`) absorb that difference; everything else, cross-references between skills, agents, and commands included, survives the trip untouched.\n\nAll the gating power lives in the manifest. Here’s how layered-rails-skills gates its nine gem manuals:\n\n```\ngem: railties\nskills:\n  layered-rails:\n    conditional:\n      references/gems/alba.md:          { gem: \"alba\" }\n      references/gems/action-policy.md: { gem: \"action_policy\" }\n      references/gems/workflow.md:      { gems: [workflow, workflow-activerecord] }\n      # ...six more gated manuals\n```\n\nPer-file conditions are the start:\n\n- Version requirements ride on the target they constrain: `gems: [sidekiq: \">= 7\"]` .\n- Multi-target gating covers both cases: `gems: [sidekiq, solid_queue]` installs when either is bundled;`gems: { all: [sidekiq, sidekiq-cron] }` requires the whole set.\n- A manifest can fence on the engine itself: `hyperdrive_version: \">= 0.8\"` skips an artifact the running Rails Hyperdrive is too old to honor and prints an upgrade hint.\n\nlayered-rails-skills declares exactly that last fence, gem-wide: its commands are ERB templates, and an engine too old to render them should skip them rather than install them raw.\n\nFor the cases YAML can’t express, ERB templates render at install time against the app’s bundle, with exactly five helpers:\n\n| Helper | Answers | \n|---|---|\n| `gem?(\"alba\")` | is this gem in the bundle (optionally, at a version)? | \n| `any_gem?(\"workflow\", \"workflow-activerecord\")` | is at least one of these? | \n| `all_gems?(\"sidekiq\", \"sidekiq-cron\")` | are all of these? | \n| `gem_version(\"railties\")` | what version resolved? | \n| `canonical_render?` | rendering the gem’s static copy, or installing into an app? | \n\nHere’s a real example, from layered-rails-skills’ skill template—the gem-reference table trimming itself to your bundle:\n\n```\n<%- gem_refs = [\n      [\"action_policy\", \"Authorization framework\", \"action-policy.md\", [\"action_policy\"]],\n      [\"view_component\", \"Component framework\", \"view-component.md\", [\"view_component\"]],\n      [... 7 more rows ...]\n    ].select { |row| any_gem?(*row[3]) } -%>\n| Gem | Purpose | Reference |\n|-----|---------|-----------|\n<%- gem_refs.each do |label, purpose, file, _targets| -%>\n| <%= label %> | <%= purpose %> | [<%= file %>](references/gems/<%= file %>) |\n<%- end -%>\n```\n\nAnd because gating lives in the manifest, not the content, it adds nothing to the agent’s context—gate as extensively as you like.\n\nA templated skill still ships a pre-rendered static copy for consumers who never run Rails Hyperdrive—skills.sh users, plain git clones. `rake hyperdrive:skills:render` regenerates that static copy from its ERB master, `hyperdrive:skills:check` fails CI when the two drift apart, and `hyperdrive:manifest:check` lints the manifest strictly, so a gating typo surfaces in your CI rather than as a warning in someone’s app.\n\nIf you maintain a Rails gem, this is the ask: your gem already ships code for machines and a README for humans. Shipping the agent-facing distillation of that README is an afternoon with the [contract docs](https://github.com/rails-hyperdrive/rails-hyperdrive/blob/main/docs/COMPANION_GEMS.md), and it makes your gem the one agents use correctly on the first try.\n\n## \n\nHonesty section, because the project is pre-1.0 so you should know where the edges are.\n\n**It speaks Claude Code today.** The install targets `.mcp.json`, `CLAUDE.md`, and the `.claude/` tree. The MCP server works with any MCP client, but content installation for other agents ([`AGENTS.md`](https://agents.md), [Cursor](https://cursor.com) rules) hasn’t shipped.\n\n**The contract is young, and breaking changes might be on the way.** Pre-1.0 means pre-1.0: expect the contract to keep sharpening before it settles.\n\n**The ecosystem is a handful of gems. So far.** Run `hyperdrive:discover` today and the list of suggestions is short. Growing it is exactly what the previous section is for.\n\n**It’s not a sandbox.** The engine 403s outside development, checks request origins, and guards every tool—but as the [security policy](https://github.com/rails-hyperdrive/rails-hyperdrive/blob/main/SECURITY.md) puts it, the read-only SQL gate exists to keep a confused AI from running `DELETE FROM users` by accident, not to enforce a privilege boundary. `run_ruby` is eval in your dev process, on purpose. The engine is development-only by design.\n\n## \n\n### \n\nYes, Rails Hyperdrive is a development-only Rails engine that gives AI coding agents an MCP server inside your dev process and installs agent knowledge from your gems. The difference in philosophy: Boost resolves your `composer.json` against a curated pack maintained inside Boost itself; Rails Hyperdrive ships zero content of its own—every skill and guideline arrives from the gems in your bundle, gated and versioned per artifact. Rails ships nothing like it today, though the idea has [come up on the Rails forum](https://discuss.rubyonrails.org/t/feature-proposal-activeboost/91112).\n\n### \n\n`bin/rails hyperdrive:init` mounts an MCP server at `/_hyperdrive` in development and writes `.mcp.json`, which Claude Code picks up automatically. With `bin/rails server` running, the agent gets MCP tools answering from the booted process—routes, schema, logs, source lookup, and a read-only SQL gate among them.\n\n### \n\nAdd a top-level `skills/` directory and declare the `hyperdrive_targets` key in your gemspec metadata—that’s the minimal recipe, and it makes your gem discoverable to `hyperdrive:discover`. A `hyperdrive.yml` manifest is needed only when your content deviates from the defaults: gating, custom directories, a command prefix.\n\n### \n\nNothing, by default: a plain `hyperdrive:sync` never overwrites a file you’ve modified. You pick the reconciliation explicitly—`--merge` (git three-way merge), `--sidecar` (the new version lands next to yours as `.new`), `--resolve` (each `.new` is handed to an agent command you configure, `git mergetool` style), or `--overwrite` (the only one that loses work).\n\n## \n\nHere’s our prediction: within a couple of years, “*does it ship agent skills?*” will join “*does it have docs?*” and “*is it maintained?*” on the checklist every Rails developer runs before adding a gem. Agents already write a lot of the code that calls your library; a gem that explains itself to agents gets used correctly, recommended by the tools themselves, and kept in the Gemfile. One that doesn’t collects hallucinated APIs and frustrated issue reports.\n\nRails spent twenty years proving that strong defaults and substitutions can coexist. Agent knowledge deserves the same deal: a contract any gem can join.\n\nEvil Martians has shipped [open source](https://evilmartians.com/opensource) the Rails community runs in production for over a decade ([AnyCable](https://anycable.io) and [TestProf](https://test-prof.evilmartians.io) among them) and Rails Hyperdrive is our wager on what a gem ships next, an that explanation agents can act on.\n\nThe whole contract fits in [one doc page](https://github.com/rails-hyperdrive/rails-hyperdrive/blob/main/docs/COMPANION_GEMS.md). Bring your gem!", "url": "https://wpnews.pro/news/rails-hyperdrive-supercharged-agentic-development-for-rails", "canonical_source": "https://evilmartians.com/chronicles/rails-hyperdrive-supercharged-agentic-development-for-rails", "published_at": "2026-09-15 00:00:00+00:00", "updated_at": "2026-09-15 17:21:52.067665+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools", "ai-products"], "entities": ["Evil Martians", "Rails Hyperdrive", "Rails Foundation", "Claude Opus 5", "Claude Fable 5.1", "Model Context Protocol", "layered-rails-skills", "Vladimir Dementyev"], "alternates": {"html": "https://wpnews.pro/news/rails-hyperdrive-supercharged-agentic-development-for-rails", "markdown": "https://wpnews.pro/news/rails-hyperdrive-supercharged-agentic-development-for-rails.md", "text": "https://wpnews.pro/news/rails-hyperdrive-supercharged-agentic-development-for-rails.txt", "jsonld": "https://wpnews.pro/news/rails-hyperdrive-supercharged-agentic-development-for-rails.jsonld"}}