← All writing

The heads down chapter of AI

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/ci is 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 test and bin/undercover — the same checks as .github/workflows/td.yml, but in under 30 seconds against the ~30 minutes Actions takes on this repo. A green bin/ci means the change is green: report it and move on rather than polling the PR. Keep config/ci.rb and 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.rb is 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 call render, redirect_to, params, session, cookies or flash) plus must_be_empty guards on app/forms, app/policies, app/decorators, app/presenters and app/components. That is the :vanilla_rails preset minus its ban on app/services — the classes there are ImageMagick renderers and Stripe API clients, not the service objects the preset is aimed at.

bin/ci runs the check, so a violation fails the build. After changing Ruby, run bin/archspec check on what you touched, and fix the code rather than the spec. Edit Archspec.rb only 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.

Related posts

When you’re working on an app with a team, you always have a set of tools/SAAS products that the team uses to triage, monitor, and build your app. Quite a few…

I was curious what the final image size and deploy times would be when you utilize the default Dockerfile that comes with Rails 8 vs. the new Buildpacks…

I recently migrated a newsletter that I run(The PHX Brief) over to a new hosting platform and used Kamal for the new deployment flow. For part of the process,…