The decisive eventIf no later todo/write exists, turn/end intentionally preserves the last list—including in_progress.

Four facts. Four owners.

assistant/messageWhat was saidProse is not task state.
todo/writeWhat was recordedWhole-list replacement.
turn/endWhy the loop stoppedProtocol outcome, not proof.
todos projectionWhat the dock rendersLatest snapshot, last wins.

Trace the actual lifetime.

01 · model calltodo_writeLast item remains active.
02 · durable logtodo/writeFull snapshot appended.
03 · final answerNo second writeProse says the work is done.
04 · turn boundaryState retainedNext turn/start clears it.

Route the mismatch.

snapshot activemodel omitted reconciliationImprove discipline or add one bounded stop reminder.
snapshot completedock still activeInspect projection asOfSeq and event delivery.
call failedno snapshot eventFix the tool error; arguments are not committed state.
old row activedock is completeThe row describes a historical call, not current state.

Reconcile truth, not color.

OperatorAsk for verification

Start a new turn, verify real outputs, then write a fresh complete list.

PromptRequire a final comparison

No in_progress only after every item has evidence.

CompositionOne stop reminder

Use agent/turn-stopping plus logged steering, bounded to one continuation.

Regression gates.

  1. Completed final writeSurvives turn end.
  2. Missing final writeRemains visible.
  3. Next turnClears prior projection.
  4. Failed callWrites no snapshot.
  5. Stop guardAt most one retry.
  6. ReplaySame list and sequence.

Primary evidence.

Keep the complete event router.

The canonical guide covers both Web surfaces, safe recovery, a bounded stop-guard design, all turn-ending reasons, and ten regression gates.

Read the full runbook