ArchSpec: when your AI agent doesn't know your rules, make the rules unbreakable
A thing is happening in Rails shops everywhere right now: AI coding assistants are writing large chunks of production code. Cursor. Claude. Copilot. Whatever your team has adopted, the output is real and it’s landing in your codebase at a pace that code review alone can’t fully absorb.
These tools are genuinely good at writing Ruby. They understand Rails conventions pretty well. They don’t understand your conventions – the ones that live in your CONTRIBUTING.md, your team wiki, and the collective memory of however many sprint retrospectives you’ve sat through.
ArchSpec is the most direct answer I’ve found to this problem.
The idea: executable architecture
The insight behind ArchSpec is deceptively simple. Your architecture already has rules. The rules just aren’t enforced. So enforce them.
You write your conventions in a file called Archspec.rb:
architecture :rails
controllers.cannot_use :helpers, :mailers, :jobs
models.cannot_use :controllers
services.cannot_call :render, :redirect_to
jobs.cannot_reference_constants "Current"
Then you add bundle exec archspec check to your CI pipeline. A PR that violates those rules fails. It doesn’t matter who wrote the code – a senior engineer, a new hire, or a model with no memory of your last architecture discussion.
That last part is the point.
What you can actually enforce
The built-in architecture :rails preset handles the most common Rails boundary violations: models reaching into controllers, services calling render, that kind of thing. The custom component system is the interesting part:
component :queries, in: "app/queries/**/*.rb"
queries.cannot_call :save!, :update!, :destroy!
component :commands, in: "app/commands/**/*.rb"
commands.must_implement :call
component :services, in: "app/services/**/*.rb"
services.must_be_empty because: "rich model"
You’re not limited to Rails defaults. If your app has a command pattern, a query layer, or any other structural convention, you can encode it here. The must_implement check is particularly useful – it’s a contract that every class in that layer exposes the right interface.
The jobs.cannot_reference_constants "Current" rule deserves a specific callout. It catches a real bug class: CurrentAttributes data set during a web request is not available in background jobs. ArchSpec can enforce this statically, before the bug surfaces at 2am in a production log.
Fast by design
ArchSpec indexes Ruby source with Rubydex and Prism, and reads constant names from your class and module declarations. It never boots your application. This matters: the feedback loop for architecture violations should be as short as possible, and a linter that takes 45 seconds to start defeats its own purpose.
The error output is CI-ready
[error] services must not call #render [methods.forbid]
app/services/create_user.rb:7:5
6 │ def call
→ 7 │ render :new
│ ^~~~~~~~~~~
8 │ end
note: CreateUser calls render
1 architecture violation found.
Clear enough for a developer to act on immediately. Clear enough for a CI log that you’re skimming at 11pm. That’s the bar good static analysis output should clear.
Getting it running takes five minutes
# Gemfile
group :development, :test do
gem "archspec"
end
bundle install
bundle exec archspec init
bundle exec archspec check
archspec init creates a starter Archspec.rb with architecture :rails. From there, you add your custom rules incrementally. Start with the violations that have burned you before. The gem is at v1.1.0, requires Ruby 3.2+, and is MIT licensed.
The bigger shift
We spent the last decade building tools to catch code quality issues automatically – RuboCop, Brakeman, database linters. Architecture was the one thing that stayed manual, enforced through review and culture and the occasional frustrated comment in a PR.
AI agents have accelerated the cost of that gap. If a single assistant can generate a week’s worth of code in a day, the architectural drift that used to accumulate over months can now happen in an afternoon.
ArchSpec isn’t a complete answer to this. It’s the right kind of answer: make the rules explicit, make them executable, and let the machine enforce them so you don’t have to.