Sprawl is the default, and it is nobody’s fault
Ask for a rate limiter and you may get: a rate limiter, applied to four endpoints instead of one, plus a configuration module, plus a metrics wrapper, plus a refactor of the middleware chain, plus a new dependency, plus updated tests for all of it.
Every addition is defensible on its own. Together they are a change nobody scoped, nobody sized, and nobody can review properly, and the two-thirds of it that was not requested is the part that will break something.
The mechanism is worth naming rather than complaining about. A system asked to produce something good, with no boundary, will produce the complete version of what it recognises. Completeness is a virtue in the abstract and a liability inside a running system, where the value of a change is inversely proportional to how much of the system it disturbs.
State the boundary explicitly, in the specification
The out-of-scope line is the cheapest control you have and it takes one sentence.
## In scope
Rate limiting on POST /signup only.
## Out of scope
- Password reset, OAuth callback, the mobile endpoints
- Any change to the existing middleware chain
- Metrics beyond the counter we already emit
- New dependencies
## If you think the schema needs to change, stop and say so.That last line is the important one and it deserves its own treatment.
“Say so and stop”
The default behaviour when a system meets an obstacle is to route around it. It will invent the missing column, assume the missing configuration value, work around the constraint, or make the failing test pass by changing the test.
None of those is a decision it is qualified to make, and all of them arrive dressed as decisions rather than as questions.
The instruction that changes this is short and works remarkably well:
If a requirement is unclear, if the schema needs to change, if this needs a new dependency, or if a test fails for a reason you do not understand: stop and say so. Do not guess.
Add it to your conventions file and to any non-trivial request. The output changes shape: instead of a confident diff built on a wrong assumption, you get a question, which costs thirty seconds instead of an afternoon.
The same instruction is worth giving human collaborators, and for the same reason. “Ask rather than assume” is the same policy; the difference is that a person will usually ask anyway when the stakes are visible, and a model has no sense of stakes.
Bounding tactics that work
One verb per change. Add a column, or use a column, not both. Move code, or change it, not both. If your description needs the word “and”, consider two changes.
Name the files that may be touched. “Change only billing/fx.py and its test.” Then check the diff against the list, which takes four seconds and catches everything.
Forbid new dependencies by default. Adding one is a supply-chain decision, and it should be a deliberate one with its own conversation, not a side effect of solving a formatting problem.
Set a size expectation up front. “This should be about 30 lines.” If what comes back is 300, that gap is information: either the problem was harder than you thought, or the response is doing things you did not ask for. Either way you want to know before you start reading.
Check the file list before the code. Open the diff, read the list of changed paths, and compare it with what you asked for. Anything unexpected there gets explained before you spend attention on the lines.
The refactor trap
One case deserves calling out because it is so common and so reasonable-sounding.
You ask for a small behaviour change in a messy function. What comes back is a clean, well-structured rewrite of the whole function, with the change in it. The rewrite is better code.
Accepting it is still a mistake, because you have converted a five-line reviewable change into a hundred-line one, and the behaviour change is now hidden inside a diff where every line is different. Nobody, including you, can tell whether the rewrite preserved the six edge cases that made the function ugly in the first place. Ugly functions are usually ugly for reasons.
The correct move is the split from earlier in this course: take the rewrite as its own change, verified as behaviour-preserving against the existing tests, and then make the five-line change on top. Two reviews, each of which means something, instead of one that does not.
Every lesson here is open. Your progress is recorded once you pass the AI at Work exam.
Check your understanding
A request for a five-line behaviour change returns a clean rewrite of the whole function with the change included. The rewrite is genuinely better structured. Why should it not be merged as one change?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Larissa, your AI tutor
