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
Theroom.token value is used by FaceMode to join your room. It must
grant:
room_joinfor the exact room name,can_publishso the avatar video and audio tracks can be published,can_subscribeso FaceMode can participate in the room’s media session.
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
Passinput_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_s16lesent 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:- It calls
POST /api/sessions/{id}/reconnectfor a fresh one-timewsToken(a consumed token is never reused). - It opens a new socket with the
facemode.<newToken>subprotocol, resends the identicalstartmessage, and waits forstartedbefore resuming. - Outgoing control
seqnumbers stay monotonic across the reconnect. Audio already sent is not replayed; sends made during the gap are delivered once on the new socket.
FaceModeNotReadyError or FaceModeAPIError.
Intentional stop(), a canonical ended message, fatal protocol errors, and
drops before the first negotiation do not reconnect.
Lifecycle and shutdown
Callawait 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 fromFaceModeError (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 ishttps://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.