feat(kernel,artifacts): inject artifact-kind vocabulary into architect context

This commit is contained in:
2026-06-11 10:06:40 +04:00
parent beed501d60
commit ed0dca8b78
10 changed files with 96 additions and 7 deletions
@@ -3,6 +3,7 @@ package com.correx.core.kernel.orchestration
// Context-entry builders kept top-level (not orchestrator members) for unit-testability
// and to stay under detekt's function-count threshold (same pattern as isBackEdge).
import com.correx.core.artifacts.kind.ArtifactKind
import com.correx.core.context.model.ContextEntry
import com.correx.core.context.model.ContextLayer
import com.correx.core.context.model.EntryRole
@@ -47,6 +48,24 @@ fun criticArtifactIds(
return graph.stages[fromStage]?.produces?.map { it.name }?.toSet() ?: emptySet()
}
fun buildArtifactKindVocabularyEntry(kinds: Collection<ArtifactKind>): ContextEntry {
val listing = kinds.joinToString(", ") { k ->
if (k.llmEmitted) "${k.id} (llm-emitted)" else k.id
}
val content = "## Available artifact kinds\n" +
"Every stage's \"produces\" MUST be exactly one of: $listing\n" +
"Do not invent kinds — a plan referencing an unknown kind fails to compile."
return ContextEntry(
id = ContextEntryId(UUID.randomUUID().toString()),
layer = ContextLayer.L1,
content = content,
sourceType = "artifactKindVocabulary",
sourceId = "artifact-kinds",
tokenEstimate = content.length / 4,
role = EntryRole.SYSTEM,
)
}
fun buildProjectProfileEntry(profile: BoundProjectProfile): ContextEntry {
val content = buildString {
append("## Project profile\n")
@@ -7,6 +7,7 @@ import com.correx.core.inference.Tokenizer
import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.journal.DecisionJournalRenderer
import com.correx.core.artifacts.ArtifactState
import com.correx.core.artifacts.kind.ArtifactKindRegistry
import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
import com.correx.core.events.events.ApprovalRequestedEvent
@@ -55,7 +56,8 @@ class DefaultSessionOrchestrator(
tokenizer: Tokenizer? = null,
private val decisionJournalRepository: DefaultDecisionJournalRepository,
private val compactionService: JournalCompactionService? = null,
) : SessionOrchestrator(repositories, engines, artifactStore, decisionJournalRepository), ApprovalGateway {
artifactKindRegistry: ArtifactKindRegistry? = null,
) : SessionOrchestrator(repositories, engines, artifactStore, decisionJournalRepository, artifactKindRegistry = artifactKindRegistry), ApprovalGateway {
override val tokenizer: Tokenizer? = tokenizer
override val cancellations: ConcurrentHashMap<SessionId, AtomicBoolean> =
ConcurrentHashMap<SessionId, AtomicBoolean>()
@@ -11,6 +11,7 @@ import com.correx.core.approvals.model.ApprovalDecision
import com.correx.core.approvals.model.ApprovalScopeIdentity
import com.correx.core.approvals.model.DomainApprovalRequest
import com.correx.core.artifacts.ArtifactState
import com.correx.core.artifacts.kind.ArtifactKindRegistry
import com.correx.core.artifacts.kind.JsonSchema
import com.correx.core.artifacts.kind.FileWrittenArtifact
import com.correx.core.artifacts.kind.ProcessResultArtifact
@@ -153,6 +154,7 @@ abstract class SessionOrchestrator(
protected val artifactStore: ArtifactStore,
private val decisionJournalRepository: DefaultDecisionJournalRepository,
private val decisionJournalRenderer: DecisionJournalRenderer = DecisionJournalRenderer(),
private val artifactKindRegistry: ArtifactKindRegistry? = null,
) {
private val log = LoggerFactory.getLogger(this::class.java)
private val eventStore: EventStore = repositories.eventStore
@@ -374,9 +376,12 @@ abstract class SessionOrchestrator(
?.let { listOf(buildProjectProfileEntry(it)) } ?: emptyList()
val retryFeedbackEntries = buildRetryFeedbackEntry(sessionEvents, stageId)
?.let { listOf(it) } ?: emptyList()
val vocabularyEntries = artifactKindRegistry
?.takeIf { stageConfig.metadata["injectArtifactKinds"] == "true" }
?.let { listOf(buildArtifactKindVocabularyEntry(it.list())) } ?: emptyList()
var accumulatedEntries =
systemPrompt + profileEntries + projectProfileEntries + journalEntries + repoMapEntries +
needsEntries + schemaEntries + promptEntries + steeringEntries + retryFeedbackEntries
needsEntries + schemaEntries + vocabularyEntries + promptEntries + steeringEntries + retryFeedbackEntries
val contextPack = contextPackBuilder.build(
id = ContextPackId(UUID.randomUUID().toString()),
sessionId = sessionId,