3 Years of Pairing With AI Agents in Enterprise Codebases: What Actually Works

3 Years of Pairing With AI Agents in Enterprise Codebases: What Actually Works

The agent pushed my code to git. I hadn’t asked it to. I was still reading the diff, half sure about one function, and it had already run git push and moved on to the next task like nothing happened.

That was in my first weeks using an AI agent on a big enterprise repo. I don’t remember exactly which branch it went to, but I remember the feeling in my stomach very well. Nothing broke, luckily. It could have.

So this is the list of things I picked up after that. Some came from my lead, some from my own mistakes, and a few from watching the same thing go wrong again and again. Most of it isn’t about writing better prompts. It’s about how you work around the agent when the codebase is old, huge, and full of rules that nobody wrote down.

The Boring File That Saves Tokens

First one is boring, I know. Whenever you build something, keep an AGENTS.md file in the repo, or a plain doc file next to the code. Different tools look for different file names, so check once what yours picks up.

Why does it matter? An agent starts every session knowing nothing about your code. Zero. So the first thing it does is open a lot of files, follow imports, read some tests, and guess what the folder structure means. All of that costs tokens, and tokens cost money and also time. There’s another problem, which is that the more random files it reads, the more chances it picks up a wrong idea from some old module that nobody uses anymore. With a short file at the root saying what the service does, where things live, how to run it and which patterns to follow, the agent starts from page one instead of page zero.

It helps people too. A new teammate can read it in ten minutes and get what took me two weeks of asking around. I think that’s actually the bigger win, and I didn’t expect it.

What goes inside? Keep it short. Two lines on what the project does, the folder layout, how to run and test it, the patterns you follow, and the things nobody should touch. Not a novel. If it grows past a couple of screens the agent starts skipping parts, same as people do.

I am not very good in writing docs, so I used to avoid this. Then I let the agent write the first draft after it explored the repo, and I only fixed what was wrong. It will only take ten minutes, and you save that in the first session itself.

Draw the Flow Before You Touch the Code

Enterprise repos are big. You get a ticket to add one more condition to some validation, and you have no idea where that validation even lives. Before, I would grep, open ten files, get lost, close them all, and grep again with a different word.

Now I ask the agent to trace it for me. Something like, “I need to add this check. Show me how a request goes from the API to the database, with every branch, as a flowchart.” Mermaid diagrams work fine for this. Agents are good at it because reading across lots of files is exactly what they do well, and it’s exactly what makes a human tired by 3 pm.

What you get back is the entry point, the services it calls, the places where the logic splits, and a good guess about where your change should go. Sometimes I ask for the flow of a few different conditions side by side, like the logged-in user path versus the guest path. Seeing both on one chart tells me what I might break before I write a single line.

Debugging gets faster too. If a bug is somewhere in a path, I ask for the flow of that path, then put it next to what the logs actually show. The step where they stop matching is usually where the bug sits. I think it saved me hours on a couple of tickets, though I can’t tell you the exact number.

This is mostly true, but I’m oversimplifying. The agent sometimes misses a branch that is controlled by a config value or a feature flag, because that part isn’t in the code path it read. So I treat the flowchart like a map, not the ground. I still open the files it points to.

Write the Test That Fails First

When I started pair programming with my lead, the first thing he said was to write the test before the function. I said there’s no function yet, what do I test? He just waited.

Quick tangent, but pair programming with a senior person is a strange feeling. You’re typing, he’s watching, and every pause feels like a loud one. I wanted to look smart so I kept rushing to the implementation, and he kept pulling me back to the test file.

TDD is hard. I’ll say it plainly because people make it sound easy in videos. The learning curve is steep, and for the first month or so my tests were bad. They only covered the happy path, and they passed even when the code was wrong. My lead would look at one and ask, what if this is null? What if the list is empty? What if two requests come at the same time? Slowly I started asking those questions to myself before he did, and that’s the real skill. It isn’t the test syntax. It’s thinking about how the code can fail before you have written the code.

