Jiufeng
Why AI Coding Drifts: Task Packets and Context Governance

Why AI Coding Drifts: Task Packets and Context Governance

From authoritative sources of truth and a single current goal to side-issue triage and machine-enforced task identity — solving context loss and drift in long tasks. Includes a real case where the work order contradicted itself, so the AI didn't drift at all: we gave it two conflicting instructions.

Vibe Coding

Part three. Part one covered why you need a Loop; part two covered building one with Claude Code + Codex. This one addresses the problem you'll hit right after: you have a plan, and the AI just doesn't follow it.

The conclusion up front:

AI drifts off the main line not because the model is weak, but because it faces too many competing goals at once with no machine-checkable boundary on the current task.

Every qualifier in that sentence matters. It isn't "it won't listen." It's "there are too many equally reasonable things in front of it."

1. Why AI drifts

More context does not mean clearer priority.

This is the counterintuitive one. The instinct is "it lacks context — feed it more," so in goes the requirements, the architecture, the history, last round's findings. The result: a hundred things are now equally present — and present is not ordered.

Your opening "this round, only do A" is, by turn 40, competing with ninety-nine other things in the same pool. It hasn't forgotten that line. It just no longer treats it as the most important one.

Requirements, architecture, history and incidental findings compete with each other.

They're all real, all relevant, all worth doing. When the model sees "there's an obvious bug right here," that bug is concrete, present, and solvable — while "this round's single goal" is an abstract sentence. Concrete things have gravity.

The model gravitates to the most concrete problem in front of it.

That isn't a defect; people do the same. The difference is that people have a brake — "hold on, this isn't what I'm supposed to be doing today" — and that brake comes from external, checkable boundaries: your sprint, your PR scope, a colleague who'll ask "why did you touch this?"

The AI has none of those. Unless you give it some.

A chat log is not a reliable project state.

This is the worst one. People treat "we discussed it" as "it's decided." But a chat log is linear, ambiguous, and unversioned. That "sure, let's go with that for now" from three days ago — you remember it as a brush-off; the model remembers it as a decision.

2. Fixes that feel right and don't work

Before the working approach, let's kill a few dead ends — because they're all intuitive.

❌ Keep appending "please follow the plan strictly." Say it ten times and the weight doesn't decuple. It's just item 101 in the pool.

❌ Dump every architecture doc into the prompt. You think you're providing context; you're manufacturing competition. The more you add, the lower the relative weight of "this round's single goal."

❌ Re-explain the full background every round. Expensive, and every re-explanation risks new wording drift — today you say "candidate sequence," tomorrow "retry queue," and the model will diligently treat them as two concepts.

❌ Maintain an ever-growing control.md copy of project state. The subtlest one. You end up with a copy that's permanently half a step behind the real state. And once two states disagree, the model picks whichever is easier to parse — usually your handwritten one, because it's tidier.

❌ Switch tasks the moment you find a problem. "Well, I'm already in here" — that sentence is where drift starts, not where it ends.

What all five share: they add things. But the problem was never "not enough things." It's "too many things with no priority."

3. What a Task Packet is

A Task Packet is this round's work order. It isn't a document. It's a boundary.

A minimal one answers seven questions:

