diff --git a/core/router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt b/core/router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt index b017e34b..0e86e9ca 100644 --- a/core/router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt +++ b/core/router/src/main/kotlin/com/correx/core/router/RouterContextBuilder.kt @@ -55,10 +55,18 @@ class DefaultRouterContextBuilder( operator understand and steer what the engine is doing. Ground every statement in the workflow state and conversation context you are given. - Never invent stages, tool results, file contents, or status you have not been shown; - if something is not in context, say you do not have it. Be concise and direct — plain - sentences, no filler. When the operator is steering an approval, help them state - clearly what they want changed. + Never invent stages, tool results, file contents, or status you have not been shown. + Be concise and direct — plain sentences, no filler. When the operator is steering + an approval, help them state clearly what they want changed. + + When the operator's request is not something you can answer or steer directly: + do not simply refuse. Triage instead, citing only what appears in your context: + (a) if "## Available workflows" is present, suggest the best-fitting workflow and + say what it would accomplish for this request; + (b) if "## Project profile" is present and the request relates to the project, + point at the relevant conventions or commands; + (c) if recalled cross-session memory shows similar prior work, say so and reference it; + (d) otherwise offer to think the problem through together in chat. """.trimIndent() private val NARRATION_SYSTEM_PROMPT = diff --git a/testing/deterministic/src/test/kotlin/RouterContextBuilderTest.kt b/testing/deterministic/src/test/kotlin/RouterContextBuilderTest.kt index d6a46a69..221b191f 100644 --- a/testing/deterministic/src/test/kotlin/RouterContextBuilderTest.kt +++ b/testing/deterministic/src/test/kotlin/RouterContextBuilderTest.kt @@ -130,7 +130,8 @@ class RouterContextBuilderTest { ), ) // Budget allows L0 + 2 long conversation turns; oldest 2 turns are dropped - val pack = runBlocking { builderWide.build(state, TokenBudget(limit = 500)) } + // Budget increased from 500 to 600 to account for larger system prompt with triage directive + val pack = runBlocking { builderWide.build(state, TokenBudget(limit = 600)) } val l1Entries = pack.layers[ContextLayer.L1] ?: emptyList() // Only the last 2 turns (oldest-first drop) should remain assertEquals(2, l1Entries.size) @@ -841,6 +842,15 @@ class RouterContextBuilderTest { assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" }) } + @Test + fun `system prompt contains triage directive not bare refusal`() { + val pack = buildPack(RouterState(), TokenBudget(limit = 10000)) + val content = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" }!!.content + assertFalse(content.contains("if something is not in context, say you do not have it")) + assertTrue(content.contains("Available workflows", ignoreCase = true)) + assertTrue(content.contains("project profile", ignoreCase = true)) + } + @Test fun `truncatedLayers populated with affected layers when drops occur`() { val longText = "x".repeat(800)