> ## Documentation Index
> Fetch the complete documentation index at: https://daily-hush-pcc-session-recordings-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Running Bots Locally

> The bot entry point, the built-in development runner, and the transports it supports.

Pipecat ships a built-in development runner (`pipecat.runner.run`) that handles the server-side glue most bots need during development: creating Daily rooms, accepting WebRTC offers, terminating telephony WebSockets, and serving a prebuilt UI to talk to your bot. You write the bot; the runner does everything around it.

This page covers the canonical shape: a bot file with a single async entry point, run via `pipecat.runner.run.main()`.

<Note>
  The development runner is a **local development tool** — it isn't built or
  supported for production use. See [The runner is a development
  tool](#the-runner-is-a-development-tool-not-a-production-server) below, and
  [Running Bots in Production](./running-bots-in-production) for what to reach
  for instead.
</Note>

## The bot entry point

Every bot is a Python file with an async `bot()` function that takes a `RunnerArguments`. The runner calls this function once per session, passing in everything the bot needs to connect (room URL, token, WebRTC connection, WebSocket, etc.) as fields on the argument object.

```python bot.py theme={null}
from pipecat.runner.types import RunnerArguments

async def bot(runner_args: RunnerArguments):
    """Entry point. The runner calls this once per session."""
    # ... configure the transport from runner_args, build a pipeline, run it.
    ...

if __name__ == "__main__":
    from pipecat.runner.run import main
    main()
```

The bot is encapsulated: it doesn't know how the request to start a session arrived, who authenticated it, or whether it's running locally or in production. Everything it needs to do its job is on `runner_args`. This is the property that makes the same bot file portable across the development runner, Pipecat Cloud, and most production self-hosting setups.

## Choosing a transport

`RunnerArguments` has transport-specific subclasses; the runner instantiates the right one based on how the session was initiated. A typical multi-transport bot pattern-matches:

```python theme={null}
from pipecat.runner.types import (
    DailyRunnerArguments,
    RunnerArguments,
    SmallWebRTCRunnerArguments,
    WebSocketRunnerArguments,
)

async def bot(runner_args: RunnerArguments):
    match runner_args:
        case DailyRunnerArguments():
            transport = DailyTransport(runner_args.room_url, runner_args.token, "Bot", ...)
        case SmallWebRTCRunnerArguments():
            transport = SmallWebRTCTransport(webrtc_connection=runner_args.webrtc_connection, ...)
        case WebSocketRunnerArguments():
            transport = FastAPIWebsocketTransport(websocket=runner_args.websocket, ...)

    await run_pipeline(transport)
```

A bot can support a single transport or many. In both the local development runner and Pipecat Cloud, the client chooses how to start the session by sending a `transport` value in the `/start` request (`webrtc`, `daily`, `twilio`, `telnyx`, `plivo`, `exotel`, or `websocket`); `-t/--transport` only restricts the local runner to one transport and sets that default.

For working files demonstrating each transport, see [`examples/runner-examples/`](https://github.com/pipecat-ai/pipecat-examples/tree/main/runner-examples) — the `01-` through `04-` files step from a single-transport bot through to a factory-driven multi-transport setup.

## Installing and running

```bash theme={null}
uv add "pipecat-ai[runner]"
```

Run the bot file directly:

```bash theme={null}
uv run bot.py
```

By default, the development runner starts a local server on `localhost:7860`, serves the prebuilt client UI at `/client`, and lets clients start sessions through `POST /start`. Clients can request the transport in that start request; `-t/--transport` is available when you want to restrict the local runner to one transport.

For the full `/start` request shape, transport-specific behavior, and CLI flags, see the [Development Runner guide](/api-reference/server/utilities/runner/guide).

## What the runner does for you

Behind `uv run bot.py` the runner is doing a fair amount of work depending on the transport the client requests:

* **WebRTC (`smallwebrtc`)** — mounts a prebuilt UI at `/client`, accepts `POST /api/offer` (with ICE candidates via `PATCH /api/offer`), and bridges the resulting `SmallWebRTCConnection` to your `bot()` function. Also exposes a `POST /start` endpoint that returns a `sessionId`, mimicking Pipecat Cloud's start API.
* **Daily** — calls Daily's REST API to create a room and issue tokens, then either redirects the browser to the room (via `GET /daily`) or returns the room URL + token via `POST /start`.
* **Telephony** — returns the carrier-specific XML stub (TwiML for Twilio, the equivalent for Telnyx/Plivo/Exotel) on `POST /`, then accepts the bidirectional media WebSocket at `/ws` and hands it to your bot wrapped in a `WebSocketRunnerArguments`.
* **Plain WebSocket** — for non-telephony clients (e.g. browser apps using protobuf framing). `POST /start` with `"transport": "websocket"` returns a `wsUrl` for the `/ws-client` endpoint; the bot receives a `WebSocketRunnerArguments` with `transport_type="websocket"`.
* **Daily PSTN dial-in** (`--dialin`) — handles Daily's pinless dial-in webhook, creates a SIP-enabled room, and dispatches the bot with full dial-in context.

`GET /` redirects to the prebuilt client UI at `/client/`, and `GET /status` reports which transports the running instance accepts. The `POST /start` and `/sessions/{id}/...` endpoints exposed by the runner are deliberately shaped the same way as [Pipecat Cloud](/pipecat-cloud/introduction)'s session API. That means a client built against the development runner works against PCC unchanged, and a custom production dispatcher can offer the same contract if you want clients to remain portable.

## Adding your own routes

The runner exports its FastAPI app as a module-level attribute, so you can add custom routes before calling `main()`:

```python theme={null}
from pipecat.runner.run import app, main

@app.get("/healthz")
async def healthz():
    return {"status": "ok"}

if __name__ == "__main__":
    main()
```

This is the simplest way to extend the runner — for things like health checks, internal status endpoints, or accepting metadata alongside session-start requests — without forking the dispatcher itself.

## The runner is a development tool, not a production server

The development runner is exactly that — a tool for local development. It's deliberately simple: its `POST /start` endpoint accepts session-start requests from anyone, some transport webhooks (like Daily's dial-in webhook) are likewise unauthenticated, and there's no rate limiting or backpressure anywhere. It's also a single process with no lifecycle management. Those are the right tradeoffs on your own machine, and the wrong ones on a public address, where a single request can spin up a bot and create paid rooms on your account.

For anything real, put a proper dispatcher or a managed runtime in front of your bots instead of exposing the runner. The next page, [Running Bots in Production](./running-bots-in-production), walks through those options — including how to package the bot into a container image — and, if you only need something reachable for a prototype or alpha test, the minimum you'd want in front of the runner and where that stops being enough.
