Skip to content

feat(agents): persist per-session plan state as a typed projection#528

Merged
dohooo merged 6 commits into
dohooo:mainfrom
david-engelmann:feature/session-plan-state
Jun 3, 2026
Merged

feat(agents): persist per-session plan state as a typed projection#528
dohooo merged 6 commits into
dohooo:mainfrom
david-engelmann:feature/session-plan-state

Conversation

@david-engelmann

Copy link
Copy Markdown
Contributor

Summary

  • Adds a backend projection of the latest agent plan per session, so a future pinned-plan UI can survive reloads without rescanning the chat transcript.
  • Codex turn/plan/updated events and Claude ExitPlanMode tool calls get normalised into the same typed Plan shape and upserted into a new session_plan_state table.
  • New getSessionPlanState Tauri command + UiMutationEvent::SessionPlanChanged invalidation channel. The pipeline accumulator/adapter — and therefore the existing inline plan card / todo list rendering — are untouched; this is a parallel side-table write.

Why

Closes #410. The bug is that Helmor already knows what the active plan is at runtime, but the only place it lives is the chat scrollback. After a reload the frontend has to rescan messages to reconstruct the plan, which is fragile and forces every consumer to re-implement the projection.

Scope boundary

Included:

  • session_plan_state table with idempotent migration + snapshot test.
  • agents::session_plan module — typed Plan, PlanItem, PlanItemStatus, PlanStatus, PlanSource, SessionPlanState shapes, plus pure parsers for both providers.
  • Streaming-bridge hooks at the two existing plan-event sites:
    • Codex turn/plan/updated in the default arm.
    • ExitPlanMode next to the existing persist_exit_plan_message call.
  • UiMutationEvent::SessionPlanChanged { session_id } with frontend type mirror, bridge handler, query key + sessionPlanStateQueryOptions.
  • get_session_plan_state(session_id) Tauri command + getSessionPlanState TS wrapper.
  • Tests:
    • 9 projection unit tests (Codex status normalisation, current-item resolution, ExitPlanMode bullet extraction with nested-line guard, raw-text fallback, empty/missing inputs, upsert round-trip, in-place replacement).
    • Schema migration snapshot for legacy DBs.
    • UI sync serialization test for the new variant + camelCase regression gate extended.

