Coding-Agent State Protocol

The Plan Is a Queue: Handing an AI Coding Agent One Session at a Time

A specification is not a plan an agent can execute. Handed the whole document, it samples — it finds a plausible place to start and starts there, which is a different thing from doing the next thing.

21 July 2026 · 9 min read · agent handoff

The re-briefing tax

Every session with a coding agent — Claude Code, Cursor, whichever ships next — opens the same way. You explain what the project is. You explain what shipped last week. You explain what is deliberately out of scope, usually because the last session went there and you had to pull it back. Then the work starts.

That opening is not a small cost. It is paid at every session boundary, it is paid from your attention rather than the machine's, and it is reconstructed from recollection, which means it drifts. The version you tell on Thursday is not quite the version you told on Monday. Neither is written down anywhere the next session can read.

The obvious fix is to write the specification once. So you write it: a long document, every phase, every constraint, the architecture, the things you have already decided not to build. Then you hand the agent the document.

And the agent samples it.

A specification is not a queue

Handed a whole plan, an agent has to choose an entry point, and it will choose a plausible one. Plausible is not the same as next. The document contains phase 7, and phase 7 is well described, and nothing in the document says not yet in a way that survives being read alongside forty other paragraphs of equally confident prose. So the session opens somewhere reasonable and somewhere wrong, and you correct it, which is the re-briefing you were trying to eliminate, now with extra steps.

The problem is not the agent's judgement. It is that a specification describes a destination and a queue describes an order. They are different data structures, and only one of them has a head.

So the unit of work stops being the plan and becomes the next session. One file per session, drafted in advance, holding what the plan holds but scoped down to what one sitting can actually finish:

That last bullet is what makes the queue self-sustaining. Drafting the successor is part of closing the current session, not a separate planning ritual you will not get around to. The queue is refilled by the work rather than alongside it.

The queue lives in the repository

These prompts are files, in docs/plan/sessions/, committed. That placement is doing more than tidiness.

A plan in a project-management tool is a claim about a repository, stored somewhere the repository cannot see. Nothing reconciles the two. The ticket says the migration shipped; the migrations directory says otherwise; both are confidently wrong at different times and there is no mechanical way to notice. Put the queue in the repository and every claim it makes is checkable against the thing it describes, by anything that can read a directory — no service, no account, no outbound call.

It also means the queue is versioned. A prompt that was rewritten before it was executed leaves a diff. When a session goes wrong you can read the instruction that produced it, at the revision it was read at, rather than reconstructing what you probably meant.

On top of that ordering sits one pointer: the state file names the head of the queue.

"current_phase": "2-payment-intents",
"next_phase": "3-idempotency-keys",
"next_prompt": "docs/plan/sessions/PHASE-3-IDEMPOTENCY-KEYS.md",

The session then starts with a command instead of a briefing:

$ casp next

next prompt · docs/plan/sessions/PHASE-3-IDEMPOTENCY-KEYS.md · status queued
──────────────────────────────────────────────────────────────────────
---
status: queued
session_id: pending
session_log: pending
drafted_at: 2026-07-22
next_after: 26-07-22-001-2-payment-intents
---

# Session — 3-idempotency-keys : Idempotency keys on the charge endpoint

That command prints the prompt. It does not execute it, schedule it, or hand it to a model. The agent reads the output and does the work, exactly as it would if you had pasted the file yourself — the difference is that which file is resolved from the repository rather than from your recollection of last Thursday.

The pointer is a claim, and claims rot

Here is the failure this design creates, and it is worth being precise about it because it is the price of the whole arrangement.

The queue works because one field names the head. That field is maintained by hand — by you, or by an agent following the closing steps of a prompt. Which means it is a claim about the repository that the repository does not enforce. A session ships. The commit lands. The prompt gets marked shipped in its own frontmatter, the log gets written. And the pointer is not moved, because moving it is the last step of a session and the last step of a session is the one that gets dropped when something else comes up.

Now the pointer names a slice that is already done. Nothing is visibly broken. The files are all present and internally consistent. The next session opens, asks for the next prompt, and is handed work that shipped yesterday — and a prompt that shipped yesterday reads exactly like a prompt that has not shipped yet, because it was written before either was true.

