🔒 Restricted

This page is gated

The Vani OS terminal dashboard demo runbook. If you're meant to see this, you already have the passphrase.

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
.\manage.ps1 status

# LiveKit — voice is dead without a 200/404 here
curl -s -o /dev/null -w "%{http_code}\n" --max-time 8 https://livekit.kcubelabs.com

# capacity — active_calls must read 0 before you start
curl -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.

1

Land on Live screen 6

The dashboard opens here automatically. Nothing to click — this is home base for the whole demo.

2

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.

3

Connect Tab 2

Second row appears. Capacity gauge reads 2 / 5 — call out that both agents are handling real, independent calls at once.

4

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.

5

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.

6

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)CategoryWhat 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 -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 "<SIM_API_TOKEN>" `
  --voice-url "https://vaniai-voice.ngrok.app"

<SIM_API_TOKEN> — the real value is in Config.api_token on the SIM sheet itself (link below), or ask whoever ran the last show. Deliberately not printed here since this page is reachable outside the private repo. 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 editorJobs.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).

Vani OS · terminal dashboard + Sheets ledger · generated for the next show day