Architecture Deep Dive
An automated outbound calling platform built on LiveKit Agents, deployed entirely on-prem.
press space or scroll to begin
Components & design decisions
Framework, AgentServer, JobContext
On-prem RKE cluster & services
End-to-end sequence + SIP trunk
One-way, two-way & AMD
Recording, audit, deploy, TURN
The Problem
One-way notifications (prescription reminders) + two-way interactive conversations (appointment scheduling) with AMD, recording & full audit trails.
FastAPI service. Receives call requests, creates rooms, dispatches agents, initiates SIP.
LiveKit Agent process. Handles real-time voice AI logic via AgentSession.
Self-hosted WebRTC server. Routes audio, manages rooms, bridges SIP, runs egress.
External PSTN gateway. Terminates outbound SIP calls to phone numbers.
The agent worker is built on the LiveKit Agents SDK — a Python framework for building real-time voice AI agents that participate in LiveKit rooms as first-class participants.
The top-level process that connects to the self-hosted LiveKit server over WebSocket. It listens for agent dispatch requests and spawns agent sessions.
src/outbound_dialer/agent/entrypoint.py
Provided to the entrypoint when the agent is dispatched. Gives access to the LiveKit room, participant events, and room metadata. Calls ctx.connect() to join (with TURN).
| Service | Protocol | URL |
|---|---|---|
| LiveKit Server | WebSocket/gRPC | wss://devservices-colo-west-livekit.corp… |
| CVS ASR | WebSocket | wss://…speak-colo-west…/v1/listen |
| CVS TTS | WebSocket | wss://…speak-colo-west…/v1/speak |
| Campaign Service | HTTPS | https://devservices-colo-west…/campaigns/v1 |
| Event Publisher | HTTPS | https://digital-retail-rx-qa…/kafka/send-message |
Source: src/outbound_dialer/services/call_service.py, sip_service.py
Agent (worker) audio
SIP Participant audio
Mixed audio (recording)
Trunk: cvs-retail-outbound
Address: cvsretailoutboundterminate.pstn.ashburn.twilio.com
From: +12134147078
Transport: TLS
Media: SRTP encrypted
Credentials stored in Vault · SIPServiceSingleton ensures trunk exists at startup
session.say() with
interruptions disabled
STT → AMD → LLM → TTS
interactive loop
Agent (worker) audio track
SIP Participant (phone) audio track
RoomComposite
audio_only = True
Mixed audio stream
Bucket: livekit-recordings-nonprod
Path: recordings/{campaign}/{session}/recording.mp3
Auto-stops on participant departure (30s timeout) · Manual stop via stop_and_wait_for_egress()
| Event | When Published |
|---|---|
| conversation_started | Greeting delivered (two-way) |
| notification_delivered | One-way message played |
| amd_hangup | Voicemail detected, call hung up |
| user_hangup | User disconnected early |
| error_http / error_exception | Agent API or runtime error |
TURN relays are required because the on-prem LiveKit server needs to route media to/from external Twilio SIP endpoints across the corporate firewall.
on-prem
on-prem
PSTN Bridge
TRANSPORT_RELAY forced · Credentials fetched from Twilio API, cached 24h · Stored in Vault
| Port | 21121 |
| Health | /health |
| Env | LIVEKIT not set |
| Entry | scripts/start.py → FastAPI |
| Route | /microservices/outbound-dialer/ |
| Port | 8081 |
| Health | / |
| Env | LIVEKIT=true |
| Entry | entrypoint.py → AgentServer |
| Route | /microservices/outbound-dialer-worker/ |
Single Docker image · docker_entrypoint.py checks LIVEKIT env var to select mode
CI — Continuous Integration
CD — Continuous Deployment
API server and worker have separate CD pipelines triggered by different deploy-configs/ paths
Key Takeaways
end of presentation