LaunchKit

AGENTS.md for a Rails app

September 24, 2026

An agent opening your Rails app for the first time can infer a great deal and guess wrong about the rest. ApplicationController is obvious. Whether your team puts a three-step Stripe flow in a service object, a form object or a fat controller is not, and neither is the command that has to be green before you will look at the diff. AGENTS.md is where that goes.

The format is framework-agnostic by design, which is why the query people actually type is rails agents md rather than agents md: the spec tells you the file exists and says nothing about what a Rails codebase owes it.

The format, and who is behind it

AGENTS.md came out of work by the teams behind OpenAI Codex, Amp, Google's Jules, Cursor and Factory, and is now stewarded by the Agentic AI Foundation, a Linux Foundation project. The site at agents.md calls it "a simple, open format for guiding coding agents" and claims adoption by more than 25 agents and IDEs and over 60,000 open-source projects. Take the second number as the project's own, since nobody else counts it.

What the specification requires is the interesting part: nothing. There are no required fields, no front matter and no schema. It is standard Markdown, placed at the repository root, and agents read whatever headings you wrote. That is a real design decision rather than an unfinished one, and it has a consequence people discover late. A format with no schema cannot be validated, so a stale AGENTS.md fails the way a stale comment fails, silently and for months.

Monorepos get nested files, one per subpackage, and the rule the site states is that the closest AGENTS.md to the edited file wins. Every tool below implements that sentence slightly differently.

Three tools, three loading rules

Reading the three implementations side by side is worth twenty minutes, because the differences decide whether your file is read at all.

The same root AGENTS.md read by three tools. Codex concatenates one file per directory from the root down and stops once the chain reaches 32 KiB. Claude Code branches: with no CLAUDE.md anywhere the AGENTS.md is read in full, but any CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md from the root down hands the project back and the AGENTS.md is never read. Cursor reads it automatically as a plain-Markdown alternative to .cursor/rules.

Codex builds an instruction chain once per run. It reads AGENTS.override.md from ~/.codex if present and otherwise ~/.codex/AGENTS.md, then walks from the project root down to your working directory taking at most one file per directory, and concatenates them root first. Files nearer the working directory therefore land later in the prompt and override what came before. The cap is project_doc_max_bytes, 32 KiB by default, and Codex stops adding files once the combined size reaches it. A root file that fills the budget silences the nested ones underneath it.

Claude Code ships AGENTS.md support as a mod whose default mode is claude-md-or-agents-md: a project with no instruction files of its own gets its AGENTS.md files instead, loaded exactly where and how CLAUDE.md would be. A CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md in any directory from the root down to the working directory hands the whole project back to the engine and the mod stays out. Two other modes exist, claude-md-and-agents-md and claude-md, if you want both files or neither. One detail is easy to miss: nested AGENTS.md files attach on a text Read only, which is narrower than the engine's own handling of CLAUDE.md.

Cursor documents AGENTS.md as a plain-Markdown alternative to the .cursor/rules directory, without the front matter an .mdc rule carries, and applies a subdirectory's file automatically to work on files in that directory or below.

The practical reading: write one root file, assume it is the only one anybody loads, and treat nested files as an optimisation you add when the root file is too big. What to put in .cursor/rules that does not belong in AGENTS.md and Claude Code in a Rails codebase take the two tools one level further down.

What Rails does not imply

Convention over configuration means a large share of a Rails app needs no explanation to anybody, human or otherwise. app/models/user.rb holds User, db/schema.rb is generated, the request goes through a controller. None of that belongs in your file.

What belongs is every decision your team made that Rails permits but does not choose. Reading LaunchKit, the Rails 8.1 boilerplate this site sells, the list is short and none of it is guessable:

  • app/forms/ holds form objects, and multi-field input plus validation plus the write live there rather than in the controller. Onboarding::ProfileForm is the pattern to copy.
  • app/services/ holds one-job service objects namespaced by domain, so a new Stripe step is a Billing::* class next to Billing::CreateCheckoutSession, not a private controller method.
  • Every user-facing string goes through config/locales/en.yml and is rendered with t(...). A literal string in an .erb is a defect, and an agent writing idiomatic Rails will produce one on its first try.
  • Optional modules are gated on Feature.enabled?(:ai), defined in app/models/feature.rb, and a new toggleable module has to guard its routes, its nav and its views or it half-disappears.
  • Secrets are read through AppConfig in config/app_config.rb, never ENV scattered in code.

