You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Anonymous prefix affinity (#309/#316/#499, src/prefix-affinity.ts) resolves header-less clients by longest-prefix match over an append-only hash chain (h_i = sha256(h_{i-1} || msg_i)). It deliberately mirrors vLLM/SGLang radix prefix caching at the identity layer.
Current fork semantics (documented in the file header): when a request's history diverges from every stored chain, it forks into a brand-new session with zero compression state. The discarded parent candidates are recorded in lineage (parents[], reason: "forked"|"truncated") — but lineage is UI/debug only, never used for matching or state inheritance (MIN_FORK_PREFIX = 3, comment says so explicitly).
The gap
A fork (user edits history, rewinds, or two parallel conversations share a common opening) throws away every compression block anchored inside the shared prefix:
the parent session may have folded 100K tokens of that prefix into blocks;
the forked branch re-sends the same bytes raw and must re-compress from scratch (nudge starts at ~0% accumulated);
after compression, the branch's blocks duplicate the parent's coverage over identical content.
This is the classic radix-tree property that prefix-affinity stops short of: identity is content-addressed, but compression state is not — it lives per session (kernel StateStore, blocks owned by one session id).
Proposal (discussion)
Make the fold state effectively tree-shaped so a fork inherits the folds anchored inside its shared prefix:
On fork detection (existing lineage machinery), copy/adopt the parent session's blocks whose effectiveMessageIds fall entirely inside the shared prefix (items 0..lcp-1) into the new session, remapping anchors.
Keep per-session ownership for persistence — this is a one-time adoption at fork time, not shared live state (avoids two branches mutating one block).
Reuse the existing per-item hash chain as the prefix-addressing mechanism (MAX_STORED_ITEMS = 128 currently caps it — may need raising or a separate block-anchor index keyed by item hash).
Background
Anonymous prefix affinity (#309/#316/#499,
src/prefix-affinity.ts) resolves header-less clients by longest-prefix match over an append-only hash chain (h_i = sha256(h_{i-1} || msg_i)). It deliberately mirrors vLLM/SGLang radix prefix caching at the identity layer.Current fork semantics (documented in the file header): when a request's history diverges from every stored chain, it forks into a brand-new session with zero compression state. The discarded parent candidates are recorded in
lineage(parents[],reason: "forked"|"truncated") — but lineage is UI/debug only, never used for matching or state inheritance (MIN_FORK_PREFIX = 3, comment says so explicitly).The gap
A fork (user edits history, rewinds, or two parallel conversations share a common opening) throws away every compression block anchored inside the shared prefix:
This is the classic radix-tree property that prefix-affinity stops short of: identity is content-addressed, but compression state is not — it lives per session (
kernel StateStore, blocks owned by one session id).Proposal (discussion)
Make the fold state effectively tree-shaped so a fork inherits the folds anchored inside its shared prefix:
effectiveMessageIdsfall entirely inside the shared prefix (items0..lcp-1) into the new session, remapping anchors.MAX_STORED_ITEMS = 128currently caps it — may need raising or a separate block-anchor index keyed by item hash).Open questions
/compact-adjacent flows, web UIs with regenerate.restoreOutputBudget-style per-session metadata (must NOT inherit).Spawned from the #619 discussion — orthogonal to its remaining decode-fail scope (PR #622).