This page is for operators who run Voice.GradientFlow for a workspace. It is the only page in these docs that lists variable names. End users enter keys in the builder’s Settings panel; see Keys. Copy .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.
A variable that is not plumbed into the Compose file is silently ignored by the container. When you add a new variable, add it to the Compose service as well.The shipped docker-compose.yml does not yet pass these through: GEMINI_API_KEY, TOGETHER_API_KEY, MOONSHOT_API_KEY, DEEPSEEK_API_KEY, CEREBRAS_API_KEY, FIREWORKS_API_KEY, PERPLEXITY_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY, OVHCLOUD_API_KEY, AZURE_API_KEY, TELNYX_API_KEY, RIME_API_KEY, NEUPHONIC_API_KEY, RESEMBLE_API_KEY, ASSEMBLYAI_API_KEY, and the avatar keys except LEMONSLICE_API_KEY. Setting them in .env alone has no effect on a Docker install; add each one you need to the agent service (and to app, so Settings shows it as provided by the workspace).

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:
  1. 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.
  2. 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 v2 with what it rewrote.
  3. Once that line shows failedFiles: 0, remove the old key (DATA_ENCRYPTION_KEY=2:<new>) and restart both again.
The same startup pass encrypts keys still stored in plaintext in versions published before encryption existed.

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_KEY is shared across the OpenAI LLM, TTS and Whisper STT, and by LLM (Override).
  • BUILDER_DEEPGRAM_API_KEY is for dictation in the builder only; DEEPGRAM_API_KEY is for Deepgram inside agents.
  • BUILDER_ANTHROPIC_API_KEY is for the AI builder only; ANTHROPIC_API_KEY is 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.