Skip to main content
The livekit-plugins-facemode package connects a LiveKit Agents AgentSession to FaceMode. The plugin creates the FaceMode session, streams your TTS audio to FaceMode over the canonical WebSocket protocol, and routes the avatar video track into your LiveKit room.

Install

Quickstart

A complete agent looks like this:
1

Create the AvatarSession

Construct AvatarSession with your api_key and avatar_id. Both can come from environment variables.
2

Connect and start the avatar

Call await ctx.connect(), then avatar.start(session, ctx.room, room=...). The room argument is the room credential object sent to FaceMode; it is separate from the already-connected ctx.room the agent uses for media. avatar.start() must be called before session.start(), and only once per AvatarSession instance.
3

Start the agent with audio output disabled

Start the AgentSession with room_io.RoomOptions(audio_output=False). The plugin replaces the session’s audio output so TTS frames go to FaceMode instead of being published directly.
4

Wait for the avatar to join

await avatar.wait_for_join() resolves when the avatar video track appears in the room. It raises FaceModeNotReadyError if the track does not arrive within the timeout (30 seconds by default; pass timeout=None to wait indefinitely).

AvatarSession options

Useful properties: avatar.session_id returns the active FaceMode session ID, and avatar.avatar_identity returns the resolved participant identity.

start() signature

room accepts a LiveKitRoom instance or a mapping with type, url, token, and optional name. room_config= is an explicit alias for room= if you prefer to avoid the overloaded name.

Room token grants

The room.token value is used by FaceMode to join your room. It must grant:
  • room_join for the exact room name,
  • can_publish so the avatar video and audio tracks can be published,
  • can_subscribe so FaceMode can participate in the room’s media session.
If the token fixes a participant identity, set avatar_participant_identity to match and make sure your room policy allows that identity. Keep the token on your server; never expose it to browser clients or log it.

Input provider

Pass input_provider to pin the upstream speech input provider for the session. Supported values: deepgram, gemini, gnani, elevenlabs, openai, cartesia, sarvam, custom. When omitted, the field is not sent and the backend default applies. An invalid value raises ValueError at construction time.

Audio format

The plugin negotiates the canonical protocol using your TTS output format and locks it for the session:
  • Encoding is pcm_s16le sent as binary WebSocket frames.
  • Sample rate must be an integer between 8000 and 48000 Hz. Preferred rates are 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, and 48000; other in-range rates are accepted and logged once.
  • Channels must be 1 or 2.
  • A format change mid-session raises FaceModeProtocolError.

Reconnect behavior

If the WebSocket transport drops after the protocol has started, the plugin reconnects automatically in the background:
  1. It calls POST /api/sessions/{id}/reconnect for a fresh one-time wsToken (a consumed token is never reused).
  2. It opens a new socket with the facemode.<newToken> subprotocol, resends the identical start message, and waits for started before resuming.
  3. Outgoing control seq numbers stay monotonic across the reconnect. Audio already sent is not replayed; sends made during the gap are delivered once on the new socket.
Reconnect is bounded to 2 attempts with backoff and runs single-flight. Exhaustion surfaces a typed FaceModeNotReadyError or FaceModeAPIError. Intentional stop(), a canonical ended message, fatal protocol errors, and drops before the first negotiation do not reconnect.

Lifecycle and shutdown

Call await avatar.stop() (or aclose()) to shut down. On a negotiated session the plugin sends end_session and waits briefly for the canonical ended acknowledgement before closing the socket. Note that ended is a protocol acknowledgement, not confirmation that backend cleanup is complete. When LiveKit interrupts the agent’s audio output, the plugin sends cancel_utterance; a normal end of speech sends end_utterance.

Error types

All errors derive from FaceModeError (a RuntimeError):

Timeouts

The plugin disables WebSocket compression and native keepalives; after negotiation it sends an application-level ping every 15 seconds.

API URL override

The default API URL is https://api.facemode.io/api. Set api_url (or the FACEMODE_API_URL environment variable in your own config) for a self-hosted or local backend.