Coding-Agent State Protocol

The Agent Wasn't Wrong About Its Own Work. It Was Wrong About Yours.

A long-running session is reliable about the work it did and unreliable about the work it did not — and the gap widens with every minute it stays open. That asymmetry, not merge conflicts, is what actually breaks when agents share a repository.

18 August 2026 · 7 min read · state drift

The expected failure, when several coding agents work on one repository, is two sessions editing the same file. That one is loud, it is a merge conflict, and git has handled it for twenty years.

The failure that actually shows up is quieter and does not touch the same file at all. A session reads something about the repository — a symbol exists, a commit landed, a branch is current — and that reading is accurate at the moment it happens. Then the session works for forty minutes. Then it acts on the reading. Somewhere in between, the reading stopped being true, and nothing raised its hand.

Three measured trials on three separate repositories produced fourteen recorded incidents. Stale belief about shared state was the dominant pattern in all three.

The asymmetry

A session is close to reliable about its own work. It made the edit, so it knows the edit happened; its account of what it changed is the strongest thing it holds.

It has no comparable grip on anything it did not do. Everything it believes about the rest of the repository is a snapshot from whenever it last looked. There is no mechanism that invalidates that snapshot, no cache that expires, no signal that fires when a fact goes stale. The belief simply persists at full confidence while the world moves.

This is why the error grows with session length rather than with complexity. A short session is wrong about less because less had time to change.

The dev server is on the wrong side of it

Here is the part that turns a stale belief into a deployed bug, and it is worth stating precisely because it defeats the reflex people actually use.

A session writes code that consumes a symbol another session is supposed to provide — a shared constant, a type, a translation key, a column. It checks the obvious way: it opens the page in the browser and the symbol renders. Confirmation.

The confirmation is worthless. A dev server reads the working tree. It renders a symbol that exists on disk with complete indifference to whether that symbol has been committed. What a build reads, and what a deploy reads, is HEAD. Verifying in the browser proves the symbol exists on this laptop. It never proves the symbol exists in what is being pushed.

The check that distinguishes them fits on one line and does not require the two sessions to be synchronized:

$ grep -c "in-flight register" blog/content/in-flight-state-parallel-coding-agents.md
7

$ git show HEAD:blog/content/in-flight-state-parallel-coding-agents.md | grep -c "in-flight register"
fatal: path 'blog/content/in-flight-state-parallel-coding-agents.md' exists on disk, but not in 'HEAD'

Seven occurrences on disk. Zero in what a push would carry. Every tool that reads the working tree — the editor, the dev server, the agent's own file reads — reports the first number and is right to. Only the second one describes what ships.

Ordering, not just checking

The one-liner is the detection. The rule that removes the need for it is an ordering rule: the shared thing gets committed before the thing that consumes it.

That is stronger than checking before push, and the difference only surfaces when someone goes backwards. A consumer committed underneath the symbols it consumes yields a coherent tree at the tip and nowhere else. Continuous deployment follows the tip, so the window stays shut — until a pinned rebuild, a one-step rollback, or a bisection lands on the consumer commit, and the defect the pre-push check was supposed to prevent renders exactly as it would have.

Checking before push protects the tip. Committing in the right order protects the history.

Why the check has to be mechanical

Every stale belief in the trials was held sincerely and was individually plausible. None of them was carelessness, and none of them would have been caught by a session reviewing its own reasoning more carefully, because the reasoning was sound — the input had changed.

Which is what a deterministic check is for. A verdict that resolves against the commit graph does not care how long the session has been open or how confident its account is. last_commit naming a commit that is not in the history is a fact about the repository, settled the same way on any machine, at any hour. It does not become true because the session is sure.

The cheap version of that discipline, at the moment it costs least:

That last item is the same category, one layer down: a session that ran git add half an hour ago is also a stale belief, held by the index rather than by the session.

Where this leaves parallel sessions

None of this argues against running several at once. It argues that the thing to design for is the belief gap, not the conflict — the conflicts are visible and git already handles them. The recorded incidents, the collisions that actually occur, and the launch-time questions worth answering first are set out on the fleet page.

The asymmetry is the part worth carrying: a session's account of its own work is its strongest claim, and its account of everyone else's is its weakest. Trust the first. Resolve the second against git.

$ npm i -g @justethales/casp
$ casp init && casp check

Local, deterministic, zero telemetry. No account, no outbound call, no model in the gate.

The model holds the context. CASP proves the state is true — against git.

Related articles