[F036] orchestrator: read transcript for overload detection too

The SDK server writes model-API errors (529/500/503) to /tmp/sdk-server.log,
not stdout, so an overload marker can appear only in the durable Claude
transcript — the same rationale already applied to the session-limit
detector. _provider_overload_park_target read only docker logs, so an
overload was missed and the agent crash-respawned straight back into it.

Now concatenates the transcript tail before matching, mirroring the
rate-limit path.
This commit is contained in:
Renn F
2026-06-28 11:04:32 +02:00
parent dd28a1f997
commit 4c9f26762f
2 changed files with 27 additions and 1 deletions
+7 -1
View File
@@ -6413,7 +6413,13 @@ Start by:
if provider_type not in (None, ModelProvider.ANTHROPIC.value):
return None
tail = await self._tail_container_logs(f"roboco-agent-{agent_id}")
lowered = tail.lower()
# F036: the SDK server writes model-API errors to /tmp/sdk-server.log,
# not stdout, so the overload marker (529/500/503) may appear only in
# the durable Claude transcript — the same rationale already applied to
# the session-limit detector. Without the transcript an overload is
# missed and the agent crash-respawns straight back into it.
transcript_tail = self._transcript_tail_text(agent_id)
lowered = (tail + "\n" + transcript_tail).lower()
if any(marker in lowered for marker in _ANTHROPIC_OVERLOAD_MARKERS):
return ModelProvider.ANTHROPIC.value
return None
@@ -103,6 +103,26 @@ async def test_clean_output_is_not_overload(
assert await orch._provider_overload_park_target("be-dev-1", _instance()) is None
@pytest.mark.asyncio
async def test_detects_overload_marker_in_transcript(
orch: AgentOrchestrator, monkeypatch: pytest.MonkeyPatch
) -> None:
# F036: the SDK server writes model-API errors to /tmp/sdk-server.log, not
# stdout, so the overload marker (529/500/503) may appear only in the durable
# Claude transcript — exactly the rationale already applied to the
# session-limit detector. Without reading the transcript here an overload
# is missed and the agent crash-respawns straight back into it.
monkeypatch.setattr(settings, "overload_break_enabled", True)
monkeypatch.setattr(orch, "_tail_container_logs", AsyncMock(return_value=""))
monkeypatch.setattr(
orch, "_transcript_tail_text", lambda _a, _lines=80: _OVERLOAD_LOG
)
assert (
await orch._provider_overload_park_target("be-dev-1", _instance())
== "anthropic"
)
@pytest.mark.asyncio
async def test_disabled_flag_never_parks(
orch: AgentOrchestrator, monkeypatch: pytest.MonkeyPatch