feat(recovery,context): tier-2 intent-holder arbiter + remaining-delta pinning + surfaced signal events
- recovery: two-tier repair ladder — owner then arbiter (recovery stage recast
as intent-holder, holds initial intent, reconciles cross-file contract disputes)
with independent INTENT_ROUTE_BUDGET; RecoveryRoutingTest coverage
- context: remaining-delta checklist pinning (RemainingDelta{Pinning,Entry}Test)
- surface write-only events (session name, quality signals) into stats/browse
- parseToolArguments lenient-Json fallback for bare-key drift
- tools: ChildProcess seam
This commit is contained in:
+4
-1
@@ -54,7 +54,10 @@ class DefaultContextPackBuilder(
|
||||
// decisionJournal is deliberately NOT here anymore: the journal is OPTIONAL-bucket, capped
|
||||
// by OptionalCeilings. Pinning is otherwise bucket-driven (ContextBucket.REQUIRED); this set
|
||||
// only covers entries built outside the orchestrator that never stamp buckets.
|
||||
private val neverDropSourceTypes = setOf("steeringNote", "eventHistory", "factSheet")
|
||||
// "remainingDelta" is the shrinking stage-contract checklist (stage-termination design
|
||||
// 2026-07-11): it must survive every budget/dedup pass, since its entire purpose is to give
|
||||
// the model a progress signal that outlives the truncation which otherwise wipes its memory.
|
||||
private val neverDropSourceTypes = setOf("steeringNote", "eventHistory", "factSheet", "remainingDelta")
|
||||
|
||||
private companion object {
|
||||
const val CHARS_PER_TOKEN = 4
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import com.correx.core.context.builder.DefaultContextPackBuilder
|
||||
import com.correx.core.context.compression.DefaultContextCompressor
|
||||
import com.correx.core.context.model.ContextEntry
|
||||
import com.correx.core.context.model.ContextLayer
|
||||
import com.correx.core.context.model.TokenBudget
|
||||
import com.correx.core.events.types.ContextEntryId
|
||||
import com.correx.core.events.types.ContextPackId
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* The remaining-delta checklist (stage-termination design 2026-07-11) must survive the budget/dedup
|
||||
* passes — its whole point is to outlive the truncation that gives the model amnesia. It is pinned
|
||||
* via sourceType "remainingDelta" in [DefaultContextPackBuilder.neverDropSourceTypes].
|
||||
*/
|
||||
class RemainingDeltaPinningTest {
|
||||
|
||||
private val builder = DefaultContextPackBuilder(DefaultContextCompressor())
|
||||
private val sessionId = SessionId("s1")
|
||||
private val stageId = StageId("stage-1")
|
||||
private val packId = ContextPackId("pack-1")
|
||||
|
||||
@Test
|
||||
fun `remainingDelta entry survives a wildly over-budget build`() = kotlinx.coroutines.runBlocking {
|
||||
val checklist = ContextEntry(
|
||||
id = ContextEntryId("delta-1"),
|
||||
layer = ContextLayer.L1,
|
||||
content = "## Remaining to finish this stage\n- [ ] Foo.tsx — exports_default_component",
|
||||
sourceType = "remainingDelta",
|
||||
sourceId = "remaining-delta",
|
||||
tokenEstimate = 500,
|
||||
)
|
||||
// A large freeform entry that would be dropped to fit the tiny budget.
|
||||
val filler = ContextEntry(
|
||||
id = ContextEntryId("filler-1"),
|
||||
layer = ContextLayer.L2,
|
||||
content = "lorem ipsum ".repeat(200),
|
||||
sourceType = "toolResult",
|
||||
sourceId = "filler-1",
|
||||
tokenEstimate = 5000,
|
||||
)
|
||||
val pack = builder.build(packId, sessionId, stageId, listOf(filler, checklist), TokenBudget(limit = 10))
|
||||
val retained = pack.layers.values.flatten()
|
||||
assertTrue(
|
||||
retained.any { it.sourceType == "remainingDelta" },
|
||||
"remainingDelta checklist must be retained even when the budget is blown",
|
||||
)
|
||||
Unit
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user