diff --git a/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestratorContext.kt b/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestratorContext.kt index 4d106a0d..8e78ade6 100644 --- a/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestratorContext.kt +++ b/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestratorContext.kt @@ -52,11 +52,21 @@ internal fun SessionOrchestrator.evictArtifactContentCache(sessionId: SessionId) internal suspend fun SessionOrchestrator.buildSchemaEntries( responseFormat: ResponseFormat, stageId: StageId, + artifactName: String, ): List { if (responseFormat !is ResponseFormat.Json) return emptyList() val compactSchema = Json.encodeToString(JsonSchema.serializer(), responseFormat.schema) - val instruction = "Respond with a single JSON object matching this schema. " + - "Do not include markdown, code fences, or commentary outside the JSON. " + + // #416: names emit_artifact as the channel. ResponseFormat.Json is emitted under exactly the + // condition that offers the tool (an llmEmitted slot — see emitArtifactTool), so the tool is + // always available here, and the kernel prefers it: tool-calling models are more reliable at it + // and it sidesteps llama.cpp's grammar+tools incompatibility. The old text said only "respond + // with a single JSON object", contradicting every role prompt that says to call emit_artifact. + // Raw JSON stays valid as the second sentence describes, since the executor accepts either + // (llmArtifactOverride ?: response.text) and the tools-less final pass explicitly demands it. + val instruction = "Produce the '$artifactName' artifact by calling the $EMIT_ARTIFACT_TOOL tool " + + "with its fields filled in, matching this schema. If you are told to stop calling tools, " + + "output the same object as a single JSON object in your final message instead. Either way, " + + "no markdown, no code fences, and no commentary outside the JSON. " + "Schema: $compactSchema" return listOf( ContextEntry( diff --git a/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestratorExecution.kt b/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestratorExecution.kt index b4403a0a..83b46a10 100644 --- a/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestratorExecution.kt +++ b/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestratorExecution.kt @@ -152,7 +152,11 @@ internal suspend fun SessionOrchestrator.executeStage( ?.let { ResponseFormat.Json(it.kind.deriveJsonSchema()) } ?: ResponseFormat.Text - val schemaEntries = buildSchemaEntries(responseFormat, stageId) + val schemaEntries = buildSchemaEntries( + responseFormat, + stageId, + llmEmittedSlots.firstOrNull()?.name?.value ?: "", + ) val intentEntries = buildIntentEntry(sessionId) val steeringEntries = buildSteeringNoteEntries(sessionId) val clarificationEntries = buildClarificationAnswerEntries(sessionId)