feat(context): static-doc compression — prune CLAUDE.md/AGENTS.md doc entries

The big win the pipeline missed: projectProfile + agentInstructions doc dumps
(~14k chars) are L0-SYSTEM -> STATIC -> never pruned, re-sent verbatim every turn.
Now pruned via the same TokenPruner at a gentler ratio (keep 60% vs 45% freeform),
protected spans (code fences/paths/keys) kept so directives survive. Gated by
TOKEN_PRUNE (level 3+); base systemPrompt stays verbatim.
This commit is contained in:
2026-07-01 14:57:19 +04:00
parent e3743835ec
commit cb4e41a59f
2 changed files with 38 additions and 2 deletions
@@ -51,7 +51,13 @@ class DefaultContextPackBuilder(
const val CHARS_PER_TOKEN = 4 const val CHARS_PER_TOKEN = 4
const val FACT_SHEET_ORDINAL = -1 const val FACT_SHEET_ORDINAL = -1
const val DEFAULT_PRUNE_RATIO = 0.45 const val DEFAULT_PRUNE_RATIO = 0.45
const val DOC_PRUNE_RATIO = 0.6
const val DOC_MIN_CHARS = 1200
const val TIER0_TURNS = 3 const val TIER0_TURNS = 3
// Large static instruction docs (CLAUDE.md project profile, AGENTS.md / DOX framework)
// injected verbatim every turn. Pruned at DOC_PRUNE_RATIO — gentler than freeform, with
// protected spans (code fences, paths, config keys) kept, so directives survive.
val DOC_SOURCE_TYPES = setOf("projectProfile", "agentInstructions")
} }
override suspend fun build( override suspend fun build(
@@ -142,11 +148,19 @@ class DefaultContextPackBuilder(
.map { it.ordinal }.sortedDescending() .map { it.ordinal }.sortedDescending()
val tier0 = if (tierSplit) freeformOrdinals.take(TIER0_TURNS).toSet() else emptySet() val tier0 = if (tierSplit) freeformOrdinals.take(TIER0_TURNS).toSet() else emptySet()
return entries.map { entry -> return entries.map { entry ->
if (classifier.classify(entry) != ContextClass.FREEFORM || entry.ordinal in tier0) entry val isFreeform = classifier.classify(entry) == ContextClass.FREEFORM && entry.ordinal !in tier0
else reencode(entry, tokenPruner.prune(entry.content, tagger.protectedSpans(entry.content), DEFAULT_PRUNE_RATIO)) val isLargeDoc = entry.sourceType in DOC_SOURCE_TYPES && entry.content.length >= DOC_MIN_CHARS
when {
isFreeform -> reencode(entry, tokenPruner.prune(entry.content, spansOf(entry), DEFAULT_PRUNE_RATIO))
// Static instruction docs: gentler ratio so directives survive the classifier.
isLargeDoc -> reencode(entry, tokenPruner.prune(entry.content, spansOf(entry), DOC_PRUNE_RATIO))
else -> entry
}
} }
} }
private fun spansOf(entry: ContextEntry) = tagger.protectedSpans(entry.content)
private fun applyToMe(entries: List<ContextEntry>): List<ContextEntry> { private fun applyToMe(entries: List<ContextEntry>): List<ContextEntry> {
val (freeform, rest) = entries.partition { classifier.classify(it) == ContextClass.FREEFORM } val (freeform, rest) = entries.partition { classifier.classify(it) == ContextClass.FREEFORM }
return (rest + toMeMerger.merge(freeform)).sortedBy { it.ordinal } return (rest + toMeMerger.merge(freeform)).sortedBy { it.ordinal }
@@ -137,6 +137,28 @@ class CompressionPipelineStagesTest {
Unit Unit
} }
@Test
fun `large static doc entries are pruned at level 3 while base system prompt is untouched`() = kotlinx.coroutines.runBlocking {
val pruner = object : com.correx.core.context.compression.TokenPruner {
override suspend fun prune(content: String, protectedSpans: List<String>, targetRatio: Double): String = "DOC-PRUNED"
}
val builder = com.correx.core.context.builder.DefaultContextPackBuilder(
com.correx.core.context.compression.DefaultContextCompressor(), CompressionPolicy(3), tokenPruner = pruner,
)
val bigDoc = "# CLAUDE.md\n" + "guidance line ".repeat(120) // > DOC_MIN_CHARS
val entries = listOf(
entry("proj", ContextLayer.L0, EntryRole.SYSTEM, bigDoc).copy(sourceType = "projectProfile"),
entry("sys", ContextLayer.L0, EntryRole.SYSTEM, "you are an agent").copy(sourceType = "systemPrompt"),
)
val flat = builder.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),
).layers.values.flatten()
assertEquals("DOC-PRUNED", flat.first { it.sourceType == "projectProfile" }.content)
assertEquals("you are an agent", flat.first { it.sourceType == "systemPrompt" }.content)
Unit
}
@Test @Test
fun `classifier separates static structured freeform`() { fun `classifier separates static structured freeform`() {
val c = ContextClassifier() val c = ContextClassifier()