[eric] agents: the exhausted-respawn 400 cards the router restart honestly instead of blaming the model

This commit is contained in:
ciregenz
2026-08-31 12:24:31 -07:00
parent e4d141a48d
commit 1db307f5f5
2 changed files with 35 additions and 1 deletions
@@ -172,6 +172,33 @@ async def handle_run_error(e: Exception, session: AgentSession, session_id: str,
)
return
logger.warning(f"Agent {session_id}: deferred-tool 400 again after a respawn; carding it")
# The generic card below says "switch to another model", which for THIS failure blames the
# wrong thing: the model was fine, our router was restarting. Say what happened and what
# actually works (a plain resend once it settles; proven live on the same transcript).
friendly_msg = (
"The app's model router was restarting when this chat connected, and the automatic "
"retry hit the same window. It settles within a minute; send your message again and "
"the chat picks up where it left off."
)
error_msg = Message(role="system", content=friendly_msg, branch_id=session.active_branch_id)
absorb_repeat_card(session, error_msg)
await ws_manager.send_to_session(session_id, "agent:message", {
"session_id": session_id,
"message": error_msg.model_dump(mode="json"),
})
try:
from backend.apps.service.client import submit_diagnostic
submit_diagnostic({
"kind": "model_error",
"subkind": "stale_tool_schema_respawn_exhausted",
"flight": flight_recorder.build_envelope(session_id, "model_error", "stale_tool_schema", session.model, "stream" if turn.current_turn_emitted else "spawn", -1),
"session_id": session_id,
"model": session.model,
"error_preview": redact_for_telemetry(str(e), limit=400),
})
except Exception:
logger.debug("submit_diagnostic stale_tool_schema failed", exc_info=True)
return
if is_context_overflow_error(e, extra_text=p_stderr_tail):
p_tier_gate = is_long_context_error(e, extra_text=p_stderr_tail)
@@ -156,4 +156,11 @@ async def test_the_second_identical_400_does_card(monkeypatch):
s.stale_tool_schema_retry_used = True # the one-shot is already spent
await mod.handle_run_error(RuntimeError(REAL), s, s.id, TurnState(), [])
assert [m for m in s.messages if m.role == "system"], "a spent budget must produce an honest card"
cards = [m for m in s.messages if m.role == "system"]
assert cards, "a spent budget must produce an honest card"
# The card must blame the actual culprit (our router restarting), never the model, and must
# promise the thing that is proven to work (a plain resend on the same chat).
text = cards[-1].content
assert "router" in text.lower(), text
assert "send your message again" in text.lower(), text
assert "switch" not in text.lower(), "the generic switch-models advice blames the wrong thing"