feat(context,kernel): compression pipeline stage 3 — replay-safe tier summarizer

ContextSummarizedEvent (registered) keyed on stable session sequence, not the
ephemeral pack ordinal, so replay reuses the recorded summary artifact and never
re-calls the LLM (invariants #6/#8). TierContextSummarizer mirrors
JournalCompactionService for freeform context (tool outputs/docs/retrieved text)
the journal path doesn't cover. ContextState + DefaultContextReducer fold the
event; reducer replay-safety unit-tested.
This commit is contained in:
2026-07-01 14:03:47 +04:00
parent b50688df1d
commit e0c222392c
6 changed files with 151 additions and 1 deletions
@@ -0,0 +1,45 @@
import com.correx.core.context.DefaultContextReducer
import com.correx.core.context.state.ContextState
import com.correx.core.events.events.ContextSummarizedEvent
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class ContextSummaryReducerTest {
private val reducer = DefaultContextReducer()
private fun stored(payload: ContextSummarizedEvent, seq: Long) = StoredEvent(
metadata = EventMetadata(
eventId = EventId("e$seq"),
sessionId = SessionId("s"),
timestamp = Instant.fromEpochSeconds(0),
schemaVersion = 1,
causationId = null,
correlationId = null,
),
sequence = seq,
sessionSequence = seq,
payload = payload,
)
@Test
fun `latest summary event wins and records stable coverage`() {
// Replaying the same events must yield the same state — coverage is keyed on the stable
// event sequence, so no live LLM call is needed to rebuild (invariant #8).
var state = ContextState()
state = reducer.reduce(state, stored(
ContextSummarizedEvent(SessionId("s"), 10L, ArtifactId("a1"), tier = 2, entriesOmittedCount = 5), 1L))
state = reducer.reduce(state, stored(
ContextSummarizedEvent(SessionId("s"), 25L, ArtifactId("a2"), tier = 3, entriesOmittedCount = 8), 2L))
assertEquals(25L, state.summarizedThroughSequence)
assertEquals(ArtifactId("a2"), state.summaryArtifactId)
assertEquals(3, state.summaryTier)
}
}