Configuration
All configuration is via environment variables, loaded through Pydantic Settings. Copy .env.example to .env and edit.
Environment Variables
Auth (Casdoor SSO + owner)
The gateway is owner-only: the browser signs in via Casdoor (JWT), MCP/CLI
clients use owner-minted PATs, and only OWNER_NAME may use any surface. With
CASDOOR_ENABLED=false the gateway runs in dev-owner mode — permitted only on
a loopback HOST. Startup refuses SSO-enabled-with-missing-config and
SSO-disabled-off-loopback.
| Variable |
Description |
Default |
Required |
CASDOOR_ENABLED |
Enable Casdoor SSO |
false |
No |
CASDOOR_ENDPOINT |
Casdoor base URL |
https://id.ouranos.helu.ca |
If SSO on |
CASDOOR_CLIENT_ID |
Casdoor application client ID |
— |
If SSO on |
CASDOOR_CLIENT_SECRET |
Casdoor application client secret |
— |
If SSO on |
CASDOOR_ORG_NAME |
Casdoor organization |
heluca |
No |
CASDOOR_APP_NAME |
Casdoor application name |
— |
No |
OWNER_NAME |
Casdoor username of the single operator |
— |
If SSO on |
PUBLIC_BASE_URL |
Public base URL for OAuth discovery (else derived) |
— |
No |
SIP Trunk
| Variable |
Description |
Default |
Required |
SIP_TRUNK_HOST |
Your SIP provider hostname |
— |
Yes |
SIP_TRUNK_PORT |
SIP signaling port |
5060 |
No |
SIP_TRUNK_USERNAME |
SIP auth username |
— |
Yes |
SIP_TRUNK_PASSWORD |
SIP auth password |
— |
Yes |
SIP_TRUNK_DID |
Your phone number (E.164) |
— |
Yes |
SIP_TRUNK_TRANSPORT |
Transport protocol (udp, tcp, tls) |
udp |
No |
Server
| Variable |
Description |
Default |
Required |
HOST |
Bind address. Off-loopback requires CASDOOR_ENABLED=true |
0.0.0.0 |
No |
PORT |
Bind port |
8000 |
No |
DEBUG |
SQLAlchemy echo + uvicorn reload |
false |
No |
LOG_LEVEL |
Root log level (debug/info/warning/error) |
info |
No |
LOG_FORMAT |
text (human-readable) or json (structured, for Loki) |
text |
No |
LOG_FORMAT=json renders one JSON object per line, including uvicorn's access
log — method, path, status_code (numeric, so it can be range-filtered) and
client_addr arrive as queryable fields rather than a formatted string. The
Docker image sets it; text is the default so local development stays readable.
Safety
| Variable |
Description |
Default |
Required |
MAX_CONCURRENT_CALLS |
Cap on simultaneous outbound calls |
4 |
No |
USE_MOCK_SIP |
Run the mock SIP engine — no real calls. Must be asked for explicitly; an unconfigured trunk without it fails startup |
false |
No |
SIP_ENGINE |
sippy (signalling only — no audio reaches the classifier) or pjsua2 (call control + media) |
sippy |
No |
Gateway
| Variable |
Description |
Default |
Required |
GATEWAY_SIP_HOST |
Bind address for the device-registration listener |
0.0.0.0 |
No |
GATEWAY_SIP_PORT |
Port for device SIP registration |
5060 |
No |
GATEWAY_SIP_DOMAIN |
SIP domain devices register against |
gateway.local |
No |
LLM
| Variable |
Description |
Default |
Required |
LLM_BASE_URL |
OpenAI-compatible API endpoint |
http://localhost:11434/v1 |
No |
LLM_MODEL |
Model name for IVR analysis |
llama3 |
No |
LLM_API_KEY |
API key (if required) |
not-needed |
No |
LLM_TIMEOUT |
Request timeout in seconds |
30.0 |
No |
LLM_MAX_TOKENS |
Max tokens per response |
1024 |
No |
LLM_TEMPERATURE |
Sampling temperature |
0.3 |
No |
Speech-to-Text
| Variable |
Description |
Default |
Required |
SPEACHES_URL |
Speaches/Whisper STT endpoint |
http://localhost:22070 |
No |
SPEACHES_MODEL |
Whisper model name |
whisper-large-v3 |
No |
Database
| Variable |
Description |
Default |
Required |
DATABASE_URL |
PostgreSQL connection string. Startup exits with a readable error if unset |
— |
Yes |
Notifications
| Variable |
Description |
Default |
Required |
NOTIFY_SMS_NUMBER |
Phone number for SMS alerts (E.164) |
— |
No |
Receptionist
| Variable |
Description |
Default |
Required |
RECEPTIONIST_ENABLED |
Answer inbound calls with the AI receptionist |
true |
No |
RECEPTIONIST_GREETING_TEMPLATE |
Spoken greeting |
"Hi, you've reached Robert's line. Who's calling, and what's this about?" |
No |
RECEPTIONIST_MESSAGE_PROMPT |
Spoken prompt before recording a message |
"Please leave your message after the tone." |
No |
RECEPTIONIST_LLM_PERSONA |
System prompt shaping the receptionist's decisions |
See config.py |
No |
RECEPTIONIST_LISTEN_TIMEOUT_S |
Seconds to wait for the caller to speak |
15.0 |
No |
RECEPTIONIST_END_OF_UTTERANCE_SILENCE_S |
Silence marking the end of a turn |
1.2 |
No |
RECEPTIONIST_MESSAGE_MAX_SECONDS |
Voicemail cap |
90 |
No |
Audio Classifier
| Variable |
Description |
Default |
Required |
CLASSIFIER_WINDOW_SECONDS |
Audio window size for classification |
3.0 |
No |
CLASSIFIER_SILENCE_THRESHOLD |
RMS below this = silence |
0.85 |
No |
CLASSIFIER_MUSIC_THRESHOLD |
Spectral flatness below this = music |
0.7 |
No |
CLASSIFIER_SPEECH_THRESHOLD |
Spectral flatness above this = speech |
0.6 |
No |
Hold Slayer
| Variable |
Description |
Default |
Required |
MAX_HOLD_TIME |
Maximum seconds to wait on hold |
7200 |
No |
HOLD_CHECK_INTERVAL |
Seconds between audio checks |
2.0 |
No |
DEFAULT_TRANSFER_DEVICE |
Device to transfer to |
sip_phone |
No |
Recording
| Variable |
Description |
Default |
Required |
RECORDING_DIR |
Directory for WAV recordings |
recordings |
No |
RECORDING_MAX_SECONDS |
Maximum recording duration |
7200 |
No |
RECORDING_SAMPLE_RATE |
Audio sample rate |
16000 |
No |
Settings Architecture
Configuration is managed by Pydantic Settings in config.py:
LLM settings are nested under settings.llm as a LLMSettings sub-model.
Deployment
Development
Production
Note: Hold Slayer is designed as a single-process application. Multiple workers would each have their own SIP engine and call state. For high availability, run behind a load balancer with sticky sessions.
Docker
Port mapping:
8000 — HTTP API + WebSocket + MCP
5080/udp — SIP device registration
10000-20000/udp — RTP media ports