Deferred (intentionally out of scope; subsequent PRs in track C):

  • Pinned-plan UI (Track C / PR C2).
  • Continue / revise prompt builders + buttons (Track C / PR C3).
  • Per-provider plan-completion / cancellation signals (the enum stays Active for now; new variants land when there's a clear provider signal to drive them).

Design notes

  • The pipeline accumulator emits the inline plan card / todo-list exactly as before — projection is a separate observation. That keeps the existing pipeline snapshot suites stable and means the storage-shape "MUST have snapshot test coverage" rule from AGENTS.md applies only to the new table, not to the message rows.
  • Codex events lack stable per-step ids; the projection derives codex-{idx} so the pinned UI has a key and replays land in lockstep when a subsequent turn/plan/updated arrives.
  • ExitPlanMode is free-text markdown. The parser pulls top-level bullets into structured items (rejecting indented sub-items by checking for leading whitespace before the bullet) and keeps the raw markdown as a rawText fallback so the panel can render prose-only or nested plans even though the structured list is shallow.
  • load_session_plan_state logs and returns None on JSON validation failure rather than bubbling — a stale row from a hypothetical breaking shape change won't crash the panel.

Test plan

  • cd src-tauri && cargo nextest run — 1144 / 1144 pass (9 new projection tests + 1 new migration test + 1 new ui_sync test).
  • cd src-tauri && cargo test --doc — clean.
  • cd src-tauri && cargo fmt --all -- --check — clean.
  • bun run lint — biome + clippy -D warnings clean.
  • bun run typecheck — clean.
  • bun run test:frontend — 1092 / 1092 pass.

Coverage added

New tests:

  • agents::session_plan::tests::codex_plan_maps_statuses_and_picks_current_item
  • agents::session_plan::tests::codex_plan_falls_back_to_first_pending_when_no_in_progress
  • agents::session_plan::tests::codex_plan_returns_none_for_missing_or_empty_plan_array
  • agents::session_plan::tests::codex_plan_treats_unknown_status_as_pending
  • agents::session_plan::tests::exit_plan_mode_extracts_bullet_items_and_preserves_raw_text
  • agents::session_plan::tests::exit_plan_mode_returns_none_for_empty_or_missing_plan
  • agents::session_plan::tests::exit_plan_mode_handles_unbulleted_prose_as_raw_text_only
  • agents::session_plan::tests::upsert_and_load_round_trips_typed_shape
  • agents::session_plan::tests::upsert_replaces_existing_row_in_place
  • ui_sync::events::tests::session_plan_changed_uses_camel_case_type_and_field
  • tests/schema_migrations.rs::session_plan_state_migration_creates_table_on_legacy_dbs

Snapshot updates:

  • tests/snapshots/schema_migrations__session_plan_state_migration.snap (new).

Follow-up

This is PR C1 in the durable-plan-state track. The natural follow-up slices:

  • C2: pinned-plan UI rendering getSessionPlanState with sessionPlanChanged invalidation.
  • C3: continue / revise prompt builders + composer actions.

The backend API is intended to be stable enough that either of those can land independently.

@vercel

vercel Bot commented May 12, 2026

Copy link
Copy Markdown

@david-engelmann is attempting to deploy a commit to the Caspian's Team Team on Vercel.

A member of the Team first needs to authorize it.

@david-engelmann david-engelmann force-pushed the feature/session-plan-state branch 2 times, most recently from f33407b to 95cb7c9 Compare May 14, 2026 19:10
Provider plan/todo events (Codex turn/plan/updated, Claude ExitPlanMode)
get normalised into a typed Plan and upserted into a new
session_plan_state table so a pinned-plan UI can render the latest plan
without rescanning scrollback after a reload.

The pipeline accumulator + adapter are untouched: the inline plan card
and todo-list rendering keep the same wire shape. The projection is a
parallel side-table write triggered at the same persistence points
where exit-plan messages and Codex stream events already fire.

Adds:
- session_plan_state table + idempotent migration.
- agents::session_plan module with PlanSource / PlanItemStatus /
  PlanStatus enums and a typed Plan/SessionPlanState shape.
- plan_from_codex_event + plan_from_exit_plan_mode parsers that
  normalise statuses, derive stable per-position item ids, and keep
  raw text for the unstructured ExitPlanMode case.
- upsert_session_plan{,_via_pool} + load_session_plan_state.
- UiMutationEvent::SessionPlanChanged with frontend mirror + bridge
  handler + sessionPlanStateQueryOptions for cache invalidation.
- get_session_plan_state Tauri command + getSessionPlanState TS
  wrapper.

Tests:
- 9 projection unit tests covering Codex status mapping, currentItemId
  resolution, ExitPlanMode bullet extraction, unbulleted-prose fallback,
  empty/missing input, upsert round-trip, and in-place replacement.
- Schema migration snapshot for legacy DBs.
- UI sync serialization test for the new variant + extension of the
  camelCase regression gate.

Closes dohooo#410 (backend projection slice; pinned-plan UI + continue/revise
actions are follow-up PRs in track C).
@david-engelmann david-engelmann force-pushed the feature/session-plan-state branch from 95cb7c9 to e77d229 Compare May 14, 2026 19:46
dohooo added 4 commits June 2, 2026 21:21
…state

# Conflicts:
#	src-tauri/src/schema.rs
#	src-tauri/tests/schema_migrations.rs
…tate-resolved

# Conflicts:
#	src-tauri/src/schema.rs
#	src-tauri/tests/schema_migrations.rs
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jun 3, 2026
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jun 3, 2026
@dohooo dohooo merged commit 07a4e18 into dohooo:main Jun 3, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Pin the active agent plan at the bottom of the chat

2 participants