Bolna voice agent: calls drop at 12 seconds

This page covers the research, the root-cause analysis and the prototype plan for the Upwork invite. The goal is to reproduce the 12-second disconnect inside real Bolna code, diagnose it automatically, fix it, and show a stable, low-latency call, all before the client gives us access to anything.

10 s + 2 sBolna silence threshold plus its 2 s watchdog poll = "~12 s"
3prime suspects, all ending as inactivity_timeout or a component error
30 minto pin the cause given one execution ID
Real Bolnaopen-source code running locally in Docker for the demo
< 1 starget turn latency, measured per turn
CONFIRMED read in Bolna's source or official docs INFERRED strong reasoning, not yet observed on the client's calls TEST to verify in the lab

01Summary in 60 seconds

Plain English

Bolna runs a small "is anyone still talking?" check every 2 seconds. If neither the AI nor the caller has spoken for 10 seconds, it hangs up. 10 seconds plus up to 2 seconds before the next check is the 12 seconds the client sees.

On a healthy call that never happens, because the agent greets the caller and the caller answers. So a clean 12-second drop means something in the pipeline is silently broken. Either the caller never hears the agent (a text-to-speech or audio-format problem), or the agent never "hears" the caller (a speech-to-text or encoding problem). Bolna can't tell the difference from real silence, so it ends the call as inactivity_timeout.

Our pitch: "I reproduced your exact bug inside Bolna's own code, built a tool that names the cause from the call logs, and here's the fixed call running for 2 minutes."

  1. Run real Bolna locally (open-source, Docker: the Bolna app plus Redis). It's the same engine the hosted platform is built on.
  2. Call it with a fake Twilio: a Python client that speaks Twilio's exact Media Streams protocol, streaming a scripted caller voice (WAV) and playing the agent's replies. No phone carrier, no trial-account blocks, and the same result on every take.
  3. Flip one setting at a time to reproduce each root cause. Each one drops at about 12 s.
  4. Call Doctor reads Bolna's logs and the caller's event log and draws a timeline. It shows who hung up and why, flags which event is missing, and names the cause.
  5. Apply the fix and show a 2-minute call with interruptions and a per-turn latency breakdown.

02The job, decoded

Client wroteWhat it really meansWhat we show
"Calls disconnect after about 12 seconds"Bolna's silence watchdog, or a component error, ends the call because audio or transcripts aren't flowingReproduce it, diagnose it automatically, fix it
"Build, configure, optimize agents (Bolna, ElevenLabs…)"Agent JSON configuration: transcriber, LLM, synthesizer, task_configBad config next to good config, explained field by field
"Troubleshoot call failures, improve reliability"Reading execution data: hangup_by, hangup_reason, raw logsCall Doctor timeline plus rule-based root-cause hints
"Latency, speech quality, conversational flow"Speech-to-text final, LLM time to first token, TTS first byte, interruptionsPer-turn latency bars, p50/p95, barge-in demo
"Twilio or other telephony"Media Streams websocket, mulaw 8 kHz, <Connect><Stream>, error codes 3192xA fake Twilio caller that implements the protocol exactly
"LLMs, prompts, APIs, webhooks"Bolna /v2/agent, /call, /executions, webhook payloadsAgent created via API, execution data parsed
Skills listed: Illustrator, Illustration, Audio ProductionMis-tagged by the client; ignore(nothing)

03How Bolna works

Bolna is an open-source Python framework (bolna-ai/bolna, MIT, about 790 stars, updated daily). The hosted platform (api.bolna.ai, platform.bolna.ai) is built on it. C

Phone / Twilio<Connect><Stream>mulaw 8 kHz, 20 ms frames TaskManager TelephonyInputHandler start / media / mark / stop TelephonyOutputHandler media + mark → Twilio watchdog · every 2 shangup_after_silence = 10call_terminate = 90 TranscriberDeepgram · encoding/rate LLM agentOpenAI · Groq · … SynthesizerElevenLabs · ulaw_8000 Execution record status: completed / call-disconnected telephony_data.hangup_by / hangup_reason latency_data · transcript · cost GET /executions/{id}/log → raw logs webhook_url → POST on each status

