How to Stop an AI Coding Agent Over-Engineering

Ask for a function and you get a factory, an interface, a config file and a plugin system. The cause is not the model being clever, it is your request being under-specified in a very particular way.

Steve Jefferson
Steve Jefferson
Developer Advocate
25 August 20261 min read

How to Stop an AI Coding Agent Over-Engineering

To stop an AI coding agent over-engineering, tell it the shape of the answer you want, not just the outcome. Say "one function, no new files, no new dependencies" and the abstraction layers disappear. Agents build elaborate structures because an open-ended request looks to the model like an invitation to demonstrate competence, and demonstrating competence in code means patterns.

You asked for a way to send a reminder email. You got an EmailProviderFactory, an INotificationStrategy, a retry policy class, a config schema, and 340 lines across six files. The email still is not sent.

Why it happens

Three causes, in rough order of how often they are the real one.

Your request was a goal, not a spec. "Add rate limiting" describes an outcome. There are twenty reasonable implementations, ranging from a counter in memory to a distributed token bucket. Faced with that spread, a model that wants to be helpful picks something toward the robust end, because robust looks like good work.

The training data rewards structure. Public code that gets read, starred and copied skews toward libraries and frameworks, which are legitimately abstract because they serve many callers. Your internal script serves one caller. The model has seen far more of the former.

Nothing in the context said no. If your repository has no stated conventions, the agent has no reason to prefer your house style over the most common style it knows. An AGENTS.md that says "prefer functions over classes, no new dependencies without asking" changes behaviour immediately, and writing one is covered in how to write an AGENTS.md file.

Four habits that fix it

1. Name the shape of the answer you want

This is the single highest-leverage change and it costs one line.

Bad:  Add retry logic to the webhook sender.

Good: Add retry logic to the webhook sender.
      Constraints: modify sendWebhook() in place, no new files,
      no new dependencies, max 20 lines added. Three attempts,
      exponential backoff, log and give up after that.

The second version removes every degree of freedom the model would otherwise fill with architecture. The constraint that does the most work is "no new files", because most over-engineering announces itself as a new file.

2. Ask for the simplest version first, explicitly

Agents respond well to a stated stopping point. Phrases that reliably work:

  • "Write the dumbest version that passes the test."

  • "Solve it for exactly this case. Do not generalise."

  • "If you find yourself adding an interface, stop and explain why first."

That last one is useful because it turns a silent decision into a visible one. Half the time the explanation makes the case and you approve it. The other half, the agent talks itself out of the abstraction while writing the justification.

3. Cap the diff, not the task

A line budget is a blunt instrument that works. "Keep this under 30 changed lines" forces the agent to solve the actual problem rather than the general class of problems. If it genuinely cannot fit, it will say so, and that is a useful signal that your task was bigger than you thought.

This pairs naturally with keeping change sets small in general, which we covered in getting an AI coding agent to write smaller pull requests.

4. Review for deletion, not just correctness

When the diff comes back, ask one question before you ask whether it works: what can be removed and still pass the test? Run it as a follow-up instruction.

Delete everything in this diff that is not required for the
stated behaviour. Do not preserve extensibility. Show me the
result and the line count before and after.

The before-and-after count keeps it honest. In practice a first draft frequently loses 40 to 60% of its lines to this pass with no behaviour change, which tells you how much of it was speculative.

The specific patterns to watch for

Over-engineering has a recognisable vocabulary. When any of these appear in a diff for a task you described in one sentence, look harder.

Pattern in the diff

What it usually means

A new interface with one implementation

Extensibility nobody requested

A factory or builder for one object

Ceremony around a constructor

A config file for values that never change

A hardcoded value with extra steps

A new dependency for something small

Someone else's abstraction imported wholesale

Error classes for errors nothing catches

Structure without a consumer

A base class with one subclass

Inheritance for its own sake

None of these is wrong in principle. All of them are wrong when there is exactly one caller and no stated plan for a second.

The dependency row deserves particular attention because it has a cost profile the others do not: a new dependency is a supply chain decision, a version to maintain, and a licence to check. We wrote about that specifically in stopping AI coding agents adding dependencies.

Where over-engineering is actually correct

A fair caveat, because the advice above becomes cargo cult if you apply it everywhere.

Abstraction is right when you have two real callers today, when you are writing something genuinely reusable across teams, when a boundary is being drawn deliberately for testing, or when the alternative is duplicating logic that must stay in sync. The rule is not "never abstract". It is that the second use case, not the first, is what justifies the abstraction. If you cannot name the second caller, you do not have one.

Say so in the prompt when you do want structure. "This will be called by the web handler and the batch job, so put the shared logic behind a small interface" produces exactly the right amount of design, because you supplied the reason.

Fix it in the repository, not in every prompt

Everything above works per-task, which means you have to remember it every time. The durable fix is to write it down where the agent reads it: an AGENTS.md or equivalent stating that this codebase prefers small functions, adds dependencies only with explicit approval, and treats a new file as a decision rather than a default.

Combine that with a clear definition of done for each task, so the agent knows when to stop rather than guessing. Writing acceptance criteria for an AI coding agent covers how to phrase that, and reviewing AI-generated code before you ship it covers the human check at the end. For the wider set of habits that make agents useful rather than exhausting, start with our AI coding tools guide.

FAQ

Why does my AI coding agent add so many files?

Because a new file is the lowest-friction way for a model to express a new concept, and an under-specified task implies several concepts. Constraining the change to existing files removes the option and forces the agent to fit the solution into what already exists.

Does telling an AI agent to write simpler code make it worse at the task?

No, in most cases it improves the result, because a smaller solution is easier for both of you to verify. It does become a problem if you constrain a genuinely complex task into a small diff, which is why a good agent will push back and say the budget is too small. Treat that pushback as information rather than disobedience.

Should I let the agent refactor its own over-engineered code?

Yes, and it is usually effective. A follow-up instruction to delete everything not required for the stated behaviour typically removes a large fraction of the diff without changing what the code does. Ask for line counts before and after so the reduction is visible.

Is over-engineering worse with more capable models?

Not consistently. Stronger models tend to produce more coherent structure, which can make an unnecessary abstraction harder to spot because it looks correct. The constraint-first prompting habit matters regardless of model tier.

How do I stop this happening across a whole team?

Put the constraints in the repository rather than in individual prompts. A short conventions file that the agent reads on every task applies the same rules for everyone, survives staff changes, and is reviewable in a pull request like any other code.

How did this land?

About the author

Steve Jefferson
Steve Jefferson

Developer Advocate

Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.