= 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>`.

=== 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?