What happens then depends on the agent. Sometimes it notices the code already exists and says so. Sometimes it re-implements a slice slightly differently from the version already in the tree, and now you have two idempotency implementations and a bad afternoon. The one thing it cannot do is be relied upon to notice, because noticing is exactly the kind of judgement call that varies run to run.

So the handoff refuses:

$ casp next

next_prompt is already SHIPPED: docs/plan/sessions/PHASE-3-IDEMPOTENCY-KEYS.md
  → casp was not bumped after that session. Run `npx @justethales/casp check` and reconcile before starting.
(exit 1)

Two exact strings compared: the status in the prompt's frontmatter, and the path in the state file. No inference, no model, no confidence score. The command that hands work to an agent declines to hand it work the repository says is finished.

Why the refusal belongs in the gate, not in the prompt

It is tempting to solve this with instructions. Add a line to every prompt: check whether this already shipped before starting. It reads like it should work.

It does not, for the same reason the re-briefing does not scale: it asks the thing being checked to perform the check. An agent reading its own instruction to verify its own starting conditions is the model grading itself, and it will pass most of the time, which is precisely the property that makes it unusable as a floor. A check you trust is one that produces the same verdict on the same repository every time, whether or not anyone was paying attention.

So the same condition is a rule in the push gate, where it has teeth:

$ casp check

casp:check · 14 PASS · 1 WARN · 1 FAIL
  PASS  next_prompt file exists · docs/plan/sessions/PHASE-3-IDEMPOTENCY-KEYS.md
  FAIL  CASP-PROMPT-003 next_prompt is already SHIPPED · docs/plan/sessions/PHASE-3-IDEMPOTENCY-KEYS.md has status: shipped — casp was not bumped after that session
        → either update state.json.next_prompt to the real next slice, or re-execute the shipped prompt explicitly
  PASS  last_session_id has a matching session log · session-logs/26-07-22-001-2-payment-intents.md
  WARN  CASP-GIT-001 last_commit is in history but not at HEAD · state=ed8bd4b HEAD=f947fe8
        → if the new commits are out-of-band work, bump state.last_commit to f947fe8
  PASS  all 3 prompt(s) have canonical status
  PASS  casp + sessions + logs are committed

✗ 1 drift detected. Push blocked — fix before push.
(exit 1)

Read the two findings together, because they are the same event seen from two angles. The FAIL says the queue's head names finished work. The WARN says commits landed that the state file has not acknowledged. Something shipped, and the project's account of itself did not move. That is state drift in the planning surface rather than the code surface, and it is detectable for the same reason: both halves of the claim are in the repository, so they can be compared.

The remedies in that fix hint are deliberately two, and the second one matters. Re-execute the shipped prompt explicitly is a legitimate answer — sometimes a slice genuinely needs redoing. The rule is not asserting the work is done. It is asserting that whether the work is done is currently ambiguous, and that a session should not open on an ambiguity.

What the queue is not

Being exact about the boundary, because the shape invites a familiar assumption.

This is not orchestration. Nothing dispatches a prompt to a model, watches for completion, or advances a pointer on its own. There is no runner, no scheduler, no daemon, no queue worker. casp next prints a file to standard output and exits; every judgement about whether the work was done well stays with the human and the agent.

It is also not a task tracker. There are no assignees, no estimates, no board, no status beyond the handful of values a check can resolve mechanically against files in the repository. A prompt is queued or shipped, a state file names one of them as next, and a rule compares those two facts. Everything a project-management tool is actually for — priorities, deadlines, who is doing what — is deliberately absent, because none of it is checkable against a repository, and a check that starts having opinions it cannot resolve stops being a floor.

What is left is small on purpose: an ordered set of instructions in version control, one pointer at the head, and a deterministic refusal when the pointer and the queue disagree. That is enough to delete the re-briefing, and it is little enough that it cannot quietly become the thing it is supposed to sit underneath.

The order was always the plan

The specification was never the problem. Writing down the architecture, the constraints, the things deliberately not being built — all of that is work worth doing, and an agent handed it does read it.

What was missing was the ordering, in a form something other than a person could resolve. A queue in the repository provides it, the session prompt is its unit, and the pointer at its head is a claim like any other claim a project makes about itself: worth checking mechanically, at the boundary where being wrong starts to cost something.

You write the plan once. Your agent asks what is next. And when the answer and the repository disagree, that surfaces before the session opens rather than after it ships.

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

Related articles