feat(context): render retry repair mandate as final user turn (#293)
buildRetryFeedbackEntry was L1/SYSTEM, so PromptRenderer folded it into the leading system block — far from the assistant/tool transcript and weaker than the original stage task. Flip it to USER role and give the renderer an explicit trailing repair-mandate slot (a sourceType set, extensible for recovery later): repair mandates are lifted out of the inline flow and emitted once as the final message, after the tool evidence and the steering anchor. retryFeedback is already in REQUIRED_SOURCE_TYPES so it stays unprunable. Golden renderer test proves the final message is the repair USER mandate and leading system no longer carries it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,30 @@ class PromptRendererOrderingTest {
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retry repair mandate renders as the final user turn after the tool transcript`() = kotlinx.coroutines.runBlocking {
|
||||
// #293: even though the repair mandate is stamped mid-transcript by input order, the renderer
|
||||
// must lift it to the very end (after assistant/tool evidence) and never fold it into system.
|
||||
val builder = DefaultContextPackBuilder(DefaultContextCompressor())
|
||||
val entries = listOf(
|
||||
entry("sys", ContextLayer.L0, EntryRole.SYSTEM, "systemPrompt"),
|
||||
entry("task", ContextLayer.L1, EntryRole.USER, "agentPrompt"),
|
||||
entry("repair", ContextLayer.L1, EntryRole.USER, "retryFeedback"),
|
||||
entry("call1", ContextLayer.L2, EntryRole.ASSISTANT, "assistantToolCall", sourceId = "tool1"),
|
||||
entry("result1", ContextLayer.L2, EntryRole.TOOL, "toolResult", sourceId = "tool1"),
|
||||
)
|
||||
val pack = builder.build(ContextPackId("p"), sessionId, stageId, entries, TokenBudget(limit = 4000))
|
||||
|
||||
val messages = PromptRenderer.render(pack)
|
||||
assertEquals("user" to "repair", messages.last().role to messages.last().content)
|
||||
assertEquals(1, messages.count { it.content == "repair" }, "mandate must appear exactly once")
|
||||
org.junit.jupiter.api.Assertions.assertFalse(
|
||||
messages.first().content.contains("repair"),
|
||||
"leading system must not carry the repair mandate",
|
||||
)
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `without ordinals the live user turn still renders last (router chat fallback)`() {
|
||||
// Packs built outside DefaultContextPackBuilder (router chat) leave ordinal at 0;
|
||||
|
||||
Reference in New Issue
Block a user