Run multiple LiveKit voice agents in a single shared worker process.
Project description
OpenRTC is designed for the common case where you want to run several different voice agents on a small VPS without paying the memory cost of one full LiveKit worker per agent.
Table of Contents
Why OpenRTC exists
A standard livekit-agents worker process loads shared runtime assets such as
Python, Silero VAD, and turn-detection models. If you run ten agents as ten
separate workers, you pay that base memory cost ten times.
OpenRTC keeps your agent classes completely standard and only centralizes the worker boilerplate:
- shared prewarm for VAD and turn detection
- metadata-based dispatch to the correct agent
- per-agent
AgentSessionconstruction inside one worker
Your agent code still subclasses livekit.agents.Agent directly. If you stop
using OpenRTC later, your agent classes still work as normal LiveKit agents.
What OpenRTC wraps
OpenRTC intentionally wraps only the worker orchestration layer:
AgentServer()setup and prewarm- a universal
@server.rtc_session()entrypoint - per-call
AgentSession()creation with the right providers
OpenRTC does not replace:
livekit.agents.Agent@function_toolRunContexton_enter,on_exit,llm_node,stt_node,tts_node- standard LiveKit deployment patterns
Memory comparison
| Deployment model | Shared runtime loads | Approximate memory shape |
|---|---|---|
| 10 separate LiveKit workers | 10x | ~500 MB × 10 |
| 1 OpenRTC pool with 10 agents | 1x shared + per-call session cost | ~500 MB shared + active-call overhead |
The exact numbers depend on your providers, concurrency, and environment, but OpenRTC is built to reduce duplicate worker overhead.
Installation
Install OpenRTC from PyPI:
pip install openrtc
openrtc depends on livekit-agents[silero,turn-detector], so the runtime
plugins required by shared prewarm are installed with the base package.
If you are developing locally, the repository uses uv for environment and
command management.
Required environment variables
OpenRTC uses the same environment variables as a standard LiveKit worker:
LIVEKIT_URL=ws://localhost:7880
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=secret
Add only the provider keys needed for the models you actually use:
DEEPGRAM_API_KEY=...
OPENAI_API_KEY=...
CARTESIA_API_KEY=...
GROQ_API_KEY=...
ELEVENLABS_API_KEY=...
Quick start: register agents directly with add()
Use AgentPool.add(...) when you want the most explicit setup.
from livekit.agents import Agent
from openrtc import AgentPool
class RestaurantAgent(Agent):
def __init__(self) -> None:
super().__init__(instructions="You help callers make restaurant bookings.")
class DentalAgent(Agent):
def __init__(self) -> None:
super().__init__(instructions="You help callers manage dental appointments.")
pool = AgentPool()
pool.add(
"restaurant",
RestaurantAgent,
stt="deepgram/nova-3:multi",
llm="openai/gpt-4.1-mini",
tts="cartesia/sonic-3",
greeting="Welcome to reservations.",
)
pool.add(
"dental",
DentalAgent,
stt="deepgram/nova-3:multi",
llm="openai/gpt-4.1-mini",
tts="cartesia/sonic-3",
)
pool.run()
Quick start: discover agent files with @agent_config(...)
Use discovery when you want one agent module per file. OpenRTC will import each
module, find a local Agent subclass, and optionally read overrides from the
@agent_config(...) decorator.
from pathlib import Path
from openrtc import AgentPool
pool = AgentPool(
default_stt="deepgram/nova-3:multi",
default_llm="openai/gpt-4.1-mini",
default_tts="cartesia/sonic-3",
)
pool.discover(Path("./agents"))
pool.run()
Example agent file:
from livekit.agents import Agent
from openrtc import agent_config
@agent_config(name="restaurant", greeting="Welcome to reservations.")
class RestaurantAgent(Agent):
def __init__(self) -> None:
super().__init__(instructions="You help callers make restaurant bookings.")
Discovery defaults
A discovered module does not need to provide any OpenRTC metadata. If the agent
class has no @agent_config(...) decorator:
- the agent name defaults to the Python filename stem
- STT/LLM/TTS/greeting fall back to
AgentPool(...)defaults
That keeps discovery straightforward while still allowing per-agent overrides when needed.
Routing behavior
For each incoming room, AgentPool resolves the agent in this order:
ctx.job.metadata["agent"]ctx.job.metadata["demo"]ctx.room.metadata["agent"]ctx.room.metadata["demo"]- room name prefix match, such as
restaurant-call-123 - the first registered agent
This lets one worker process host several agents while staying compatible with standard LiveKit job and room metadata.
If metadata references an unknown registered name, OpenRTC raises a ValueError
instead of silently falling back.
Greetings and session options
OpenRTC can play a greeting after ctx.connect() and pass extra options into
AgentSession(...).
pool.add(
"restaurant",
RestaurantAgent,
greeting="Welcome to reservations.",
session_kwargs={"allow_interruptions": False},
max_tool_steps=4,
preemptive_generation=True,
)
Direct keyword arguments take precedence over the same keys inside
session_kwargs.
Provider model strings
OpenRTC passes provider strings through to livekit-agents, so the common case
stays simple.
STT examples
deepgram/nova-3deepgram/nova-3:multiassemblyai/...google/...
LLM examples
openai/gpt-4.1-miniopenai/gpt-4.1groq/llama-4-scoutanthropic/claude-sonnet-4-20250514
TTS examples
cartesia/sonic-3elevenlabs/...openai/tts-1
If you need advanced provider configuration, you can still pass provider objects instead of strings.
CLI usage
OpenRTC includes a CLI for discovery-based workflows.
List discovered agents
openrtc list \
--agents-dir ./agents \
--default-stt deepgram/nova-3:multi \
--default-llm openai/gpt-4.1-mini \
--default-tts cartesia/sonic-3
Run in production mode
openrtc start --agents-dir ./agents
Run in development mode
openrtc dev --agents-dir ./agents
Both start and dev discover agents first and then hand off to the underlying
LiveKit worker runtime.
Public API at a glance
OpenRTC currently exposes:
AgentPoolAgentConfigAgentDiscoveryConfigagent_config(...)
On AgentPool, the primary public methods and properties are:
add(...)discover(...)list_agents()get(name)remove(name)run()server
Project structure
src/openrtc/
├── __init__.py
├── cli.py
└── pool.py
pool.pycontains the coreAgentPoolimplementation and discovery helperscli.pyprovides discovery and worker startup commands__init__.pyexposes the public package API
Contributing
Contributions are welcome. Please read CONTRIBUTING.md before opening a pull request.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openrtc-0.0.9.tar.gz.
File metadata
- Download URL: openrtc-0.0.9.tar.gz
- Upload date:
- Size: 606.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23047a6bdccb742c36d4b174a4176133332513b2f22fc3fa01d28199ecc79777
|
|
| MD5 |
e2033c0f1e54331477b2220ae2b44474
|
|
| BLAKE2b-256 |
a147b9d438465040e498e77f304d90e8eaea38faf01ad45c966a9f75e0d4a6aa
|
File details
Details for the file openrtc-0.0.9-py3-none-any.whl.
File metadata
- Download URL: openrtc-0.0.9-py3-none-any.whl
- Upload date:
- Size: 12.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6198ac1d2d601dd8b8f5f0a2bb6f425b53426b9503cd76477f5fe25b132a2ba4
|
|
| MD5 |
763500ba19b8e61e860caba6645f1087
|
|
| BLAKE2b-256 |
1c7f4dc1b65e6da9b0f07cc147c69f673234c168add3bb906488455b411d5ab9
|