The 1,500-Word Budget That Keeps Your Agents From Wrecking Your Repo
JDJean Desauw
13 min read
Freelance React Native lead
Lead React Native, on your team.
Two years leading Odisei Play: one codebase on iOS, Android and web, a release every two weeks, with two engineers and AI agents. That role on your team, or the repo setup that makes the pace possible.
What I actually do when I prepare a codebase for agentic coding, and why "add an AGENTS.md and ship it" is not it.
The mess your agents are quietly wiring into the repo
Your codebase gets messier every time an agent runs, and you might not notice until a designer asks why there are fifteen button components. Rule files that grow until nobody reads them. Agents that look busy and leave a mess nobody can untangle.
The danger isn't that teams fail to see value. It's that they think they're moving fast while quietly wiring chaos into the code their team will inherit.
A repo your agents can read, and a team that stays in control. That's the whole goal.
Start with what the agent sees, not the model
Stop blaming the model. The model is fine. The problem is the repo you hand it.
The agent can only be as good as the context it's handed. A repo where the conventions are implied, the structure is "obvious to humans", and the rules live in someone's head is a repo where the agent will guess. And when it guesses, it generates follow-up issues faster than they can be reviewed out.
So I start by making the codebase legible on purpose. Conventions written down. Structure that's discoverable, not tribal. That clarity helps your human engineers too. A repo your agents can read is a repo your team can read.
This is the work I do in client repos: the router, the rules layer, and the hooks and lint rules that hold them in place. See how I help teams.
AGENTS.md is a router, not a dumping ground
Here's where most teams get it wrong, and it's a mistake I refuse to make in my own work.
My AGENTS.md is an entry point. Every agent reads it first, by convention. But it doesn't hold the rules. It points to them. Here is the shape we use at Odisei Play.
AGENTS.md the router, hard cap 1,500 words.ai-rules/ README.md index of every rule file, one line each navigation.md one topic per file state-management.md testing.md ... 66 files todaypackages/ui/CLAUDE.md rules that only apply inside this package
The router has two parts. First, the hard gates: the handful of rules that apply to every task, whatever it touches. Ours has nine, things like "never merge a PR without an explicit command in the current turn" and "every deployed environment reads the production database". Second, a routing table. One row per rule family, and each row points at a section, not a file:
| If you touch | Read first ||--------------------------------------|-----------------------------------------------------------|| Links, back, child exits, new tabs | .ai-rules/navigation.md § "Back-Button Discipline" || Any new feature state | .ai-rules/state-management.md § "Always use useValue" || Writing tests, Storybook, Playwright | .ai-rules/testing.md § "Test Design Philosophy" |
The rule files themselves can be long. navigation.md is 831 lines. That's fine, because nobody loads it whole. Each heading states one rule as a sentence, and the body under it gives the mechanism and the issue number that made it a rule. When an agent touches back navigation, it reads the router, follows one row, and loads one section. Everything else stays out of the context window.
Two things keep this from turning into yet another messy convention.
A rule lives in exactly one place, the most specific owner. The router never holds a rule. A package file only holds what applies inside that package. If two files say the same thing, that's a bug, and the fix is a delete, not a third file. So the agent never has to pick between two overlapping rules. The structure makes sure there is one.
And a script enforces the shape. yarn check:agent-guidance fails the build when the router passes 1,500 words or when a row cites a section heading that doesn't exist. Rules drift. Headings get renamed. Without the check, a pointer to nothing looks exactly like a pointer to something. Ours started at 293 lines. It's 115 today, and most of my edits to it are deletions.
If you work in a large monorepo you're now asking how this holds up when the topics multiply. Here is what keeps our router small, and where I'd stop trusting my own experience.
The router has a budget, and the build enforces it. Target 800 to 1,200 words, hard cap 1,500. Ours sits at 1,480 with 48 rows and 9 gates, so every new row has to push an old one out. That's the point. A row exists only when no existing row already reaches that file, and a row covers a rule family, not a rule. "Links, back, child exits, new tabs" is one row that reaches one section. And when a rule can be a lint rule instead, it becomes a lint rule and the paragraph goes. Forty of ours went that way.
The other half is scope. Rules that only matter inside one package live in that package's own file, and the agent loads it only when it works there. The root router never carries them. That's how the pattern scales: not one bigger router, but a small one per package, with the root keeping only the gates that cut across everything. Our repo has one app and nine packages, with 66 rule files. I haven't run this on a fifty-service monorepo. If I did, I'd expect the root router to hold ten gates and nothing else, and each service to own its routing table.
What about a task that needs rules from several files at once? Rows point at sections, so a change that touches state and navigation loads two sections, not two files. Each is a few hundred words. If a task needs five families, I read that as a signal the change is too wide for one pull request, and I split it before the agent starts.
The rule files that overgrow
The routing file stays lean. The per-topic rule files are where the mess accumulates, and it happens even when the structure is right.
You add a rule for the one time the agent took the wrong path. Then another. Some of those rules patch around model weaknesses that vanish in the next model release. Others were right once and are wrong now. Others repeat each other because three rules ended up saying the same thing in three ways.
Ours doubled in six months. Writing this piece is what made me see why. We never put the cleanup on the calendar. Every rule got added the day something went wrong, and no rule was ever scheduled to be reread. The fix I'm adding is boring: thirty minutes every week or two, open the files the agents read most, delete what no longer earns its lines. Context is a budget. The router stays small by design. The rule files stay honest only if someone is booked to cut them.
What survives the next model
You might be reading this and thinking the whole approach is a patch for today's models. Longer context windows, better instruction following, built-in tools, and the rule files become dead weight. I've thought about it, and I don't believe it, for one reason. Most of our rules are not about the model. They're about the codebase.
"Every deployed environment reads the production database" is true whatever model you run. So is the import graph between packages, the fact that a quest step navigates with replace and not push, the fact that a delete confirms exactly once. No model update makes those facts obsolete. A bigger context window doesn't make them obsolete either. It just lets the agent load the wrong file more comfortably, and two rules that contradict each other don't resolve themselves because the window got longer.
The rules that do age are the ones about model behavior, and they age fast. Here's a real one. On July 14 we wrote a hard rule: the expensive model never executes, every sub-agent pins a cheaper one. The reason was a review that fanned out seven sub-agents on the expensive model and burned a tenth of a weekly quota in minutes. Ten days later the harness moved to one model across the board, and the rule had nothing to point at. We deleted it. And the risk flipped, because the new model reached for sub-agents more readily than the old one, so the guidance went from "delegate by default" to "cap the fan-out". Same incident, opposite rule, ten days apart.
That's why every rule in our files carries its precedent, the issue or pull request that made it a rule. When a model changes, I don't reread the rules and guess. I sort out the ones that describe model behavior, rerun the precedent without the rule, and delete the rule if the model no longer takes the wrong path. The rules about the codebase stay. The scripts that enforce them stay. What you're investing in is the part that survives.
For the record, the setup runs on Claude Code first, with Codex alongside it, on Opus 5 and Fable 5.1 and a GPT model for some review agents. The rule files are plain markdown that any of them can read. Only the router file name and the per-tool config differ.
And since I'm asked what a pruning session looks like, here's the biggest one we ran. March 11, two days after the monorepo split, one commit: 48 files changed, 83 lines added, 8,400 removed. Out went a whole skills/ folder, 7,000 lines copied from Expo's own plugin docs. The model already knows Expo, and Expo keeps those docs current better than we would. Out went a coding-standards file that repeated the linter. The api rules went from 472 lines to 84 by keeping the rules and cutting the examples. And one contradiction about how to merge class names, stated two different ways in two files, got resolved by picking one.
Most prunes are smaller than that, and they are not about the model at all. In July a rule in our SEO file still said an artist needs two songs to be indexed. The product had shipped every artist indexable weeks before. The rule contradicted the code, and a three-line commit fixed it. The model hadn't changed. The code had. That's the common case, and it's the one the fortnightly session is for.
Autonomy is a dial, not a virtue
The goal isn't more autonomy. Autonomy is a dial, not a virtue, and turning it up before the guardrails exist is how you ship a mess to production.
What earns autonomy:
A spec that says what the agent will do before it runs, so there's something to check against.
Hooks and lint rules that act as CI for the agent, the mechanical checks that catch it breaking its own rules the moment it happens. We have 40 custom ESLint rules, each with its own test file, and a script that fails the build when a component is copied instead of reused.
A review loop fast enough to actually happen, so "reviewed by a human" doesn't become a bottleneck that everyone routes around.
The team stays in control of the decisions and the code that ships. The agent does more of the work, not more of the deciding.
The review loop in practice
"Fast review loop" is easy to agree with and easy to skip, so here is ours, with the numbers.
The agent reviews the diff. The human reviews the report. That's the split. When a pull request is ready, one command runs several review passes over the local diff: a correctness pass, a style pass, and audits written for this codebase, things like "did you add a button when one exists in the package". Nothing is posted to GitHub. The passes fold into one compiled report with a fix plan, grouped by severity. Then it stops and waits for a human.
The human reads the report, not eight hundred lines of diff. They approve or reject each group. Only then do the fixes run, one commit per group. The report is the review artifact, and reading a report takes ten minutes where reading the diff took an hour. That's what makes the loop fast enough to happen every time.
Two details matter more than they look. The model that reviews is not the model that wrote the code. A reviewer grading its own output shares its blind spots by construction, so the detection passes run on a different vendor's model. And every review ends by writing back into the rule files. If the review found a class of mistake, the rule that would have caught it goes in, so next time a lint rule or a hook catches it before a human reads anything.
Who does it? The person merging. On our team that's the two leads, who also author nine PRs in ten. The designer's pull requests go through the same command, run by whichever lead merges them.
What "fast enough" means for us, from 815 pull requests merged in 2026: median time from open to merge is between 1.5 and 8.5 hours depending on the month, and three in four merge within 24 hours. That's on about 200 pull requests a month with four people. The month it drifted to 8.5 hours was August, when we were shipping a release, and I noticed because the number moved. A review loop you can't measure is a review loop you'll route around without noticing.
What good looks like
The best signal I can point to is what changed for the rest of the team, not for the agent.
In the Odisei Play codebase, the payoff was our designer. In March we split the app into a monorepo with a packages/ui package and Storybook, on top of rule files we had been writing since January. She joined that same week. Six months later she has 277 commits on the UI package, 182 components and 177 stories, shipped through 40 reviewed pull requests. The package went from zero components to 390. Team throughput went from a monthly peak of 20 merged PRs in 2025 to 200 in July 2026.
Not everything moved the right way. Modal files on the app side went up, not down. The rule files grew, as I said above. And I can't prove causation from a git log, only that the timeline lines up. But design stopped degrading on the way to production, and I can point at the commits that show it.
That's the real test of agentic prep. Not whether the agent is faster. Whether the humans on the team can suddenly work where they used to be blocked.
What it cost
You're sitting on the success story and wondering what it took. Here is what it took us, from the commit history, because I didn't keep a timesheet.
The migration itself was one engineer, me, for about a week. The first monorepo config landed on March 6. The designer quick-start guide landed on March 10. The move touched 1,645 files, most of them renames, and I used agents to do the mechanical part. Then three more weeks of hardening that I would have underestimated if you had asked me up front: Storybook on web and native, Metro watch folders, Yarn patches, Netlify and EAS config that assumed a single app. Call it a month of one lead's time, on a codebase I had already worked in for two years. On a repo you don't know, budget more.
The ongoing cost is the rule files. We land between 40 and 90 commits a month on them. July hit 247, the month we rebuilt the router. That is the maintenance burden, and it doesn't go away. Book the cleanup session I described above, and give it an owner. I'm doing that now. If nobody owns it, the rules rot and you're back to guessing agents inside a year.
If your repo is a mess today, the first step I'd take is the split: a monorepo with one package per part of the product, what people call a domain-driven cut. It forces you to name the boundaries, which is the thing the agent was missing anyway. And it lets you fix one package at a time instead of the whole repo at once. Start with the package that has a person waiting for it. For us that was packages/ui, because the designer was waiting.
How long before you see something? Our designer pushed her first Storybook story two days after the split. Her first pull request merged ten days after. The team-level numbers took a month to move.
If your repo fights your agents, that's a fixable problem. Tell me the one thing your agents did this week that made you wince. Let's talk about your repo.