Each of those is a rule an agent will violate while writing perfectly reasonable Rails, which is the test for whether a line belongs in the file at all. Conventions are the context argues the general case: the thing you are selling an agent is a codebase it does not have to guess in.

The commands, read off bin/ rather than remembered

The single highest-value section of an AGENTS.md is the one that says how to check the work, and it is the section most often wrong, because people write it from memory.

In LaunchKit, bundle exec rspec runs the specs and bin/ci runs the gate. They are not the same thing, and config/ci.rb says so in seven lines:

CI.run do
  step "Setup", "bin/setup --skip-server"
  step "Style: Ruby", "bin/rubocop"
  step "Security: Gem audit", "bin/bundler-audit"
  step "Security: Importmap vulnerability audit", "bin/importmap audit"
  step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
  step "Autoloading: Zeitwerk check", "bin/rails zeitwerk:check"
  step "Tests: Prepare database", "bin/rails db:test:prepare"
  step "Tests: RSpec", "bundle exec rspec"
end

An agent told "run the tests" runs RSpec, reports green and has skipped five gates, one of which is zeitwerk:check. That one matters more than it sounds: a misnamed constant in a new service object loads fine in development and takes the app down on boot in production, and RSpec will not notice.

The same applies to setup. bin/setup installs gems, enables the versioned pre-commit hook, bootstraps per-environment credentials and prepares the database, and the hook it installs then runs RuboCop, Zeitwerk, Brakeman, bundle-audit and RSpec on every commit. An agent that does not know SKIP_HOOKS=1 git commit exists will sit there for minutes wondering why a commit is slow, and an agent that does know will reach for it far too readily, so say which one you meant. What to test when an agent wrote the code covers the part after the command is right.

What an agent may not touch

Off-limits directories are a better use of the file than most prose, because the cost of getting them wrong is high and the rule is one line each.

