Runbook

Install

uv sync --group dev --group voice     # one venv; Pipecat 1.7.0 with the extras the bot and the eval harness need
make db                               # Postgres on :5433 — Docker when the daemon is up, a local cluster otherwise
cp .env.example .env                  # then fill DEEPGRAM_API_KEY, MURF_API_KEY, AWS credentials, SARVAM_API_KEY

.env may be read and edited; it is never printed and never committed. AWS_REGION must be ap-south-1: Bedrock first-token p50 is 519 ms there and 12,109 ms in us-east-1 (OBSERVED 2026-08-26).

Run the bot

poc/pc-web/server/run.sh                       # the live bot, http://localhost:7860/studio
poc/pc-web/server/run.sh -t eval --port 7861   # headless, for the regression suite; tags sessions `eval`

run.sh sources .env, pins the region, the model, the voice and the STT endpoint, and derives ORI_RUN_TAG from the transport flag so an eval run can never join the live corpus. Do not restart the :7860 bot to run an eval; use :7861.

Every knob

All of them are read once into BotConfig (poc/pc-web/server/config.py) and written into the session header, so a latency number on disk always names the configuration that produced it.

env var default what it does
ORI_DEEPGRAM_URL wss://api.in.deepgram.com/v2/listen the STT socket; see below
ORI_EOT_THRESHOLD 0.8 Flux end-of-turn confidence; 0.7 cut him off mid-number
AWS_BEDROCK_MODEL google.gemma-3-27b-it the prose model
AWS_REGION ap-south-1 never a US region
LIVE_VOICE hi-IN-shweta Murf voice, chosen by ear
ORI_RUN_TAG live eval keeps a run out of the corpus and disables the idle timeout
ORI_HOLD_S 1.5 how long a cut-off figure waits for its continuation
ORI_PLAYOUT_GRACE_S 0.4 audio the browser is still playing after the server says it stopped
ORI_HANGUP_WATCHDOG_S 20 armed to hang up but no audio ever finished: end anyway
ORI_EVAL_PONG_TIMEOUT 180 the eval socket's patience for a grader running Whisper on this CPU
OTEL_EXPORTER_OTLP_ENDPOINT unset tracing is off unless a collector exists

The India STT endpoint

api.in.deepgram.com terminates in AWS ap-south-2 (Hyderabad). OBSERVED from Mumbai, same minute, n=8: TCP p50 38 ms vs 270 ms to api.deepgram.com; websocket handshake 303 ms vs 1,175 ms. A websocket pays that round trip repeatedly, so roughly 258 ms of the old 679 ms STT p50 was geography.

It is undocumented: absent from Deepgram's docs, changelog and status page. No SLA, no incident visibility. Its TLS certificate was issued 2026-09-08 and expires 2026-12-07. It is data-plane only (/v1/projects returns 404). Our key and flux-general-multi are accepted on the handshake.

If calls go deaf — the studio page shows a red service_error line and the session record carries the row — flip back with no code change:

ORI_DEEPGRAM_URL=wss://api.deepgram.com/v2/listen poc/pc-web/server/run.sh

STT settings that ship, with the measurement behind each: language_hints ["hi","en"] (callers code-switch); numerals=True (Flux renders Hinglish numbers in English, where numerals works: 7/8 → 8/8 rates recovered, 0 wrong either way, n=14); no keyterms (14/14 byte-identical transcripts with and without, and a hallucinated हज़ार next to a truck size is a wrong rate).

Verify

.venv/bin/python -m pytest tests/ -q                       # the suite
.venv/bin/lint-imports                                     # "domain is pure": 2 contracts kept
.venv/bin/ruff check app tests poc/pc-web/server scripts   # lint
make mutate                                                # mutation testing over the money path and the router
set -a; . ./.env; set +a; .venv/bin/python poc/e2e.py      # end to end, a fresh bot per scenario

Measure

.venv/bin/python poc/analysis/mine_calls.py     # every live call: intents, endings, latency, feedback
.venv/bin/python poc/analysis/replay_calls.py   # replay live calls through the current brain
.venv/bin/python poc/analysis/perturb.py        # the router's brittleness under five perturbations
.venv/bin/python poc/analysis/mine_words.py     # the model's vocabulary and what the voice is handed
.venv/bin/python poc/converse_bakeoff.py        # 12 models × 3 passes × 27 calls, two judges
.venv/bin/python poc/bakeoff.py                 # the gross-failure screen (leaks, stray slots, parroting)
.venv/bin/python poc/rate_call.py               # score a call by ear

Results land under app/data/asr/ (gitignored); the ones worth keeping are copied to docs/experiments/<date>-<topic>/.

Publish these docs

./deploy/docs.sh infra    # once: bucket, certificate, CloudFront, DNS for docs.logistics.citesearch.in
./deploy/docs.sh site     # build docs/ into site-build/ and sync

Separate from deploy/deploy.sh, which owns the load-booking demo and must not be run casually — it mutates live AWS resources.