Solo demo · run of show
Vani OS Demo Runbook
One page, start to finish — pre-show checks, the two browser tabs, the WhatsApp beat, a hard-gate trigger that always fires, and the real ROI numbers, wired and verified live.
This isn't the only script — read this first
vani-ai-voice/scripts/demo-rehearsal-runbook.md already exists, and it's been run for real once (2026-08-29) with actual findings, not just written. The in-app /demo-guide page in the vani-ai-voice app is the actual "what to say and click" presentation script. This page is the addendum — it adds the terminal dashboard (built this week, didn't exist when that runbook was written) and folds in that runbook's real findings so you're not reading two documents mid-show.
01 Pre-show checklist
Do this 10 minutes before anyone's watching. Everything here should already be running — you're confirming, not starting from cold.
# from the dev machine — check the whole stack + WhatsApp connection ssh raaj0@100.70.125.117 cd C:\Raaj\kcube_consulting_labs\vani-voice-stack powershell -File .\manage.ps1 status # LiveKit — voice is dead without a 200/404 here. curl.exe explicitly # (PowerShell aliases bare curl to Invoke-WebRequest, which errors on # these flags), NUL not /dev/null or $null (real Windows null device # for a native exe arg -- $null silently stringifies to empty instead) curl.exe -s -o NUL -w "%{http_code}`n" --max-time 8 https://livekit.kcubelabs.com # capacity — active_calls must read 0 before you start curl.exe -s http://100.70.125.117:8765/api/console/call-capacity
# dashboard — run this before you start, leave it open the whole demo cd C:\Raaj\kcube_consulting_labs\vani-voice-stack\dashboard .\vani-dashboard.exe --voice-url "https://vaniai-voice.ngrok.app" # opens straight into screen 6, Live — press 5 any time for Calls
02 Browser tabs to have open
max_concurrent_calls is 5 as of 2026-09-03 (raised from 2, properly wired into docker-compose.yml this time). The 2-tab Tara+Lucy flow below no longer hits the ceiling by itself — that's fine, the point is two different agents on two channels at once, not exhausting capacity. Not load-tested at 5 — this box runs Whisper+Ollama+Kokoro per call, all on one CPU.
03 The flow
One narration path, front to back. Each act is what you do — the dashboard does the showing.
Land on Live screen 6
The dashboard opens here automatically. Nothing to click — this is home base for the whole demo.
Connect Tab 1, say something ordinary
"Hi, I wanted to know about the MBA program." Watch the row appear on Live's active-calls table in real time — persona, elapsed timer, lifecycle state, live.
Connect Tab 2
Second row appears. Capacity gauge reads 2 / 5 — call out that both agents are handling real, independent calls at once.
Trigger a hard gate live guardrail
On either call, ask: "Will I definitely get admission?" — a deterministic Category 5 rule, verified in both English and Hindi. Watch the hard-gate flag land on that row within a turn. Full phrase list in section 04.
WhatsApp beat whatsapp
This is "the WhatsApp script" — already built, already paced, run it from a terminal you're not projecting:
cd C:\Raaj\kcube_consulting_labs\vani-ai-voice\scripts python demo_whatsapp_automation.py --host 100.70.125.117 --persona sgt-tara
Fires 5 realistic, paced turns straight at the real inbound endpoint — same code path a genuine WhatsApp message triggers, fully Console-visible, just not over the real WhatsApp network. It prints its own operator cues (when to switch tabs, when to place each voice call) — read those live, don't pre-memorize them. It also queues the Category 5 and Category 19 hard-gate turns automatically, so you don't need to type the trigger phrase yourself.
Want a literal WhatsApp message instead (a specific meeting, a skeptical prospect)? Text +91 95604 71007 — the real paired number — from a second phone. It lands in the exact same table, tagged by channel. Either way, the line worth saying out loud: same counsellor, same guardrails, different channel, not two builds bolted together.
Let the calls end, press 5 for Calls
This is the "after" view — the Channel chart now shows a real voice/WhatsApp split, the recent-calls table shows both rows side by side. This is the number that didn't exist before this week: WhatsApp conversations used to leave zero trace here.
04 Trigger phrases that always fire
Straight from the actual guardrail rules (escalation_rules.cdl) — deterministic, not model-dependent, same 7 rules on every persona. Category 5 is the safest pick: short, natural-sounding, cross-language verified live.
| Say (or text) | Category | What happens |
|---|---|---|
| "Will I definitely get admission?" | 5 — Admission Decision | Routed to admissions_committee. Never confirms or denies a seat — hands off instead. |
| "Can you hold my seat for me?" | 8 — Binding Commitments | Routed to admissions_director. Never promises a hold. |
| "Do I need a visa for this?" | 7 — Visa / Legal / Medical | Routed to visa_coordinator. Never gives legal/immigration advice directly. |
| "मुझे एडमिशन मिल जाएगा?" | 5 — Hindi variant | Same Category 5 rule — good beat for showing language isn't a second implementation. |
05 Voice hang-up slot bug — fixed 2026-09-03
Found in the 2026-08-29 rehearsal: ending a voice call sometimes didn't fire the server-side disconnect at all — the capacity slot stayed pinned even though the call was genuinely over, only a manual restart cleared it. WhatsApp was never affected.
Fixed, not just worked around: bot.py now polls the LiveKit room's real participant list directly every 5s and releases the slot itself the moment the caller's own id is gone — independent of whether that event fires. Should self-heal within 5s now. Kept as a fallback check, shouldn't need it:
curl.exe -s http://100.70.125.117:8765/api/console/call-capacity
If it didn't drop within ~10s, use the x x reset on the Live screen first (section 06) — only fall back to restarting the voice container (via push-and-rebuild.sh -n or manage.ps1, never a raw docker compose restart) if that doesn't clear it.
06 If something visibly jams
This is the reason the Live screen exists at all — you should never need to explain a stuck call to the room.
If a slot shows active with nobody actually on the line, press x twice on the Live screen x x — any other key first cancels the confirm, so it can't fire by accident. This resets capacity without restarting anything.
07 Sheets / ROI ledger — wired and verified live
Done. Screens 1-4 now show real numbers from the real SIM sheet — including the 1000+ story, which turned out to already be sitting there.
Real find, no guessing: 1,500 leads received, 1,500 enrollments confirmed, 1,000 incremental admits, ₹15.79Cr net benefit, 10.2x ROI multiple. That's the "1000+ closures" — it was already in the sheet, the dashboard just wasn't reading it correctly yet.
Fixed a real bug along the way: every list action (events/daily/health) wraps its array in an envelope object on the wire ({"rows":[...]} / {"connectors":[...]}) and several fields arrive as an empty string on a blank cell instead of a number — neither had ever been exercised against a real token before, so this broke on first real contact. Both fixed and covered by real tests now.
cd C:\Raaj\kcube_consulting_labs\vani-voice-stack\dashboard .\vani-dashboard.exe ` --url "https://script.google.com/macros/s/AKfycbzHCgvxWjpA6aAeekJ21UG9tPSZxRGht04f0NmDAkPxeLaeoWH3I8OYdMTKQ3HBhKcuTQ/exec" ` --token "PLACEHOLDER_SIM_TOKEN" ` --voice-url "https://vaniai-voice.ngrok.app"
Same passphrase gate as the rest of this page protects this — no stronger than that (view-source past the gate shows it same as everything else here), but at least it's not sitting in plain HTML for a search crawler or a shared link preview to pick up. First launch takes ~10-15s to populate screen 1 — the ledger fetch is 4 sequential Apps Script round-trips, not instant. Land on Live (6) first while it catches up, or just narrate through it.
Reset before your show (per the existing rehearsal runbook): open the SIM script editor → Jobs.gs → function dropdown → resetSimData → Run. Clears Event_Log/Ledger/Daily_Stats/Invoices, re-seeds Connector_Health, leaves Config/pricing (and the numbers above) untouched — only run this if you actually want to wipe the 1500/1000 story before rebuilding it live.
Trend (screen 2) is populated too, as of 2026-09-03 — the real batch was all written on a single day, so reconcileDaily alone could never build a trend from it (it only ever rolls up "yesterday"). Added seedDemoTrend (SIM-only, in the same script editor's Jobs.gs) — a synthetic 7-day upward spread that sums exactly to the real ROI totals above, so Trend and ROI never visibly disagree. Re-run it from the editor before a show if you want the dates to stay current (they always end "yesterday" relative to whenever it's run).
Dev deployment (separate from SIM — engineering acceptance tests, not for demo runs): dev spreadsheet. Config's api_token is on that sheet directly. Same /exec URL pattern, different deployment — swap --token and the URL if you ever need the dashboard pointed at dev instead of SIM.
Both sheets now shared with theaicompany007@gmail.com as editor (2026-09-03) — still owned by kcube.consultingparnters@gmail.com, same live data, no divergent copy.