Skip to main content
Field Guides

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:

Troubleshooting

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

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.

import logging
from livekit import api
from livekit.agents import AgentSession, AgentServer, JobContext, cli
from livekit.agents.voice import Agent
from livekit.plugins import silero
logger = logging.getLogger("safe-entrypoint")
logger.setLevel(logging.INFO)
server = AgentServer()
@server.rtc_session(agent_name="my-agent")
async def entrypoint(ctx: JobContext):
try:
# Your setup code that might fail
session = AgentSession(
stt="deepgram/nova-3",
llm="openai/gpt-4o",
tts="cartesia/sonic-3",
vad=silero.VAD.load(),
)
await session.start(
agent=Agent(instructions="You are a helpful assistant."),
room=ctx.room,
)
except Exception as e:
logger.error(f"Entrypoint failed: {e}", exc_info=True)
# Clean up: delete the room to end the SIP call
lkapi = api.LiveKitAPI()
try:
await lkapi.room.delete_room(
api.DeleteRoomRequest(room=ctx.room.name)
)
logger.info(f"Deleted room {ctx.room.name} after entrypoint failure")
except Exception as cleanup_error:
logger.error(f"Failed to delete room: {cleanup_error}")
finally:
await lkapi.aclose()
if __name__ == "__main__":
cli.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.

lkapi = api.LiveKitAPI()
try:
await lkapi.room.delete_room(
api.DeleteRoomRequest(room=ctx.room.name)
)
finally:
await 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.

lkapi = api.LiveKitAPI()
try:
# List participants and remove only SIP ones
res = await lkapi.room.list_participants(
api.ListParticipantsRequest(room=ctx.room.name)
)
for p in res.participants:
if p.identity.startswith("sip_"):
await lkapi.room.remove_participant(
api.RoomParticipantIdentity(
room=ctx.room.name,
identity=p.identity,
)
)
finally:
await 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.

except Exception as e:
logger.error(f"Entrypoint failed: {e}", exc_info=True)
try:
# Start a minimal session just to say goodbye
fallback_session = AgentSession(
tts="cartesia/sonic-3",
)
fallback_agent = Agent(
instructions="Apologize briefly for the technical difficulty and say goodbye."
)
await fallback_session.start(agent=fallback_agent, room=ctx.room)
await fallback_session.say(
"I'm sorry, we're experiencing a technical issue. Please try again later.",
allow_interruptions=False,
)
fallback_session.shutdown()
except Exception:
# If even the fallback fails, just delete the room
lkapi = api.LiveKitAPI()
try:
await lkapi.room.delete_room(
api.DeleteRoomRequest(room=ctx.room.name)
)
finally:
await 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:

@session.on("error")
def on_error(error_event):
if not error_event.error.recoverable:
logger.error(f"Unrecoverable error: {error_event.error}")
session.say(
"I'm having trouble right now. Let me transfer you.",
allow_interruptions=False,
)

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:

from livekit.agents import llm, stt, tts
from livekit.plugins import deepgram, openai, assemblyai
session = AgentSession(
stt=stt.FallbackAdapter([
deepgram.STT(),
assemblyai.STT(),
]),
llm=llm.FallbackAdapter([
openai.LLM(model="gpt-4o"),
openai.LLM.with_azure(model="gpt-4o", ...),
]),
)

How it works

  1. The entrypoint() function is wrapped in a try/except that catches any exception during setup.
  2. When an exception occurs, the agent logs the error and performs cleanup based on the chosen strategy.
  3. For SIP calls, deleting the room or removing the SIP participant ensures the caller isn't left on a silent line.
  4. For errors during an active session, the error event and FallbackAdapter provide 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/except costs nothing and prevents a poor caller experience.
  • Log the original error: Always log the exception with exc_info=True before cleanup so you can diagnose the root cause.
  • Clean up the API client: Use await lkapi.aclose() in a finally block to prevent resource leaks.
  • Handle cleanup failures: The cleanup itself can fail (network issues, permissions). Wrap it in its own try/except so 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.