What I Won't Let AI Touch

I run a nine-agent code review system in production. I still won't hand AI five decisions — not despite knowing these tools, but because I do. The ones you can't cheaply reverse stay mine: the decisions that compound.

Jun 1, 2026~9 min read
What I Won't Let AI Touch

What I Won't Let AI Touch

I run a nine-agent code review system in production. Nearly 2,900 lines of calibrated instructions, reading every pull request my team opens. By any reasonable measure, I'm deep in this.

So when I say there are decisions I won't hand to AI, read it the right way. It isn't caution, and it isn't doubt about the tools. It's the opposite. I know exactly what they do well — and exactly where works and right stop being the same word.

Let me show you where that line sits, on a codebase where the stakes are about as low as they get: my own.

I built this site — code-nomad.com — solo, with AI moving at full speed alongside me. No team to coordinate, no client, no users who'll notice if I get something wrong for a week. And even here, AI handed me a component that worked perfectly and was quietly wrong. It pulled blog content by reaching straight into the file system from inside a presentation component — skipping the data layer entirely. It rendered. It passed. The diff looked clean, because every line, on its own, was reasonable. The problem wasn't in any line. It was in the shape.

Nobody was going to catch that in review, because there was no review — it was just me. The cost showed up later, the first time I wanted to change how posts were loaded and found the logic smeared across three components that all had their own opinion about it.

If the line exists even on a personal blog with no users and no regulator, you can imagine where it sits on the work that actually ships to people. So here's the distinction I've been chasing. Not can AI write this — it almost always can. The question that actually matters is different.

The only question worth asking

How expensive is it to reverse when it's wrong?

Most decisions in a codebase are cheap to walk back. The internals of a function. A styling choice. The structure of a test. Get one wrong and you edit it — five minutes, no coordination, nobody downstream notices. These are two-way doors. You can step through, look around, and step back out if you don't like it.

AI is excellent at two-way doors. I let it run at full speed through every one of them, and I move faster for it.

One-way doors are a different animal. Walking them back isn't an edit — it's a migration, a coordination cost, sometimes a quiet rewrite that eats a weekend nobody planned for. These are the decisions that compound: every line written afterward assumes they were right, so the cost of being wrong grows with every commit that builds on top.

There are five of them I keep for myself. The examples below are all from this site — small, low-stakes, mine. The mechanism is identical at any size.

1. Module boundaries

AI optimizes the file in front of it. That's its strength and its blind spot in the same breath. It will write a clean, correct change inside the boundary you point it at — and it cannot see the coupling it's quietly creating elsewhere, because that coupling isn't in its context.

You don't need a monorepo for this to bite. On a single Next.js app it's already there: the line between the data layer that reads and parses my Markdown, and the components that render it. A boundary isn't a folder — it's a promise about what's allowed to depend on what. AI doesn't hold that promise. It holds the task. So it reaches for the import that makes the immediate problem disappear, a component starts reading files directly, and the seam I meant to keep sealed leaks on the next commit.

Where the line goes, what's allowed to cross it, and what each side is permitted to assume — that's mine.

2. The data model and its contracts

The clearest example on this whole site is the thing at the very top of every post: the frontmatter schema. title, excerpt, date, tags, featuredImage. The moment something depends on that shape, changing it stops being an edit and becomes a migration.

Rename one field and it isn't one change — it's every post file that uses the old name, the parser that reads it, the types derived from it, the OG tags and structured data built on top, the RSS feed. All of it has to move at once, or break.

AI invents shapes that read beautifully in isolation. A field nested where it's convenient for this component, a structure shaped around this screen. It looks right, because locally it is right. Then it leaks into everything that adapts to it, and you've poured the foundation before deciding what you were building. The model and the contracts on top of it are the slowest thing in the system to change. I decide those before AI writes a line against them.

3. The server/client boundary

In the App Router this is the architectural decision, and it hides in plain sight. What runs on the server and what ships to the client isn't a performance tweak — it's a decision about what the browser downloads, what gets indexed, and where your data lives.

This site made the tension concrete for me, because I use GSAP for animation and GSAP only runs on the client. The path of least resistance is to drop "use client" at the top of a page to make one animation work — and in doing that you drag the entire subtree onto the client, including the blog content you specifically wanted server-rendered so it would render fast and get indexed. AI takes that path every time. Faced with the choice, it picks whatever makes the component compile and render, and "use client" is a very easy thing to type. The boundary stops being a decision and becomes an accident.

I'd rather it be a decision: animation islands stay client, content stays on the server, and the line between them is drawn on purpose.

4. Failure semantics

AI writes the happy path beautifully. Take the contact form on this site — it sends mail through Resend. Ask AI for that and you'll get a clean, readable, working call for the case where the email goes through.

The failure path is where the real design lives, and it's the part AI under-writes almost every time. What the visitor sees when Resend is down or the request times out. Whether a failed send looks identical to a successful one — because the worst version of this is the form that says "thanks, message sent!" while the message went nowhere. Whether a double-click sends twice. None of that is in the happy path, so none of it shows up unless someone decided it had to.

That someone is me. Failure semantics aren't an afterthought you bolt on — they're a decision you make before the first line, because the happy-path code looks identical whether you thought about failure or not. That's exactly why it's a one-way door: by the time the gap is visible, it's already shipped.

5. The domain vocabulary

This is the quietest one and the one that compounds hardest — and I'll be honest that on a personal blog the stakes are small. But the mechanism is the same one that does real damage on bigger systems, so it's worth watching even here.

Every system is built on a set of words. On this site, right now, it's the i18n keys — I'm adding Polish, and the moment a translation file depends on a key, that key is a contract. It's also the tag taxonomy: is a post tagged ai, or artificial-intelligence, or machine-learning? Pick one and mean it.

AI invents synonyms. It'll call the same concept three different things across three files, each one locally sensible, and every synonym is a small fracture in the shared language. One file says ai, the next says aiTools, a third spells the message key differently and the Polish translation silently falls back to English. Drift sets in, and the answer to "do these two things mean the same thing?" ends up buried in code that read fine at every individual commit. The vocabulary is mine to set and mine to defend. It's the cheapest thing to get right at the start and the most expensive to fix once it's spread.

What actually changed

AI made my execution faster. On my team, call it three times faster on the kind of work where execution is the bottleneck.

People hear that as three times the output. That's not what it is. What it actually means is that every decision arrives three times faster — including the wrong ones. A bad boundary used to take a week to build its way into the codebase, slowly, with enough friction that you might catch it on the way in. Now it lands in an afternoon, clean and tested and wrong in exactly the way that doesn't show up in a diff.

Speed doesn't change which doors are one-way. It just gets you to them faster, with less friction on the way through — which means the moment of decision matters more now, not less. The thing that used to protect you was that building the wrong thing was slow. That protection is gone.

So the job didn't get easier. It moved up the stack. Less of my day goes to writing the code. More of it goes to deciding which code should exist, where the lines are, and what the system is allowed to assume — and then letting AI move at full speed inside those lines.

That part isn't delegable yet.

I'm not convinced it should be.

Was this helpful?

What Not to Let AI Decide in a Next.js Codebase | Code Nomad