Join JS Mastery Pro to apply what you learned today through real-world builds, weekly challenges, and a community of developers working toward the same goal.
You ask your coding agent to add a "remove teammate" button. Someone leaves the company, an admin clicks the button, they're gone from the workspace. Simple. You've built this a dozen times.
The agent reads your code, finds the users table, and writes the handler. The code is clean. When the admin clicks remove, it deletes the user row. The tests pass, because the tests check that deleting a user deletes the user. It works in staging. The reviewer sees a delete that deletes and approves it. It ships on a Tuesday.
Three weeks later, finance can't close the quarter. Half the invoices throw an error when they load. The rest show a blank space where a customer name should be. Nobody has touched the billing code in months.

It takes most of a day to find the cause, and the cause is that button. Billing links every invoice back to the user who was charged, so it can print their name. Some of those users no longer exist. Three people left, an admin removed them, and in your app, "remove" now means the row is gone forever.
Deleting the row is correct. It's exactly what the request asked for. It's what you'll find in ten thousand tutorials called "how to delete a user." It passed the tests because the tests asked the same shallow question the code answered. If a junior developer on your team had written that line, you probably would have approved it too, and it would have broken in the same way.
The problem was never the delete. The problem is that your application has a rule the agent couldn't see.
Because in your system, you never delete users. You never have. Years ago, probably before half the team joined, someone learned the hard way that other tables point back at users: invoices, audit logs, activity history. Erasing a user leaves all of it pointing at nothing. So a rule was born. Users are never deleted, only deactivated. Flip a status, hide them from the UI, keep the row.
That rule is one of the most important facts about your users table. And it's written down nowhere. Not in the schema. Not in a comment. It lives in the memory of the people who got burned, and in the shape of a hundred other files that quietly assume a user row never disappears.
A new engineer picks it up in about a month. Not by reading a document. They work in the code, notice that everyone deactivates instead of deletes, ask "wait, why do we do it like this?" in a review, and get the story from someone who remembers.
The agent doesn't get that month. It gets the repository, the task you gave it, and whatever context it can reach before it starts working. And in everything it has ever read, "remove a user" means delete, because that's what it means almost everywhere else in the world. Your codebase is the exception. If nothing in the repository says so, the agent has no way to find out.
On a new project, an agent is brilliant. There's almost no history to contradict, so the familiar answer is usually the right answer. Nothing pushes back. This is why agent demos feel like magic.
A mature codebase is the opposite. Every strange thing in it is strange on purpose.
The odd retry around one network call is there because it took the site down at 2am. The field that's nullable when it clearly shouldn't be is nullable because a data import years ago left rows blank, and half the app now depends on handling that. The function that looks dead is alive, called by a cron job in another repo. The service that looks internal is quietly used by a paying partner.
None of these look important on their own. Each one is a decision your team made after learning something the expensive way. Each one is a scar, and every scar is a rule.
So the better your system gets, the further it moves from the obvious solution, and the more confidently an agent will undo the exact things protecting it. That's the uncomfortable part. The experience baked into a mature codebase is the thing that keeps it safe, and it's also the thing the agent can't see. It will hand you back something that looks cleaner than what was there.
The agent can read your code. It can't read why the code became that way.

After this happens a few times, the natural reaction is to review the agent's work more carefully. You should. But there's a hard limit on what review can catch, and it's worth seeing clearly.
Look at the change again. What's wrong with this code?
await db.user.delete({where: { id: userId },});
Nothing, if all you know is the task.
The code is valid. The intent is obvious. The tests pass. The diff looks perfectly reasonable. To object, the reviewer has to already be carrying one fact in their head: we never delete users here.
That's the real problem. The information you need in order to catch the mistake isn't in the diff. It's in someone's memory.
That works when the right person happens to review the change and remembers the relevant history. It stops working when agents are producing more code, across more parts of the codebase, faster than any one person can hold context for. Reviews don't fail because developers got careless. They fail because the thing you'd need to know in order to object was never on the screen.
If a rule lives only in someone's head, you're betting on that person being available, remembering it, and noticing that this particular change touches it. That's a fragile way to scale knowledge.
If the rule can't reliably be in your head at review time, it has to live somewhere the agent reads before it writes. That's the whole move. And it's smaller and stranger than "write more documentation."
You're not writing for humans here. Humans learn these rules by getting burned, and a human who reads a wiki page has forgotten it by Thursday. You're writing for one specific reader: a fast, capable stranger who understands software, doesn't know your history, and wakes up remembering nothing at the start of every session.
That reader doesn't need a tour of your codebase. It needs the exceptions. The short list of rules that make your application different from the obvious solution, and enough context to understand why those rules exist.
For the users table, the whole thing is a few lines:
## users table- Users are never hard-deleted. "Removing" a user means setting status = 'inactive'.- Why: invoices, audit logs, and activity history all reference users.Deleting the row breaks historical data, and the failure usually shows upweeks later, far away from the change that caused it.- If a task really seems to require a hard delete, stop and ask a human first.
Put that somewhere your agent reliably reads before it starts working, whether that's AGENTS.md, CLAUDE.md, a rules file, or whatever your tool supports.

