fix(phonic): reconnect after abnormal disconnects - #6828
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
|
|
||
| self._client = AsyncPhonic( | ||
| api_key=self._opts.api_key, | ||
| reconnect_conversation_on_abnormal_disconnect=True, |
There was a problem hiding this comment.
π΄ Voice sessions can go silent with no error when the provider connection drops for good
Automatic reconnection is turned on (reconnect_conversation_on_abnormal_disconnect=True at livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py:389) without handling the case where reconnecting ultimately fails, so a dropped call ends quietly instead of reporting a failure.
Impact: When the connection cannot be restored, the agent simply stops talking and listening and nothing is notified, so the call hangs instead of erroring out or being recovered.
Silent break in the reconnectable socket iterator swallows abnormal-close errors
With the flag enabled, client.conversations.connect() yields a ReconnectableAsyncConversationsSocketClient instead of AsyncConversationsSocketClient (phonic SDK phonic/conversations/client.py:1258-1286). Its __aiter__ catches ConnectionClosed and only re-raises when the close code is neither normal (1000) nor "reconnectable" (1006/1012/1001-restarting); for a 1006 abnormal close where reconnect is impossible (no conversation_id yet) or exhausted (10 attempts), it just breaks without raising (phonic/conversations/reconnectable_socket_client.py:193-206, 155-191).
The plugin's _recv_task (livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py:956-995) therefore returns normally. In _main_task (...:926-938) recv_task is done with no exception, so no _emit_error(..., recoverable=True) is raised; the code cancels the peers, closes the socket and the current generation, and the session ends without surfacing any error to AgentSession.
Before this change, the plain socket iterator propagated ConnectionClosedError on abnormal closes, so the plugin logged and emitted a recoverable error.
Prompt for agents
Enabling reconnect_conversation_on_abnormal_disconnect makes phonic's connect() return a ReconnectableAsyncConversationsSocketClient whose __aiter__ swallows ConnectionClosed for abnormal/reconnectable close codes (1006/1012/1001-restarting) once reconnection is impossible or exhausted: it breaks out of the loop instead of raising. As a result RealtimeSession._recv_task in livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py finishes normally, _main_task sees no exception, and the session tears down without emitting any error, leaving the agent silently dead. Consider detecting an unexpected end of the receive loop (e.g. loop exited while _session_should_close is not set) and emitting a recoverable error / raising so the agent session can react.
Was this helpful? React with π or π to provide feedback.
|
|
||
| self._client = AsyncPhonic( | ||
| api_key=self._opts.api_key, | ||
| reconnect_conversation_on_abnormal_disconnect=True, |
There was a problem hiding this comment.
π‘ Tool results and audio sent while the connection is being restored are dropped without notice
Messages such as tool results are handed to the connection while it is being re-established (via the client created at livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py:389), where they are quietly discarded, so the assistant can wait forever for a result that was never delivered.
Impact: After a brief network hiccup the conversation can stall mid tool call, with the assistant never responding.
Reconnectable client no-ops sends while reconnecting, but the plugin already marked the tool call as answered
All send_* methods of ReconnectableAsyncConversationsSocketClient are guarded by _is_send_safe() and silently return when _reconnecting is True (phonic/conversations/reconnectable_socket_client.py:106-107, 221-263). The reconnect window includes an exponential backoff sleep of up to 5s per attempt (...:178-191).
In update_chat_ctx the plugin removes the call id from self._pending_tool_call_ids before calling send_tool_call_output (livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py:475-488), so a dropped send is never retried and no error is raised; Phonic resumes the conversation still waiting for that tool output. The same silent drop applies to send_say, send_generate_reply, send_reset and buffered audio chunks in _send_task.
Was this helpful? React with π or π to provide feedback.
Summary
Test plan
Made with Cursor