Skip to content

M2: router gate (LLM-judge classifier, fail-open) - #26

Merged
stubbi merged 8 commits into
mainfrom
feat/m2-router-gate
Jun 18, 2026
Merged

stubbi merged 8 commits into
mainfrom
feat/m2-router-gate

Conversation

@stubbi

@stubbi stubbi commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the router gate, the M1 design's primary cost lever: a profile can route easy queries to a single model and fuse only the hard ones. Built TDD against docs/plans/2026-06-18-m2-router-gate.md, each task independently reviewed plus a final whole-branch review.

  • RouterConfig gains policy (always_fuse | classifier), classifier_model, and threshold, with validation (policy known, model required for classifier, threshold in 0.0..=1.0, loop-guard covers the classifier model).
  • ClassifierRouter: a cheap LLM-judge scores query difficulty 0..1; fuse if score >= threshold, else route to the single model. Fails OPEN to fusion on any backend error or unparseable score.
  • Pipeline builds the router per profile; Pipeline::new signature unchanged so chorus-server needed no edits. A chorus_router_decisions_total{profile,decision} metric is emitted for both branches.
  • Docs + example config updated; metrics hoisted to a workspace dependency.

Deferred to the deploy track (needs live inference): threshold calibration on real traffic and the budget-panel sovereignty benchmark. The SearXNG tool adapter remains M2 scope, not in this PR.

Test Plan

  • cargo test --workspace (41 tests: 39 unit + 2 integration)
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo fmt --all -- --check clean
  • cargo deny check ok

🤖 Generated with Claude Code

stubbi and others added 8 commits June 18, 2026 20:47
Signed-off-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
Signed-off-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
Signed-off-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
Signed-off-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
…eline

Signed-off-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
Signed-off-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
Add the metrics facade crate (0.24, matching chorus-server) to
chorus-core and increment a counter labelled by profile name and
decision branch (fuse/single) next to the existing tracing::info! call
in Pipeline::run. The counter is a no-op without a recorder installed,
so existing tests are unaffected.

Signed-off-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
…s, boundary test

- Hoist metrics = "0.24" into [workspace.dependencies]; replace direct pins in
  chorus-core and chorus-server with metrics.workspace = true (DRY).
- Replace silent unwrap_or_else fallback in Pipeline::router_for "classifier" arm
  with an if-let that emits tracing::warn! and returns AlwaysFuse when
  classifier_model is None (config validation normally prevents this, but the
  fallback now fails safe to higher-quality fusion rather than misusing
  single_model as the scorer).
- Add /// doc comments to RouterConfig::classifier_model and ::threshold matching
  the style of nearby AggregatorConfig fields.
- Add score_at_threshold_fuses test in classifier_tests: score == threshold routes
  to Fuse because the comparison is score < threshold (equality fuses).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
@stubbi
stubbi merged commit 3670ffe into main Jun 18, 2026
3 checks passed
@stubbi
stubbi deleted the feat/m2-router-gate branch June 18, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant