quaint-carpenter-27927
03/26/2026, 9:33 AMtall-airline-16756
03/26/2026, 9:33 AMtall-airline-16756
03/26/2026, 9:34 AMSILENCE_THRESHOLD = 3 # seconds
user_last_spoke_time = time.time()
# Called by your framework when the user starts speaking
def on_user_started_speaking():
global user_last_spoke_time
user_last_spoke_time = time.time()
# Background monitor - run this as an async task for the session
async def silence_monitor(agent):
while True:
await asyncio.sleep(0.5) # check frequently but not constantly
elapsed = time.time() - user_last_spoke_time
if elapsed > SILENCE_THRESHOLD:
await agent.say("Are you still there?") # or your TTS call
# decide whether to:
# - break (ask once), or
# - reset last_spoke_time and continue monitoring, or
# - retry prompting N times then hang up
break
Practical notes and options
• Event to use: Use whatever event your integration exposes for “user started speaking” (VAD/voice-activity events). If you receive more detailed events (e.g., start/stop), update the timestamp on start; if only audio level/meters are available, update when level crosses a threshold.
• Poll interval: 0.5–1 second is usually good. Too frequent wastes CPU; too rare delays the prompt.
• Debounce/false positives: Only prompt when the user was recently active (e.g., last_spoke_time was within the last minute) to avoid prompting inactive/left users immediately.
• Prompt strategy:
◦ Ask once then wait for response.
◦ Ask up to N times with increasing delays.
◦ If no response after prompts, end call or mark session inactive.
• Cleanup: Ensure the monitor task is cancelled when the session ends to avoid leaks.
• Integrate with other signals: You can combine this with connection-state or silence-detection confidence if your audio pipeline provides it.
If you want, tell me:
• What language/framework your agent runs in (Node, Python, etc.)
• How you currently detect user speech (VAD event, audio level, or nothing)
With that I can give a concrete code sample that matches your stack and show how to wire the monitor into your session lifecycle.
Sources: Agents Framework | livekit/livekit_composite | DeepWiki | How to detect and handle silence during calls | examples/frontdesk | livekit/livekit_composite | DeepWiki | examples/voice_agents | livekit/livekit_composite | DeepWiki, with interactive elementsquaint-carpenter-27927
03/26/2026, 10:46 AMlivekit-agents library.
2. How we detect user speech: We are currently detecting speech purely through LiveKit's built-in VAD events. Specifically, we hook into @session.on("user_speech_committed") to know when the user stops speaking, @session.on("agent_started_speaking") to know when the bot begins audio playback, and @session.on("agent_speech_committed") to know when the bot finishes.
Can you provide a minimal, non-blocking Python async polling implementation that integrates directly with these specific @session.on events?tall-airline-16756
03/26/2026, 10:47 AMlivekit-agents session events you listed (user_speech_committed, agent_started_speaking, agent_speech_committed). It tracks silence, runs a background monitor using asyncio tasks, cancels/cleans up properly, and calls the agent TTS via session.say() after 3 seconds of silence.
Important behaviors implemented:
• Reset the silence timer when the user finishes speaking (user_speech_committed).
• Cancel the timer while the agent is speaking (agent_started_speaking) to avoid overlapping prompts.
• Restart the timer when the agent finishes speaking (agent_speech_committed).
• Non-blocking: uses asyncio.create_task() and fine-grained sleeps.
• Safe cleanup: cancels the background timeout task when the session is shutting down.
Code (adapt into your agent init/session setup):
import asyncio
from typing import Optional
# from livekit.agents import AgentSession # use your actual import
SILENCE_THRESHOLD = 3.0 # seconds
async def attach_silence_prompt_monitor(session):
"""
Attach silence-detection prompt logic to an AgentSession.
Call this after the session is created/started.
"""
timeout_task: Optional[asyncio.Task] = None
shutdown = False
def _cancel_timeout():
nonlocal timeout_task
if timeout_task and not timeout_task.done():
timeout_task.cancel()
timeout_task = None
def _schedule_timeout():
nonlocal timeout_task
_cancel_timeout()
timeout_task = asyncio.create_task(_silent_timeout())
async def _silent_timeout():
try:
await asyncio.sleep(SILENCE_THRESHOLD)
# Only prompt if agent is not currently speaking.
# Adjust checks according to your session state model if different.
# Example assumes session has properties or state you can inspect.
# If there's no state, rely on events (agent_started_speaking cancels the task).
await session.say("Are you still there?", allow_interruptions=True)
except asyncio.CancelledError:
# expected when user/agent speech interrupts the timeout
return
# Event: user finished speaking -> start/reset silence timer
@session.on("user_speech_committed")
def _on_user_speech_committed(*_args, **_kwargs):
# user just finished speaking, start the timeout
_schedule_timeout()
# Event: agent starts speaking -> cancel any pending user-silence prompt
@session.on("agent_started_speaking")
def _on_agent_started_speaking(*_args, **_kwargs):
_cancel_timeout()
# Event: agent finished speaking -> start/reset silence timer (listening again)
@session.on("agent_speech_committed")
def _on_agent_speech_committed(*_args, **_kwargs):
_schedule_timeout()
# Optional: if your session supports shutdown callbacks, ensure cleanup
def _on_shutdown():
nonlocal shutdown
shutdown = True
_cancel_timeout()
# If AgentSession has a lifecycle hook for shutdown, register it here.
# Example: session.add_shutdown_callback(_on_shutdown)
if hasattr(session, "add_shutdown_callback"):
session.add_shutdown_callback(_on_shutdown)
# Start initial timer so we begin monitoring immediately (optional)
_schedule_timeout()
Notes and small adjustments you may want
• allow_interruptions: I used allow_interruptions=True in session.say() so incoming user speech can interrupt the prompt. Remove or adjust if you want the prompt to run to completion.
• Checking agent state: If you have an easy way to inspect whether the agent is speaking or in a listening state, you can add a guard before calling session.say() (but the event-driven cancel already prevents overlaps).
• Multiple prompts / retries: To ask more than once, replace the break-style behavior with a retry counter in _silent_timeout() and re-schedule after prompts as needed.
• Debounce for long inactive sessions: If you only want prompts for recently active users, keep a timestamp of recent activity and check before scheduling the timeout.
• Poll interval: This implementation uses a single sleep for the threshold and is efficient; no tight loop required.
If you want, I can:
• Convert this into a complete runnable snippet using your exact session creation/startup code, or
• Add retry logic (ask N times with increasing delays), or
• Show how to integrate with any session state fields your code already uses.
Which next step would you like?
Sources: Turns overview | LiveKit Documentation | Agent session | LiveKit Documentation, with interactive elements