Atmee avatar plugin for LiveKit Agents
Bring your own LiveKit voice agent; Atmee renders a v1 avatar — a talking head generated from a single portrait — into your room. This plugin targets Atmee's v1 avatars specifically. Works like the other avatar plugins for LiveKit Agents: the avatar joins your room as its own participant, your agent's speech drives its video.
from livekit.plugins import atmee
avatar_id = await atmee.AtmeeAPI().create_avatar(name="Val", image="portrait.jpg")
avatar = atmee.AvatarSession(avatar_id=avatar_id) # ATMEE_API_KEY in the environment
await avatar.start(session, room=ctx.room)
await session.start(agent=..., room=ctx.room) # the agent's TTS drives the avatar's video
Installation
pip install livekit-plugins-atmee
Python 3.10+, livekit-agents 1.6.8 or newer.
Prerequisites
- An Atmee API key (
sk_atmee_...), inATMEE_API_KEY. Create one in the Atmee studio at atmee.ai/studio/api-keys. Keep it on your agent's side; it never goes to a browser. - Your own LiveKit project (LiveKit Cloud or self-hosted):
LIVEKIT_URL,LIVEKIT_API_KEY,LIVEKIT_API_SECRET— the same variables your agent already uses. Atmee never sees your LiveKit secret: the plugin mints a token for the avatar participant locally and hands only that token to Atmee.
Usage
1. Create an avatar from a portrait
Once, from a script or your backend (see examples/create_avatar.py):
async with atmee.AtmeeAPI() as api: # reads ATMEE_API_KEY from the environment
avatar_id = await api.create_avatar(name="Val", image="portrait.jpg")
All it needs is a name and a single portrait. image may be a local
file path, raw image bytes, or an https:// URL Atmee can download; .jpg,
.png, and .webp are accepted. A portrait-only avatar is render-ready
immediately — nothing to wait for. You can also create and manage avatars in the
Atmee studio at atmee.ai; any avatar of your account with a
portrait works.
2. Render it in your agent
async def entrypoint(ctx: agents.JobContext):
await ctx.connect()
session = AgentSession(stt=..., llm=..., tts=..., vad=...)
avatar = atmee.AvatarSession(avatar_id=os.environ["ATMEE_AVATAR_ID"])
await avatar.start(session, room=ctx.room) # before session.start
await session.start(agent=MyAgent(), room=ctx.room)
start() returns as soon as Atmee's rendering worker acknowledged the start
(about a second); the avatar's video and audio tracks appear in the room a few
seconds later, published by the participant atmee-avatar-agent. Your agent
publishes no audio of its own — the avatar speaks for it, lips in sync.
Options on AvatarSession:
| Argument | Default | Meaning |
|---|---|---|
avatar_id |
required | The Atmee avatar to render. |
avatar_version |
"v1" |
The avatar generation to render. Only "v1" is available today; "v2" raises ValueError (see Avatar versions). |
api_key, api_url |
ATMEE_API_KEY, ATMEE_API_URL |
Credentials and API base. api_url is optional and defaults to production, https://api.atmanity.us; set it only to target another environment. |
avatar_participant_identity |
atmee-avatar-agent |
Identity the avatar joins with; set it when one room hosts several avatars. |
max_duration_seconds |
3600 |
Hard ceiling of the session; also the most it can bill if your agent dies without a trace. |
wait_for |
"initializing" |
"avatar_joined" makes start() block until the avatar is in the room. |
metadata |
None |
Free-form JSON stored with the session on the Atmee side. |
avatar.session_id and avatar.session_info hold the Atmee session after
start(); avatar.on("avatar_disconnected", ...) fires if the avatar
participant leaves while your agent is still running.
Avatar versions
Atmee avatars come in generations, and the plugin states which one it renders:
v1— a talking-head avatar generated from one portrait, lip-synced to your agent's speech. This is what the plugin renders today, and the default everywhere, so existing code needs no change.v2— Atmee's next avatar generation. It is not yet available through the plugin:AvatarSession(..., avatar_version="v2")andcreate_avatar(..., avatar_version="v2")raiseValueErrorright away, before any request is sent.
from livekit.plugins import atmee
avatar = atmee.AvatarSession(avatar_id=..., avatar_version="v1") # explicit is fine
avatar.avatar_version # "v1"
avatar.session_info.avatar_version # "v1", after start()
atmee.SUPPORTED_AVATAR_VERSIONS # frozenset({"v1"})
atmee.AvatarVersion is the Literal["v1", "v2"] type of the argument;
AtmeeAPI.create_avatar() takes the same avatar_version and reports it as
AvatarInfo.version. The version is plugin-side surface: it never appears in
the requests sent to the Atmee API, and it is unrelated to AvatarInfo.kind
(conversational | render_only), which says whether an avatar has a voice
and persona.
Lifecycle and billing
Billing starts when the avatar sees your agent in the room and runs by the minute. It stops when
- your agent leaves the room or the room closes (the rendering worker notices and reports the end itself — a crashed agent stops being billed within seconds),
aclose()runs — registered automatically as a job shutdown callback; it ends the session and removes the avatar participant from your room,- the rendering worker itself dies or loses contact: it proves it is alive once a minute, and a session silent for a few minutes is closed and billed only up to the last proof,
- or the session reaches
max_duration_seconds.
Errors
All API failures raise atmee.AtmeeException with status_code, code
(the API's error code, e.g. insufficient_credits, avatar_not_renderable,
missing_publish_on_behalf) and message. Two subclasses are worth
handling: AtmeeNoCapacityError (every rendering worker is busy; check
retry_after and try again later) and AtmeeAvatarNotReadyError (the avatar
has no portrait yet).
Examples
-
examples/agent.py— a Deepgram + OpenAI + Silero agent with an Atmee avatar; run withpython examples/agent.py devand open the LiveKit Agents Playground. -
examples/gpt_live_1_agent.py— the same agent using OpenAIgpt-live-1(LiveKit's nativeGPTLiveModel) instead of the STT/LLM/TTS pipeline. Runsdelegation="client"by default — gpt-live-1 answers itself, no backend round-trip, lowest latency, backchannels while you talk — with expressive full-duplex instructions.GPT_LIVE_DELEGATION=responsesswitches to a backend reasoning model (gpt-5.6-luna): smarter and able to speak unprompted, but slower on every turn. Needslivekit-agents[openai]and an OpenAI key with gpt-live-1 access. -
examples/dispatch.py— helper for the gpt-live-1 example: prints a viewer URL, waits until you've joined, then dispatches the avatar into that room (viewer-first). Run the worker in one terminal and this in another. -
examples/create_avatar.py— create an avatar from a portrait and print its id. -
tests/test_live_agent.py— a manual live smoke test: a real GPT voice agent (OpenAI LLM + TTS, silero VAD) with an Atmee avatar in a real LiveKit room, asserting the avatar joins, publishes video, and that its agent-driven audio flows. Skipped unlessRUN_LIVE_ATMEE_AGENT=1and the credentials are set:RUN_LIVE_ATMEE_AGENT=1 ATMEE_API_KEY=… ATMEE_AVATAR_ID=… \ LIVEKIT_URL=… LIVEKIT_API_KEY=… LIVEKIT_API_SECRET=… OPENAI_API_KEY=… \ uv run --group dev --with "livekit-agents[openai,silero]" \ pytest tests/test_live_agent.py -m live -s
How it works
your agent (livekit-agents) ── plugin ──► Atmee API (X-Api-Key, https)
│ │
│ audio over lk.audio_stream ▼
└──────────────► your LiveKit room ◄── Atmee rendering worker
joins as atmee-avatar-agent,
publishes video + audio
AvatarSession.start()mints a LiveKit access token for the avatar participant with your LiveKit credentials: identityatmee-avatar-agent,kind: agent, aroomJoingrant for your room, and the attributelk.publish_on_behalfset to your agent's identity. Atmee receives only that token, never your secret.- It calls
POST /v1/avatars/{avatarId}/avatar_sessionswith your LiveKit URL and the token. Atmee reserves a session, starts a rendering worker, and answers as soon as the worker acknowledged the start. - The worker joins your room with the token, waits for your agent, receives
its audio over the LiveKit data stream (
lk.audio_stream, 16 kHz PCM, the standardDataStreamAudioOutput), and publishes lip-synced video and audio on behalf of your agent. - When your agent leaves or the room closes, the worker notices from inside
the room and reports the end to Atmee, which stops billing.
aclose()removes the avatar participant and ends the session explicitly as well.
Development
uv venv && uv pip install -e . --group dev
uv run pytest -q
uv run ruff check . && uv run ruff format --check . && uv run mypy
Releases are published to PyPI from v* tags by .github/workflows/publish.yml
(PyPI trusted publishing).
License
Apache-2.0.
Metadata
Release files for livekit-plugins-atmee 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| livekit_plugins_atmee-0.1.3.tar.gz | 20.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| livekit_plugins_atmee-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.2 kB
Release files / livekit_plugins_atmee-0.1.3.tar.gz
| Download URL | livekit_plugins_atmee-0.1.3.tar.gz |
|---|---|
| Size | 20.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7d316b667a9697df0a685af2f38c7d9b98bf9fe41e4701443632e1a406771d0d
|
|
BLAKE2b-256 checksum How to use checksums |
61cd7ece51df56675a9bb84a51ea267abf4bf53b93da7811d0d0df1e784ac6c3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / livekit_plugins_atmee-0.1.3-py3-none-any.whl
| Download URL | livekit_plugins_atmee-0.1.3-py3-none-any.whl |
|---|---|
| Size | 23.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c77d84197bceac82888cb3263459ded5e3f273222cbec05e5cca6227c6c259aa
|
|
BLAKE2b-256 checksum How to use checksums |
613f3749fcd70b1cb8dd9187658d8a5327606100330c3f9969e9d436ee9a97d5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log