/** * Intelligent Model Router — lexical complexity heuristic + Thompson bandit * * Dynamically routes requests to the optimal Claude model (haiku/sonnet/opus) * based on task complexity, uncertainty, and online-learned routing outcomes. * * Mechanism (shipped): * - Complexity score = blend of lexical, semantic-depth, task-scope, and * uncertainty heuristics (see `computeLexicalComplexity` and friends). * Pure JS arithmetic — no model load, no tensor math. * - Model selection = Thompson-sampling Beta-Bernoulli bandit with * complexity-bucketed Beta(α,β) priors, persisted to * `.swarm/model-router-state.json` and updated by `recordOutcome` after * each routing decision. * - Uncertainty quantification + a circuit breaker drive escalation when * the bandit's confidence is low or downstream failures are observed. * * Routing Strategy: * - Haiku: high confidence, low complexity (fast, cheap) * - Sonnet: medium confidence, moderate complexity (balanced) * - Opus: low confidence, high complexity (most capable) * * Note (#2329): An earlier design (ADR-026 + this file's previous header) * described a Tiny-Dancer / FastGRNN neural router with embedding-based * complexity scoring. That path was never wired in directly. * * Note (ADR-148, #2334): The cost-optimal neural router is now wired as an * optional, gated addition via `./neural-router.ts` (which uses * `@metaharness/router`, optionally accelerated by `@ruvector/tiny-dancer`). * It is double-gated on `CLAUDE_FLOW_ROUTER_NEURAL=1` + an embedding being * supplied + a corpus/artifact being loadable. When any gate is closed the * shipped heuristic + bandit path runs unchanged and the result carries * `routedBy: 'heuristic'` (default) or `'bandit-fallback'` (neural enabled * but declined). When all gates are open and a backend resolves, the result * carries `routedBy: 'metaharness-knn' | 'metaharness-krr' | 'fastgrnn'`. * * @module model-router */ /** * Available Claude models for routing */ export type ClaudeModel = 'haiku' | 'sonnet' | 'opus' | 'inherit'; /** * Model capabilities and characteristics */ export declare const MODEL_CAPABILITIES: Record; /** * Complexity indicators for task classification */ export declare const COMPLEXITY_INDICATORS: { high: string[]; medium: string[]; low: string[]; }; /** * Model router configuration */ export interface ModelRouterConfig { /** Confidence threshold for model selection (default: 0.85) */ confidenceThreshold: number; /** Maximum uncertainty before escalating (default: 0.15) */ maxUncertainty: number; /** Enable circuit breaker (default: true) */ enableCircuitBreaker: boolean; /** Failures before circuit opens (default: 5) */ circuitBreakerThreshold: number; /** Path for router state persistence */ statePath: string; /** Auto-save interval in decisions (default: 20) */ autoSaveInterval: number; /** Enable cost optimization (default: true) */ enableCostOptimization: boolean; /** Prefer faster models when confidence is high (default: true) */ preferSpeed: boolean; } /** * Routing decision result */ export interface ModelRoutingResult { /** Selected model */ model: ClaudeModel; /** Confidence in the decision (0-1) */ confidence: number; /** Uncertainty estimate (0-1) */ uncertainty: number; /** Computed complexity score (0-1) */ complexity: number; /** Reasoning for the selection */ reasoning: string; /** Alternative models considered */ alternatives: Array<{ model: ClaudeModel; score: number; }>; /** Inference time in microseconds */ inferenceTimeUs: number; /** Estimated cost multiplier */ costMultiplier: number; /** * Which decision mechanism produced this result (ADR-148 / ADR-074 — * observable, not inferred). Always one of: * 'heuristic' — neural disabled (default). Pure Thompson bandit. * 'bandit-fallback' — neural enabled but returned no decision (load fail). * 'hybrid' — neural enabled AND returned predictions; the * bandit's Beta(α,β) priors were perturbed by the * neural's predicted quality before sampling. */ routedBy: 'hybrid' | 'bandit-fallback' | 'heuristic'; /** * The neural backend that contributed to a `hybrid` decision, if any. * Absent on `heuristic` and `bandit-fallback`. Tracked separately from * `routedBy` so the decision mechanism and the model identity remain * distinct observable surfaces. */ neuralBackend?: 'metaharness-knn' | 'metaharness-krr' | 'fastgrnn'; /** * Execution provider hint (ADR-148 phase 2). 'anthropic' = default * Anthropic API path (MODEL_MAP). 'openrouter' = call through * OpenRouter using `openrouterModel`. Resolved per-call from * `CLAUDE_FLOW_ROUTER_PROVIDER` / `OPENROUTER_API_KEY`. */ provider?: 'anthropic' | 'openrouter'; /** * Concrete OpenRouter model slug for this tier when `provider==='openrouter'`, * loaded from `assets/model-router/openrouter-alts.json` (overridable via * `CLAUDE_FLOW_ROUTER_OPENROUTER_ALTS=`). Downstream consumers can * use this to override the default MODEL_MAP-derived Anthropic slug. */ openrouterModel?: string; /** * ADR-149 — concrete picked model id (e.g. `openai/gpt-4.1`, * `inclusionai/ling-2.6-flash`, `anthropic/claude-sonnet-4-6`). Set when * the neural backend returned a per-model pick. The `model` field above * remains the tier label for back-compat with Anthropic-API consumers * that map tier → MODEL_MAP id. */ modelId?: string; } /** * Complexity analysis result */ export interface ComplexityAnalysis { /** Overall complexity score (0-1) */ score: number; /** Indicators found */ indicators: { high: string[]; medium: string[]; low: string[]; }; /** Feature breakdown */ features: { lexicalComplexity: number; semanticDepth: number; taskScope: number; uncertaintyLevel: number; }; } /** * Beta(α, β) prior for Thompson sampling. Each model carries one of these; * outcomes update α (successes) and β (failures) so the router auto-balances * cost/quality without manual threshold tuning. See ADR-101. */ export interface BetaPrior { alpha: number; beta: number; } /** * Router state for persistence */ /** * Complexity bucket for per-task bandit priors. Bands mirror * MODEL_CAPABILITIES.maxComplexity (haiku 0.4, sonnet 0.7) so the taxonomy * isn't arbitrary. Keying priors by bucket fixes the global-bandit defect where * failures on one task type suppressed a model for ALL task types (audit * docs/reviews/intelligence-system-audit-2026-05-29.md; see ADR-142). */ export type ComplexityBucket = 'low' | 'med' | 'high'; declare function complexityBucket(score: number): ComplexityBucket; type BucketedPriors = Record>; /** * ADR-149 — per-modelId Beta priors, keyed by complexity bucket. Shadow * state that accumulates from recordOutcomeByModelId() so the bandit can * distinguish e.g. `inclusionai/ling-2.6-flash` from * `anthropic/claude-haiku-4-5-20251001` even though both collapse to the * `haiku` tier in BucketedPriors. Selection still uses BucketedPriors; * a future refactor switches to BucketedPriorsById when it has enough * data to be trustworthy. */ type BucketedPriorsById = Record>; /** * Sample θ ~ Beta(α, β) via the identity Beta(α,β) = X / (X+Y) where * X ~ Gamma(α), Y ~ Gamma(β). Returns the mean for degenerate α+β=0 * (shouldn't happen in practice but defensive). */ export declare function sampleBeta(alpha: number, beta: number): number; /** * Intelligent Model Router using complexity-based routing */ export declare class ModelRouter { private config; private state; private decisionCount; private consecutiveFailures; /** * ADR-148 — in-memory counters surfaced via `getStats()` and read by the * `hooks_intelligence_stats` MCP tool. Process-local, not persisted (these * are operational metrics, not authoritative state — see ADR-074/086). */ private routedByCounts; private neuralBackendCounts; private abDisagreements; private abComparisons; constructor(config?: Partial); /** * Route a task to the optimal model. * * When `embedding` is supplied and `CLAUDE_FLOW_ROUTER_NEURAL=1` is set, * the cost-optimal neural backend (ADR-148) is consulted first; its * decision is used when its `metBar` clears the configured quality bar * and `routedBy` reflects which backend produced the decision. Otherwise * the shipped heuristic + Thompson bandit path runs (byte-identical to * the pre-ADR-148 behavior) and the result carries `routedBy: * 'bandit-fallback'` (neural was enabled but declined) or * `'heuristic'` (neural was disabled). */ route(task: string, embedding?: number[]): Promise; /** * Analyze task complexity */ analyzeComplexity(task: string, embedding?: number[]): ComplexityAnalysis; /** * Compute lexical complexity from text features */ private computeLexicalComplexity; /** * Compute semantic depth from indicators and embedding */ private computeSemanticDepth; /** * Compute task scope from content analysis */ private computeTaskScope; /** * Compute uncertainty level from task phrasing */ private computeUncertaintyLevel; /** * Compute scores for each model */ private computeModelScores; /** * Apply circuit breaker adjustments */ private applyCircuitBreaker; /** * Select the best model from scores. Uses Thompson sampling (#1772): * each model's deterministic complexity score is multiplied by a draw * θ_m ~ Beta(α_m, β_m) from its bandit prior. Models with strong empirical * track records get sampled higher; models with poor outcomes get sampled * lower; the system auto-corrects against tier overuse without manual * threshold tuning. Beta(1,1) = uniform on cold start so behavior matches * the prior deterministic router until outcomes accumulate. */ private selectModel; /** * Build human-readable reasoning */ private buildReasoning; /** * Track routing decision for learning */ private trackDecision; /** * Record outcome for learning */ recordOutcome(task: string, model: ClaudeModel, outcome: 'success' | 'failure' | 'escalated'): void; /** * ADR-149 — record an outcome keyed by the CONCRETE model id (e.g. * 'inclusionai/ling-2.6-flash') rather than the tier label. Updates the * shadow `priorsById` state without affecting `priors` (tier priors). * * This is the per-model learning signal the bandit needs to eventually * distinguish GPT-4.1 from Sonnet within the 'sonnet' tier. Selection * currently still uses tier priors; this state accumulates so a future * refactor can switch the selector over once there's enough data. * * Cost-adjusted reward semantics: cheap models get the highest reward on * success (their successes are most cost-efficient). We map modelId to * its closest tier for the reward table — the routing math doesn't have * a per-modelId reward configuration yet. */ recordOutcomeByModelId(task: string, modelId: string, outcome: 'success' | 'failure' | 'escalated'): void; /** * Get router statistics */ getStats(): { totalDecisions: number; modelDistribution: Record; avgComplexity: number; avgConfidence: number; circuitBreakerTrips: number; consecutiveFailures: Record; /** ADR-148: per-decision-mechanism counts (process-local, not persisted). */ routedByCounts: Record; /** ADR-148: per-neural-backend counts (process-local). */ neuralBackendCounts: Record, number>; /** ADR-148: A/B mode disagreement rate over the active process. */ ab: { comparisons: number; disagreements: number; disagreementRate: number; }; /** * ADR-149: state schema version. 2 = bucketed tier priors only; 3 = also * carries `priorsById` shadow state. Bumps on first recordOutcomeByModelId(). */ stateVersion: number; /** * ADR-149: per-modelId Beta priors per complexity bucket. Empty until * recordOutcomeByModelId() fires. NOT consumed by selectModel() yet; this * is shadow state for a future selection-by-modelId refactor. */ priorsById?: BucketedPriorsById; }; /** * Load state from disk */ private loadState; /** * Save state to disk */ private saveState; /** * Reset router state */ reset(): void; /** * Public read-only accessor for the bandit priors. Useful for tests, * dashboards, and the pending hooks_intelligence_stats integration that * surfaces convergence in the dashboard. Returns a copy. */ getBanditPriors(bucket?: ComplexityBucket): Record; /** All bucketed priors (copy) — for dashboards/tests. */ getBucketedPriors(): BucketedPriors; } /** * Get or create the singleton ModelRouter instance */ export declare function getModelRouter(config?: Partial): ModelRouter; /** * Reset the singleton instance */ export declare function resetModelRouter(): void; /** * Create a new ModelRouter instance (non-singleton) */ export declare function createModelRouter(config?: Partial): ModelRouter; /** * Quick route function for common use case */ export declare function routeToModel(task: string): Promise; /** * Route with full result */ export declare function routeToModelFull(task: string, embedding?: number[]): Promise; /** * Analyze task complexity without routing */ export declare function analyzeTaskComplexity(task: string): ComplexityAnalysis; /** * Get model router statistics */ export declare function getModelRouterStats(): ReturnType; /** * Record routing outcome for learning */ export declare function recordModelOutcome(task: string, model: ClaudeModel, outcome: 'success' | 'failure' | 'escalated'): void; /** * ADR-149 — record an outcome keyed by the concrete model id rather than * the tier label. Updates the shadow `priorsById` state. Selection logic * still uses tier priors; this data accumulates for a future per-modelId * selector refactor. * * Safe to call alongside `recordModelOutcome` — they update independent * state slices so double-counting is impossible. */ export declare function recordModelOutcomeByModelId(task: string, modelId: string, outcome: 'success' | 'failure' | 'escalated'): void; /** * ADR-149 iter 14 — read-only access to the per-modelId Beta priors. The * neural-router consumes this to apply per-model Thompson sampling on top * of its predicted-quality vector when CLAUDE_FLOW_ROUTER_BANDIT_PER_MODEL=1. * Returns the legacy bucketed priors (`priorsById[bucket][modelId]`) when * present, else null. */ export declare function getModelRouterPriorsById(): Record> | null; /** * Re-export the complexity-bucket helper so the neural-router (which gets * the task text via the route() call) can map a complexity score to the * matching bandit bucket. */ export { complexityBucket }; //# sourceMappingURL=model-router.d.ts.map