feat(context): compression pipeline stage 2 — fact sheet + strict allocation

FactSheet derives load-bearing spans (newest-first, capped) and pins them as an
L0 SYSTEM entry never dropped by the compressor (level 7). Event-derived each
assembly, not a persisted file, per invariant #1. Strict budget allocation
(level 9) compresses structured groups before freeform so conversation can't
starve tool logs / artifacts.
This commit is contained in:
2026-07-01 13:56:09 +04:00
parent 61be90070f
commit b50688df1d
3 changed files with 112 additions and 4 deletions
@@ -2,6 +2,7 @@ import com.correx.core.context.compression.CompressionPolicy
import com.correx.core.context.compression.CompressionStage
import com.correx.core.context.compression.ContextClass
import com.correx.core.context.compression.ContextClassifier
import com.correx.core.context.compression.FactSheet
import com.correx.core.context.compression.FormatCompressor
import com.correx.core.context.compression.ProtectedSpanTagger
import com.correx.core.context.model.ContextEntry
@@ -67,6 +68,38 @@ class CompressionPipelineStagesTest {
assertEquals("log line\nlog line\nother", out)
}
@Test
fun `fact sheet extracts newest-first and caps size`() {
val fs = FactSheet(ProtectedSpanTagger(), maxFacts = 2)
val entries = listOf(
entry("chat", ContextLayer.L2, EntryRole.USER, "old 1.1.1.1").copy(ordinal = 0),
entry("chat", ContextLayer.L2, EntryRole.USER, "new 2.2.2.2 and 3.3.3.3").copy(ordinal = 1),
)
val facts = fs.extract(entries)
// newest entry's spans come first; cap of 2 evicts the oldest entry's IP
assertEquals(listOf("2.2.2.2", "3.3.3.3"), facts)
}
@Test
fun `builder pins a fact sheet at level 7 and omits it below`() {
val entries = listOf(entry("chat", ContextLayer.L1, EntryRole.USER, "deploy to 10.0.0.1"))
val compressor = com.correx.core.context.compression.DefaultContextCompressor()
fun buildAt(level: Int) = com.correx.core.context.builder.DefaultContextPackBuilder(
compressor, CompressionPolicy(level)
).build(
com.correx.core.events.types.ContextPackId("p"),
com.correx.core.events.types.SessionId("s"),
com.correx.core.events.types.StageId("st"),
entries,
com.correx.core.context.model.TokenBudget(limit = 4000),
)
val atSeven = buildAt(7).layers.values.flatten()
assertTrue(atSeven.any { it.sourceType == "factSheet" && it.content.contains("10.0.0.1") })
assertFalse(buildAt(6).layers.values.flatten().any { it.sourceType == "factSheet" })
}
@Test
fun `classifier separates static structured freeform`() {
val c = ContextClassifier()