FieldAnswers
The one goal this roundthe only thing this round does
Authoritative referenceswhere the facts live (reference, don't copy)
Architectural invariantswhat this round must not break
Allowed change surfacewhich paths may be touched (machine-parseable)
Explicitly not doingwhat is forbidden this round
Acceptance evidencewhich command, which exit code counts as done
Stop and escalation conditionswhat forces a stop and a human decision

Note that last field. Most work-order templates don't have it — and it's exactly what decides whether the AI, at a fork, picks one itself or stops and asks.

4. How authoritative docs relate to the Packet

Here's where people go wrong: the Packet is not a miniature architecture document.

DocumentAnswers
Requirementswhy
Architecturewhat it should become
Designwhich technical path
Planwhere we are
Ledgerwhat's already been decided
Task Packetwhat this round does

Every line in the Packet should be a pointer, not a copy:

| Authoritative input | Alignment | Relation to this round |
|---|---|---|
| Architecture | <path>#<section> | constraints that must not break |
| Plan         | <path>#<item>    | the current item and its dependencies |
| Ledger       | <path>#<decision>| settled items, residual risks |

Why reference and not copy: copying creates a second source of truth. Once the two disagree — and they will — you have a new problem: which one counts? The model won't ask. It'll pick one.

5. Why "the one goal" carries everything

Two ways to write it:

❌ Wrong:

Complete the health-routing capabilities and handle any issues found.

What's wrong with it? Every word is fine, and it has no boundary.

  • "health-routing capabilities" — related how closely? If we touched routing, is monitoring related?
  • "handle any issues found" — find anything, fix anything. That's authorization for unlimited expansion.

That isn't a goal. It's a blank check.

✅ Right:

Under a flag that defaults to off, make async retry consume the already-persisted candidate sequence, keeping the old path byte-identical.

Look at what it adds:

ClauseDoes
"under a flag that defaults to off"release boundary — production is untouched
"make async retry consume"the action — specific enough to verify
"the already-persisted candidate sequence"precondition — does not include "and also build persistence"
"keeping the old path byte-identical"an invariant — a sentence a diff can check

A good single goal tells you both what "done" means and what "out of bounds" means.

One rule of thumb: if it doesn't fit on one line, it isn't one thing — split the round.

6. A real case: the work order contradicted itself

This is the most valuable part of this post, and it's more interesting than "the AI drifted" — because the AI didn't.

A rate-limiting work order had these two fields:

The one goal this round: add cross-replica per-IP rate limiting to the auth endpoints (the login family + registration + password-reset verification), without hurting normal users.

Out of scope: registration, /user/reset, /user/reset_by_codepending a user decision.

See it? The same packet includes registration and reset in the goal, and marks them "pending a decision" in the scope.

Claude's actual diff changed all three.

Then Codex's finding (desensitized):

MAJOR — the packet and the actual diff conflict on the main line: the packet marks register and the two reset endpoints "out of scope, pending a user decision," while the diff has already folded all three in; and the "one goal this round" field includes them. This scope expansion touches security policy and priority; it needs an explicit user decision and a reconciled packet before it can be approved.

Note what Codex said: "a reconciled packet before it can be approved." It didn't tell Claude to revert the code. It pointed out that the work order itself is broken.

The lesson worth keeping:

The source of drift is often not the executor. It's the work order.

You gave it two conflicting instructions and it picked the more concrete one (the goal explicitly named registration). That isn't drifting. That's following instructions.

So next time you think "the AI isn't listening," check the packet first: did you say both "do it" and "don't"?

7. Triaging side issues

You will always find other problems mid-implementation. Finding a side issue doesn't mean fixing it now — leave this unwritten and the Loop becomes an infinite TODO collector by day two.

Four destinations, pick one:

ClassWhenAction
IMPLEMENT_NOWcan't finish the current goal without it, and it's in scopefix and test this round
ACCEPTED_RISKrisk understood, current goal deliverable, human acceptedrecord it, don't block
LATERvaluable but not this goalnext packet, don't switch the main line
ESCALATE_USERchanges architectural semantics, scope, priority or release riskstop, hand over evidence + recommendation

One detail people get wrong: the test for IMPLEMENT_NOW is not "is this serious?" It's "can the current goal be finished without it?"

A serious problem that doesn't block the current goal goes to LATER or ESCALATE_USER — not "well, it's serious, so let's just fix it." Severity decides how soon it gets handled. It doesn't decide that it gets handled this round.

8. How a heartbeat proves the main line is moving

Each progress report only needs to answer five things:

  1. What is the current goal (in business language, not an ID)
  2. What verifiable capability did this round add
  3. Any drift; if so, where it stopped and what decision is needed
  4. What's still missing
  5. Why the next step is the highest priority right now

Point 2 is the core: "verifiable capability," not "I changed three files." Changing three files isn't progress. Making a path work that didn't work before is.

One anti-pattern to kill: don't just emit A1, B3, Phase 2.

IDs are for machines. You scan "A1 done, moving to B3" and you have no idea what happened. So one of my rules is an ID dictionary:

Only IDs defined in the table may appear in the body, heartbeats, review reports or evidence packs. When reporting to the user, the human-readable name comes first, then the ID.

It sounds fussy. What it actually stops is terminology drift — A1 quietly meaning something else three days later, with nobody noticing.

9. Enforcing task identity by machine

Everything above is about writing. But only a constraint the machine can enforce is a constraint. Four machine boundaries:

1. Bind the packet, the checkout, the HEAD, the guard and the review prompt together. One task, one identity. Not "I remember which task this is" — every artifact carries the task identity.

2. The change surface is checked against an allowlist by machine. Write "allowed change surface" as path/**, not prose — it has to parse into an allowlist and be compared against the real diff.

This is why I say "allowed change surface" matters more than "goal": a goal is natural language the executor can read a hundred ways; a surface is a set of paths a machine compares directly.

3. Parallel tasks get separate worktrees. Two tasks sharing one tree means diffs bleed. That's not a discipline problem; it's a physics problem.

4. Review checks the main line first, the implementation second. The order matters. In that rate-limiting case, Codex's first MAJOR was "the packet and the diff conflict on the main line" — it asked "is this what this round was for?" before "is this correct?"

Reverse the order and you get a review that says the code is excellent — and shouldn't have been written this round.

10. One backstop signal

The nine points above are prevention. But process deforms on its own, so you need a detector:

Two rounds in a row that only edit the packet, the review prompt and the ledger, with no code or decision progress — that isn't rigor, that's spinning.

When you see it, re-answer one question: did this step directly move the one current goal closer to done?

If the answer is "no, but it made the process better" — that's drift. And it's the hardest kind to notice, because it looks diligent, organized, and productive every round.

It just isn't producing the goal.

Conclusion

Context governance is not about giving the model more context.

Three things matter:

  • A clear source of truth — requirements, architecture and ledger each own their piece; the Packet references and never copies. Two truths equal no truth.
  • A clear priority — the single goal field takes exactly one thing. Doesn't fit? Split.
  • A clear current boundary — which paths may be touched is decided by a machine, not by the executor's interpretation.

So the value of a Task Packet isn't that it adds a document.

Its value is that it reduces the number of choices the AI has to make right now.

The fewer things in front of it, the less it drifts. This has nothing to do with model strength — the stronger the model, the more reasonable options it can see at once, and the more it needs someone to say which one this round is for.


Next: machine gates — why "please comply" is never enough, and which constraints are worth the cost of becoming a gate that returns a non-zero exit code.