LiveAvatar provisions the LiveKit room in their own LiveKit project. Our agent connects into that room as a regular client using the agent token LiveAvatar mints for us. We never touch our own LK project for room ownership — only for the inference gateway plugins.
Entrypoint: src/liveavatar_hosted_demo.py.
- Quickest possible end-to-end demo.
- You don't have (or don't want to manage) a LiveKit Cloud project for rooms.
- You want LiveAvatar to handle room lifecycle, participant management, etc.
┌──────────────────────────────┐ 1. POST /v1/sessions/token ┌────────────────┐
│ │ ─────────────────────────────▶│ │
│ liveavatar_hosted_demo.py │ 2. POST /v1/sessions/start │ LiveAvatar API │
│ │ ◀─────────────────────────────│ │
└──────┬───────────────────────┘ livekit_url, agent_token, └────────────────┘
│ client_token, ws_url
│ 3. simulate_job_with_metadata(token=agent_token)
▼
┌────────────────────────┐
│ worker.py │ LiveKit voice pipeline (STT → LLM → TTS)
│ (LK worker subprocess) │ with `tts_node` teeing audio frames to:
└──────────┬─────────────┘
│
├─ rtc.Room.connect(livekit_url, agent_token)
│
└─ websockets.connect(ws_url)
│ agent.speak / agent.speak_end / agent.interrupt
▼
LiveAvatar media server ──► avatar lip-syncs ──► LK room
┌──────────────┐
│ viewer/ │ served at http://127.0.0.1:<port>/
│ index.html │ joins the same LK room w/ client_token,
│ (vanilla JS) │ subscribes to avatar's audio + video
└──────────────┘
Complete the common setup first. This demo needs:
LIVEAVATAR_API_KEYAVATAR_IDLIVEKIT_API_KEY/LIVEKIT_API_SECRET(used by the inference gateway plugins — billed to your LK Cloud project, even though the room is LiveAvatar's)
LIVEKIT_URL is not used by this demo. The room URL comes back from
the LiveAvatar API.
⚠️ Inference is billed to your LK Cloud project. The room belongs to LiveAvatar, but every STT / LLM / TTS call still routes through your project's inference gateway and consumes LiveKit credits.
python src/liveavatar_hosted_demo.pyThis will:
- Mint a LiveAvatar session via the API.
- Start an embedded LiveKit Agents worker locally (devmode, unregistered).
- Dispatch a single job in-process using the pre-minted agent token.
- Open a viewer in your default browser, pre-filled with the room URL + client token, that auto-connects and turns on your microphone.
Speak. The agent will respond in the avatar's voice with lip-synced video.
Stop with Ctrl-C.
simulate_job_with_metadata (in src/worker.py) is a thin variant of
AgentServer.simulate_job that injects Job.metadata so we can pass the
LiveAvatar media-server WS URL through to the worker entrypoint. It
reuses the same @server.rtc_session handler that the BYO demo uses, so
the worker code path is identical to a real production dispatch.
It touches a few private AgentServer attrs (_id, _ws_url,
_proc_pool) to build the RunningJobInfo directly. That's fine for a
demo, but don't ship this to production unmodified — see below.
lk agent deploy does not work for this flow. LK Cloud workers
register against your LK project; LiveAvatar's room lives in their LK
project. Foreign project = no dispatch.
Instead, ship a long-lived python process (Fly / Render / ECS / k8s) that:
- Calls the LiveAvatar API to mint a session.
- Calls
rtc.Room.connect(livekit_url, agent_token)directly. - Starts an
AgentSessionagainst that connected room. - Opens the WS bridge to the media server.
You can drop AgentServer and simulate_job_with_metadata entirely —
AgentSession.start(room=connected_room, ...) is all you need. Use the
existing pipeline.build_session / build_room_options /
mute_agent_audio_on_publish / wire_*_observability helpers as-is.
If you'd rather scale via dispatch, switch to the BYO LiveKit demo.