Skip to content

fix(tui): recover plan state when resuming a session - #65

Merged
jkyberneees merged 1 commit into
mainfrom
fix/plan-strip-after-resume
Sep 2, 2026
Merged

fix(tui): recover plan state when resuming a session#65
jkyberneees merged 1 commit into
mainfrom
fix/plan-strip-after-resume

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Root cause

Restoring a session (/sessionshandleSessionDetail) swapped the transcript but left the planning surface untouched — and neither live path covered a resume:

  • replayTranscript rebuilds turns outside handleEvent, so the persisted plan tool calls never set the WS trigger that schedules a fetch.
  • The adopt's session event carries the already-swapped session id (handleSessionDetail sets m.sessionID first), so prevSession == ev.SessionID — the switch-delta guard that arms planResetPending never fires (a live switch between two different sessions does).
  • case sessionDetailMsg returns the handler's own batch, without the planFollowup tail.

Net: planInit stayed false (or held the previous session's stale snapshot), so planStripLabel() rendered nothing after restore — not even during the next busy run — until the agent happened to call the plan tool again. Cross-session version crosstalk was also possible via the monotonic planVer guard.

Fix

handleSessionDetail now resets the plan surface and seeds the fetch in the resume batch itself:

m.resetPlanState()
...
return tea.Batch(note, m.adoptSession(), m.fetchPlan())

The endpoint is session-id-scoped REST, so the immediate fetch cannot race the WS switch.

Test (RED-first)

TestPlanSurface_SessionResumeResetsAndRefetches pins the full loop: stale knowledge dropped → fetch seeded → old plan must not leak into the strip → resumed session's snapshot renders plan 1/4 while busy. Failed before the fix (init=true ver=7), passes after.

End-to-end validation against a live odek serve (v1.39.0)

A throwaway client harness (bodek's own internal/client) drove a real odek serve:

Probe Result
Prompt agent → plan created found=true ver=2, 3 steps, probe-a in_progress
Fresh connection → session_switch/plan ✅ plan recovered intact
Full serve restart → switch → /plan ✅ plan recovered (persisted on disk)
SessionDetail transcript ✅ plan tool calls persisted for replay

Conclusion: odek recovers the plan on session load on both the same-process and restart paths — the missing indicator was purely the client-side gap fixed here.

Checklist

  • go fmt clean · go vet clean · full go test -race -count=1 ./... green (7/7 packages)
  • golangci-lint runs in CI (binary not permitted locally)

handleSessionDetail swapped the transcript but kept the previous
session's plan knowledge and never seeded a refetch: the replayed
transcript bypasses the WS plan trigger, and the adopt's session event
carries the already-swapped id, so the switch-delta reset never fired.
The strip then stayed empty — or showed stale steps — until the next
live plan call.

Reset the plan surface and fetch the resumed session's snapshot in the
resume batch itself (RED-first: TestPlanSurface_SessionResumeResetsAnd-
Refetches).

Validated against a live odek serve (v1.39.0): the plan survives
session_switch from a fresh connection and a full serve restart.
@jkyberneees
jkyberneees merged commit 153aa7b into main Sep 2, 2026
7 checks passed
@jkyberneees
jkyberneees deleted the fix/plan-strip-after-resume branch September 2, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant