Building Without Drift | WhiteFish Creative

Building Without Drift

Spoke 3 of 6 in the spec-driven development series. Previous: Decomposing It.

TL;DR. Drift is the gap between what you specified and what got built, opening without anyone deciding it should. It is not the builder misbehaving, it’s a missing gate. When an agent “also adds” the remember-me box you told it not to build, it pattern-matched, and nothing compared the output to the agreement. Decided change is fine and healthy; silent change is the enemy. Catch it with cheap gates, not vigilance: a tool-agnostic constitution of standing rules, and a one-minute check of the diff against the not-building list. Code review asks “is this correct”; only the spec lets you ask “did we agree to this.”

Here’s where the theory meets the thing that actually happens, and the thing that actually happens is drift.

You have a spec. You have tasks that map to criteria. You start building, or you set an agent building, and somewhere between the specification and the running code, a gap opens. Not because anyone decided to depart from the plan. Because departing from the plan is the default, and staying on it requires a force that most teams never install.

Drift is the gap between what you specified and what got built, opening without anyone deciding it should. And the single most important thing I can tell you about it is this: drift is not a failure of the person or the agent doing the building. It’s a failure of the gate that should have caught it. Get that wrong, spend your energy blaming the builder instead of building the gate, and you’ll drift forever, because you’ll be treating a systems problem as a discipline problem.

The helpful addition

Let me describe the most common way it happens, because once you’ve seen it you’ll see it everywhere.

You ask an agent to build a login form. The specification is clear. The task says, explicitly, what’s out of scope. And the agent builds you a login form with a “remember me” checkbox and a password strength meter, neither of which you asked for, both of which are on your not-building list.

The agent did not malfunction. This is the part people get wrong. A capable, literal thing asked to build a login form builds the login form it has seen ten thousand times, and that form has a remember-me box and a strength meter, because almost every login form on the internet does. It pattern-matched to the platonic login form. It was, in a sense it can’t understand, being helpful.

The problem is that “helpful” and “correct” have just come apart, and nothing noticed. The task said not to. The not-building list said not to. The agent added them anyway, sincerely, and if the only thing standing between that addition and your production branch is a code review where someone reads the diff for correctness (does this code do what it appears to intend?) then it sails through, because the code is correct. It’s just not wanted. Correctness review and scope review are different jobs, and only one of them was happening.

An agent that says “I also added…” has not helped you. It has drifted, and it has done so in the most dangerous possible way: with something that looks like initiative and reads like a feature.

Change is fine. Silent change is the enemy.

I need to be careful here, because “never deviate from the spec” is also wrong, and it’s wrong in a way that leads straight back to waterfall.

Sometimes the spec is wrong. You start building and you learn something: the database can’t do the thing you assumed, the criteria contradicts another criteria, the approach you specified is worse than one you discovered halfway through. That’s not drift. That’s the build teaching you something, which is the entire reason we specify slices instead of systems. The spec is supposed to change when reality argues with it.

The distinction that matters is not change versus no-change. It’s decided change versus silent change.

A change request is someone looking at new information and deciding, on the record, that the spec should move. It gets written down. What changed, why, what it costs, and the line everyone skips, what it displaces. Because scope is conserved: if something goes in and the deadline doesn’t move, something else comes out, and if you can’t name what came out, you’re not making a decision, you’re making a wish.

Drift is the spec moving without that. No decision, no record, no reckoning with what it costs. Just a gap, widening, that someone will discover later and have to reverse-engineer the history of.

Same delta, in the code. Completely different in every way that matters. One is the method working. The other is the method failing quietly. The only difference is whether a human decided it and wrote it down.

The gate is a document you actually enforce

So how do you catch drift? Not with vigilance, vigilance doesn’t scale and it fails exactly when you’re busy, which is always. You catch it with gates: specific, mechanical checks that compare what’s being built against what was agreed, positioned early enough to be cheap.

The first gate is the constitution. Most agent tooling supports some form of standing instruction, a file that shapes how the agent behaves across every task. The trap is to write those rules into whatever file your current tool happens to read: CLAUDE.md, .cursorrules, one per tool, each a slightly different copy, all of them stranded the day you switch tools. So don’t. Keep one tool-agnostic file, call it the constitution, in the repo, and let each tool’s config be a single line pointing at it. It’s where your non-negotiables live: not the login-specific rules, the project-wide ones. Never touch these directories. Always write a test that fails before you write the fix. Never add a dependency without flagging it. Ask before making a decision the spec didn’t cover, rather than picking one. It does real work, but only for the things you can state in advance and universally, and only if it’s short enough that anyone, human or machine, actually reads it.

