Repository navigation
Model-call liveness: keepalive, waiting heartbeat, unreachable provider, refiner status (#77) - #78
Merged
Merged
Conversation
…er, refiner status (chian#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 (chian#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.
Fixes #77.
What happened (2026-10-08, refiner build on Argo)
140.221.15.46 → Argo:443) became half-open, and the client washttpx.Client(timeout=None), so the read blocked forever./build statusshowedstate: refining, model_wait: nullthroughout.model_waitonly tracks the Builder's in-process calls, and since Construct workflows through scoped Episodes and update coding/viewer interfaces #66 the work happens in refiner Run calls recorded under the refiner's Duet.Changes
agent/provider_http.py: provider clients use TCP keepalive (idle 60 s, interval 15 s, 4 probes, about 2 min to declare a vanished peer) and a 30 s connect timeout. Reads stay unbounded, because a live gateway may send nothing until a long completion finishes, and keepalive ACKs from a live peer keep the socket open. A dead peer surfaces as a read error, which Replace a model attempt whose connection dropped mid-response (ADR 0008 amendment) #76 replaces once. Used by the launch transport and the chat coding adapter.call_waitingevery 120 s, with idle seconds, phase, whether a probe is active, and the time to the next probe. A stall is now visible in the ledger, and "supervisor stopped" can be told apart from "still waiting".provider_unreachableandModelCallRecoveryExhausted("Model provider unreachable …")instead of preserving it forever. "No response at all" means a connect failure, or a timeout while the probe was still indispatching/request_dispatched, i.e. before response headers. A probe that received headers, or succeeded, resets the count, so a slow but live provider is unaffected./build statuswhile refining.model_waitnow describes the build's newest model call from the ledger: episode, state, phase, idle seconds (also aselapsed_secondsfor the status line), physical attempt, endpoint, andresponse_seen. It uses a newDuetStore.events_of_type()cross-Duet read, filtered byowner_duet_id. On the real stalled ledger it reports:{"active": true, "episode": "launch", "state": "original_preserved", "phase": "request_dispatched", "idle_seconds": 10433.6, "response_seen": false, "physical_attempt": 1, "endpoint": "https://apps.inside.anl.gov/argoapi/v1"}Tests
tests/agent/test_model_call_liveness.py, 8 tests:events_of_typeacross Duets.Unchanged and passing:
test_model_call_failed_attempt_replacement(7),test_episode_model_failure_reporting(5),test_launch_transport_streams(4),test_refinement_chat_coding(14).test_build_refinement_job.py::test_one_build_enters_refinement_and_cancel_stops_same_job[materialized-build-linked-refiner]fails identically on cleanmain; it is pre-existing and unrelated.Not addressed
On macOS the monotonic clock stops during sleep, so probe scheduling ignores sleep time. Keepalive (1) covers the network-loss case regardless.
🤖 Generated with Claude Code