kural
Open-source voice AI agent framework.
Build phone agents with the LLM, speech, and telephony providers of your choice. Bring your own keys. Run for the cost of a phone number.
Why kural
Bring your own keys
Plug in OpenAI, OpenRouter, Groq, Ollama, vLLM, or any OpenAI-compatible endpoint. Same for STT and TTS. No middleman markup.
Free path exists
OpenRouter free models + local Whisper + local Piper TTS + a $1/month Twilio number. Production voice agent for call minutes only.
Self-hostable
Single Python process. Deploy on a $5 VPS, your laptop, or a container. No SaaS lock-in.
Phone-first
Twilio telephony out of the box. Browser audio works too. Built on Pipecat — every stage is a swappable frame processor.
How it works today
kural ships three modes, switchable via KURAL_MODE. voice is the v0.1 cascade; echo is a transport smoke test; telephony (v0.3) runs the same cascade over real phone calls.
Silero VAD decides when the caller stopped speaking. Each layer is built through a small Adapter Protocol and a registry, so swapping the STT, LLM, or TTS backend is one env var (KURAL_LLM_PROVIDER, KURAL_STT_PROVIDER, KURAL_TTS_PROVIDER). Currently registered: OpenAI-compatible LLM; faster-whisper and Deepgram STT; Piper, ElevenLabs, and Kokoro TTS.
Telephony (v0.3)
KURAL_MODE=telephony runs a FastAPI/uvicorn server instead of a local audio loop. Inbound calls hit a webhook, get bridged onto a Twilio Media Streams WebSocket, and run through the same voice pipeline above — only the transport differs. Each call is its own worker, so multiple calls run concurrently.
Twilio is the first TelephonyAdapter implementation, wired through the same registry pattern as the LLM/STT/TTS layers — Pipecat already ships serializers for Telnyx, Plivo, Exotel, Genesys, and Vonage, so adding another provider is a new adapter class, not a pipeline change. A small REST API (POST /calls/outbound, GET /calls, GET /calls/{sid}) places outbound calls and reads call history from a local SQLite log.
Latency budget (voice mode)
Target on a recent Apple Silicon laptop, all local: < 2 s end-to-end (caller stops speaking → first audio of reply).
| Stage | Budget | Notes |
|---|---|---|
| VAD endpointing | ~250 ms | Silero start/stop windows |
| STT (Whisper distil-medium.en) | ~400 ms | Per utterance, batched |
| LLM (small local model) | ~800 ms | First-token latency dominates |
| TTS (Piper) | ~400 ms | First audio chunk |
| Audio I/O + scheduling | ~150 ms | Buffering, sample-rate conversion |
Target architecture
Provider matrix
| Layer | Free / local | Paid (BYOK) |
|---|---|---|
| LLM | Ollama, vLLM, OpenRouter free tier | OpenAI, Anthropic (via OpenRouter), Groq, Together |
| STT | faster-whisper, Distil-Whisper | Deepgram (shipped), AssemblyAI |
| TTS | Piper (shipped), Kokoro (shipped) | ElevenLabs (shipped), Cartesia |
| Phone | — | Twilio (shipped); Telnyx, Plivo, Exotel, Genesys, Vonage adapters possible, not yet built |
Quickstart
Install once:
# macOS: brew install portaudio # Ubuntu: sudo apt-get install -y portaudio19-dev git clone https://github.com/nnavnita/kural cd kural cp .env.example .env python -m venv .venv && source .venv/bin/activate pip install -e .
Voice mode (default) — full STT → LLM → TTS
Cheapest local path: a small model via Ollama.
# 1. Pull a small model once ollama pull llama3.1:8b ollama serve # 2. Point kural at it (or edit .env) export KURAL_LLM_BASE_URL=http://localhost:11434/v1 export KURAL_LLM_API_KEY=ollama # ignored by Ollama; any string export KURAL_LLM_MODEL=llama3.1:8b kural # or: python -m kural.server
Whisper and Piper auto-download weights on first run (~1 GB total). Wear headphones, speak, hear the reply.
Echo mode — transport smoke test, no API keys
KURAL_MODE=echo kural
Telephony mode — Twilio inbound/outbound calls
Needs a publicly reachable URL for Twilio's webhooks — ngrok http 8000 works for local dev.
# 1. Tunnel a public URL to your machine ngrok http 8000 # 2. Configure (or edit .env) export KURAL_MODE=telephony export KURAL_PUBLIC_BASE_URL=https://your-tunnel.ngrok.app export TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx export TWILIO_AUTH_TOKEN=your_auth_token export TWILIO_PHONE_NUMBER=+15551234567 kural # serves webhook + media stream + REST API on :8000
Point the Twilio number's voice webhook at <public-url>/telephony/voice. Place an outbound call with curl -X POST :8000/calls/outbound -d '{"to":"+15557654321"}'; list history with curl :8000/calls.
Configuration
Settings.from_env reads:
| Variable | Default | Meaning |
|---|---|---|
KURAL_MODE | voice | voice (STT→LLM→TTS), echo (passthrough), or telephony (Twilio server) |
KURAL_SAMPLE_RATE | 16000 | Audio input rate; 16 kHz matches Whisper/Silero |
KURAL_OUTPUT_SAMPLE_RATE | 24000 | Audio output rate; higher for smoother TTS playback |
KURAL_LLM_PROVIDER | openai | Registry key selecting the LLM adapter |
KURAL_LLM_BASE_URL | OpenAI default | OpenAI-compatible endpoint (Ollama, OpenRouter, vLLM, …) |
KURAL_LLM_API_KEY | unset | API key for the LLM endpoint |
KURAL_LLM_MODEL | gpt-4o-mini | Model identifier passed to the provider |
KURAL_STT_PROVIDER | whisper | Registry key selecting the STT adapter (whisper or deepgram) |
KURAL_STT_MODEL | distil-medium.en | faster-whisper model id, or Deepgram model name (e.g. nova-3-general) |
DEEPGRAM_API_KEY | unset | Required when KURAL_STT_PROVIDER=deepgram |
KURAL_TTS_PROVIDER | piper | Registry key selecting the TTS adapter (piper, kokoro, or elevenlabs) |
KURAL_TTS_VOICE | en_US-amy-medium | Piper/Kokoro voice id, or ElevenLabs voice ID |
ELEVENLABS_API_KEY | unset | Required when KURAL_TTS_PROVIDER=elevenlabs |
KURAL_AGENT_PROMPT | built-in | System prompt seeded into the LLM context |
KURAL_LOG_LEVEL | INFO | loguru level |
KURAL_TELEPHONY_PROVIDER | twilio | Registry key selecting the telephony adapter |
KURAL_PUBLIC_BASE_URL | unset | Publicly reachable URL for Twilio webhooks; required for telephony mode |
TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN / TWILIO_PHONE_NUMBER | unset | Twilio credentials; required for telephony mode |
KURAL_DB_PATH | kural.db | SQLite call log path (telephony mode) |
KURAL_PORT | 8000 | Bind port for the telephony server |
Stop the agent with Ctrl+C. See .env.example for the full list.
Roadmap
- Done v0 — Local echo agent (mic → speaker passthrough)
- Done v0.1 — STT → LLM → TTS pipeline with one provider per layer
- Done v0.2 — Provider adapters: OpenAI-compatible LLM; faster-whisper + Deepgram STT; Piper + ElevenLabs + Kokoro TTS; README provider matrix + "add a provider" guide
- Done v0.3 — Twilio telephony integration: inbound/outbound calls, concurrent, call history
- Planned v0.4 — Configurable agent personas (system prompt, tools)
- Planned v1.0 — Production-ready, documented, examples