Skip to content

fix: thread outcomes through router fallback, widen response cache key - #162

Open
shrdgn wants to merge 1 commit into
mainfrom
claude/vibrant-archimedes-enyylp
Open

fix: thread outcomes through router fallback, widen response cache key#162
shrdgn wants to merge 1 commit into
mainfrom
claude/vibrant-archimedes-enyylp

Conversation

@shrdgn

@shrdgn shrdgn commented Aug 26, 2026

Copy link
Copy Markdown
Owner

What & why

Two small, unrelated correctness fixes found during a scheduled repo review, bundled together since each is a one-line-cause / self-contained change:

1. router.route_async silently disabled the routing learning loop on classifier fallback

router._call_classifier's two fallback paths — a classifier error (text is None) and an unrecognized classifier reply — both called route(body, config) without the outcomes argument, even though route_async itself receives outcomes and the normal (non-model-mode) heuristic path passes it through correctly. Since route_request() (the server's actual entry point) always goes through route_async, any time router.mode: model's classifier errored or replied with neither SOLO nor FUSE, that request's routing decision silently bypassed the EMA-based learning nudge (openfusion.outcomes.OutcomeStore) instead of falling back to the heuristic with the learned nudge still active.

Fix: pass outcomes through on both fallback call sites (openfusion/router.py).

2. responsecache.cache_key omitted self-fusion and debate config fields

cache_key already accounts for strategy, aggregator, panel/judge models, tools, and sampling params, but not config.self_fusion.n / temperature_spread / seed_offset, nor config.debate.rounds. Two server configs differing only in self-fusion sample count/spread or debate round count produce identical cache keys and can serve each other's cached answer, even though they make a different number of upstream calls and synthesize differently — the same class of staleness bug as the already-open #153/#155 (which covers ToolsConfig fields specifically; this covers a different, still-missing set of fields).

Fix: add the four omitted fields to the payload dict in cache_key (openfusion/responsecache.py).

Both fixes include regression tests that fail on main and pass with the fix.

How it was tested

  • ruff check . passes
  • pytest -q passes (493 passed, no live network)
  • New behavior has a test (tests/test_router.py::test_route_async_classifier_error_still_consults_outcomes, ::test_route_async_unrecognized_response_still_consults_outcomes, tests/test_responsecache.py::test_cache_key_differs_on_self_fusion_and_debate_config)
  • Docs updated (CHANGELOG.md Fixed entries)
  • No secrets, prompts, or response bodies added to logs or metrics
  • Quality/cost claims backed by a bench/run.py number — not applicable, no quality/cost behavior change

Notes for reviewers

Neither fix touches request/response shape or config schema — both are pure bug fixes within existing modules (router.py, responsecache.py). No follow-ups needed; this closes both gaps completely as scoped.


Generated by Claude Code

- router.route_async dropped its `outcomes` argument on both classifier
  fallback paths (classifier error, unrecognized reply), silently disabling
  the routing learning loop for any request that hit those paths instead of
  falling back to the heuristic with the learned nudge intact.
- responsecache.cache_key omitted self_fusion (n/temperature_spread/
  seed_offset) and debate.rounds, so two configs differing only in those
  fields could serve each other's cached answer despite running a different
  number of upstream calls.

Found via an automated repo-review scheduled task; verified by reading both
code paths directly and adding regression tests that fail without the fix.
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.

2 participants