Files
hold-slayer/docs/configuration.md
Robert Helewka ff7ea8623a Stage 6: learn_call_flow rebuilt on the learner, docs truth sweep
The call-flow learner finally gets fed: exploration mode records its
IVR discoveries on the call (ActiveCall.exploration_steps) instead of
throwing them away, persistence stores them in the call record's
metadata, and the rebuilt learn_call_flow MCP tool turns a completed
exploration call into a stored flow via CallFlowLearner — correct
constructor (llm_client from get_llm, heuristic labels when the LLM
is unavailable), build for a new number, merge/refine when a flow
already exists. save_learned_flow/update_flow_from_model keep the
CallFlow↔row mapping in call_persistence.

Test gaps closed: tests/test_learner.py (discoveries→linked steps,
exploration persistence, learn-then-refine through the in-memory MCP
client, no-data and unknown-call answers) and tests/test_websocket.py
(4401 without token, trunk-status-then-replay on connect, per-call
stream filtering).

Docs aligned to code: README (15 tools incl. learn_call_flow, HTTP
not SSE, Python 3.12+, PostgreSQL+Alembic — no SQLite fallback, media
pipeline marked stub-mode until pjsua2 installed, Alembic and honest
/health checked off); docs/mcp-server.md rewritten against the actual
tool surface (hangup not end_call, real params, 3 real resources,
/mcp/ streamable HTTP + bearer auth); architecture/development/
configuration drift fixed.

pyproject: pruned never-imported deps (websockets, librosa,
soundfile, python-multipart).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:45:35 -04:00

5.2 KiB

Configuration

All configuration is via environment variables, loaded through Pydantic Settings. Copy .env.example to .env and edit.

Environment Variables

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

Gateway

Variable Description Default Required
GATEWAY_SIP_PORT Port for device SIP registration 5080 No
GATEWAY_RTP_PORT_MIN Minimum RTP port 10000 No
GATEWAY_RTP_PORT_MAX Maximum RTP port 20000 No
GATEWAY_HOST Bind address 0.0.0.0 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 or SQLite connection string sqlite+aiosqlite:///./hold_slayer.db No

Notifications

Variable Description Default Required
NOTIFY_SMS_NUMBER Phone number for SMS alerts (E.164) 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:

from config import get_settings

settings = get_settings()
settings.sip_trunk_host      # "sip.provider.com"
settings.llm.base_url        # "http://localhost:11434/v1"
settings.llm.model           # "llama3"
settings.speaches_url        # "http://localhost:22070"
settings.database_url        # "sqlite+aiosqlite:///./hold_slayer.db"

LLM settings are nested under settings.llm as a LLMSettings sub-model.

Deployment

Development

# 1. Clone and install
git clone <repo-url>
cd hold-slayer
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

# 2. Configure
cp .env.example .env
# Edit .env

# 3. Start Ollama (for LLM)
ollama serve
ollama pull llama3

# 4. Start Speaches (for STT)
docker run -p 22070:8000 ghcr.io/speaches-ai/speaches

# 5. Run
uvicorn main:app --host 0.0.0.0 --port 8000 --reload

Production

# PostgreSQL is required (no SQLite fallback)
DATABASE_URL=postgresql+asyncpg://user:pass@localhost/hold_slayer

# Use vLLM for faster inference
LLM_BASE_URL=http://localhost:8000/v1
LLM_MODEL=meta-llama/Llama-3-8B-Instruct

# Run with multiple workers (note: each worker is independent)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1

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

FROM python:3.13-slim

# Install system dependencies for PJSUA2 and Sippy
RUN apt-get update && apt-get install -y \
    build-essential \
    libpjproject-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .
RUN pip install -e .

EXPOSE 8000 5080/udp 10000-20000/udp

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Port mapping:

  • 8000 — HTTP API + WebSocket + MCP
  • 5080/udp — SIP device registration
  • 10000-20000/udp — RTP media ports