Now when you ask for "remove teammate," the agent isn't guessing what "remove" means in your application. It reasons from your rule and writes the deactivation instead. The model didn't get smarter. You stopped hiding the truth from it.
There's a small difference here that turns out to be the difference between a rule that holds and a rule that only works once.
You could write this:
- Never delete users.
That's useful. This is better:
- Users are never hard-deleted, because invoices, audit logs, and activityhistory reference them. Deleting a user breaks historical records far awayfrom the original change.
The rule covers the case you thought of. The reason covers the cases you didn't.
Six months from now someone asks the agent to build an account cleanup job. Or redesign billing. Or add a data retention policy. Your original line said nothing about any of that. But an agent that knows why the constraint exists can work out that the cleanup job is the same trap wearing different clothes, and stop to ask.
Rules tell the agent what your codebase expects. Reasons tell it where the edge of the rule is.
Every real system has dozens.
These rules are scattered across code, tests, old tickets, comments, Slack threads, incident write-ups, and mostly people's heads.
Humans are good at reassembling that. We ask questions. We talk to whoever wrote it. We remember the outage from three years ago. We notice that everyone follows a strange convention and eventually find out why.
The agent has none of that. It only knows what's in front of it.
When an AI coding task goes wrong, the first instinct is to write a better prompt. More instructions. More steps. Explain the feature in more detail.
Sometimes that helps. But there's a better question to ask first.
Does the agent know the rules of the system it's changing?
You can write a perfect prompt for "remove a teammate." It still won't know your company never hard-deletes users, unless that fact exists somewhere it can read. No amount of prompt engineering invents information that isn't there.
That's the shift that happens when you stop asking AI to generate code and start asking an agent to work inside a real codebase. The question stops being:
"How do I get the model to write this code?"
And becomes:
"What does the model need to know before it writes this code?"
The second question is the one that actually changes your results, and it's why project rules, context files, and agent skills matter as much as they do. Not because your team needs another documentation system. Most teams already have plenty of documentation nobody reads. The point is narrower: get the constraints in front of the agent before it starts making decisions.
You don't need to document every function. You don't need to explain every folder. You don't need to turn your repository into a textbook. You need the things that would make an otherwise reasonable engineer stop and say:
"Wait. We don't do that here."
Those are the rules worth writing down. The exceptions. The constraints. The decisions that only make sense if you know the history.
You don't have to redesign your whole repository to work this way. Pick the table or the module that scares you most. Write five lines: the rule, the reason, what to do instead, and when the agent should stop and ask a human. Put it where the agent reads before it writes. Then ask it for the change you were afraid to ask for.
The goal isn't an agent that never makes mistakes. It will make them. The goal is to stop making it guess at things your team already knows.
And the order matters as much as the writing. You do this before you let it build, not after it breaks. The scars already exist. The only question is whether you translate them into something the agent can read before it reopens the wound, or after.
This is a big part of what we work through in the Agentic Engineering course, using a real open source codebase instead of a clean demo project. Not because watching an agent generate another CRUD app teaches you anything, but because the messy parts are where the actual skill lives, and they only show up in a codebase that already has a history.
Your codebase has that history. It has decisions nobody remembers making, weird code that exists for very good reasons, and lessons paid for with outages, broken data, angry customers, and long nights. All of it is valuable. None of it helps you if it stays in someone's head.
So pick one rule your team learned the hard way. Write it down. Give it to the agent before the next change.
It might save you a Tuesday.