feature(event-sourcing): baseline for the architecture review fixes.

- fixed the tool overlay in tui.
- fixed tool calling - added schemas.
- getting all sessionIds via event store.
- instead of sending all events on the replay - there's a new way for replaying.
- session snapshot is now a thing getting sent for previously created sessions.
- added logging to important parts.
- revert workflowId from WorkflowCompletedEvent.
This commit is contained in:
2026-05-24 18:50:00 +04:00
parent f827685ed0
commit fc7b879891
43 changed files with 525 additions and 211 deletions
@@ -37,7 +37,6 @@ class EventsTest {
sessionId = SessionId("s1"),
terminalStageId = StageId("stage-a"),
totalStages = 1,
workflowId = workflowId,
)
val json = eventJson.encodeToString(EventPayload.serializer(), event)
@@ -5,14 +5,20 @@ import com.correx.core.events.events.SteeringNoteAddedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.ContextPackId
import com.correx.core.events.types.EventId
import com.correx.core.events.types.InferenceRequestId
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.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.ProviderHealth
import com.correx.core.inference.ResponseFormat
import com.correx.core.inference.TokenUsage
import com.correx.core.router.ChatMode
import com.correx.core.router.DefaultRouterFacade
@@ -24,6 +30,8 @@ import com.correx.core.router.model.RouterResponse
import com.correx.core.router.model.RouterState
import com.correx.core.router.model.TurnRole
import com.correx.core.router.model.WorkflowStatus
import com.correx.testing.fixtures.inference.MockTokenizer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
@@ -405,25 +413,25 @@ class RouterFacadeTest {
requiredCapabilities: Set<ModelCapability>,
): InferenceProvider {
return object : InferenceProvider {
override val id = com.correx.core.events.types.ProviderId("mock")
override val id = ProviderId("mock")
override val name = "Mock"
override val tokenizer = com.correx.testing.fixtures.inference.MockTokenizer()
override val tokenizer = MockTokenizer()
override suspend fun infer(request: InferenceRequest): InferenceResponse {
assertEquals(1, capturedContextPacks.size)
assertEquals(capturedContextPacks[0].id, request.contextPack.id)
return InferenceResponse(
requestId = request.requestId,
text = "response",
finishReason = com.correx.core.inference.FinishReason.Stop,
finishReason = FinishReason.Stop,
tokensUsed = TokenUsage(promptTokens = 10, completionTokens = 5),
latencyMs = 0,
)
}
override suspend fun healthCheck(): com.correx.core.inference.ProviderHealth =
com.correx.core.inference.ProviderHealth.Healthy
override suspend fun healthCheck(): ProviderHealth =
ProviderHealth.Healthy
override fun capabilities(): Set<com.correx.core.inference.CapabilityScore> = emptySet()
override fun capabilities(): Set<CapabilityScore> = emptySet()
}
}
},
@@ -456,7 +464,7 @@ class RouterFacadeTest {
)
facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
val req = capturedRequests[0]
assertTrue(req.responseFormat is com.correx.core.inference.ResponseFormat.Text)
assertTrue(req.responseFormat is ResponseFormat.Text)
}
@Test
@@ -512,22 +520,22 @@ class RouterFacadeTest {
private fun mockProvider(responseText: String): InferenceProvider =
object : InferenceProvider {
override val id = com.correx.core.events.types.ProviderId("mock")
override val id = ProviderId("mock")
override val name = "Mock"
override val tokenizer = com.correx.testing.fixtures.inference.MockTokenizer()
override val tokenizer = MockTokenizer()
override suspend fun infer(request: InferenceRequest): InferenceResponse = InferenceResponse(
requestId = request.requestId,
text = responseText,
finishReason = com.correx.core.inference.FinishReason.Stop,
finishReason = FinishReason.Stop,
tokensUsed = TokenUsage(promptTokens = 10, completionTokens = 5),
latencyMs = 0,
)
override suspend fun healthCheck(): com.correx.core.inference.ProviderHealth =
com.correx.core.inference.ProviderHealth.Healthy
override suspend fun healthCheck(): ProviderHealth =
ProviderHealth.Healthy
override fun capabilities(): Set<com.correx.core.inference.CapabilityScore> =
setOf(com.correx.core.inference.CapabilityScore(ModelCapability.General, 1.0))
override fun capabilities(): Set<CapabilityScore> =
setOf(CapabilityScore(ModelCapability.General, 1.0))
}
private fun mockProviderWithCapture(
@@ -535,24 +543,24 @@ class RouterFacadeTest {
requestIds: MutableList<InferenceRequestId>,
): InferenceProvider =
object : InferenceProvider {
override val id = com.correx.core.events.types.ProviderId("mock")
override val id = ProviderId("mock")
override val name = "Mock"
override val tokenizer = com.correx.testing.fixtures.inference.MockTokenizer()
override val tokenizer = MockTokenizer()
override suspend fun infer(request: InferenceRequest): InferenceResponse {
requestIds.add(request.requestId)
return InferenceResponse(
requestId = request.requestId,
text = responseText,
finishReason = com.correx.core.inference.FinishReason.Stop,
finishReason = FinishReason.Stop,
tokensUsed = TokenUsage(promptTokens = 10, completionTokens = 5),
latencyMs = 0,
)
}
override suspend fun healthCheck(): com.correx.core.inference.ProviderHealth =
com.correx.core.inference.ProviderHealth.Healthy
override suspend fun healthCheck(): ProviderHealth =
ProviderHealth.Healthy
override fun capabilities(): Set<com.correx.core.inference.CapabilityScore> = emptySet()
override fun capabilities(): Set<CapabilityScore> = emptySet()
}
private fun mockProviderWithRequestCapture(
@@ -560,24 +568,24 @@ class RouterFacadeTest {
requests: MutableList<InferenceRequest>,
): InferenceProvider =
object : InferenceProvider {
override val id = com.correx.core.events.types.ProviderId("mock")
override val id = ProviderId("mock")
override val name = "Mock"
override val tokenizer = com.correx.testing.fixtures.inference.MockTokenizer()
override val tokenizer = MockTokenizer()
override suspend fun infer(request: InferenceRequest): InferenceResponse {
requests.add(request)
return InferenceResponse(
requestId = request.requestId,
text = responseText,
finishReason = com.correx.core.inference.FinishReason.Stop,
finishReason = FinishReason.Stop,
tokensUsed = TokenUsage(promptTokens = 10, completionTokens = 5),
latencyMs = 0,
)
}
override suspend fun healthCheck(): com.correx.core.inference.ProviderHealth =
com.correx.core.inference.ProviderHealth.Healthy
override suspend fun healthCheck(): ProviderHealth =
ProviderHealth.Healthy
override fun capabilities(): Set<com.correx.core.inference.CapabilityScore> = emptySet()
override fun capabilities(): Set<CapabilityScore> = emptySet()
}
private fun emptyContextPack(): ContextPack = ContextPack(
@@ -591,7 +599,7 @@ class RouterFacadeTest {
private class MapBackedEventStore : EventStore {
val appendedEvents = mutableListOf<NewEvent>()
private val storedEvents = mutableMapOf<com.correx.core.events.types.EventId, StoredEvent>()
private val storedEvents: MutableMap<EventId, StoredEvent> = mutableMapOf()
private var nextSequence = 1L
override suspend fun append(event: NewEvent): StoredEvent {
@@ -620,10 +628,12 @@ class RouterFacadeTest {
override fun lastSequence(sessionId: SessionId): Long? =
read(sessionId).maxOfOrNull { it.sequence }
override fun subscribe(sessionId: SessionId): kotlinx.coroutines.flow.Flow<StoredEvent> =
override fun subscribe(sessionId: SessionId): Flow<StoredEvent> =
throw UnsupportedOperationException("subscribe not implemented for mock")
override fun allEvents(): Sequence<StoredEvent> =
storedEvents.values.asSequence()
override fun allSessionIds(): Set<SessionId> = storedEvents.values.map { it.metadata.sessionId }.toSet()
}
}
@@ -65,7 +65,7 @@ class RouterProjectorTest {
)
val updated = projector.apply(
started,
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 3, workflowId)),
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 3)),
)
assertEquals(WorkflowStatus.COMPLETED, updated.workflowStatus)
assertNull(updated.currentStageId)
@@ -176,7 +176,7 @@ class RouterProjectorTest {
stored(payload = OrchestrationResumedEvent(sessionId, StageId("a"))),
stored(payload = StageCompletedEvent(sessionId, StageId("b"), StageId("t2"))),
stored(payload = StageFailedEvent(sessionId, StageId("b"), StageId("t3"), "error")),
stored(payload = WorkflowCompletedEvent(sessionId, StageId("c"), 3, workflowId)),
stored(payload = WorkflowCompletedEvent(sessionId, StageId("c"), 3)),
)
var projected = state
@@ -206,7 +206,7 @@ class RouterProjectorTest {
state = projector.apply(
state,
stored(payload = WorkflowCompletedEvent(sessionId, StageId("stage-A"), 1, workflowId)),
stored(payload = WorkflowCompletedEvent(sessionId, StageId("stage-A"), 1)),
)
assertEquals(WorkflowStatus.COMPLETED, state.workflowStatus)
assertNull(state.currentStageId)
@@ -58,7 +58,7 @@ class RouterReducerTest {
)
val completed = reducer.reduce(
started,
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 3, workflowId)),
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 3)),
)
assertEquals(WorkflowStatus.COMPLETED, completed.workflowStatus)
assertNull(completed.currentStageId)
@@ -233,7 +233,7 @@ class RouterReducerTest {
assertEquals(StageOutcomeKind.SUCCESS, s2.l2Memory[0].outcome)
val s3 =
reducer.reduce(s2, stored(payload = WorkflowCompletedEvent(sessionId, StageId("stage-A"), 1, workflowId)))
reducer.reduce(s2, stored(payload = WorkflowCompletedEvent(sessionId, StageId("stage-A"), 1)))
assertEquals(WorkflowStatus.COMPLETED, s3.workflowStatus)
assertNull(s3.currentStageId)
@@ -81,7 +81,7 @@ class OrchestrationProjectorTest {
failureReason = "Something went wrong",
),
),
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1, workflowId)),
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1)),
)
val result1 = events.fold(initialState) { state, event ->
@@ -101,7 +101,7 @@ class OrchestrationProjectorTest {
val s2 =
projector.apply(s1, stored(payload = OrchestrationPausedEvent(sessionId, stageId, "approval required")))
val s3 = projector.apply(s2, stored(payload = OrchestrationResumedEvent(sessionId, stageId)))
val s4 = projector.apply(s3, stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1, workflowId)))
val s4 = projector.apply(s3, stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1)))
assertEquals(OrchestrationStatus.RUNNING, s1.status)
assertEquals(OrchestrationStatus.PAUSED, s2.status)
@@ -21,8 +21,8 @@ class OrchestrationReducerTest {
private val reducer = DefaultOrchestrationReducer()
private val sessionId = SessionId("s1")
private val stageId = StageId("stage-1")
private val state = OrchestrationState(stageId)
private val workflowId = "workflow-test"
private val state = OrchestrationState(workflowId, stageId)
@Test
fun `WorkflowStartedEvent sets status to RUNNING`() {
@@ -58,7 +58,7 @@ class OrchestrationReducerTest {
val completed = reducer.reduce(
started,
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1, workflowId)),
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1)),
)
assertTrue(completed.failureReason.isNullOrBlank())
assertEquals(OrchestrationStatus.COMPLETED, completed.status)
@@ -49,7 +49,7 @@ class RouterProjectorTest {
val started =
projector.apply(initialState, stored(payload = WorkflowStartedEvent(sessionId, workflowId, stageId)))
val completed =
projector.apply(started, stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1, workflowId)))
projector.apply(started, stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1)))
assertEquals(WorkflowStatus.COMPLETED, completed.workflowStatus)
assertNull(completed.currentStageId)
}
@@ -134,7 +134,7 @@ class RouterProjectorTest {
stored(payload = StageCompletedEvent(sessionId, StageId("stage-2"), StageId("t1"))),
stored(payload = SteeringNoteAddedEvent(sessionId, "note")),
stored(payload = StageFailedEvent(sessionId, stageId, StageId("t2"), "timeout")),
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 2, workflowId)),
stored(payload = WorkflowCompletedEvent(sessionId, stageId, 2)),
)
val result1 = events.fold(initialState) { state, event ->
@@ -155,7 +155,7 @@ class RouterProjectorTest {
projector.apply(s1, stored(payload = StageCompletedEvent(sessionId, StageId("stage-2"), StageId("t1"))))
val s3 = projector.apply(s2, stored(payload = StageFailedEvent(sessionId, stageId, StageId("t2"), "timeout")))
val s4 = projector.apply(s3, stored(payload = WorkflowStartedEvent(sessionId, workflowId, StageId("stage-3"))))
val s5 = projector.apply(s4, stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1, workflowId)))
val s5 = projector.apply(s4, stored(payload = WorkflowCompletedEvent(sessionId, stageId, 1)))
assertEquals(WorkflowStatus.RUNNING, s1.workflowStatus)
assertEquals(StageId("stage-2"), s2.l2Memory[0].stageId)