Skip to content

[EPIC-W][LOU-W9] Sessions checkpoint every step and resume a crashed turn - #89

Merged
LinuxDevil merged 2 commits into
mainfrom
lou-w9-sessions-checkpoint
Oct 1, 2026
Merged

LinuxDevil merged 2 commits into
mainfrom
lou-w9-sessions-checkpoint

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What

  • agent.session({ id, store, checkpointStore? }): with a checkpointStore, or a store that carries both stores ({ sessions, checkpoints }, which SqliteStore matches), every turn runs with sessionId: '<id>.turn-<n>' (n = transcript length when the turn started) and that store, so it is checkpointed after each model response and tool result. stream() does the same.
  • session.resume() finishes an interrupted turn through the executor's existing resume path (input: [] on the same sessionId). Recorded tool calls and model responses are not run again. The finished turn is added to the transcript exactly as send() adds it, and the turn's checkpoint is deleted. It returns the result, or null when nothing is pending.
  • session.pending() returns { status: 'in-progress' | 'awaiting-approval', approvalId? } or null. session.discardPending() drops an unfinished turn.
  • send() / stream() while a turn is pending: the pending turn is resumed first, then the new message is sent.
  • Approvals: a checkpointed turn that pauses stays in its checkpoint, not in the transcript, until it finishes. While it waits, resume() / send() throw SessionAwaitingApprovalError and bind the approval to the session. agent.approvals.resolve() then continues the turn in that session and passes the session's checkpointStore to resumeAfterApproval().
  • If a turn finished but the process died before the transcript was saved, the next resume() / send() / pending() adds that turn to the transcript instead of running it again. An aborted turn deletes its checkpoint, so abort semantics are unchanged.
  • Docs: new "Durable sessions" section in docs/sessions.md, a cross-link from docs/durable-execution.md, a shorter README "Durable sessions with SQLite" example, a CHANGELOG entry under Added, and regenerated llms-full.txt.

Why

Epic W, AUDIT §7 "Durability / resume" row and differentiator 1 (host-agnostic durable sessions). Sessions saved the transcript only at the end of a turn, and durable execution was not connected to them, so a crash in the middle of a turn inside a session lost that turn.

Acceptance criteria

  • agent.session({ id, store, checkpointStore? }), plus { sessions, checkpoints }-shaped store (SqliteStore), runs every turn with a derived per-turn sessionId + checkpointStore
  • session.resume() continues an in-progress turn via input: [] without re-running finished tools, appends the completed turn like send() and returns its result. It throws SessionAwaitingApprovalError when the turn is awaiting approval (resolvable with agent.approvals.resolve()) and returns null when nothing is pending. A session.pending() helper is added.
  • A new send() / stream() while a turn is pending resumes it first, then runs the new input (documented)
  • Tests (mockModel + KV / in-memory / SQLite stores): a crash via PropagatingToolError on the second step and a failing checkpoint write, each resumed in a fresh createAgent + session; model.calls.length shows finished steps are not re-called; the session store holds the full turn once; an approval pause resolves through approvals.resolve() after a restart; the stream() path checkpoints too
  • Docs cross-linked, llms regenerated, CHANGELOG (Added), README example shortened and still type-checked
  • Scope: src/session/*, session() wiring and the resume lambda in src/createAgent.ts, src/createAgentApprovals.ts, docs. The executor, resume.ts, subagents, security, utils and package.json are untouched. Non-test source diff: +202/-30.

How verified

  • npx tsc --noEmit: no errors
  • npm run lint: 0 errors, 420 warnings. The changed files have 0 warnings, so the total is unchanged from main.
  • npm run test:types: no type errors
  • npx vitest run: 147 test files passed; 2104 tests passed, 3 skipped
  • npm run build && npm run build --workspace=packages/create-loushy-agent: both succeed
  • npm run docs:verify-snippets -- --skip-build: all 103 snippets type-check; 6 also run cleanly
  • npm run docs:llms:check: up to date
  • npm run test:coverage: passes (147 files, 2104 tests). npm run fallow: "No issues found". The one duplicate it lists (AnthropicProvider / OpenAIProvider) was already on main.

Left for W9.2: fingerprint-on-resume (detecting that the agent config changed between the crash and the resume).

🤖 Generated with Claude Code

https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR


Generated by Claude Code

claude added 2 commits October 1, 2026 14:33
agent.session({ id, store, checkpointStore }) (or a { sessions, checkpoints }
store such as SqliteStore) runs each turn with sessionId '<id>.turn-<n>' and
that CheckpointStore, so the turn is checkpointed after every model response
and tool result. session.resume() finishes an interrupted turn through the
executor's resume path (input: []) without re-running recorded steps, adds it
to the transcript as send() would and returns its result (null when nothing
is pending); pending() reports an unfinished turn and discardPending() drops
it. send()/stream() resume a pending turn first. A turn paused for approval
stays in its checkpoint until it finishes; resume()/send() throw
SessionAwaitingApprovalError and agent.approvals.resolve() continues the turn
in the session, passing the session's checkpointStore to
resumeAfterApproval().

Docs: "Durable sessions" in docs/sessions.md, cross-link from
docs/durable-execution.md, shorter README SQLite example, CHANGELOG, llms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
…point

# Conflicts:
#	CHANGELOG.md
#	src/createAgent.ts
@LinuxDevil
LinuxDevil merged commit e3352e2 into main Oct 1, 2026
1 check passed
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.

2 participants