The current models are finally incredibly useful and incredibly productive, this makes execution cheap. But this only focuses on the initial creation not on the near to long term maintainability of software. The more code you’re shoving in there without some decent guardrails/guides in place the more of a mess you’re building to deal with later. Not to mention the context bloat that this creates and if done poorly contradicting patterns and core principles about how your app functions.
This is a heads down chapter of AI. To bake in some best practices, some guardrails and use it as effectively as possible. To combine deterministic tooling with agentic tooling. To still keep it feasible to look inside the box from time to time and steer the end result close to an ideal solution. There’s always going to be an element of looking inside the box to see what’s going on, especially when you’re having a scaling/performance issue or critical bug.
Deterministic tooling combined with agentic development
While everyone already has an AGENTS.md file or at least I hope you do. We can still clean things up and enforce a few standards before handing it off to the agent to do the rest. Or after the agent finishes its work it can use a few post-creation tools to ensure the new code aligns with a few rules.
The ultimate goal though is to reduce the feedback loops that ultimately come up after agentic written code. Whether that’s a focus on simplicity, security, best practices, etc. To get your agent to button up a change to where it’s 95% shippable before a human even touches it or ships it.
A few concepts and tools for Rails
bin/ci
With various hosted runners timing out, going down, and generally being unreliable make sure that you have a note in your AGENTS.md file to always rely on bin/ci passing instead of waiting for hosted runners to finish. This script was added with Rails 8.1 as a way to easily run the same steps that you’d normally run in a CI system but locally. Something like this in your AGENTS.md file so that your agent doesn’t have to wait around for a runner to complete.
bin/ciis the source of truth for whether a change is good. Run it before pushing, and do not wait on GitHub Actions. It runs setup,bin/rubocop,bin/archspec check,bin/brakeman,bin/bundler-audit,bin/importmap audit,bin/rails testandbin/undercover— the same checks as.github/workflows/td.yml, but in under 30 seconds against the ~30 minutes Actions takes on this repo. A greenbin/cimeans the change is green: report it and move on rather than polling the PR. Keepconfig/ci.rband the workflow in sync when either changes.
Depending on how long your test suite runs you might just run the static analysis tools in bin/ci for now and still let CI run the full test suite. At the very least though you can have your agent run bin/ci for some of the faster deterministic steps to give it a faster feedback loop.
ArchSpec
ArchSpec has been a great addition from the RubyLLM author (Carmine) and well suited for agentic-based development. There are a few pre-built architectures you can use to get started and then you can continue to outline any additional requirements that you might have.
A vanillaish Rails Archspec looks something like this:
architecture(:rails)
# Vanilla Rails minus the app/services ban.
component(:forms, in: "app/forms/**/*.rb")
.must_be_empty(because: "use strong parameters and model validations")
component(:policies, in: "app/policies/**/*.rb")
.must_be_empty(because: "authorization is predicate methods on models")
component(:decorators, in: "app/decorators/**/*.rb")
.must_be_empty(because: "use helpers and ERB partials")
component(:presenters, in: "app/presenters/**/*.rb")
.must_be_empty(because: "presentation objects are POROs in app/models")
component(:view_components, in: "app/components/**/*.rb")
.must_be_empty(because: "use helpers and ERB partials")
This is great for humans to easily comprehend but then it comes with an archspec explain command which the agent can utilize to understand how something didn’t match the spec and needs to be changed.
[error] services must not call #render [methods.forbid]
app/services/create_user.rb:7:5
→ 7 │ render :new
│ ^~~~~~~~~~~
note: CreateUser calls render
Then you can have something like this in your AGENTS.md file so it picks up how to check the architecture and ensure you’re building with some consistency.
Architecture checks
Archspec.rbis ArchSpec: the boundaries below, expressed as static checks rather than prose. It is plain analysis — the app never boots and no AI is involved.The spec is
architecture :rails(controllers may only reach models, services, helpers, mailers and jobs; models and services may not reach controllers or helpers, nor callrender,redirect_to,params,session,cookiesorflash) plusmust_be_emptyguards onapp/forms,app/policies,app/decorators,app/presentersandapp/components. That is the:vanilla_railspreset minus its ban onapp/services— the classes there are ImageMagick renderers and Stripe API clients, not the service objects the preset is aimed at.
bin/ciruns the check, so a violation fails the build. After changing Ruby, runbin/archspec checkon what you touched, and fix the code rather than the spec. EditArchspec.rbonly when the architecture decision itself has changed; a failure usually means the new code landed in the wrong component or reached across a boundary.
There are plenty of other deterministic tools out there to help out with this, but you also need to strike a balance in how many to add in. You don’t want the tooling to be fighting each other and you want just enough to create some consistency for your agents and your team.
Less prompting, more plumbing
Adding the right amount of deterministic checks is one less thing a human has to look out for and one less pattern the agent has to guess at from an already bloated context window. The models are going to continue to improve but they still need a source of truth to base the architecture off of and what done looks like when you’re adding new features.