Skip to content

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

  1. 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.
  2. Write the unordered TODO (TODO.md at 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.
  3. Write the ordered PLAN (PLAN.md at 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").
  4. 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_start only 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)

  1. Read TODO.md and PLAN.md top to bottom — the mandate headers apply to you.
  2. 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).
  3. Find the first unchecked PLAN item in the stated trajectory. Do it. Verify it. Mark it. Move on.
  4. Re-arm the goal if the row is not active — /goal shows it, or await 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's goal_done/goal_blocked is why. Over a stopped goal goal_start is a restart: fresh id, zeroed counters. A paused row is the one thing goal_start refuses — the brake is the user's — and await 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.