Repository navigation
Replace a model attempt whose connection dropped mid-response (ADR 0008 amendment) - #76
Merged
Merged
Conversation
…08 amendment) Third live refiner build on Argo (2026-10-08, build e25c9514): the Materialization Implementer's streamed call failed after 164 s with "peer closed connection without sending complete message body (incomplete chunked read)" (RemoteProtocolError, http_status None, classified retryable). Its route's recovery policy was retry_on_healthy_probe with max_replacements 1, but ADR 0008 replacement covered only silent calls, so the supervisor raised and the whole build stopped. The supervisor now also replaces a physical attempt that failed because the connection dropped with no provider verdict (RemoteProtocolError, read/write/ connect errors, no HTTP status), under retry_on_healthy_probe and within the same max_replacements budget, after a short backoff, recorded as recovery_replacement_proposed with basis retryable_transport_failure. Provider error responses (HTTP status such as 503, or a stream error event) still fail without a retry, as test_episode_model_failure_reporting requires; preserve/disabled modes and legacy routes are unchanged. ADR 0008 amended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 8, 2026
wilke
added a commit
that referenced
this pull request
Oct 8, 2026
…er, refiner status (#77) A refiner build on Argo stalled for hours (2026-10-08): the host left the ANL VPN mid-request, the provider socket became half-open, and the client had no read timeout. Every ADR 0008 health probe then timed out before any response, which the policy treats as "preserve the original" — indefinitely — and /build status showed model_wait: null because refiner calls bypass the Builder's in-process wait tracker. - agent/provider_http.py: provider clients get TCP keepalive (~2 min to declare a vanished peer) and a 30 s connect timeout; reads stay unbounded so long silent generations remain legal. Used by the launch transport and the chat coding adapter. A dead connection now fails as a read error, which the dropped-transport replacement (#76) handles. - model_call_recovery: a silent call records call_waiting every 120 s (idle seconds, phase, probe state), so a stall is visible and a stopped supervisor is distinguishable from a waiting one. Three consecutive health probes that get no response at all (connect failure, or a timeout before response headers) end the call with provider_unreachable instead of preserving it forever; a probe that received headers resets the count. - /build status: while refining, model_wait describes the build's newest model call from the ledger (episode, phase, idle seconds, attempt, endpoint, response_seen), via a new DuetStore.events_of_type cross-Duet read of model_launch_call events carrying owner_duet_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Failure
This was the third live refiner build on Argo (2026-10-08,
build_request_e25c9514…). The Materialization Implementer's streamed call failed after 164 s:The route's frozen recovery policy was
retry_on_healthy_probewithmax_replacements: 1. However, ADR 0008 replacement only covers attempts that go silent. An attempt that raises went straight tophysical_attempt_failed→ModelCallFailed("no alternate route was selected"), and the build endedinterrupted3 minutes after starting.The same Argo drop also hit the chat coding adapter earlier today (fixed there in #75). Argo drops long streamed responses intermittently, so one replacement turns a lost build into a short delay.
Change
agent/model_call_recovery.py: when the primary attempt raises, the supervisor calls a newreplace_failed_primary(). It replaces the attempt only if all of the following hold:retry_on_healthy_probe;http_status is None, and the transport error type is in{RemoteProtocolError, ReadError, WriteError, ConnectError, ReadTimeout, APIConnectionError};max_replacementsbudget used for silent calls isn't exhausted;A replacement closes the failed attempt, backs off (5 s × attempt, capped by
recovery_grace_seconds), and starts a new physical attempt on the same route. It's recorded asrecovery_replacement_proposedwith basisretryable_transport_failureandduplicate_server_work_possible: true. Only one result is ever admitted, as before.Unchanged:
test_episode_model_failure_reportingrequires ("Reporting must not introduce a retry");preserve/disabledmodes;ADR 0008: adds a dated amendment describing this case.
Tests
tests/agent/test_model_call_failed_attempt_replacement.py, 7 tests:preserveanddisabledkeep failing;Unchanged and passing:
test_episode_model_failure_reporting.py(5) andtest_launch_transport_streams.py(4).Note
The chat coding adapter (#75) has its own small retry loop, because Implementer coding sessions don't go through this supervisor. ADR 0008 prefers a single shared policy, so a follow-up could route coding-model calls through
supervise_model_call.🤖 Generated with Claude Code