| concept | CognitiveRelay | |||||
|---|---|---|---|---|---|---|
| status | stable | |||||
| tracked_sources |
|
|||||
| related |
|
|||||
| last_verified | 2026-04-29 |
CognitiveRelay is a provider-agnostic LLM router that sits between
AgentLLMService and the actual model call. It evaluates declarative
RoutingRules in order against a RoutingContext (cognitive phase,
agent identity, task hints) and resolves an AIConfiguration to use.
The first matching rule wins; if no rule matches, the agent's default
configuration is used as fallback. Every routing decision emits a
RoutingEvent.
The cognitive layer must not import provider SDKs directly. Three consequences fall out of that single rule:
- Multi-provider portability. The same agent code runs against
Anthropic, OpenAI, Google, and any future provider, because the choice
of provider is data (
AIConfiguration), not imports. - Phase-aware model selection. The PROPEL loop has phases with very
different latency / quality / cost profiles. Routing rules can use
CognitivePhasein the context to sendPERCEIVEto a cheap fast model andPLANto a more capable one — without the agent code knowing or caring. - Observable, replayable decisions. Every routing decision is an
event.
ArcTraceProjectionreadsroutingReasonfrom the matchedProviderCallStartedEventto show in the trace why a particular model was chosen for a particular phase. Bypassing the relay means bypassing this story, and the trace shows a model call with no explanation.
The relay also decouples agent code from configuration churn: switching
the PERCEIVE phase to a different model is a config change, not a code
change.
agents/domain/routing/CognitiveRelay.kt— the interface;resolveandresolveWithMetadata.agents/domain/routing/CognitiveRelayImpl.kt— production implementation that matches rules.agents/domain/routing/CognitiveRelayPassthrough.kt— test/dev helper that always returns the fallback.agents/domain/routing/RelayConfig.kt— the rule set.agents/domain/routing/RoutingRule.kt— predicate + target configuration.agents/domain/routing/RoutingContext.kt— what rules match against (CognitivePhase,agentId, hints).agents/domain/routing/RoutingDecision.kt— the event payload.agents/domain/reasoning/AgentLLMService.kt— the only legitimate caller.agents/domain/event/RoutingEvent.kt,ProviderCallStartedEvent.kt,ProviderCallCompletedEvent.kt— observability events.
- Cognition code never imports a provider SDK. Anthropic, OpenAI, Google clients live below
domain/ai/and are addressed exclusively viaAIConfiguration. Aimport com.anthropic(or equivalent) anywhere underagents/domain/reasoning/oragents/domain/cognition/is a violation. - All LLM calls go through the relay.
AgentLLMServiceis the single entry point. Direct construction of anAIConfigurationand a model client in domain code bypasses routing, observability, and rule precedence. - Routing rules are evaluated in declared order; first match wins. Reordering rules changes routing behaviour. The relay is intentionally not "best match" — it is "first match", because predictability beats cleverness.
- Every resolved call emits
ProviderCallStartedEventandProviderCallCompletedEvent. WithcognitivePhase,providerId,modelId, androutingReasonpopulated.ArcTraceProjectionjoins these to buildModelInvocationTrace; missing events produce gaps in the trace. - Hot-swap goes through
updateConfig. Mutating aRelayConfigfield in place is not supported.updateConfigexists so changes can be observed by long-running reasoning sessions. - The fallback is not optional. A relay that can return null on no-match would make every
AgentLLMServicecaller responsible for a fallback decision, defeating the point. Every call must produce someAIConfiguration.
- Add a routing rule — extend
RelayConfig.ruleswith a newRoutingRulewhose predicate examinesRoutingContext. Order matters; more specific rules earlier. - Route by phase —
RoutingContext.phaseis aCognitivePhase; rules can switch on it directly. - Inspect a routing decision in tests — call
resolveWithMetadatainstead ofresolve. The returnedRoutingResolution.reasonis a free-form tag describing which rule matched ("phase=PLAN" / "default fallback"). - Trace which model handled a phase —
ArcTraceProjection.project(runId)returnsModelInvocationTraces keyed by phase, withroutingReasonpopulated from the routing event.
- Constructing an
AIConfigurationin domain code "just for this one call". That call won't appear in the trace with any routing reason, and the next person who needs to change the model has to find every such call. - Encoding routing in
if/elseinsideAgentLLMService. The relay exists because this gets unmanageable. Predicates belong inRoutingRules where they're inspectable, ordered, and observable. - Importing a provider SDK from cognition code. Even "temporarily" or "for a quick test" — the import lingers, and now
agents/domain/...is provider-coupled. - Treating
routingReasonas an internal detail. It is read by the trace and displayed to humans debugging cognitive runs. A blank or unhelpful reason ("matched") is a regression in observability. - Mutating
RelayConfigfields directly to "hot-update" rules. TheupdateConfigpath emits the changes; direct mutation is invisible to subscribers and to the trace.