Environment
Every variable the kit reads, what it unlocks, and what happens when it is unset.
Every variable this kit reads. Names only — values live in your secret store,
and env/README.md in the repository says where each one is.
The list in
env/.env.exampleis generated from what the code actually reads, not from memory. A list maintained by hand goes stale the first time somebody is in a hurry, and a stale list is worse than none: it is read as complete, so the missing key is not looked for, it is assumed not to exist.
The switchboard#
| Variable | Unset means |
|---|---|
LIVEKIT_URL | no media server to point clients at |
LIVEKIT_API_KEY | token minting returns 503 |
LIVEKIT_API_SECRET | token minting returns 503, and webhooks are refused entirely |
REGIONS_JSON | single-region mode; caller_country is ignored |
LIVEGRID_REGION | webhook events are stored with a NULL region |
Control plane and API#
| Variable | Unset means |
|---|---|
UDK_SECRET | every request to the control plane is authorised. Local development only |
LIVEGRID_CONTROL_URL | the API cannot reach the control plane; /public/live reports unreachable |
LIVEGRID_API_URL | the worker has nowhere to post records |
LIVEGRID_INGEST_SECRET | ingest answers 503 rather than accepting anything |
LIVEGRID_DEFAULT_ORG_ID | calls are recorded with org_id NULL rather than dropped |
CUSTOMER_JWT_SECRET | /public/* cannot verify a customer token |
MCP_JWT_SECRET | no MCP token can be verified, so every caller is anonymous |
UDK_SECRET unset is the one to be careful about. It is treated as local
development and the service says so on boot — but nothing stops it running that
way in production, where it is a public token-minting API.
The render box#
| Variable | Unset means |
|---|---|
LIVEGRID_RENDER_SECRET | the render service refuses to start, and avatar enrolment claims are refused |
LIVEGRID_RENDER_URL | the worker cannot reach the renderer |
LIVEGRID_ARTIFACT_DIR, MUSETALK_ROOT, LIVEGRID_FACE_MODEL | paths, not secrets — the box will not render without them |
GOOGLE_API_KEY | this unit currently has no quality gate |
That last one deserves its wording. The renderer runs without it; what stops is the review pass that watches the output for motion. Judging an avatar is a temporal problem — does the mouth track the words, do the eyes blink — and it has already overturned a claim that hand-picked frames wrongly supported. Treat "unset" as "nothing is checking", not as "fine".
The agent#
LLM_PROVIDER, LLM_API_KEY, LLM_BASE_URL, LLM_MODEL, TTS_MODEL,
TTS_VOICE, STT_MODEL, STT_LANGUAGE, AGENT_NAME,
MIN_ENDPOINTING_DELAY_S, AVATAR_PROVIDER, AVATAR_ID.
AGENT_NAME is the one that produces a silent failure: it is the fleet name
start_call dispatches to. A worker registered under a different name joins
nothing, and the call simply never gets an agent.
Avatar vendors#
One key per vendor, named on Providers and pricing.
A vendor with no key set is still listed — it is only excluded from
available: true.
Tuning knobs#
LIVEGRID_TARGET_WIDTH, LIVEGRID_TARGET_HEIGHT, LIVEGRID_FACE_PAD,
LIVEGRID_BOTTOM_RAMP, LIVEGRID_SIDE_RAMP, LIVEGRID_JPEG_QUALITY,
LIVEGRID_MAX_CONCURRENT.
Every one has a measured default, and the reason each number is what it is lives
beside it in render/enroll.py and render/musetalk_renderer.py. Override
deliberately, not to see what happens.
Never commit a value#
env/.env.example holds key names. A private repository is not a secret store —
it is copied into every clone, every CI runner and every agent worktree.