refactor(model-routing): centralize explicit foreground fallback policy (#6020)

* refactor(model-routing): centralize explicit foreground fallback policy

Make foreground fallback an explicit per-user, availability-only policy shared by streaming Chat, non-stream Chat, and Agent runs.

Preserve strict defaults, owner/model and credential boundaries, pinned Agent routes, and truthful per-round provenance/accounting. Carry provider-reported model identifiers through native streaming adapters, non-stream responses, and caches, and keep legacy default_model_fallbacks as tombstoned raw storage that generic settings APIs and agent tools cannot expose or mutate.

* fix(agent-loop): restore rebase-dropped qwen routing, workspace prompt, and temperature clamp

* fix(model-routing): thread selected endpoint identity, fix cost classification and fallback eligibility

* fix(chat): restore stream helpers and harden run stop lifecycle

* fix(model-routing): let numeric provider codes win over symbolic rate-limit statuses

* fix(agent-loop): apply qwen temperature and notes-tool clamps per fallback candidate

* fix(chat): honor queued stop across resend and reload canonical terminal on EOF

* fix(chat): track stop queue and cleanup ownership by per-send generation

* fix(agent-loop): preserve requested temperature for non-qwen fallback candidates

* fix(chat): reserve send ownership before any await and scope stop to the current send

* fix(chat): clear the previous run identity at send reservation

---------

Co-authored-by: RaresKeY <158580472+RaresKeY@users.noreply.github.com>
Co-authored-by: StressTestor <212606152+StressTestor@users.noreply.github.com>
This commit is contained in:
Alexandre Teixeira
2026-08-14 08:10:30 +01:00
committed by GitHub
co-authored by RaresKeY StressTestor
parent b52296471b
commit c4369305f0
46 changed files with 11418 additions and 1006 deletions
+174 -1
View File
@@ -81,6 +81,13 @@ def _make_stream_with_save(sink, chunks, *, hang_after=None):
return gen()
async def _collect_subscription(session_id, expected_run=None):
return [
event
async for event in agent_runs.subscribe(session_id, expected_run)
]
# --------------------------------------------------------------------------- #
# agent_runs: detached-run semantics (what NORMAL chat/agent streams use)
# --------------------------------------------------------------------------- #
@@ -136,7 +143,7 @@ async def test_stop_cancels_detached_run_and_saves_partial_exactly_once():
break
await sub.aclose()
stopped = agent_runs.stop(session_id)
stopped = agent_runs.stop(session_id, run.run_id)
assert stopped is True
await run.task # propagates promptly — not stuck on the hung await
@@ -165,6 +172,172 @@ async def test_normal_completion_saves_exactly_once_not_partial():
assert sink.saves == []
@pytest.mark.asyncio
async def test_detached_run_identity_is_stable_for_replay_and_unique_per_run():
session_id = "sess-detached-run-identity"
agent_runs._RUNS.pop(session_id, None)
first = agent_runs.start(session_id, _make_stream_with_save(_FakeSaveSink(), ["one"]))
first_id = first.run_id
assert agent_runs.get_run_id(session_id) == first_id
await first.task
assert agent_runs.get_run_id(session_id) == first_id
second = agent_runs.start(session_id, _make_stream_with_save(_FakeSaveSink(), ["two"]))
assert second.run_id != first_id
assert agent_runs.get_run_id(session_id) == second.run_id
await second.task
@pytest.mark.asyncio
async def test_lazy_subscription_stays_bound_to_header_run_after_replacement():
session_id = "sess-detached-lazy-subscription"
agent_runs._RUNS.pop(session_id, None)
async def stream(label):
yield f'data: {{"delta":"{label}"}}\n\n'
first = agent_runs.start(session_id, stream("first"))
await first.task
# StreamingResponse does not iterate its body until after construction.
# Capture the same exact run object used for its identity header.
lazy_body = agent_runs.subscribe(session_id, first)
second = agent_runs.start(session_id, stream("second"))
await second.task
replayed = [event async for event in lazy_body]
assert replayed == ['data: {"delta":"first"}\n\n']
assert agent_runs.get_run_id(session_id) == second.run_id
@pytest.mark.asyncio
async def test_stale_run_identity_cannot_stop_replacement_run():
session_id = "sess-detached-stale-stop"
agent_runs._RUNS.pop(session_id, None)
release = asyncio.Event()
async def finished():
yield 'data: {"delta":"old"}\n\n'
async def replacement():
yield 'data: {"delta":"new"}\n\n'
await release.wait()
first = agent_runs.start(session_id, finished())
await first.task
second = agent_runs.start(session_id, replacement())
await asyncio.sleep(0)
assert agent_runs.stop(session_id) is False
assert agent_runs.stop(session_id, first.run_id) is False
assert second.task is not None and not second.task.done()
assert agent_runs.stop(session_id, second.run_id) is True
await second.task
@pytest.mark.asyncio
async def test_triple_replacement_closes_middle_subscriber_and_preserves_save_order():
session_id = "sess-detached-triple-replacement"
agent_runs._RUNS.pop(session_id, None)
first_closing = asyncio.Event()
release_first = asyncio.Event()
third_started = asyncio.Event()
async def first_stream():
try:
yield 'data: {"delta":"first"}\n\n'
await asyncio.Event().wait()
finally:
first_closing.set()
await release_first.wait()
async def middle_stream():
yield 'data: {"delta":"middle"}\n\n'
async def third_stream():
third_started.set()
yield 'data: {"delta":"third"}\n\n'
first = agent_runs.start(session_id, first_stream())
while not first.buffer:
await asyncio.sleep(0)
middle = agent_runs.start(session_id, middle_stream())
await first_closing.wait()
assert middle.task is not None and not middle.task.done()
middle_events_task = asyncio.create_task(
_collect_subscription(session_id, middle)
)
while not middle.subscribers:
await asyncio.sleep(0)
third = agent_runs.start(session_id, third_stream())
# The superseded middle response closes immediately even though its task
# remains as the transitive barrier for the first run's partial save.
assert await asyncio.wait_for(middle_events_task, timeout=1) == []
assert middle.status == "stopped"
assert middle.task is not None and not middle.task.done()
assert not third_started.is_set()
release_first.set()
await asyncio.wait_for(first.task, timeout=1)
await asyncio.wait_for(middle.task, timeout=1)
await asyncio.wait_for(third.task, timeout=1)
assert first.status == "stopped"
assert middle.status == "stopped"
assert third.status == "done"
assert third_started.is_set()
@pytest.mark.asyncio
async def test_reconnect_replays_pinned_fallback_run_without_restarting_tools():
session_id = "sess-detached-fallback-resume"
agent_runs._RUNS.pop(session_id, None)
release = asyncio.Event()
tool_executions = 0
fallback = 'data: {"type":"fallback","answered_by":"backup","candidate_index":1}\n\n'
tool = 'data: {"type":"tool_output","tool":"bash","output":"ok"}\n\n'
async def pinned_run():
nonlocal tool_executions
yield fallback
tool_executions += 1
yield tool
await release.wait()
yield 'data: {"delta":"backup finished"}\n\n'
yield "data: [DONE]\n\n"
run = agent_runs.start(session_id, pinned_run())
first = agent_runs.subscribe(session_id)
first_events = []
async for event in first:
first_events.append(event)
if len(first_events) == 2:
break
await first.aclose()
assert run.status == "running"
assert tool_executions == 1
assert agent_runs._RUNS[session_id] is run
resumed_events = []
resumed = agent_runs.subscribe(session_id)
async for event in resumed:
resumed_events.append(event)
if len(resumed_events) == 2:
release.set()
await run.task
assert resumed_events[:2] == [fallback, tool]
assert resumed_events[-1] == "data: [DONE]\n\n"
assert tool_executions == 1
assert agent_runs._RUNS[session_id] is run
# --------------------------------------------------------------------------- #
# chat_stream: Compare panes must NOT be detached, so the Stop button (closing
# the SSE) cancels the upstream generator promptly — exercising the same