Why AI Loves Boring Code

ruby dev.to

Your AI agent is not equally smart everywhere.

Same model, same prompt, same day, and it will code like a senior in one codebase and like a confident intern with a head injury in another. The difference isn't the model. It's what the codebase asks of it. And once you see why, you'll understand the strangest plot twist of the AI era: the twenty-year-old "boring" framework everyone spent a decade dunking on didn't just survive into the age of agents. It turned out to be accidentally optimized for them.

One prompt, two worlds

Run a thought experiment with me. (Then run it for real. It's more fun for real.)

Take one task, "add password reset," and hand it to the same AI agent in two different codebases.

Codebase A is a two-year-old JavaScript application. Before the agent can write a single line, it must correctly answer a quiz: Is this the App Router or the Pages Router? Which of the four popular auth libraries is installed, or is it custom? Are mutations server actions, API routes, or tRPC? Which ORM: Prisma, Drizzle, raw SQL? Where do emails live, and is it Resend, SendGrid, or nodemailer behind a wrapper someone wrote in 2024 and never documented? Every question is a fork. Every fork is a chance to guess wrong. And the agent will answer confidently, because confidence is the one thing it never runs out of.

Codebase B is a Rails app. The quiz has almost no questions, because the framework already answered them twenty years ago, in writing, the same way for everyone. Models live in app/models. The user model is User. Mail goes through Action Mailer in app/mailers. Routes live in one file. Migrations look like every migration since 2005. Password reset isn't even a design problem; it's a well-worn groove. The agent doesn't guess where things go. It knows, in the deepest sense available to a language model: it has seen this exact shape, in this exact place, tens of thousands of times.

Same model. Same skill. Different terrain. In Codebase A, the agent spends its intelligence answering an environment quiz. In Codebase B, the quiz doesn't exist, so the same intelligence gets spent on your actual feature.

This isn't anecdote-versus-anecdote. There are three structural reasons it falls out this way, and none of them are going away as models improve.

Reason 1: The corpus doesn't argue with itself

A language model is, at its core, a pattern-completion machine. It writes what its training data makes most plausible. So ask: what did each ecosystem feed it?

JavaScript fed it more, by volume, no contest. But look at what that volume contains. Every framework funeral of the last fifteen years left a body in the corpus: jQuery answers, class-component answers, hooks answers, Pages Router answers, App Router answers, all sharing keywords, all syntactically alive, all contradicting each other. When you ask for "how to fetch data in React," the model's knowledge is a superposition of five eras that disagree. A million tutorials that argue with each other don't teach a model the right way. They teach it to be confidently inconsistent.

Rails fed the model less volume but something rarer: agreement. Two decades of code that puts the same things in the same places with the same names. The 2012 blog posts, the 2016 Stack Overflow answers, the 2024 codebases: on everything that matters, they corroborate instead of contradict. (The edges churned here too — the Webpacker years were real. But the spine the model learns from never moved.) In machine-learning terms, the signal is dense and self-consistent. In practical terms, the model's prior — its instinct before it even reads your code — already matches your codebase.

Convention over Configuration was invented to spare human brains. It turned out to be a data-labeling strategy for machines, run faithfully for two decades by people who had no idea they were doing it.

Don't take my word for it. Take the framework creator's. "Convention over configuration set the path for 20+ years of great training data for AI to use today," DHH wrote in early 2026. "Not only does this mean agents do great with Rails, but also that squishy humans can quickly and confidently review the output without a jungle of distracting boilerplate." He calls Rails "one of the most token-efficient ways of building web apps" — token-efficient being exactly the budget we'll price out in Reason 3.

One honest wrinkle, before a commenter shouts it: the newest parts of Rails 8 (Solid Queue, the auth generator, production SQLite) are exactly where the corpus is thinnest. Left alone, an agent will sometimes reach for Sidekiq or Devise out of 2019 habit. The fix is one small conventions file in your repo pinning the Rails 8 defaults — which is this whole argument in miniature: structure in, quality out.

Reason 2: Every decision you didn't make is a guess the agent can't fluff

Here's a way to think about hallucination that's more useful than "the AI lies sometimes."

An agent working in your codebase faces a series of decision points. Each point has some probability of a wrong turn. Wrong turns compound: a bad guess about your auth library poisons every subsequent file it touches. So the reliability of an agent on a task is roughly a function of how many unforced decisions your stack makes it take.

A configuration-driven stack maximizes decision points: it's the whole sales pitch, that you choose everything. Which means in the AI era, you've built a minefield of guessable choices and handed the map to something that guesses for a living.

An omakase stack minimizes them. The framework already chose; the choices are global, documented, and stable since 2004. An agent in an omakase restaurant can't order wrong. What's left for it to decide is the part you actually wanted decided: your domain, your feature, your product.

The agent multiplies whatever your foundation makes cheap. Boring stacks make correctness cheap.

Reason 3: Context is a budget, and convention is compression

Every model has a context window: the amount of your world it can hold in mind at once. It grows every year but never goes infinite, and it stays famously blurry in the middle. Things buried deep in a huge context fade, much like your own memory of last Wednesday.

So treat context like what it is: a budget. The question becomes: how much understanding does each token buy?

In a sprawling, bespoke codebase, the answer is: very little. The agent must read your special router wrapper, your custom hooks, your folder taxonomy, your wrapper around the wrapper, because none of it matches anything it knows. Comprehension is paid for token by token.

In a conventional codebase, comprehension comes pre-paid. The agent doesn't need to read app/models/user.rb to know what it is, where it is, and how it behaves. Convention already told it. The tokens you spend go toward what's unique about your app, which is the only part worth spending on. Convention is compression: it lets a finite window hold the shape of your entire application, so the agent's edits can be globally sane instead of locally plausible.

What it looks like at my desk

I write code with an agent every working day. It's not a demo for me, it's the job. And the daily texture is exactly what the theory predicts. In a Rails app, I describe a feature in plain language and read back a diff that respects conventions I never mentioned, touches the files I'd have touched, and passes the tests it also wrote. A loyalty discount, a new mailer, an admin filter — the kind of thing that's a paragraph to ask for and used to be an afternoon to wire up — comes back in one pass, in the right places, looking like the rest of the app. My role has quietly inverted: I used to type and occasionally think; now I think and occasionally type. The agent drafts; I edit.

I've watched the same agent — same model, same week — fall apart on the other terrain. A client system, the fashionable kind: a large JavaScript front end stitched to a custom backend, three ways to do everything, a folder layout someone invented under deadline. Ask for one feature and the agent writes something plausible that quietly assumes the wrong data-fetching pattern, wires into the wrong one of two auth flows, invents a helper that already exists three directories over. Nothing it produces is stupid. It's all locally reasonable, and globally wrong, because there was no global to be right about. The work stopped being writing code and became babysitting a confident stranger through a house with no floor plan.

The asymmetry isn't subtle. It's the difference between delegating and babysitting.

"But the models will get better"

They will. Let's take the strongest version of the objection: context windows grow, models reason better, soon the agent will handle any codebase, however chaotic. Won't this take age like a 2019 hot take on GPT-2?

Three answers, honest ones.

First: improvement lifts both terrains. When models get better, they get better at Rails too. The gap between "agent in conventional code" and "agent in chaotic code" is structural — corpus consistency, decision surface, context economics — so the tide raises both boats without closing the distance between them. You don't choose a stack to make AI possible. You choose it to bank the relative advantage, which compounds with every model release.

Second: verification doesn't scale like generation. Suppose the agent really can one-shot a feature across forty bespoke files. Someone still has to check it, and that someone scales at human speed. A conventional codebase isn't just easier to write into; it's exponentially easier to review, because wrongness is visible against the pattern. In an everything-custom codebase, wrong and right look identical until production tells you which was which. As generation accelerates, reviewability becomes the bottleneck asset. Convention is reviewability.

Third: even the futurists hedge this way. Notice what the AI labs themselves do: they pour effort into making models follow instructions, conform to specs, respect project conventions. The entire tooling industry is converging on "give the model clearer, more consistent context." The whole direction of travel assumes what this argues: structure in, quality out. Betting on convention isn't betting against AI progress. It's betting in the same direction these systems already lean.

One objection deserves its own answer, because half of you have been holding it since the first paragraph: Ruby has no static types, and aren't types exactly the guardrails an agent needs? Let me concede the strong version first, because the weak dismissal you usually hear is wrong. Types are a real, cheap, author-time guardrail, and they catch exactly the class of thing agents do get wrong: hallucinated field names, wrong signatures, a null that shouldn't be there, an import that doesn't resolve. If your team already lives happily in TypeScript, keep it; you are not doing it wrong.

So the case here is not "types don't help." It's narrower and, I think, truer: a conventional codebase gives an agent much of what types give it — one obvious shape per thing, learned from a coherent corpus — plus guards types can't provide: behavior, not just shape. Tests and evals check that the refund is correct, not merely well-typed. Types and conventions-plus-tests are peers, not enemies.

The quiet reversal

Step back and look at what just happened to the industry's pecking order, because it's genuinely funny.

For fifteen years, the case against Rails-shaped things was social: smaller community, fewer tutorials, fewer Stack Overflow answers, harder hiring. The case for the JavaScript sprawl was the same coin flipped: everyone's there, every question answered, infinite talent pool.

Then your new pair programmer arrived — one that has read all of it, never sleeps, and doesn't care which community is bigger. It cares which corpus is coherent. The social argument evaporated; the structural argument is all that's left. Twenty-odd years of unfashionable consistency just became the single most machine-legible asset in software, and the ecosystem that optimized for buzz handed its training data five incompatible accents.

The boring kid didn't get cool. The definition of cool got rewritten by a machine that grades on consistency.


This is an adapted chapter from my book, The Boring AI Engineer: Why Boring Technology Wins When AI Writes the Code. If the argument landed — or if you think I'm wrong — I'd genuinely like to hear it.

Source: dev.to

arrow_back Back to Tutorials