---
title: "Python agents: handling exceptions in your entrypoint"
date: "2026-03-02"
authors: "Chris Wilson"
tags: "guide"
---

# Python agents: handling exceptions in your entrypoint

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](https://docs.livekit.io/agents/start/voice-ai).
- LiveKit SIP configured to [accept inbound calls](https://docs.livekit.io/telephony/accepting-calls/workflow-setup#setup-for-accepting-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.

```python
import logging
from livekit.agents import AgentSession, AgentServer, JobContext, cli
from livekit.agents.voice import Agent

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",
        )

        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
        try:
            await ctx.delete_room()
            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}")


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.

```python
await ctx.delete_room()
```

### 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.

```python
from livekit import api

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.

```python
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
        await ctx.delete_room()
```

## 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:

```python
@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](https://docs.livekit.io/reference/agents/events).

You can also use a `FallbackAdapter` to automatically retry with backup providers when the primary STT, LLM, or TTS fails:

```python
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.responses.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.