fix(agent): always prefer streaming for API calls to prevent hung subagents (#3120)
The non-streaming API call path (_interruptible_api_call) had no wall-clock timeout. When providers keep connections alive with SSE keep-alive pings but never deliver a response, httpx's inactivity timeout never fires and the call hangs indefinitely. Subagents always used the non-streaming path because they have no stream consumers (quiet_mode=True). This caused delegate_task to hang for 40+ minutes in production. The streaming path has two layers of protection: - httpx read timeout (60s, HERMES_STREAM_READ_TIMEOUT) - Stale stream detection (90s, HERMES_STREAM_STALE_TIMEOUT) Both work because streaming sends chunks continuously — a 90-second gap between chunks genuinely means the connection is broken, even for reasoning models that take minutes to complete. Now run_conversation() always prefers the streaming path. The streaming method falls back to non-streaming automatically if the provider doesn't support it. Stream delta callbacks are no-ops when no consumers are registered, so there's no overhead for subagents.
This commit is contained in:
@@ -273,6 +273,9 @@ def test_401_credential_refresh_recovers(monkeypatch):
|
||||
return _anthropic_response("Auth refreshed")
|
||||
|
||||
self._interruptible_api_call = _fake_api_call
|
||||
# Also patch streaming path — run_conversation now prefers
|
||||
# streaming for health checking even without stream consumers.
|
||||
self._interruptible_streaming_api_call = lambda api_kwargs, **kw: _fake_api_call(api_kwargs)
|
||||
return super().run_conversation(
|
||||
user_message, conversation_history=conversation_history, task_id=task_id
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user