A windowed conversation view
Proposal for Durable: the view holds a window of the newest entries with a stable start; older entries are fetched in pages; a reconnect resumes after the last entry it saw.
type ConversationView = {
conversation: ConversationRecord;
head?: EntryRecord; // the current compaction summary, if any
entries: readonly EntryRecord[]; // the window: active entries with id >= from
earlier: boolean; // older active entries exist before it
docs: { "pi.live", "pi.agent", "pi.inbox", "pi.provider", "pi.usage" };
};
conversation.watch(context, { from?: EntryId, tail?: number }); // default: the last `tail`
conversation.entries({ before, order: "descending" }, limit, cursor, ctx); // exists today
Watch it work
Shown with tail = 4 (the proposal: about 50). The window grows by appends until it holds twice tail; then the oldest half leaves in one splice.
store: every active entry
client: view window (blue), kept or fetched (purple)
window 0 entries
ops sent 0
last snapshot -
What each step costs
| step | wire |
|---|---|
| a new entry | one append splice: p entries <len> 0 [entry] |
| window reaches 2 × tail | one splice removing the oldest half: p entries 0 <tail> [], once every tail entries |
| streaming into the newest message | appends on docs["pi.live"].generation.message, unchanged |
| hold, re-hold, overflow | a snapshot of at most 2 × tail entries plus the live documents, never the whole transcript |
| scroll up | a page from entries(), outside the view: the client keeps it, the view never grows |
reconnect with from: lastSeen + 1 | only the entries it missed plus the documents (issue #11327) |
Why a stable start instead of “always the last 50”: a sliding window would remove one entry at the front for every entry appended at the back, two ops per entry, and every index in the window would shift. With a stable start, entries only append, and the front moves in one splice now and then.