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:
@@ -38,7 +38,7 @@ dependencies {
|
||||
implementation project(':infrastructure:inference:llama_cpp')
|
||||
implementation project(':infrastructure:inference:openai_compat')
|
||||
implementation project(':infrastructure:inference:commons')
|
||||
implementation project(':core:router')
|
||||
implementation project(':core:talkie')
|
||||
implementation project(':core:tools')
|
||||
implementation project(':core:toolintent')
|
||||
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.retry.DefaultRetryCoordinator
|
||||
import com.correx.core.risk.DefaultRiskAssessor
|
||||
import com.correx.core.router.RouterFacade
|
||||
import com.correx.core.router.l3.L3MetadataRehydrator
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.talkie.TalkieFacade
|
||||
import com.correx.core.talkie.l3.L3MetadataRehydrator
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.sessions.DefaultSessionReducer
|
||||
import com.correx.core.sessions.DefaultSessionRepository
|
||||
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
|
||||
// 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(
|
||||
transitionResolver = DefaultTransitionResolver { condition, ctx -> condition.evaluate(ctx) },
|
||||
contextPackBuilder = DefaultContextPackBuilder(
|
||||
@@ -345,7 +345,7 @@ fun main() {
|
||||
val artifactKindRegistry = DefaultArtifactKindRegistry().also { reg ->
|
||||
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:
|
||||
// persisted vectors survive restart but the metadata map does not (no-op for
|
||||
// non-rehydratable backends like in_memory).
|
||||
@@ -390,11 +390,11 @@ fun main() {
|
||||
InfrastructureModule.createWorkflowLoader(configArtifactKindsEarly),
|
||||
)
|
||||
// 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).
|
||||
fun buildRouterFacade(cfg: CorrexConfig): RouterFacade {
|
||||
val rc = cfg.router
|
||||
val domainRouterConfig = RouterConfig(
|
||||
fun buildTalkieFacade(cfg: CorrexConfig): TalkieFacade {
|
||||
val rc = cfg.talkie
|
||||
val domainTalkieConfig = TalkieConfig(
|
||||
conversationKeepLast = rc.conversationKeepLast,
|
||||
retrievalK = rc.retrievalK,
|
||||
tokenBudget = TokenBudget(limit = rc.tokenBudget),
|
||||
@@ -410,16 +410,16 @@ fun main() {
|
||||
),
|
||||
narrationModelId = rc.narration.modelId,
|
||||
)
|
||||
return InfrastructureModule.createRouterFacade(
|
||||
return InfrastructureModule.createTalkieFacade(
|
||||
eventStore = eventStore,
|
||||
inferenceRouter = inferenceRouter,
|
||||
config = domainRouterConfig,
|
||||
config = domainTalkieConfig,
|
||||
tokenizer = firstProvider.tokenizer,
|
||||
embedder = embedder,
|
||||
l3MemoryStore = l3MemoryStore,
|
||||
workflowSummaryProvider = {
|
||||
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 ->
|
||||
@@ -429,7 +429,7 @@ fun main() {
|
||||
},
|
||||
)
|
||||
}
|
||||
val routerFacade = buildRouterFacade(correxConfig)
|
||||
val routerFacade = buildTalkieFacade(correxConfig)
|
||||
val sessionUndoService = com.correx.apps.server.undo.SessionUndoService(
|
||||
eventStore = eventStore,
|
||||
artifactStore = artifactStore,
|
||||
@@ -546,7 +546,7 @@ fun main() {
|
||||
modelSwapper = modelSwapper,
|
||||
resourceProbe = resourceProbe,
|
||||
workspaceResolver = workspaceResolver,
|
||||
narrationMaxPerRun = correxConfig.router.narration.maxPerRun,
|
||||
narrationMaxPerRun = correxConfig.talkie.narration.maxPerRun,
|
||||
projectMemory = projectMemory,
|
||||
architectContradictionChecker = architectContradictionChecker,
|
||||
configHolder = configHolder,
|
||||
@@ -564,7 +564,7 @@ fun main() {
|
||||
// services. Built after the module so the rebuild hook can swap them in place.
|
||||
module.configService = ConfigService(configHolder, ConfigLoader.configPath()) { cfg ->
|
||||
module.applyRebuiltServices(
|
||||
newRouterFacade = buildRouterFacade(cfg),
|
||||
newTalkieFacade = buildTalkieFacade(cfg),
|
||||
newProjectMemory = buildProjectMemory(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.OrchestrationConfig
|
||||
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.SessionSummary
|
||||
import com.correx.core.sessions.SessionSummaryProjector
|
||||
@@ -72,7 +72,7 @@ class ServerModule(
|
||||
val defaultOrchestrationConfig: OrchestrationConfig,
|
||||
// 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.
|
||||
routerFacade: RouterFacade,
|
||||
routerFacade: TalkieFacade,
|
||||
val orchestrationRepository: OrchestrationRepository,
|
||||
val approvalRepository: DefaultApprovalRepository,
|
||||
val toolRegistry: ToolRegistry,
|
||||
@@ -134,7 +134,7 @@ class ServerModule(
|
||||
// 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.
|
||||
@Volatile
|
||||
var routerFacade: RouterFacade = routerFacade
|
||||
var routerFacade: TalkieFacade = routerFacade
|
||||
private set
|
||||
|
||||
@Volatile
|
||||
@@ -154,11 +154,11 @@ class ServerModule(
|
||||
* In-flight sessions are untouched; the next session/turn reads the new instances.
|
||||
*/
|
||||
fun applyRebuiltServices(
|
||||
newRouterFacade: RouterFacade,
|
||||
newTalkieFacade: TalkieFacade,
|
||||
newProjectMemory: com.correx.apps.server.memory.ProjectMemoryService?,
|
||||
newProfileAdaptationService: com.correx.apps.server.personalization.ProfileAdaptationService?,
|
||||
) {
|
||||
this.routerFacade = newRouterFacade
|
||||
this.routerFacade = newTalkieFacade
|
||||
this.projectMemory = newProjectMemory
|
||||
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.TransitionExecutedEvent
|
||||
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.WorkflowProposedEvent
|
||||
import com.correx.core.events.risk.RiskAction
|
||||
@@ -249,7 +249,7 @@ suspend fun domainEventToServerMessage(
|
||||
sequence = seq,
|
||||
sessionSequence = sessionSequence,
|
||||
)
|
||||
is RouterNarrationEvent -> ServerMessage.Narration(
|
||||
is TalkieNarrationEvent -> ServerMessage.Narration(
|
||||
sessionId = p.sessionId,
|
||||
content = p.content,
|
||||
stageId = p.stageId,
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import com.correx.core.events.events.RelatedDecision
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.inference.Embedder
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
|
||||
/**
|
||||
* Display-only architect contradiction surfacing (BACKLOG §B-§4). When the architect records a
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ import com.correx.core.events.events.RepoKnowledgeHit
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.inference.Embedder
|
||||
import com.correx.core.kernel.orchestration.RepoKnowledgeRetriever
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
|
||||
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.journal.DefaultDecisionJournalRepository
|
||||
import com.correx.core.journal.ProjectMemoryDistiller
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.datetime.Clock
|
||||
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.stores.EventStore
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.router.RouterFacade
|
||||
import com.correx.core.router.model.NarrationTrigger
|
||||
import com.correx.core.talkie.TalkieFacade
|
||||
import com.correx.core.talkie.model.NarrationTrigger
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
@@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class NarrationSubscriber(
|
||||
private val eventStore: EventStore,
|
||||
private val routerFacade: RouterFacade,
|
||||
private val routerFacade: TalkieFacade,
|
||||
private val scope: CoroutineScope,
|
||||
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.SessionId
|
||||
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.serialization.Serializable
|
||||
|
||||
|
||||
@@ -562,7 +562,7 @@ sealed interface ServerMessage {
|
||||
|
||||
@Serializable
|
||||
@SerialName("router.response")
|
||||
data class RouterResponseMessage(
|
||||
data class TalkieResponseMessage(
|
||||
val sessionId: SessionId,
|
||||
val content: String,
|
||||
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.ProjectProfileWriter
|
||||
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.infrastructure.inference.commons.ResourceProbe
|
||||
import com.correx.infrastructure.inference.commons.ResourceSnapshot
|
||||
@@ -253,7 +253,7 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
||||
is ClientMessage.ChatInput -> {
|
||||
// The USER and ROUTER turns are emitted as ChatTurnEvents inside onUserInput
|
||||
// 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) {
|
||||
runCatching {
|
||||
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.ProtocolSerializer
|
||||
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 io.ktor.server.websocket.DefaultWebSocketServerSession
|
||||
import io.ktor.websocket.Frame
|
||||
@@ -93,7 +93,7 @@ class SessionStreamHandler(private val module: ServerModule) {
|
||||
session.send(
|
||||
Frame.Text(
|
||||
ProtocolSerializer.encodeServerMessage(
|
||||
RouterResponseMessage(
|
||||
TalkieResponseMessage(
|
||||
sessionId = msg.sessionId,
|
||||
content = response.content,
|
||||
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.ProjectIdentity
|
||||
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.events.events.ArtifactContentStoredEvent
|
||||
import com.correx.core.events.events.ArtifactCreatedEvent
|
||||
|
||||
+7
-7
@@ -35,11 +35,11 @@ import com.correx.core.kernel.orchestration.OrchestratorEngines
|
||||
import com.correx.core.kernel.orchestration.OrchestratorRepositories
|
||||
import com.correx.core.kernel.retry.DefaultRetryCoordinator
|
||||
import com.correx.core.risk.DefaultRiskAssessor
|
||||
import com.correx.core.router.l3.L3Hit
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.talkie.l3.L3Hit
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.sessions.DefaultSessionReducer
|
||||
import com.correx.core.sessions.DefaultSessionRepository
|
||||
import com.correx.core.sessions.SessionProjector
|
||||
@@ -182,10 +182,10 @@ class ArchitectContradictionHookTest {
|
||||
tokenizer = provider.tokenizer,
|
||||
decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore),
|
||||
)
|
||||
val routerFacade = InfrastructureModule.createRouterFacade(
|
||||
val routerFacade = InfrastructureModule.createTalkieFacade(
|
||||
eventStore = eventStore,
|
||||
inferenceRouter = inferenceRouter,
|
||||
config = RouterConfig(),
|
||||
config = TalkieConfig(),
|
||||
tokenizer = provider.tokenizer,
|
||||
)
|
||||
val noopProviderRegistry = object : ProviderRegistry {
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ import com.correx.core.kernel.orchestration.OrchestratorEngines
|
||||
import com.correx.core.kernel.orchestration.OrchestratorRepositories
|
||||
import com.correx.core.kernel.retry.DefaultRetryCoordinator
|
||||
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.DefaultSessionRepository
|
||||
import com.correx.core.sessions.SessionProjector
|
||||
@@ -116,10 +116,10 @@ fun buildTestServerModule(
|
||||
decisionJournalRepository = decisionJournalRepository,
|
||||
)
|
||||
|
||||
val routerFacade = InfrastructureModule.createRouterFacade(
|
||||
val routerFacade = InfrastructureModule.createTalkieFacade(
|
||||
eventStore = eventStore,
|
||||
inferenceRouter = modelSwapper,
|
||||
config = RouterConfig(),
|
||||
config = TalkieConfig(),
|
||||
tokenizer = firstProvider.tokenizer,
|
||||
)
|
||||
|
||||
|
||||
+4
-4
@@ -3,10 +3,10 @@ package com.correx.apps.server.memory
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.inference.Embedder
|
||||
import com.correx.core.router.l3.L3Hit
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.talkie.l3.L3Hit
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ package com.correx.apps.server.memory
|
||||
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.inference.Embedder
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import com.correx.core.inference.Embedder
|
||||
import com.correx.core.journal.DecisionJournalProjector
|
||||
import com.correx.core.journal.DefaultDecisionJournalReducer
|
||||
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.infrastructure.persistence.InMemoryEventStore
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import com.correx.core.inference.Embedder
|
||||
import com.correx.core.journal.DecisionJournalProjector
|
||||
import com.correx.core.journal.DefaultDecisionJournalReducer
|
||||
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.events.types.SessionId
|
||||
import com.correx.infrastructure.persistence.InMemoryEventStore
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import com.correx.core.inference.Embedder
|
||||
import com.correx.core.journal.DecisionJournalProjector
|
||||
import com.correx.core.journal.DefaultDecisionJournalReducer
|
||||
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.infrastructure.persistence.InMemoryEventStore
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
+17
-17
@@ -25,10 +25,10 @@ import com.correx.core.events.types.StageId
|
||||
import com.correx.core.events.types.TransitionId
|
||||
import com.correx.core.events.types.ValidationReportId
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import com.correx.core.router.ChatMode
|
||||
import com.correx.core.router.RouterFacade
|
||||
import com.correx.core.router.model.NarrationTrigger
|
||||
import com.correx.core.router.model.RouterResponse
|
||||
import com.correx.core.talkie.ChatMode
|
||||
import com.correx.core.talkie.TalkieFacade
|
||||
import com.correx.core.talkie.model.NarrationTrigger
|
||||
import com.correx.core.talkie.model.TalkieResponse
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -72,10 +72,10 @@ class NarrationSubscriberTest {
|
||||
override suspend fun lastGlobalSequence(): Long = 0L
|
||||
}
|
||||
|
||||
private class RecordingRouterFacade : RouterFacade {
|
||||
private class RecordingTalkieFacade : TalkieFacade {
|
||||
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")
|
||||
|
||||
override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) {
|
||||
@@ -99,7 +99,7 @@ class NarrationSubscriberTest {
|
||||
|
||||
@Test
|
||||
fun `triggers are narrated in order for matching events`(): Unit = runBlocking {
|
||||
val facade = RecordingRouterFacade()
|
||||
val facade = RecordingTalkieFacade()
|
||||
NarrationSubscriber(fakeStore, facade, scope).start()
|
||||
|
||||
// Wait for subscription to be attached
|
||||
@@ -122,7 +122,7 @@ class NarrationSubscriberTest {
|
||||
|
||||
@Test
|
||||
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()
|
||||
|
||||
while (liveFlow.subscriptionCount.value == 0) yield()
|
||||
@@ -158,7 +158,7 @@ class NarrationSubscriberTest {
|
||||
|
||||
@Test
|
||||
fun `maxPerRun=1 skips second trigger after first narration`(): Unit = runBlocking {
|
||||
val facade = RecordingRouterFacade()
|
||||
val facade = RecordingTalkieFacade()
|
||||
NarrationSubscriber(fakeStore, facade, scope, maxPerRun = 1).start()
|
||||
|
||||
while (liveFlow.subscriptionCount.value == 0) yield()
|
||||
@@ -180,10 +180,10 @@ class NarrationSubscriberTest {
|
||||
@Test
|
||||
fun `a throwing narrate does not prevent subsequent narrations`(): Unit = runBlocking {
|
||||
var callCount = 0
|
||||
val facade = object : RouterFacade {
|
||||
val facade = object : TalkieFacade {
|
||||
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")
|
||||
|
||||
override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) {
|
||||
@@ -210,7 +210,7 @@ class NarrationSubscriberTest {
|
||||
|
||||
@Test
|
||||
fun `execution plan rejection triggers narration with rejection reason`(): Unit = runBlocking {
|
||||
val facade = RecordingRouterFacade()
|
||||
val facade = RecordingTalkieFacade()
|
||||
NarrationSubscriber(fakeStore, facade, scope).start()
|
||||
|
||||
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
|
||||
* still pending in the channel.
|
||||
*/
|
||||
private class GatingRouterFacade : RouterFacade {
|
||||
private class GatingTalkieFacade : TalkieFacade {
|
||||
val calls = CopyOnWriteArrayList<NarrationTrigger>()
|
||||
val release = CompletableDeferred<Unit>()
|
||||
// Signals that the worker has pulled the first (warmup) trigger and is now blocked,
|
||||
@@ -252,7 +252,7 @@ class NarrationSubscriberTest {
|
||||
val blocked = CompletableDeferred<Unit>()
|
||||
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")
|
||||
|
||||
override suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger) {
|
||||
@@ -289,7 +289,7 @@ class NarrationSubscriberTest {
|
||||
|
||||
@Test
|
||||
fun `resolved approval drops the still-queued pause narration`(): Unit = runBlocking {
|
||||
val facade = GatingRouterFacade()
|
||||
val facade = GatingTalkieFacade()
|
||||
NarrationSubscriber(fakeStore, facade, scope).start()
|
||||
|
||||
while (liveFlow.subscriptionCount.value == 0) yield()
|
||||
@@ -320,7 +320,7 @@ class NarrationSubscriberTest {
|
||||
|
||||
@Test
|
||||
fun `unrelated approval resolution leaves the pending pause intact`(): Unit = runBlocking {
|
||||
val facade = GatingRouterFacade()
|
||||
val facade = GatingTalkieFacade()
|
||||
NarrationSubscriber(fakeStore, facade, scope).start()
|
||||
|
||||
while (liveFlow.subscriptionCount.value == 0) yield()
|
||||
@@ -342,7 +342,7 @@ class NarrationSubscriberTest {
|
||||
|
||||
@Test
|
||||
fun `resume drops every queued pause narration for the session`(): Unit = runBlocking {
|
||||
val facade = GatingRouterFacade()
|
||||
val facade = GatingTalkieFacade()
|
||||
NarrationSubscriber(fakeStore, facade, scope).start()
|
||||
|
||||
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.events.types.ProviderId
|
||||
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.DefaultSessionRepository
|
||||
import com.correx.core.sessions.SessionProjector
|
||||
@@ -165,10 +165,10 @@ class EventInspectRoutesTest {
|
||||
tokenizer = provider.tokenizer,
|
||||
decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore),
|
||||
)
|
||||
val routerFacade = InfrastructureModule.createRouterFacade(
|
||||
val routerFacade = InfrastructureModule.createTalkieFacade(
|
||||
eventStore = eventStore,
|
||||
inferenceRouter = inferenceRouter,
|
||||
config = RouterConfig(),
|
||||
config = TalkieConfig(),
|
||||
tokenizer = provider.tokenizer,
|
||||
)
|
||||
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.events.types.ProviderId
|
||||
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.DefaultSessionRepository
|
||||
import com.correx.core.sessions.SessionProjector
|
||||
@@ -444,10 +444,10 @@ class TaskRoutesTest {
|
||||
tokenizer = provider.tokenizer,
|
||||
decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore),
|
||||
)
|
||||
val routerFacade = InfrastructureModule.createRouterFacade(
|
||||
val routerFacade = InfrastructureModule.createTalkieFacade(
|
||||
eventStore = eventStore,
|
||||
inferenceRouter = inferenceRouter,
|
||||
config = RouterConfig(),
|
||||
config = TalkieConfig(),
|
||||
tokenizer = provider.tokenizer,
|
||||
)
|
||||
val noopProviderRegistry = object : ProviderRegistry {
|
||||
|
||||
@@ -186,10 +186,10 @@ class WorkspaceHandshakeTest {
|
||||
decisionJournalRepository = InfrastructureModule.createDecisionJournalRepository(eventStore),
|
||||
)
|
||||
|
||||
val routerFacade = InfrastructureModule.createRouterFacade(
|
||||
val routerFacade = InfrastructureModule.createTalkieFacade(
|
||||
eventStore = eventStore,
|
||||
inferenceRouter = inferenceRouter,
|
||||
config = com.correx.core.router.model.RouterConfig(),
|
||||
config = com.correx.core.talkie.model.TalkieConfig(),
|
||||
tokenizer = provider.tokenizer,
|
||||
)
|
||||
|
||||
|
||||
@@ -418,14 +418,14 @@ object ConfigLoader {
|
||||
val toolsFileReadSection = sections["tools.file_read"] ?: emptyMap()
|
||||
val toolsFileWriteSection = sections["tools.file_write"] ?: 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,
|
||||
// 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.
|
||||
val routerEmbedderSection = sections["embedder"] ?: sections["router.embedder"] ?: emptyMap()
|
||||
val routerL3Section = sections["l3"] ?: sections["router.l3"] ?: emptyMap()
|
||||
val routerGenerationSection = sections["router.generation"] ?: emptyMap()
|
||||
val routerNarrationSection = sections["router.narration"] ?: emptyMap()
|
||||
val routerGenerationSection = sections["talkie.generation"] ?: sections["router.generation"] ?: emptyMap()
|
||||
val routerNarrationSection = sections["talkie.narration"] ?: sections["router.narration"] ?: emptyMap()
|
||||
val modelsSection = sections["models"] ?: emptyMap()
|
||||
|
||||
val server = ServerConfig(
|
||||
@@ -575,7 +575,7 @@ object ConfigLoader {
|
||||
modelId = asStringOrNull(routerNarrationSection["model_id"]),
|
||||
)
|
||||
|
||||
val router = RouterConfig(
|
||||
val router = TalkieConfig(
|
||||
embedder = embedder,
|
||||
l3 = l3,
|
||||
conversationKeepLast = asInt(routerSection["conversation_keep_last"], 6),
|
||||
@@ -655,7 +655,7 @@ object ConfigLoader {
|
||||
cli = cli,
|
||||
tools = tools,
|
||||
providers = providers,
|
||||
router = router,
|
||||
talkie = router,
|
||||
models = models,
|
||||
modelsSettings = modelsSettings,
|
||||
artifacts = artifacts,
|
||||
|
||||
@@ -9,7 +9,7 @@ data class CorrexConfig(
|
||||
val cli: CliConfig = CliConfig(),
|
||||
val tools: ToolsConfig = ToolsConfig(),
|
||||
val providers: List<ProviderConfig> = emptyList(),
|
||||
val router: RouterConfig = RouterConfig(),
|
||||
val talkie: TalkieConfig = TalkieConfig(),
|
||||
val models: List<ModelConfig> = emptyList(),
|
||||
val modelsSettings: ModelsSettings = ModelsSettings(),
|
||||
val artifacts: List<ArtifactKindConfig> = emptyList(),
|
||||
@@ -189,7 +189,7 @@ data class ProviderConfig(
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RouterConfig(
|
||||
data class TalkieConfig(
|
||||
val embedder: EmbedderConfig = EmbedderConfig(),
|
||||
val l3: L3Config = L3Config(),
|
||||
val conversationKeepLast: Int = 6,
|
||||
|
||||
@@ -51,36 +51,36 @@ object CorrexConfigWriter {
|
||||
b.section("tools.file_edit")
|
||||
b.kv("enabled", cfg.tools.fileEditEnabled)
|
||||
|
||||
b.section("router")
|
||||
b.kv("conversation_keep_last", cfg.router.conversationKeepLast)
|
||||
b.kv("retrieval_k", cfg.router.retrievalK)
|
||||
b.kv("token_budget", cfg.router.tokenBudget)
|
||||
b.section("talkie")
|
||||
b.kv("conversation_keep_last", cfg.talkie.conversationKeepLast)
|
||||
b.kv("retrieval_k", cfg.talkie.retrievalK)
|
||||
b.kv("token_budget", cfg.talkie.tokenBudget)
|
||||
|
||||
b.section("embedder")
|
||||
b.kv("backend", str(cfg.router.embedder.backend))
|
||||
b.kv("dimension", cfg.router.embedder.dimension)
|
||||
cfg.router.embedder.url?.let { b.kv("url", str(it)) }
|
||||
cfg.router.embedder.modelId?.let { b.kv("model_id", str(it)) }
|
||||
b.kv("backend", str(cfg.talkie.embedder.backend))
|
||||
b.kv("dimension", cfg.talkie.embedder.dimension)
|
||||
cfg.talkie.embedder.url?.let { b.kv("url", str(it)) }
|
||||
cfg.talkie.embedder.modelId?.let { b.kv("model_id", str(it)) }
|
||||
|
||||
b.section("l3")
|
||||
b.kv("backend", str(cfg.router.l3.backend))
|
||||
cfg.router.l3.persistPath?.let { b.kv("persist_path", str(it)) }
|
||||
b.kv("python_executable", str(cfg.router.l3.pythonExecutable))
|
||||
cfg.router.l3.scriptPath?.let { b.kv("script_path", str(it)) }
|
||||
b.kv("dim", cfg.router.l3.dim)
|
||||
b.kv("bit_width", cfg.router.l3.bitWidth)
|
||||
b.kv("backend", str(cfg.talkie.l3.backend))
|
||||
cfg.talkie.l3.persistPath?.let { b.kv("persist_path", str(it)) }
|
||||
b.kv("python_executable", str(cfg.talkie.l3.pythonExecutable))
|
||||
cfg.talkie.l3.scriptPath?.let { b.kv("script_path", str(it)) }
|
||||
b.kv("dim", cfg.talkie.l3.dim)
|
||||
b.kv("bit_width", cfg.talkie.l3.bitWidth)
|
||||
|
||||
b.section("router.generation")
|
||||
b.kv("temperature", cfg.router.generation.temperature)
|
||||
b.kv("top_p", cfg.router.generation.topP)
|
||||
b.kv("max_tokens", cfg.router.generation.maxTokens)
|
||||
b.section("talkie.generation")
|
||||
b.kv("temperature", cfg.talkie.generation.temperature)
|
||||
b.kv("top_p", cfg.talkie.generation.topP)
|
||||
b.kv("max_tokens", cfg.talkie.generation.maxTokens)
|
||||
|
||||
b.section("router.narration")
|
||||
b.kv("temperature", cfg.router.narration.temperature)
|
||||
b.kv("top_p", cfg.router.narration.topP)
|
||||
b.kv("max_tokens", cfg.router.narration.maxTokens)
|
||||
b.kv("max_per_run", cfg.router.narration.maxPerRun)
|
||||
cfg.router.narration.modelId?.let { b.kv("model_id", str(it)) }
|
||||
b.section("talkie.narration")
|
||||
b.kv("temperature", cfg.talkie.narration.temperature)
|
||||
b.kv("top_p", cfg.talkie.narration.topP)
|
||||
b.kv("max_tokens", cfg.talkie.narration.maxTokens)
|
||||
b.kv("max_per_run", cfg.talkie.narration.maxPerRun)
|
||||
cfg.talkie.narration.modelId?.let { b.kv("model_id", str(it)) }
|
||||
|
||||
b.section("orchestration")
|
||||
b.kv("stage_timeout_ms", cfg.orchestration.stageTimeoutMs)
|
||||
|
||||
@@ -87,57 +87,57 @@ object EditableConfig {
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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)) } },
|
||||
),
|
||||
EditableField(
|
||||
"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 }) } },
|
||||
),
|
||||
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 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 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) =
|
||||
rtr(c) { it.copy(generation = f(it.generation)) }
|
||||
private fun nar(c: CorrexConfig, f: (NarrationSettings) -> NarrationSettings) =
|
||||
|
||||
@@ -307,16 +307,16 @@ class ConfigLoaderTest {
|
||||
parseTomlMethod.isAccessible = true
|
||||
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
|
||||
|
||||
assertEquals("llamacpp", result.router.embedder.backend)
|
||||
assertEquals(1536, result.router.embedder.dimension)
|
||||
assertEquals("http://localhost:11000", result.router.embedder.url)
|
||||
assertEquals("nomic-embed-text", result.router.embedder.modelId)
|
||||
assertEquals("llamacpp", result.talkie.embedder.backend)
|
||||
assertEquals(1536, result.talkie.embedder.dimension)
|
||||
assertEquals("http://localhost:11000", result.talkie.embedder.url)
|
||||
assertEquals("nomic-embed-text", result.talkie.embedder.modelId)
|
||||
|
||||
assertEquals("turbovec", result.router.l3.backend)
|
||||
assertEquals("/tmp/router/l3.tq", result.router.l3.persistPath)
|
||||
assertEquals("python3", result.router.l3.pythonExecutable)
|
||||
assertEquals(1536, result.router.l3.dim)
|
||||
assertEquals(4, result.router.l3.bitWidth)
|
||||
assertEquals("turbovec", result.talkie.l3.backend)
|
||||
assertEquals("/tmp/router/l3.tq", result.talkie.l3.persistPath)
|
||||
assertEquals("python3", result.talkie.l3.pythonExecutable)
|
||||
assertEquals(1536, result.talkie.l3.dim)
|
||||
assertEquals(4, result.talkie.l3.bitWidth)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -331,16 +331,16 @@ class ConfigLoaderTest {
|
||||
parseTomlMethod.isAccessible = true
|
||||
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
|
||||
|
||||
assertEquals("noop", result.router.embedder.backend)
|
||||
assertEquals(1536, result.router.embedder.dimension)
|
||||
assertEquals(null, result.router.embedder.url)
|
||||
assertEquals(null, result.router.embedder.modelId)
|
||||
assertEquals("noop", result.talkie.embedder.backend)
|
||||
assertEquals(1536, result.talkie.embedder.dimension)
|
||||
assertEquals(null, result.talkie.embedder.url)
|
||||
assertEquals(null, result.talkie.embedder.modelId)
|
||||
|
||||
assertEquals("in_memory", result.router.l3.backend)
|
||||
assertEquals(null, result.router.l3.persistPath)
|
||||
assertEquals("python3", result.router.l3.pythonExecutable)
|
||||
assertEquals(1536, result.router.l3.dim)
|
||||
assertEquals(4, result.router.l3.bitWidth)
|
||||
assertEquals("in_memory", result.talkie.l3.backend)
|
||||
assertEquals(null, result.talkie.l3.persistPath)
|
||||
assertEquals("python3", result.talkie.l3.pythonExecutable)
|
||||
assertEquals(1536, result.talkie.l3.dim)
|
||||
assertEquals(4, result.talkie.l3.bitWidth)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -477,15 +477,15 @@ class ConfigLoaderTest {
|
||||
parseTomlMethod.isAccessible = true
|
||||
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
|
||||
|
||||
assertEquals(8, result.router.conversationKeepLast)
|
||||
assertEquals(3, result.router.retrievalK)
|
||||
assertEquals(8192, result.router.tokenBudget)
|
||||
assertEquals(0.4, result.router.generation.temperature)
|
||||
assertEquals(0.8, result.router.generation.topP)
|
||||
assertEquals(600, result.router.generation.maxTokens)
|
||||
assertEquals(0.55, result.router.narration.temperature)
|
||||
assertEquals(2048, result.router.narration.maxTokens)
|
||||
assertEquals(25, result.router.narration.maxPerRun)
|
||||
assertEquals(8, result.talkie.conversationKeepLast)
|
||||
assertEquals(3, result.talkie.retrievalK)
|
||||
assertEquals(8192, result.talkie.tokenBudget)
|
||||
assertEquals(0.4, result.talkie.generation.temperature)
|
||||
assertEquals(0.8, result.talkie.generation.topP)
|
||||
assertEquals(600, result.talkie.generation.maxTokens)
|
||||
assertEquals(0.55, result.talkie.narration.temperature)
|
||||
assertEquals(2048, result.talkie.narration.maxTokens)
|
||||
assertEquals(25, result.talkie.narration.maxPerRun)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -494,10 +494,10 @@ class ConfigLoaderTest {
|
||||
parseTomlMethod.isAccessible = true
|
||||
val result = parseTomlMethod.invoke(ConfigLoader, "[server]\nhost = \"x\"") as CorrexConfig
|
||||
|
||||
assertEquals(6, result.router.conversationKeepLast)
|
||||
assertEquals(512, result.router.generation.maxTokens)
|
||||
assertEquals(4096, result.router.narration.maxTokens)
|
||||
assertEquals(100, result.router.narration.maxPerRun)
|
||||
assertEquals(6, result.talkie.conversationKeepLast)
|
||||
assertEquals(512, result.talkie.generation.maxTokens)
|
||||
assertEquals(4096, result.talkie.narration.maxTokens)
|
||||
assertEquals(100, result.talkie.narration.maxPerRun)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -530,7 +530,7 @@ class ConfigLoaderTest {
|
||||
|
||||
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
|
||||
@@ -542,6 +542,6 @@ class ConfigLoaderTest {
|
||||
|
||||
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"),
|
||||
networkAllowedHosts = listOf("example.com"),
|
||||
),
|
||||
router = RouterConfig(
|
||||
talkie = TalkieConfig(
|
||||
conversationKeepLast = 7,
|
||||
retrievalK = 4,
|
||||
tokenBudget = 5000,
|
||||
|
||||
@@ -25,7 +25,7 @@ class EditableConfigTest {
|
||||
CorrexConfig(),
|
||||
mapOf("router.generation.temperature" to "0.25", "orchestration.stage_timeout_ms" to "90000"),
|
||||
).getOrThrow()
|
||||
assertEquals(0.25, out.router.generation.temperature)
|
||||
assertEquals(0.25, out.talkie.generation.temperature)
|
||||
assertEquals(90_000L, out.orchestration.stageTimeoutMs)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ data class ContextTruncatedEvent(
|
||||
|
||||
@Serializable
|
||||
@SerialName("RouterNarration")
|
||||
data class RouterNarrationEvent(
|
||||
data class TalkieNarrationEvent(
|
||||
val sessionId: SessionId,
|
||||
val narrationId: 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.CritiqueFindingsRecordedEvent
|
||||
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.ProjectProfileBoundEvent
|
||||
import com.correx.core.events.events.SessionWorkspaceBoundEvent
|
||||
@@ -147,7 +147,7 @@ val eventModule = SerializersModule {
|
||||
subclass(ChatSessionStartedEvent::class)
|
||||
subclass(SessionWorkingTaskEvent::class)
|
||||
subclass(ChatTurnEvent::class)
|
||||
subclass(RouterNarrationEvent::class)
|
||||
subclass(TalkieNarrationEvent::class)
|
||||
subclass(SessionWorkspaceBoundEvent::class)
|
||||
subclass(OperatorProfileBoundEvent::class)
|
||||
subclass(ProjectProfileBoundEvent::class)
|
||||
|
||||
+6
-4
@@ -1,16 +1,16 @@
|
||||
package com.correx.core.events.serialization
|
||||
|
||||
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.inference.TokenUsage
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class RouterNarrationEventSerializationTest {
|
||||
class TalkieNarrationEventSerializationTest {
|
||||
|
||||
private val sample = RouterNarrationEvent(
|
||||
private val sample = TalkieNarrationEvent(
|
||||
sessionId = SessionId("sess-1"),
|
||||
narrationId = "narr-42",
|
||||
trigger = "stage_start",
|
||||
@@ -24,6 +24,8 @@ class RouterNarrationEventSerializationTest {
|
||||
@Test
|
||||
fun `round-trips as polymorphic EventPayload`() {
|
||||
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")
|
||||
val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded)
|
||||
assertEquals(sample, decoded)
|
||||
@@ -31,7 +33,7 @@ class RouterNarrationEventSerializationTest {
|
||||
|
||||
@Test
|
||||
fun `round-trips with optional fields absent`() {
|
||||
val minimal = RouterNarrationEvent(
|
||||
val minimal = TalkieNarrationEvent(
|
||||
sessionId = SessionId("sess-2"),
|
||||
narrationId = "narr-0",
|
||||
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)
|
||||
}
|
||||
+2
-2
@@ -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.IdeaDiscardedEvent
|
||||
import com.correx.core.events.events.IdeaPromotedEvent
|
||||
import com.correx.core.events.stores.EventStore
|
||||
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
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.correx.core.router
|
||||
package com.correx.core.talkie
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
+19
-19
@@ -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.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.StageId
|
||||
import com.correx.core.inference.Tokenizer
|
||||
import com.correx.core.router.model.Idea
|
||||
import com.correx.core.router.model.NarrationTrigger
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.RouterL2Entry
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.RouterTurn
|
||||
import com.correx.core.router.model.TurnRole
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.talkie.model.Idea
|
||||
import com.correx.core.talkie.model.NarrationTrigger
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.RouterL2Entry
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.TalkieTurn
|
||||
import com.correx.core.talkie.model.TurnRole
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
interface RouterContextBuilder {
|
||||
interface TalkieContextBuilder {
|
||||
suspend fun build(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
budget: TokenBudget,
|
||||
availableWorkflows: List<WorkflowSummary> = emptyList(),
|
||||
projectProfileText: String? = null,
|
||||
@@ -38,18 +38,18 @@ interface RouterContextBuilder {
|
||||
* Default delegates to [build] so that anonymous test stubs that only override [build] compile
|
||||
* 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)
|
||||
}
|
||||
|
||||
class DefaultRouterContextBuilder(
|
||||
private val config: RouterConfig,
|
||||
class DefaultTalkieContextBuilder(
|
||||
private val config: TalkieConfig,
|
||||
private val tokenizer: Tokenizer? = null,
|
||||
// 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()
|
||||
// interface — and its many test doubles — stay unchanged.
|
||||
private val ideaProvider: () -> List<Idea> = { emptyList() },
|
||||
) : RouterContextBuilder {
|
||||
) : TalkieContextBuilder {
|
||||
|
||||
companion object {
|
||||
private val SYSTEM_PROMPT =
|
||||
@@ -106,7 +106,7 @@ class DefaultRouterContextBuilder(
|
||||
}
|
||||
|
||||
override suspend fun build(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
budget: TokenBudget,
|
||||
availableWorkflows: List<WorkflowSummary>,
|
||||
projectProfileText: String?,
|
||||
@@ -179,7 +179,7 @@ class DefaultRouterContextBuilder(
|
||||
// --- Conversation turns (L1) ---
|
||||
// The current (protected) user turn is the last element in conversationHistory,
|
||||
// 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.
|
||||
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 stage = stageOverride ?: state.currentStageId?.value ?: "none"
|
||||
return "Status: $status, Stage: $stage"
|
||||
@@ -400,7 +400,7 @@ class DefaultRouterContextBuilder(
|
||||
* narration lines never enter the conversation flow.
|
||||
*/
|
||||
override suspend fun buildNarrationContext(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
trigger: NarrationTrigger,
|
||||
budget: TokenBudget,
|
||||
): ContextPack {
|
||||
+23
-23
@@ -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.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.L3RetrievedHit
|
||||
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.WorkflowProposedEvent
|
||||
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.ResponseFormat
|
||||
import com.correx.core.inference.TokenUsage
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.model.NarrationTrigger
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.RouterResponse
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.model.NarrationTrigger
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.TalkieResponse
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.datetime.Clock
|
||||
import org.slf4j.LoggerFactory
|
||||
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(
|
||||
sessionId: SessionId,
|
||||
input: String,
|
||||
mode: ChatMode = ChatMode.CHAT,
|
||||
): RouterResponse
|
||||
): TalkieResponse
|
||||
|
||||
suspend fun narrate(sessionId: SessionId, trigger: NarrationTrigger)
|
||||
}
|
||||
|
||||
class DefaultRouterFacade(
|
||||
private val routerRepository: RouterRepository,
|
||||
private val routerContextBuilder: RouterContextBuilder,
|
||||
class DefaultTalkieFacade(
|
||||
private val routerRepository: TalkieRepository,
|
||||
private val routerContextBuilder: TalkieContextBuilder,
|
||||
private val inferenceRouter: InferenceRouter,
|
||||
private val eventStore: EventStore,
|
||||
private val config: RouterConfig,
|
||||
private val config: TalkieConfig,
|
||||
private val validateSteering: (suspend (String) -> String?)? = null,
|
||||
private val embedder: Embedder,
|
||||
private val l3MemoryStore: L3MemoryStore,
|
||||
private val workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() },
|
||||
private val sessionProfileProvider: suspend (SessionId) -> String? = { null },
|
||||
) : RouterFacade {
|
||||
) : TalkieFacade {
|
||||
|
||||
override suspend fun onUserInput(
|
||||
sessionId: SessionId,
|
||||
input: String,
|
||||
mode: ChatMode,
|
||||
): RouterResponse {
|
||||
): TalkieResponse {
|
||||
// Emit USER turn event and get the turnId for retrieval dedup
|
||||
val userTurnId = emitChatTurn(sessionId, input, ChatTurnRole.USER)
|
||||
|
||||
// Rebuild state with user turn appended
|
||||
var state = routerRepository.getRouterState(sessionId)
|
||||
var state = routerRepository.getTalkieState(sessionId)
|
||||
val effectiveStageId = state.currentStageId ?: StageId.NONE
|
||||
|
||||
// L3 retrieval — non-fatal; results fed back into state via event.
|
||||
@@ -119,7 +119,7 @@ class DefaultRouterFacade(
|
||||
),
|
||||
)
|
||||
// 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))
|
||||
|
||||
@@ -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) {
|
||||
val state = routerRepository.getRouterState(sessionId)
|
||||
val state = routerRepository.getTalkieState(sessionId)
|
||||
val effectiveStageId = trigger.stageId?.let { StageId(it) } ?: state.currentStageId ?: StageId.NONE
|
||||
|
||||
val contextPack = routerContextBuilder.buildNarrationContext(state, trigger, config.tokenBudget)
|
||||
@@ -234,7 +234,7 @@ class DefaultRouterFacade(
|
||||
causationId = null,
|
||||
correlationId = null,
|
||||
),
|
||||
payload = RouterNarrationEvent(
|
||||
payload = TalkieNarrationEvent(
|
||||
sessionId = sessionId,
|
||||
narrationId = narrationId,
|
||||
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)
|
||||
}
|
||||
+23
-23
@@ -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.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.WorkflowFailedEvent
|
||||
import com.correx.core.events.events.WorkflowStartedEvent
|
||||
import com.correx.core.router.model.RouterL2Entry
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.RouterTurn
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.TurnRole
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.model.RouterL2Entry
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.TalkieTurn
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.TurnRole
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
interface RouterReducer {
|
||||
val initial: RouterState
|
||||
fun reduce(state: RouterState, event: StoredEvent): RouterState
|
||||
interface TalkieReducer {
|
||||
val initial: TalkieState
|
||||
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,
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
currentStageId = null,
|
||||
@@ -34,7 +34,7 @@ class DefaultRouterReducer : RouterReducer {
|
||||
conversationHistory = emptyList(),
|
||||
)
|
||||
|
||||
override fun reduce(state: RouterState, event: StoredEvent): RouterState {
|
||||
override fun reduce(state: TalkieState, event: StoredEvent): TalkieState {
|
||||
return when (val payload = event.payload) {
|
||||
is WorkflowStartedEvent -> handleWorkflowStarted(state, event)
|
||||
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
|
||||
return state.copy(
|
||||
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(
|
||||
workflowStatus = WorkflowStatus.COMPLETED,
|
||||
currentStageId = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleWorkflowFailed(state: RouterState): RouterState {
|
||||
private fun handleWorkflowFailed(state: TalkieState): TalkieState {
|
||||
return state.copy(
|
||||
workflowStatus = WorkflowStatus.FAILED,
|
||||
currentStageId = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleOrchestrationPaused(state: RouterState): RouterState {
|
||||
private fun handleOrchestrationPaused(state: TalkieState): TalkieState {
|
||||
return state.copy(
|
||||
workflowStatus = WorkflowStatus.PAUSED
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleOrchestrationResumed(state: RouterState): RouterState {
|
||||
private fun handleOrchestrationResumed(state: TalkieState): TalkieState {
|
||||
return state.copy(
|
||||
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 entry = RouterL2Entry(
|
||||
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 entry = RouterL2Entry(
|
||||
stageId = payload.stageId,
|
||||
@@ -120,16 +120,16 @@ class DefaultRouterReducer : RouterReducer {
|
||||
// adr-0003 §4: harvest user steering into the current stage's decision point.
|
||||
// Accumulate here; handleStageCompleted/Failed attach and clear. Pure field
|
||||
// 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)
|
||||
|
||||
private fun handleChatTurn(state: RouterState, event: StoredEvent): RouterState {
|
||||
private fun handleChatTurn(state: TalkieState, event: StoredEvent): TalkieState {
|
||||
val payload = event.payload as ChatTurnEvent
|
||||
val turnRole = when (payload.role) {
|
||||
com.correx.core.events.events.ChatTurnRole.USER -> TurnRole.USER
|
||||
com.correx.core.events.events.ChatTurnRole.ROUTER -> TurnRole.ROUTER
|
||||
}
|
||||
val turn = RouterTurn(
|
||||
val turn = TalkieTurn(
|
||||
role = turnRole,
|
||||
content = payload.content,
|
||||
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
-1
@@ -1,4 +1,4 @@
|
||||
package com.correx.core.router
|
||||
package com.correx.core.talkie
|
||||
|
||||
import com.correx.core.events.events.ProposedWorkflow
|
||||
import kotlinx.serialization.json.Json
|
||||
+1
-1
@@ -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.withLock
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.correx.core.router.l3
|
||||
package com.correx.core.talkie.l3
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.correx.core.router.l3
|
||||
package com.correx.core.talkie.l3
|
||||
|
||||
import com.correx.core.events.types.SessionId
|
||||
import kotlinx.serialization.Serializable
|
||||
+1
-1
@@ -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.
|
||||
+1
-1
@@ -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.stores.EventStore
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.correx.core.router.l3
|
||||
package com.correx.core.talkie.l3
|
||||
|
||||
import com.correx.core.events.types.SessionId
|
||||
import kotlinx.serialization.Serializable
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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). */
|
||||
data class Idea(
|
||||
+2
-2
@@ -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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
data class NarrationTrigger(
|
||||
+2
-2
@@ -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.inference.GenerationConfig
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RouterConfig(
|
||||
data class TalkieConfig(
|
||||
val conversationKeepLast: Int = 6,
|
||||
val retrievalK: Int = 5,
|
||||
val tokenBudget: TokenBudget = TokenBudget(limit = 4096),
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package com.correx.core.router.model
|
||||
package com.correx.core.talkie.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RouterResponse(
|
||||
data class TalkieResponse(
|
||||
val content: String,
|
||||
val steeringEmitted: Boolean = false,
|
||||
)
|
||||
+4
-4
@@ -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.types.SessionId
|
||||
@@ -41,7 +41,7 @@ data class RouterL2Entry(
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RouterTurn(
|
||||
data class TalkieTurn(
|
||||
val role: TurnRole,
|
||||
val content: String,
|
||||
val timestamp: Instant,
|
||||
@@ -49,12 +49,12 @@ data class RouterTurn(
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RouterState(
|
||||
data class TalkieState(
|
||||
val sessionId: SessionId? = null,
|
||||
val workflowStatus: WorkflowStatus = WorkflowStatus.IDLE,
|
||||
val currentStageId: StageId? = null,
|
||||
val l2Memory: List<RouterL2Entry> = emptyList(),
|
||||
val conversationHistory: List<RouterTurn> = emptyList(),
|
||||
val conversationHistory: List<TalkieTurn> = emptyList(),
|
||||
val lastRetrievedMemory: List<L3RetrievedHit> = emptyList(),
|
||||
/**
|
||||
* Steering notes accumulated during the current stage, harvested into the
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.correx.core.router.model
|
||||
package com.correx.core.talkie.model
|
||||
|
||||
data class WorkflowSummary(
|
||||
val id: String,
|
||||
+1
-1
@@ -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.EventPayload
|
||||
+1
-1
@@ -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.assertTrue
|
||||
+1
-1
@@ -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.assertTrue
|
||||
+1
-1
@@ -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.utils.TypeId
|
||||
@@ -7,7 +7,7 @@ plugins {
|
||||
dependencies {
|
||||
implementation project(":core:tools")
|
||||
implementation project(":core:events")
|
||||
implementation project(":core:router")
|
||||
implementation project(":core:talkie")
|
||||
implementation project(":core:inference")
|
||||
implementation project(":core:approvals")
|
||||
implementation project(":core:sessions")
|
||||
|
||||
@@ -5,7 +5,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core:router')
|
||||
implementation project(':core:talkie')
|
||||
implementation project(':core:events')
|
||||
testImplementation "org.junit.jupiter:junit-jupiter"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test"
|
||||
|
||||
@@ -5,7 +5,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core:router')
|
||||
implementation project(':core:talkie')
|
||||
implementation project(':core:sessions')
|
||||
implementation project(':core:events')
|
||||
testImplementation "org.junit.jupiter:junit-jupiter"
|
||||
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
package com.correx.infrastructure.router.turbovec
|
||||
|
||||
import com.correx.core.router.l3.L3Hit
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.l3.RehydratableL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Hit
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.l3.RehydratableL3MemoryStore
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
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.NoopEmbedder
|
||||
import com.correx.core.inference.Tokenizer
|
||||
import com.correx.core.router.DefaultRouterContextBuilder
|
||||
import com.correx.core.router.DefaultRouterFacade
|
||||
import com.correx.core.router.IdeaReader
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.DefaultRouterRepository
|
||||
import com.correx.core.router.RouterFacade
|
||||
import com.correx.core.router.RouterProjector
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.talkie.DefaultTalkieContextBuilder
|
||||
import com.correx.core.talkie.DefaultTalkieFacade
|
||||
import com.correx.core.talkie.IdeaReader
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.DefaultTalkieRepository
|
||||
import com.correx.core.talkie.TalkieFacade
|
||||
import com.correx.core.talkie.TalkieProjector
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
||||
import com.correx.core.tools.contract.Tool
|
||||
@@ -306,22 +306,22 @@ object InfrastructureModule {
|
||||
}
|
||||
}
|
||||
|
||||
fun createRouterFacade(
|
||||
fun createTalkieFacade(
|
||||
eventStore: EventStore,
|
||||
inferenceRouter: InferenceRouter,
|
||||
config: RouterConfig = RouterConfig(),
|
||||
config: TalkieConfig = TalkieConfig(),
|
||||
tokenizer: Tokenizer? = null,
|
||||
embedder: Embedder = createNoopEmbedder(),
|
||||
l3MemoryStore: L3MemoryStore = createInMemoryL3MemoryStore(),
|
||||
workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() },
|
||||
sessionProfileProvider: suspend (SessionId) -> String? = { null },
|
||||
): RouterFacade {
|
||||
val reducer = DefaultRouterReducer()
|
||||
val projector = RouterProjector(reducer)
|
||||
): TalkieFacade {
|
||||
val reducer = DefaultTalkieReducer()
|
||||
val projector = TalkieProjector(reducer)
|
||||
val replayer = DefaultEventReplayer(eventStore, projector)
|
||||
val repository = DefaultRouterRepository(replayer)
|
||||
val repository = DefaultTalkieRepository(replayer)
|
||||
val ideaReader = IdeaReader(eventStore)
|
||||
val contextBuilder = DefaultRouterContextBuilder(config, tokenizer, ideaProvider = { ideaReader.activeIdeas() })
|
||||
return DefaultRouterFacade(repository, contextBuilder, inferenceRouter, eventStore, config, null, embedder, l3MemoryStore, workflowSummaryProvider, sessionProfileProvider)
|
||||
val contextBuilder = DefaultTalkieContextBuilder(config, tokenizer, ideaProvider = { ideaReader.activeIdeas() })
|
||||
return DefaultTalkieFacade(repository, contextBuilder, inferenceRouter, eventStore, config, null, embedder, l3MemoryStore, workflowSummaryProvider, sessionProfileProvider)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ include ':core:validation'
|
||||
include ':core:approvals'
|
||||
include ':core:tools'
|
||||
include ':core:toolintent'
|
||||
include ':core:router'
|
||||
include ':core:talkie'
|
||||
include ':core:sessions'
|
||||
include ':core:tasks'
|
||||
include ':core:config'
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
testImplementation(project(":core:events"))
|
||||
testImplementation(project(":core:router"))
|
||||
testImplementation(project(":core:talkie"))
|
||||
testImplementation(project(":core:sessions"))
|
||||
testImplementation(project(":core:transitions"))
|
||||
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.types.EventId
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.router.l3.L3Hit
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MetadataRehydrator
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.l3.RehydratableL3MemoryStore
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Hit
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MetadataRehydrator
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.l3.RehydratableL3MemoryStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
+120
-120
@@ -5,15 +5,15 @@ import com.correx.core.context.model.TokenBudget
|
||||
import com.correx.core.events.events.L3RetrievedHit
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.router.DefaultRouterContextBuilder
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.router.model.RouterL2Entry
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.RouterTurn
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.TurnRole
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.DefaultTalkieContextBuilder
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import com.correx.core.talkie.model.RouterL2Entry
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.TalkieTurn
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.TurnRole
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.Clock
|
||||
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.Test
|
||||
|
||||
class RouterContextBuilderTest {
|
||||
class TalkieContextBuilderTest {
|
||||
|
||||
private val config = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 200))
|
||||
private val builder = DefaultRouterContextBuilder(config)
|
||||
private val config = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 200))
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build fits within budget when all entries are small`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hi", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hi", clock.now()),
|
||||
),
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
@@ -60,14 +60,14 @@ class RouterContextBuilderTest {
|
||||
fun `build drops entries when budget is exceeded`() {
|
||||
// Build a state where conversation + L2 entries together exceed the tight budget
|
||||
val longContent = "x".repeat(800) // ~400 tokens each
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, longContent, clock.now()),
|
||||
RouterTurn(TurnRole.ROUTER, longContent, clock.now()),
|
||||
RouterTurn(TurnRole.USER, longContent, clock.now()),
|
||||
TalkieTurn(TurnRole.USER, longContent, clock.now()),
|
||||
TalkieTurn(TurnRole.ROUTER, longContent, clock.now()),
|
||||
TalkieTurn(TurnRole.USER, longContent, clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
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`() {
|
||||
// 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.
|
||||
val configNoConv = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderNoConv = DefaultRouterContextBuilder(configNoConv)
|
||||
val configNoConv = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderNoConv = DefaultTalkieContextBuilder(configNoConv)
|
||||
val summary = "x".repeat(200) // ~50 tokens; with prefix "Stage sN (SUCCESS): " ~55 tokens total
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -115,18 +115,18 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build drops conversation turns oldest-first when conversationKeepLast exceeded`() {
|
||||
val configWide = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderWide = DefaultRouterContextBuilder(configWide)
|
||||
val configWide = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderWide = DefaultTalkieContextBuilder(configWide)
|
||||
val longContent = "x".repeat(400)
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-03T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-04T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-03T00: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.
|
||||
@@ -144,7 +144,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L0 system prompt is always present`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
currentStageId = null,
|
||||
@@ -156,7 +156,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L0 workflow status is always present`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.FAILED,
|
||||
currentStageId = StageId("failed-stage"),
|
||||
@@ -171,7 +171,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L0 entries survive zero budget`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.COMPLETED,
|
||||
currentStageId = null,
|
||||
@@ -185,12 +185,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L0 entries survive budget that cannot cover L1 or L2`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "x".repeat(500), clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "x".repeat(500), clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
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.
|
||||
// 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 configTight = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderTight = DefaultRouterContextBuilder(configTight)
|
||||
val configTight = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderTight = DefaultTalkieContextBuilder(configTight)
|
||||
val baseTime = Instant.parse("2026-01-01T00:00:00Z")
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -251,9 +251,9 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L2 entries fit within budget are all retained`() {
|
||||
val config = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -274,7 +274,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `system prompt is classified as L0`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
)
|
||||
@@ -286,7 +286,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `workflow status is classified as L0`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -298,13 +298,13 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversation turns are classified as L1`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hello", clock.now()),
|
||||
RouterTurn(TurnRole.ROUTER, "hi", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hello", clock.now()),
|
||||
TalkieTurn(TurnRole.ROUTER, "hi", clock.now()),
|
||||
),
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
@@ -317,7 +317,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `l2 memory entries are classified as L2`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -335,12 +335,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `no entries other than L0 L1 L2 appear in pack`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hello", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hello", clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
|
||||
@@ -359,18 +359,18 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversationHistory respects conversationKeepLast from config`() {
|
||||
val config = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "one", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "two", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, "three", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "four", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, "five", Instant.parse("2026-01-05T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "one", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "two", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "three", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "four", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "five", Instant.parse("2026-01-05T00:00:00Z")),
|
||||
),
|
||||
)
|
||||
val pack = runBlocking { builder.build(state, TokenBudget(limit = 10000)) }
|
||||
@@ -381,17 +381,17 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversationHistory takeLast preserves ordering`() {
|
||||
val config = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "first", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "second", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, "third", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "fourth", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "first", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "second", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "third", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "fourth", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
),
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
@@ -409,7 +409,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build with empty state produces L0 only`() {
|
||||
val state = RouterState()
|
||||
val state = TalkieState()
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
val l0Entries = pack.layers[ContextLayer.L0]
|
||||
assertNotNull(l0Entries)
|
||||
@@ -420,7 +420,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `pack contains correct metadata on empty state`() {
|
||||
val state = RouterState()
|
||||
val state = TalkieState()
|
||||
val pack = buildPack(state, TokenBudget(limit = 5000))
|
||||
assertEquals(5000, pack.budgetLimit)
|
||||
assertEquals(0, pack.compressionMetadata.entriesDropped)
|
||||
@@ -430,7 +430,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `pack has correct context pack id and session info`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -447,7 +447,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `system prompt has expected content`() {
|
||||
val state = RouterState()
|
||||
val state = TalkieState()
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
val systemEntry = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" }
|
||||
assertNotNull(systemEntry)
|
||||
@@ -461,7 +461,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `workflow status entry contains status and stage`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.COMPLETED,
|
||||
currentStageId = null,
|
||||
@@ -475,12 +475,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversation turn entry is formatted with role prefix`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "user message", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "user message", clock.now()),
|
||||
),
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
@@ -492,7 +492,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `l2 entry is formatted with stage outcome and summary`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -516,12 +516,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `budgetUsed equals sum of tokenEstimate of all retained entries`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hello world", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hello world", clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
|
||||
@@ -535,14 +535,14 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `compressionMetadata entriesDropped reflects dropped count`() {
|
||||
val config = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "x".repeat(200), clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "x".repeat(200), clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()),
|
||||
@@ -561,17 +561,17 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build with full state including all layers`() {
|
||||
val config = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "first turn", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "first reply", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
RouterTurn(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.USER, "first turn", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "first reply", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "second turn", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "second reply", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(
|
||||
@@ -614,7 +614,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build with null sessionId produces unknown pack id`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 1000))
|
||||
@@ -627,7 +627,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L3 hits injected into pack ordered by score descending`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -667,7 +667,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L3 entries carry recalled memory label in content`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
lastRetrievedMemory = listOf(
|
||||
@@ -694,7 +694,7 @@ class RouterContextBuilderTest {
|
||||
fun `L3 hits dropped when budget exhausted and counted in entriesDropped`() {
|
||||
// Budget tight enough for L0 but not L3 entries with long text
|
||||
val longText = "x".repeat(800) // ~200 tokens
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
lastRetrievedMemory = listOf(
|
||||
@@ -721,14 +721,14 @@ class RouterContextBuilderTest {
|
||||
@Test
|
||||
fun `system prompt and current user turn survive tight budget`() {
|
||||
val longContent = "x".repeat(800) // ~200 tokens each
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(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"),
|
||||
RouterTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "current-turn"),
|
||||
TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z"), turnId = "old-turn"),
|
||||
TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z"), turnId = "mid-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.
|
||||
@@ -748,17 +748,17 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `oldest conversation turn is dropped before newest when budget is tight`() {
|
||||
val config3 = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder3 = DefaultRouterContextBuilder(config3)
|
||||
val config3 = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder3 = DefaultTalkieContextBuilder(config3)
|
||||
val longContent = "x".repeat(800) // ~200 tokens each
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(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"),
|
||||
RouterTurn(TurnRole.USER, "newest turn", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
|
||||
TalkieTurn(TurnRole.USER, "oldest turn $longContent", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"),
|
||||
TalkieTurn(TurnRole.ROUTER, "middle turn $longContent", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"),
|
||||
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.
|
||||
@@ -776,12 +776,12 @@ class RouterContextBuilderTest {
|
||||
@Test
|
||||
fun `L3 and L2 dropped before recent conversation when budget is tight`() {
|
||||
val longContent = "x".repeat(200) // ~50 tokens each
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "current user message", clock.now(), turnId = "curr"),
|
||||
TalkieTurn(TurnRole.USER, "current user message", clock.now(), turnId = "curr"),
|
||||
),
|
||||
lastRetrievedMemory = listOf(
|
||||
L3RetrievedHit(
|
||||
@@ -816,7 +816,7 @@ class RouterContextBuilderTest {
|
||||
@Test
|
||||
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 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" }
|
||||
assertNotNull(entry)
|
||||
assertTrue(entry!!.content.contains("## Available workflows"))
|
||||
@@ -825,27 +825,27 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
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" })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `project profile text is injected into L0 when non-null`() {
|
||||
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" }
|
||||
assertEquals(text, entry!!.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
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" })
|
||||
}
|
||||
|
||||
@Test
|
||||
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
|
||||
assertFalse(content.contains("if something is not in context, say you do not have it"))
|
||||
assertTrue(content.contains("Available workflows", ignoreCase = true))
|
||||
@@ -855,13 +855,13 @@ class RouterContextBuilderTest {
|
||||
@Test
|
||||
fun `truncatedLayers populated with affected layers when drops occur`() {
|
||||
val longText = "x".repeat(800)
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, longText, clock.now(), turnId = "old"),
|
||||
RouterTurn(TurnRole.USER, "current", clock.now(), turnId = "curr"),
|
||||
TalkieTurn(TurnRole.USER, longText, clock.now(), turnId = "old"),
|
||||
TalkieTurn(TurnRole.USER, "current", clock.now(), turnId = "curr"),
|
||||
),
|
||||
lastRetrievedMemory = listOf(
|
||||
L3RetrievedHit(
|
||||
@@ -889,12 +889,12 @@ class RouterContextBuilderTest {
|
||||
// The current user turn must still be included; budgetUsed must report the true total;
|
||||
// appliedStrategies must contain "BudgetExceeded".
|
||||
val longUserTurn = "x".repeat(200) // ~50 tokens
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
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.
|
||||
@@ -928,16 +928,16 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversationKeepLast zero still includes the current user turn`() {
|
||||
val configZero = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderZero = DefaultRouterContextBuilder(configZero)
|
||||
val state = RouterState(
|
||||
val configZero = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderZero = DefaultTalkieContextBuilder(configZero)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(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"),
|
||||
RouterTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
|
||||
TalkieTurn(TurnRole.USER, "old turn 1", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"),
|
||||
TalkieTurn(TurnRole.ROUTER, "old reply", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"),
|
||||
TalkieTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
|
||||
),
|
||||
)
|
||||
val pack = runBlocking { builderZero.build(state, TokenBudget(limit = 10000)) }
|
||||
@@ -955,7 +955,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `appliedStrategies includes L3Recall when lastRetrievedMemory is non-empty`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -978,7 +978,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `appliedStrategies does not include L3Recall when lastRetrievedMemory is empty`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
)
|
||||
@@ -995,12 +995,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `layers map is sorted by ContextLayer ordinal`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hello", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hello", clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
|
||||
+211
-211
@@ -29,26 +29,26 @@ import com.correx.core.inference.NoopEmbedder
|
||||
import com.correx.core.inference.ProviderHealth
|
||||
import com.correx.core.inference.ResponseFormat
|
||||
import com.correx.core.inference.TokenUsage
|
||||
import com.correx.core.router.ChatMode
|
||||
import com.correx.core.router.DefaultRouterContextBuilder
|
||||
import com.correx.core.router.DefaultRouterFacade
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.IdeaReader
|
||||
import com.correx.core.router.RouterContextBuilder
|
||||
import com.correx.core.router.RouterFacade
|
||||
import com.correx.core.router.RouterProjector
|
||||
import com.correx.core.router.RouterRepository
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.RouterResponse
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.RouterTurn
|
||||
import com.correx.core.router.model.TurnRole
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.ChatMode
|
||||
import com.correx.core.talkie.DefaultTalkieContextBuilder
|
||||
import com.correx.core.talkie.DefaultTalkieFacade
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.IdeaReader
|
||||
import com.correx.core.talkie.TalkieContextBuilder
|
||||
import com.correx.core.talkie.TalkieFacade
|
||||
import com.correx.core.talkie.TalkieProjector
|
||||
import com.correx.core.talkie.TalkieRepository
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.TalkieResponse
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.TalkieTurn
|
||||
import com.correx.core.talkie.model.TurnRole
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
||||
import com.correx.testing.fixtures.EventFixtures
|
||||
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.Test
|
||||
|
||||
class RouterFacadeTest {
|
||||
class TalkieFacadeTest {
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// CHAT mode
|
||||
@@ -135,21 +135,21 @@ class RouterFacadeTest {
|
||||
fun `STEERING mode appends SteeringNoteAddedEvent with current stageId`(): Unit = runBlocking {
|
||||
val mockStore = mockEventStore()
|
||||
val stageId = StageId("stage-A")
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("steering response"),
|
||||
eventStore = mockStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -166,26 +166,26 @@ class RouterFacadeTest {
|
||||
|
||||
@Test
|
||||
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 replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedStates.add(state)
|
||||
return emptyContextPack()
|
||||
}
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -206,26 +206,26 @@ class RouterFacadeTest {
|
||||
|
||||
@Test
|
||||
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 replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedStates.add(state)
|
||||
return emptyContextPack()
|
||||
}
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -246,17 +246,17 @@ class RouterFacadeTest {
|
||||
|
||||
@Test
|
||||
fun `state is passed through to context builder`(): Unit = runBlocking {
|
||||
val capturedState = mutableListOf<RouterState>()
|
||||
val mockContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
val capturedState = mutableListOf<TalkieState>()
|
||||
val mockContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedState.add(state)
|
||||
return emptyContextPack()
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = StageId("s1"),
|
||||
@@ -265,7 +265,7 @@ class RouterFacadeTest {
|
||||
routerContextBuilder = mockContextBuilder,
|
||||
inferenceRouter = mockInferenceRouter("inference response"),
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -278,20 +278,20 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `budget is passed through to context builder`(): Unit = runBlocking {
|
||||
val capturedBudget = mutableListOf<TokenBudget>()
|
||||
val mockContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
val mockContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedBudget.add(budget)
|
||||
return emptyContextPack()
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = mockContextBuilder,
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 4200)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 4200)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -313,20 +313,20 @@ class RouterFacadeTest {
|
||||
}
|
||||
}
|
||||
val stateStageId = StageId("state-stage")
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stateStageId,
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -346,20 +346,20 @@ class RouterFacadeTest {
|
||||
return mockProvider("response")
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
currentStageId = null,
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -378,16 +378,16 @@ class RouterFacadeTest {
|
||||
return mockProviderWithCapture("response", capturedRequestIds)
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -408,16 +408,16 @@ class RouterFacadeTest {
|
||||
return mockProviderWithRequestCapture("response", capturedRequests)
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -431,12 +431,12 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `context pack is passed to inference provider`(): Unit = runBlocking {
|
||||
val capturedContextPacks = mutableListOf<ContextPack>()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
val pack = ContextPack(
|
||||
id = ContextPackId("test-pack"),
|
||||
sessionId = state.sessionId ?: SessionId("unknown"),
|
||||
@@ -478,7 +478,7 @@ class RouterFacadeTest {
|
||||
}
|
||||
},
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -488,12 +488,12 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `responseFormat defaults to Text`(): Unit = runBlocking {
|
||||
val capturedRequests = mutableListOf<InferenceRequest>()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = object : InferenceRouter {
|
||||
override suspend fun route(
|
||||
@@ -504,7 +504,7 @@ class RouterFacadeTest {
|
||||
}
|
||||
},
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -514,7 +514,7 @@ class RouterFacadeTest {
|
||||
}
|
||||
|
||||
@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 facade = facadeWithMocks(eventStore = mockStore, chatMode = ChatMode.STEERING)
|
||||
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))
|
||||
}
|
||||
val eventStore = mockEventStore()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = object : InferenceRouter {
|
||||
override suspend fun route(stageId: StageId, requiredCapabilities: Set<ModelCapability>): InferenceProvider =
|
||||
metricsProvider
|
||||
},
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -717,16 +717,16 @@ class RouterFacadeTest {
|
||||
store: EventStore,
|
||||
reply: String,
|
||||
workflows: List<WorkflowSummary>,
|
||||
): RouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(sessionId = sessionId)
|
||||
): TalkieFacade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(sessionId = sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter(reply),
|
||||
eventStore = store,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
workflowSummaryProvider = { workflows },
|
||||
@@ -739,28 +739,28 @@ class RouterFacadeTest {
|
||||
chatMode: ChatMode = ChatMode.CHAT,
|
||||
embedder: Embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(),
|
||||
): RouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
): TalkieFacade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = StageId("s1"),
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("inference response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = embedder,
|
||||
l3MemoryStore = l3MemoryStore,
|
||||
).let { impl ->
|
||||
object : RouterFacade {
|
||||
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse =
|
||||
object : TalkieFacade {
|
||||
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse =
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -872,21 +872,21 @@ class RouterFacadeTest {
|
||||
)
|
||||
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
|
||||
embedder = stubEmbedder,
|
||||
l3MemoryStore = l3Store,
|
||||
)
|
||||
@@ -928,9 +928,9 @@ class RouterFacadeTest {
|
||||
)
|
||||
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
|
||||
// Bootstrap session with a ChatTurnEvent whose turnId matches the L3 entry
|
||||
@@ -954,17 +954,17 @@ class RouterFacadeTest {
|
||||
)
|
||||
)
|
||||
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
|
||||
embedder = stubEmbedder,
|
||||
l3MemoryStore = l3Store,
|
||||
)
|
||||
@@ -986,16 +986,16 @@ class RouterFacadeTest {
|
||||
throw RuntimeException("embedding service unavailable")
|
||||
}
|
||||
val eventStore = mockEventStore()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = throwingEmbedder,
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -1014,9 +1014,9 @@ class RouterFacadeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `replay of L3MemoryRetrievedEvent populates lastRetrievedMemory in RouterState`(): Unit = runBlocking {
|
||||
val reducer = DefaultRouterReducer()
|
||||
val projector = RouterProjector(reducer)
|
||||
fun `replay of L3MemoryRetrievedEvent populates lastRetrievedMemory in TalkieState`(): Unit = runBlocking {
|
||||
val reducer = DefaultTalkieReducer()
|
||||
val projector = TalkieProjector(reducer)
|
||||
val sessionId = SessionId("replay-session")
|
||||
val hits = listOf(
|
||||
L3RetrievedHit(
|
||||
@@ -1048,28 +1048,28 @@ class RouterFacadeTest {
|
||||
|
||||
@Test
|
||||
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 sessionId = SessionId("truncation-session")
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(
|
||||
routerContextBuilder = DefaultTalkieContextBuilder(
|
||||
config = TalkieConfig(
|
||||
conversationKeepLast = 4,
|
||||
tokenBudget = TokenBudget(limit = 80),
|
||||
),
|
||||
),
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(
|
||||
config = TalkieConfig(
|
||||
conversationKeepLast = 4,
|
||||
tokenBudget = TokenBudget(limit = 80),
|
||||
),
|
||||
@@ -1112,16 +1112,16 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `no ContextTruncatedEvent when budget is generous`(): Unit = runBlocking {
|
||||
val eventStore = mockEventStore()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
routerContextBuilder = DefaultTalkieContextBuilder(
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
),
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -1136,9 +1136,9 @@ class RouterFacadeTest {
|
||||
val longContent = "x".repeat(800)
|
||||
val sessionId = SessionId("turnid-session")
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
|
||||
// Pre-seed with a large turn so the budget overflows on the next call
|
||||
@@ -1162,17 +1162,17 @@ class RouterFacadeTest {
|
||||
)
|
||||
)
|
||||
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
|
||||
routerContextBuilder = DefaultTalkieContextBuilder(
|
||||
config = TalkieConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
|
||||
),
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
|
||||
config = TalkieConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -1192,28 +1192,28 @@ class RouterFacadeTest {
|
||||
// Each turn must emit L3MemoryRetrievedEvent (with empty hits), so that
|
||||
// lastRetrievedMemory is always current-turn-scoped and never stale.
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val capturedStates = mutableListOf<RouterState>()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val capturedStates = mutableListOf<TalkieState>()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
private val real = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
private val real = DefaultTalkieContextBuilder(
|
||||
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)
|
||||
return real.build(state, budget)
|
||||
}
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -1265,25 +1265,25 @@ class RouterFacadeTest {
|
||||
|
||||
val capturedPacks = mutableListOf<ContextPack>()
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
private val real = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
private val real = DefaultTalkieContextBuilder(
|
||||
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) }
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
|
||||
embedder = stubEmbedder,
|
||||
l3MemoryStore = l3Store,
|
||||
)
|
||||
@@ -1321,16 +1321,16 @@ class RouterFacadeTest {
|
||||
return mockProvider("narration text")
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(
|
||||
config = TalkieConfig(
|
||||
tokenBudget = TokenBudget(limit = 5000),
|
||||
narrationModelId = "llama-cpp:phi-3-mini",
|
||||
),
|
||||
@@ -1339,7 +1339,7 @@ class RouterFacadeTest {
|
||||
)
|
||||
facade.narrate(
|
||||
sessionId = SessionId("test-session"),
|
||||
trigger = com.correx.core.router.model.NarrationTrigger(
|
||||
trigger = com.correx.core.talkie.model.NarrationTrigger(
|
||||
kind = "test",
|
||||
instruction = "describe what happened",
|
||||
),
|
||||
@@ -1366,16 +1366,16 @@ class RouterFacadeTest {
|
||||
return mockProvider("response")
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(
|
||||
config = TalkieConfig(
|
||||
tokenBudget = TokenBudget(limit = 5000),
|
||||
narrationModelId = "llama-cpp:phi-3-mini",
|
||||
),
|
||||
@@ -1393,9 +1393,9 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `workflowSummaryProvider is called and forwarded to context builder on each turn`(): Unit = runBlocking {
|
||||
val captured = mutableListOf<List<WorkflowSummary>>()
|
||||
val capturingBuilder = object : RouterContextBuilder {
|
||||
val capturingBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
budget: TokenBudget,
|
||||
availableWorkflows: List<WorkflowSummary>,
|
||||
projectProfileText: String?,
|
||||
@@ -1413,9 +1413,9 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `sessionProfileProvider is called and forwarded to context builder`(): Unit = runBlocking {
|
||||
val capturedProfiles = mutableListOf<String?>()
|
||||
val capturingBuilder = object : RouterContextBuilder {
|
||||
val capturingBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
budget: TokenBudget,
|
||||
availableWorkflows: List<WorkflowSummary>,
|
||||
projectProfileText: String?,
|
||||
@@ -1433,9 +1433,9 @@ class RouterFacadeTest {
|
||||
}
|
||||
|
||||
private fun facadeWith(
|
||||
contextBuilder: RouterContextBuilder = object : RouterContextBuilder {
|
||||
contextBuilder: TalkieContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
budget: TokenBudget,
|
||||
availableWorkflows: List<WorkflowSummary>,
|
||||
projectProfileText: String?,
|
||||
@@ -1443,14 +1443,14 @@ class RouterFacadeTest {
|
||||
},
|
||||
workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() },
|
||||
sessionProfileProvider: suspend (SessionId) -> String? = { null },
|
||||
): RouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(sessionId = sessionId)
|
||||
): TalkieFacade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(sessionId = sessionId)
|
||||
},
|
||||
routerContextBuilder = contextBuilder,
|
||||
inferenceRouter = mockInferenceRouter("inference response"),
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
workflowSummaryProvider = workflowSummaryProvider,
|
||||
+52
-52
@@ -4,7 +4,7 @@ import com.correx.core.context.model.TokenBudget
|
||||
import com.correx.core.events.events.ChatTurnEvent
|
||||
import com.correx.core.events.events.EventMetadata
|
||||
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.stores.EventStore
|
||||
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.ResponseFormat
|
||||
import com.correx.core.inference.TokenUsage
|
||||
import com.correx.core.router.DefaultRouterContextBuilder
|
||||
import com.correx.core.router.DefaultRouterFacade
|
||||
import com.correx.core.router.RouterContextBuilder
|
||||
import com.correx.core.router.RouterRepository
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.model.NarrationTrigger
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.talkie.DefaultTalkieContextBuilder
|
||||
import com.correx.core.talkie.DefaultTalkieFacade
|
||||
import com.correx.core.talkie.TalkieContextBuilder
|
||||
import com.correx.core.talkie.TalkieRepository
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.model.NarrationTrigger
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import com.correx.testing.fixtures.inference.MockTokenizer
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
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.Test
|
||||
|
||||
class RouterNarrationTest {
|
||||
class TalkieNarrationTest {
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// narrate() — event emission
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
@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 knownUsage = TokenUsage(promptTokens = 8, completionTokens = 3)
|
||||
val eventStore = MapBackedEventStore()
|
||||
@@ -68,7 +68,7 @@ class RouterNarrationTest {
|
||||
|
||||
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)
|
||||
val event = narrationEvents[0]
|
||||
assertEquals("narration line", event.content)
|
||||
@@ -112,8 +112,8 @@ class RouterNarrationTest {
|
||||
|
||||
facade.narrate(SessionId("sess-4"), trigger)
|
||||
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>()
|
||||
assertTrue(narrationEvents.isEmpty(), "blank inference must not emit RouterNarrationEvent")
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
|
||||
assertTrue(narrationEvents.isEmpty(), "blank inference must not emit TalkieNarrationEvent")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,30 +124,30 @@ class RouterNarrationTest {
|
||||
|
||||
facade.narrate(SessionId("sess-5"), trigger)
|
||||
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>()
|
||||
assertTrue(narrationEvents.isEmpty(), "empty inference must not emit RouterNarrationEvent")
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
|
||||
assertTrue(narrationEvents.isEmpty(), "empty inference must not emit TalkieNarrationEvent")
|
||||
}
|
||||
|
||||
@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 eventStore = MapBackedEventStore()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
override suspend fun buildNarrationContext(state: RouterState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
override suspend fun buildNarrationContext(state: TalkieState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("stage narration", latencyMs = 10L, tokensUsed = TokenUsage(1, 1)),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -155,7 +155,7 @@ class RouterNarrationTest {
|
||||
|
||||
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(SessionId("session-x"), narrationEvents[0].sessionId)
|
||||
assertEquals("my-stage", narrationEvents[0].stageId)
|
||||
@@ -167,8 +167,8 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext includes system entry in L0`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val trigger = NarrationTrigger(kind = "test", instruction = "Do something.")
|
||||
val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000))
|
||||
|
||||
@@ -179,8 +179,8 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext includes workflow status entry`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(
|
||||
sessionId = SessionId("s"),
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = StageId("my-stage"),
|
||||
@@ -196,8 +196,8 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext includes trigger instruction as final entry`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val instruction = "Narrate the completed action now."
|
||||
val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction)
|
||||
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
|
||||
// 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").
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING)
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING)
|
||||
val instruction = "Tell the user the stage just completed."
|
||||
val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction)
|
||||
val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000))
|
||||
@@ -231,8 +231,8 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext stays within budget`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val trigger = NarrationTrigger(kind = "test", instruction = "Short.")
|
||||
val budget = TokenBudget(limit = 5000)
|
||||
val pack = builder.buildNarrationContext(state, trigger, budget)
|
||||
@@ -242,13 +242,13 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext does not include conversationHistory turns`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(
|
||||
sessionId = SessionId("s"),
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
conversationHistory = listOf(
|
||||
com.correx.core.router.model.RouterTurn(
|
||||
role = com.correx.core.router.model.TurnRole.USER,
|
||||
com.correx.core.talkie.model.TalkieTurn(
|
||||
role = com.correx.core.talkie.model.TurnRole.USER,
|
||||
content = "secret conversation turn",
|
||||
timestamp = kotlinx.datetime.Clock.System.now(),
|
||||
),
|
||||
@@ -274,19 +274,19 @@ class RouterNarrationTest {
|
||||
latencyMs: Long = 10L,
|
||||
tokensUsed: TokenUsage = TokenUsage(5, 5),
|
||||
l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(),
|
||||
): DefaultRouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
): DefaultTalkieFacade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = StageId("s1"),
|
||||
)
|
||||
},
|
||||
routerContextBuilder = DefaultRouterContextBuilder(RouterConfig()),
|
||||
routerContextBuilder = DefaultTalkieContextBuilder(TalkieConfig()),
|
||||
inferenceRouter = mockInferenceRouter(responseText, latencyMs, tokensUsed),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = l3MemoryStore,
|
||||
)
|
||||
@@ -372,7 +372,7 @@ class RouterNarrationTest {
|
||||
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
|
||||
|
||||
+10
-10
@@ -9,27 +9,27 @@ import com.correx.core.events.events.WorkflowFailedEvent
|
||||
import com.correx.core.events.events.WorkflowStartedEvent
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.RouterProjector
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.TalkieProjector
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RouterProjectorTest {
|
||||
class TalkieProjectorTest {
|
||||
|
||||
private val reducer = DefaultRouterReducer()
|
||||
private val projector = RouterProjector(reducer)
|
||||
private val reducer = DefaultTalkieReducer()
|
||||
private val projector = TalkieProjector(reducer)
|
||||
private val sessionId = SessionId("s1")
|
||||
private val stageId = StageId("stage-1")
|
||||
private val workflowId = "workflow-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()
|
||||
assertEquals(WorkflowStatus.IDLE, state.workflowStatus)
|
||||
assertNull(state.sessionId)
|
||||
@@ -190,7 +190,7 @@ class RouterProjectorTest {
|
||||
|
||||
@Test
|
||||
fun `full lifecycle through projector mirrors reducer pipeline`() {
|
||||
var state: RouterState = projector.initial()
|
||||
var state: TalkieState = projector.initial()
|
||||
|
||||
state = projector.apply(
|
||||
state,
|
||||
+5
-5
@@ -9,9 +9,9 @@ import com.correx.core.events.events.WorkflowFailedEvent
|
||||
import com.correx.core.events.events.WorkflowStartedEvent
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import kotlinx.datetime.Instant
|
||||
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.Test
|
||||
|
||||
class RouterReducerTest {
|
||||
class TalkieReducerTest {
|
||||
|
||||
private val reducer = DefaultRouterReducer()
|
||||
private val reducer = DefaultTalkieReducer()
|
||||
private val sessionId = SessionId("s1")
|
||||
private val stageId = StageId("stage-1")
|
||||
private val workflowId = "workflow-test"
|
||||
@@ -29,6 +29,6 @@ dependencies {
|
||||
testImplementation(project(":infrastructure:tools"))
|
||||
testImplementation(project(":infrastructure:tools:filesystem"))
|
||||
testImplementation(project(":apps:server"))
|
||||
testImplementation(project(":core:router"))
|
||||
testImplementation(project(":core:talkie"))
|
||||
testImplementation(project(":core:config"))
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,7 +10,7 @@ import com.correx.core.inference.Embedder
|
||||
import com.correx.core.journal.DecisionJournalProjector
|
||||
import com.correx.core.journal.DefaultDecisionJournalReducer
|
||||
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.infrastructure.persistence.InMemoryEventStore
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@@ -12,7 +12,7 @@ dependencies {
|
||||
testImplementation(project(":core:inference"))
|
||||
testImplementation(project(":core:kernel"))
|
||||
testImplementation(project(":core:artifacts"))
|
||||
testImplementation(project(":core:router"))
|
||||
testImplementation(project(":core:talkie"))
|
||||
testImplementation(project(":core:journal"))
|
||||
implementation(project(":testing:fixtures"))
|
||||
}
|
||||
+7
-7
@@ -8,19 +8,19 @@ import com.correx.core.events.events.WorkflowFailedEvent
|
||||
import com.correx.core.events.events.WorkflowStartedEvent
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.RouterProjector
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.TalkieProjector
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RouterProjectorTest {
|
||||
class TalkieProjectorTest {
|
||||
|
||||
private val reducer = DefaultRouterReducer()
|
||||
private val projector = RouterProjector(reducer)
|
||||
private val reducer = DefaultTalkieReducer()
|
||||
private val projector = TalkieProjector(reducer)
|
||||
private val sessionId = SessionId("s1")
|
||||
private val stageId = StageId("stage-1")
|
||||
private val workflowId = "workflow-test"
|
||||
Reference in New Issue
Block a user