refactor: rename the router subsystem to Talkie

The 'router' name was misleading — it's the always-on conversational front-end
(CHAT triage + STEERING into a running workflow), not a routing layer, and it's
distinct from InferenceRouter. Rename core:router -> core:talkie, package
com.correx.core.router -> com.correx.core.talkie, and RouterFacade/Config/State/
Repository/ContextBuilder/Projector/Reducer/Response -> Talkie*. Config section
[router] -> [talkie] (legacy [router] still read as fallback).

Persisted wire formats are preserved: ChatTurnRole.ROUTER and the
RouterNarrationEvent @SerialName("RouterNarration") stay, so existing event
logs still replay. RouterNarrationEvent the class is now TalkieNarrationEvent.
This commit is contained in:
2026-07-03 13:26:00 +04:00
parent 77c28ba313
commit 595ec187bc
84 changed files with 736 additions and 734 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ dependencies {
implementation project(':infrastructure:inference:llama_cpp') implementation project(':infrastructure:inference:llama_cpp')
implementation project(':infrastructure:inference:openai_compat') implementation project(':infrastructure:inference:openai_compat')
implementation project(':infrastructure:inference:commons') implementation project(':infrastructure:inference:commons')
implementation project(':core:router') implementation project(':core:talkie')
implementation project(':core:tools') implementation project(':core:tools')
implementation project(':core:toolintent') implementation project(':core:toolintent')
implementation project(':infrastructure:tools') implementation project(':infrastructure:tools')
@@ -44,9 +44,9 @@ import com.correx.core.kernel.orchestration.WorkspaceTools
import com.correx.core.kernel.orchestration.WorkspaceToolRegistryProvider import com.correx.core.kernel.orchestration.WorkspaceToolRegistryProvider
import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.kernel.retry.DefaultRetryCoordinator
import com.correx.core.risk.DefaultRiskAssessor import com.correx.core.risk.DefaultRiskAssessor
import com.correx.core.router.RouterFacade import com.correx.core.talkie.TalkieFacade
import com.correx.core.router.l3.L3MetadataRehydrator import com.correx.core.talkie.l3.L3MetadataRehydrator
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.DefaultSessionRepository
import com.correx.core.sessions.SessionProjector import com.correx.core.sessions.SessionProjector
@@ -294,7 +294,7 @@ fun main() {
// Constructed before the engines so the context builder can rank freeform turns by relevance // Constructed before the engines so the context builder can rank freeform turns by relevance
// to the current query (pipeline §6, query-conditioned selection). Reused by L3 retrieval below. // to the current query (pipeline §6, query-conditioned selection). Reused by L3 retrieval below.
val embedder = InfrastructureModule.createEmbedderFromConfig(correxConfig.router.embedder) val embedder = InfrastructureModule.createEmbedderFromConfig(correxConfig.talkie.embedder)
val engines = OrchestratorEngines( val engines = OrchestratorEngines(
transitionResolver = DefaultTransitionResolver { condition, ctx -> condition.evaluate(ctx) }, transitionResolver = DefaultTransitionResolver { condition, ctx -> condition.evaluate(ctx) },
contextPackBuilder = DefaultContextPackBuilder( contextPackBuilder = DefaultContextPackBuilder(
@@ -345,7 +345,7 @@ fun main() {
val artifactKindRegistry = DefaultArtifactKindRegistry().also { reg -> val artifactKindRegistry = DefaultArtifactKindRegistry().also { reg ->
configArtifactKindsEarly.forEach { reg.register(it) } configArtifactKindsEarly.forEach { reg.register(it) }
} }
val l3MemoryStore = InfrastructureModule.createL3MemoryStoreFromConfig(correxConfig.router.l3) val l3MemoryStore = InfrastructureModule.createL3MemoryStoreFromConfig(correxConfig.talkie.l3)
// Rebuild the L3 metadata map from the ChatTurnEvent log before serving queries: // Rebuild the L3 metadata map from the ChatTurnEvent log before serving queries:
// persisted vectors survive restart but the metadata map does not (no-op for // persisted vectors survive restart but the metadata map does not (no-op for
// non-rehydratable backends like in_memory). // non-rehydratable backends like in_memory).
@@ -390,11 +390,11 @@ fun main() {
InfrastructureModule.createWorkflowLoader(configArtifactKindsEarly), InfrastructureModule.createWorkflowLoader(configArtifactKindsEarly),
) )
// Builds the router facade from a config snapshot, mapping the [router] block onto the domain // Builds the router facade from a config snapshot, mapping the [router] block onto the domain
// RouterConfig. Reused by ConfigService's rebuild hook so router knob edits apply live (the // TalkieConfig. Reused by ConfigService's rebuild hook so router knob edits apply live (the
// embedder and L3 store are construction-time and reused; the facade is what gets swapped). // embedder and L3 store are construction-time and reused; the facade is what gets swapped).
fun buildRouterFacade(cfg: CorrexConfig): RouterFacade { fun buildTalkieFacade(cfg: CorrexConfig): TalkieFacade {
val rc = cfg.router val rc = cfg.talkie
val domainRouterConfig = RouterConfig( val domainTalkieConfig = TalkieConfig(
conversationKeepLast = rc.conversationKeepLast, conversationKeepLast = rc.conversationKeepLast,
retrievalK = rc.retrievalK, retrievalK = rc.retrievalK,
tokenBudget = TokenBudget(limit = rc.tokenBudget), tokenBudget = TokenBudget(limit = rc.tokenBudget),
@@ -410,16 +410,16 @@ fun main() {
), ),
narrationModelId = rc.narration.modelId, narrationModelId = rc.narration.modelId,
) )
return InfrastructureModule.createRouterFacade( return InfrastructureModule.createTalkieFacade(
eventStore = eventStore, eventStore = eventStore,
inferenceRouter = inferenceRouter, inferenceRouter = inferenceRouter,
config = domainRouterConfig, config = domainTalkieConfig,
tokenizer = firstProvider.tokenizer, tokenizer = firstProvider.tokenizer,
embedder = embedder, embedder = embedder,
l3MemoryStore = l3MemoryStore, l3MemoryStore = l3MemoryStore,
workflowSummaryProvider = { workflowSummaryProvider = {
workflowRegistry.listAll().map { s -> workflowRegistry.listAll().map { s ->
com.correx.core.router.model.WorkflowSummary(s.workflowId, s.description, s.stageIds) com.correx.core.talkie.model.WorkflowSummary(s.workflowId, s.description, s.stageIds)
} }
}, },
sessionProfileProvider = { sid -> sessionProfileProvider = { sid ->
@@ -429,7 +429,7 @@ fun main() {
}, },
) )
} }
val routerFacade = buildRouterFacade(correxConfig) val routerFacade = buildTalkieFacade(correxConfig)
val sessionUndoService = com.correx.apps.server.undo.SessionUndoService( val sessionUndoService = com.correx.apps.server.undo.SessionUndoService(
eventStore = eventStore, eventStore = eventStore,
artifactStore = artifactStore, artifactStore = artifactStore,
@@ -546,7 +546,7 @@ fun main() {
modelSwapper = modelSwapper, modelSwapper = modelSwapper,
resourceProbe = resourceProbe, resourceProbe = resourceProbe,
workspaceResolver = workspaceResolver, workspaceResolver = workspaceResolver,
narrationMaxPerRun = correxConfig.router.narration.maxPerRun, narrationMaxPerRun = correxConfig.talkie.narration.maxPerRun,
projectMemory = projectMemory, projectMemory = projectMemory,
architectContradictionChecker = architectContradictionChecker, architectContradictionChecker = architectContradictionChecker,
configHolder = configHolder, configHolder = configHolder,
@@ -564,7 +564,7 @@ fun main() {
// services. Built after the module so the rebuild hook can swap them in place. // services. Built after the module so the rebuild hook can swap them in place.
module.configService = ConfigService(configHolder, ConfigLoader.configPath()) { cfg -> module.configService = ConfigService(configHolder, ConfigLoader.configPath()) { cfg ->
module.applyRebuiltServices( module.applyRebuiltServices(
newRouterFacade = buildRouterFacade(cfg), newTalkieFacade = buildTalkieFacade(cfg),
newProjectMemory = buildProjectMemory(cfg), newProjectMemory = buildProjectMemory(cfg),
newProfileAdaptationService = buildProfileAdaptation(cfg), newProfileAdaptationService = buildProfileAdaptation(cfg),
) )
@@ -39,7 +39,7 @@ import com.correx.core.kernel.execution.WorkflowResult
import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator
import com.correx.core.kernel.orchestration.OrchestrationConfig import com.correx.core.kernel.orchestration.OrchestrationConfig
import com.correx.core.kernel.orchestration.OrchestrationRepository import com.correx.core.kernel.orchestration.OrchestrationRepository
import com.correx.core.router.RouterFacade import com.correx.core.talkie.TalkieFacade
import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.DefaultSessionRepository
import com.correx.core.sessions.SessionSummary import com.correx.core.sessions.SessionSummary
import com.correx.core.sessions.SessionSummaryProjector import com.correx.core.sessions.SessionSummaryProjector
@@ -72,7 +72,7 @@ class ServerModule(
val defaultOrchestrationConfig: OrchestrationConfig, val defaultOrchestrationConfig: OrchestrationConfig,
// Rebuilt by ConfigService on a live config edit (router knobs); read at use-time by // Rebuilt by ConfigService on a live config edit (router knobs); read at use-time by
// GlobalStreamHandler.onUserInput so the next chat turn picks up the new facade. // GlobalStreamHandler.onUserInput so the next chat turn picks up the new facade.
routerFacade: RouterFacade, routerFacade: TalkieFacade,
val orchestrationRepository: OrchestrationRepository, val orchestrationRepository: OrchestrationRepository,
val approvalRepository: DefaultApprovalRepository, val approvalRepository: DefaultApprovalRepository,
val toolRegistry: ToolRegistry, val toolRegistry: ToolRegistry,
@@ -134,7 +134,7 @@ class ServerModule(
// Config-derived services that ConfigService swaps on a live edit. Reads happen at use-time // Config-derived services that ConfigService swaps on a live edit. Reads happen at use-time
// (next session / next chat turn), so in-flight sessions keep the config they started with. // (next session / next chat turn), so in-flight sessions keep the config they started with.
@Volatile @Volatile
var routerFacade: RouterFacade = routerFacade var routerFacade: TalkieFacade = routerFacade
private set private set
@Volatile @Volatile
@@ -154,11 +154,11 @@ class ServerModule(
* In-flight sessions are untouched; the next session/turn reads the new instances. * In-flight sessions are untouched; the next session/turn reads the new instances.
*/ */
fun applyRebuiltServices( fun applyRebuiltServices(
newRouterFacade: RouterFacade, newTalkieFacade: TalkieFacade,
newProjectMemory: com.correx.apps.server.memory.ProjectMemoryService?, newProjectMemory: com.correx.apps.server.memory.ProjectMemoryService?,
newProfileAdaptationService: com.correx.apps.server.personalization.ProfileAdaptationService?, newProfileAdaptationService: com.correx.apps.server.personalization.ProfileAdaptationService?,
) { ) {
this.routerFacade = newRouterFacade this.routerFacade = newTalkieFacade
this.projectMemory = newProjectMemory this.projectMemory = newProjectMemory
this.profileAdaptationService = newProfileAdaptationService this.profileAdaptationService = newProfileAdaptationService
} }
@@ -39,7 +39,7 @@ import com.correx.core.events.events.ToolCallAssessedEvent
import com.correx.core.events.events.ToolInvocationRequestedEvent import com.correx.core.events.events.ToolInvocationRequestedEvent
import com.correx.core.events.events.TransitionExecutedEvent import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.events.WorkflowCompletedEvent import com.correx.core.events.events.WorkflowCompletedEvent
import com.correx.core.events.events.RouterNarrationEvent import com.correx.core.events.events.TalkieNarrationEvent
import com.correx.core.events.events.WorkflowFailedEvent import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowProposedEvent import com.correx.core.events.events.WorkflowProposedEvent
import com.correx.core.events.risk.RiskAction import com.correx.core.events.risk.RiskAction
@@ -249,7 +249,7 @@ suspend fun domainEventToServerMessage(
sequence = seq, sequence = seq,
sessionSequence = sessionSequence, sessionSequence = sessionSequence,
) )
is RouterNarrationEvent -> ServerMessage.Narration( is TalkieNarrationEvent -> ServerMessage.Narration(
sessionId = p.sessionId, sessionId = p.sessionId,
content = p.content, content = p.content,
stageId = p.stageId, stageId = p.stageId,
@@ -5,8 +5,8 @@ import com.correx.core.events.events.RelatedDecision
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId import com.correx.core.events.types.StageId
import com.correx.core.inference.Embedder import com.correx.core.inference.Embedder
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
/** /**
* Display-only architect contradiction surfacing (BACKLOG §B-§4). When the architect records a * Display-only architect contradiction surfacing (BACKLOG §B-§4). When the architect records a
@@ -4,8 +4,8 @@ import com.correx.core.events.events.RepoKnowledgeHit
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.inference.Embedder import com.correx.core.inference.Embedder
import com.correx.core.kernel.orchestration.RepoKnowledgeRetriever import com.correx.core.kernel.orchestration.RepoKnowledgeRetriever
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
private const val RETRIEVAL_OVERSAMPLE_FACTOR = 4 private const val RETRIEVAL_OVERSAMPLE_FACTOR = 4
@@ -13,9 +13,9 @@ import com.correx.core.events.types.SessionId
import com.correx.core.inference.Embedder import com.correx.core.inference.Embedder
import com.correx.core.journal.DefaultDecisionJournalRepository import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.journal.ProjectMemoryDistiller import com.correx.core.journal.ProjectMemoryDistiller
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -13,8 +13,8 @@ import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowStartedEvent import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.stores.EventStore import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.router.RouterFacade import com.correx.core.talkie.TalkieFacade
import com.correx.core.router.model.NarrationTrigger import com.correx.core.talkie.model.NarrationTrigger
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
@@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap
class NarrationSubscriber( class NarrationSubscriber(
private val eventStore: EventStore, private val eventStore: EventStore,
private val routerFacade: RouterFacade, private val routerFacade: TalkieFacade,
private val scope: CoroutineScope, private val scope: CoroutineScope,
private val maxPerRun: Int = DEFAULT_MAX_PER_RUN, private val maxPerRun: Int = DEFAULT_MAX_PER_RUN,
) { ) {
@@ -8,7 +8,7 @@ import com.correx.core.events.types.ClarificationRequestId
import com.correx.core.events.types.GrantId import com.correx.core.events.types.GrantId
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId import com.correx.core.events.types.StageId
import com.correx.core.router.ChatMode import com.correx.core.talkie.ChatMode
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -562,7 +562,7 @@ sealed interface ServerMessage {
@Serializable @Serializable
@SerialName("router.response") @SerialName("router.response")
data class RouterResponseMessage( data class TalkieResponseMessage(
val sessionId: SessionId, val sessionId: SessionId,
val content: String, val content: String,
val steeringEmitted: Boolean, val steeringEmitted: Boolean,
@@ -35,7 +35,7 @@ import com.correx.core.kernel.orchestration.WorkspaceContext
import com.correx.core.config.ProjectProfileLoader import com.correx.core.config.ProjectProfileLoader
import com.correx.core.config.ProjectProfileWriter import com.correx.core.config.ProjectProfileWriter
import com.correx.core.config.withConvention import com.correx.core.config.withConvention
import com.correx.core.router.IdeaReader import com.correx.core.talkie.IdeaReader
import com.correx.core.utils.TypeId import com.correx.core.utils.TypeId
import com.correx.infrastructure.inference.commons.ResourceProbe import com.correx.infrastructure.inference.commons.ResourceProbe
import com.correx.infrastructure.inference.commons.ResourceSnapshot import com.correx.infrastructure.inference.commons.ResourceSnapshot
@@ -253,7 +253,7 @@ class GlobalStreamHandler(private val module: ServerModule) {
is ClientMessage.ChatInput -> { is ClientMessage.ChatInput -> {
// The USER and ROUTER turns are emitted as ChatTurnEvents inside onUserInput // The USER and ROUTER turns are emitted as ChatTurnEvents inside onUserInput
// and reach the client as chat.turn frames via streamGlobal — no direct // and reach the client as chat.turn frames via streamGlobal — no direct
// RouterResponseMessage (the conversation is event-derived). // TalkieResponseMessage (the conversation is event-derived).
withSessionContext(msg.sessionId) { withSessionContext(msg.sessionId) {
runCatching { runCatching {
module.routerFacade.onUserInput(msg.sessionId, msg.text, msg.mode) module.routerFacade.onUserInput(msg.sessionId, msg.text, msg.mode)
@@ -6,7 +6,7 @@ import com.correx.apps.server.logging.withSessionContext
import com.correx.apps.server.protocol.ClientMessage import com.correx.apps.server.protocol.ClientMessage
import com.correx.apps.server.protocol.ProtocolSerializer import com.correx.apps.server.protocol.ProtocolSerializer
import com.correx.apps.server.protocol.ServerMessage import com.correx.apps.server.protocol.ServerMessage
import com.correx.apps.server.protocol.ServerMessage.RouterResponseMessage import com.correx.apps.server.protocol.ServerMessage.TalkieResponseMessage
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import io.ktor.server.websocket.DefaultWebSocketServerSession import io.ktor.server.websocket.DefaultWebSocketServerSession
import io.ktor.websocket.Frame import io.ktor.websocket.Frame
@@ -93,7 +93,7 @@ class SessionStreamHandler(private val module: ServerModule) {
session.send( session.send(
Frame.Text( Frame.Text(
ProtocolSerializer.encodeServerMessage( ProtocolSerializer.encodeServerMessage(
RouterResponseMessage( TalkieResponseMessage(
sessionId = msg.sessionId, sessionId = msg.sessionId,
content = response.content, content = response.content,
steeringEmitted = response.steeringEmitted, steeringEmitted = response.steeringEmitted,
@@ -14,7 +14,7 @@ import com.correx.core.approvals.GRANT_LEDGER_SESSION_ID
import com.correx.core.approvals.GrantScope import com.correx.core.approvals.GrantScope
import com.correx.core.approvals.ProjectIdentity import com.correx.core.approvals.ProjectIdentity
import com.correx.core.events.types.ProjectId import com.correx.core.events.types.ProjectId
import com.correx.core.router.IdeaReader import com.correx.core.talkie.IdeaReader
import com.correx.core.config.EditableConfig import com.correx.core.config.EditableConfig
import com.correx.core.events.events.ArtifactContentStoredEvent import com.correx.core.events.events.ArtifactContentStoredEvent
import com.correx.core.events.events.ArtifactCreatedEvent import com.correx.core.events.events.ArtifactCreatedEvent
@@ -35,11 +35,11 @@ import com.correx.core.kernel.orchestration.OrchestratorEngines
import com.correx.core.kernel.orchestration.OrchestratorRepositories import com.correx.core.kernel.orchestration.OrchestratorRepositories
import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.kernel.retry.DefaultRetryCoordinator
import com.correx.core.risk.DefaultRiskAssessor import com.correx.core.risk.DefaultRiskAssessor
import com.correx.core.router.l3.L3Hit import com.correx.core.talkie.l3.L3Hit
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.DefaultSessionRepository
import com.correx.core.sessions.SessionProjector import com.correx.core.sessions.SessionProjector
@@ -182,10 +182,10 @@ class ArchitectContradictionHookTest {
tokenizer = provider.tokenizer, tokenizer = provider.tokenizer,
decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore), decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore),
) )
val routerFacade = InfrastructureModule.createRouterFacade( val routerFacade = InfrastructureModule.createTalkieFacade(
eventStore = eventStore, eventStore = eventStore,
inferenceRouter = inferenceRouter, inferenceRouter = inferenceRouter,
config = RouterConfig(), config = TalkieConfig(),
tokenizer = provider.tokenizer, tokenizer = provider.tokenizer,
) )
val noopProviderRegistry = object : ProviderRegistry { val noopProviderRegistry = object : ProviderRegistry {
@@ -24,7 +24,7 @@ import com.correx.core.kernel.orchestration.OrchestratorEngines
import com.correx.core.kernel.orchestration.OrchestratorRepositories import com.correx.core.kernel.orchestration.OrchestratorRepositories
import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.kernel.retry.DefaultRetryCoordinator
import com.correx.core.risk.DefaultRiskAssessor import com.correx.core.risk.DefaultRiskAssessor
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.DefaultSessionRepository
import com.correx.core.sessions.SessionProjector import com.correx.core.sessions.SessionProjector
@@ -116,10 +116,10 @@ fun buildTestServerModule(
decisionJournalRepository = decisionJournalRepository, decisionJournalRepository = decisionJournalRepository,
) )
val routerFacade = InfrastructureModule.createRouterFacade( val routerFacade = InfrastructureModule.createTalkieFacade(
eventStore = eventStore, eventStore = eventStore,
inferenceRouter = modelSwapper, inferenceRouter = modelSwapper,
config = RouterConfig(), config = TalkieConfig(),
tokenizer = firstProvider.tokenizer, tokenizer = firstProvider.tokenizer,
) )
@@ -3,10 +3,10 @@ package com.correx.apps.server.memory
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId import com.correx.core.events.types.StageId
import com.correx.core.inference.Embedder import com.correx.core.inference.Embedder
import com.correx.core.router.l3.L3Hit import com.correx.core.talkie.l3.L3Hit
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertNull
@@ -2,8 +2,8 @@ package com.correx.apps.server.memory
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.inference.Embedder import com.correx.core.inference.Embedder
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -10,7 +10,7 @@ import com.correx.core.inference.Embedder
import com.correx.core.journal.DecisionJournalProjector import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.infrastructure.persistence.InMemoryEventStore import com.correx.infrastructure.persistence.InMemoryEventStore
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -6,7 +6,7 @@ import com.correx.core.inference.Embedder
import com.correx.core.journal.DecisionJournalProjector import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.infrastructure.persistence.InMemoryEventStore import com.correx.infrastructure.persistence.InMemoryEventStore
@@ -13,7 +13,7 @@ import com.correx.core.inference.Embedder
import com.correx.core.journal.DecisionJournalProjector import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.infrastructure.persistence.InMemoryEventStore import com.correx.infrastructure.persistence.InMemoryEventStore
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -25,10 +25,10 @@ import com.correx.core.events.types.StageId
import com.correx.core.events.types.TransitionId import com.correx.core.events.types.TransitionId
import com.correx.core.events.types.ValidationReportId import com.correx.core.events.types.ValidationReportId
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import com.correx.core.router.ChatMode import com.correx.core.talkie.ChatMode
import com.correx.core.router.RouterFacade import com.correx.core.talkie.TalkieFacade
import com.correx.core.router.model.NarrationTrigger import com.correx.core.talkie.model.NarrationTrigger
import com.correx.core.router.model.RouterResponse import com.correx.core.talkie.model.TalkieResponse
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -72,10 +72,10 @@ class NarrationSubscriberTest {
override suspend fun lastGlobalSequence(): Long = 0L override suspend fun lastGlobalSequence(): Long = 0L
} }
private class RecordingRouterFacade : RouterFacade { private class RecordingTalkieFacade : TalkieFacade {
val calls = CopyOnWriteArrayList<Pair<SessionId, NarrationTrigger>>() val calls = CopyOnWriteArrayList<Pair<SessionId, NarrationTrigger>>()
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse = override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse =
error("unused in narration tests") error("unused in narration tests")
override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) { override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) {
@@ -99,7 +99,7 @@ class NarrationSubscriberTest {
@Test @Test
fun `triggers are narrated in order for matching events`(): Unit = runBlocking { fun `triggers are narrated in order for matching events`(): Unit = runBlocking {
val facade = RecordingRouterFacade() val facade = RecordingTalkieFacade()
NarrationSubscriber(fakeStore, facade, scope).start() NarrationSubscriber(fakeStore, facade, scope).start()
// Wait for subscription to be attached // Wait for subscription to be attached
@@ -122,7 +122,7 @@ class NarrationSubscriberTest {
@Test @Test
fun `pause narration is grounded with the pending approval tool and preview`(): Unit = runBlocking { fun `pause narration is grounded with the pending approval tool and preview`(): Unit = runBlocking {
val facade = RecordingRouterFacade() val facade = RecordingTalkieFacade()
NarrationSubscriber(fakeStore, facade, scope).start() NarrationSubscriber(fakeStore, facade, scope).start()
while (liveFlow.subscriptionCount.value == 0) yield() while (liveFlow.subscriptionCount.value == 0) yield()
@@ -158,7 +158,7 @@ class NarrationSubscriberTest {
@Test @Test
fun `maxPerRun=1 skips second trigger after first narration`(): Unit = runBlocking { fun `maxPerRun=1 skips second trigger after first narration`(): Unit = runBlocking {
val facade = RecordingRouterFacade() val facade = RecordingTalkieFacade()
NarrationSubscriber(fakeStore, facade, scope, maxPerRun = 1).start() NarrationSubscriber(fakeStore, facade, scope, maxPerRun = 1).start()
while (liveFlow.subscriptionCount.value == 0) yield() while (liveFlow.subscriptionCount.value == 0) yield()
@@ -180,10 +180,10 @@ class NarrationSubscriberTest {
@Test @Test
fun `a throwing narrate does not prevent subsequent narrations`(): Unit = runBlocking { fun `a throwing narrate does not prevent subsequent narrations`(): Unit = runBlocking {
var callCount = 0 var callCount = 0
val facade = object : RouterFacade { val facade = object : TalkieFacade {
val calls = CopyOnWriteArrayList<String>() val calls = CopyOnWriteArrayList<String>()
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse = override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse =
error("unused") error("unused")
override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) { override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) {
@@ -210,7 +210,7 @@ class NarrationSubscriberTest {
@Test @Test
fun `execution plan rejection triggers narration with rejection reason`(): Unit = runBlocking { fun `execution plan rejection triggers narration with rejection reason`(): Unit = runBlocking {
val facade = RecordingRouterFacade() val facade = RecordingTalkieFacade()
NarrationSubscriber(fakeStore, facade, scope).start() NarrationSubscriber(fakeStore, facade, scope).start()
while (liveFlow.subscriptionCount.value == 0) yield() while (liveFlow.subscriptionCount.value == 0) yield()
@@ -244,7 +244,7 @@ class NarrationSubscriberTest {
* letting the test enqueue further events (and resolve the pause) while the pause narration is * letting the test enqueue further events (and resolve the pause) while the pause narration is
* still pending in the channel. * still pending in the channel.
*/ */
private class GatingRouterFacade : RouterFacade { private class GatingTalkieFacade : TalkieFacade {
val calls = CopyOnWriteArrayList<NarrationTrigger>() val calls = CopyOnWriteArrayList<NarrationTrigger>()
val release = CompletableDeferred<Unit>() val release = CompletableDeferred<Unit>()
// Signals that the worker has pulled the first (warmup) trigger and is now blocked, // Signals that the worker has pulled the first (warmup) trigger and is now blocked,
@@ -252,7 +252,7 @@ class NarrationSubscriberTest {
val blocked = CompletableDeferred<Unit>() val blocked = CompletableDeferred<Unit>()
private var first = true private var first = true
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse = override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse =
error("unused in narration tests") error("unused in narration tests")
override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) { override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) {
@@ -289,7 +289,7 @@ class NarrationSubscriberTest {
@Test @Test
fun `resolved approval drops the still-queued pause narration`(): Unit = runBlocking { fun `resolved approval drops the still-queued pause narration`(): Unit = runBlocking {
val facade = GatingRouterFacade() val facade = GatingTalkieFacade()
NarrationSubscriber(fakeStore, facade, scope).start() NarrationSubscriber(fakeStore, facade, scope).start()
while (liveFlow.subscriptionCount.value == 0) yield() while (liveFlow.subscriptionCount.value == 0) yield()
@@ -320,7 +320,7 @@ class NarrationSubscriberTest {
@Test @Test
fun `unrelated approval resolution leaves the pending pause intact`(): Unit = runBlocking { fun `unrelated approval resolution leaves the pending pause intact`(): Unit = runBlocking {
val facade = GatingRouterFacade() val facade = GatingTalkieFacade()
NarrationSubscriber(fakeStore, facade, scope).start() NarrationSubscriber(fakeStore, facade, scope).start()
while (liveFlow.subscriptionCount.value == 0) yield() while (liveFlow.subscriptionCount.value == 0) yield()
@@ -342,7 +342,7 @@ class NarrationSubscriberTest {
@Test @Test
fun `resume drops every queued pause narration for the session`(): Unit = runBlocking { fun `resume drops every queued pause narration for the session`(): Unit = runBlocking {
val facade = GatingRouterFacade() val facade = GatingTalkieFacade()
NarrationSubscriber(fakeStore, facade, scope).start() NarrationSubscriber(fakeStore, facade, scope).start()
while (liveFlow.subscriptionCount.value == 0) yield() while (liveFlow.subscriptionCount.value == 0) yield()
@@ -33,7 +33,7 @@ import com.correx.core.kernel.orchestration.OrchestratorRepositories
import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.kernel.retry.DefaultRetryCoordinator
import com.correx.core.events.types.ProviderId import com.correx.core.events.types.ProviderId
import com.correx.core.risk.DefaultRiskAssessor import com.correx.core.risk.DefaultRiskAssessor
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.DefaultSessionRepository
import com.correx.core.sessions.SessionProjector import com.correx.core.sessions.SessionProjector
@@ -165,10 +165,10 @@ class EventInspectRoutesTest {
tokenizer = provider.tokenizer, tokenizer = provider.tokenizer,
decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore), decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore),
) )
val routerFacade = InfrastructureModule.createRouterFacade( val routerFacade = InfrastructureModule.createTalkieFacade(
eventStore = eventStore, eventStore = eventStore,
inferenceRouter = inferenceRouter, inferenceRouter = inferenceRouter,
config = RouterConfig(), config = TalkieConfig(),
tokenizer = provider.tokenizer, tokenizer = provider.tokenizer,
) )
val noopProviderRegistry = object : ProviderRegistry { val noopProviderRegistry = object : ProviderRegistry {
@@ -26,7 +26,7 @@ import com.correx.core.kernel.orchestration.OrchestratorRepositories
import com.correx.core.kernel.retry.DefaultRetryCoordinator import com.correx.core.kernel.retry.DefaultRetryCoordinator
import com.correx.core.events.types.ProviderId import com.correx.core.events.types.ProviderId
import com.correx.core.risk.DefaultRiskAssessor import com.correx.core.risk.DefaultRiskAssessor
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.sessions.DefaultSessionReducer import com.correx.core.sessions.DefaultSessionReducer
import com.correx.core.sessions.DefaultSessionRepository import com.correx.core.sessions.DefaultSessionRepository
import com.correx.core.sessions.SessionProjector import com.correx.core.sessions.SessionProjector
@@ -444,10 +444,10 @@ class TaskRoutesTest {
tokenizer = provider.tokenizer, tokenizer = provider.tokenizer,
decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore), decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore),
) )
val routerFacade = InfrastructureModule.createRouterFacade( val routerFacade = InfrastructureModule.createTalkieFacade(
eventStore = eventStore, eventStore = eventStore,
inferenceRouter = inferenceRouter, inferenceRouter = inferenceRouter,
config = RouterConfig(), config = TalkieConfig(),
tokenizer = provider.tokenizer, tokenizer = provider.tokenizer,
) )
val noopProviderRegistry = object : ProviderRegistry { val noopProviderRegistry = object : ProviderRegistry {
@@ -186,10 +186,10 @@ class WorkspaceHandshakeTest {
decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore), decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore),
) )
val routerFacade = InfrastructureModule.createRouterFacade( val routerFacade = InfrastructureModule.createTalkieFacade(
eventStore = eventStore, eventStore = eventStore,
inferenceRouter = inferenceRouter, inferenceRouter = inferenceRouter,
config = com.correx.core.router.model.RouterConfig(), config = com.correx.core.talkie.model.TalkieConfig(),
tokenizer = provider.tokenizer, tokenizer = provider.tokenizer,
) )
@@ -418,14 +418,14 @@ object ConfigLoader {
val toolsFileReadSection = sections["tools.file_read"] ?: emptyMap() val toolsFileReadSection = sections["tools.file_read"] ?: emptyMap()
val toolsFileWriteSection = sections["tools.file_write"] ?: emptyMap() val toolsFileWriteSection = sections["tools.file_write"] ?: emptyMap()
val toolsFileEditSection = sections["tools.file_edit"] ?: emptyMap() val toolsFileEditSection = sections["tools.file_edit"] ?: emptyMap()
val routerSection = sections["router"] ?: emptyMap() val routerSection = sections["talkie"] ?: sections["router"] ?: emptyMap()
// Embedder + L3 are shared infra (the orchestrator uses them for workflow repo-knowledge, // Embedder + L3 are shared infra (the orchestrator uses them for workflow repo-knowledge,
// not just the router chat layer). Prefer the top-level [embedder]/[l3] sections; fall back // not just the router chat layer). Prefer the top-level [embedder]/[l3] sections; fall back
// to the legacy [router.embedder]/[router.l3] so existing configs keep working. // to the legacy [router.embedder]/[router.l3] so existing configs keep working.
val routerEmbedderSection = sections["embedder"] ?: sections["router.embedder"] ?: emptyMap() val routerEmbedderSection = sections["embedder"] ?: sections["router.embedder"] ?: emptyMap()
val routerL3Section = sections["l3"] ?: sections["router.l3"] ?: emptyMap() val routerL3Section = sections["l3"] ?: sections["router.l3"] ?: emptyMap()
val routerGenerationSection = sections["router.generation"] ?: emptyMap() val routerGenerationSection = sections["talkie.generation"] ?: sections["router.generation"] ?: emptyMap()
val routerNarrationSection = sections["router.narration"] ?: emptyMap() val routerNarrationSection = sections["talkie.narration"] ?: sections["router.narration"] ?: emptyMap()
val modelsSection = sections["models"] ?: emptyMap() val modelsSection = sections["models"] ?: emptyMap()
val server = ServerConfig( val server = ServerConfig(
@@ -575,7 +575,7 @@ object ConfigLoader {
modelId = asStringOrNull(routerNarrationSection["model_id"]), modelId = asStringOrNull(routerNarrationSection["model_id"]),
) )
val router = RouterConfig( val router = TalkieConfig(
embedder = embedder, embedder = embedder,
l3 = l3, l3 = l3,
conversationKeepLast = asInt(routerSection["conversation_keep_last"], 6), conversationKeepLast = asInt(routerSection["conversation_keep_last"], 6),
@@ -655,7 +655,7 @@ object ConfigLoader {
cli = cli, cli = cli,
tools = tools, tools = tools,
providers = providers, providers = providers,
router = router, talkie = router,
models = models, models = models,
modelsSettings = modelsSettings, modelsSettings = modelsSettings,
artifacts = artifacts, artifacts = artifacts,
@@ -9,7 +9,7 @@ data class CorrexConfig(
val cli: CliConfig = CliConfig(), val cli: CliConfig = CliConfig(),
val tools: ToolsConfig = ToolsConfig(), val tools: ToolsConfig = ToolsConfig(),
val providers: List<ProviderConfig> = emptyList(), val providers: List<ProviderConfig> = emptyList(),
val router: RouterConfig = RouterConfig(), val talkie: TalkieConfig = TalkieConfig(),
val models: List<ModelConfig> = emptyList(), val models: List<ModelConfig> = emptyList(),
val modelsSettings: ModelsSettings = ModelsSettings(), val modelsSettings: ModelsSettings = ModelsSettings(),
val artifacts: List<ArtifactKindConfig> = emptyList(), val artifacts: List<ArtifactKindConfig> = emptyList(),
@@ -189,7 +189,7 @@ data class ProviderConfig(
) )
@Serializable @Serializable
data class RouterConfig( data class TalkieConfig(
val embedder: EmbedderConfig = EmbedderConfig(), val embedder: EmbedderConfig = EmbedderConfig(),
val l3: L3Config = L3Config(), val l3: L3Config = L3Config(),
val conversationKeepLast: Int = 6, val conversationKeepLast: Int = 6,
@@ -51,36 +51,36 @@ object CorrexConfigWriter {
b.section("tools.file_edit") b.section("tools.file_edit")
b.kv("enabled", cfg.tools.fileEditEnabled) b.kv("enabled", cfg.tools.fileEditEnabled)
b.section("router") b.section("talkie")
b.kv("conversation_keep_last", cfg.router.conversationKeepLast) b.kv("conversation_keep_last", cfg.talkie.conversationKeepLast)
b.kv("retrieval_k", cfg.router.retrievalK) b.kv("retrieval_k", cfg.talkie.retrievalK)
b.kv("token_budget", cfg.router.tokenBudget) b.kv("token_budget", cfg.talkie.tokenBudget)
b.section("embedder") b.section("embedder")
b.kv("backend", str(cfg.router.embedder.backend)) b.kv("backend", str(cfg.talkie.embedder.backend))
b.kv("dimension", cfg.router.embedder.dimension) b.kv("dimension", cfg.talkie.embedder.dimension)
cfg.router.embedder.url?.let { b.kv("url", str(it)) } cfg.talkie.embedder.url?.let { b.kv("url", str(it)) }
cfg.router.embedder.modelId?.let { b.kv("model_id", str(it)) } cfg.talkie.embedder.modelId?.let { b.kv("model_id", str(it)) }
b.section("l3") b.section("l3")
b.kv("backend", str(cfg.router.l3.backend)) b.kv("backend", str(cfg.talkie.l3.backend))
cfg.router.l3.persistPath?.let { b.kv("persist_path", str(it)) } cfg.talkie.l3.persistPath?.let { b.kv("persist_path", str(it)) }
b.kv("python_executable", str(cfg.router.l3.pythonExecutable)) b.kv("python_executable", str(cfg.talkie.l3.pythonExecutable))
cfg.router.l3.scriptPath?.let { b.kv("script_path", str(it)) } cfg.talkie.l3.scriptPath?.let { b.kv("script_path", str(it)) }
b.kv("dim", cfg.router.l3.dim) b.kv("dim", cfg.talkie.l3.dim)
b.kv("bit_width", cfg.router.l3.bitWidth) b.kv("bit_width", cfg.talkie.l3.bitWidth)
b.section("router.generation") b.section("talkie.generation")
b.kv("temperature", cfg.router.generation.temperature) b.kv("temperature", cfg.talkie.generation.temperature)
b.kv("top_p", cfg.router.generation.topP) b.kv("top_p", cfg.talkie.generation.topP)
b.kv("max_tokens", cfg.router.generation.maxTokens) b.kv("max_tokens", cfg.talkie.generation.maxTokens)
b.section("router.narration") b.section("talkie.narration")
b.kv("temperature", cfg.router.narration.temperature) b.kv("temperature", cfg.talkie.narration.temperature)
b.kv("top_p", cfg.router.narration.topP) b.kv("top_p", cfg.talkie.narration.topP)
b.kv("max_tokens", cfg.router.narration.maxTokens) b.kv("max_tokens", cfg.talkie.narration.maxTokens)
b.kv("max_per_run", cfg.router.narration.maxPerRun) b.kv("max_per_run", cfg.talkie.narration.maxPerRun)
cfg.router.narration.modelId?.let { b.kv("model_id", str(it)) } cfg.talkie.narration.modelId?.let { b.kv("model_id", str(it)) }
b.section("orchestration") b.section("orchestration")
b.kv("stage_timeout_ms", cfg.orchestration.stageTimeoutMs) b.kv("stage_timeout_ms", cfg.orchestration.stageTimeoutMs)
@@ -87,57 +87,57 @@ object EditableConfig {
), ),
EditableField( EditableField(
"router.conversation_keep_last", ConfigFieldType.INT, "router.conversation_keep_last", ConfigFieldType.INT,
getString = { it.router.conversationKeepLast.toString() }, getString = { it.talkie.conversationKeepLast.toString() },
withString = { c, v -> rtr(c) { it.copy(conversationKeepLast = int("router.conversation_keep_last", v)) } }, withString = { c, v -> rtr(c) { it.copy(conversationKeepLast = int("router.conversation_keep_last", v)) } },
), ),
EditableField( EditableField(
"router.retrieval_k", ConfigFieldType.INT, "router.retrieval_k", ConfigFieldType.INT,
getString = { it.router.retrievalK.toString() }, getString = { it.talkie.retrievalK.toString() },
withString = { c, v -> rtr(c) { it.copy(retrievalK = int("router.retrieval_k", v)) } }, withString = { c, v -> rtr(c) { it.copy(retrievalK = int("router.retrieval_k", v)) } },
), ),
EditableField( EditableField(
"router.token_budget", ConfigFieldType.INT, "router.token_budget", ConfigFieldType.INT,
getString = { it.router.tokenBudget.toString() }, getString = { it.talkie.tokenBudget.toString() },
withString = { c, v -> rtr(c) { it.copy(tokenBudget = int("router.token_budget", v)) } }, withString = { c, v -> rtr(c) { it.copy(tokenBudget = int("router.token_budget", v)) } },
), ),
EditableField( EditableField(
"router.generation.temperature", ConfigFieldType.DOUBLE, "router.generation.temperature", ConfigFieldType.DOUBLE,
getString = { it.router.generation.temperature.toString() }, getString = { it.talkie.generation.temperature.toString() },
withString = { c, v -> gen(c) { it.copy(temperature = dbl("router.generation.temperature", v)) } }, withString = { c, v -> gen(c) { it.copy(temperature = dbl("router.generation.temperature", v)) } },
), ),
EditableField( EditableField(
"router.generation.top_p", ConfigFieldType.DOUBLE, "router.generation.top_p", ConfigFieldType.DOUBLE,
getString = { it.router.generation.topP.toString() }, getString = { it.talkie.generation.topP.toString() },
withString = { c, v -> gen(c) { it.copy(topP = dbl("router.generation.top_p", v)) } }, withString = { c, v -> gen(c) { it.copy(topP = dbl("router.generation.top_p", v)) } },
), ),
EditableField( EditableField(
"router.generation.max_tokens", ConfigFieldType.INT, "router.generation.max_tokens", ConfigFieldType.INT,
getString = { it.router.generation.maxTokens.toString() }, getString = { it.talkie.generation.maxTokens.toString() },
withString = { c, v -> gen(c) { it.copy(maxTokens = int("router.generation.max_tokens", v)) } }, withString = { c, v -> gen(c) { it.copy(maxTokens = int("router.generation.max_tokens", v)) } },
), ),
EditableField( EditableField(
"router.narration.temperature", ConfigFieldType.DOUBLE, "router.narration.temperature", ConfigFieldType.DOUBLE,
getString = { it.router.narration.temperature.toString() }, getString = { it.talkie.narration.temperature.toString() },
withString = { c, v -> nar(c) { it.copy(temperature = dbl("router.narration.temperature", v)) } }, withString = { c, v -> nar(c) { it.copy(temperature = dbl("router.narration.temperature", v)) } },
), ),
EditableField( EditableField(
"router.narration.top_p", ConfigFieldType.DOUBLE, "router.narration.top_p", ConfigFieldType.DOUBLE,
getString = { it.router.narration.topP.toString() }, getString = { it.talkie.narration.topP.toString() },
withString = { c, v -> nar(c) { it.copy(topP = dbl("router.narration.top_p", v)) } }, withString = { c, v -> nar(c) { it.copy(topP = dbl("router.narration.top_p", v)) } },
), ),
EditableField( EditableField(
"router.narration.max_tokens", ConfigFieldType.INT, "router.narration.max_tokens", ConfigFieldType.INT,
getString = { it.router.narration.maxTokens.toString() }, getString = { it.talkie.narration.maxTokens.toString() },
withString = { c, v -> nar(c) { it.copy(maxTokens = int("router.narration.max_tokens", v)) } }, withString = { c, v -> nar(c) { it.copy(maxTokens = int("router.narration.max_tokens", v)) } },
), ),
EditableField( EditableField(
"router.narration.max_per_run", ConfigFieldType.INT, "router.narration.max_per_run", ConfigFieldType.INT,
getString = { it.router.narration.maxPerRun.toString() }, getString = { it.talkie.narration.maxPerRun.toString() },
withString = { c, v -> nar(c) { it.copy(maxPerRun = int("router.narration.max_per_run", v)) } }, withString = { c, v -> nar(c) { it.copy(maxPerRun = int("router.narration.max_per_run", v)) } },
), ),
EditableField( EditableField(
"router.narration.model_id", ConfigFieldType.STRING, "router.narration.model_id", ConfigFieldType.STRING,
getString = { it.router.narration.modelId.orEmpty() }, getString = { it.talkie.narration.modelId.orEmpty() },
withString = { c, v -> nar(c) { it.copy(modelId = v.trim().ifBlank { null }) } }, withString = { c, v -> nar(c) { it.copy(modelId = v.trim().ifBlank { null }) } },
), ),
EditableField( EditableField(
@@ -215,7 +215,7 @@ private fun srv(c: CorrexConfig, f: (ServerConfig) -> ServerConfig) = c.copy(ser
private fun tui(c: CorrexConfig, f: (TuiConfig) -> TuiConfig) = c.copy(tui = f(c.tui)) private fun tui(c: CorrexConfig, f: (TuiConfig) -> TuiConfig) = c.copy(tui = f(c.tui))
private fun cli(c: CorrexConfig, f: (CliConfig) -> CliConfig) = c.copy(cli = f(c.cli)) private fun cli(c: CorrexConfig, f: (CliConfig) -> CliConfig) = c.copy(cli = f(c.cli))
private fun tls(c: CorrexConfig, f: (ToolsConfig) -> ToolsConfig) = c.copy(tools = f(c.tools)) private fun tls(c: CorrexConfig, f: (ToolsConfig) -> ToolsConfig) = c.copy(tools = f(c.tools))
private fun rtr(c: CorrexConfig, f: (RouterConfig) -> RouterConfig) = c.copy(router = f(c.router)) private fun rtr(c: CorrexConfig, f: (TalkieConfig) -> TalkieConfig) = c.copy(talkie = f(c.talkie))
private fun gen(c: CorrexConfig, f: (GenerationSettings) -> GenerationSettings) = private fun gen(c: CorrexConfig, f: (GenerationSettings) -> GenerationSettings) =
rtr(c) { it.copy(generation = f(it.generation)) } rtr(c) { it.copy(generation = f(it.generation)) }
private fun nar(c: CorrexConfig, f: (NarrationSettings) -> NarrationSettings) = private fun nar(c: CorrexConfig, f: (NarrationSettings) -> NarrationSettings) =
@@ -307,16 +307,16 @@ class ConfigLoaderTest {
parseTomlMethod.isAccessible = true parseTomlMethod.isAccessible = true
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
assertEquals("llamacpp", result.router.embedder.backend) assertEquals("llamacpp", result.talkie.embedder.backend)
assertEquals(1536, result.router.embedder.dimension) assertEquals(1536, result.talkie.embedder.dimension)
assertEquals("http://localhost:11000", result.router.embedder.url) assertEquals("http://localhost:11000", result.talkie.embedder.url)
assertEquals("nomic-embed-text", result.router.embedder.modelId) assertEquals("nomic-embed-text", result.talkie.embedder.modelId)
assertEquals("turbovec", result.router.l3.backend) assertEquals("turbovec", result.talkie.l3.backend)
assertEquals("/tmp/router/l3.tq", result.router.l3.persistPath) assertEquals("/tmp/router/l3.tq", result.talkie.l3.persistPath)
assertEquals("python3", result.router.l3.pythonExecutable) assertEquals("python3", result.talkie.l3.pythonExecutable)
assertEquals(1536, result.router.l3.dim) assertEquals(1536, result.talkie.l3.dim)
assertEquals(4, result.router.l3.bitWidth) assertEquals(4, result.talkie.l3.bitWidth)
} }
@Test @Test
@@ -331,16 +331,16 @@ class ConfigLoaderTest {
parseTomlMethod.isAccessible = true parseTomlMethod.isAccessible = true
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
assertEquals("noop", result.router.embedder.backend) assertEquals("noop", result.talkie.embedder.backend)
assertEquals(1536, result.router.embedder.dimension) assertEquals(1536, result.talkie.embedder.dimension)
assertEquals(null, result.router.embedder.url) assertEquals(null, result.talkie.embedder.url)
assertEquals(null, result.router.embedder.modelId) assertEquals(null, result.talkie.embedder.modelId)
assertEquals("in_memory", result.router.l3.backend) assertEquals("in_memory", result.talkie.l3.backend)
assertEquals(null, result.router.l3.persistPath) assertEquals(null, result.talkie.l3.persistPath)
assertEquals("python3", result.router.l3.pythonExecutable) assertEquals("python3", result.talkie.l3.pythonExecutable)
assertEquals(1536, result.router.l3.dim) assertEquals(1536, result.talkie.l3.dim)
assertEquals(4, result.router.l3.bitWidth) assertEquals(4, result.talkie.l3.bitWidth)
} }
@Test @Test
@@ -477,15 +477,15 @@ class ConfigLoaderTest {
parseTomlMethod.isAccessible = true parseTomlMethod.isAccessible = true
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
assertEquals(8, result.router.conversationKeepLast) assertEquals(8, result.talkie.conversationKeepLast)
assertEquals(3, result.router.retrievalK) assertEquals(3, result.talkie.retrievalK)
assertEquals(8192, result.router.tokenBudget) assertEquals(8192, result.talkie.tokenBudget)
assertEquals(0.4, result.router.generation.temperature) assertEquals(0.4, result.talkie.generation.temperature)
assertEquals(0.8, result.router.generation.topP) assertEquals(0.8, result.talkie.generation.topP)
assertEquals(600, result.router.generation.maxTokens) assertEquals(600, result.talkie.generation.maxTokens)
assertEquals(0.55, result.router.narration.temperature) assertEquals(0.55, result.talkie.narration.temperature)
assertEquals(2048, result.router.narration.maxTokens) assertEquals(2048, result.talkie.narration.maxTokens)
assertEquals(25, result.router.narration.maxPerRun) assertEquals(25, result.talkie.narration.maxPerRun)
} }
@Test @Test
@@ -494,10 +494,10 @@ class ConfigLoaderTest {
parseTomlMethod.isAccessible = true parseTomlMethod.isAccessible = true
val result = parseTomlMethod.invoke(ConfigLoader, "[server]\nhost = \"x\"") as CorrexConfig val result = parseTomlMethod.invoke(ConfigLoader, "[server]\nhost = \"x\"") as CorrexConfig
assertEquals(6, result.router.conversationKeepLast) assertEquals(6, result.talkie.conversationKeepLast)
assertEquals(512, result.router.generation.maxTokens) assertEquals(512, result.talkie.generation.maxTokens)
assertEquals(4096, result.router.narration.maxTokens) assertEquals(4096, result.talkie.narration.maxTokens)
assertEquals(100, result.router.narration.maxPerRun) assertEquals(100, result.talkie.narration.maxPerRun)
} }
@Test @Test
@@ -530,7 +530,7 @@ class ConfigLoaderTest {
val result = ConfigLoader.parseTomlForTest(toml) val result = ConfigLoader.parseTomlForTest(toml)
assertEquals("llama-cpp:phi-3-mini", result.router.narration.modelId) assertEquals("llama-cpp:phi-3-mini", result.talkie.narration.modelId)
} }
@Test @Test
@@ -542,6 +542,6 @@ class ConfigLoaderTest {
val result = ConfigLoader.parseTomlForTest(toml) val result = ConfigLoader.parseTomlForTest(toml)
assertNull(result.router.narration.modelId) assertNull(result.talkie.narration.modelId)
} }
} }
@@ -26,7 +26,7 @@ class CorrexConfigWriterTest {
shellAllowedExecutables = listOf("git", "ls"), shellAllowedExecutables = listOf("git", "ls"),
networkAllowedHosts = listOf("example.com"), networkAllowedHosts = listOf("example.com"),
), ),
router = RouterConfig( talkie = TalkieConfig(
conversationKeepLast = 7, conversationKeepLast = 7,
retrievalK = 4, retrievalK = 4,
tokenBudget = 5000, tokenBudget = 5000,
@@ -25,7 +25,7 @@ class EditableConfigTest {
CorrexConfig(), CorrexConfig(),
mapOf("router.generation.temperature" to "0.25", "orchestration.stage_timeout_ms" to "90000"), mapOf("router.generation.temperature" to "0.25", "orchestration.stage_timeout_ms" to "90000"),
).getOrThrow() ).getOrThrow()
assertEquals(0.25, out.router.generation.temperature) assertEquals(0.25, out.talkie.generation.temperature)
assertEquals(90_000L, out.orchestration.stageTimeoutMs) assertEquals(90_000L, out.orchestration.stageTimeoutMs)
} }
@@ -53,7 +53,7 @@ data class ContextTruncatedEvent(
@Serializable @Serializable
@SerialName("RouterNarration") @SerialName("RouterNarration")
data class RouterNarrationEvent( data class TalkieNarrationEvent(
val sessionId: SessionId, val sessionId: SessionId,
val narrationId: String, val narrationId: String,
val trigger: String, val trigger: String,
@@ -20,7 +20,7 @@ import com.correx.core.events.events.ClarificationRequestedEvent
import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.events.CritiqueFindingsRecordedEvent import com.correx.core.events.events.CritiqueFindingsRecordedEvent
import com.correx.core.events.events.CritiqueOutcomeCorrelatedEvent import com.correx.core.events.events.CritiqueOutcomeCorrelatedEvent
import com.correx.core.events.events.RouterNarrationEvent import com.correx.core.events.events.TalkieNarrationEvent
import com.correx.core.events.events.OperatorProfileBoundEvent import com.correx.core.events.events.OperatorProfileBoundEvent
import com.correx.core.events.events.ProjectProfileBoundEvent import com.correx.core.events.events.ProjectProfileBoundEvent
import com.correx.core.events.events.SessionWorkspaceBoundEvent import com.correx.core.events.events.SessionWorkspaceBoundEvent
@@ -147,7 +147,7 @@ val eventModule = SerializersModule {
subclass(ChatSessionStartedEvent::class) subclass(ChatSessionStartedEvent::class)
subclass(SessionWorkingTaskEvent::class) subclass(SessionWorkingTaskEvent::class)
subclass(ChatTurnEvent::class) subclass(ChatTurnEvent::class)
subclass(RouterNarrationEvent::class) subclass(TalkieNarrationEvent::class)
subclass(SessionWorkspaceBoundEvent::class) subclass(SessionWorkspaceBoundEvent::class)
subclass(OperatorProfileBoundEvent::class) subclass(OperatorProfileBoundEvent::class)
subclass(ProjectProfileBoundEvent::class) subclass(ProjectProfileBoundEvent::class)
@@ -1,16 +1,16 @@
package com.correx.core.events.serialization package com.correx.core.events.serialization
import com.correx.core.events.events.EventPayload import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.RouterNarrationEvent import com.correx.core.events.events.TalkieNarrationEvent
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.inference.TokenUsage import com.correx.core.inference.TokenUsage
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
class RouterNarrationEventSerializationTest { class TalkieNarrationEventSerializationTest {
private val sample = RouterNarrationEvent( private val sample = TalkieNarrationEvent(
sessionId = SessionId("sess-1"), sessionId = SessionId("sess-1"),
narrationId = "narr-42", narrationId = "narr-42",
trigger = "stage_start", trigger = "stage_start",
@@ -24,6 +24,8 @@ class RouterNarrationEventSerializationTest {
@Test @Test
fun `round-trips as polymorphic EventPayload`() { fun `round-trips as polymorphic EventPayload`() {
val encoded = eventJson.encodeToString(EventPayload.serializer(), sample) val encoded = eventJson.encodeToString(EventPayload.serializer(), sample)
// Wire discriminator is intentionally still "RouterNarration": the class was renamed
// Router->Talkie, but changing a persisted @SerialName would break replay of existing logs.
assertTrue(encoded.contains("\"type\":\"RouterNarration\""), "SerialName must be present: $encoded") assertTrue(encoded.contains("\"type\":\"RouterNarration\""), "SerialName must be present: $encoded")
val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded) val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded)
assertEquals(sample, decoded) assertEquals(sample, decoded)
@@ -31,7 +33,7 @@ class RouterNarrationEventSerializationTest {
@Test @Test
fun `round-trips with optional fields absent`() { fun `round-trips with optional fields absent`() {
val minimal = RouterNarrationEvent( val minimal = TalkieNarrationEvent(
sessionId = SessionId("sess-2"), sessionId = SessionId("sess-2"),
narrationId = "narr-0", narrationId = "narr-0",
trigger = "manual", trigger = "manual",
@@ -1,15 +0,0 @@
package com.correx.core.router
import com.correx.core.events.events.StoredEvent
import com.correx.core.router.model.RouterState
import com.correx.core.sessions.projections.Projection
class RouterProjector(
private val reducer: RouterReducer,
) : Projection<RouterState> {
override fun initial(): RouterState = reducer.initial
override fun apply(state: RouterState, event: StoredEvent): RouterState =
reducer.reduce(state, event)
}
@@ -1,16 +0,0 @@
package com.correx.core.router
import com.correx.core.events.types.SessionId
import com.correx.core.router.model.RouterState
import com.correx.core.sessions.projections.replay.EventReplayer
interface RouterRepository {
suspend fun getRouterState(sessionId: SessionId): RouterState
}
class DefaultRouterRepository(
private val replayer: EventReplayer<RouterState>,
) : RouterRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState =
replayer.rebuild(sessionId)
}
@@ -1,11 +1,11 @@
package com.correx.core.router package com.correx.core.talkie
import com.correx.core.events.events.IdeaCapturedEvent import com.correx.core.events.events.IdeaCapturedEvent
import com.correx.core.events.events.IdeaDiscardedEvent import com.correx.core.events.events.IdeaDiscardedEvent
import com.correx.core.events.events.IdeaPromotedEvent import com.correx.core.events.events.IdeaPromotedEvent
import com.correx.core.events.stores.EventStore import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.router.model.Idea import com.correx.core.talkie.model.Idea
/** /**
* Rebuilds the operator's idea board from the event log. Cross-session by design: it folds over * Rebuilds the operator's idea board from the event log. Cross-session by design: it folds over
@@ -1,4 +1,4 @@
package com.correx.core.router package com.correx.core.talkie
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
@@ -1,4 +1,4 @@
package com.correx.core.router package com.correx.core.talkie
import com.correx.core.context.model.CompressionMetadata import com.correx.core.context.model.CompressionMetadata
import com.correx.core.context.model.ContextEntry import com.correx.core.context.model.ContextEntry
@@ -11,20 +11,20 @@ import com.correx.core.events.types.ContextPackId
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId import com.correx.core.events.types.StageId
import com.correx.core.inference.Tokenizer import com.correx.core.inference.Tokenizer
import com.correx.core.router.model.Idea import com.correx.core.talkie.model.Idea
import com.correx.core.router.model.NarrationTrigger import com.correx.core.talkie.model.NarrationTrigger
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.router.model.RouterL2Entry import com.correx.core.talkie.model.RouterL2Entry
import com.correx.core.router.model.RouterState import com.correx.core.talkie.model.TalkieState
import com.correx.core.router.model.RouterTurn import com.correx.core.talkie.model.TalkieTurn
import com.correx.core.router.model.TurnRole import com.correx.core.talkie.model.TurnRole
import com.correx.core.router.model.WorkflowSummary import com.correx.core.talkie.model.WorkflowSummary
import java.util.* import java.util.*
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
interface RouterContextBuilder { interface TalkieContextBuilder {
suspend fun build( suspend fun build(
state: RouterState, state: TalkieState,
budget: TokenBudget, budget: TokenBudget,
availableWorkflows: List<WorkflowSummary> = emptyList(), availableWorkflows: List<WorkflowSummary> = emptyList(),
projectProfileText: String? = null, projectProfileText: String? = null,
@@ -38,18 +38,18 @@ interface RouterContextBuilder {
* Default delegates to [build] so that anonymous test stubs that only override [build] compile * Default delegates to [build] so that anonymous test stubs that only override [build] compile
* without needing a stub override. * without needing a stub override.
*/ */
suspend fun buildNarrationContext(state: RouterState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = suspend fun buildNarrationContext(state: TalkieState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack =
build(state, budget, emptyList(), null) build(state, budget, emptyList(), null)
} }
class DefaultRouterContextBuilder( class DefaultTalkieContextBuilder(
private val config: RouterConfig, private val config: TalkieConfig,
private val tokenizer: Tokenizer? = null, private val tokenizer: Tokenizer? = null,
// Cross-session ideas to surface to the router (router-only context). Read fresh each build so // Cross-session ideas to surface to the router (router-only context). Read fresh each build so
// newly-captured ideas appear on the next turn; injected via the constructor so the build() // newly-captured ideas appear on the next turn; injected via the constructor so the build()
// interface — and its many test doubles — stay unchanged. // interface — and its many test doubles — stay unchanged.
private val ideaProvider: () -> List<Idea> = { emptyList() }, private val ideaProvider: () -> List<Idea> = { emptyList() },
) : RouterContextBuilder { ) : TalkieContextBuilder {
companion object { companion object {
private val SYSTEM_PROMPT = private val SYSTEM_PROMPT =
@@ -106,7 +106,7 @@ class DefaultRouterContextBuilder(
} }
override suspend fun build( override suspend fun build(
state: RouterState, state: TalkieState,
budget: TokenBudget, budget: TokenBudget,
availableWorkflows: List<WorkflowSummary>, availableWorkflows: List<WorkflowSummary>,
projectProfileText: String?, projectProfileText: String?,
@@ -179,7 +179,7 @@ class DefaultRouterContextBuilder(
// --- Conversation turns (L1) --- // --- Conversation turns (L1) ---
// The current (protected) user turn is the last element in conversationHistory, // The current (protected) user turn is the last element in conversationHistory,
// regardless of conversationKeepLast — it must always be present. // regardless of conversationKeepLast — it must always be present.
val protectedUserTurn: RouterTurn? = state.conversationHistory.lastOrNull() val protectedUserTurn: TalkieTurn? = state.conversationHistory.lastOrNull()
// Apply conversationKeepLast cap. The protected turn may or may not fall within this window. // Apply conversationKeepLast cap. The protected turn may or may not fall within this window.
val cappedTurns = state.conversationHistory.takeLast(config.conversationKeepLast) val cappedTurns = state.conversationHistory.takeLast(config.conversationKeepLast)
@@ -317,7 +317,7 @@ class DefaultRouterContextBuilder(
) )
} }
private fun buildWorkflowStatusContent(state: RouterState, stageOverride: String? = null): String { private fun buildWorkflowStatusContent(state: TalkieState, stageOverride: String? = null): String {
val status = capitalizeFirst(state.workflowStatus.name.lowercase()) val status = capitalizeFirst(state.workflowStatus.name.lowercase())
val stage = stageOverride ?: state.currentStageId?.value ?: "none" val stage = stageOverride ?: state.currentStageId?.value ?: "none"
return "Status: $status, Stage: $stage" return "Status: $status, Stage: $stage"
@@ -400,7 +400,7 @@ class DefaultRouterContextBuilder(
* narration lines never enter the conversation flow. * narration lines never enter the conversation flow.
*/ */
override suspend fun buildNarrationContext( override suspend fun buildNarrationContext(
state: RouterState, state: TalkieState,
trigger: NarrationTrigger, trigger: NarrationTrigger,
budget: TokenBudget, budget: TokenBudget,
): ContextPack { ): ContextPack {
@@ -1,4 +1,4 @@
package com.correx.core.router package com.correx.core.talkie
import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.events.ChatTurnRole import com.correx.core.events.events.ChatTurnRole
@@ -8,7 +8,7 @@ import com.correx.core.events.events.IdeaCapturedEvent
import com.correx.core.events.events.L3MemoryRetrievedEvent import com.correx.core.events.events.L3MemoryRetrievedEvent
import com.correx.core.events.events.L3RetrievedHit import com.correx.core.events.events.L3RetrievedHit
import com.correx.core.events.events.NewEvent import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.RouterNarrationEvent import com.correx.core.events.events.TalkieNarrationEvent
import com.correx.core.events.events.SteeringNoteAddedEvent import com.correx.core.events.events.SteeringNoteAddedEvent
import com.correx.core.events.events.WorkflowProposedEvent import com.correx.core.events.events.WorkflowProposedEvent
import com.correx.core.events.stores.EventStore import com.correx.core.events.stores.EventStore
@@ -23,53 +23,53 @@ import com.correx.core.inference.InferenceRouter
import com.correx.core.inference.ModelCapability import com.correx.core.inference.ModelCapability
import com.correx.core.inference.ResponseFormat import com.correx.core.inference.ResponseFormat
import com.correx.core.inference.TokenUsage import com.correx.core.inference.TokenUsage
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
import com.correx.core.router.model.NarrationTrigger import com.correx.core.talkie.model.NarrationTrigger
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.router.model.RouterResponse import com.correx.core.talkie.model.TalkieResponse
import com.correx.core.router.model.WorkflowSummary import com.correx.core.talkie.model.WorkflowSummary
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.util.UUID import java.util.UUID
private val log = LoggerFactory.getLogger(DefaultRouterFacade::class.java) private val log = LoggerFactory.getLogger(DefaultTalkieFacade::class.java)
interface RouterFacade { interface TalkieFacade {
suspend fun onUserInput( suspend fun onUserInput(
sessionId: SessionId, sessionId: SessionId,
input: String, input: String,
mode: ChatMode = ChatMode.CHAT, mode: ChatMode = ChatMode.CHAT,
): RouterResponse ): TalkieResponse
suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger)
} }
class DefaultRouterFacade( class DefaultTalkieFacade(
private val routerRepository: RouterRepository, private val routerRepository: TalkieRepository,
private val routerContextBuilder: RouterContextBuilder, private val routerContextBuilder: TalkieContextBuilder,
private val inferenceRouter: InferenceRouter, private val inferenceRouter: InferenceRouter,
private val eventStore: EventStore, private val eventStore: EventStore,
private val config: RouterConfig, private val config: TalkieConfig,
private val validateSteering: (suspend (String) -> String?)? = null, private val validateSteering: (suspend (String) -> String?)? = null,
private val embedder: Embedder, private val embedder: Embedder,
private val l3MemoryStore: L3MemoryStore, private val l3MemoryStore: L3MemoryStore,
private val workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() }, private val workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() },
private val sessionProfileProvider: suspend (SessionId) -> String? = { null }, private val sessionProfileProvider: suspend (SessionId) -> String? = { null },
) : RouterFacade { ) : TalkieFacade {
override suspend fun onUserInput( override suspend fun onUserInput(
sessionId: SessionId, sessionId: SessionId,
input: String, input: String,
mode: ChatMode, mode: ChatMode,
): RouterResponse { ): TalkieResponse {
// Emit USER turn event and get the turnId for retrieval dedup // Emit USER turn event and get the turnId for retrieval dedup
val userTurnId = emitChatTurn(sessionId, input, ChatTurnRole.USER) val userTurnId = emitChatTurn(sessionId, input, ChatTurnRole.USER)
// Rebuild state with user turn appended // Rebuild state with user turn appended
var state = routerRepository.getRouterState(sessionId) var state = routerRepository.getTalkieState(sessionId)
val effectiveStageId = state.currentStageId ?: StageId.NONE val effectiveStageId = state.currentStageId ?: StageId.NONE
// L3 retrieval — non-fatal; results fed back into state via event. // L3 retrieval — non-fatal; results fed back into state via event.
@@ -119,7 +119,7 @@ class DefaultRouterFacade(
), ),
) )
// Rebuild state so lastRetrievedMemory reflects this turn // Rebuild state so lastRetrievedMemory reflects this turn
state = routerRepository.getRouterState(sessionId) state = routerRepository.getTalkieState(sessionId)
val contextPack = routerContextBuilder.build(state, config.tokenBudget, workflowSummaryProvider(), sessionProfileProvider(sessionId)) val contextPack = routerContextBuilder.build(state, config.tokenBudget, workflowSummaryProvider(), sessionProfileProvider(sessionId))
@@ -199,11 +199,11 @@ class DefaultRouterFacade(
} }
} }
return RouterResponse(content = content, steeringEmitted = steeringEmitted) return TalkieResponse(content = content, steeringEmitted = steeringEmitted)
} }
override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) { override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) {
val state = routerRepository.getRouterState(sessionId) val state = routerRepository.getTalkieState(sessionId)
val effectiveStageId = trigger.stageId?.let { StageId(it) } ?: state.currentStageId ?: StageId.NONE val effectiveStageId = trigger.stageId?.let { StageId(it) } ?: state.currentStageId ?: StageId.NONE
val contextPack = routerContextBuilder.buildNarrationContext(state, trigger, config.tokenBudget) val contextPack = routerContextBuilder.buildNarrationContext(state, trigger, config.tokenBudget)
@@ -234,7 +234,7 @@ class DefaultRouterFacade(
causationId = null, causationId = null,
correlationId = null, correlationId = null,
), ),
payload = RouterNarrationEvent( payload = TalkieNarrationEvent(
sessionId = sessionId, sessionId = sessionId,
narrationId = narrationId, narrationId = narrationId,
trigger = trigger.kind, trigger = trigger.kind,
@@ -0,0 +1,15 @@
package com.correx.core.talkie
import com.correx.core.events.events.StoredEvent
import com.correx.core.talkie.model.TalkieState
import com.correx.core.sessions.projections.Projection
class TalkieProjector(
private val reducer: TalkieReducer,
) : Projection<TalkieState> {
override fun initial(): TalkieState = reducer.initial
override fun apply(state: TalkieState, event: StoredEvent): TalkieState =
reducer.reduce(state, event)
}
@@ -1,4 +1,4 @@
package com.correx.core.router package com.correx.core.talkie
import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.events.L3MemoryRetrievedEvent import com.correx.core.events.events.L3MemoryRetrievedEvent
@@ -11,22 +11,22 @@ import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.WorkflowCompletedEvent import com.correx.core.events.events.WorkflowCompletedEvent
import com.correx.core.events.events.WorkflowFailedEvent import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowStartedEvent import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.router.model.RouterL2Entry import com.correx.core.talkie.model.RouterL2Entry
import com.correx.core.router.model.RouterState import com.correx.core.talkie.model.TalkieState
import com.correx.core.router.model.RouterTurn import com.correx.core.talkie.model.TalkieTurn
import com.correx.core.router.model.StageOutcomeKind import com.correx.core.talkie.model.StageOutcomeKind
import com.correx.core.router.model.TurnRole import com.correx.core.talkie.model.TurnRole
import com.correx.core.router.model.WorkflowStatus import com.correx.core.talkie.model.WorkflowStatus
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
interface RouterReducer { interface TalkieReducer {
val initial: RouterState val initial: TalkieState
fun reduce(state: RouterState, event: StoredEvent): RouterState fun reduce(state: TalkieState, event: StoredEvent): TalkieState
} }
class DefaultRouterReducer : RouterReducer { class DefaultTalkieReducer : TalkieReducer {
override val initial: RouterState = RouterState( override val initial: TalkieState = TalkieState(
sessionId = null, sessionId = null,
workflowStatus = WorkflowStatus.IDLE, workflowStatus = WorkflowStatus.IDLE,
currentStageId = null, currentStageId = null,
@@ -34,7 +34,7 @@ class DefaultRouterReducer : RouterReducer {
conversationHistory = emptyList(), conversationHistory = emptyList(),
) )
override fun reduce(state: RouterState, event: StoredEvent): RouterState { override fun reduce(state: TalkieState, event: StoredEvent): TalkieState {
return when (val payload = event.payload) { return when (val payload = event.payload) {
is WorkflowStartedEvent -> handleWorkflowStarted(state, event) is WorkflowStartedEvent -> handleWorkflowStarted(state, event)
is WorkflowCompletedEvent -> handleWorkflowCompleted(state) is WorkflowCompletedEvent -> handleWorkflowCompleted(state)
@@ -50,7 +50,7 @@ class DefaultRouterReducer : RouterReducer {
} }
} }
private fun handleWorkflowStarted(state: RouterState, event: StoredEvent): RouterState { private fun handleWorkflowStarted(state: TalkieState, event: StoredEvent): TalkieState {
val payload = event.payload as WorkflowStartedEvent val payload = event.payload as WorkflowStartedEvent
return state.copy( return state.copy(
sessionId = payload.sessionId, sessionId = payload.sessionId,
@@ -59,33 +59,33 @@ class DefaultRouterReducer : RouterReducer {
) )
} }
private fun handleWorkflowCompleted(state: RouterState): RouterState { private fun handleWorkflowCompleted(state: TalkieState): TalkieState {
return state.copy( return state.copy(
workflowStatus = WorkflowStatus.COMPLETED, workflowStatus = WorkflowStatus.COMPLETED,
currentStageId = null currentStageId = null
) )
} }
private fun handleWorkflowFailed(state: RouterState): RouterState { private fun handleWorkflowFailed(state: TalkieState): TalkieState {
return state.copy( return state.copy(
workflowStatus = WorkflowStatus.FAILED, workflowStatus = WorkflowStatus.FAILED,
currentStageId = null currentStageId = null
) )
} }
private fun handleOrchestrationPaused(state: RouterState): RouterState { private fun handleOrchestrationPaused(state: TalkieState): TalkieState {
return state.copy( return state.copy(
workflowStatus = WorkflowStatus.PAUSED workflowStatus = WorkflowStatus.PAUSED
) )
} }
private fun handleOrchestrationResumed(state: RouterState): RouterState { private fun handleOrchestrationResumed(state: TalkieState): TalkieState {
return state.copy( return state.copy(
workflowStatus = WorkflowStatus.RUNNING workflowStatus = WorkflowStatus.RUNNING
) )
} }
private fun handleStageCompleted(state: RouterState, event: StoredEvent): RouterState { private fun handleStageCompleted(state: TalkieState, event: StoredEvent): TalkieState {
val payload = event.payload as StageCompletedEvent val payload = event.payload as StageCompletedEvent
val entry = RouterL2Entry( val entry = RouterL2Entry(
stageId = payload.stageId, stageId = payload.stageId,
@@ -100,7 +100,7 @@ class DefaultRouterReducer : RouterReducer {
) )
} }
private fun handleStageFailed(state: RouterState, event: StoredEvent): RouterState { private fun handleStageFailed(state: TalkieState, event: StoredEvent): TalkieState {
val payload = event.payload as StageFailedEvent val payload = event.payload as StageFailedEvent
val entry = RouterL2Entry( val entry = RouterL2Entry(
stageId = payload.stageId, stageId = payload.stageId,
@@ -120,16 +120,16 @@ class DefaultRouterReducer : RouterReducer {
// adr-0003 §4: harvest user steering into the current stage's decision point. // adr-0003 §4: harvest user steering into the current stage's decision point.
// Accumulate here; handleStageCompleted/Failed attach and clear. Pure field // Accumulate here; handleStageCompleted/Failed attach and clear. Pure field
// extraction from the event stream — no inference. // extraction from the event stream — no inference.
private fun handleSteeringNote(state: RouterState, payload: SteeringNoteAddedEvent): RouterState = private fun handleSteeringNote(state: TalkieState, payload: SteeringNoteAddedEvent): TalkieState =
state.copy(pendingSteeringNotes = state.pendingSteeringNotes + payload.content) state.copy(pendingSteeringNotes = state.pendingSteeringNotes + payload.content)
private fun handleChatTurn(state: RouterState, event: StoredEvent): RouterState { private fun handleChatTurn(state: TalkieState, event: StoredEvent): TalkieState {
val payload = event.payload as ChatTurnEvent val payload = event.payload as ChatTurnEvent
val turnRole = when (payload.role) { val turnRole = when (payload.role) {
com.correx.core.events.events.ChatTurnRole.USER -> TurnRole.USER com.correx.core.events.events.ChatTurnRole.USER -> TurnRole.USER
com.correx.core.events.events.ChatTurnRole.ROUTER -> TurnRole.ROUTER com.correx.core.events.events.ChatTurnRole.ROUTER -> TurnRole.ROUTER
} }
val turn = RouterTurn( val turn = TalkieTurn(
role = turnRole, role = turnRole,
content = payload.content, content = payload.content,
timestamp = Instant.fromEpochMilliseconds(payload.timestampMs), timestamp = Instant.fromEpochMilliseconds(payload.timestampMs),
@@ -0,0 +1,16 @@
package com.correx.core.talkie
import com.correx.core.events.types.SessionId
import com.correx.core.talkie.model.TalkieState
import com.correx.core.sessions.projections.replay.EventReplayer
interface TalkieRepository {
suspend fun getTalkieState(sessionId: SessionId): TalkieState
}
class DefaultTalkieRepository(
private val replayer: EventReplayer<TalkieState>,
) : TalkieRepository {
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId)
}
@@ -1,4 +1,4 @@
package com.correx.core.router package com.correx.core.talkie
import com.correx.core.events.events.ProposedWorkflow import com.correx.core.events.events.ProposedWorkflow
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@@ -1,4 +1,4 @@
package com.correx.core.router.l3 package com.correx.core.talkie.l3
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -1,4 +1,4 @@
package com.correx.core.router.l3 package com.correx.core.talkie.l3
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -1,4 +1,4 @@
package com.correx.core.router.l3 package com.correx.core.talkie.l3
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -1,4 +1,4 @@
package com.correx.core.router.l3 package com.correx.core.talkie.l3
/** /**
* Cross-session memory store interface for vector-based retrieval. * Cross-session memory store interface for vector-based retrieval.
@@ -1,4 +1,4 @@
package com.correx.core.router.l3 package com.correx.core.talkie.l3
import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.stores.EventStore import com.correx.core.events.stores.EventStore
@@ -1,4 +1,4 @@
package com.correx.core.router.l3 package com.correx.core.talkie.l3
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -1,4 +1,4 @@
package com.correx.core.router.l3 package com.correx.core.talkie.l3
/** /**
* An [L3MemoryStore] whose vectors persist independently of the JVM (e.g. a * An [L3MemoryStore] whose vectors persist independently of the JVM (e.g. a
@@ -1,4 +1,4 @@
package com.correx.core.router.model package com.correx.core.talkie.model
/** An active idea on the operator's cross-session scratchpad (a captured, not-yet-discarded note). */ /** An active idea on the operator's cross-session scratchpad (a captured, not-yet-discarded note). */
data class Idea( data class Idea(
@@ -1,9 +1,9 @@
package com.correx.core.router.model package com.correx.core.talkie.model
/** /**
* Describes the event or condition that prompted a narration inference call. * Describes the event or condition that prompted a narration inference call.
* *
* The trigger is stored verbatim in [RouterNarrationEvent.trigger] so it can be * The trigger is stored verbatim in [TalkieNarrationEvent.trigger] so it can be
* replayed and displayed without re-querying the environment. * replayed and displayed without re-querying the environment.
*/ */
data class NarrationTrigger( data class NarrationTrigger(
@@ -1,11 +1,11 @@
package com.correx.core.router.model package com.correx.core.talkie.model
import com.correx.core.context.model.TokenBudget import com.correx.core.context.model.TokenBudget
import com.correx.core.inference.GenerationConfig import com.correx.core.inference.GenerationConfig
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class RouterConfig( data class TalkieConfig(
val conversationKeepLast: Int = 6, val conversationKeepLast: Int = 6,
val retrievalK: Int = 5, val retrievalK: Int = 5,
val tokenBudget: TokenBudget = TokenBudget(limit = 4096), val tokenBudget: TokenBudget = TokenBudget(limit = 4096),
@@ -1,9 +1,9 @@
package com.correx.core.router.model package com.correx.core.talkie.model
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class RouterResponse( data class TalkieResponse(
val content: String, val content: String,
val steeringEmitted: Boolean = false, val steeringEmitted: Boolean = false,
) )
@@ -1,4 +1,4 @@
package com.correx.core.router.model package com.correx.core.talkie.model
import com.correx.core.events.events.L3RetrievedHit import com.correx.core.events.events.L3RetrievedHit
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
@@ -41,7 +41,7 @@ data class RouterL2Entry(
) )
@Serializable @Serializable
data class RouterTurn( data class TalkieTurn(
val role: TurnRole, val role: TurnRole,
val content: String, val content: String,
val timestamp: Instant, val timestamp: Instant,
@@ -49,12 +49,12 @@ data class RouterTurn(
) )
@Serializable @Serializable
data class RouterState( data class TalkieState(
val sessionId: SessionId? = null, val sessionId: SessionId? = null,
val workflowStatus: WorkflowStatus = WorkflowStatus.IDLE, val workflowStatus: WorkflowStatus = WorkflowStatus.IDLE,
val currentStageId: StageId? = null, val currentStageId: StageId? = null,
val l2Memory: List<RouterL2Entry> = emptyList(), val l2Memory: List<RouterL2Entry> = emptyList(),
val conversationHistory: List<RouterTurn> = emptyList(), val conversationHistory: List<TalkieTurn> = emptyList(),
val lastRetrievedMemory: List<L3RetrievedHit> = emptyList(), val lastRetrievedMemory: List<L3RetrievedHit> = emptyList(),
/** /**
* Steering notes accumulated during the current stage, harvested into the * Steering notes accumulated during the current stage, harvested into the
@@ -1,4 +1,4 @@
package com.correx.core.router.model package com.correx.core.talkie.model
data class WorkflowSummary( data class WorkflowSummary(
val id: String, val id: String,
@@ -1,4 +1,4 @@
package com.correx.core.router package com.correx.core.talkie
import com.correx.core.events.events.EventMetadata import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload import com.correx.core.events.events.EventPayload
@@ -1,4 +1,4 @@
package com.correx.core.router package com.correx.core.talkie
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
@@ -1,4 +1,4 @@
package com.correx.core.router package com.correx.core.talkie
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
@@ -1,4 +1,4 @@
package com.correx.core.router.l3 package com.correx.core.talkie.l3
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.utils.TypeId import com.correx.core.utils.TypeId
+1 -1
View File
@@ -7,7 +7,7 @@ plugins {
dependencies { dependencies {
implementation project(":core:tools") implementation project(":core:tools")
implementation project(":core:events") implementation project(":core:events")
implementation project(":core:router") implementation project(":core:talkie")
implementation project(":core:inference") implementation project(":core:inference")
implementation project(":core:approvals") implementation project(":core:approvals")
implementation project(":core:sessions") implementation project(":core:sessions")
+1 -1
View File
@@ -5,7 +5,7 @@ plugins {
} }
dependencies { dependencies {
implementation project(':core:router') implementation project(':core:talkie')
implementation project(':core:events') implementation project(':core:events')
testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.junit.jupiter:junit-jupiter"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test"
+1 -1
View File
@@ -5,7 +5,7 @@ plugins {
} }
dependencies { dependencies {
implementation project(':core:router') implementation project(':core:talkie')
implementation project(':core:sessions') implementation project(':core:sessions')
implementation project(':core:events') implementation project(':core:events')
testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.junit.jupiter:junit-jupiter"
@@ -1,9 +1,9 @@
package com.correx.infrastructure.router.turbovec package com.correx.infrastructure.router.turbovec
import com.correx.core.router.l3.L3Hit import com.correx.core.talkie.l3.L3Hit
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
import com.correx.core.router.l3.RehydratableL3MemoryStore import com.correx.core.talkie.l3.RehydratableL3MemoryStore
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
@@ -22,17 +22,17 @@ import com.correx.core.inference.InferenceRouter
import com.correx.core.inference.ModelCapability import com.correx.core.inference.ModelCapability
import com.correx.core.inference.NoopEmbedder import com.correx.core.inference.NoopEmbedder
import com.correx.core.inference.Tokenizer import com.correx.core.inference.Tokenizer
import com.correx.core.router.DefaultRouterContextBuilder import com.correx.core.talkie.DefaultTalkieContextBuilder
import com.correx.core.router.DefaultRouterFacade import com.correx.core.talkie.DefaultTalkieFacade
import com.correx.core.router.IdeaReader import com.correx.core.talkie.IdeaReader
import com.correx.core.router.DefaultRouterReducer import com.correx.core.talkie.DefaultTalkieReducer
import com.correx.core.router.DefaultRouterRepository import com.correx.core.talkie.DefaultTalkieRepository
import com.correx.core.router.RouterFacade import com.correx.core.talkie.TalkieFacade
import com.correx.core.router.RouterProjector import com.correx.core.talkie.TalkieProjector
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.router.model.WorkflowSummary import com.correx.core.talkie.model.WorkflowSummary
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.core.tools.contract.Tool import com.correx.core.tools.contract.Tool
@@ -306,22 +306,22 @@ object InfrastructureModule {
} }
} }
fun createRouterFacade( fun createTalkieFacade(
eventStore: EventStore, eventStore: EventStore,
inferenceRouter: InferenceRouter, inferenceRouter: InferenceRouter,
config: RouterConfig = RouterConfig(), config: TalkieConfig = TalkieConfig(),
tokenizer: Tokenizer? = null, tokenizer: Tokenizer? = null,
embedder: Embedder = createNoopEmbedder(), embedder: Embedder = createNoopEmbedder(),
l3MemoryStore: L3MemoryStore = createInMemoryL3MemoryStore(), l3MemoryStore: L3MemoryStore = createInMemoryL3MemoryStore(),
workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() }, workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() },
sessionProfileProvider: suspend (SessionId) -> String? = { null }, sessionProfileProvider: suspend (SessionId) -> String? = { null },
): RouterFacade { ): TalkieFacade {
val reducer = DefaultRouterReducer() val reducer = DefaultTalkieReducer()
val projector = RouterProjector(reducer) val projector = TalkieProjector(reducer)
val replayer = DefaultEventReplayer(eventStore, projector) val replayer = DefaultEventReplayer(eventStore, projector)
val repository = DefaultRouterRepository(replayer) val repository = DefaultTalkieRepository(replayer)
val ideaReader = IdeaReader(eventStore) val ideaReader = IdeaReader(eventStore)
val contextBuilder = DefaultRouterContextBuilder(config, tokenizer, ideaProvider = { ideaReader.activeIdeas() }) val contextBuilder = DefaultTalkieContextBuilder(config, tokenizer, ideaProvider = { ideaReader.activeIdeas() })
return DefaultRouterFacade(repository, contextBuilder, inferenceRouter, eventStore, config, null, embedder, l3MemoryStore, workflowSummaryProvider, sessionProfileProvider) return DefaultTalkieFacade(repository, contextBuilder, inferenceRouter, eventStore, config, null, embedder, l3MemoryStore, workflowSummaryProvider, sessionProfileProvider)
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@ include ':core:validation'
include ':core:approvals' include ':core:approvals'
include ':core:tools' include ':core:tools'
include ':core:toolintent' include ':core:toolintent'
include ':core:router' include ':core:talkie'
include ':core:sessions' include ':core:sessions'
include ':core:tasks' include ':core:tasks'
include ':core:config' include ':core:config'
+1 -1
View File
@@ -6,7 +6,7 @@ plugins {
dependencies { dependencies {
testImplementation(project(":core:events")) testImplementation(project(":core:events"))
testImplementation(project(":core:router")) testImplementation(project(":core:talkie"))
testImplementation(project(":core:sessions")) testImplementation(project(":core:sessions"))
testImplementation(project(":core:transitions")) testImplementation(project(":core:transitions"))
testImplementation(project(":core:validation")) testImplementation(project(":core:validation"))
@@ -8,12 +8,12 @@ import com.correx.core.events.events.StoredEvent
import com.correx.core.events.stores.EventStore import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.EventId import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.router.l3.L3Hit import com.correx.core.talkie.l3.L3Hit
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.router.l3.L3MetadataRehydrator import com.correx.core.talkie.l3.L3MetadataRehydrator
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
import com.correx.core.router.l3.RehydratableL3MemoryStore import com.correx.core.talkie.l3.RehydratableL3MemoryStore
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
@@ -5,15 +5,15 @@ import com.correx.core.context.model.TokenBudget
import com.correx.core.events.events.L3RetrievedHit import com.correx.core.events.events.L3RetrievedHit
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId import com.correx.core.events.types.StageId
import com.correx.core.router.DefaultRouterContextBuilder import com.correx.core.talkie.DefaultTalkieContextBuilder
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.router.model.WorkflowSummary import com.correx.core.talkie.model.WorkflowSummary
import com.correx.core.router.model.RouterL2Entry import com.correx.core.talkie.model.RouterL2Entry
import com.correx.core.router.model.RouterState import com.correx.core.talkie.model.TalkieState
import com.correx.core.router.model.RouterTurn import com.correx.core.talkie.model.TalkieTurn
import com.correx.core.router.model.StageOutcomeKind import com.correx.core.talkie.model.StageOutcomeKind
import com.correx.core.router.model.TurnRole import com.correx.core.talkie.model.TurnRole
import com.correx.core.router.model.WorkflowStatus import com.correx.core.talkie.model.WorkflowStatus
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
@@ -24,12 +24,12 @@ import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class RouterContextBuilderTest { class TalkieContextBuilderTest {
private val config = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 200)) private val config = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 200))
private val builder = DefaultRouterContextBuilder(config) private val builder = DefaultTalkieContextBuilder(config)
private fun buildPack(state: RouterState, budget: TokenBudget): ContextPack = runBlocking { private fun buildPack(state: TalkieState, budget: TokenBudget): ContextPack = runBlocking {
builder.build(state, budget) builder.build(state, budget)
} }
@@ -43,12 +43,12 @@ class RouterContextBuilderTest {
@Test @Test
fun `build fits within budget when all entries are small`() { fun `build fits within budget when all entries are small`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "hi", clock.now()), TalkieTurn(TurnRole.USER, "hi", clock.now()),
), ),
) )
val pack = buildPack(state, TokenBudget(limit = 10000)) val pack = buildPack(state, TokenBudget(limit = 10000))
@@ -60,14 +60,14 @@ class RouterContextBuilderTest {
fun `build drops entries when budget is exceeded`() { fun `build drops entries when budget is exceeded`() {
// Build a state where conversation + L2 entries together exceed the tight budget // Build a state where conversation + L2 entries together exceed the tight budget
val longContent = "x".repeat(800) // ~400 tokens each val longContent = "x".repeat(800) // ~400 tokens each
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, longContent, clock.now()), TalkieTurn(TurnRole.USER, longContent, clock.now()),
RouterTurn(TurnRole.ROUTER, longContent, clock.now()), TalkieTurn(TurnRole.ROUTER, longContent, clock.now()),
RouterTurn(TurnRole.USER, longContent, clock.now()), TalkieTurn(TurnRole.USER, longContent, clock.now()),
), ),
l2Memory = listOf( l2Memory = listOf(
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()), RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
@@ -82,10 +82,10 @@ class RouterContextBuilderTest {
fun `build drops entries oldest-first for L2 memory when budget forces partial drop`() { fun `build drops entries oldest-first for L2 memory when budget forces partial drop`() {
// Each L2 entry: "Stage sN (SUCCESS): " + 200-char summary ≈ 55 tokens each. // Each L2 entry: "Stage sN (SUCCESS): " + 200-char summary ≈ 55 tokens each.
// Use a config with conversationKeepLast=0 so only L0 + L2 compete for budget. // Use a config with conversationKeepLast=0 so only L0 + L2 compete for budget.
val configNoConv = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) val configNoConv = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
val builderNoConv = DefaultRouterContextBuilder(configNoConv) val builderNoConv = DefaultTalkieContextBuilder(configNoConv)
val summary = "x".repeat(200) // ~50 tokens; with prefix "Stage sN (SUCCESS): " ~55 tokens total val summary = "x".repeat(200) // ~50 tokens; with prefix "Stage sN (SUCCESS): " ~55 tokens total
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -115,18 +115,18 @@ class RouterContextBuilderTest {
@Test @Test
fun `build drops conversation turns oldest-first when conversationKeepLast exceeded`() { fun `build drops conversation turns oldest-first when conversationKeepLast exceeded`() {
val configWide = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) val configWide = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
val builderWide = DefaultRouterContextBuilder(configWide) val builderWide = DefaultTalkieContextBuilder(configWide)
val longContent = "x".repeat(400) val longContent = "x".repeat(400)
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z")), TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z")),
RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z")), TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z")),
RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-03T00:00:00Z")), TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-03T00:00:00Z")),
RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-04T00:00:00Z")), TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-04T00:00:00Z")),
), ),
) )
// Budget allows L0 + 2 long conversation turns; oldest 2 turns are dropped. // Budget allows L0 + 2 long conversation turns; oldest 2 turns are dropped.
@@ -144,7 +144,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `L0 system prompt is always present`() { fun `L0 system prompt is always present`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.IDLE, workflowStatus = WorkflowStatus.IDLE,
currentStageId = null, currentStageId = null,
@@ -156,7 +156,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `L0 workflow status is always present`() { fun `L0 workflow status is always present`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.FAILED, workflowStatus = WorkflowStatus.FAILED,
currentStageId = StageId("failed-stage"), currentStageId = StageId("failed-stage"),
@@ -171,7 +171,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `L0 entries survive zero budget`() { fun `L0 entries survive zero budget`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.COMPLETED, workflowStatus = WorkflowStatus.COMPLETED,
currentStageId = null, currentStageId = null,
@@ -185,12 +185,12 @@ class RouterContextBuilderTest {
@Test @Test
fun `L0 entries survive budget that cannot cover L1 or L2`() { fun `L0 entries survive budget that cannot cover L1 or L2`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "x".repeat(500), clock.now()), TalkieTurn(TurnRole.USER, "x".repeat(500), clock.now()),
), ),
l2Memory = listOf( l2Memory = listOf(
RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()), RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()),
@@ -215,10 +215,10 @@ class RouterContextBuilderTest {
// Each L2 entry: "Stage sN (SUCCESS): " + 100-char summary ≈ 30 tokens each. // Each L2 entry: "Stage sN (SUCCESS): " + 100-char summary ≈ 30 tokens each.
// L0 ~30 tokens; budget 90 leaves ~60. Two entries (~30 each = ~60) fit; oldest dropped. // L0 ~30 tokens; budget 90 leaves ~60. Two entries (~30 each = ~60) fit; oldest dropped.
val summary = "x".repeat(100) // ~25 tokens; with prefix ~30 tokens total val summary = "x".repeat(100) // ~25 tokens; with prefix ~30 tokens total
val configTight = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) val configTight = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
val builderTight = DefaultRouterContextBuilder(configTight) val builderTight = DefaultTalkieContextBuilder(configTight)
val baseTime = Instant.parse("2026-01-01T00:00:00Z") val baseTime = Instant.parse("2026-01-01T00:00:00Z")
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -251,9 +251,9 @@ class RouterContextBuilderTest {
@Test @Test
fun `L2 entries fit within budget are all retained`() { fun `L2 entries fit within budget are all retained`() {
val config = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) val config = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
val builder = DefaultRouterContextBuilder(config) val builder = DefaultTalkieContextBuilder(config)
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -274,7 +274,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `system prompt is classified as L0`() { fun `system prompt is classified as L0`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.IDLE, workflowStatus = WorkflowStatus.IDLE,
) )
@@ -286,7 +286,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `workflow status is classified as L0`() { fun `workflow status is classified as L0`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -298,13 +298,13 @@ class RouterContextBuilderTest {
@Test @Test
fun `conversation turns are classified as L1`() { fun `conversation turns are classified as L1`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "hello", clock.now()), TalkieTurn(TurnRole.USER, "hello", clock.now()),
RouterTurn(TurnRole.ROUTER, "hi", clock.now()), TalkieTurn(TurnRole.ROUTER, "hi", clock.now()),
), ),
) )
val pack = buildPack(state, TokenBudget(limit = 10000)) val pack = buildPack(state, TokenBudget(limit = 10000))
@@ -317,7 +317,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `l2 memory entries are classified as L2`() { fun `l2 memory entries are classified as L2`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -335,12 +335,12 @@ class RouterContextBuilderTest {
@Test @Test
fun `no entries other than L0 L1 L2 appear in pack`() { fun `no entries other than L0 L1 L2 appear in pack`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "hello", clock.now()), TalkieTurn(TurnRole.USER, "hello", clock.now()),
), ),
l2Memory = listOf( l2Memory = listOf(
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()), RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
@@ -359,18 +359,18 @@ class RouterContextBuilderTest {
@Test @Test
fun `conversationHistory respects conversationKeepLast from config`() { fun `conversationHistory respects conversationKeepLast from config`() {
val config = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) val config = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
val builder = DefaultRouterContextBuilder(config) val builder = DefaultTalkieContextBuilder(config)
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "one", Instant.parse("2026-01-01T00:00:00Z")), TalkieTurn(TurnRole.USER, "one", Instant.parse("2026-01-01T00:00:00Z")),
RouterTurn(TurnRole.ROUTER, "two", Instant.parse("2026-01-02T00:00:00Z")), TalkieTurn(TurnRole.ROUTER, "two", Instant.parse("2026-01-02T00:00:00Z")),
RouterTurn(TurnRole.USER, "three", Instant.parse("2026-01-03T00:00:00Z")), TalkieTurn(TurnRole.USER, "three", Instant.parse("2026-01-03T00:00:00Z")),
RouterTurn(TurnRole.ROUTER, "four", Instant.parse("2026-01-04T00:00:00Z")), TalkieTurn(TurnRole.ROUTER, "four", Instant.parse("2026-01-04T00:00:00Z")),
RouterTurn(TurnRole.USER, "five", Instant.parse("2026-01-05T00:00:00Z")), TalkieTurn(TurnRole.USER, "five", Instant.parse("2026-01-05T00:00:00Z")),
), ),
) )
val pack = runBlocking { builder.build(state, TokenBudget(limit = 10000)) } val pack = runBlocking { builder.build(state, TokenBudget(limit = 10000)) }
@@ -381,17 +381,17 @@ class RouterContextBuilderTest {
@Test @Test
fun `conversationHistory takeLast preserves ordering`() { fun `conversationHistory takeLast preserves ordering`() {
val config = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000)) val config = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
val builder = DefaultRouterContextBuilder(config) val builder = DefaultTalkieContextBuilder(config)
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "first", Instant.parse("2026-01-01T00:00:00Z")), TalkieTurn(TurnRole.USER, "first", Instant.parse("2026-01-01T00:00:00Z")),
RouterTurn(TurnRole.ROUTER, "second", Instant.parse("2026-01-02T00:00:00Z")), TalkieTurn(TurnRole.ROUTER, "second", Instant.parse("2026-01-02T00:00:00Z")),
RouterTurn(TurnRole.USER, "third", Instant.parse("2026-01-03T00:00:00Z")), TalkieTurn(TurnRole.USER, "third", Instant.parse("2026-01-03T00:00:00Z")),
RouterTurn(TurnRole.ROUTER, "fourth", Instant.parse("2026-01-04T00:00:00Z")), TalkieTurn(TurnRole.ROUTER, "fourth", Instant.parse("2026-01-04T00:00:00Z")),
), ),
) )
val pack = buildPack(state, TokenBudget(limit = 10000)) val pack = buildPack(state, TokenBudget(limit = 10000))
@@ -409,7 +409,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `build with empty state produces L0 only`() { fun `build with empty state produces L0 only`() {
val state = RouterState() val state = TalkieState()
val pack = buildPack(state, TokenBudget(limit = 10000)) val pack = buildPack(state, TokenBudget(limit = 10000))
val l0Entries = pack.layers[ContextLayer.L0] val l0Entries = pack.layers[ContextLayer.L0]
assertNotNull(l0Entries) assertNotNull(l0Entries)
@@ -420,7 +420,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `pack contains correct metadata on empty state`() { fun `pack contains correct metadata on empty state`() {
val state = RouterState() val state = TalkieState()
val pack = buildPack(state, TokenBudget(limit = 5000)) val pack = buildPack(state, TokenBudget(limit = 5000))
assertEquals(5000, pack.budgetLimit) assertEquals(5000, pack.budgetLimit)
assertEquals(0, pack.compressionMetadata.entriesDropped) assertEquals(0, pack.compressionMetadata.entriesDropped)
@@ -430,7 +430,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `pack has correct context pack id and session info`() { fun `pack has correct context pack id and session info`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -447,7 +447,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `system prompt has expected content`() { fun `system prompt has expected content`() {
val state = RouterState() val state = TalkieState()
val pack = buildPack(state, TokenBudget(limit = 10000)) val pack = buildPack(state, TokenBudget(limit = 10000))
val systemEntry = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" } val systemEntry = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" }
assertNotNull(systemEntry) assertNotNull(systemEntry)
@@ -461,7 +461,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `workflow status entry contains status and stage`() { fun `workflow status entry contains status and stage`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.COMPLETED, workflowStatus = WorkflowStatus.COMPLETED,
currentStageId = null, currentStageId = null,
@@ -475,12 +475,12 @@ class RouterContextBuilderTest {
@Test @Test
fun `conversation turn entry is formatted with role prefix`() { fun `conversation turn entry is formatted with role prefix`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "user message", clock.now()), TalkieTurn(TurnRole.USER, "user message", clock.now()),
), ),
) )
val pack = buildPack(state, TokenBudget(limit = 10000)) val pack = buildPack(state, TokenBudget(limit = 10000))
@@ -492,7 +492,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `l2 entry is formatted with stage outcome and summary`() { fun `l2 entry is formatted with stage outcome and summary`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -516,12 +516,12 @@ class RouterContextBuilderTest {
@Test @Test
fun `budgetUsed equals sum of tokenEstimate of all retained entries`() { fun `budgetUsed equals sum of tokenEstimate of all retained entries`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "hello world", clock.now()), TalkieTurn(TurnRole.USER, "hello world", clock.now()),
), ),
l2Memory = listOf( l2Memory = listOf(
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()), RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
@@ -535,14 +535,14 @@ class RouterContextBuilderTest {
@Test @Test
fun `compressionMetadata entriesDropped reflects dropped count`() { fun `compressionMetadata entriesDropped reflects dropped count`() {
val config = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10)) val config = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10))
val builder = DefaultRouterContextBuilder(config) val builder = DefaultTalkieContextBuilder(config)
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "x".repeat(200), clock.now()), TalkieTurn(TurnRole.USER, "x".repeat(200), clock.now()),
), ),
l2Memory = listOf( l2Memory = listOf(
RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()), RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()),
@@ -561,17 +561,17 @@ class RouterContextBuilderTest {
@Test @Test
fun `build with full state including all layers`() { fun `build with full state including all layers`() {
val config = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000)) val config = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
val builder = DefaultRouterContextBuilder(config) val builder = DefaultTalkieContextBuilder(config)
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "first turn", Instant.parse("2026-01-01T00:00:00Z")), TalkieTurn(TurnRole.USER, "first turn", Instant.parse("2026-01-01T00:00:00Z")),
RouterTurn(TurnRole.ROUTER, "first reply", Instant.parse("2026-01-02T00:00:00Z")), TalkieTurn(TurnRole.ROUTER, "first reply", Instant.parse("2026-01-02T00:00:00Z")),
RouterTurn(TurnRole.USER, "second turn", Instant.parse("2026-01-03T00:00:00Z")), TalkieTurn(TurnRole.USER, "second turn", Instant.parse("2026-01-03T00:00:00Z")),
RouterTurn(TurnRole.ROUTER, "second reply", Instant.parse("2026-01-04T00:00:00Z")), TalkieTurn(TurnRole.ROUTER, "second reply", Instant.parse("2026-01-04T00:00:00Z")),
), ),
l2Memory = listOf( l2Memory = listOf(
RouterL2Entry( RouterL2Entry(
@@ -614,7 +614,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `build with null sessionId produces unknown pack id`() { fun `build with null sessionId produces unknown pack id`() {
val state = RouterState( val state = TalkieState(
workflowStatus = WorkflowStatus.IDLE, workflowStatus = WorkflowStatus.IDLE,
) )
val pack = buildPack(state, TokenBudget(limit = 1000)) val pack = buildPack(state, TokenBudget(limit = 1000))
@@ -627,7 +627,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `L3 hits injected into pack ordered by score descending`() { fun `L3 hits injected into pack ordered by score descending`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -667,7 +667,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `L3 entries carry recalled memory label in content`() { fun `L3 entries carry recalled memory label in content`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
lastRetrievedMemory = listOf( lastRetrievedMemory = listOf(
@@ -694,7 +694,7 @@ class RouterContextBuilderTest {
fun `L3 hits dropped when budget exhausted and counted in entriesDropped`() { fun `L3 hits dropped when budget exhausted and counted in entriesDropped`() {
// Budget tight enough for L0 but not L3 entries with long text // Budget tight enough for L0 but not L3 entries with long text
val longText = "x".repeat(800) // ~200 tokens val longText = "x".repeat(800) // ~200 tokens
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
lastRetrievedMemory = listOf( lastRetrievedMemory = listOf(
@@ -721,14 +721,14 @@ class RouterContextBuilderTest {
@Test @Test
fun `system prompt and current user turn survive tight budget`() { fun `system prompt and current user turn survive tight budget`() {
val longContent = "x".repeat(800) // ~200 tokens each val longContent = "x".repeat(800) // ~200 tokens each
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z"), turnId = "old-turn"), TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z"), turnId = "old-turn"),
RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z"), turnId = "mid-turn"), TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z"), turnId = "mid-turn"),
RouterTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "current-turn"), TalkieTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "current-turn"),
), ),
) )
// Budget 60: L0 frame ~30 tokens -> remaining ~30; current user turn ~4 tokens -> protected. // Budget 60: L0 frame ~30 tokens -> remaining ~30; current user turn ~4 tokens -> protected.
@@ -748,17 +748,17 @@ class RouterContextBuilderTest {
@Test @Test
fun `oldest conversation turn is dropped before newest when budget is tight`() { fun `oldest conversation turn is dropped before newest when budget is tight`() {
val config3 = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000)) val config3 = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
val builder3 = DefaultRouterContextBuilder(config3) val builder3 = DefaultTalkieContextBuilder(config3)
val longContent = "x".repeat(800) // ~200 tokens each val longContent = "x".repeat(800) // ~200 tokens each
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "oldest turn $longContent", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"), TalkieTurn(TurnRole.USER, "oldest turn $longContent", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"),
RouterTurn(TurnRole.ROUTER, "middle turn $longContent", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"), TalkieTurn(TurnRole.ROUTER, "middle turn $longContent", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"),
RouterTurn(TurnRole.USER, "newest turn", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"), TalkieTurn(TurnRole.USER, "newest turn", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
), ),
) )
// Budget 100: L0 frame ~30 tokens -> remaining ~70; newest turn ~3 tokens -> protected. // Budget 100: L0 frame ~30 tokens -> remaining ~70; newest turn ~3 tokens -> protected.
@@ -776,12 +776,12 @@ class RouterContextBuilderTest {
@Test @Test
fun `L3 and L2 dropped before recent conversation when budget is tight`() { fun `L3 and L2 dropped before recent conversation when budget is tight`() {
val longContent = "x".repeat(200) // ~50 tokens each val longContent = "x".repeat(200) // ~50 tokens each
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "current user message", clock.now(), turnId = "curr"), TalkieTurn(TurnRole.USER, "current user message", clock.now(), turnId = "curr"),
), ),
lastRetrievedMemory = listOf( lastRetrievedMemory = listOf(
L3RetrievedHit( L3RetrievedHit(
@@ -816,7 +816,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `available workflows entry is injected into L0 when non-empty`() { fun `available workflows entry is injected into L0 when non-empty`() {
val workflows = listOf(WorkflowSummary("healthcheck", "Runs the health check pipeline", listOf("collect", "report"))) val workflows = listOf(WorkflowSummary("healthcheck", "Runs the health check pipeline", listOf("collect", "report")))
val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), availableWorkflows = workflows) } val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), availableWorkflows = workflows) }
val entry = (pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "availableWorkflows" } val entry = (pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "availableWorkflows" }
assertNotNull(entry) assertNotNull(entry)
assertTrue(entry!!.content.contains("## Available workflows")) assertTrue(entry!!.content.contains("## Available workflows"))
@@ -825,27 +825,27 @@ class RouterContextBuilderTest {
@Test @Test
fun `available workflows entry is omitted when list is empty`() { fun `available workflows entry is omitted when list is empty`() {
val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000)) } val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000)) }
assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "availableWorkflows" }) assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "availableWorkflows" })
} }
@Test @Test
fun `project profile text is injected into L0 when non-null`() { fun `project profile text is injected into L0 when non-null`() {
val text = "## Project profile\nA Kotlin service.\n### Conventions\n- Use runCatching" val text = "## Project profile\nA Kotlin service.\n### Conventions\n- Use runCatching"
val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), projectProfileText = text) } val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), projectProfileText = text) }
val entry = (pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" } val entry = (pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" }
assertEquals(text, entry!!.content) assertEquals(text, entry!!.content)
} }
@Test @Test
fun `project profile text is omitted when null or blank`() { fun `project profile text is omitted when null or blank`() {
val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), projectProfileText = " ") } val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), projectProfileText = " ") }
assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" }) assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" })
} }
@Test @Test
fun `system prompt contains triage directive not bare refusal`() { fun `system prompt contains triage directive not bare refusal`() {
val pack = buildPack(RouterState(), TokenBudget(limit = 10000)) val pack = buildPack(TalkieState(), TokenBudget(limit = 10000))
val content = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" }!!.content 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")) 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("Available workflows", ignoreCase = true))
@@ -855,13 +855,13 @@ class RouterContextBuilderTest {
@Test @Test
fun `truncatedLayers populated with affected layers when drops occur`() { fun `truncatedLayers populated with affected layers when drops occur`() {
val longText = "x".repeat(800) val longText = "x".repeat(800)
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, longText, clock.now(), turnId = "old"), TalkieTurn(TurnRole.USER, longText, clock.now(), turnId = "old"),
RouterTurn(TurnRole.USER, "current", clock.now(), turnId = "curr"), TalkieTurn(TurnRole.USER, "current", clock.now(), turnId = "curr"),
), ),
lastRetrievedMemory = listOf( lastRetrievedMemory = listOf(
L3RetrievedHit( L3RetrievedHit(
@@ -889,12 +889,12 @@ class RouterContextBuilderTest {
// The current user turn must still be included; budgetUsed must report the true total; // The current user turn must still be included; budgetUsed must report the true total;
// appliedStrategies must contain "BudgetExceeded". // appliedStrategies must contain "BudgetExceeded".
val longUserTurn = "x".repeat(200) // ~50 tokens val longUserTurn = "x".repeat(200) // ~50 tokens
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, longUserTurn, clock.now(), turnId = "current"), TalkieTurn(TurnRole.USER, longUserTurn, clock.now(), turnId = "current"),
), ),
) )
// Budget 1: system prompt + workflow status alone exceed this. // Budget 1: system prompt + workflow status alone exceed this.
@@ -928,16 +928,16 @@ class RouterContextBuilderTest {
@Test @Test
fun `conversationKeepLast zero still includes the current user turn`() { fun `conversationKeepLast zero still includes the current user turn`() {
val configZero = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000)) val configZero = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
val builderZero = DefaultRouterContextBuilder(configZero) val builderZero = DefaultTalkieContextBuilder(configZero)
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "old turn 1", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"), TalkieTurn(TurnRole.USER, "old turn 1", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"),
RouterTurn(TurnRole.ROUTER, "old reply", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"), TalkieTurn(TurnRole.ROUTER, "old reply", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"),
RouterTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"), TalkieTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
), ),
) )
val pack = runBlocking { builderZero.build(state, TokenBudget(limit = 10000)) } val pack = runBlocking { builderZero.build(state, TokenBudget(limit = 10000)) }
@@ -955,7 +955,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `appliedStrategies includes L3Recall when lastRetrievedMemory is non-empty`() { fun `appliedStrategies includes L3Recall when lastRetrievedMemory is non-empty`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
@@ -978,7 +978,7 @@ class RouterContextBuilderTest {
@Test @Test
fun `appliedStrategies does not include L3Recall when lastRetrievedMemory is empty`() { fun `appliedStrategies does not include L3Recall when lastRetrievedMemory is empty`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
) )
@@ -995,12 +995,12 @@ class RouterContextBuilderTest {
@Test @Test
fun `layers map is sorted by ContextLayer ordinal`() { fun `layers map is sorted by ContextLayer ordinal`() {
val state = RouterState( val state = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
conversationHistory = listOf( conversationHistory = listOf(
RouterTurn(TurnRole.USER, "hello", clock.now()), TalkieTurn(TurnRole.USER, "hello", clock.now()),
), ),
l2Memory = listOf( l2Memory = listOf(
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()), RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
@@ -29,26 +29,26 @@ import com.correx.core.inference.NoopEmbedder
import com.correx.core.inference.ProviderHealth import com.correx.core.inference.ProviderHealth
import com.correx.core.inference.ResponseFormat import com.correx.core.inference.ResponseFormat
import com.correx.core.inference.TokenUsage import com.correx.core.inference.TokenUsage
import com.correx.core.router.ChatMode import com.correx.core.talkie.ChatMode
import com.correx.core.router.DefaultRouterContextBuilder import com.correx.core.talkie.DefaultTalkieContextBuilder
import com.correx.core.router.DefaultRouterFacade import com.correx.core.talkie.DefaultTalkieFacade
import com.correx.core.router.DefaultRouterReducer import com.correx.core.talkie.DefaultTalkieReducer
import com.correx.core.router.IdeaReader import com.correx.core.talkie.IdeaReader
import com.correx.core.router.RouterContextBuilder import com.correx.core.talkie.TalkieContextBuilder
import com.correx.core.router.RouterFacade import com.correx.core.talkie.TalkieFacade
import com.correx.core.router.RouterProjector import com.correx.core.talkie.TalkieProjector
import com.correx.core.router.RouterRepository import com.correx.core.talkie.TalkieRepository
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.router.model.RouterResponse import com.correx.core.talkie.model.TalkieResponse
import com.correx.core.router.model.RouterState import com.correx.core.talkie.model.TalkieState
import com.correx.core.router.model.RouterTurn import com.correx.core.talkie.model.TalkieTurn
import com.correx.core.router.model.TurnRole import com.correx.core.talkie.model.TurnRole
import com.correx.core.router.model.WorkflowSummary import com.correx.core.talkie.model.WorkflowSummary
import com.correx.core.router.model.WorkflowStatus import com.correx.core.talkie.model.WorkflowStatus
import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.testing.fixtures.EventFixtures import com.correx.testing.fixtures.EventFixtures
import com.correx.testing.fixtures.inference.MockTokenizer import com.correx.testing.fixtures.inference.MockTokenizer
@@ -63,7 +63,7 @@ import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class RouterFacadeTest { class TalkieFacadeTest {
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// CHAT mode // CHAT mode
@@ -135,21 +135,21 @@ class RouterFacadeTest {
fun `STEERING mode appends SteeringNoteAddedEvent with current stageId`(): Unit = runBlocking { fun `STEERING mode appends SteeringNoteAddedEvent with current stageId`(): Unit = runBlocking {
val mockStore = mockEventStore() val mockStore = mockEventStore()
val stageId = StageId("stage-A") val stageId = StageId("stage-A")
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
RouterState( TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
) )
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter("steering response"), inferenceRouter = mockInferenceRouter("steering response"),
eventStore = mockStore, eventStore = mockStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -166,26 +166,26 @@ class RouterFacadeTest {
@Test @Test
fun `conversation history grows per call - user and router turns appended`(): Unit = runBlocking { fun `conversation history grows per call - user and router turns appended`(): Unit = runBlocking {
val capturedStates = mutableListOf<RouterState>() val capturedStates = mutableListOf<TalkieState>()
val eventStore = mockEventStore() val eventStore = mockEventStore()
val replayer = DefaultEventReplayer<RouterState>( val replayer = DefaultEventReplayer<TalkieState>(
store = eventStore, store = eventStore,
projection = RouterProjector(DefaultRouterReducer()), projection = TalkieProjector(DefaultTalkieReducer()),
) )
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId) replayer.rebuild(sessionId)
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack { override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
capturedStates.add(state) capturedStates.add(state)
return emptyContextPack() return emptyContextPack()
} }
}, },
inferenceRouter = mockInferenceRouter("router reply"), inferenceRouter = mockInferenceRouter("router reply"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -206,26 +206,26 @@ class RouterFacadeTest {
@Test @Test
fun `conversation history is session-scoped - different sessions do not share history`(): Unit = runBlocking { fun `conversation history is session-scoped - different sessions do not share history`(): Unit = runBlocking {
val capturedStates = mutableListOf<RouterState>() val capturedStates = mutableListOf<TalkieState>()
val eventStore = mockEventStore() val eventStore = mockEventStore()
val replayer = DefaultEventReplayer<RouterState>( val replayer = DefaultEventReplayer<TalkieState>(
store = eventStore, store = eventStore,
projection = RouterProjector(DefaultRouterReducer()), projection = TalkieProjector(DefaultTalkieReducer()),
) )
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId) replayer.rebuild(sessionId)
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack { override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
capturedStates.add(state) capturedStates.add(state)
return emptyContextPack() return emptyContextPack()
} }
}, },
inferenceRouter = mockInferenceRouter("response"), inferenceRouter = mockInferenceRouter("response"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -246,17 +246,17 @@ class RouterFacadeTest {
@Test @Test
fun `state is passed through to context builder`(): Unit = runBlocking { fun `state is passed through to context builder`(): Unit = runBlocking {
val capturedState = mutableListOf<RouterState>() val capturedState = mutableListOf<TalkieState>()
val mockContextBuilder = object : RouterContextBuilder { val mockContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack { override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
capturedState.add(state) capturedState.add(state)
return emptyContextPack() return emptyContextPack()
} }
} }
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
RouterState( TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = StageId("s1"), currentStageId = StageId("s1"),
@@ -265,7 +265,7 @@ class RouterFacadeTest {
routerContextBuilder = mockContextBuilder, routerContextBuilder = mockContextBuilder,
inferenceRouter = mockInferenceRouter("inference response"), inferenceRouter = mockInferenceRouter("inference response"),
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -278,20 +278,20 @@ class RouterFacadeTest {
@Test @Test
fun `budget is passed through to context builder`(): Unit = runBlocking { fun `budget is passed through to context builder`(): Unit = runBlocking {
val capturedBudget = mutableListOf<TokenBudget>() val capturedBudget = mutableListOf<TokenBudget>()
val mockContextBuilder = object : RouterContextBuilder { val mockContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack { override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
capturedBudget.add(budget) capturedBudget.add(budget)
return emptyContextPack() return emptyContextPack()
} }
} }
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = mockContextBuilder, routerContextBuilder = mockContextBuilder,
inferenceRouter = mockInferenceRouter("response"), inferenceRouter = mockInferenceRouter("response"),
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 4200)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 4200)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -313,20 +313,20 @@ class RouterFacadeTest {
} }
} }
val stateStageId = StageId("state-stage") val stateStageId = StageId("state-stage")
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState( override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stateStageId, currentStageId = stateStageId,
) )
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter, inferenceRouter = mockInferenceRouter,
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -346,20 +346,20 @@ class RouterFacadeTest {
return mockProvider("response") return mockProvider("response")
} }
} }
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState( override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.IDLE, workflowStatus = WorkflowStatus.IDLE,
currentStageId = null, currentStageId = null,
) )
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter, inferenceRouter = mockInferenceRouter,
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -378,16 +378,16 @@ class RouterFacadeTest {
return mockProviderWithCapture("response", capturedRequestIds) return mockProviderWithCapture("response", capturedRequestIds)
} }
} }
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter, inferenceRouter = mockInferenceRouter,
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -408,16 +408,16 @@ class RouterFacadeTest {
return mockProviderWithRequestCapture("response", capturedRequests) return mockProviderWithRequestCapture("response", capturedRequests)
} }
} }
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter, inferenceRouter = mockInferenceRouter,
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -431,12 +431,12 @@ class RouterFacadeTest {
@Test @Test
fun `context pack is passed to inference provider`(): Unit = runBlocking { fun `context pack is passed to inference provider`(): Unit = runBlocking {
val capturedContextPacks = mutableListOf<ContextPack>() val capturedContextPacks = mutableListOf<ContextPack>()
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack { override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
val pack = ContextPack( val pack = ContextPack(
id = ContextPackId("test-pack"), id = ContextPackId("test-pack"),
sessionId = state.sessionId ?: SessionId("unknown"), sessionId = state.sessionId ?: SessionId("unknown"),
@@ -478,7 +478,7 @@ class RouterFacadeTest {
} }
}, },
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -488,12 +488,12 @@ class RouterFacadeTest {
@Test @Test
fun `responseFormat defaults to Text`(): Unit = runBlocking { fun `responseFormat defaults to Text`(): Unit = runBlocking {
val capturedRequests = mutableListOf<InferenceRequest>() val capturedRequests = mutableListOf<InferenceRequest>()
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = object : InferenceRouter { inferenceRouter = object : InferenceRouter {
override suspend fun route( override suspend fun route(
@@ -504,7 +504,7 @@ class RouterFacadeTest {
} }
}, },
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -514,7 +514,7 @@ class RouterFacadeTest {
} }
@Test @Test
fun `onUserInput returns RouterResponse with content and mode flag`(): Unit = runBlocking { fun `onUserInput returns TalkieResponse with content and mode flag`(): Unit = runBlocking {
val mockStore = mockEventStore() val mockStore = mockEventStore()
val facade = facadeWithMocks(eventStore = mockStore, chatMode = ChatMode.STEERING) val facade = facadeWithMocks(eventStore = mockStore, chatMode = ChatMode.STEERING)
val response = facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!") val response = facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
@@ -576,19 +576,19 @@ class RouterFacadeTest {
override fun capabilities(): Set<CapabilityScore> = setOf(CapabilityScore(ModelCapability.General, 1.0)) override fun capabilities(): Set<CapabilityScore> = setOf(CapabilityScore(ModelCapability.General, 1.0))
} }
val eventStore = mockEventStore() val eventStore = mockEventStore()
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = object : InferenceRouter { inferenceRouter = object : InferenceRouter {
override suspend fun route(stageId: StageId, requiredCapabilities: Set<ModelCapability>): InferenceProvider = override suspend fun route(stageId: StageId, requiredCapabilities: Set<ModelCapability>): InferenceProvider =
metricsProvider metricsProvider
}, },
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -717,16 +717,16 @@ class RouterFacadeTest {
store: EventStore, store: EventStore,
reply: String, reply: String,
workflows: List<WorkflowSummary>, workflows: List<WorkflowSummary>,
): RouterFacade = DefaultRouterFacade( ): TalkieFacade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(sessionId = sessionId) override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(sessionId = sessionId)
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter(reply), inferenceRouter = mockInferenceRouter(reply),
eventStore = store, eventStore = store,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
workflowSummaryProvider = { workflows }, workflowSummaryProvider = { workflows },
@@ -739,28 +739,28 @@ class RouterFacadeTest {
chatMode: ChatMode = ChatMode.CHAT, chatMode: ChatMode = ChatMode.CHAT,
embedder: Embedder = NoopEmbedder(dimension = 8), embedder: Embedder = NoopEmbedder(dimension = 8),
l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(),
): RouterFacade = DefaultRouterFacade( ): TalkieFacade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
RouterState( TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = StageId("s1"), currentStageId = StageId("s1"),
) )
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter("inference response"), inferenceRouter = mockInferenceRouter("inference response"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = embedder, embedder = embedder,
l3MemoryStore = l3MemoryStore, l3MemoryStore = l3MemoryStore,
).let { impl -> ).let { impl ->
object : RouterFacade { object : TalkieFacade {
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse = override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse =
impl.onUserInput(sessionId = sessionId, input = input, mode = chatMode) impl.onUserInput(sessionId = sessionId, input = input, mode = chatMode)
override suspend fun narrate(sessionId: SessionId, trigger: com.correx.core.router.model.NarrationTrigger) = override suspend fun narrate(sessionId: SessionId, trigger: com.correx.core.talkie.model.NarrationTrigger) =
impl.narrate(sessionId = sessionId, trigger = trigger) impl.narrate(sessionId = sessionId, trigger = trigger)
} }
} }
@@ -872,21 +872,21 @@ class RouterFacadeTest {
) )
val eventStore = mockEventStore() val eventStore = mockEventStore()
val replayer = DefaultEventReplayer<RouterState>( val replayer = DefaultEventReplayer<TalkieState>(
store = eventStore, store = eventStore,
projection = RouterProjector(DefaultRouterReducer()), projection = TalkieProjector(DefaultTalkieReducer()),
) )
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId) replayer.rebuild(sessionId)
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter("router reply"), inferenceRouter = mockInferenceRouter("router reply"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
embedder = stubEmbedder, embedder = stubEmbedder,
l3MemoryStore = l3Store, l3MemoryStore = l3Store,
) )
@@ -928,9 +928,9 @@ class RouterFacadeTest {
) )
val eventStore = mockEventStore() val eventStore = mockEventStore()
val replayer = DefaultEventReplayer<RouterState>( val replayer = DefaultEventReplayer<TalkieState>(
store = eventStore, store = eventStore,
projection = RouterProjector(DefaultRouterReducer()), projection = TalkieProjector(DefaultTalkieReducer()),
) )
// Bootstrap session with a ChatTurnEvent whose turnId matches the L3 entry // Bootstrap session with a ChatTurnEvent whose turnId matches the L3 entry
@@ -954,17 +954,17 @@ class RouterFacadeTest {
) )
) )
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId) replayer.rebuild(sessionId)
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter("router reply"), inferenceRouter = mockInferenceRouter("router reply"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
embedder = stubEmbedder, embedder = stubEmbedder,
l3MemoryStore = l3Store, l3MemoryStore = l3Store,
) )
@@ -986,16 +986,16 @@ class RouterFacadeTest {
throw RuntimeException("embedding service unavailable") throw RuntimeException("embedding service unavailable")
} }
val eventStore = mockEventStore() val eventStore = mockEventStore()
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter("response"), inferenceRouter = mockInferenceRouter("response"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = throwingEmbedder, embedder = throwingEmbedder,
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -1014,9 +1014,9 @@ class RouterFacadeTest {
} }
@Test @Test
fun `replay of L3MemoryRetrievedEvent populates lastRetrievedMemory in RouterState`(): Unit = runBlocking { fun `replay of L3MemoryRetrievedEvent populates lastRetrievedMemory in TalkieState`(): Unit = runBlocking {
val reducer = DefaultRouterReducer() val reducer = DefaultTalkieReducer()
val projector = RouterProjector(reducer) val projector = TalkieProjector(reducer)
val sessionId = SessionId("replay-session") val sessionId = SessionId("replay-session")
val hits = listOf( val hits = listOf(
L3RetrievedHit( L3RetrievedHit(
@@ -1048,28 +1048,28 @@ class RouterFacadeTest {
@Test @Test
fun `ContextTruncatedEvent emitted when context builder drops entries`(): Unit = runBlocking { fun `ContextTruncatedEvent emitted when context builder drops entries`(): Unit = runBlocking {
// Use the real DefaultRouterContextBuilder with a tight budget and large conversation // Use the real DefaultTalkieContextBuilder with a tight budget and large conversation
val longContent = "x".repeat(800) // ~200 tokens each val longContent = "x".repeat(800) // ~200 tokens each
val sessionId = SessionId("truncation-session") val sessionId = SessionId("truncation-session")
val eventStore = mockEventStore() val eventStore = mockEventStore()
val replayer = DefaultEventReplayer<RouterState>( val replayer = DefaultEventReplayer<TalkieState>(
store = eventStore, store = eventStore,
projection = RouterProjector(DefaultRouterReducer()), projection = TalkieProjector(DefaultTalkieReducer()),
) )
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId) replayer.rebuild(sessionId)
}, },
routerContextBuilder = DefaultRouterContextBuilder( routerContextBuilder = DefaultTalkieContextBuilder(
config = RouterConfig( config = TalkieConfig(
conversationKeepLast = 4, conversationKeepLast = 4,
tokenBudget = TokenBudget(limit = 80), tokenBudget = TokenBudget(limit = 80),
), ),
), ),
inferenceRouter = mockInferenceRouter("response"), inferenceRouter = mockInferenceRouter("response"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig( config = TalkieConfig(
conversationKeepLast = 4, conversationKeepLast = 4,
tokenBudget = TokenBudget(limit = 80), tokenBudget = TokenBudget(limit = 80),
), ),
@@ -1112,16 +1112,16 @@ class RouterFacadeTest {
@Test @Test
fun `no ContextTruncatedEvent when budget is generous`(): Unit = runBlocking { fun `no ContextTruncatedEvent when budget is generous`(): Unit = runBlocking {
val eventStore = mockEventStore() val eventStore = mockEventStore()
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = DefaultRouterContextBuilder( routerContextBuilder = DefaultTalkieContextBuilder(
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
), ),
inferenceRouter = mockInferenceRouter("response"), inferenceRouter = mockInferenceRouter("response"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -1136,9 +1136,9 @@ class RouterFacadeTest {
val longContent = "x".repeat(800) val longContent = "x".repeat(800)
val sessionId = SessionId("turnid-session") val sessionId = SessionId("turnid-session")
val eventStore = mockEventStore() val eventStore = mockEventStore()
val replayer = DefaultEventReplayer<RouterState>( val replayer = DefaultEventReplayer<TalkieState>(
store = eventStore, store = eventStore,
projection = RouterProjector(DefaultRouterReducer()), projection = TalkieProjector(DefaultTalkieReducer()),
) )
// Pre-seed with a large turn so the budget overflows on the next call // Pre-seed with a large turn so the budget overflows on the next call
@@ -1162,17 +1162,17 @@ class RouterFacadeTest {
) )
) )
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId) replayer.rebuild(sessionId)
}, },
routerContextBuilder = DefaultRouterContextBuilder( routerContextBuilder = DefaultTalkieContextBuilder(
config = RouterConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)), config = TalkieConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
), ),
inferenceRouter = mockInferenceRouter("response"), inferenceRouter = mockInferenceRouter("response"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)), config = TalkieConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -1192,28 +1192,28 @@ class RouterFacadeTest {
// Each turn must emit L3MemoryRetrievedEvent (with empty hits), so that // Each turn must emit L3MemoryRetrievedEvent (with empty hits), so that
// lastRetrievedMemory is always current-turn-scoped and never stale. // lastRetrievedMemory is always current-turn-scoped and never stale.
val eventStore = mockEventStore() val eventStore = mockEventStore()
val replayer = DefaultEventReplayer<RouterState>( val replayer = DefaultEventReplayer<TalkieState>(
store = eventStore, store = eventStore,
projection = RouterProjector(DefaultRouterReducer()), projection = TalkieProjector(DefaultTalkieReducer()),
) )
val capturedStates = mutableListOf<RouterState>() val capturedStates = mutableListOf<TalkieState>()
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId) replayer.rebuild(sessionId)
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
private val real = DefaultRouterContextBuilder( private val real = DefaultTalkieContextBuilder(
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
) )
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack { override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
capturedStates.add(state) capturedStates.add(state)
return real.build(state, budget) return real.build(state, budget)
} }
}, },
inferenceRouter = mockInferenceRouter("reply"), inferenceRouter = mockInferenceRouter("reply"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5), config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -1265,25 +1265,25 @@ class RouterFacadeTest {
val capturedPacks = mutableListOf<ContextPack>() val capturedPacks = mutableListOf<ContextPack>()
val eventStore = mockEventStore() val eventStore = mockEventStore()
val replayer = DefaultEventReplayer<RouterState>( val replayer = DefaultEventReplayer<TalkieState>(
store = eventStore, store = eventStore,
projection = RouterProjector(DefaultRouterReducer()), projection = TalkieProjector(DefaultTalkieReducer()),
) )
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
replayer.rebuild(sessionId) replayer.rebuild(sessionId)
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
private val real = DefaultRouterContextBuilder( private val real = DefaultTalkieContextBuilder(
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
) )
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack =
real.build(state, budget).also { capturedPacks.add(it) } real.build(state, budget).also { capturedPacks.add(it) }
}, },
inferenceRouter = mockInferenceRouter("router reply"), inferenceRouter = mockInferenceRouter("router reply"),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5), config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
embedder = stubEmbedder, embedder = stubEmbedder,
l3MemoryStore = l3Store, l3MemoryStore = l3Store,
) )
@@ -1321,16 +1321,16 @@ class RouterFacadeTest {
return mockProvider("narration text") return mockProvider("narration text")
} }
} }
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter, inferenceRouter = mockInferenceRouter,
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig( config = TalkieConfig(
tokenBudget = TokenBudget(limit = 5000), tokenBudget = TokenBudget(limit = 5000),
narrationModelId = "llama-cpp:phi-3-mini", narrationModelId = "llama-cpp:phi-3-mini",
), ),
@@ -1339,7 +1339,7 @@ class RouterFacadeTest {
) )
facade.narrate( facade.narrate(
sessionId = SessionId("test-session"), sessionId = SessionId("test-session"),
trigger = com.correx.core.router.model.NarrationTrigger( trigger = com.correx.core.talkie.model.NarrationTrigger(
kind = "test", kind = "test",
instruction = "describe what happened", instruction = "describe what happened",
), ),
@@ -1366,16 +1366,16 @@ class RouterFacadeTest {
return mockProvider("response") return mockProvider("response")
} }
} }
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState() override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter, inferenceRouter = mockInferenceRouter,
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig( config = TalkieConfig(
tokenBudget = TokenBudget(limit = 5000), tokenBudget = TokenBudget(limit = 5000),
narrationModelId = "llama-cpp:phi-3-mini", narrationModelId = "llama-cpp:phi-3-mini",
), ),
@@ -1393,9 +1393,9 @@ class RouterFacadeTest {
@Test @Test
fun `workflowSummaryProvider is called and forwarded to context builder on each turn`(): Unit = runBlocking { fun `workflowSummaryProvider is called and forwarded to context builder on each turn`(): Unit = runBlocking {
val captured = mutableListOf<List<WorkflowSummary>>() val captured = mutableListOf<List<WorkflowSummary>>()
val capturingBuilder = object : RouterContextBuilder { val capturingBuilder = object : TalkieContextBuilder {
override suspend fun build( override suspend fun build(
state: RouterState, state: TalkieState,
budget: TokenBudget, budget: TokenBudget,
availableWorkflows: List<WorkflowSummary>, availableWorkflows: List<WorkflowSummary>,
projectProfileText: String?, projectProfileText: String?,
@@ -1413,9 +1413,9 @@ class RouterFacadeTest {
@Test @Test
fun `sessionProfileProvider is called and forwarded to context builder`(): Unit = runBlocking { fun `sessionProfileProvider is called and forwarded to context builder`(): Unit = runBlocking {
val capturedProfiles = mutableListOf<String?>() val capturedProfiles = mutableListOf<String?>()
val capturingBuilder = object : RouterContextBuilder { val capturingBuilder = object : TalkieContextBuilder {
override suspend fun build( override suspend fun build(
state: RouterState, state: TalkieState,
budget: TokenBudget, budget: TokenBudget,
availableWorkflows: List<WorkflowSummary>, availableWorkflows: List<WorkflowSummary>,
projectProfileText: String?, projectProfileText: String?,
@@ -1433,9 +1433,9 @@ class RouterFacadeTest {
} }
private fun facadeWith( private fun facadeWith(
contextBuilder: RouterContextBuilder = object : RouterContextBuilder { contextBuilder: TalkieContextBuilder = object : TalkieContextBuilder {
override suspend fun build( override suspend fun build(
state: RouterState, state: TalkieState,
budget: TokenBudget, budget: TokenBudget,
availableWorkflows: List<WorkflowSummary>, availableWorkflows: List<WorkflowSummary>,
projectProfileText: String?, projectProfileText: String?,
@@ -1443,14 +1443,14 @@ class RouterFacadeTest {
}, },
workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() }, workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() },
sessionProfileProvider: suspend (SessionId) -> String? = { null }, sessionProfileProvider: suspend (SessionId) -> String? = { null },
): RouterFacade = DefaultRouterFacade( ): TalkieFacade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(sessionId = sessionId) override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(sessionId = sessionId)
}, },
routerContextBuilder = contextBuilder, routerContextBuilder = contextBuilder,
inferenceRouter = mockInferenceRouter("inference response"), inferenceRouter = mockInferenceRouter("inference response"),
eventStore = mockEventStore(), eventStore = mockEventStore(),
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
workflowSummaryProvider = workflowSummaryProvider, workflowSummaryProvider = workflowSummaryProvider,
@@ -4,7 +4,7 @@ import com.correx.core.context.model.TokenBudget
import com.correx.core.events.events.ChatTurnEvent import com.correx.core.events.events.ChatTurnEvent
import com.correx.core.events.events.EventMetadata import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.RouterNarrationEvent import com.correx.core.events.events.TalkieNarrationEvent
import com.correx.core.events.events.StoredEvent import com.correx.core.events.events.StoredEvent
import com.correx.core.events.stores.EventStore import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.ContextPackId import com.correx.core.events.types.ContextPackId
@@ -25,19 +25,19 @@ import com.correx.core.inference.NoopEmbedder
import com.correx.core.inference.ProviderHealth import com.correx.core.inference.ProviderHealth
import com.correx.core.inference.ResponseFormat import com.correx.core.inference.ResponseFormat
import com.correx.core.inference.TokenUsage import com.correx.core.inference.TokenUsage
import com.correx.core.router.DefaultRouterContextBuilder import com.correx.core.talkie.DefaultTalkieContextBuilder
import com.correx.core.router.DefaultRouterFacade import com.correx.core.talkie.DefaultTalkieFacade
import com.correx.core.router.RouterContextBuilder import com.correx.core.talkie.TalkieContextBuilder
import com.correx.core.router.RouterRepository import com.correx.core.talkie.TalkieRepository
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.router.l3.L3MemoryEntry import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.router.l3.L3MemoryStore import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query import com.correx.core.talkie.l3.L3Query
import com.correx.core.router.model.NarrationTrigger import com.correx.core.talkie.model.NarrationTrigger
import com.correx.core.router.model.RouterConfig import com.correx.core.talkie.model.TalkieConfig
import com.correx.core.router.model.RouterState import com.correx.core.talkie.model.TalkieState
import com.correx.core.router.model.WorkflowStatus import com.correx.core.talkie.model.WorkflowStatus
import com.correx.core.router.model.WorkflowSummary import com.correx.core.talkie.model.WorkflowSummary
import com.correx.testing.fixtures.inference.MockTokenizer import com.correx.testing.fixtures.inference.MockTokenizer
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -47,14 +47,14 @@ import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class RouterNarrationTest { class TalkieNarrationTest {
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// narrate() — event emission // narrate() — event emission
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
@Test @Test
fun `narrate emits RouterNarrationEvent with inference content and metrics`(): Unit = runBlocking { fun `narrate emits TalkieNarrationEvent with inference content and metrics`(): Unit = runBlocking {
val knownLatencyMs = 55L val knownLatencyMs = 55L
val knownUsage = TokenUsage(promptTokens = 8, completionTokens = 3) val knownUsage = TokenUsage(promptTokens = 8, completionTokens = 3)
val eventStore = MapBackedEventStore() val eventStore = MapBackedEventStore()
@@ -68,7 +68,7 @@ class RouterNarrationTest {
facade.narrate(SessionId("sess-1"), trigger) facade.narrate(SessionId("sess-1"), trigger)
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>() val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
assertEquals(1, narrationEvents.size) assertEquals(1, narrationEvents.size)
val event = narrationEvents[0] val event = narrationEvents[0]
assertEquals("narration line", event.content) assertEquals("narration line", event.content)
@@ -112,8 +112,8 @@ class RouterNarrationTest {
facade.narrate(SessionId("sess-4"), trigger) facade.narrate(SessionId("sess-4"), trigger)
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>() val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
assertTrue(narrationEvents.isEmpty(), "blank inference must not emit RouterNarrationEvent") assertTrue(narrationEvents.isEmpty(), "blank inference must not emit TalkieNarrationEvent")
} }
@Test @Test
@@ -124,30 +124,30 @@ class RouterNarrationTest {
facade.narrate(SessionId("sess-5"), trigger) facade.narrate(SessionId("sess-5"), trigger)
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>() val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
assertTrue(narrationEvents.isEmpty(), "empty inference must not emit RouterNarrationEvent") assertTrue(narrationEvents.isEmpty(), "empty inference must not emit TalkieNarrationEvent")
} }
@Test @Test
fun `narrate RouterNarrationEvent carries sessionId and stageId from state`(): Unit = runBlocking { fun `narrate TalkieNarrationEvent carries sessionId and stageId from state`(): Unit = runBlocking {
val stageId = StageId("my-stage") val stageId = StageId("my-stage")
val eventStore = MapBackedEventStore() val eventStore = MapBackedEventStore()
val facade = DefaultRouterFacade( val facade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
RouterState( TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = stageId, currentStageId = stageId,
) )
}, },
routerContextBuilder = object : RouterContextBuilder { routerContextBuilder = object : TalkieContextBuilder {
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack() override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
override suspend fun buildNarrationContext(state: RouterState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = emptyContextPack() override suspend fun buildNarrationContext(state: TalkieState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = emptyContextPack()
}, },
inferenceRouter = mockInferenceRouter("stage narration", latencyMs = 10L, tokensUsed = TokenUsage(1, 1)), inferenceRouter = mockInferenceRouter("stage narration", latencyMs = 10L, tokensUsed = TokenUsage(1, 1)),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore = InMemoryL3MemoryStore(),
) )
@@ -155,7 +155,7 @@ class RouterNarrationTest {
facade.narrate(SessionId("session-x"), trigger) facade.narrate(SessionId("session-x"), trigger)
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>() val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
assertEquals(1, narrationEvents.size) assertEquals(1, narrationEvents.size)
assertEquals(SessionId("session-x"), narrationEvents[0].sessionId) assertEquals(SessionId("session-x"), narrationEvents[0].sessionId)
assertEquals("my-stage", narrationEvents[0].stageId) assertEquals("my-stage", narrationEvents[0].stageId)
@@ -167,8 +167,8 @@ class RouterNarrationTest {
@Test @Test
fun `buildNarrationContext includes system entry in L0`(): Unit = runBlocking { fun `buildNarrationContext includes system entry in L0`(): Unit = runBlocking {
val builder = DefaultRouterContextBuilder(RouterConfig()) val builder = DefaultTalkieContextBuilder(TalkieConfig())
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
val trigger = NarrationTrigger(kind = "test", instruction = "Do something.") val trigger = NarrationTrigger(kind = "test", instruction = "Do something.")
val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000)) val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000))
@@ -179,8 +179,8 @@ class RouterNarrationTest {
@Test @Test
fun `buildNarrationContext includes workflow status entry`(): Unit = runBlocking { fun `buildNarrationContext includes workflow status entry`(): Unit = runBlocking {
val builder = DefaultRouterContextBuilder(RouterConfig()) val builder = DefaultTalkieContextBuilder(TalkieConfig())
val state = RouterState( val state = TalkieState(
sessionId = SessionId("s"), sessionId = SessionId("s"),
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = StageId("my-stage"), currentStageId = StageId("my-stage"),
@@ -196,8 +196,8 @@ class RouterNarrationTest {
@Test @Test
fun `buildNarrationContext includes trigger instruction as final entry`(): Unit = runBlocking { fun `buildNarrationContext includes trigger instruction as final entry`(): Unit = runBlocking {
val builder = DefaultRouterContextBuilder(RouterConfig()) val builder = DefaultTalkieContextBuilder(TalkieConfig())
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
val instruction = "Narrate the completed action now." val instruction = "Narrate the completed action now."
val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction) val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction)
val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000)) val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000))
@@ -214,8 +214,8 @@ class RouterNarrationTest {
// Regression: the trigger instruction must render as a user turn. When it was filed // Regression: the trigger instruction must render as a user turn. When it was filed
// under L0 it folded into the system message, leaving zero user messages and the chat // under L0 it folded into the system message, leaving zero user messages and the chat
// template rejected the request ("No user query found in messages"). // template rejected the request ("No user query found in messages").
val builder = DefaultRouterContextBuilder(RouterConfig()) val builder = DefaultTalkieContextBuilder(TalkieConfig())
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING) val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING)
val instruction = "Tell the user the stage just completed." val instruction = "Tell the user the stage just completed."
val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction) val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction)
val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000)) val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000))
@@ -231,8 +231,8 @@ class RouterNarrationTest {
@Test @Test
fun `buildNarrationContext stays within budget`(): Unit = runBlocking { fun `buildNarrationContext stays within budget`(): Unit = runBlocking {
val builder = DefaultRouterContextBuilder(RouterConfig()) val builder = DefaultTalkieContextBuilder(TalkieConfig())
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE) val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
val trigger = NarrationTrigger(kind = "test", instruction = "Short.") val trigger = NarrationTrigger(kind = "test", instruction = "Short.")
val budget = TokenBudget(limit = 5000) val budget = TokenBudget(limit = 5000)
val pack = builder.buildNarrationContext(state, trigger, budget) val pack = builder.buildNarrationContext(state, trigger, budget)
@@ -242,13 +242,13 @@ class RouterNarrationTest {
@Test @Test
fun `buildNarrationContext does not include conversationHistory turns`(): Unit = runBlocking { fun `buildNarrationContext does not include conversationHistory turns`(): Unit = runBlocking {
val builder = DefaultRouterContextBuilder(RouterConfig()) val builder = DefaultTalkieContextBuilder(TalkieConfig())
val state = RouterState( val state = TalkieState(
sessionId = SessionId("s"), sessionId = SessionId("s"),
workflowStatus = WorkflowStatus.IDLE, workflowStatus = WorkflowStatus.IDLE,
conversationHistory = listOf( conversationHistory = listOf(
com.correx.core.router.model.RouterTurn( com.correx.core.talkie.model.TalkieTurn(
role = com.correx.core.router.model.TurnRole.USER, role = com.correx.core.talkie.model.TurnRole.USER,
content = "secret conversation turn", content = "secret conversation turn",
timestamp = kotlinx.datetime.Clock.System.now(), timestamp = kotlinx.datetime.Clock.System.now(),
), ),
@@ -274,19 +274,19 @@ class RouterNarrationTest {
latencyMs: Long = 10L, latencyMs: Long = 10L,
tokensUsed: TokenUsage = TokenUsage(5, 5), tokensUsed: TokenUsage = TokenUsage(5, 5),
l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(), l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(),
): DefaultRouterFacade = DefaultRouterFacade( ): DefaultTalkieFacade = DefaultTalkieFacade(
routerRepository = object : RouterRepository { routerRepository = object : TalkieRepository {
override suspend fun getRouterState(sessionId: SessionId): RouterState = override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
RouterState( TalkieState(
sessionId = sessionId, sessionId = sessionId,
workflowStatus = WorkflowStatus.RUNNING, workflowStatus = WorkflowStatus.RUNNING,
currentStageId = StageId("s1"), currentStageId = StageId("s1"),
) )
}, },
routerContextBuilder = DefaultRouterContextBuilder(RouterConfig()), routerContextBuilder = DefaultTalkieContextBuilder(TalkieConfig()),
inferenceRouter = mockInferenceRouter(responseText, latencyMs, tokensUsed), inferenceRouter = mockInferenceRouter(responseText, latencyMs, tokensUsed),
eventStore = eventStore, eventStore = eventStore,
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)), config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
embedder = NoopEmbedder(dimension = 8), embedder = NoopEmbedder(dimension = 8),
l3MemoryStore = l3MemoryStore, l3MemoryStore = l3MemoryStore,
) )
@@ -372,7 +372,7 @@ class RouterNarrationTest {
storedEntries.add(entry) storedEntries.add(entry)
} }
override suspend fun query(query: L3Query): List<com.correx.core.router.l3.L3Hit> = emptyList() override suspend fun query(query: L3Query): List<com.correx.core.talkie.l3.L3Hit> = emptyList()
override suspend fun existsByTurnIdPrefix(prefix: String): Boolean = false override suspend fun existsByTurnIdPrefix(prefix: String): Boolean = false
@@ -9,27 +9,27 @@ import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowStartedEvent import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId import com.correx.core.events.types.StageId
import com.correx.core.router.DefaultRouterReducer import com.correx.core.talkie.DefaultTalkieReducer
import com.correx.core.router.RouterProjector import com.correx.core.talkie.TalkieProjector
import com.correx.core.router.model.RouterState import com.correx.core.talkie.model.TalkieState
import com.correx.core.router.model.StageOutcomeKind import com.correx.core.talkie.model.StageOutcomeKind
import com.correx.core.router.model.WorkflowStatus import com.correx.core.talkie.model.WorkflowStatus
import com.correx.testing.fixtures.EventFixtures.stored import com.correx.testing.fixtures.EventFixtures.stored
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class RouterProjectorTest { class TalkieProjectorTest {
private val reducer = DefaultRouterReducer() private val reducer = DefaultTalkieReducer()
private val projector = RouterProjector(reducer) private val projector = TalkieProjector(reducer)
private val sessionId = SessionId("s1") private val sessionId = SessionId("s1")
private val stageId = StageId("stage-1") private val stageId = StageId("stage-1")
private val workflowId = "workflow-test" private val workflowId = "workflow-test"
@Test @Test
fun `initial() returns RouterState with IDLE status and empty collections`() { fun `initial() returns TalkieState with IDLE status and empty collections`() {
val state = projector.initial() val state = projector.initial()
assertEquals(WorkflowStatus.IDLE, state.workflowStatus) assertEquals(WorkflowStatus.IDLE, state.workflowStatus)
assertNull(state.sessionId) assertNull(state.sessionId)
@@ -190,7 +190,7 @@ class RouterProjectorTest {
@Test @Test
fun `full lifecycle through projector mirrors reducer pipeline`() { fun `full lifecycle through projector mirrors reducer pipeline`() {
var state: RouterState = projector.initial() var state: TalkieState = projector.initial()
state = projector.apply( state = projector.apply(
state, state,
@@ -9,9 +9,9 @@ import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowStartedEvent import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId import com.correx.core.events.types.StageId
import com.correx.core.router.DefaultRouterReducer import com.correx.core.talkie.DefaultTalkieReducer
import com.correx.core.router.model.StageOutcomeKind import com.correx.core.talkie.model.StageOutcomeKind
import com.correx.core.router.model.WorkflowStatus import com.correx.core.talkie.model.WorkflowStatus
import com.correx.testing.fixtures.EventFixtures.stored import com.correx.testing.fixtures.EventFixtures.stored
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
@@ -20,9 +20,9 @@ import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class RouterReducerTest { class TalkieReducerTest {
private val reducer = DefaultRouterReducer() private val reducer = DefaultTalkieReducer()
private val sessionId = SessionId("s1") private val sessionId = SessionId("s1")
private val stageId = StageId("stage-1") private val stageId = StageId("stage-1")
private val workflowId = "workflow-test" private val workflowId = "workflow-test"
+1 -1
View File
@@ -29,6 +29,6 @@ dependencies {
testImplementation(project(":infrastructure:tools")) testImplementation(project(":infrastructure:tools"))
testImplementation(project(":infrastructure:tools:filesystem")) testImplementation(project(":infrastructure:tools:filesystem"))
testImplementation(project(":apps:server")) testImplementation(project(":apps:server"))
testImplementation(project(":core:router")) testImplementation(project(":core:talkie"))
testImplementation(project(":core:config")) testImplementation(project(":core:config"))
} }
@@ -10,7 +10,7 @@ import com.correx.core.inference.Embedder
import com.correx.core.journal.DecisionJournalProjector import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.router.l3.InMemoryL3MemoryStore import com.correx.core.talkie.l3.InMemoryL3MemoryStore
import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.infrastructure.persistence.InMemoryEventStore import com.correx.infrastructure.persistence.InMemoryEventStore
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies {
testImplementation(project(":core:inference")) testImplementation(project(":core:inference"))
testImplementation(project(":core:kernel")) testImplementation(project(":core:kernel"))
testImplementation(project(":core:artifacts")) testImplementation(project(":core:artifacts"))
testImplementation(project(":core:router")) testImplementation(project(":core:talkie"))
testImplementation(project(":core:journal")) testImplementation(project(":core:journal"))
implementation(project(":testing:fixtures")) implementation(project(":testing:fixtures"))
} }
@@ -8,19 +8,19 @@ import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.events.events.WorkflowStartedEvent import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.types.SessionId import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId import com.correx.core.events.types.StageId
import com.correx.core.router.DefaultRouterReducer import com.correx.core.talkie.DefaultTalkieReducer
import com.correx.core.router.RouterProjector import com.correx.core.talkie.TalkieProjector
import com.correx.core.router.model.StageOutcomeKind import com.correx.core.talkie.model.StageOutcomeKind
import com.correx.core.router.model.WorkflowStatus import com.correx.core.talkie.model.WorkflowStatus
import com.correx.testing.fixtures.EventFixtures.stored import com.correx.testing.fixtures.EventFixtures.stored
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class RouterProjectorTest { class TalkieProjectorTest {
private val reducer = DefaultRouterReducer() private val reducer = DefaultTalkieReducer()
private val projector = RouterProjector(reducer) private val projector = TalkieProjector(reducer)
private val sessionId = SessionId("s1") private val sessionId = SessionId("s1")
private val stageId = StageId("stage-1") private val stageId = StageId("stage-1")
private val workflowId = "workflow-test" private val workflowId = "workflow-test"