In a Rails app of this shape the list is db/schema.rb, which is generated by migrations; config/credentials/*.yml.enc and the master keys, which are encrypted and are written by the /admin/setup wizard or by bin/rails credentials:edit; Gemfile.lock, which is edited by bundle; and anything under storage/, tmp/ or vendor/.

Add the ones specific to your codebase. LaunchKit has two. The launchkit:* generator toolkit stays branded after bin/new renames the app, so it is not a leftover to tidy up. The documentation engine under engines/boilerplate_documentation/ needs a Markdown file and a matching entry in app/models/boilerplate_documentation/page.rb, and editing one without the other produces a page that exists and is unreachable.

The file this repository would want

The whole file, written for LaunchKit and short enough that a reader reaches the end of it, looks like this. Order matters more than completeness: commands first, because that is the part an agent needs before it can check its own work, then layout, then the rules that Rails permits and this codebase does not, then the list of files nobody edits by hand. Anything a grep would have answered in ten seconds is left out on purpose.

# AGENTS.md

Rails 8.1 SaaS boilerplate on Ruby 4.0.5. PostgreSQL is the only datastore: Solid Queue,
Solid Cache and Solid Cable all run on it. No Node and no Redis. Buyers read this code and
build on it, so clarity beats cleverness.

## Commands

    bin/setup        # gems, git hooks, per-env credentials, db, then boots
    bin/dev          # Rails plus the Tailwind watcher
    bin/doctor       # check Ruby, Postgres, the db and the model registry
    bundle exec rspec
    bin/ci           # the gate: RuboCop, bundler-audit, importmap audit, Brakeman,
                     # zeitwerk:check, db:test:prepare, RSpec. Run this before you report done.

`bin/ci` is the definition of green. A passing `rspec` is not.
A pre-commit hook runs the same checks; bypass it once with `SKIP_HOOKS=1 git commit`.

## Where code goes

- `app/controllers/` authorize, call a form or service, render. No business logic.
- `app/forms/` form objects: multi-field input, validation and the write. See `Onboarding::ProfileForm`.
- `app/services/` one job each, namespaced: `Billing::*`, `Auth::*`, `Onboarding::*`, `Referrals::*`.
- `app/models/` persistence and small domain models, namespaced (`Ai::*`, `Blog::*`, ...).

New multi-step logic is a service in the matching namespace. Copy the nearest sibling.

## Rules that are not Rails defaults

- i18n is mandatory in views. Every string via `config/locales/en.yml` and `t(...)`. No literals in `.erb`.
- Optional modules gate on `Feature.enabled?(:ai)` (`app/models/feature.rb`). Guard routes, nav and views.
- Secrets read through `AppConfig` (`config/app_config.rb`), never `ENV` in application code.
- No em-dash anywhere in copy or docs.
- Check `/dev/ui`, the kitchen sink, before hand-rolling markup.

## Tests

RSpec. Every change ships a spec. For a bug, write the failing spec first.

## Do not edit

`db/schema.rb` (migrations own it), `config/credentials/*.yml.enc` and the master keys
(`bin/rails credentials:edit --environment <env>` or `/admin/setup`), `Gemfile.lock` (bundle owns it),
`storage/`, `tmp/`, `vendor/`. The `launchkit:*` generators stay branded after `bin/new`.

## Generators

    bin/rails g launchkit:service Namespace::Name
    bin/rails g launchkit:webhook_handler event.type
    bin/rails g launchkit:feature key
    bin/rails g launchkit:admin_resource Model field:type

Roughly 60 lines. Every line is either a command, a path or a rule that Rails does not imply, and the ratio is the thing to protect as the file grows.

Where it goes wrong: a second source of truth

The failure this file is most prone to is not being wrong, it is disagreeing with the README.

LaunchKit's AGENTS.md as it stands today says local development is dockerized and that docker compose up brings up the whole stack. The README's quick start says git init, bin/new, bin/setup, against a Postgres on the host. Both work. Neither document mentions the other, and bin/ci and bin/doctor appear in the README and in neither line of the AGENTS.md.

What an agent does with that is pick one, usually the one it read most recently, and never say so. The diff looks identical either way. You find out when a spec that needed the dockerized Postgres fails on someone else's machine, or when a commit lands having never been through the Zeitwerk check.

The fix is not to write more. Name one canonical setup in AGENTS.md and let the README be the prose version of the same sequence, or write one sentence saying which document wins. This is a case where the second-best answer is fine and the absence of an answer is not.

What restating the Rails guides costs

Position first: an AGENTS.md longer than about 150 lines is usually a net loss, and the extra lines are almost always a paraphrase of the Rails guides.

The cost is concrete. Codex re-reads the chain once per run and stops adding files at 32 KiB, so a root file explaining MVC is literally crowding out the nested file that would have said something specific about app/services/. Claude Code loads it where CLAUDE.md would load, which means the start of every session. Nobody pays for it once.

The subtler cost is that a file with ten obvious lines and one important one reads as documentation, and documentation gets skimmed. A file made entirely of things the model could not have known reads as configuration.

What would change my mind: tools that load instruction files lazily rather than up front. Claude Code's nested handling already points that way, attaching a subdirectory's AGENTS.md on a text Read rather than at session start. If that becomes the norm across tools, a long file costs only when it is relevant and the length argument weakens considerably. Today it does not hold. Writing Rails an agent can read cheaply makes the same argument about the code rather than the instructions.

What this page leaves alone

Keeping CLAUDE.md and AGENTS.md in sync, which has an obvious symlink answer and a set of non-obvious problems with it, is not covered here and deserves its own page.

Nor is the question of whether the file worked. There is no metric offered above, because the only honest one is reading the diffs, and Reviewing Rails an agent wrote is where that goes. Everything here is checkable against a repository or against a tool's own documentation, and it stops there.

Keep reading

← All of Rails and agents