feat: wire L3 write path via Embedder into router
Introduces Embedder contract in core:inference with a NoopEmbedder default (zero vector) and wires both Embedder and L3MemoryStore into DefaultRouterFacade. Each ChatTurnEvent emission now embeds the turn content and writes a corresponding L3MemoryEntry, so cross-session memory is captured at the source of truth (the event emission point). InfrastructureModule defaults to NoopEmbedder + InMemoryL3MemoryStore so the system runs without an external embedding model or vector store wired. The TurboVec adapter stays unwired for now; switching to it is a configuration concern (Epic 12 config layer). Also gitignores apps/tui/logs/ (Kotlin TUI runtime logs, pre-Go rewrite).
This commit is contained in:
@@ -10,12 +10,14 @@ import com.correx.core.events.types.ProviderId
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.inference.CapabilityScore
|
||||
import com.correx.core.inference.Embedder
|
||||
import com.correx.core.inference.FinishReason
|
||||
import com.correx.core.inference.InferenceProvider
|
||||
import com.correx.core.inference.InferenceRequest
|
||||
import com.correx.core.inference.InferenceResponse
|
||||
import com.correx.core.inference.InferenceRouter
|
||||
import com.correx.core.inference.ModelCapability
|
||||
import com.correx.core.inference.NoopEmbedder
|
||||
import com.correx.core.inference.ProviderHealth
|
||||
import com.correx.core.inference.ResponseFormat
|
||||
import com.correx.core.inference.TokenUsage
|
||||
@@ -26,6 +28,9 @@ 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.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
|
||||
@@ -127,6 +132,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter("steering response"),
|
||||
eventStore = mockStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!", mode = ChatMode.STEERING)
|
||||
assertEquals(3, mockStore.appendedEvents.size)
|
||||
@@ -161,6 +168,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
val sessionId = SessionId("history-session")
|
||||
facade.onUserInput(sessionId = sessionId, input = "first message")
|
||||
@@ -199,6 +208,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("session-A"), input = "message A")
|
||||
facade.onUserInput(sessionId = SessionId("session-B"), input = "message B")
|
||||
@@ -237,6 +248,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter("inference response"),
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
|
||||
assertEquals(1, capturedState.size)
|
||||
@@ -261,6 +274,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 4200)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
|
||||
assertEquals(1, capturedBudget.size)
|
||||
@@ -294,6 +309,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
|
||||
assertEquals(stateStageId, capturedStageId[0])
|
||||
@@ -325,6 +342,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
|
||||
assertEquals(StageId("none"), capturedStageId[0])
|
||||
@@ -351,6 +370,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("s1"), input = "first")
|
||||
facade.onUserInput(sessionId = SessionId("s1"), input = "second")
|
||||
@@ -379,6 +400,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
|
||||
val req = capturedRequests[0]
|
||||
@@ -438,6 +461,8 @@ class RouterFacadeTest {
|
||||
},
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
|
||||
}
|
||||
@@ -462,6 +487,8 @@ class RouterFacadeTest {
|
||||
},
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
|
||||
val req = capturedRequests[0]
|
||||
@@ -479,6 +506,29 @@ class RouterFacadeTest {
|
||||
assertEquals(3, mockStore.appendedEvents.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CHAT round-trip stores user and router turns in L3 memory`(): Unit = runBlocking {
|
||||
val mockStore = mockEventStore()
|
||||
val l3Store = InMemoryL3MemoryStore()
|
||||
val embedder = NoopEmbedder(dimension = 8)
|
||||
val facade = facadeWithMocks(
|
||||
eventStore = mockStore,
|
||||
chatMode = ChatMode.CHAT,
|
||||
embedder = embedder,
|
||||
l3MemoryStore = l3Store,
|
||||
)
|
||||
val sessionId = SessionId("test-session")
|
||||
facade.onUserInput(sessionId = sessionId, input = "Hello!")
|
||||
val hits = l3Store.query(
|
||||
L3Query(
|
||||
vector = FloatArray(8),
|
||||
k = 10,
|
||||
sessionIdFilter = sessionId,
|
||||
)
|
||||
)
|
||||
assertEquals(2, hits.size)
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -488,6 +538,8 @@ class RouterFacadeTest {
|
||||
private fun facadeWithMocks(
|
||||
eventStore: EventStore,
|
||||
chatMode: ChatMode = ChatMode.CHAT,
|
||||
embedder: Embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(),
|
||||
): RouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
@@ -503,6 +555,8 @@ class RouterFacadeTest {
|
||||
inferenceRouter = mockInferenceRouter("inference response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = embedder,
|
||||
l3MemoryStore = l3MemoryStore,
|
||||
).let { impl ->
|
||||
object : RouterFacade {
|
||||
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse =
|
||||
|
||||
Reference in New Issue
Block a user