# --- Gradium (STT + TTS) --- GRADIUM_API_KEY= # Optional override; defaults to https://api.gradium.ai/api GRADIUM_BASE_URL= # REQUIRED: the default agent voice UID for outbound calls (a voice in your own # Gradium account). Per-tenant clones override it. No default is shipped. AGENT_VOICE_ID= # --- Gradium real-time translation (speech-to-speech, /translate) --- # Optional override; defaults to GRADIUM_BASE_URL, else https://api.gradium.ai/api GRADIUM_URL= # Optional s2s model (default: s2s-translate). # GRADIUM_TRANSLATE_MODEL=s2s-translate # Force a specific voice for translated output (otherwise the tenant's clone, # falling back to a built-in per-language voice). Required to translate into a # language with no built-in voice (built-ins: en, fr, es, de, pt). # GRADIUM_TRANSLATE_VOICE_ID= # --- LLM (read directly by gradbot.run()) --- # gradbot expects an OpenAI-compatible LLM endpoint. Set OPENAI_API_KEY for # OpenAI's API; or set LLM_BASE_URL for any other OpenAI-compatible host # (Groq, Fireworks, Together, vLLM, LM Studio, etc.). LLM_MODEL is auto- # detected if the endpoint exposes exactly one model. OPENAI_API_KEY= LLM_BASE_URL= LLM_MODEL= # --- Twilio --- # Used for the REST dial-out call. Prefer API-Key auth in production: TWILIO_API_KEY_SID= TWILIO_API_KEY_SECRET= # Legacy auth — also used to verify Twilio's HMAC-SHA1 signature on /twilio/* TWILIO_ACCOUNT_SID= TWILIO_AUTH_TOKEN= TWILIO_PHONE_NUMBER=+1xxxxxxxxxx # Optional: overrides TWILIO_PHONE_NUMBER as the "from" on outbound calls TWILIO_FROM_NUMBER= # Voicemail detection. "Enable" (default), "DetectMessageEnd", or "disable". # "Enable" returns AnsweredBy within ~3s of pickup; "DetectMessageEnd" # waits for the voicemail beep before connecting (slower but better for # leaving messages). TWILIO_MACHINE_DETECTION=Enable # --- Voicemail behaviour --- # When AnsweredBy indicates a machine, /twilio/voice can leave a brief # localized voicemail via Twilio Say before hanging up. Off by default — # some businesses are surprised by AI voicemails. ENABLE_VOICEMAIL_MESSAGE=false # Used in the voicemail message text ("calling on behalf of {OPERATOR_NAME}"). OPERATOR_NAME=Acme # --- Public reachability (the Mac must be reachable by Twilio) --- # Cloudflare tunnel, ngrok, or any equivalent PUBLIC_HTTP_URL=https://your-tunnel.example.com PUBLIC_WS_URL=wss://your-tunnel.example.com # --- Bridge security --- # REQUIRED. Bearer token for /dial, /result, /history, /tenants, /campaign, # /calls/live, /diagnostics AND the signing key for web session cookies + # magic links. Must be >=16 chars; the app refuses to start without it. # Generate: python -c "import secrets; print(secrets.token_urlsafe(36))" BRIDGE_API_KEY= # Local-dev escape hatch ONLY. When 1/true, missing BRIDGE_API_KEY / # TWILIO_AUTH_TOKEN disable auth + Twilio signature checks, and the Telegram # bot accepts everyone. NEVER set this on a public host. ALLOW_INSECURE_LOCAL= # Set to 1 only for local HTTP dev to drop the Secure flag on session cookies. INSECURE_COOKIES= # --- Concurrency cap (Gradium STT enforces a per-account session limit) --- # 0 / unset disables the cap GRADBOT_MAX_CONCURRENT=3 # --- Call lifecycle --- # Hard hangup deadline per call (seconds). 0 disables the watchdog. MAX_CALL_DURATION_SECONDS=600 # --- Per-tenant rate limits --- # Default daily quota per tenant (operator calls bypass this). DEFAULT_DAILY_QUOTA=20 # Default max concurrent in-flight calls per tenant. DEFAULT_CONCURRENT_CAP=3 # --- Database --- # SQLite path for tenants + call history. Default: ~/.gradphone/gradphone.db GRADPHONE_DB= # --- dial CLI / bot --- # Where the bridge is listening (default: http://127.0.0.1:8082) GRADBOT_BRIDGE_URL=http://127.0.0.1:8082 # --- Telegram bot (optional; only needed for `python -m gradphone.bot`) --- TELEGRAM_BOT_TOKEN= # Authorization allow-list (comma-separated Telegram user IDs). When set, ONLY # these IDs may use the bot at all — the strongest gate. Find your ID via # @userinfobot. The bot FAILS CLOSED: if both ALLOWED_TELEGRAM_IDS and # WORKSHOP_CODE are blank it refuses everyone (set ALLOW_INSECURE_LOCAL=1 to # override for local dev only). ALLOWED_TELEGRAM_IDS= # Alternative to the allow-list for events: when set, the bot is open but # /register requires this code (`/register `), so only attendees enroll. WORKSHOP_CODE= # --- Outbound destination guard --- # By default ALL outbound destinations are refused. Either list allowed # numbers (comma-separated E.164) or explicitly open it up. Keep the # allowlist tight when third parties (e.g. workshop attendees) can dial. ALLOW_ARBITRARY_OUTBOUND=false OUTBOUND_ALLOWLIST= # --- Inbound / receptionist mode (optional) --- # When true, the bridge ANSWERS incoming calls to TWILIO_PHONE_NUMBER as the # owner's receptionist. You must also point the Twilio number's Voice webhook # at https:///twilio/voice (POST). Off by default so the line is not # live by accident. OPERATOR_NAME is used as the owner's name in the greeting. ENABLE_INBOUND=false # Optional: answer in a specific cloned voice id. Blank = default per-language. INBOUND_VOICE_ID= INBOUND_LANGUAGE=en # Telegram chat id that receives relayed receptionist messages (take_message # tool). Falls back to the first id in ALLOWED_TELEGRAM_IDS. OPERATOR_TELEGRAM_ID= # --- Agent memory --- # Memory is stored locally in the SQLite DB (GRADPHONE_DB) — no extra config. # Post-call fact extraction reuses the LLM_BASE_URL / LLM_MODEL above; if the # LLM endpoint is unreachable, the `remember` tool still captures facts the # user states explicitly. (An external memory backend like Honcho can slot in # behind src/gradphone/memory.py later.) # --- Assistant mode: Gmail (optional) --- # Read-only inbox access for `/callme` assistant calls ("summarize my # emails this week"). Uses IMAP with a Gmail App Password (2FA required): # https://myaccount.google.com/apppasswords # Leave blank to disable email summaries — assistant mode still works for # plain conversation. GMAIL_ADDRESS= GMAIL_APP_PASSWORD= # Live web search for assistant-mode calls (web_search tool). When the caller # asks about current facts the LLM can't know (today's news, weather, recent # events, prices), the agent queries Linkup's sourced-answer search: # https://app.linkup.so (treat the key like a password) # Leave blank to disable web search — assistant mode still works without it. LINKUP_API_KEY= # --- Voice turn-taking / barge-in / fillers -------------------------------- # Post-assistant re-prompt nudge — NOT the caller's turn-end (a common # misread). This is the seconds of silence AFTER the assistant finishes before # gradbot injects "..." so the model prompts continuation ("are you still # there?"). 2–3s nagged during a caller's normal think-gap; 8.0s lets them # gather a thought first. Lower = the clone checks in sooner on silence; higher # = more patience. (The caller's real end-of-turn is gradbot's hardcoded VAD # cutoff; we soften it via GRADBOT_STT_PADDING_BONUS below.) GRADBOT_SILENCE_TIMEOUT_S=8.0 # STT endpointing padding (Gradium ASR). Positive = the model finalizes the # caller's turn LATER, so a mid-sentence pause is less likely to be read as # end-of-turn and cut the caller off; negative = snappier but more cut-offs. # Range -4..4 (Gradium "Transcription Settings"). 1.5 is a telephony-friendly # start; raise toward 2–3 if still cutting, lower if replies feel laggy. GRADBOT_STT_PADDING_BONUS=1.5 # How promptly partial transcripts flush (gradbot default 0.5s). GRADBOT_FLUSH_DURATION_S=0.5 # Barge-in: when the caller talks over the clone, flush Twilio's buffered # audio so the clone stops immediately. On by default; set to 0 to disable. BARGE_IN=1 # Fillers (EXPERIMENTAL, default off): play a short clip in the clone's voice # during the think-gap so the line isn't silent while the LLM responds. Pokes # audio into Twilio out-of-band from gradbot — test on a real call before # enabling for a demo. Set ENABLE_FILLERS=1 to turn on. ENABLE_FILLERS=0 # FILLER_PHRASE="Mm, let me see." # FILLER_DELAY_S=0.7 # --- Database (multi-tenant: one profile per registered user) --------------- # Dual-backend via SQLAlchemy. Leave everything below blank for the default: # local SQLite at GRADPHONE_DB (~/.gradphone/gradphone.db), schema auto-created # on startup — zero setup for local dev/testing. GRADPHONE_DB= # For the hosted deployment, point at the in-cluster Postgres. Either set a full # URL (takes precedence)… # DATABASE_URL=postgresql+psycopg://user:pass@host:5432/gradphone # …or the discrete parts (matches gradium-serve's api service): # DATABASE_TYPE=postgresql # DATABASE_HOST=... # DATABASE_PORT=5432 # DATABASE_USER=... # DATABASE_PASSWORD=... # DATABASE_NAME=gradphone # On Postgres the schema is owned by Alembic — the deployment init-container # runs `alembic upgrade head` before the app starts. (Set DB_CREATE_ALL=1 to # let the app create tables itself instead, e.g. for a throwaway DB.) # Connection pool (Postgres only): DB_POOL_SIZE=10 DB_MAX_OVERFLOW=20