feat: durable SQLite event store for session persistence#79
Merged
Conversation
Durable append-only event log backed by better-sqlite3. Every v2 protocol event will be persisted with a global sequence number, enabling deterministic replay on reconnect instead of fragile in-memory buffers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sendOrBuffer now appends every v2 event to the durable store before sending over WebSocket. Each event gets a monotonic seq number injected into the payload. Store parameter is optional for backward compatibility — existing callers without a store continue to work. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create EventStore instance at startup backed by .mitzo/events.db. Pass store to runQueryLoop, which now calls upsertSession() when the SDK assigns a session ID and markSessionInactive() when the query loop ends. Sessions appear in the durable store immediately on creation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a client reattaches with lastSeq, the server replays all events after that sequence number from the durable EventStore instead of relying on the in-memory detached buffer. Falls back to buffer-based replay for clients that don't send lastSeq (backward compat). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GET /api/sessions/:id/events?after=N returns all v2 events after the given sequence number. This provides an HTTP fallback for event replay when WebSocket reattach isn't possible. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pool entries now track the highest seq seen from any v2 event. On reconnect, lastSeq is included in the reattach request so the server can replay only missed events from the durable store. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove all localStorage read/write for message caching and the restoredViaReattach flag. Session restore now relies solely on the API (backed by the durable event store) — no more stale cache races. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Messages arriving while unmounted are now dropped — recovery happens via seq-based replay from the durable event store on reconnect. Removes messageBuffer, wsDrainBuffer, BUFFERABLE_TYPES, WS_MAX_BUFFER_SIZE, and CHAT_CACHE_KEY_PREFIX. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
With the durable event store, detached sessions no longer need in-memory buffering — missed events replay from SQLite on reattach. Removes bufferDetached(), drainDetachedBuffer(), DETACHED_BUFFER_MAX. Increases DETACHED_TTL_MS from 10min to 1hr since detached sessions are now cheap (no buffer memory pressure). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getMessages() now replays v2 events from the durable event store as the primary path. Falls back to SDK JSONL reconstruction for sessions that predate the event store migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cast WsMsg via unknown for seq field access. Use registry.get()! directly instead of casting to Mock type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
better-sqlite3, WAL mode) that durably persists every v2 protocol event with a global monotonic sequence numberlastSeqand sends it on reconnect — server replays only missed events from the store, replacing four fragile recovery paths (in-memory buffer, detached buffer, localStorage cache, SDK JSONL reconstruction)restoredViaReattachflag) and increases detach TTL from 10min to 1hrWhat changed
EventStoreclass,append()in query loop, seq-based reattach replay, events API endpoint,replayEventsToMessages()detachedBuffer,bufferDetached(),drainDetachedBuffer(),DETACHED_BUFFER_MAXlastSeqtracking in WS poolmessageBuffer,wsDrainBuffer(),BUFFERABLE_TYPES,WS_MAX_BUFFER_SIZE, localStorage cache,restoredViaReattachHow it fixes each failure mode
Test plan
:memory:DB)🤖 Generated with Claude Code