Skip to content

feat: surface stale todo state to the model each step #389

Description

@LeXwDeX

Why

Todo lists drift silently. todowrite is the only write path (packages/opencode/src/session/todo.ts — delete-all + reinsert, publishes Event.Updated), but once written nothing ever re-surfaces the list to the model: completed work stays pending, stale items linger, and in_progress is never set. The model needs the current list re-injected while uncompleted items exist, or it loses track after a few steps.

Confirmed direction

Original proposal: inject the todo snapshot via PreToolUse additionalContexts, skip on todowrite calls, skip when all done. Exploration refined the seam, keeping the intent:

  • PreToolUseadditionalContexts are prepended to each tool result (packages/opencode/src/session/tools.ts:188-192), so N parallel tool calls would duplicate the reminder N times in one batch, and pure-reasoning steps (no tool calls) would never see it — exactly where drift is worst.
  • Instead, inject once per model step through the existing SessionReminders seam (packages/opencode/src/session/prompt.ts:1682packages/opencode/src/session/reminders.ts pattern): a synthetic text part on the last user message, in-memory only (model-visible, never persisted — no transcript bloat).

Skip conditions (user-confirmed, translated to the per-step seam):

  • No todos for the session → no injection.
  • All todos completed → no injection.
  • Freshness guard (replaces the skip-on-todowrite condition): the previous step ended with a successful todowrite in this session → the list was just updated by the model, skip this step.

Scope

  • New todo reminder module (sibling of SessionReminders) reading Todo.Service.get(sessionID) at step start.
  • Compact single-line-per-item rendering (status + content); only uncompleted items or the full list with statuses — pick the smaller stable rendering.
  • Applies to any session holding todos, including child/subagent sessions (todowrite is not in ROOT_ONLY_TOOLS).
  • Freshness signal: track the last successful todowrite step per session (via tool execution or Event.Updated subscription).
  • Tests: no todos → no part; all completed → no part; pending items → exactly one part per model step with correct statuses; freshness skip after a todowrite step; parallel tool calls yield at most one reminder per model request; reminder parts are not persisted to the transcript; behavior survives compaction.

Acceptance

  • With uncompleted todos, every model step (including tool-free steps) carries at most one todo reminder reflecting current statuses.
  • After a todowrite step, the immediately following step carries no reminder.
  • All-completed and empty lists produce no reminders.
  • No duplicate reminders from parallel tool calls.
  • Transcript storage unchanged (reminder is model-visible only, not durable).
  • Focused session-tool tests and bun typecheck pass from packages/opencode.

Out of scope

  • Requiring users to configure hooks.json (this is product-native; users may still layer their own hooks).
  • Auto-mutating todo state — the reminder informs; the model still owns updates via todowrite.
  • Persistence schema changes or new events.
  • DAG node child-session-specific behavior beyond per-session applicability.

Delivery

Separate WHY from #386-#388 (session tooling, not DAG transport). Bind to its own delivery with specgit issue <number> when implementation starts; created via gh directly to avoid clobbering the active end-structured-output delivery record.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions