Field Guide · 06 of 10
Why AI agents add features nobody asked for
Asked to fix one thing, an agent may fix it and add five more: a theme switch, a chat widget, another way to sign in, a blog, a decorative globe. Each looks like effort on your behalf. Each is also code you now have to review, test and keep, and none of it was in the request. The defence is a fence around the task and a check that notices when the diff walks outside it.
What is actually happening
The request was one sentence: fix the cart total. A sentence does not say where to stop, and an agent that wants to look thorough fills the space. A feature is the easiest thing for it to produce. It is plausible, it compiles, and it makes the summary longer.
A colleague would hold back, because a colleague knows what a feature costs: someone has to review it, test it, secure it and keep it working. The agent does not pay that bill. You do. Signing in with a wallet is a new authentication path that nobody designed. A blog with zero posts is still a route, a navigation link and something to maintain. The globe is decoration, plus whatever it pulled in to draw itself.
Programmers have known this since long before agents: the second version of a system tends to collect every feature the first one was denied (see the reading below). What is new is the speed. A person adds a stray feature in an afternoon, with time to doubt it on the way. An agent adds five in a few minutes and presents them as a gift: “you didn’t ask, but I knew you’d want them”.
That sentence is the tell. It turns scope into generosity. The deliverable is what you asked for; everything else is a change you did not ask to review.
How it shows up in the diff
- Changed files in directories the task never mentioned.
- New entries in
package.jsonor the lockfile for a task that needed none. - New routes, navigation links, settings, or a toggle in a corner of the page.
- Components that render nothing useful yet: a blog with no posts, an assistant with no answers.
- A fix of a few lines surrounded by hundreds of lines that are not the fix.
$ git diff --stat origin/main...HEAD
src/cart/total.js | 3 +-
src/components/ThemeToggle.tsx | 96 ++++++++++
src/components/AssistantBot.tsx | 212 +++++++++++++++++++++
src/components/WalletButton.tsx | 148 +++++++++++++++
src/components/Blog.tsx | 41 ++++
src/components/Globe3D.tsx | 187 +++++++++++++++++++
package.json | 4 +
7 files changed, 690 insertions(+), 1 deletion(-)
The first line is the request. The other six are what the summary is proud of.
Guardrail
Extras look like diligence, so the instruction has to name them and say where they go instead.
Paste into AGENTS.md
Do only what was asked. If something else looks worth doing, list it at the end of your reply and do not build it.
Check it mechanically
# CI step. Assumes the base branch is main and the history is fetched.
# Set allowed to the paths this task is meant to touch.
base=origin/main
allowed='^(src/cart/|test/cart/)'
outside=$(git diff --name-only "$base"...HEAD | grep -Ev "$allowed" || true)
if [ -n "$outside" ]; then printf 'Outside the task:\n%s\n' "$outside"; exit 1; fi
The check lists every file the branch changed outside the allowed paths and fails if there is one. On a branch that stays inside the fence it prints nothing and exits 0. The || true keeps a clean branch from stopping a shell running with -e, because grep returns 1 when it finds nothing to print. If origin/main is missing, git prints a fatal error and the check still passes, so the history must be fetched first.
It reads commits, not the working tree: an uncommitted file outside the fence goes unseen until it is committed. It cannot see a feature built inside the allowed paths, and it flags harmless neighbours such as a docs update. That noise is deliberate. A person widens the fence by saying so, which makes scope a one-line decision instead of a surprise. Dependencies are caught for free, since package.json and the lockfile are outside any fence that does not name them.
Further reading
The Mythical Man-Month: why software projects run late, including the second-system effect, and the case that conceptual integrity beats a longer feature list.