01Providers
Bring any model.
Pick a provider for each stage, or one speech-to-speech model end to end. Swapping is a one-line change.
Speech in
Smallest.ai
Smallest.ai · Deepgram · AssemblyAI · Whisper
Reasoning
Anthropic
Anthropic · OpenAI · Gemini · Groq
Speech out
ElevenLabs
ElevenLabs · Smallest.ai · Cartesia · Rime
orOpenAI Realtimeend to end
Anything else with an HTTP or WebSocket API plugs in as a custom provider.
02Config
Describe the system.
Talker, reasoning, actions, turn-taking and shared memory in one readable file. Change a line and the pipeline reloads.
# config.cueloname: support-line talker: asr: smallest tts: elevenlabs voice: "aria" llm: model: anthropic prompt: ./prompts/support.md actions: - tools/lookup_order - tools/update_address turns: endpointing: semantic barge_in: true memory: shared: redis serve: api: openai-realtimerunning pipeline
- speech insmallesttalker.asr
- turn-takingsemanticturns
- reasoninganthropicllm.model
- actions2 toolsactions
- speech outelevenlabstalker.tts
- memoryredismemory
Hot reload keeps the call up. In-flight turns finish on the old config.
03Serve
One command. Your endpoint.
cuelo serve stands the pipeline up behind an API. Speak the OpenAI Realtime protocol so existing clients just work, or declare your own routes.
$ cuelo serve config.cuelo
✓talkersmallest → elevenlabs
✓llmanthropic · 2 tools
✓turnssemantic endpointing
✓memoryredis
✓storesqlite://cuelo.db
support-line · openai-realtime
WSws://localhost:4141/v1/realtime
POSThttp://localhost:4141/v1/realtime/sessions
GEThttp://localhost:4141/v1/models
ready in 412ms · watching config.cuelo
$ cuelo serve config.cuelo
✓talkersmallest → elevenlabs
✓llmanthropic · 2 tools
✓turnssemantic endpointing
✓memoryredis
✓storesqlite://cuelo.db
support-line · custom routes
POSThttp://localhost:4141/calls
WSws://localhost:4141/calls/:id
POSThttp://localhost:4141/calls/:id/transfer
ready in 398ms · watching config.cuelo
CUELO = "http://localhost:4141"
client = AsyncOpenAI(
base_url=f"{CUELO}/v1")
rt = client.beta.realtime
async with rt.connect(
model="support-line") as call:
...const url = "ws://localhost:4141"
const call = await connect(url)
mic.pipe(call)
call.on("audio", play)call, err := cuelo.Connect(url)
if err != nil {
return err
}
defer call.Close()
call.Stream(mic, speaker)Any OpenAI Realtime client works unchanged. Point its base URL at Cuelo.
04Observe
See every turn.
Every turn is traced: audio, VAD, transcripts, turn decisions, tool calls and each model's timing. Cuelo points at the slow stage and tells you which line to change.
ttfb p50
488ms
ttfb p95
612ms
vad tail
144ms
llm ttft
248ms
tts ttfa
96ms
barge-ins
1
Where to improve
Reasoning is half of response time on short replies.
llm.fast: groqroute one-line answers to a faster modelVAD waits 144ms after the caller stops.
turns.min_silence: 400 → 300semantic endpointing already covers false stopsTurn 6: caller barged in 1.2s into a reply.
turns.barge_in.min_words: 2ignore backchannels like "mm-hm"
Call summary written by the agent
Caller asked to change the delivery address on order #48213. The talker looked up the order, confirmed the new address back to them and updated it. Resolved in 9 turns, 1m 54s.
00:41.2callerHi, I need to change my delivery address.
00:43.1talkerSure. What's the new address?
sqlite://cuelo.db05Data
Your calls, your database.
Sessions, turns, transcripts, audio and summaries land in SQLite out of the box. Point Cuelo at Postgres or MySQL, or write an adapter for anything else.
config.cuelo
store: url: sqlite://cuelo.db audio: ./recordings # or s3:// retain: 30d
- sqlitedefault, zero setup
- postgresfor production and teams
- mysqlif that's what you run
- customa small adapter interface
- sessionspipeline, caller, started_at, duration, outcome
- turnssession_id, index, ttfb_ms, vad_ms, barge_in
- eventsturn_id, stage, name, at_ms, payload
- transcriptsturn_id, speaker, text, confidence
- audioturn_id, speaker, uri, duration_ms
- summariessession_id, intent, resolved, text
06Agents
Hand it to your agent.
Cuelo ships an MCP server, so your coding agent can build, trace, test and tune pipelines with the same tools you use. Connect it once, then just ask.
$claude mcp add cuelo -- npx -y cuelo mcp$codex mcp add cuelo -- npx -y cuelo mcp{ "mcpServers": { "cuelo": { "command": "npx", "args": ["-y", "cuelo", "mcp"] } } }2 · paste a prompt
>Build a support line that speaks Hindi and English, uses Smallest.ai for speech, and can look up orders.
●cuelo.inittemplate: support
●cuelo.config.edittalker.asr: smallest · talker.tts: smallest
●cuelo.config.editlanguages: [hi, en]
●cuelo.tool.addlookup_order
●cuelo.calltest call · 6 turns · no errors
✓support-line is live on ws://localhost:4141/v1/realtime
>Find the slowest stage in the last 100 calls and fix it in config.cuelo.
●cuelo.tracelast: 100
●cuelo.evalmetric: ttfb
→reasoning is 51% of ttfb on short replies
●cuelo.config.edit+ llm.fast: groq
●cuelo.testreplay: 100 calls
✓ttfb p50 488ms → 341ms, task success unchanged
>Replay yesterday's calls against this config and tell me what broke.
●cuelo.testreplay: calls/2026-11-11 · 214 calls
●cuelo.evaltask_success, barge_in, ttfb
!3 regressions: transfer not offered on a repeat request
●cuelo.tracesess_91c2, sess_a04e, sess_c7b1
✓wrote reports/2026-11-12.md with the three traces
>Add a tool that hands the caller to a person if they ask twice.
●cuelo.tool.addtransfer_to_human
●cuelo.config.edit+ actions: tools/transfer_to_human
●cuelo.config.edit+ turns.escalate_after: 2
●cuelo.testscenario: asks_for_human_twice × 12
✓transferred on the second ask in 12 of 12 runs
>Write evals for interruptions and run them on every change to the config.
●cuelo.eval.addinterruptions
●cuelo.eval.addbarge_in_recovery
●edit.github/workflows/cuelo.yml
✓evals now run on every change to config.cuelo
tools your agent gets
- cuelo.init
- cuelo.config.edit
- cuelo.tool.add
- cuelo.call
- cuelo.trace
- cuelo.eval
- cuelo.test
Prefer the terminal? It's all a plain CLI too: cuelo init cuelo dev cuelo call cuelo test cuelo eval cuelo trace.