State Drift: Why AI Coding Agents Lose Track of Your Project — and the Deterministic Fix
Across many agents, sessions and models, the state you recorded quietly diverges from what git actually contains. That gap has a name, a cost, and a mechanical fix.
You are ten sessions into a project. A file says the last shipped commit is a1b2c3d and the current phase is "auth, in review." Git says the last commit is f9e8d7c and the branch is three merges past that phase. Nobody lied. The record simply fell behind reality, one session at a time, and no step in the loop ever compared the two.
That gap between your recorded state and your real state in git is state drift. It is the quiet failure mode of AI-assisted development, and it gets worse precisely as the work gets more autonomous — more agents, more sessions, more models, each trusting a status file that nothing validates.
What state drift actually looks like
State drift is undramatic, which is why it survives. A concrete instance:
# the recorded state state.json → last_commit: a1b2c3d phase: "auth · in review" # what git actually holds git HEAD → f9e8d7c (auth merged, billing started) ✗ the file and the repository disagree — and nothing noticed.
The next agent opens the project, reads the file, and orients itself against a world that no longer exists. It "resumes" a review that already merged. Multiply that across a fleet of agents or a week of sessions and the record becomes fiction — confidently, in prose a model wrote and no check ever tested.
Why it happens: the state is prose, and prose isn't verified
Coding agents are good at editing code and good at writing plausible summaries of what they did. The summary — the phase, the "next step," the last commit — is generated text. It is written with the same probabilistic process that writes the code, and it is subject to the same failure: it can be confidently wrong. When the only record of "where we are" is a paragraph a model produced, the record inherits the model's uncertainty.
Git, by contrast, is not prose. It is a deterministic ledger with an exact answer to "what is actually here." The drift is the distance between the two, and it accumulates because nothing in the normal loop compares the paragraph to the ledger.
Why storage-and-recall tools don't close the gap
The reflex is to reach for a "memory" layer — something that stores context and hands it back next session. That helps a model re-load what it was told, but it does not verify it. A store-and-recall system will faithfully hand back the same drifted paragraph it was given; if the record was wrong when it was saved, it is wrong when it is recalled, now with a veneer of persistence.
The distinction is categorical, and it's the whole point: storage answers "what did we say?" Validation answers "is it true?" Drift is a truth problem, not a recall problem — so the fix has to be a check, not a cache.
And the check cannot itself be a model. If a probabilistic system verifies a probabilistic record, you have added a second opinion, not a ground truth. The one thing in the loop that must not be a model forming an opinion is the thing that decides whether the state is true.
The fix: validate state against git, deterministically, at the boundary
This is exactly the job of CASP — the Coding-Agent State Protocol, and it is deliberately narrow. CASP does one thing: it proves your project's recorded state matches git, with a hard exit code, and it blocks the push the moment they disagree.
$ casp check PASS state.json last_commit matches HEAD PASS next_prompt exists and is queued PASS last_session_id maps to a session log ────────────────────────────── casp:check · 3 PASS · 0 WARN · 0 FAIL (exit 0) # after drift: FAIL last_commit a1b2c3d is not at HEAD (f9e8d7c) casp:check · 2 PASS · 0 WARN · 1 FAIL (exit 1 — push blocked)
No model runs inside that check. It is state versus git, and its answer is an exit code: 0 clean or 1 drift. That is the deterministic floor of the self-verification loop — the one gate that isn't the model grading its own homework.
Both boundaries, gated
Drift is cheapest to catch where it would escape. CASP gates two boundaries:
- The start.
casp nextrefuses to begin a new session on drift — so no agent orients against a fictional record. - The push.
casp install-hookwirescasp checkinto the pre-push hook, so a drifted state cannot leave your machine. The floor fires inside the loop, not beside it.
What CASP is not
Its usefulness comes from its boundaries. CASP does not orchestrate agents, schedule work, review code, or manage tasks, and no LLM — not even advisory — ever enters the check. It is git-native, local-only, zero telemetry, and model-agnostic: it works the same with today's coding agents and whatever ships next, because it never depended on any of them. It is a floor, not a harness.
Start in two minutes
# install npm i -g @justethales/casp # in your repo casp init # seed the state files casp check # prove state == git (exit 0/1) casp install-hook # gate every push on drift
@justethales/casp · MIT · git-native · zero telemetry · source on GitHub
The model holds the context. CASP proves the state is true — against git.