Prerequisites
Area
API / Backend
Problem or Motivation
After foreground Chat and Agent routing becomes strict by default, the remaining fallback behavior needs one explicit policy boundary. Current code spreads fallback-list resolution, tuple construction, stream eligibility, event forwarding, and model provenance across route, resolver, LLM, and Agent-loop modules.
That scattered shape makes several contracts unclear:
- which per-user setting enables fallback;
- which provider failures may switch models;
- whether request/auth/configuration errors can escape to another provider;
- which model owns later Agent tool rounds after a successful failover;
- which model should be recorded in round events and final metrics.
The stored default_model_fallbacks list is legacy data. It must remain persisted for compatibility and rollback reference, but no replacement policy may read or migrate it automatically.
Proposed Solution
Introduce one foreground routing policy and candidate representation shared by streaming Chat, non-stream POST /api/chat, and Agent mode.
The refactor should:
- add new per-user settings for an explicit opt-in flag and a new ordered concrete candidate list;
- default the policy to disabled;
- resolve candidates through one owner-scoped service rather than assembling raw tuples in route code;
- classify only availability failures as fallback-eligible, including connection/read timeouts, rate limiting, and explicitly selected upstream 5xx responses;
- keep 400, 401, 403, malformed request, unsupported model, and configuration errors on the selected model and surface them directly;
- pin the first fallback that produces substantive output or a completed tool call for the remainder of that Agent run;
- emit accurate selected-model, active-model, reason, and per-round provenance;
- remove confirmed-dead foreground fallback helpers or event handlers after usage checks;
- retain legacy stored settings without runtime readers or migration.
Parent Tracker
Part of #5625.
Depends On
Scope
In scope:
- foreground streaming Chat, non-stream
POST /api/chat, and Agent routing only;
- new backend per-user fallback policy keys;
- owner-scoped concrete fallback candidate resolution;
- eligible-error classification;
- Agent-run stickiness after fallback;
- round/final model provenance;
- removal of confirmed-dead foreground fallback paths;
- focused unit, route, and multi-round regressions.
Out of scope:
- Settings UI;
- Utility, Vision, Deep Research, scheduled tasks, teacher, image, embedding, or search-provider routing;
- reading or migrating
default_model_fallbacks;
- automatic model tiers;
- provider-specific retry/backoff changes unrelated to fallback choice.
Acceptance Criteria
- A single service owns foreground fallback enablement and ordered candidate resolution.
- Streaming Chat and non-stream
POST /api/chat apply the same owner-scoped, availability-only fallback policy and report the actual route.
- The policy is disabled when the new per-user setting is absent or false.
- Existing
default_model_fallbacks values are never read by foreground routing.
- Only documented availability failures advance to another model.
- 400, 401, 403, malformed-request, unsupported-model, and local configuration failures never switch providers.
- Once fallback output or a tool call is accepted, every later Agent round uses that same model.
- Fallback events and final metrics identify the actual model for each round without stale A-to-B-to-A labels.
- Candidate resolution enforces endpoint ownership and excludes disabled or missing endpoints/models.
- Confirmed-dead legacy helper/handler code is removed without deleting stored legacy values.
- Focused tests cover strict mode, explicit mode, eligible and ineligible errors, per-user isolation, and multi-round stickiness.
Alternatives Considered
- Reuse
default_model_fallbacks behind the new toggle. Rejected because legacy data must not silently become the replacement policy.
- Keep raw tuple assembly in each caller. Rejected because eligibility, ownership, and provenance would continue to drift.
- Retry the selected model at the beginning of every Agent round. Rejected after a successful failover because it can silently alternate providers inside one run.
- Treat every pre-output error as eligible. Rejected because authentication, authorization, request, and configuration failures should be fixed explicitly.
Prior Art / Related Issues
Are you willing to implement this?
Yes — this will be the second branch in the stacked series.
Prerequisites
Area
API / Backend
Problem or Motivation
After foreground Chat and Agent routing becomes strict by default, the remaining fallback behavior needs one explicit policy boundary. Current code spreads fallback-list resolution, tuple construction, stream eligibility, event forwarding, and model provenance across route, resolver, LLM, and Agent-loop modules.
That scattered shape makes several contracts unclear:
The stored
default_model_fallbackslist is legacy data. It must remain persisted for compatibility and rollback reference, but no replacement policy may read or migrate it automatically.Proposed Solution
Introduce one foreground routing policy and candidate representation shared by streaming Chat, non-stream
POST /api/chat, and Agent mode.The refactor should:
Parent Tracker
Part of #5625.
Depends On
Scope
In scope:
POST /api/chat, and Agent routing only;Out of scope:
default_model_fallbacks;Acceptance Criteria
POST /api/chatapply the same owner-scoped, availability-only fallback policy and report the actual route.default_model_fallbacksvalues are never read by foreground routing.Alternatives Considered
default_model_fallbacksbehind the new toggle. Rejected because legacy data must not silently become the replacement policy.Prior Art / Related Issues
Are you willing to implement this?
Yes — this will be the second branch in the stacked series.