I am using Soniox TTS. Even when the session ends ...
# ask-ai
b
I am using Soniox TTS. Even when the session ends on the LiveKit side, the Soniox WebSocket connection remains active, which is driving up costs.
✅ 1
t
Thanks for reaching out, RikuKomiya. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I can help troubleshoot the Soniox WebSocket staying open after the LiveKit session ends. To give concrete, safe recommendations I need a few details about how you wired Soniox into your LiveKit flow: 1. Is Soniox TTS running in the client (browser/mobile) or on a server/process you control? 2. LiveKit deployment: LiveKit Cloud or self-hosted? 3. Which LiveKit SDK and version are you using (JS/React/Android/iOS/server)? 4. Which Soniox client/library and version? 5. Where/how do you open the Soniox WebSocket? Please paste the code (or pseudocode) that opens/closes it and what triggers those calls. 6. How do you detect the LiveKit session end? (participant disconnected event, room ended webhook, SDK connection closed, etc.) 7. What behavior do you want: close Soniox when a specific participant leaves, when the last participant leaves, or when a Room is explicitly ended? 8. Any logs showing LiveKit disconnect but Soniox still connected, and whether calling
WebSocket.close()
returns errors or is never called? Answering these will let me recommend exact places to add cleanup (client-side event handlers, server-side webhook handlers, per-room socket lifecycle, or ensuring
close()
is called). If you prefer, paste the relevant code and I’ll point out the minimal changes to reliably close the Soniox connection when the LiveKit session ends. , with interactive elements
b
1. Is Soniox TTS running on the client, or on a managed server/process? Soniox is not used for TTS in the current codebase. It is used for STT. TTS is handled by OpenAI / Azure OpenAI. Soniox STT runs in the managed Python LiveKit agent process, not in the browser or mobile client. 2. Is the LiveKit deployment LiveKit Cloud or self-hosted? Runtime behavior depends on LIVEKIT_URL. The codebase contains LiveKit Cloud-style URL examples, and I did not find a self-hosted LiveKit server configuration. So it appears to be LiveKit Cloud, but the production value of LIVEKIT_URL should be checked in Secret Manager / Worker environment variables to confirm. 3. LiveKit SDKs and versions Python agent:livekit-agents 1.5.5,livekit 1.1.5 React Native:@livekit/react-native 2.9.5 React components:@livekit/components-react 2.9.17 JS client:livekit-client 2.16.0 Server SDK:livekit-server-sdk 2.14.2 WebRTC:@livekit/react-native-webrtc 125.0.11 4. Soniox client/library and version The project uses livekit-plugins-soniox 1.5.5. It does not appear to use a direct Soniox SDK. Soniox is accessed through the LiveKit Agents Soniox plugin. The websockets package version is 15.0.1. 5. Where and how is the Soniox WebSocket opened? There is no application-level code that directly calls WebSocket(...) or explicitly opens/closes a Soniox WebSocket. The code creates soniox.STT(params=...), passes it into AgentSession, and then calls session.start(...). The WebSocket connection to Soniox is handled internally by LiveKit Agents / livekit-plugins-soniox. Pseudocode:
soniox_stt_options = soniox.STTOptions(
model="stt-rt-v4",
language_hints=...,
enable_language_identification=True,
context=...,
client_reference_id=user_id,
)
stt = soniox.STT(params=soniox_stt_options)
session = AgentSession(
stt=stt,
llm=...,
tts=...,
turn_detection=...,
vad=...,
)
await session.start(room=ctx.room, agent=agent, ...)
6. How is the LiveKit session termination detected? On the server side, in voice mode, the agent checks ctx.room.remote_participants every 5 seconds. If the room has zero remote participants for LIVEKIT_EMPTY_ROOM_GRACE_SECONDS, default 30 seconds, it calls session.aclose(). On the client side, disconnection is handled by setting the LiveKitRoom connect prop to false. Text chat has an onDisconnected log, but that is not directly wired to Soniox close handling. 7. Desired behavior for closing Soniox Based on the current implementation, voice mode is designed to close the agent session, including Soniox/STT, when the last remote participant has left and the grace period has elapsed. It does not close when a specific participant leaves. Explicit room cleanup is configured via session.start(... delete_room_on_close=True), so room deletion is tied to session close. 8. Are there logs showing Soniox remains connected after LiveKit disconnects? Does WebSocket.close() return an error? I did not find logs in the repository that directly show Soniox remaining connected after LiveKit disconnects. There is also no app-level code that calls WebSocket.close() for Soniox. So this is not a case where WebSocket.close() is returning an error. Rather, the application code is not directly calling WebSocket.close(); the Soniox connection lifecycle is managed internally by the LiveKit Agents Soniox plugin.
t
Thanks — this is helpful detail. I’m responding to your concern that Soniox STT WebSockets stay open after the LiveKit session ends and drive costs. Summary of facts from your setup • Soniox STT runs server-side inside the managed Python agent via livekit-plugins-soniox 1.5.5; the plugin manages the WebSocket internally. • Your code starts STT by creating
soniox.STT(...)
, passing it into
AgentSession
, and calling
session.start(...)
. There’s no app-level WebSocket open/close. • The agent detects an empty room and calls
session.aclose()
after the configured grace period (default 30s). • Proper cleanup (including closing STT connections) happens when
session.aclose()
or
session.shutdown(drain=True)
runs. If the process exits before those calls, connections may remain. Recommended next steps to ensure Soniox closes when the LiveKit session ends 1. Confirm session teardown is actually being called: ◦ Add short-lived debug logs around the empty-room detection and immediately before/after
await session.aclose()
to confirm it runs every time the room becomes empty. 1. Add a shutdown callback to guarantee cleanup on job shutdown: ◦ Ensure your job registers a shutdown hook that calls
await session.aclose()
so cleanup runs on orchestrated shutdowns:
Copy code
async def on_shutdown():
         await session.aclose()
     ctx.add_shutdown_callback(on_shutdown)
1. Enable/inspect agent debug logs for STT teardown: ◦ Turn on debug logs and look for messages that indicate STT/soniox teardown and session report uploads (these confirm resource cleanup). 1. Verify you’re on a recent plugin version: ◦ If
session.aclose()
logs show teardown but Soniox connections still persist, upgrade
livekit-plugins-soniox
(check GitHub for fixes against lingering WS connections). 1. Reproduce and capture evidence: ◦ Reproduce an end-to-end flow: start session → let grace period elapse → confirm
session.aclose()
logs → then check Soniox connection state (plugin logs or Soniox dashboard). Capture logs showing teardown or persistence. 1. If process is being killed hard: ◦ Ensure your process/container runtime isn’t forcibly killing the agent (SIGKILL) before cleanup. Use graceful stop so the shutdown hook runs. If you’d like, paste the specific snippet where you perform the empty-room check and call
session.aclose()
and I’ll point out exactly where to add logging and a shutdown callback. Sources: Soniox STT plugin guide | LiveKit Documentation | Job lifecycle | LiveKit Documentation | Agent session | LiveKit Documentation | Why is my Agent insights data missing? | LiveKit, with interactive elements