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
@@ -16,16 +16,22 @@ class DefaultOrchestrationReducer : OrchestrationReducer {
event: StoredEvent,
): OrchestrationState = when (val p = event.payload) {
is WorkflowStartedEvent -> state.copy(
workflowId = p.workflowId,
status = OrchestrationStatus.RUNNING,
currentStageId = p.startStageId,
retryPolicy = p.retryPolicy,
)
is WorkflowCompletedEvent -> state.copy(
status = OrchestrationStatus.COMPLETED,
currentStageId = p.terminalStageId,
)
is WorkflowFailedEvent -> state.copy(status = OrchestrationStatus.FAILED, failureReason = p.reason)
is WorkflowFailedEvent -> state.copy(
status = OrchestrationStatus.FAILED,
failureReason = p.reason,
)
is OrchestrationPausedEvent -> state.copy(
status = OrchestrationStatus.PAUSED,
pendingApproval = true,
@@ -94,7 +94,7 @@ class DefaultSessionOrchestrator(
}
is TransitionDecision.Stay -> if (isTerminal(enriched.graph, enriched.currentStageId)) {
completeWorkflow(enriched.sessionId, enriched.currentStageId, enriched.stageCount, enriched.graph.id)
completeWorkflow(enriched.sessionId, enriched.currentStageId, enriched.stageCount)
} else {
failWorkflow(
sessionId = enriched.sessionId,
@@ -140,7 +140,7 @@ class DefaultSessionOrchestrator(
if (isTerminal(ctx.graph, nextStageId)) {
// Terminal stage is a sentinel — not executed, so don't count it.
return StepResult.Terminal(
completeWorkflow(ctx.sessionId, nextStageId, ctx.stageCount, ctx.graph.id),
completeWorkflow(ctx.sessionId, nextStageId, ctx.stageCount),
)
}
@@ -1,5 +1,6 @@
package com.correx.core.kernel.orchestration
import com.correx.core.approvals.DefaultApprovalRepository
import com.correx.core.artifacts.repository.ArtifactRepository
import com.correx.core.events.stores.EventStore
import com.correx.core.inference.InferenceRepository
@@ -11,4 +12,5 @@ data class OrchestratorRepositories(
val orchestrationRepository: OrchestrationRepository,
val sessionRepository: DefaultSessionRepository,
val artifactRepository: ArtifactRepository,
val approvalRepository: DefaultApprovalRepository,
)
@@ -87,7 +87,7 @@ class ReplayOrchestrator(
val nextStageId = decision.to
if (isTerminal(ctx.graph, nextStageId)) {
return completeWorkflow(ctx.sessionId, nextStageId, ctx.stageCount, ctx.graph.id)
return completeWorkflow(ctx.sessionId, nextStageId, ctx.stageCount)
}
when (val result = enterStage(ctx, nextStageId, session)) {
@@ -101,7 +101,7 @@ class ReplayOrchestrator(
}
is TransitionDecision.Stay -> if (isTerminal(ctx.graph, ctx.currentStageId)) {
completeWorkflow(ctx.sessionId, ctx.currentStageId, ctx.stageCount, ctx.graph.id)
completeWorkflow(ctx.sessionId, ctx.currentStageId, ctx.stageCount)
} else {
step(ctx)
}
@@ -112,7 +112,6 @@ abstract class SessionOrchestrator(
private val toolExecutor: ToolExecutor? = engines.toolExecutor
private val toolRegistry: ToolRegistry? = engines.toolRegistry
private val inferenceRepository: InferenceRepository = repositories.inferenceRepository
private val artifactRepository = repositories.artifactRepository
internal val orchestrationRepository: OrchestrationRepository = repositories.orchestrationRepository
internal abstract val cancellations: ConcurrentHashMap<SessionId, AtomicBoolean>
internal val pendingApprovals: ConcurrentHashMap<ApprovalRequestId, CompletableDeferred<ApprovalDecision>> =
@@ -145,7 +144,7 @@ abstract class SessionOrchestrator(
.onFailure {
log.error(
"[SessionOrchestrator] stage=${stageId.value}: " +
"failed to load prompt '$path': ${it.message}",
"failed to load prompt '$path': ${it.message}",
)
}
.getOrNull()
@@ -168,7 +167,7 @@ abstract class SessionOrchestrator(
.onFailure {
log.error(
"[SessionOrchestrator] stage=${stageId.value}: " +
"failed to load default system prompt '$path': ${it.message}",
"failed to load default system prompt '$path': ${it.message}",
)
}
.getOrNull()
@@ -192,7 +191,7 @@ abstract class SessionOrchestrator(
.onFailure {
log.error(
"[SessionOrchestrator] stage=${stageId.value}: " +
"failed to load prompt '$path': ${it.message}",
"failed to load prompt '$path': ${it.message}",
)
}
.getOrNull()
@@ -214,7 +213,7 @@ abstract class SessionOrchestrator(
val llmEmittedSlots = stageConfig.produces.filter { it.kind.llmEmitted }
require(llmEmittedSlots.size <= 1) {
"Stage '${stageId.value}' declares ${llmEmittedSlots.size} LLM-emitted artifact kinds; " +
"only 0 or 1 is supported"
"only 0 or 1 is supported"
}
val responseFormat = llmEmittedSlots.firstOrNull()
?.let { ResponseFormat.Json(it.kind.deriveJsonSchema()) }
@@ -269,6 +268,7 @@ abstract class SessionOrchestrator(
emitToolArtifacts(sessionId, stageId, stageConfig)
verifyProduces(sessionId, stageId, stageConfig)
}
is StageExecutionResult.Failure -> outcome
}
}
@@ -321,6 +321,8 @@ abstract class SessionOrchestrator(
validationReportId = ValidationReportId(UUID.randomUUID().toString()),
riskSummaryId = null,
timestamp = Clock.System.now(),
toolName = toolCall.function.name,
preview = toolCall.function.arguments.take(200),
)
emit(sessionId, OrchestrationPausedEvent(sessionId, stageId, "APPROVAL_PENDING"))
emit(
@@ -333,6 +335,8 @@ abstract class SessionOrchestrator(
sessionId = sessionId,
stageId = stageId,
projectId = null,
toolName = toolCall.function.name,
preview = toolCall.function.arguments.take(200),
),
)
val deferred = CompletableDeferred<ApprovalDecision>()
@@ -399,8 +403,8 @@ abstract class SessionOrchestrator(
if (responseFormat !is ResponseFormat.Json) return emptyList()
val compactSchema = Json.encodeToString(JsonSchema.serializer(), responseFormat.schema)
val instruction = "Respond with a single JSON object matching this schema. " +
"Do not include markdown, code fences, or commentary outside the JSON. " +
"Schema: $compactSchema"
"Do not include markdown, code fences, or commentary outside the JSON. " +
"Schema: $compactSchema"
return listOf(
ContextEntry(
id = ContextEntryId(UUID.randomUUID().toString()),
@@ -442,7 +446,7 @@ abstract class SessionOrchestrator(
val missingIds = missing.joinToString(", ") { it.value }
log.error(
"[Orchestrator] stage missing produced artifacts " +
"session=${sessionId.value} stage=${stageId.value} missing=$missingIds",
"session=${sessionId.value} stage=${stageId.value} missing=$missingIds",
)
return StageExecutionResult.Failure(
"stage ${stageId.value} did not produce declared artifacts: $missingIds",
@@ -598,13 +602,12 @@ abstract class SessionOrchestrator(
sessionId: SessionId,
terminalStageId: StageId,
stageCount: Int,
workflowId: String,
): WorkflowResult.Completed {
log.debug(
"[Orchestrator] COMPLETED session={} terminalStage={} stages={}",
sessionId.value, terminalStageId.value, stageCount,
)
emit(sessionId, WorkflowCompletedEvent(sessionId, terminalStageId, stageCount, workflowId))
emit(sessionId, WorkflowCompletedEvent(sessionId, terminalStageId, stageCount))
cancellations.remove(sessionId)
return WorkflowResult.Completed(sessionId, terminalStageId)
}
@@ -642,6 +645,7 @@ abstract class SessionOrchestrator(
// --- event emission ---
internal suspend fun emit(sessionId: SessionId, payload: EventPayload) {
log.debug("[session {}] emitting event, payload: {}", sessionId.value.substring(0, 7), payload)
eventStore.append(
NewEvent(
metadata = EventMetadata(