Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 72 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ LLM_MAX_TOOL_ROUNDS=5
# Wall-clock cap on one whole agentic turn (langgraph engine only).
LLM_AGENT_TIMEOUT_S=30.0

# Per-request context ceiling for the vLLM server (not a memory reservation --
# KV cache is paged in on demand). At --gpu-memory-utilization 0.50 the default
# Qwen3-30B-A3B-FP8 gets ~293k tokens of KV cache, so 49152 leaves worst-case
# room for ~6 concurrent full-length sequences: three sit at 50% of the cache,
# four at 67%. Raise toward the model's native 262144 only if you also drop
# --max-num-seqs, or long requests will preempt each other mid-call.
MAX_MODEL_LEN=49152

# Extra args appended to the vLLM server command. The default enables native
# tool-call parsing with the "hermes" parser (correct for the default Qwen3
# model). Set to match your model — e.g. for openai/gpt-oss-*:
Expand Down Expand Up @@ -169,9 +177,20 @@ ENABLE_PLUGIN_AUTODISCOVERY=true
WEATHER_LATITUDE=
WEATHER_LONGITUDE=

# WEB_SEARCH tool: URL of a SearxNG instance. The compose files ship one
# behind a profile: `docker compose --profile search up -d` then set
# SEARXNG_URL=http://searxng:8080
# Home/base address, injected into the system prompt so the agent knows where
# "here"/"home" is. The MAP tool routes from the WEATHER_LATITUDE/LONGITUDE
# coordinates above; this is the human-readable label. Empty omits it.
# Example: 851 Chalcedony St, San Diego
AGENT_LOCATION=

# MAP tool: driving distance/time + directions via OpenStreetMap (keyless).
# Needs WEATHER_LATITUDE/LONGITUDE for the "from home" origin.
ENABLE_MAP_TOOL=true

# WEB_SEARCH tool: URL of a SearxNG instance. The base compose starts one by
# default (open-webui depends on it); docker-compose.dgx.yml keeps it behind a
# profile (`docker compose -f docker-compose.dgx.yml --profile search up -d`).
# Then set SEARXNG_URL=http://searxng:8080
SEARXNG_URL=
WEB_SEARCH_MAX_RESULTS=3

Expand All @@ -192,6 +211,51 @@ CONTAINER_CTL_ALLOWLIST=
# Targets follow the same OUTBOUND_* dial policy below.
ENABLE_TRANSFER_TOOL=true

# ===================
# Caller identity verification (optional)
# ===================
# Prove a caller is who they claim before sensitive actions, via a static PIN
# and/or a rolling TOTP code entered on the keypad (the VERIFY tool). The
# feature stays dormant until you set a global factor below OR enroll a caller
# via POST /verify/credentials — otherwise nothing changes.
ENABLE_VERIFY_TOOL=true
# Global fallback factors, shared across all callers (per-caller enrollments in
# data/verify_credentials.json take precedence). Leave empty for no global factor.
VERIFY_PIN=
VERIFY_TOTP_SECRET=
# TOTP algorithm parameters (RFC 6238) — must match the caller's authenticator /
# issuing system. Digits per code, seconds per step, and the HMAC hash
# (SHA1|SHA256|SHA512). SHA1 / 6 digits / 30s are the near-universal defaults.
VERIFY_TOTP_DIGITS=6
VERIFY_TOTP_PERIOD=30
VERIFY_TOTP_ALGORITHM=SHA1
# Accept TOTP codes within +/- this many steps (clock-skew tolerance).
VERIFY_TOTP_WINDOW=1
# Comma-separated tool names that REQUIRE a verified caller before they run
# (fail closed). Example: VERIFY_REQUIRED_TOOLS=TRANSFER,CONTAINER_CTL
VERIFY_REQUIRED_TOOLS=
# Wrong-code attempts allowed per call, and how long to wait for the caller to
# START keying in a code (seconds).
VERIFY_MAX_ATTEMPTS=3
VERIFY_DTMF_TIMEOUT_S=20.0
# Once digits are being entered, auto-submit after this gap with no new key, so
# the caller need not press '#' and a time-based one-time code doesn't expire
# while waiting out the full window.
VERIFY_DTMF_INTERDIGIT_S=3.0
# Issuer label embedded in authenticator provisioning URIs (enrollment/QR).
VERIFY_ISSUER=General Disarray
# Spoken lines for the outbound "call and verify" flow (POST /verify/call): the
# agent dials the caller and asks them to key in their PIN/OTP, then verifies it.
VERIFY_CALL_PROMPT=Please enter your PIN or one-time code, then press pound.
VERIFY_CALL_RETRY_PHRASE=That code wasn't right. Please try again.
VERIFY_CALL_SUCCESS_PHRASE=Thank you — your identity is verified. Goodbye.
VERIFY_CALL_FAIL_PHRASE=I could not verify your identity. Goodbye.
# Where per-caller enrollments live (default: <DATA_DIR>/verify_credentials.json,
# written 0600 — it holds plaintext TOTP secrets, so it is git-ignored).
#VERIFY_CREDENTIALS_FILE=
# Open WebUI host port (base compose only; sip-agent already publishes API_PORT).
#OPEN_WEBUI_PORT=3000