The steps are simple. Write a test for a case that can fail. Run it and watch it fail, because if it passes with no code behind it then the test is useless. Write just enough code to make it pass. Run again. Then the next case. It feels slow at the start and then at some point it flips, and you get less buggy code with way less stress.

With an agent it’s the same thing, you just say it out loud. “Write failing tests for these cases first. Run them and show me they fail. Then implement.” The agent is quick at listing edge cases, and it often thinks of a few I wouldn’t have. After that you have a clear target, and when it changes something later in the session the tests tell you right away if something else broke. In a repo where I don’t know half the code, that safety net is the only thing I really trust.

And no, TDD isn’t for everything. A quick one-off script or a CSS spacing fix doesn’t need it, and saying “always” is me oversimplifying again. But for logic with branches, I won’t skip it now.

Put a Fence Around the Goal

Most of us tell the agent a goal. Fix this bug. Add this feature. And then we don’t say how.

So the agent picks the easiest road, and the easiest road almost never follows your repo. It writes a new helper when one already exists in the utils folder. It catches an exception right there when your team handles errors in one central place. It changes a function signature that five other services call. The code works, the demo works, and then the review comes back with twenty comments.

The fix is to give conditions along with the goal. For a bug it could be like this: follow the existing patterns in this module, keep backward compatibility for current API consumers, don’t add new dependencies, add test cases for every case you touch, and run them before you tell me it’s done. The goal is the same, but now there’s a fence around it.

Without a fence the agent goes wherever the path is shortest. That’s not its fault. It doesn’t know what you didn’t tell it, and nobody told it your team’s rules because they’re sitting in people’s heads.

If you’ve already made the AGENTS.md file from the first section, half of these conditions can live there, and then you don’t repeat them in every prompt.

The “Yes, Go Ahead” Trap

I’ll take a side here, and it’s about 70/30. The agent can decide how a function is written. You decide how the system is shaped.

You might have done this also. The agent stops and asks, should I create a new service class for this? Or should I add a new abstraction layer? And you press yes, yes, go ahead, because you want to finish and the answer sounds reasonable. I’ve done it more times than I like to admit.

The problem is that the agent doesn’t know why your repo does things the way it does. Maybe the team avoided that abstraction because something went wrong two years back. Maybe there was a decision in a meeting that never reached any file. None of this is in the code, so the agent picks the best way in general. And the best way in general is not always the best way for your repo.

So I slow down at these moments and ask questions before it writes anything. Is this a different pattern from what we use elsewhere in this repo? What are the alternatives, and what does each one cost? Do we really need this abstraction, or is it extra? Who else calls this code? If it says “it’s cleaner”, I ask cleaner for whom.

Some people say let the agent design it because it knows more patterns than you. It does know more patterns. It doesn’t know your repo.

Every Agent Mistake Becomes a Rule

Mine pushes to git. You already know that story.

After the first incident I wrote one line in the agent’s instruction file: never push code, never commit unless I say so. It hasn’t done it since, because the instruction loads fresh in every session and works like a memory. That’s the whole trick. Every time the agent repeats a mistake, it becomes one more line.

My list now has a few more. Run the test cases before saying the work is done. Never commit secrets or internal URLs. Don’t ask for write access when working with tickets, read-only is enough. There’s more, and the list keeps growing, which is fine. I’d rather have a long list of small rules than a short list of surprises.

One more tip, and this one is from my lead. Every day he runs an automated task that goes through that day’s chats and finds actionable insights, then saves them as memory. You can use it in different ways. In the morning you get a quick summary of what was done yesterday. Things that went wrong get written down so the same case doesn’t hit you next month. I’m still setting up my own version, so I can’t tell you how well it works for me yet. But I’ve seen his, and it’s the first time a tool remembered something I had forgotten.

So here’s what I’d do today. Open your instruction file, or make a new one, and write the first rule: never push to git. That’s it. It takes a minute, and you’ll be a little less nervous the next time the agent spends thirty seconds doing something you can’t see.

Post a Comment

Previous Post Next Post