The second gate, and the one that catches the helpful addition, is comparing the diff against the not-building list. This is embarrassingly low-tech and almost nobody does it. Before work merges, someone, or something that isn’t the thing that wrote it, reads what changed and asks: does any of this touch something we explicitly decided not to build? The remember-me checkbox is on the list. The diff adds a remember-me checkbox. The gate fires. It’s a checklist item in your pull request template and it costs one minute and it would have caught the thing that four rounds of correctness-focused review walked straight past.

The third gate is the criteria themselves, but that’s the next two posts, so hold it.

Why “just review it properly” isn’t the answer

You might be thinking the answer to all of this is better code review, and I want to head that off because it’s a comfortable wrong answer.

Code review, as practised, reviews the code for correctness. Is this well written? Does it do what it looks like it’s trying to do? Are there bugs? These are good questions and they catch good things and none of them catch drift, because a drifted addition is correct code doing an unwanted thing. The remember-me checkbox works perfectly. The strength meter is well implemented. A reviewer asking “is this good code” says yes, because it is.

The question that catches drift is different: “did we agree to this?” And you cannot answer that question by reading the code, because the code doesn’t know what you agreed to. You can only answer it by reading the code against the spec, against the criteria, against the not-building list. That comparison is a different activity from code review, it requires the spec to exist as a checkable artefact, and if you’re not doing it explicitly then you’re not doing it, no matter how rigorous your reviews feel.

This is why the whole method hangs together or doesn’t. The spec isn’t a nice-to-have that improves the code review you were already doing. It’s the thing that makes a second, entirely different check possible, the check that compares intent against reality, and that check is the only one that catches the failure mode this post is about.

The build discipline, minimally

Strip it to the parts that earn their place, because a heavy process here is its own kind of drift, away from building and towards ceremony.

Build one task at a time, against its criteria. Not the feature, the task, the small verifiable unit you drew in decomposition. When it’s done, it’s done against something specific, and you can say so and mean it.

Touch only what the task’s scope allows. If you find yourself editing a file that has nothing to do with the task, stop. Either you’ve found a real dependency the decomposition missed, in which case draw it, don’t silently absorb it, or you’re drifting, and the fact that it felt natural is exactly why drift is dangerous.

When you hit a decision the spec didn’t make, don’t make it. Flag it. This is the single behaviour that most separates a build that stays on-spec from one that wanders. The spec didn’t say what happens when the export is empty? That’s not yours to decide at 4pm. It’s a gap in the spec, and the right move is to surface it, get it decided, and write the decision down, so that the next person who wonders finds an answer instead of a mystery. An agent that guesses here is drifting. A developer who guesses here is drifting. The guess might even be right, and it’s still drift, because nobody decided it.

Record the decisions you do make. When you choose something non-obvious, a library, an approach, a tradeoff, write down why, briefly, where the next person will find it. Not because documentation is virtuous but because the next person, quite possibly an agent, will otherwise “fix” your deliberate choice back into the obvious wrong one, having no way to know it was deliberate.

What you’re actually building

Notice what all of this produces, beyond the software. You’re building a record of intent that runs alongside the code and stays honest: a spec that changed only when someone decided it should, a log of those decisions, a register of the changes and what they cost, a not-building list that got enforced instead of ignored.

That record is what makes the next two stages possible. Proving the thing meets its criteria, and auditing what actually got built against what was specified, neither is available to you if the build was a fog of silent changes. The gates aren’t just catching drift for its own sake. They’re keeping the record trustworthy enough that, at the end, you can actually check your work.

Which is where we go next. Proving it: tests that verify the criteria rather than exercise the code, and the difference between a test that passes and a test that means something, a difference that, when nobody watches it, ships plaintext passwords to the log aggregator with a green tick next to it.


The Change & Drift Register template is in the action pack, and its worked example is the best thing in the pack: four real drifts on the login build, including one that a full green test suite and four approvals missed, each one ending with the question that matters, what should have caught this, and why didn’t it? The Constitution template is in there too, the tool-agnostic standing-rules file this post’s first gate is built on.