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().
The development runner is a local development tool — it isn’t built or
supported for production use. See The runner is a development
tool below, and
Running bots in production for what to reach
for instead.
The bot entry point
Every bot is a Python file with an asyncbot() 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.
bot.py
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:
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/ — the 01- through 04- files step from a single-transport bot through to a factory-driven multi-transport setup.
Installing and running
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.
What the runner does for you
Behinduv 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, acceptsPOST /api/offer(with ICE candidates viaPATCH /api/offer), and bridges the resultingSmallWebRTCConnectionto yourbot()function. Also exposes aPOST /startendpoint that returns asessionId, 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 viaPOST /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/wsand hands it to your bot wrapped in aWebSocketRunnerArguments. - Plain WebSocket — for non-telephony clients (e.g. browser apps using protobuf framing).
POST /startwith"transport": "websocket"returns awsUrlfor the/ws-clientendpoint; the bot receives aWebSocketRunnerArgumentswithtransport_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’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 callingmain():
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: itsPOST /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, 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.