Context
V0 maintains each session's cold-path snapshot (SessionRuntime.settled) via a continuous in-memory fold of EventDeltas (packages/daemon/src/messages/fold.ts, foldEventDelta). This is the daemon-side mirror of the iOS transcript reducer (transcript-collection-factory onEvent) — both apply the same deltas the same way, so the daemon's cold snapshot equals what iOS shows live, with no JSONL re-read (no dependency on the SDK's async flush timing, no live/settled divergence).
The deferred bit
compact_started / compact_applied are a deliberate no-op on both sides in V0:
- iOS reducer no-ops compact (divider + prune deferred).
foldEventDelta mirrors that no-op so settled == live.
Re-reading the compacted JSONL on compact_applied would instead prune settled (filter to the post-compaction surviving UUIDs) and thus diverge from iOS's still-unpruned live state — which is why we don't do it today.
Consequence: after a compaction, both the daemon's settled and the iOS transcript keep the full pre-compaction timeline in memory (correct for rendering, but not pruned). No divider is shown.
V0.5+ work
Teach both reducers to prune on compact_applied, kept mirrored so cold == live:
- iOS reducer: on
compact_applied, filter the transcript to preservedUuids and append a compact divider item.
foldEventDelta: apply the same prune + divider — still purely in-memory, still no re-read.
Both must land together to preserve the cold==live invariant. This pairs with the surviving-UUID metadata work in #13 (own getSessionMessages that preserves compactMetadata / subtype / isCompactSummary off the raw JSONL) — the prune needs that metadata to know which UUIDs survived.
Pointers
Context
V0 maintains each session's cold-path snapshot (
SessionRuntime.settled) via a continuous in-memory fold ofEventDeltas (packages/daemon/src/messages/fold.ts,foldEventDelta). This is the daemon-side mirror of the iOS transcript reducer (transcript-collection-factoryonEvent) — both apply the same deltas the same way, so the daemon's cold snapshot equals what iOS shows live, with no JSONL re-read (no dependency on the SDK's async flush timing, no live/settled divergence).The deferred bit
compact_started/compact_appliedare a deliberate no-op on both sides in V0:foldEventDeltamirrors that no-op sosettled == live.Re-reading the compacted JSONL on
compact_appliedwould instead prunesettled(filter to the post-compaction surviving UUIDs) and thus diverge from iOS's still-unpruned live state — which is why we don't do it today.Consequence: after a compaction, both the daemon's
settledand the iOS transcript keep the full pre-compaction timeline in memory (correct for rendering, but not pruned). No divider is shown.V0.5+ work
Teach both reducers to prune on
compact_applied, kept mirrored so cold == live:compact_applied, filter the transcript topreservedUuidsand append a compact divider item.foldEventDelta: apply the same prune + divider — still purely in-memory, still no re-read.Both must land together to preserve the cold==live invariant. This pairs with the surviving-UUID metadata work in #13 (own
getSessionMessagesthat preservescompactMetadata/subtype/isCompactSummaryoff the raw JSONL) — the prune needs that metadata to know which UUIDs survived.Pointers
packages/daemon/src/messages/fold.ts— thedefault:branch comment already documents this deferral.packages/app/src/lib/transcript-collection-factory.ts—onEventcompact handling.