.env.example to .env at the project root and fill in values. Restart the affected service after a change. On the hosted VPS, variables carry a DEV_ or STAGING_ prefix per environment; see the operations runbook in the repository. Use .env.example as the exhaustive template, and see docs/adaptive-latency-routing.md in the repository for routing, candidate-pack, turn-detection and rollout controls.
Voice infrastructure
AI builder
Agent runtime
Preemptive TTS is effective only when the Persona setting is on, both generation gates permit it, the master, allowlist and cohort gates pass, the pipeline is cascaded, TTS is present and streaming, and output is neither SIP nor avatar. Invalid values fail closed. Decisions are frozen at session start; recreate the worker after changing these variables. For rollback, set
PREEMPTIVE_TTS_ENABLED=false and recreate the worker.
Session admission
These limits cover Playground sessions (Draft and Live), public-API web sessions and share-link calls. A share link is charged like one user, so the per-user limits are also how many calls one link can have at once and start per window. SIP calls dispatch directly through the media server, so hosted defaults keep worker capacity in reserve for them.
Active leases do not expire on time alone. Explicit cleanup and the verified
room_finished webhook release them; a saturated request reconciles them against actual rooms.
Data encryption key
Generate a key with
openssl rand -base64 32. Give the app and the worker the same value, keep a copy somewhere safe outside the server, and never commit it: without it, saved keys and SIP passwords cannot be read back, and published versions that carry keys cannot start calls.
A production server (NODE_ENV=production) will not start without a valid key. Elsewhere a missing key is a warning, and only saving a key, publishing with keys or adding a SIP password needs it. A malformed key always stops the app and the worker at startup.
The variable holds a key ring, newest first: 2:<new key>,1:<old key>. A key with no N: in front is version 1. The first key encrypts; every key in the ring decrypts. To rotate:
- Generate a new key and put it first, keeping the old one after it:
DATA_ENCRYPTION_KEY=2:<new>,1:<old>. The number must be higher than any version in use. - Restart the app and the worker. At startup the app re-encrypts every stored secret under the new key, and logs
[secrets] stored secrets under key v2with what it rewrote. - Once that line shows
failedFiles: 0, remove the old key (DATA_ENCRYPTION_KEY=2:<new>) and restart both again.
Platform admins
Provider keys for agents
Keys set here apply to every user and to published agents. A user can still enter a different key in Settings for their own browser session. A workspace admin can also save a key from Settings, on any install: Save stores it encrypted in the database, for every agent in that workspace. A call uses a key pasted in the browser tab first, then the workspace’s saved key, then the key set here. Saved keys go with every Playground call at once, and are stored with each version published from then on; a published version keeps the keys it was published with until it is published again. A saved key goes only to its provider’s own endpoint, never to a base URL an agent’s author typed (the rule the worker applies to the keys set here), and only calls started by the workspace’s editors, admins and owner use saved keys.LLM
Ollama and Custom (OpenAI-compatible) models have no variable. They run without a key, and a key a user pastes in Settings travels with the agent. The worker never sends one of its own keys to a base URL set in an agent, so
OPENAI_API_KEY is not used for OpenAI with a Base URL either; that agent needs its key pasted in Settings. See Your own model server.
TTS
STT
Avatars
Tools and after-the-call steps
Storage
Authentication and public address
Health and metrics
The app serves two unauthenticated endpoints for the container healthcheck, the deploy workflow and any external monitor:
The Compose healthcheck on
app hits /api/health; the production deploy waits on /api/ready, so a media-server restart never marks the app container unhealthy.
The worker can expose Prometheus metrics for its job pool, load and latency:
The media server also serves Prometheus metrics on its own port (
7880/metrics). Nothing in the stack scrapes either endpoint by default; point your own Prometheus at them.
Notes
OPENAI_API_KEYis shared across the OpenAI LLM, TTS and Whisper STT, and by LLM (Override).BUILDER_DEEPGRAM_API_KEYis for dictation in the builder only;DEEPGRAM_API_KEYis for Deepgram inside agents.BUILDER_ANTHROPIC_API_KEYis for the AI builder only;ANTHROPIC_API_KEYis for Anthropic inside agents.- Playground sessions resolve server provider keys inside the worker instead of copying them into room metadata. Keys a user typed in Settings take precedence for that user’s session. They reach the worker in the agent dispatch, never in room metadata, so no one else in the call receives them.
- Published dispatch metadata contains only a schema version and a numeric deployment id. The worker resolves the matching snapshot and fails startup if it is missing or invalid.
- Share links store pre-filled values on the server, not in the URL. When a caller starts a call, the values and the inputs the caller answered go into that call’s short-lived join token, with input-variable defaults.