feat(context): role means message type, layer means pinning (#312/#313)
The system block is now for content that does not change during a run. Anything the run mutates renders as a user message — both because a mutating system prefix defeats prompt caching and because models under-weight system-folded content against the trailing user turn. PromptRenderer: role alone decides the message type; the old `layer == L0 ||` clause is gone. That clause was silently overriding role on four packs (InferenceSummarizer, SemanticReviewerImpl, CapabilityGapReflectorImpl, Talkie session-naming) which are L0+USER prompts — they were rendering as a system-only request with no user turn at all. Re-roled SYSTEM -> USER, all mutable within a run: recoveryTicket, remainingDelta, groundingFeedback, rejectionFeedback (trailing slot), plus verifiedBaseline, promotedConcept, claimedTask, clarificationAnswer, locked steeringNote, factSheet (inline, still L0-pinned). Left SYSTEM (immutable): systemPrompt, operatingGuidance, schemaInstruction, projectProfile, operatorProfile, agentInstructions, successfulPlanShape. Trailing-slot scarcity guard: repairMandateSourceTypes joined ALL matches, so a recovery stage on a retry with an unmet delta would stack three competing mandates. Now a precedence list emits exactly one (recoveryTicket > retryFeedback > groundingFeedback > rejectionFeedback) with remainingDelta appended as the completion signal. ContextClassifier keys STATIC on layer alone — L0 means pinned/never pruned regardless of role, so the re-roled L0 entries don't fall through to FREEFORM and get token-pruned. Also fixes a stale ContextFeedbackTest assertion (expected a CAS hash the producer deliberately stopped emitting) and a stale initialIntent doc comment claiming L0/SYSTEM where the code says L1/USER. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ import com.correx.core.transitions.graph.TransitionEdge
|
||||
import com.correx.core.transitions.graph.WorkflowGraph
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -95,10 +96,10 @@ class ContextFeedbackTest {
|
||||
val entry = buildRetryFeedbackEntry(events, StageId("impl"))!!
|
||||
assertTrue(entry.content.contains("## Retry repair state"), "content: ${entry.content}")
|
||||
assertTrue(entry.content.contains("gate 'execution'"), "content: ${entry.content}")
|
||||
assertTrue(
|
||||
entry.content.contains("frontend/src/hooks/queries.ts — CAS cafebabe"),
|
||||
"content: ${entry.content}",
|
||||
)
|
||||
// Path only: the raw CAS hash is opaque noise the model can't act on and confuses it into
|
||||
// reasoning about hashes — it patches by path via file_read/file_write.
|
||||
assertTrue(entry.content.contains("- frontend/src/hooks/queries.ts"), "content: ${entry.content}")
|
||||
assertFalse(entry.content.contains("cafebabe"), "content: ${entry.content}")
|
||||
assertTrue(entry.content.contains("do NOT re-read"), "content: ${entry.content}")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user