Python Agents: Handling exceptions in your entrypoint
Gracefully handle errors in your entrypoint() to prevent silent calls and dangling SIP sessions when using Python Agents with telephony.
Last Updated:
When an unhandled exception occurs inside your entrypoint() function, the agent process exits and leaves the LiveKit room. However, the SIP call itself isn't automatically terminated. This means the phone call stays connected to a silent, empty room until someone hangs up. This applies to both inbound and outbound calls — any scenario where an exception occurs after the call is established.
This can happen when any setup code in your entrypoint throws unexpectedly: a failed API call, a misconfigured service, a missing environment variable, or a transient infrastructure issue.
This guide covers why this happens, why the framework doesn't handle it automatically, and several strategies for cleaning up gracefully.
Why the framework doesn't auto-close the room
The Agents framework intentionally doesn't delete the room or hang up the call when an agent leaves. This is by design — the framework can't know your use case. For example:
- The room might be a multi-participant meeting where one agent crashing shouldn't end the call for everyone.
- You might want a different agent to retry or take over.
- You might have other participants in the room that should continue.
Because the agent leaves after the exception with CLIENT_REQUEST_LEAVE, another agent won't automatically be dispatched to replace it either. The room is left as-is.
Prerequisites
- An agent created using the Voice AI quickstart.
- LiveKit SIP configured to accept inbound calls.
Wrapping your entrypoint in try/except
The most straightforward approach is to wrap your entrypoint() function body in a try/except block. This gives you full control over cleanup when something goes wrong.
1import logging2from livekit import api3from livekit.agents import AgentSession, AgentServer, JobContext, cli4from livekit.agents.voice import Agent5from livekit.plugins import silero67logger = logging.getLogger("safe-entrypoint")8logger.setLevel(logging.INFO)910server = AgentServer()111213@server.rtc_session(agent_name="my-agent")14async def entrypoint(ctx: JobContext):15try:16# Your setup code that might fail17session = AgentSession(18stt="deepgram/nova-3",19llm="openai/gpt-4o",20tts="cartesia/sonic-3",21vad=silero.VAD.load(),22)2324await session.start(25agent=Agent(instructions="You are a helpful assistant."),26room=ctx.room,27)2829except Exception as e:30logger.error(f"Entrypoint failed: {e}", exc_info=True)3132# Clean up: delete the room to end the SIP call33lkapi = api.LiveKitAPI()34try:35await lkapi.room.delete_room(36api.DeleteRoomRequest(room=ctx.room.name)37)38logger.info(f"Deleted room {ctx.room.name} after entrypoint failure")39except Exception as cleanup_error:40logger.error(f"Failed to delete room: {cleanup_error}")41finally:42await lkapi.aclose()434445if __name__ == "__main__":46cli.run_app(server)
Choosing a cleanup strategy
Deleting the room is the most aggressive cleanup option. Depending on your use case, you might want a different approach.
Option 1: Delete the room
Best for dedicated SIP call rooms where the agent is the only reason the room exists. Deleting the room disconnects all participants, including the SIP caller.
1lkapi = api.LiveKitAPI()2try:3await lkapi.room.delete_room(4api.DeleteRoomRequest(room=ctx.room.name)5)6finally:7await lkapi.aclose()
Option 2: Remove the SIP participant
Best for rooms where other participants should stay connected. This hangs up the phone call without affecting other room members.
1lkapi = api.LiveKitAPI()2try:3# List participants and remove only SIP ones4res = await lkapi.room.list_participants(5api.ListParticipantsRequest(room=ctx.room.name)6)7for p in res.participants:8if p.identity.startswith("sip_"):9await lkapi.room.remove_participant(10api.RoomParticipantIdentity(11room=ctx.room.name,12identity=p.identity,13)14)15finally:16await lkapi.aclose()
Option 3: Notify then hang up
Best when you want to give the caller a brief explanation before ending the call. Start a minimal session, apologize, then shut down.
1except Exception as e:2logger.error(f"Entrypoint failed: {e}", exc_info=True)34try:5# Start a minimal session just to say goodbye6fallback_session = AgentSession(7tts="cartesia/sonic-3",8)910fallback_agent = Agent(11instructions="Apologize briefly for the technical difficulty and say goodbye."12)1314await fallback_session.start(agent=fallback_agent, room=ctx.room)1516await fallback_session.say(17"I'm sorry, we're experiencing a technical issue. Please try again later.",18allow_interruptions=False,19)2021fallback_session.shutdown()2223except Exception:24# If even the fallback fails, just delete the room25lkapi = api.LiveKitAPI()26try:27await lkapi.room.delete_room(28api.DeleteRoomRequest(room=ctx.room.name)29)30finally:31await lkapi.aclose()
Handling errors after session start
Exceptions can also occur after your session has started — for example, from inference API failures during the conversation. For these cases, use the error event on AgentSession rather than wrapping the entrypoint:
1@session.on("error")2def on_error(error_event):3if not error_event.error.recoverable:4logger.error(f"Unrecoverable error: {error_event.error}")5session.say(6"I'm having trouble right now. Let me transfer you.",7allow_interruptions=False,8)
For more details, see Events and error handling.
You can also use a FallbackAdapter to automatically retry with backup providers when the primary STT, LLM, or TTS fails:
1from livekit.agents import llm, stt, tts2from livekit.plugins import deepgram, openai, assemblyai34session = AgentSession(5stt=stt.FallbackAdapter([6deepgram.STT(),7assemblyai.STT(),8]),9llm=llm.FallbackAdapter([10openai.LLM(model="gpt-4o"),11openai.LLM.with_azure(model="gpt-4o", ...),12]),13)
How it works
- The
entrypoint()function is wrapped in atry/exceptthat catches any exception during setup. - When an exception occurs, the agent logs the error and performs cleanup based on the chosen strategy.
- For SIP calls, deleting the room or removing the SIP participant ensures the caller isn't left on a silent line.
- For errors during an active session, the
errorevent andFallbackAdapterprovide runtime resilience.
Best practices
- Always wrap your entrypoint: Even if your setup code looks safe today, external services can fail at any time. A
try/exceptcosts nothing and prevents a poor caller experience. - Log the original error: Always log the exception with
exc_info=Truebefore cleanup so you can diagnose the root cause. - Clean up the API client: Use
await lkapi.aclose()in afinallyblock to prevent resource leaks. - Handle cleanup failures: The cleanup itself can fail (network issues, permissions). Wrap it in its own
try/exceptso a cleanup failure doesn't mask the original error. - Consider a shutdown callback: Use
ctx.add_shutdown_callback()for cleanup that should run regardless of how the entrypoint exits.