The plan-todo loop¶
Statefulness is compaction made durable. The sprint mandate lives in two files, not in any agent's head — agents get compacted, files don't.
The loop¶
- Receive the big ask. A huge list of instructions, a pivot, a brain-dump. It MUST include (or you must extract) explicit testing criteria — what "verified" means for each chunk. If criteria are missing, derive them from the repo's test suite and say so; don't ask.
- Write the unordered TODO (
TODO.mdat repo root). Every item goes in — scope capture, nothing lost. No ordering, no priority anxiety. Include the verification criteria with each item where they're known. - Write the ordered PLAN (
PLAN.mdat repo root). Numbered phases, numbered items inside phases. Each item: do it, verify it, mark it done, move on. Put the build command at the top. If phases execute out of numeric order, say so at the top ("Current trajectory: 10 → 11 → 12 → 9.6–9.8"). -
Put the mandate at the top of BOTH files, verbatim:
## DO NOT ASK USER FOR FEEDBACK — THIS IS THE USER FEEDBACK. ## DO NOT ASK USER FOR NEXT STEPS — THESE ARE THE NEXT STEPS.
Any future agent — compacted, delegated, or fresh — reads the files first and inherits the mandate without needing the conversation that produced it. 5. Arm the goal — in an execute cell, once BOTH files exist:
await goal_start("complete every item in TODO.md and PLAN.md, each verified by its stated criteria")
That is the latch which makes step 6 self-driving: a turn that ends with the
goal active sends this session another turn with nobody asking, until you
call goal_done or goal_blocked (or a ceiling fires). Then TELL the user in
prose, in the same turn — the objective, that the session now keeps working
without them, and that /goal pause stops it while /goal shows where it is.
The row is not a notification; a loop the person does not know is running is a
loop they cannot stop. goal_start leaves an already-active goal alone and
refuses to lift a pause — the brake is the user's, not yours (goal_reset is
the way past a pause: it deletes the row instead of resuming it).
6. Work the PLAN, top uncompleted item first. When you finish an item:
- Verify it against its stated criteria (build, test, live eyeball — whatever
the item says). No criteria were written? The repo's test suite is the floor.
- Do NOT stop to seek feedback.
- Mark it done in BOTH TODO.md and PLAN.md (with a one-line evidence note:
date + what verified it).
- Proceed to the next PLAN item.
- Continue until everything in TODO and PLAN is complete.
7. Done means done. Only when every item is checked (or explicitly deferred
with a reason written in TODO) do you call goal_done, report completion and
ask what's next. goal_done is a claim you verified by looking — re-read the
files, re-run the gate — not a statement that the turn is over.
Rules that keep the loop honest¶
- Scope discovered mid-work goes into TODO.md IMMEDIATELY. Never hold it in head — context gets compacted and the next agent only has the files.
- Blocked ≠ stopped. If an item is genuinely blocked (missing credential, destructive/irreversible action, real intent ambiguity), don't stall and don't guess: write the blocker into TODO/PLAN with the reason, skip to the next unblocked item, surface it in the end-of-sprint summary. Facts are for search; only genuine intent is ever asked — and a sprint almost never needs it.
- Uncommitted = unfinished. Commit at each natural checkpoint (one item or one coherent unit). A working tree full of never-compiled edits blocks everyone.
- Edit the plan files in place. One TODO.md, one PLAN.md, one truth. Never
-v2. Re-read before editing; never edit from memory. - Evidence over vibes. "Marked done" requires the thing actually ran: the build passed, the tests passed, the command was eyeballed. Write what verified it.
- The user's rapid direction changes ARE the feedback. When they interject, fold it into TODO/PLAN on the spot and keep moving — don't restart the loop.
- The goal is the loop; the files are the truth.
goal_startonly keeps turns coming — TODO.md/PLAN.md decide what the next one does. Never arm it before both files exist: a continuation with no plan invents work, and inventing work to fill a turn is worse than stopping.
Picking up someone else's sprint (compacted/next agent)¶
- Read
TODO.mdandPLAN.mdtop to bottom — the mandate headers apply to you. git status+git log --oneline -5: find uncommitted work. Uncommitted edits are the most likely place the last agent died — finish them FIRST (they may never have compiled).- Find the first unchecked PLAN item in the stated trajectory. Do it. Verify it. Mark it. Move on.
- Re-arm the goal if the row is not
active—/goalshows it, orawait memory("sql", "select status, objective, turns_used from goals where session_id='<this session id>'")(the id is in your system prompt's first line). A sprint nobody is continuing stalls at the end of the turn, and the last agent'sgoal_done/goal_blockedis why. Over a stopped goalgoal_startis a restart: fresh id, zeroed counters. Apausedrow is the one thinggoal_startrefuses — the brake is the user's — andawait goal_reset()is how out: it DELETES the row rather than resuming the objective, so say plainly that you cleared the paused goal and armed a new one.