Scenario playbooks recipes
What it is. The individual knobs are only useful as a combination. Each deployment shape has a small set of settings that matter together. Pick the scenario that matches the customer's environment, set the cluster of values below, then verify (Step 60). Don't tune one knob at a time in isolation: noise, barge-in, and latency trade off against each other.
Noisy call center
- WebRTC APM on (sttConfig.additionalSettings.webrtcApmEnabled) for echo/noise suppression on the bot's own line.
- DeepFilterNet on (sttConfig.additionalSettings.deepFilterEnabled) if you have the
NC_OPT_URLservice — it gates background hum and crosstalk before STT. - vadConfidence ~0.8 so the VAD doesn't fire on background voices.
- vadMinVolume ~0.5 so quiet crosstalk doesn't count as speech.
- numberOfWords 2–3 (noise doesn't barge in) + interruption phrases for real stops.
- botSpeechGraceSecs ~0.3 if any echo.
Digit / TC / IBAN collection
- Prefer DTMF for codes/cards — far more reliable than spoken digit recognition.
- For spoken numbers read to the caller: language = tr + speechNormalization.identityNumbers / generalNumbers on (Part 4).
- Keyterms for proper nouns; boost only cautiously (digits bias poorly).
- Re-ask would help here but is dead — flag it to the customer rather than promising it (see Appendix).
Barge-in-heavy chatty caller
- numberOfWords 0–1 for responsive interruption.
- waitSeconds low (~0.3).
- Acknowledgement phrases so backchannels ("evet", "hmm") don't truncate the bot mid-sentence.
Latency-sensitive deployment
- Lighter TTS model (flash / turbo).
- Lower waitSeconds, lower vadStopSecs.
- Smart turn OFF — it adds a per-turn inference call.
- Skip DeepFilterNet if the CPU/GPU budget is tight.
Speaker-mode / hands-free echo
- WebRTC APM on (sttConfig.additionalSettings.webrtcApmEnabled, AEC) — the bot hearing itself through the speaker is the #1 cause of self-interruption.
- botSpeechGraceSecs ~0.3–0.4.
- numberOfWords ≥ 2 so echo fragments don't count as an interruption.
Symptom these fix: "the bot keeps cutting itself off in the branch office", "it can't hear digits over the floor noise", "it talks over me", "there's a half-second of dead air every turn", "on speakerphone it interrupts itself."
Try it — tuning playbook selector
The mapping recap: dashboard → validator → runtime discipline
What it is. The discipline that makes you trustworthy on a customer call: for any knob, you can name its config key, its validator range/default, and its pipecat-agent consumer (file:line). The full settings table is in phase0-report.md — the searchable finder below reproduces it. Three classes of trap to keep front of mind:
confidenceReask.* (the entire re-ask group), stopSpeakingPlan.voiceSeconds, stopSpeakingPlan.backOffSeconds, and aicEnabled (read but hard-disabled for "crate instability"). They exist in the UI/schema but no runtime consumer reads them.deepFilterEnabled does nothing without NC_OPT_URL pointed at the noise-cancel service. Provider / voice / language can be overridden by is_on_premise (on-prem forces FreyaTTS + Leyla regardless of the dashboard choice).waitSeconds falls back to 0.6 (not the schema's 0.4). silenceTimeout defaults to 10 in the seed but 0 in the validator. voiceSeconds reads 0.3 in defaults vs 0.4 in the validator (and is dead anyway). If a customer's behavior doesn't match the dashboard number, suspect one of these.The honest answer when you're unsure whether a knob is wired in a newer branch than dev is "verify with your pair" — not a guess.
Try it — setting → runtime mapping finder
| Dashboard label | Config key | Validator range | Default | pipecat-agent file:line | Status |
|---|
sttConfig.additionalSettings.vadStopSecs, find every place it is read in pipecat-agent, show me the file:line, and tell me the validator range and default from the dashboard schema — then tell me whether the runtime fallback matches the schema default."Changing a setting via the API GET → merge → PUT → publish
What it is. Settings live on the agent config. The safe pattern (the same one the team's MCP freya_agents workflow uses): GET the agent, merge your single change into the JSON, PUT the whole object back. Never blind-write a partial body — the response is double-nested and a naive write will silently wipe sibling fields (system_prompt has been lost this way). Then publish a version so the change actually goes live.
# 1. GET current agent config (note the double-nested response shape)
curl -s "$FREYA_API_URL/api/v2/agents/$AGENT_ID" \
-H "Authorization: Bearer $FREYA_API_KEY" > agent.json
# 2. edit agent.json: set sttConfig.additionalSettings.vadStopSecs = 0.5
# (merge into the existing object; keep every sibling field intact)
# 3. PUT the full object back
curl -X PUT "$FREYA_API_URL/api/v2/agents/$AGENT_ID" \
-H "Authorization: Bearer $FREYA_API_KEY" \
-H "Content-Type: application/json" \
--data @agent.json
# 4. publish a new version so the change goes live
# (use the dashboard "Publish" button, or the MCP freya_agents publish action)
- Dashboard UI for one-off changes.
- API / MCP for repeatable tuning across agents.
- Always publish after the change — an unpublished draft does not affect live calls.
Trap it avoids: "I changed it in the dashboard but the call still behaves the old way" — almost always an unpublished draft or a partial-write that dropped fields. Confirm the exact endpoint shape and publish step with your pair / the MCP freya_agents tool; response bodies are double-nested.
freya_agents MCP tool, raise vadStopSecs to 0.5, PUT it back, publish a new version, then make a test call and pull ?track=user to confirm the early cutoff is gone."Verify the change with a real call or simulation close the loop
What it is. A tuning change isn't done until you've observed it. Three ways to close the loop, in increasing rigor:
- Test web/phone call. Reproduce the original symptom and confirm it's gone. For VAD / echo issues, pull the separated recording tracks — combined audio hides the gap:
GET /api/v2/call/{callId}/recording?track=user ?track=assistantMeasure the silence between the caller's last word (user track) and the bot's first word (assistant track). Without
track=you get the mixed file and the gap is invisible. - Simulation. For systematic changes, run the agent against synthetic personas in the simulation engine and compare pass rate before / after.
- Check the runtime logs. The agent logs the actual VAD params it booted with. If your dashboard change didn't reach this line, it didn't take effect — suspect a stale published version or an env override:
VAD params: confidence=..., start_secs=..., user_turn_stop_timeout=... # base_service.py:1552
The habit that matters: the base_service.py:1552 log-line check is what separates "I changed the dashboard" from "the change is live." Always close the loop.
{callId}, find the base_service.py:1552 VAD params line, and tell me whether user_turn_stop_timeout matches the vadStopSecs=0.5 I just published — if not, find which env override or stale version is winning."Appendix — the dead-knob honesty list tell the truth
Tell customers the truth about what does nothing today (confirmed against pipecat-agent@dev). When you're unsure whether a knob is wired in a newer branch, say "(verify with your pair)" rather than guessing.
| Knob | Status |
|---|---|
confidenceReask.* (entire re-ask group) | UI + schema present, no runtime consumer |
stopSpeakingPlan.voiceSeconds | visible slider, no consumer |
stopSpeakingPlan.backOffSeconds | hidden div, no consumer |
aicEnabled | read but hard-disabled ("crate instability") |
Deepgram keywords | disabled (TODO); only keyterm works |