I kept rebuilding the same scaffolding around LiveKit Agents for client projects, so I cleaned it up and open-sourced it (MIT). Sharing here because most of it maps directly to questions that come up in this category.
It’s three pieces, each runnable and deployable on its own:
- agent: a LiveKit voice worker (Deepgram STT, an OpenAI model, Cartesia TTS, Silero VAD, multilingual turn detector)
- backend: a FastAPI service that mints room tokens
- frontend: React + Vite + Tailwind on the Agents UI components (audio visualizer, live transcript, text chat)
The LiveKit-specific decisions, since those are the interesting part here:
-
Explicit dispatch via agent_name. The worker registers under a name; the web client requests it through the token’s room_config (the backend passes room_config straight through), and SIP comes in through a dispatch rule. One worker, one dispatch path you can reason about.
-
Web and SIP on the same worker. A single identify() step classifies the participant (SIP participant kind, or a sip.phoneNumber attribute) and the conversation after that is identical. No second code path for “browser” vs “phone number.”
-
Standard token endpoint. POST /token follows LiveKit’s documented token-endpoint schema, so the client SDKs connect with TokenSource.endpoint(…) and no glue. Backend and agent share the same API key/secret, so a backend-minted token is valid for the room the agent joins.
-
Prewarm loads Silero VAD once per process and shares it across sessions, and the server uses a drain timeout so deploys don’t cut active calls.
-
Turn detection uses the multilingual model with VAD-based interruption, set through TurnHandlingOptions.
Providers are single-line swaps, and it points at self-hosted LiveKit or Cloud by changing the URL. Docker Compose brings the whole stack up with one command, or you can run each service in its own terminal. Typed, tested, CI across all three.
Built on livekit-agents 1.5.x. I’d value a second opinion on two things from people who’ve shipped more of these than I have: the dispatch setup (is room_config passthrough the cleanest way to trigger a named agent from the web client?), and the web-vs-SIP identification (anything I’m missing for edge cases like transferred calls?).
Happy to answer anything about the structure.
