Necessity of calling commitUserTurn during manual ...
# ask-ai
q
Necessity of calling commitUserTurn during manual turn taking? I notice that when I call this as instructed in the docs, duplicate STT connections are being held open, inflating my current count. without it (or maybe due to something else) I'm suffering an ongoing memory leak in my agent process.
t
Thanks for reaching out, Brian. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I hear your concern about duplicate STT connections and an ongoing memory leak when using manual turn-taking and
commit_user_turn()
. I’ll explain the role of
commit_user_turn()
, what could cause duplicate STT connections, and then ask a few targeted questions so I can help you debug this faster. Short answer • Yes — calling
commit_user_turn()
is necessary in manual turn mode to signal the end of a user turn and let the agent finish processing the input (LLM work, TTS, etc.). Not calling it can leave the turn open and prevent normal cleanup. • However,
commit_user_turn()
itself is not explicitly documented as opening or closing STT connections. Duplicate STT connections and memory leaks are usually caused by how audio/stream lifecycle and agent tasks are managed, not by the call alone. What to check next (likely causes and mitigations) • Ensure you explicitly close or stop any audio/STT streams when a turn ends: ◦ If you create
rtc.AudioStream
or similar stream objects per turn, make sure they’re closed on commit/clear/interrupt. • Ensure every
commit_user_turn()
is paired with proper cleanup: ◦ If you start async tasks to forward audio to STT, cancel/await them when the turn completes or is interrupted. • Look for orphaned tasks or handlers in lifecycle hooks: ◦ Confirm you don’t leave background listeners, callbacks, or long-lived promises running after a turn finishes. • Review features that increase resource retention: ◦ Features like enhanced noise cancellation or
preemptive_generation
can increase CPU/memory; confirm whether those are enabled and whether they spawn extra threads or connections that need teardown. • Monitor connection reuse in your STT provider integration: ◦ Some STT client libraries may open new websockets/streams per audio session if not reused; ensure your integration reuses connections where intended. Targeted troubleshooting steps you can run now 1. Add logging around turn lifecycle: ◦ Log when you create/start and when you close/stop STT/audio streams and when you call
commit_user_turn()
. 1. Reproduce with a minimal flow: ◦ Run a short script that opens a turn, sends audio, calls
commit_user_turn()
, and repeat N times while watching active connections and memory. 1. Check for un-awaited/cancelled tasks: ◦ Search for spawned async tasks that are not awaited or canceled in error/cleanup paths. 1. Temporarily disable
preemptive_generation
and noise-cancellation features to see whether memory/connection counts stabilize. 2. Inspect your STT client for connection pooling settings: ◦ Ensure you’re not creating a new STT websocket per small audio chunk unless required. Questions to narrow this down • Are you using enhanced noise cancellation or
preemptive_generation
? If so, which? • Which STT provider and client library are you using (built-in LiveKit STT or an external provider)? • Do you open a new STT/audio stream for every user turn, or reuse one stream per session/agent? • Can you share (sanitized) pseudocode showing how you start audio capture, send audio to STT, and where you call
commit_user_turn()
and any cleanup? • When you say “duplicate STT connections are being held open,” do you see multiple websocket/stream connections at the STT provider level, or multiple internal objects in your process (e.g., many
AudioStream
instances)? If you share the answers or a small pseudocode snippet, I’ll propose concrete code-level changes and the exact places to add cleanup to stop connection growth and eliminate the leak. Sources: Turns overview | LiveKit Documentation | Self-hosted deployments | LiveKit Documentation | Agent speech and audio | LiveKit Documentation | Video | LiveKit Documentation | Capturing the First Few Seconds of a Call for Processing, with interactive elements
q
ah actually clearUserTurn is the one in question that we skip
t
I don't have the answer you're looking for. You could also try asking your question: • in one of the other Slack channels or • to https://deepwiki.com/livekit/livekit_composite which is trained on all LiveKit source code If you find the answer, please post it here to help others!
q
answers to your questions: 1. Enhanced noise cancellation / preemptive_generation? - Noise cancellation: Only in realtime mode (inputAudioNoiseReduction: { type: "near_field" } on gpt-realtime-mini). Pipeline mode (where the leak is) has neither enabled. - Preemptive generation: Not enabled anywhere. 2. STT provider and client library? - Using LiveKit's built-in inference STT (@alert-easter-19258/agents-plugin-livekit-inference), not a direct external provider. - Pipeline mode: deepgram/nova-2 (index.ts) or deepgram/nova-3 (voiceClient.ts) - We create the STT instance once per session (new inference.STT(...) passed to AgentSession constructor). 3. New STT/audio stream per turn or reuse? - We don't manage STT streams ourselves — the LiveKit AgentSession handles that internally. We just call commitUserTurn() / setAudioEnabled(). If the SDK is opening a new WebSocket per turn internally, that's not something we control. 4. Turn handling code (sanitized): // Session created once with manual turn detection session = new voice.AgentSession({ stt: new inference.STT({ model: "deepgram/nova-2" }), llm: new inference.LLM({ model: "openai/gpt-4.1-mini" }), tts: new inference.TTS({ model: "elevenlabs/eleven_flash_v2_5" }), turnHandling: { turnDetection: "manual" }, }); // PTT press registerRpcMethod("pttPress", async () => { session.interrupt(); session.input.setAudioEnabled(true); // NOTE: we do NOT call clearUserTurn() — observed it leaks STT WebSocket }); // PTT release registerRpcMethod("pttRelease", async () => { session.input.setAudioEnabled(false); session.commitUserTurn(); }); In manual turn mode with LiveKit inference STT, does AgentSession internally create/destroy STT WebSocket connections per turn cycle (setAudioEnabled(false) → commitUserTurn() → setAudioEnabled(true))? Or does it reuse a single connection? If it creates new ones, are old ones being properly closed?