feat(model): bound silence instead of the whole call #883
No reviewers
Labels
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
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-gaming/sirens-echo!883
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/hs68-model-idle-timeout"
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?
Kai approved this on 2026-08-15 and named one thing to read first: #577's investigation, which landed 2026-08-16. This is the consuming half. The emitting half shipped in agent-proxy
d8277f4and has been inert since, exactly as its own doc predicted:The defect, stated precisely
A total deadline fires on schedule however many bytes arrived. So a turn that was queued behind other work and a turn whose backend had hung died at the same second, and the member was told
turn timed outfor both. There were two total deadlines, not one - the turn context andhttp.Client.Timeout, both set tocfg.RequestTimeout.What changed
stream: true. Heartbeats have nowhere to go on a non-streaming request, which is why the proxy documents them as streaming-only.Two bounds instead of one.
SIRENS_ECHO_MODEL_IDLE_TIMEOUT(new, 45s) bounds silence. Any line resets it, and a heartbeat comment is a line.http.Client.Timeoutis now unset. Leaving it would have cut a streaming completion on schedule and reintroduced the defect one layer down.Silence became its own failure.
ErrModelSilent, causemodel_silent, spansirens_echo.model.silent, and the member is told the backend went quiet rather than that the turn timed out.That split is acceptance criterion 3 and it is the part that matters most: retrying silence can work, and retrying a turn that ran the ceiling out while making progress cannot. Telling a member to retry the second one is the true-sentence-that-misdirects shape from #449.
Assembly
Content and reasoning concatenate. Tool calls arrive as fragments keyed by
index, so name and argument pieces are joined per call - a truncated argument string would reach the executor and fail for a reason that is not the model's.Calls are returned in the order their indexes first appeared, not sorted by index, because that is the order the model asked for them in. Pinned by a test that streams index 1 before index 0.
Reasoning stays unnamed when the stream carried none.
reasoning_contentabsent andreasoning_content: ""are echoed back differently on the next round, so inventing one would corrupt the roundtrip thatdocs/sirens-echo-reasoning-roundtrip.mdexists to protect.The non-streamed shape still works
Selected on
Content-Type. Agent Proxy's own non-streaming surface still exists, and a backend that answers a streaming request with a whole body is read as one rather than failing a turn that arrived intact. That path gets no heartbeats, so the turn ceiling is its only bound - unchanged from today.This is also why the existing test fixtures still pass without being rewritten. One assertion did change and had to:
TestProxyClientSendsBoundedCommunityRequestassertedstream must be false, which is now wrong by design.Acceptance, checked
TestHeartbeatsKeepAStreamAliveThroughSilence- three beats at 60ms under a 120ms idle bound, total read outlives itTestASilentBackendFailsAtTheIdleTimeoutTestSilenceAndTheCeilingAreDifferentSentences, plusTestTheTurnCeilingStillEndsAStreamproving the ceiling does not collapse into silence1 + 1does not failAlso pinned: fragmented tool calls, two-call ordering, an unparsable heartbeat being skipped rather than fatal, a stream ending without
[DONE], and reasoning staying unnamed.What I deliberately did not do
The ceiling number. Kai's sizing note here says to clear
litellm_request's 233.71s p99 with margin, and the guidance was to read #577's result before choosing. I read it, and it argues against picking a number in this PR: after Echo's lane went to 5m, 4 of 8 turns still landed within 1.5s of the new ceiling, and round 0 alone ranged 133.5s to 255.4s. A bigger number did not stop turns reaching the wall. #577 is open andconsult, so the number stays Kai's and this PR ships the mechanism it was waiting on.The fourth acceptance criterion is unverified.
1 + 1dying at 181s needs the live lane to confirm, and nothing here reaches it. The mechanism that killed it is gone; whether it stays gone is a measurement someone should take against real traffic, which is currently about four turns a day (#577).just gatepasses.closes #171