Figure 1: The Bolna pipeline. Closing the websocket ends a <Connect><Stream> call, because Bolna's TwiML has nothing after it.

Agent configuration (JSON)

agent_config.tasks[0]
  tools_config.transcriber {provider, model, encoding, sampling_rate, endpointing}
  tools_config.llm_agent   {agent_type, llm_config{provider, model}}
  tools_config.synthesizer {provider, provider_config{voice_id, model}, audio_format}
  tools_config.input/output {provider: "twilio", format: "wav"}
  task_config {hangup_after_silence, call_terminate, check_if_user_online,
               trigger_user_online_message_after, hangup_after_LLMCall,
               voicemail, incremental_delay, number_of_words_for_interruption}
agent_prompts.task_1.system_prompt

Hosted API used in the demo

  • POST /v2/agent → agent_id
  • POST /call {agent_id, recipient_phone_number} → execution_id
  • GET /executions/{id}: status, hangup_by, hangup_reason, latency_data
  • GET /executions/{id}/log: raw logs for each component
  • Pricing: $5 free credit, about $0.06/min on preferred models

Self-hosted: docker compose up in local_setup/ starts bolna-app :5001 + redis (plus twilio-app and ngrok for real calls).

04Why exactly 12 seconds

024681012 s watchdog checks every 2 s (red ticks) stream start = silence clock starts greeting should play here… …but no acknowledged agent audio and no transcript >10 s silent → hangup "AI last spoke" = last Twilio mark ack (or stream start) · "user last spoke" = last interim transcript (∞ if none) · both > 10 s → inactivity_timeout between 10.0 and 12.0 s
From the source code C
  • task_manager.py: hang_conversation_after = conversation_config.get("hangup_after_silence", 10), and __check_for_completion() loops with await asyncio.sleep(2).
  • The "user last spoke" time is reset only by a transcript. The "AI last spoke" time is reset by Twilio's mark acknowledgement, which arrives after the audio has actually played.
  • The hangup check runs before the "are you still there?" check. With both set to 10 s, the reminder never plays.
  • Other hard-coded timeouts: __await_stream_sid(timeout=10), Deepgram connect timeout 10 s, hangup_mark_event_timeout=10.

05Root-cause matrix

