How to detect and handle silence during calls
Learn how to automatically end calls after periods of silence using the built-in user away timeout and custom retry mechanisms.
Last Updated:
There are times when you may need to automatically end a call after a period of silence, such as when a user becomes unresponsive or walks away from a call. This guide explains how to implement silence detection and automatic call termination.
Using the built-in user away timeout
LiveKit Agents has a built-in user_away_timeout feature that detects when a user has been inactive. When the timeout is reached, the user state changes to "away". You can configure this timeout and listen for the user_state_changed event:
import asyncioimport logging
from livekit.agents import ( Agent, AgentServer, AgentSession, JobContext, UserStateChangedEvent, cli,)from livekit.plugins import cartesia, deepgram, openai, silero
logger = logging.getLogger("silence-detection-agent")
server = AgentServer()
@server.rtc_session()async def entrypoint(ctx: JobContext): session = AgentSession( vad=silero.VAD.load(), llm=openai.LLM(model="gpt-4o-mini"), stt=deepgram.STT(), tts=cartesia.TTS(), user_away_timeout=5.0, # seconds of silence before user is marked "away" )
@session.on("user_state_changed") def _user_state_changed(ev: UserStateChangedEvent): if ev.new_state == "away": logger.info("User has been silent for too long, shutting down session") session.shutdown()
await session.start( agent=Agent(instructions="You are a helpful assistant."), room=ctx.room )
if __name__ == "__main__": cli.run_app(server)
Implementing custom silence detection with retries
For more advanced scenarios, such as prompting the user before disconnecting, you can implement a retry mechanism:
import asyncioimport logging
from livekit.agents import ( Agent, AgentServer, AgentSession, JobContext, UserStateChangedEvent, cli,)from livekit.plugins import cartesia, deepgram, openai, silero
logger = logging.getLogger("silence-detection-agent")
server = AgentServer()
@server.rtc_session()async def entrypoint(ctx: JobContext): session = AgentSession( vad=silero.VAD.load(), llm=openai.LLM(model="gpt-4o-mini"), stt=deepgram.STT(), tts=cartesia.TTS(), user_away_timeout=12.5, # seconds before user is marked "away" )
inactivity_task: asyncio.Task | None = None
async def user_presence_task(): # Try to ping the user 3 times, if we get no answer, close the session for _ in range(3): await session.generate_reply( instructions="The user has been inactive. Politely check if the user is still present." ) await asyncio.sleep(10)
logger.info("No response after multiple attempts, shutting down") session.shutdown()
@session.on("user_state_changed") def _user_state_changed(ev: UserStateChangedEvent): nonlocal inactivity_task if ev.new_state == "away": inactivity_task = asyncio.create_task(user_presence_task()) return
# User is now speaking or listening, cancel the inactivity task if inactivity_task is not None: inactivity_task.cancel() inactivity_task = None
await session.start( agent=Agent(instructions="You are a helpful assistant."), room=ctx.room )
if __name__ == "__main__": cli.run_app(server)
The user_state_changed event provides a UserStateChangedEvent with the following possible states:
"speaking"- VAD detected the user has started speaking"listening"- VAD detected the user has stopped speaking"away"- The user hasn't responded for the configureduser_away_timeoutduration
Related: Answering machine detection
For outbound calling scenarios where you need to detect answering machines or voicemail systems, see the telephony documentation for dedicated detection methods. Silence detection can complement these approaches but isn't a reliable standalone method for voicemail detection.