docs: document SIP_ENGINE and correct config tables against the models
Started as the SIP_ENGINE row flagged in the last commit. Cross-checking the tables against config.py mechanically (rather than by eye) turned up more, including two entries that were actively wrong. Corrections: - GATEWAY_RTP_PORT_MIN/MAX and GATEWAY_HOST are documented in configuration.md but do not exist — no code reads them and they are absent from .env.example. Setting them today does nothing. Replaced with the real GATEWAY_SIP_ fields (host/port/domain). - GATEWAY_SIP_PORT was documented as 5080 in two places; the code and .env.example both say 5060. - DATABASE_URL was documented with a SQLite default. There is none, and startup exits if it is unset. Additions — every env var the models accept is now documented somewhere (verified bidirectionally: nothing in the models undocumented, nothing documented that the models reject): - Server section: HOST, PORT, DEBUG, LOG_LEVEL, LOG_FORMAT - Safety section: MAX_CONCURRENT_CALLS, USE_MOCK_SIP, SIP_ENGINE - Receptionist section (configuration.md had none, though seven vars exist) Structural staleness, from the PR #8 media-plane work: - core/pjsua_engine.py was absent from the component list and file tree; so were dial_plan.py (the emergency guard) and sip_engine.py. - architecture.md's banner still read "media plane in transition". The engine landed; it is now two selectable engines with the audio consequence stated. - Tech Stack described "single-process async architecture" — the simplification CLAUDE.md explicitly calls out. Now points at the threading model, since there are three execution contexts. - The Asterisk lab shipped in PR #8 with its own README but nothing linked to it. Linked from the test section and both doc indexes. - CLAUDE.md's "no structured JSON logging" gap is closed; test count was 146 across 16 files, now 189 across 19. The other listed gaps (no /metrics, no rate limiting, no health-probe log filter) were re-verified and still hold. Deliberately not hardcoding a test count in the README — that is the same staleness this commit is clearing up. All internal links and anchors verified to resolve; 189 tests pass; lint unchanged at its 216 baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,9 @@ Comprehensive documentation for the Hold Slayer AI telephony gateway.
|
||||
|----------|-------------|
|
||||
| [Architecture](architecture.md) | System architecture, component diagram, data flow |
|
||||
| [Core Engine](core-engine.md) | SIP engine, media pipeline, call manager, event bus |
|
||||
| [Dial Plan](dial-plan.md) | Number normalisation and the emergency-number guard |
|
||||
| [PJSUA2 Build](pjsua2-build.md) | Building the `pjsua2` bindings (not pip-installable) |
|
||||
| [Asterisk Lab](../tests/lab/README.md) | The fake PSTN used for media validation |
|
||||
| [Hold Slayer Service](hold-slayer-service.md) | IVR navigation, hold detection, human detection, transfer |
|
||||
| [Audio Classifier](audio-classifier.md) | Waveform analysis, feature extraction, classification logic |
|
||||
| [Services](services.md) | LLM client, transcription, recording, analytics, notifications |
|
||||
|
||||
@@ -4,11 +4,12 @@ Hold Slayer is a single-process async Python application built on FastAPI. It
|
||||
acts as an intelligent B2BUA (Back-to-Back User Agent) sitting between your SIP
|
||||
trunk (PSTN access) and your desk phone/softphone.
|
||||
|
||||
> **Media plane in transition.** The gateway currently signals with Sippy and
|
||||
> intends PJSUA2 to carry media, but PJSUA2 will not surface an RTP stream for
|
||||
> a dialog it does not own — so no audio ever reaches the classifier. The fix
|
||||
> moves *call placement* into PJSUA2 while Sippy keeps the SBC roles. See
|
||||
> [Media plane: why PJSUA2 places the call](#media-plane-why-pjsua2-places-the-call)
|
||||
> **Two SIP engines, selected by `SIP_ENGINE`.** `sippy` (the default) signals
|
||||
> only — PJSUA2 will not surface an RTP stream for a dialog it does not own, so
|
||||
> **no audio reaches the classifier** on that path. `pjsua2`
|
||||
> (`core/pjsua_engine.py`) places the call itself and is the only mode where
|
||||
> audio reaches the classifier; it is opt-in while being proven against the lab.
|
||||
> Read [Media plane: why PJSUA2 places the call](#media-plane-why-pjsua2-places-the-call)
|
||||
> before changing anything in `core/`.
|
||||
|
||||
## System Diagram
|
||||
|
||||
@@ -34,14 +34,36 @@ SSO-disabled-off-loopback.
|
||||
| `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_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 |
|
||||
| `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
|
||||
|
||||
@@ -65,7 +87,7 @@ SSO-disabled-off-loopback.
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `DATABASE_URL` | PostgreSQL or SQLite connection string | `sqlite+aiosqlite:///./hold_slayer.db` | No |
|
||||
| `DATABASE_URL` | PostgreSQL connection string. Startup exits with a readable error if unset | — | Yes |
|
||||
|
||||
### Notifications
|
||||
|
||||
@@ -73,6 +95,18 @@ SSO-disabled-off-loopback.
|
||||
|----------|-------------|---------|----------|
|
||||
| `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 |
|
||||
|
||||
Reference in New Issue
Block a user