= core-router == purpose Provides a chat/steering interface for users to interact with a running workflow. Maintains its own event-sourced state (workflow status, L2 stage memory, conversation history) and builds a tailored context pack for routing inferences that decide the next workflow action. == responsibilities * Accepting user input via chat or steering mode * Maintaining conversation history per session (in-memory, scoped to facade lifecycle) * Tracking workflow lifecycle (idle, running, paused, completed, failed) from orchestration events * Recording L2 stage memory entries (summaries of completed/failed stages) * Building a `ContextPack` for routing decisions that includes system prompt, workflow status, conversation turns, and stage summaries * Routing user inputs as steering notes when in STEERING mode (emitting `SteeringNoteAddedEvent`) * Dispatching inference requests to a provider for router responses == non-responsibilities * Does not orchestrate workflows — only tracks state and provides an interface * Does not produce L2 summaries — they are generated from event metadata * Does not manage the full context assembly pipeline — only builds the router-specific subset * Does not persist conversation history — it is held in-memory in the facade == key types === RouterFacade * **kind**: interface * **purpose**: Entry point for user input. Returns a `RouterResponse`. === DefaultRouterFacade * **kind**: class * **purpose**: Coordinates repository, context builder, inference router, and event store to process user input. Supports `CHAT` and `STEERING` modes. === RouterRepository * **kind**: interface * **purpose**: Rebuilds `RouterState` for a session. === DefaultRouterRepository * **kind**: class * **purpose**: Rebuilds `RouterState` via `EventReplayer`. === RouterContextBuilder * **kind**: interface * **purpose**: Builds a router-specific `ContextPack` from `RouterState` and a token budget. === DefaultRouterContextBuilder * **kind**: class * **purpose**: Assembles L0 entries (system prompt, workflow status) and L1 entries (conversation history, stage summaries). Uses token budget with oldest-first eviction. === RouterReducer / DefaultRouterReducer * **kind**: interface / class * **purpose**: Transforms `RouterState` given stored events. Handles `WorkflowStartedEvent`, `WorkflowCompletedEvent`, `WorkflowFailedEvent`, `OrchestrationPausedEvent`, `OrchestrationResumedEvent`, `StageCompletedEvent`, `StageFailedEvent`. === RouterProjector * **kind**: class * **purpose**: Wraps `RouterReducer` as a `Projection`. === RouterState * **kind**: data class * **purpose**: Event-sourced state for the router. * **fields**: `sessionId`, `workflowStatus`, `currentStageId`, `l2Memory` (list of `RouterL2Entry`), `conversationHistory` (list of `RouterTurn`) === RouterConfig * **kind**: data class * **purpose**: Configuration for the router. * **fields**: `conversationKeepLast` (default 6), `tokenBudget` (default 4096) === RouterResponse * **kind**: data class * **purpose**: Response from the router to the user. * **fields**: `content`, `steeringEmitted` === WorkflowStatus * **kind**: enum * **variants**: `IDLE`, `RUNNING`, `PAUSED`, `COMPLETED`, `FAILED` === StageOutcomeKind * **kind**: enum * **variants**: `SUCCESS`, `FAILURE`, `CANCELLED` === RouterL2Entry * **kind**: data class * **purpose**: A summary of a completed/failed stage for L2 memory. * **fields**: `stageId`, `summary`, `outcome`, `timestamp` === RouterTurn * **kind**: data class * **purpose**: A single turn in the conversation history. * **fields**: `role` (TurnRole), `content`, `timestamp` === TurnRole * **kind**: enum * **variants**: `USER`, `ROUTER` === ChatMode * **kind**: enum * **variants**: `CHAT`, `STEERING` == event flow *inbound:* * `WorkflowStartedEvent` → sets session ID, status to `RUNNING`, current stage * `WorkflowCompletedEvent` → sets status to `COMPLETED`, clears current stage * `WorkflowFailedEvent` → sets status to `FAILED`, clears current stage * `OrchestrationPausedEvent` → sets status to `PAUSED` * `OrchestrationResumedEvent` → sets status to `RUNNING` * `StageCompletedEvent` → appends L2 entry with outcome SUCCESS * `StageFailedEvent` → appends L2 entry with outcome FAILURE, clears current stage *outbound:* * `SteeringNoteAddedEvent` → emitted by `DefaultRouterFacade` when mode is `STEERING` and user submits input == integration points * `:core:events` — `StoredEvent`, `SteeringNoteAddedEvent`, orchestration lifecycle event types, `EventStore`, `EventMetadata`, `EventId`, `SessionId`, `StageId` * `:core:context` — `ContextPack`, `ContextEntry`, `ContextLayer`, `CompressionMetadata`, `TokenBudget`, `ContextEntryId`, `ContextPackId` * `:core:inference` — `InferenceRouter`, `InferenceRequest`, `InferenceProvider`, `GenerationConfig`, `ModelCapability`, `ResponseFormat` * `:core:sessions` — `Projection`, `EventReplayer` == invariants * Conversation history is held in-memory in `DefaultRouterFacade` — it is not rebuilt from events. On restart, conversation history is empty. * `RouterTurn` is separate from the event-sourced conversation log — the reducer does not track individual turns, only L2 stage entries. * L2 entries are created by the reducer from `StageCompletedEvent` and `StageFailedEvent` metadata, not from actual LLM-generated summaries. * `RouterContextBuilder.L0` entries (system prompt, workflow status) are never dropped. L1 and L2 entries are evicted oldest-first under budget pressure. == PlantUML diagram [plantuml, core-router, "png"] ---- include::../../diagrams/core-router.puml[] ---- == known issues * Conversation history is held in a `ConcurrentHashMap` in `DefaultRouterFacade` and is not event-sourced. This means router conversation context is lost on process restart. The reducer tracks event-derived L2 entries but not individual conversation turns. == open questions * Should conversation turns be event-sourced so the full conversation survives restarts? * The `RouterTurn` timestamp is generated by `Clock.System.now()` on the facade side — should it be derived from event metadata instead?