WordloopWordloop
Reference

Configuration

Every environment variable, config key, and feature flag, organised by service.

Configuration

Every service in the Wordloop platform loads its configuration from environment variables, following the Twelve-Factor App config principle. This page is the canonical catalogue of those variables — what they do, what their defaults are, and which service owns them.

Local defaults are generated by ./dev setup env. The variables listed here are the full contract; your local .env files typically override only the subset you need.

Common variables

Variables consumed by multiple services.

VariableService(s)Default (local)Purpose
APP_ENValldevelopmentdevelopment, test, staging, production. Controls auth mode, logging verbosity, and feature defaults.
DATABASE_URLcorederivedPostgres connection string.
PUBSUB_EMULATOR_HOSTcore, mllocalhost:8085Local Pub/Sub emulator. Unset in production.
OTEL_EXPORTER_OTLP_ENDPOINTallhttp://localhost:4318Collector endpoint for traces, metrics, and logs.
LOG_LEVELallinfodebug, info, warn, error.

wordloop-core

VariableDefaultPurpose
CORE_PORT4002HTTP + WebSocket port.
CLERK_SECRET_KEY—Backend Clerk key for JWT verification.
CLERK_PUBLISHABLE_KEY—Frontend-shared key; surfaced for debug.
STORAGE_BUCKETwordloop-local-audioGCS bucket for audio artefacts.
AUDIO_URL_SIGNING_KEY—HMAC key for audio playback proxy links (GET /meetings/{id}/audio). Signs {meeting_id}:{user_id}:{exp}, keeping playback links a per-user capability. Required outside APP_ENV=test — Core refuses to start without it. Dedicated to this purpose: reusing SERVICE_AUTH_TOKEN would let every holder of the service token mint a playback link for any user's audio. Generate with openssl rand -hex 32. In APP_ENV=test only, an unset key is derived from SERVICE_AUTH_TOKEN via HMAC (never the token itself), with a startup warning.

RECORDING_* — live audio durability and recovery

Tunes the live-recording lifecycle: segment storage, progress events, gap recovery, and shutdown timing. Source: services/wordloop-core/internal/config/config.go (RecordingConfig).

VariableDefaultPurpose
RECORDING_SEGMENT_TARGET_MS2000Target duration of one GCS storage segment.
RECORDING_PROGRESS_EVERY_FRAMES100Emits AudioStoredProgressEvent every N frames.
RECORDING_PROGRESS_INTERVAL_SECONDS10Emits AudioStoredProgressEvent at least this often.
RECORDING_GAP_UPLOAD_DEADLINE_SECONDS1800How long after stop gap chunks are accepted.
RECORDING_GAP_SWEEP_INTERVAL_SECONDS30Deadline sweeper cadence — how often Core checks for recordings past their gap-upload deadline.
RECORDING_STOP_SETTLE_MS500How long Core keeps accepting trailing audio frames after a stop command.
RECORDING_ML_DRAIN_TIMEOUT_SECONDS5Bounds the ML drain call during stop.
RECORDING_DISCONNECT_GRACE_SECONDS120How long Core waits after a user's last WebSocket connection closes before stopping their live recording with reason connection_closed. A reconnect or resume inside the window cancels the stop. 0 stops the recording as soon as the connection drops. The default covers four attempts at the App's 30s maximum reconnect backoff.
RECORDING_MAX_CHUNK_BYTES1048576 (1 MiB)Largest gap-recovery chunk accepted.
RECORDING_MAX_DURATION_SECONDS14400 (4h)Absolute ceiling for one recording. A StartRecordingCommand may lower it per recording, never raise it.
RECORDING_AUDIO_URL_TTL_SECONDS900 (15 min)How long a playback link from GET /meetings/{id}/audio-url stays valid.

wordloop-ml

VariableDefaultPurpose
ML_PORT4003FastAPI port.
MODEL_PROVIDERanthropicChooses which model adapter to load.
ANTHROPIC_API_KEY—Set when MODEL_PROVIDER=anthropic.
OPENAI_API_KEY—Set when MODEL_PROVIDER=openai.
ML_CACHE_TTL_SECONDS3600Cache lifetime for deterministic model calls.

ML_* — live insight cadence, streaming durability, and no-audio detection

Source: services/wordloop-ml/src/wordloop/config/settings.py (Settings).

VariableDefaultPurpose
ML_TALKING_POINT_CADENCE_SEGMENTS4Unprocessed live transcript segments that trigger a draft talking-point extraction.
ML_TALKING_POINT_CADENCE_SECONDS30.0Seconds since the last extraction after which the next segment triggers one regardless of segment count (whichever cadence fires first).
ML_TASK_CADENCE_SEGMENTS12Unprocessed live transcript segments that trigger a draft task extraction.
ML_SEGMENT_FEATURE_MAX_PER_SPEAKER8Upper bound of batch transcript segments per speaker label that get a voice feature_vector (the longest ones are chosen). 0 disables feature emission.
ML_SEGMENT_FEATURE_DIMENSIONS192Dimension Core's transcript_segments.feature_vector / people.voice_vector columns (pgvector vector(192)) accept; matches the ECAPA-TDNN ONNX output. An embedding model change must be paired with a Core migration — see ADR 0008.
ML_BACKPRESSURE_QUEUE_DEPTH64Per-session audio send-queue depth above which backpressure is reported.
ML_AUDIO_QUEUE_CAPACITY512Hard bound of the per-session send queue; frames beyond it are dropped and counted.
ML_INSIGHT_WARNING_GAP_FRAMES300A single sequence gap of this many frames (30s at 100ms) reports insight_warning.
ML_DRAIN_TIMEOUT_SECONDS0.5Upper bound DELETE /streaming/{id} waits for the queue to drain before answering.
ML_INSIGHT_CONTEXT_SEGMENTS60Recent transcript segments pulled from Core to rebuild insight context on resume.
ML_NO_AUDIO_WINDOW_SECONDS10.0Audio duration over which every decodable frame must be silent before no_audio_detected is reported (the test stack uses 1).
ML_NO_AUDIO_RMS_THRESHOLD50.0Per-frame RMS (int16 scale, 0-32767) below which a PCM16 frame counts as silent.
ML_NO_AUDIO_BATCH_FRAMES10Containerised (webm/ogg) frames decoded together for no-audio scoring. At the browser's 100ms timeslice this is ~1s of audio per codec call, so ffmpeg runs about once a second per session instead of once per frame. Raw PCM16 streams ignore this — they are scored inline without a codec.

wordloop-app

VariableDefaultPurpose
NEXT_PUBLIC_CORE_URLhttp://localhost:4002URL the browser uses to reach Core.
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY—Clerk frontend key.
APP_PORT4001Next.js port.

Feature flags

Feature flags are served dynamically — they are not environment variables. See the flag dashboard for the current state and owners. Progressive-delivery principles (Progressive Delivery) govern how flags are created, rolled, and retired.

Further reading

On this page