# ===================
# REST API security & limits
# ===================
Expand Down Expand Up @@ -318,8 +382,11 @@ TURN_ACK_MODE=chime
# Virtual numbers: ephemeral single-use inbound extensions created via
# POST /virtual-numbers. A call dialed to one is answered with the number's
# purpose as context; the outcome is webhooked and the number cleared.
# Unused numbers expire after the TTL. Requires the PBX/registrar (if any)
# to route the range to the agent; direct SIP dialing works out of the box.
# Unused numbers expire after the TTL. With "persistent": true a number
# becomes a long-lived trigger number (n8n "SIP Agent Trigger" node) whose
# every call fires the selected events (answered/first_speech/speech/
# completed). Requires the PBX/registrar (if any) to route the range to the
# agent; direct SIP dialing works out of the box.
# VIRTUAL_NUMBERS_ENABLED=false
# VIRTUAL_NUMBER_DEFAULT_TTL_S=900
# VIRTUAL_NUMBER_MAX_TTL_S=86400
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ data/workflows.json
data/virtual_numbers.json
data/mcp_servers.json
data/personas.json
# Per-caller PIN hashes + plaintext TOTP secrets — never commit.
data/verify_credentials.json
data/verify_credentials.tmp

# Ship one example knowledge doc (indexed by RAG, exercised by the e2e
# KNOWLEDGE test); keep any other user-dropped documents ignored.
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "speaches"]
path = speaches
url = https://github.com/speaches-ai/speaches.git
[submodule "examples/n8n-nodes"]
path = examples/n8n-nodes
url = https://github.com/CHA0S-CORP/n8n-nodes.git
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ cd speaches # ruff format/check, pyright, pytest
- TTS for common phrases (greetings, acknowledgments, etc.) is pre-cached at startup from `config.phrases` (see `PhrasesConfig.get_all_phrases_for_cache`); new fixed phrases should flow through that path for instant playback. Phrases can be overridden via `PHRASES_*` env vars (JSON array or comma-separated) or a `data/phrases.json` file.
- Commit style uses emoji-prefixed conventional commits (e.g. `✨ feat:`, `fix:`).
- `docker-compose.dgx.yml` is a separate, self-contained compose for the DGX Spark / GB10 target (recently split out) — keep it in sync with the base compose when changing service wiring.
- `examples/n8n-nodes-general-disarray/` is a custom n8n node package for the agent's REST API. Its `dist/` is bind-mounted into the n8n container (`N8N_CUSTOM_EXTENSIONS=/custom-nodes`) — run `examples/n8n-nodes-general-disarray/build.sh` before recreating the n8n container, and keep the n8n service blocks in both compose files in lockstep.
- `examples/n8n-nodes/` is a git submodule of [CHA0S-CORP/n8n-nodes](https://github.com/CHA0S-CORP/n8n-nodes); the agent's custom n8n node package lives there at `packages/n8n-nodes-general-disarray/`. Its `dist/` is bind-mounted into the n8n container (`N8N_CUSTOM_EXTENSIONS=/custom-nodes`) — `git submodule update --init examples/n8n-nodes`, run `examples/n8n-nodes/packages/n8n-nodes-general-disarray/build.sh` before recreating the n8n container, and keep the n8n service blocks in both compose files in lockstep. Node code changes go to the n8n-nodes repo (PR there, then bump the submodule pointer here).
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ TTS_SPEED=1.1
| ☎️ `TRANSFER` | Blind-transfer the caller (SIP REFER) | *"Transfer me to extension 2001"* |

Several tools stay hidden until configured: `WEB_SEARCH` needs `SEARXNG_URL`
(`docker compose --profile search up -d`), `FORECAST` needs `WEATHER_LATITUDE`/`WEATHER_LONGITUDE`,
(started by default in the base compose; `--profile search` on `docker-compose.dgx.yml`), `FORECAST` needs `WEATHER_LATITUDE`/`WEATHER_LONGITUDE`,
`GPU_STATUS`/`ALERTS` need the observability stack, and `CONTAINER_CTL` needs **both** a
`CONTAINER_CTL_ALLOWLIST` and the (commented-out) docker-socket mount — the socket is
root-equivalent on the host, so enable it deliberately.
Expand Down
49 changes: 38 additions & 11 deletions docker-compose.dgx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ services:
environment:
- NVIDIA_VISIBLE_DEVICES=all
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN:-}
# Blackwell (GB10/sm_121): DeepGemm FP8 MoE asserts ("Unknown SF transformation")
# on the 30B-A3B-FP8. Keep this unless you've confirmed the 25.11 image fixed it.
- VLLM_USE_DEEP_GEMM=0 # <-- re-added (was missing)
ipc: host
ulimits:
memlock: -1
Expand All @@ -43,17 +46,16 @@ services:
vllm serve
${LLM_MODEL:-Qwen/Qwen3-30B-A3B-Instruct-2507-FP8}
--port 8000
--gpu-memory-utilization 0.60
--max-model-len 8192
--gpu-memory-utilization 0.75
--max-model-len 65536
--trust-remote-code
--enable-prefix-caching
--enable-chunked-prefill
--max-num-batched-tokens 2048
--max-num-seqs 4
--kv-cache-dtype fp8
--max-num-batched-tokens 8192
--max-num-seqs 16
${VLLM_TOOL_ARGS---enable-auto-tool-choice --tool-call-parser hermes}
ports:
# Host-side publish is parameterized so the stack can coexist with other
# services on the box; in-cluster traffic uses http://vllm:8000 regardless.
- "${VLLM_PORT:-8000}:8000"
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
Expand All @@ -71,7 +73,6 @@ services:
count: all
capabilities: [gpu]
restart: unless-stopped

