Bound one request total upstream time and record what the caller got #119
No reviewers
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agent-proxy!119
Loading…
Reference in a new issue
No description provided.
Delete branch "cancel-abandoned-work"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bug
Fixes #112. Fixes #106.
The timeout ladder is inverted at every layer:
sirens-echoturnagent-proxyPOST /v1/chat/completionslitellmlitellm_requestlitellmReceived Proxy Server RequestThe caller gives up first and the abandoned request runs on for another 7 to 13 minutes with nobody to receive its answer. #106 records the same shape from the far end: litellm generated for 207 seconds after agent-proxy stopped waiting, then returned 500, while every agent-proxy span in that trace read
has_error: falsewith an emptyresponse_status_code.The caller is not at fault -
sirens-echobuilds its request withhttp.NewRequestWithContexton its turn deadline and does disconnect at 180s.Root cause
PROXY_REQUEST_TIMEOUTis per attempt. Withmax_retriesand a fallback chain, nothing bounded the request as a whole. AndPOST /v1/completionshad no disconnect watcher at all, whilePOST /v1/chat/completionshas had one.Fix
PROXY_REQUEST_DEADLINE- a wall clock for the whole request: queue wait, every retry, every fallback. Past it no further attempt starts, and the in-flight attempt is cut byasyncio.wait_for, which closes the httpx request and with it the upstream connection. Answers 504, not a 502 folded into backend-unavailable. Defaults to 0 (off), so a deployment that does not set it is unchanged.X-Request-Deadline-Ms(orX-Request-Timeout-Ms) lets a caller declare its own budget. It may only shorten the configured deadline. Set below the caller's own timeout, it closes the connection while the caller is still listening./v1/completionsgets the disconnect watcher. Both surfaces now share_until_disconnect. The body is read to completion first, since the watcher andrequest.json()would otherwise race for the same ASGI receive channel.http.response.status_codeon the request span for every terminal response. That is the measurement half of #106.How to verify
ward exec test- 8 new cases intests/test_cancellation.py, 1 intests/test_error_spans.py.test_deadline_cuts_the_attempt_and_stops_upstream_workasserts the mock transport observes cancellation.test_expired_deadline_starts_no_attemptasserts an already-spent budget reaches no upstream.test_caller_deadline_only_shortens_the_configured_oneasserts 1000ms against a 100s ceiling still yields 100s.test_completions_disconnect_cancels_upstreamcovers the surface that had no watcher.Test plan
ward exec format-check,lint,typecheck,pre-commitall cleanRisk
Low by default: the deadline is off unless configured, so behaviour is unchanged out of the box. The
/v1/completionswatcher is a behaviour change on that surface - a caller that disconnects mid-request now has its work cancelled, which is what the chat surface already did.Four test doubles gained a
deadline=None/ signature update. Two new closed-taxonomy entries and one newRequestOutcomevalue.What this does not do
It does not make litellm stop. Closing the connection is the only cancellation signal HTTP offers; whether the far end acts on it is litellm's own behaviour and needs verifying there. The deadline is what makes agent-proxy stop asking.
Note
Branched off
upstream-error-classification(#118), which carries the upstream-span half of #106. Review #118 first; this branch contains its commit.