Three phases of kodr, one story. Until now a run was a one-shot: prompt in, proposal out, done. Phases 42-44 turn that into a session you can record, resume, and read back. 42 writes the transcript, 43 picks it up, 44 lets you look at it.
Phase 42: write it down (and a quiet bug)
Foundation phase, mostly invisible - except it fixed a bug that had been silently corrupting every transcript. completeWithContinuations returned a messages array that ended with the user’s last message. The final assistant reply lived in response.md but was never appended to the history. So the canonical transcript was always missing its last entry - the most important one. Same gap in the tool-call loop. The fix is one line at each return path: push the assistant message before returning.
With that corrected, the run dir gains conversation.json - the complete system → user → [assistant → user → ...] → assistant array. That’s the artifact phase 43 will reload. Two more breadcrumbs land here too:
sessionIdandparentRunDirinsummary.json- a fresh run is its own session with a null parent; continuations will chain off these..kodr/last-run- a plain text file pointing at the most recent run dir. Plain text on purpose:cat-able, written with one call, no parsing on the resume path. It’s per-workspace, so running kodr from two projects doesn’t clobber state.
Phase 43: pick it up
Now the payoff:
kodr run -p "yes, do that" --continue
kodr run -p "also add a farewell function" --session 2026-05-29T10-00-00.000Z
--continue reads .kodr/last-run; --session <id> names a run dir. Both load that run’s conversation.json, append the new user turn, and hand the whole history to the model - which now sees its own prior answer as context, exactly like a real chat.
The interesting design call is the frozen system prompt. The original workspace context lives as the first message in conversation.json, and on continuation kodr reuses it verbatim rather than repacking the workspace. Repacking would be “more correct” after a --yes applied changes, but it double-feeds the file map (old context in history, new context prepended) and balloons tokens every turn. Freezing is cheaper, deterministic, and right for the common case - “yes, do that” follow-ups rarely need fresh file context. The resolver also refuses to silently fall back to a fresh run on a bad session id; it throws a clear error, because a quiet fallback would strand a user who expected their history to carry.
The chain links cleanly:
fresh run A: sessionId=A, parentRunDir=null
→ continue to B: sessionId=A, parentRunDir=A
→ continue to C: sessionId=A, parentRunDir=B
Phase 44: look back
The trilogy’s third act - see what you built:
$ kodr session list
2026-05-29T10-00-00.000Z turns=3 [ok] qwen/qwen3.6-35b-a3b
$ kodr session show 2026-05-29T10-00-00.000Z
Turn 1 [ok] tokens=1234
User: Write a greet.mjs module that says hello
Assistant: Here is the implementation: ...
Turn 2 [ok] tokens=889
User: Also add a farewell function
Assistant: I've updated the module to include...
scanSessions reuses the exact directory-scanning pattern from prompt-history - groups run dirs by sessionId, sorts chronologically, no new infrastructure. Each run’s transcript ends with a [user, assistant] pair, so pulling the last pair gives one clean semantic turn per run; show truncates replies at 120 chars (the full text is always in response.md), and --json returns the lot for tooling.
The nice thing about this trilogy is that it’s all just conversation.json plus chain links - no database, no new format. Which means the ambitious follow-ups (session heal to re-run a failing turn, session diff to compare two branches that diverged at a choice) compose directly on top, no infrastructure required. Persistence as plain files keeps paying out.
Links:
- Phase docs: 42-conversation-transcripts, 43-session-continuation, 44-session-browsing
- Session scanning: src/run-history.mjs
- The run and continuation flow: src/app.mjs