Files
correx/docs/specs/2026-05-20-router/exec/task-013.md
T
kami 2c459da009 feat(router): implement Epic 14 — core:router module
Implements the full conversational router facade: RouterState, RouterReducer,
RouterProjector, RouterRepository, RouterContextBuilder, RouterFacade, protocol
types, WebSocket wiring, infrastructure factory, and deterministic test suite.

Also fixes spec divergences found in post-implementation review:
- Add SteeringNote domain object to core:context (epic prerequisite)
- Rename RouterFacade.handleChat → onUserInput per spec interface contract
- Add in-memory ConcurrentHashMap conversation history to DefaultRouterFacade
- Make RouterRepository.getRouterState suspend
- Rename RouterConfig.keepLast → conversationKeepLast, fix defaults (6, 4096)
- Refactor InfrastructureModule.createRouterFacade to self-assemble internally
- Fix FileReadTool: allowedPaths was dead constructor param (@SuppressUnusedParameter);
  now stored as private val and enforced in validateRequest
- Disable koverVerify on modules tested via testing/ submodules or with
  hardware/integration dependencies (24 modules); build gate now passes clean
2026-05-21 15:06:20 +04:00

4.3 KiB

task-013

status: done

goal

Implement RouterFacade — the orchestrating entry point that loads state via RouterRepository, builds context via RouterContextBuilder, routes inference via InferenceRouter, and conditionally emits SteeringNoteAddedEvent via EventStore.

target artifact

core/router/src/main/kotlin/com/correx/core/router/RouterFacade.kt

execution steps

  1. Create RouterFacade interface in RouterFacade.kt at core/router/src/main/kotlin/com/correx/core/router/RouterFacade.kt

    • Interface signature: suspend fun handleChat(sessionId: SessionId, text: String, mode: ChatMode = ChatMode.CHAT, stageId: StageId? = null, requiredCapabilities: Set<ModelCapability> = setOf(ModelCapability.General)): RouterResponse
    • Package: com.correx.core.router
    • Imports: SessionId, StageId from com.correx.core.events.types; ChatMode enum (CHAT, STEERING) declared locally in this file; ModelCapability from com.correx.core.inference; RouterResponse from com.correx.core.router.model
  2. Create DefaultRouterFacade class implementing RouterFacade

    • Constructor injects: RouterRepository, RouterContextBuilder, InferenceRouter, EventStore, RouterConfig
    • All constructor params are interfaces or data classes — never instantiate concrete dependencies inside the class
    • handleChat() implementation: a. Load RouterState from routerRepository.getRouterState(sessionId) b. Determine effectiveStageId: use provided stageId parameter, or fall back to state.currentStageId, or create StageId("none") as final default c. Build ContextPack via contextBuilder.build(state, config.tokenBudget) d. Route provider via inferenceRouter.route(effectiveStageId, requiredCapabilities) e. Call provider.infer(InferenceRequest(...)) with:
      • requestId = new InferenceRequestId(java.util.UUID.randomUUID().toString())
      • sessionId = from parameter
      • stageId = effectiveStageId
      • contextPack = built ContextPack
      • generationConfig = GenerationConfig(temperature = 0.7, topP = 0.9, maxTokens = 512)
      • responseFormat = ResponseFormat.Text f. Extract text from InferenceResponse g. If mode == ChatMode.STEERING: create NewEvent with SteeringNoteAddedEvent(sessionId = sessionId, content = text, stageId = stageId) and EventMetadata (eventId, sessionId, timestamp = Clock.System.now(), schemaVersion = 1, causationId = null, correlationId = null); call eventStore.append(newEvent) h. Return RouterResponse(content = text, steeringEmitted = (mode == ChatMode.STEERING))
  3. Add ChatMode enum in the same file

    • enum class ChatMode { CHAT, STEERING }
  4. Verify no raw event payloads, artifacts, or tool outputs leak into the context pack — the RouterFacade only delegates to RouterContextBuilder for context assembly

  5. Verify compilation with ./gradlew :core:router:compileKotlin

changed artifacts

  • core/router/src/main/kotlin/com/correx/core/router/RouterFacade.kt — new file (interface + implementation + ChatMode enum)

blockers

  • None — task-011 (RouterRepository) and task-012 (RouterContextBuilder) are complete and the artifact files exist

notes

  • The RouterFacade does NOT implement protocol types, WebSocket handlers, or infrastructure wiring — those are separate tasks (014-018)
  • ChatMode is declared locally in this file because it is a router-specific concept (not shared with the server protocol layer, which will define its own types in task-014)
  • The GenerationConfig defaults (temperature=0.7, topP=0.9, maxTokens=512) are reasonable router defaults — infrastructure wiring can override if needed
  • The requiredCapabilities defaults to General — this is a reasonable default for a routing assistant
  • The SteeringNoteAddedEvent content is the inference response text, not the original user input — this allows the steering note to carry the router's synthesized guidance
  • Exception handling: InferenceRouter.route() and InferenceProvider.infer() may throw; these propagate up to the caller (WebSocket handler in task-016)
  • The EventDispatcher class from :core:events is available but RouterFacade writes events directly to EventStore.append() to maintain clear ownership boundaries