feat: inject recalled L3 memory into router context with budget

Record L3 retrieval as an event carrying hit text (invariant #9), then
rebuild router state and inject recalled memories as a SYSTEM L3 layer in
the context pack. Apply token budget: protected frames (L0 immutable +
current user turn) are never dropped; honest budgetUsed is reported and
'BudgetExceeded' is flagged in appliedStrategies when they overflow. L1/L2
fit newest-first so oldest entries drop first. Emit ContextTruncatedEvent
when entries are dropped. L3MemoryRetrievedEvent is emitted on every CHAT
turn (empty hits reset recalled memory).
This commit is contained in:
2026-05-30 18:40:08 +04:00
parent e6239515bd
commit 8e6a3e1470
6 changed files with 852 additions and 77 deletions
@@ -37,3 +37,13 @@ data class L3RetrievedHit(
val text: String,
val score: Float,
)
@Serializable
@SerialName("ContextTruncated")
data class ContextTruncatedEvent(
val sessionId: SessionId,
val turnId: String,
val entriesDropped: Int,
val truncatedLayers: List<String>,
val timestampMs: Long,
) : EventPayload
@@ -9,6 +9,7 @@ import com.correx.core.events.events.ArtifactValidatedEvent
import com.correx.core.events.events.ArtifactValidatingEvent
import com.correx.core.events.events.ChatSessionStartedEvent
import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.events.ContextTruncatedEvent
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.L3MemoryRetrievedEvent
import com.correx.core.events.events.InferenceCompletedEvent
@@ -75,6 +76,7 @@ val eventModule = SerializersModule {
subclass(ChatSessionStartedEvent::class)
subclass(ChatTurnEvent::class)
subclass(L3MemoryRetrievedEvent::class)
subclass(ContextTruncatedEvent::class)
}
}