- Quickstart - a complete voice desk
- Pros and cons - control and complexity
- Bind the models - one job per entry
- Choose the turn - defaults and selectors
- Extend the desk - grow the workflow
- Advanced - switch architecture
- Troubleshooting - correct common failures
- Where to go next - model references
Quickstart
Create a new package with a LiveKit target. If initialization opens the console, select LiveKit and finish creating the package.Terminal
voice-desk/agent.yaml with this complete file:
voice-desk/agent.yaml
channels: and capacity: live in targets.yaml. Set them at the top of the scaffold’s file:
targets.yaml
state.py as it is. The package needs it, and this desk saves no values.
Keep the scaffold’s targets.yaml, but remove its target-level models: overrides because the model palette above replaces the scaffold’s.
Keep its framework provider and version pin, and remove any phone connection: for this browser example. Replace voice-desk/instructions.md with this complete prompt:
voice-desk/instructions.md
OPENAI_API_KEY and SLNG_API_KEY in your shell or the package’s .env, then run:
Terminal
uv.
The example below stays with this same desk agent.
Pros and cons
Pros
- Control the workflow. On LiveKit and Pipecat, combine tasks, handoffs, saved values, and tool calls. Each step can have its own instructions and access to the values it needs.
- Choose each role. Use a supported transcriber, reasoning model, and voice independently. Spend more on the part that needs better quality without replacing the others.
- Find the failing stage. Compare the recognized text, model reply, tool result, and spoken output. A wrong transcription needs a different fix from a failed tool.
Cons
- More sources of delay. Turn detection, transcription, reasoning, speech generation, and tools all contribute. Streaming reduces the wait, but does not remove those costs.
- More parts to operate. Several integrations bring separate credentials, availability limits, and failure modes.
- Less audio context for reasoning. A transcript carries the words, but can lose tone, hesitation, and other clues in the caller’s voice.
1. Bind the three models
The quickstart names the transcriber, reasoning model, and voice undermodels.
The agent binds its reasoning and voice by name. A single transcriber is selected automatically for the package; a top-level listen: names one when there are several.
cascade | realtime | live
Set
cascade, or omit the key for the same result. Cascade is supported on all four targets, subject to each target’s model and feature limits. It is the only architecture the Twilio target takes.a models.think entry name
required
Agent-level reasoning binding, such as
agents.desk.think: reasoning.a models.speak entry name
required
Agent-level voice binding, such as
agents.desk.speak: voice.a models.listen entry name
Agent-level transcriber, for an agent that speaks another language. Omit it to use the package’s: the top-level
listen:, or the only models.listen entry. When every cascaded agent names its own, no top-level listen: is needed. Cascaded agents only, and its entry takes no fallback:. The slng and twilio targets refuse one that differs from the package’s. On livekit and pipecat each agent speaks and listens with its own models after a handoff.
A model name is forwarded to its provider. Validation checks the binding, not whether your account can use that model.
2. Choose when the agent replies
The quickstart binds LiveKit’s local turn detector. A cascade needs a turn binding. When adding Pipecat, overridedetector for that target with provider: local and model: silero.
Follow Turn detection for the target override shape and timing options.
a models.turn entry name
Package-level selector. One turn entry is selected automatically. With several entries, name the one to use here.
3. Add tools, tasks, or saved state
Extend the same desk with a local tool whose Python function has typed arguments and a Pydantic return model, then add a task when a job needs its own steps. For example, the caller wants to change an existing request:- Look up the request with a tool.
- Collect the change in a task and save its result.
- Confirm the details before running the update tool.
- Handle a refused update, or hand the call to another agent with the context it needs.
State class in state.py holds values across steps. Each prompt names the values it may read; the tool checks the conditions for changing the record.
Separate steps give you places to check the behavior, but do not guarantee a correct tool call.
Test successful updates, missing inputs, and failed actions.
Tasks, flows, and handoffs are supported on LiveKit and Pipecat cascade targets.
SLNG has its own target limits.
Both S2S architectures support tools, but their current Unmute integrations do not support these task and state controls.
The complete salon concierge shows booking, task return, and handoff.
Its phone routes and tracing need their own configuration and credentials.
Advanced
Compare latency across architectures
Compare latency across architectures
Transcription can run while the caller speaks, and synthesis can begin while the model generates text.
Do not add all stage durations as though every stage waits for the previous one to finish.
Model choice, turn timing, network placement, and tools affect the wait for the first audio.Tune the slow stage before replacing the architecture, and compare completed actions as well as response time.
See where the time goes and the architecture comparison.
Switch this desk to speech-to-speech
Switch this desk to speech-to-speech
Follow Switch architecture.
Replace the model palette and the agent’s
think:/speak: bindings together.
Remove old target overrides and any features the destination refuses.Switch back from live or realtime
Switch back from live or realtime
Use the quickstart’s complete
agent.yaml for a minimal cascade, keeping your prompt file.
For an existing package, copy its model palette and think:/speak: bindings instead, and add the required provider secrets.
Remove models.live, models.realtime, and the old agent binding before validating.
Do not replace a larger package’s whole file if you need to retain its tools or workflow.Troubleshooting
Validation rejects an agent-level listen field
Listening is selected for the package, not for each agent. Fix: move the selector to the top level, or omit it when only one transcriber exists.The agent waits too long or cuts callers off
Turn settings or provider latency may be responsible. Fix: inspect the dev measurements and follow Turn taking before replacing all three models.A provider is refused for one role
Targets support different providers for each role. Fix: choose a supported integration from that role’s reference, then validate every declared target.Where to go next
Add a task
Give one job its own workflow.
Choose architecture
Compare and switch pipelines.