📞Layer 1: Voice Edge
The call's entry point (HGW / Asterisk / bridge). Current operations vs. Phase C v2.
The physical entry point for calls (the Voice Edge). A Hikari Denwa HGW receives the call, Asterisk
passes the audio to bridge.py over AudioSocket, and OpenAI Realtime makes the decision.
Architecture
Current operations vs. Phase C v2
Calls from listed numbers (blocklist) get an announcement and Hangup(21) at Gate 1 (route-decision=block) (fail-safe = block). The path that cuts a call on the mid-call verdict (spam) does not fire in the current code (see the AMI row under “Key parameters” below; slated for removal). Anything undecidable or any fault goes to a human (fail-open = stay in business); the final design (2026-09-16) rings the fixed phone once instead (stage 1 E01, not implemented). We do not use cutting everything off (fail-closed); it was removed in C-2(b). See the glossary for details.
Key parameters
- AudioSocket:
127.0.0.1:9092. Asterisk sends slin (16-bit PCM, 8 kHz) (dialplanc(slin)); the bridge resamples to 24 kHz PCM.AUDIO_FORMAT=ulawis a compatibility mode that, per measurement, is never needed (bridge_audio_format()) - AMI:
127.0.0.1:5039. The bridge’shangup_by_uuid(Statuslookup →Hangup) is still in the code, but the path from the in-call verdict never fires (conversation_may_terminate()is always False). Slated for removal by the final design (2026-09-16) - OpenAI Realtime: default model
gpt-realtime-2.1(override withAEGIS_REALTIME_MODEL), connecting over/v1/realtime - Hand-off to a human:
Dial(PJSIP/…@hgw-trunk,30)toHUMAN_EXTEN(set in kitted; empty = the called extension). The placeholders<ALLOW_DESTINATION>/<HUMAN_PEER>do not exist in the current dialplan - route-decision
decisionhas 4 values (block/human/ai/take_message); empty → static fallback, unknown → a human
The OpenAI Realtime Beta API was retired on 2026-05-12. bridge.py is migrated to GA (default model gpt-realtime-2.1);
connect / session.update accepted / classify_call relay are verified against real OpenAI.
The “classify → closing line → AMI Hangup” run-through is a July 2026 record; that path does not fire in the current code (see below).
Real Asterisk hands AudioSocket slin (16-bit PCM, 8 kHz, LE) (measured 2026-08-03; dialplan c(slin)).
The bridge default AUDIO_FORMAT=slin takes it as-is and resamples to 24 kHz PCM.
AUDIO_FORMAT=ulaw is a compatibility mode for an AudioSocket that carries μlaw; measurements show no case that needs it.
The earlier text here (“μlaw 8 kHz direct-send, default pcm16”) was wrong (corrected 2026-09-16).
The path that books an AMI Hangup from classify_call’s should_terminate (Plan B / D-1) remains in the code, but
conversation_may_terminate() is always False, so it never fires on any call (bridge src/bridge.py, br=a557a5b1d41d).
The final design of 2026-09-16 (Plan A) removes this path; in-call decisions arrive as a reception mode in tool responses. The caller is never cut on a conversation verdict.
The hand-off contract is aegis-docs docs/PHONE_CALL_CONTRACT.md. Main changes (all unimplemented; each stage’s acceptance sheet says what landed):
- The call ID is created before route-decision and also given to human-direct and announce-and-end calls (today it is created only on the AI path and never sent to the verdict)
- Undecidable (empty, bad JSON, 401, timeout) → ring the fixed phone once (today: the static
AI_ROUTE_DEFAULTfallback) - If nobody answers, one AI re-intake (only for calls that already went through AI); never back to a human from the re-intake
- Messages are committed on the phone side too; unsent-to-server still counts as taken. Messages are never auto-deleted. At the cap, AI intake stops but the fixed phone still rings
- The AI intake path is 300 s from ring (180 first AI / 30 ring / 90 re-intake are guides). No time limit once a human answers
- Recordings are deleted from the Pi after the NAS confirms them; untransferred ones are kept within half of the SD’s free space, oldest first