#CauseMechanismSignature in logs / executionFix
ASTT encoding mismatchThe transcriber is set to linear16/16000 but Twilio sends mulaw/8000. Deepgram decodes noise and returns empty transcripts.inactivity_timeout, no user turns in the transcript, no "interim transcript" linesencoding: mulaw, sampling_rate: 8000
BAgent audio never playsA TTS failure (key, quota, voice ID), wrong output format, or no welcome audio (OSS issue #507). No mark ack comes back, so the "AI last spoke" clock never resets.inactivity_timeout or synthesizer_error, empty synthesizer turns, "No welcome message audio"ulaw_8000 output, a valid voice and key, welcome message configured
CDeepgram idle closeNo audio reaches Deepgram within 10 s, so it closes with 1011 / NET-0001.transcriber_connection_error, "WebSocket closed 1011"Forward media immediately, KeepAlive, correct stream track
DSilence threshold too tighthangup_after_silence 10 combined with slow first turns and an online check that never firesinactivity_timeout even on calls that work20–30 s, online check at 8–12 s, welcome message
EException closes the socketAn LLM error, a missing agent in Redis, a tool-call bug (#879). Twilio then logs 31921.llm_error, code 1006, a tracebackFix the component, add retries
FHard cap / LLM hangup / voicemailcall_terminate set too small, hangup_after_LLMCall false positive, voicemail misdetectionmax_duration_reached, llm_prompted_hangup, voicemail_detectedConfig and prompt review
GTwilio trial / setupTrial accounts strip <Stream> or require "press any key"; a stream URL with a query string gives 31920Twilio Debugger 31920/31921, the trial announcementUpgrade the account, use <Parameter> instead of a query string

A, B and C are the top suspects for a clean ~12 s drop. Infrastructure idle timeouts (load balancer 60 s, Cloudflare 100 s, ngrok) work on a different time scale and are unlikely here.

0630-minute diagnosis playbook

  1. (3 min) Get the IDs. One failing call: CallSid, Bolna execution_id, the Deepgram request ID, and UTC times of answer and hangup.
  2. (5 min) Who hung up? Bolna's hangup_by / hangup_code / hangup_reason, and Twilio's "Who hung up", Debugger alerts (31920/31921/31924/11200) and any answering-machine detection result. The reason code often names the culprit outright.
  3. (5 min) Config review. hangup_after_silence, call_terminate, transcriber encoding/sampling_rate, synthesizer format and voice, voicemail, the LLM hangup prompt, the online check.
  4. (7 min) Line up the logs. In order: websocket accepted → start → first media → STT connected → first interim transcript → TTS first chunk → first mark ack → hangup line. The first missing event is the broken component.
  5. (5 min) Test by elimination. Set hangup_after_silence to 60. If the drop moves, the silence watchdog is confirmed, and the task becomes finding why nothing is flowing. Swap the TTS or STT provider to isolate the faulty component.
  6. (5 min) Platform checks. Twilio trial status, container restarts or out-of-memory kills, worker count, the ngrok inspector.

07Call Lab architecture (the prototype)

Fake Twilio caller connected → start(streamSid, mulaw/8000)media: 160 B / 20 ms (base64)answers mark · plays agent audioscripted caller WAV or live mic→ events.jsonl + stereo call.wav Real Bolna (Docker) bolna-app :5001 ws /chat/v1/{agent_id}redis (agent configs) agents: broken-encoding · broken-tts · no-audio · tight-silence · fixed → docker logs (timestamps) Providers (your keys) Deepgram nova-3 · mulaw/8000OpenAI / Groq · streamingElevenLabs flash v2.5 · ulaw_8000(optional) Cartesia A/B Call Doctor (FastAPI + web UI) ingest: caller events.jsonl + Bolna docker logs + (client) /executions/{id} + /log + Twilio Call Events → unified timeline · who hung up · missing-event detector · rule-based root cause · latency per turn → "Diagnosis: transcriber encoding linear16/16k vs stream mulaw/8k → no transcripts → inactivity_timeout @ 11.6 s" ws

Figure 2: Every piece runs locally. The only external calls are to Deepgram, the LLM and ElevenLabs, using your keys. An optional real Twilio call is filmed once as proof.

Why this convinces the client

It's their engine (real Bolna code, not a look-alike), their symptom (a drop at about 12 s with inactivity_timeout), and their logs: the Doctor can read Bolna execution data and raw logs as well. Given one execution ID on day 1, we run the same tool on their call.

08Fault scenarios (one Bolna agent each)

AgentChange from the good configExpected result
broken-encodingtranscriber encoding: linear16, sampling_rate: 16000Greeting plays, caller speaks, no transcript, hangup at 10–12 s with inactivity_timeout TEST
broken-ttssynthesizer: invalid voice ID or wrong formatDead air, then hangup at 10–12 s with synthesizer_error or inactivity_timeout TEST
no-audioCaller sends start but holds back media (simulates a dropped inbound track)Deepgram NET-0001 / 1011, then transcriber_connection_error TEST
tight-silenceGood pipeline, hangup_after_silence: 10, the caller thinks for 11 sA healthy-looking call dies mid-conversation
fixedmulaw/8000, ulaw_8000, welcome message, silence 25 s, online check 10 s, interruptions enabled2+ minute call, barge-in, latency under 1 s at p50

09The fake Twilio caller

  • Sends exactly what Twilio sends: connected, then start with streamSid, callSid, mediaFormat {audio/x-mulaw, 8000, 1} and customParameters, then 160-byte, 20 ms media frames, then stop.
  • Plays the agent's media through the speakers live, so it's audible in the video. Echoes mark events back in playback order, as Twilio does, and honours clear (interruption).
  • Scripted caller: lines come from a caller script, rendered once to mulaw WAV with a TTS voice and cached, then played with realistic pauses. Every take is identical.
  • Writes events.jsonl with wall-clock timestamps, plus call.wav (stereo: caller left, agent right) for the video.
  • Optional --mic mode: talk to the agent live.

10Call Doctor

  • Inputs: the caller's events.jsonl, Bolna container logs (parsed by pattern), and adapters for the client's data: GET /executions/{id}, /executions/{id}/log, Twilio Call Events and Voice Insights "Who hung up".
  • Timeline UI: swim lanes for Caller, Telephony, STT, LLM, TTS and Watchdog. Events plotted against call time, with the 10 s silence window shaded and the hangup marked.
  • Missing-event detector: walks the expected chain (start → media → STT connect → interim transcript → TTS first byte → mark ack). The first gap is highlighted.
  • Rules engine: for example "inactivity_timeout + no user transcript + transcriber encoding ≠ stream encoding → encoding mismatch (confidence high)".
  • Output: a one-paragraph diagnosis with the fix (as a config diff), as HTML and as text to paste into an Upwork message.

11Latency instrumentation

StageMeasured asTarget p50 / p95
Endpointing + STT finalcaller stops speaking → final transcript≤ 350 / 500 ms
LLM first tokenrequest → first token≤ 375 / 750 ms
TTS first bytefirst text → first audio≤ 100 / 250 ms
Turn gapcaller stops → first agent audio heard (caller side)< 800 ms / 1.2 s

These targets follow Twilio's guidance for STT → LLM → TTS chains. Our numbers are measured inside the platform and exclude the phone network. Bolna's own latency_data is shown next to ours.

12Project layout

bolna-call-lab/ ├─ bolna/ # git clone bolna-ai/bolna (unchanged engine) + local_setup docker-compose ├─ agents/ # broken-encoding.json · broken-tts.json · tight-silence.json · fixed.json ├─ lab/ │ ├─ fake_twilio.py # Media Streams client: WAV / mic, playback, marks, events.jsonl, call.wav │ ├─ scenarios.py # create agents via POST /agent, run each scenario end to end │ ├─ caller_script.yaml # what the "caller" says and when (+ cached TTS audio) │ └─ g711.py # mulaw ↔ PCM (numpy table, no audioop) ├─ doctor/ │ ├─ ingest.py # caller events, docker logs, Bolna executions API, Twilio Call Events │ ├─ rules.py # missing-event detector + root-cause rules │ ├─ app.py # FastAPI: /calls, /calls/{id} timeline │ └─ ui/ # timeline, latency bars, diagnosis card ├─ demo/ # scripted recorder (like ProTanki): director + make_video + voice-over studio ├─ .env.example # DEEPGRAM / OPENAI or GROQ / ELEVENLABS keys └─ README.md

13Build plan

Phase 0: Spike ~1 h

  • Clone Bolna and bring up bolna-app + redis with Docker
  • Create one good agent with POST /agent
  • Fake caller connects to /chat/v1/{agent_id}, hears the greeting, gets a reply
  • ✅ Milestone: real Bolna talking over the Twilio protocol on this PC

Phase 1: Reproduce ~3 h

  • Four broken agents plus the fixed one
  • Scripted caller WAV with timed lines and an 11 s "thinking" pause
  • Confirm each fault drops at 10–12 s with the expected reason

Phase 2: Call Doctor ~4 h

  • Log parsers, unified timeline, missing-event detector, rules
  • Web UI in the same visual language as ProTanki Commander
  • Bolna executions-API and Twilio adapters (tested with sample payloads from the docs)

Phase 3: Quality and latency ~2 h

  • Fixed agent: interruption test, latency p50/p95, optional ElevenLabs vs Cartesia A/B
  • Optional: one real Twilio call to your phone, filmed (needs an upgraded Twilio account)

Phase 4: Video ~1 h + your voice-over

  • Demo director (scripted scenes, backdrop, captions) as before
  • Voice-over Studio reused: you read and record, it merges

14What I need from you

ItemWhyCost
Deepgram API keyThe STT Bolna uses by defaultFree credit on signup
OpenAI or Groq API keyThe LLMCents for the demo (Groq has a free tier)
ElevenLabs API keyTTS (the provider the client named)Free tier is enough; a $5 Starter plan avoids limits
Bolna account (optional)Show the same settings in the hosted dashboard$5 free credit
Twilio upgrade (optional)One real phone-call proof clipAbout $20 top-up + about $1/month number
How to hand over keys

You create the accounts and put the keys in .env yourself. I never type passwords or keys into websites. Rotate the keys after recording, in case one appears on screen.

15Video plan (about 4:30)

TimeOn screenMessage
0:00–0:20Title card, then Call Doctor showing a red hangup at 11.6 s"Your calls drop at about 12 s. That's Bolna's 10-second silence watchdog plus its 2-second check. Here's why it fires and how to fix it."
0:20–0:50Architecture diagram with the timers in redWhere audio and transcripts flow, and where they silently break
0:50–1:50Reproduce: real Bolna, the broken-encoding agent; caller audio audible; drops at about 12 s"This is Bolna's own engine, and this is your symptom"
1:50–2:30Call Doctor: swim lanes, missing event highlighted, the diagnosis card"It names the cause from the logs automatically"
2:30–3:00Quick montage of broken-tts and no-audio, each with its own signature"Three different causes, the same 12-second symptom; the logs tell them apart"
3:00–3:50Fixed agent: 2-minute call with an interruption; latency bars p50/p95"Fixed, stable, about 0.8 s responses"
3:50–4:15Config diff (optionally the same fields in the Bolna dashboard)"These are the exact settings to change"
4:15–4:30End card"Send me one execution ID and I'll tell you which one it is within an hour."

16Proposal (draft)

Hi! A drop at about 12 seconds in Bolna is almost always its 10-second silence watchdog (it checks every 2 s, so it fires between 10 and 12 s). It fires because the agent's audio or the caller's transcripts are silently not flowing, most often a transcriber encoding mismatch (linear16/16k vs Twilio's mulaw/8k) or a TTS/output-format failure.

I reproduced this inside Bolna's own open-source engine, built a small "Call Doctor" that names the cause from the call logs, and recorded the fix: [Loom link]

To pin down yours quickly:
1. Hosted Bolna or self-hosted? Twilio, Plivo or Exotel, and is the Twilio account on trial?
2. One or two execution IDs, plus the hangup_by / hangup_reason they show
3. Does every call drop, inbound and outbound? Does the greeting play, and does the agent ever reply?
4. Which transcriber and voice are configured, and are voicemail detection or prompt-based hangup on?

Given one execution ID I can usually name the cause within an hour, then tune latency and conversation flow for production.

Rate: the client posted $5–50/hr and wants an expert. Bid $40–45/hr. Optionally offer a fixed first milestone, "root cause + fix", so the client takes no risk on the first deliverable.

17Risks

RiskMitigation
Bolna's local setup is fragile (the maintainers say so)Phase 0 spike first. Fallback: a minimal pipeline of our own that uses the same protocol and timers.
The hosted platform differs slightly from the open-source codeSay "as in the open-source engine"; verify field names against the current docs
The client's cause is something else (for example a carrier issue)The playbook and the Doctor cover all of causes A–G, and the pitch is the method, not one guess
API costsCache the caller audio; total probably under $10, or about $30 with Twilio
Keys visible on screenKeys only in .env, never shown; rotate after recording

18Glossary

TermMeaning
STT / TTSSpeech-to-text (hearing the caller) and text-to-speech (the agent's voice)
Media StreamsTwilio's websocket that carries live call audio in both directions
mulaw 8 kHzThe compressed audio format used by phone calls; every component has to agree on it
markA Twilio message that confirms a piece of agent audio has finished playing
EndpointingDeciding that the caller has finished speaking
Barge-inThe caller interrupting the agent; the agent should stop talking immediately
ExecutionBolna's record of one call (status, transcript, hangup reason, latency)

19Sources