# ============================================================================
# Speaches Server - Unified STT (Whisper) + TTS (Piper/Kokoro)
# OpenAI-compatible API for both transcription and speech synthesis
Expand Down Expand Up @@ -176,6 +177,10 @@ services:
# Location for WEATHER / FORECAST / QUAKES-near (empty disables those tools)
- WEATHER_LATITUDE=${WEATHER_LATITUDE:-}
- WEATHER_LONGITUDE=${WEATHER_LONGITUDE:-}
# Home address for the system prompt + MAP tool routing origin
- AGENT_LOCATION=${AGENT_LOCATION:-}
# MAP tool: driving distance/time via OpenStreetMap (needs coordinates above)
- ENABLE_MAP_TOOL=${ENABLE_MAP_TOOL:-true}
# WEB_SEARCH via SearxNG (start it with: docker compose --profile search up -d)
- SEARXNG_URL=${SEARXNG_URL:-}
- WEB_SEARCH_MAX_RESULTS=${WEB_SEARCH_MAX_RESULTS:-3}
Expand All @@ -188,6 +193,28 @@ services:
- ENABLE_TRANSFER_TOOL=${ENABLE_TRANSFER_TOOL:-true}
# DRINK_RECIPE tool (TheCocktailDB lookups)
- ENABLE_DRINK_TOOL=${ENABLE_DRINK_TOOL:-true}
# Optional caller identity verification (VERIFY tool, DTMF PIN/OTP entry).
# Off until a global PIN/secret is set or a caller is enrolled via
# /verify/credentials. VERIFY_REQUIRED_TOOLS hard-gates the listed tools.
- ENABLE_VERIFY_TOOL=${ENABLE_VERIFY_TOOL:-true}
- VERIFY_PIN=${VERIFY_PIN:-}
- VERIFY_TOTP_SECRET=${VERIFY_TOTP_SECRET:-}
# TOTP algorithm params (RFC 6238) — must match the caller's authenticator.
- VERIFY_TOTP_DIGITS=${VERIFY_TOTP_DIGITS:-6}
- VERIFY_TOTP_PERIOD=${VERIFY_TOTP_PERIOD:-30}
- VERIFY_TOTP_ALGORITHM=${VERIFY_TOTP_ALGORITHM:-SHA1}
- VERIFY_TOTP_WINDOW=${VERIFY_TOTP_WINDOW:-1}
- VERIFY_REQUIRED_TOOLS=${VERIFY_REQUIRED_TOOLS:-}
- VERIFY_MAX_ATTEMPTS=${VERIFY_MAX_ATTEMPTS:-3}
- VERIFY_DTMF_TIMEOUT_S=${VERIFY_DTMF_TIMEOUT_S:-20.0}
- VERIFY_DTMF_INTERDIGIT_S=${VERIFY_DTMF_INTERDIGIT_S:-3.0}
- VERIFY_ISSUER=${VERIFY_ISSUER:-General Disarray}
# Spoken lines for the outbound "call and verify" flow (POST /verify/call).
- VERIFY_CALL_PROMPT=${VERIFY_CALL_PROMPT:-}
- VERIFY_CALL_RETRY_PHRASE=${VERIFY_CALL_RETRY_PHRASE:-}
- VERIFY_CALL_SUCCESS_PHRASE=${VERIFY_CALL_SUCCESS_PHRASE:-}
- VERIFY_CALL_FAIL_PHRASE=${VERIFY_CALL_FAIL_PHRASE:-}
- VERIFY_CREDENTIALS_FILE=${VERIFY_CREDENTIALS_FILE:-}
# Single-line prompt override; multi-line prompts go in data/system_prompt.txt
- SYSTEM_PROMPT=${SYSTEM_PROMPT:-}
# Per-turn acknowledgment: chime (earcon, default) | phrase | none
Expand Down Expand Up @@ -280,7 +307,7 @@ services:
- CALL_EVENT_WEBHOOK_URL=${CALL_EVENT_WEBHOOK_URL:-}
- CALL_EVENTS=${CALL_EVENTS:-call.started,call.ended}
- CALL_EVENT_INCLUDE_TRANSCRIPT=${CALL_EVENT_INCLUDE_TRANSCRIPT:-true}
# Virtual numbers: ephemeral single-use inbound extensions (POST /virtual-numbers)
# Virtual numbers: ephemeral single-use inbound extensions + persistent trigger numbers (POST /virtual-numbers)
- VIRTUAL_NUMBERS_ENABLED=${VIRTUAL_NUMBERS_ENABLED:-false}
- VIRTUAL_NUMBER_DEFAULT_TTL_S=${VIRTUAL_NUMBER_DEFAULT_TTL_S:-900}
- VIRTUAL_NUMBER_MAX_TTL_S=${VIRTUAL_NUMBER_MAX_TTL_S:-86400}
Expand Down Expand Up @@ -328,7 +355,7 @@ services:
environment:
- SEARXNG_BASE_URL=http://searxng:8080/
ports:
- "127.0.0.1:8081:8080"
- "127.0.0.1:8082:8080"
restart: unless-stopped

n8n:
Expand All @@ -348,8 +375,8 @@ services:
- N8N_CUSTOM_EXTENSIONS=/custom-nodes
volumes:
- n8n_data:/home/node/.n8n
# Custom SIP Agent nodes — built by examples/n8n-nodes-general-disarray/build.sh
- ./examples/n8n-nodes-general-disarray/dist:/custom-nodes/n8n-nodes-general-disarray:ro
# Custom SIP Agent nodes — from the examples/n8n-nodes submodule (CHA0S-CORP/n8n-nodes); build with examples/n8n-nodes/packages/n8n-nodes-general-disarray/build.sh
- ./examples/n8n-nodes/packages/n8n-nodes-general-disarray/dist:/custom-nodes/n8n-nodes-general-disarray:ro

volumes:
n8n_data:
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.observability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ services:
- --port=8000
- --tensor-parallel-size=${TENSOR_PARALLEL_SIZE:-1}
- --gpu-memory-utilization=${GPU_MEMORY_UTILIZATION:-0.90}
- --max-model-len=${MAX_MODEL_LEN:-8192}
- --max-model-len=${MAX_MODEL_LEN:-49152}
- --otlp-traces-endpoint=http://otel-collector:4317
# depends_on:
# otel-collector:
Expand Down
Loading
Loading