agent.yaml.
Two different jobs, two different pages. This one is what to change. If you
are looking at a slow call and want to know which part was slow, start with
Reading the latency numbers instead, then come back here.
On this page:
- All of it in one package - every setting at once
- Where the wait actually is - the spans in one turn
- The short list - eight changes, in order
- Troubleshooting - settings that quietly do nothing
All of it in one package
agent.yaml
targets.yaml
Where the wait actually is
One turn is four spans, and they are nothing like equal:
The LLM is typically the largest span, and the silence window is the most
predictable, with the rest smaller than people expect. That is why the order
below is not the order of size. It is the order of how reliably each one pays
off.
The turn ceiling is the exception, and it is worth checking early rather than
last. Nothing in a package could reach it before
pace existed, so it sat at
the framework default no matter how short the authored silence window was.
Reading the latency numbers shows what unmute dev
reports for every turn, so you can see which span is slow on your own agent
before you change anything.
The short list
Eight changes, in the order they reliably pay off. The keys they use:on listen and speak
Binds SLNG for listening and speaking. The caching and the per-connection
settings below rest on it.
on the listen binding
The transcriber. Choose it on time from the end of speech to the final
transcript, not on its accuracy score.
gateway code
The SLNG speech gateway each
listen or speak model connects through.true | false
default:"false"
Holds the TTS connection open, so the provider’s session setup is done before
the text arrives.
snappy | balanced | patient
default:"balanced"
On a
turn binding. The ceiling: the longest the agent keeps waiting before
answering regardless.positive duration
On a
turn binding. The floor: how long you have to stay quiet before the
agent believes you finished.list of entries
Lookups that run once, before the greeting, into variables the prompt can
name.
string
On a
think binding. "none" asks for no thinking before the first token.string
On a tool. A short line the agent says while the tool runs.
Bind SLNG for listening and speaking
unmute init does this already, and every shipped example keeps it. It is the
choice the rest of this section rests on: the caching described in
Execution Layer and
Context Router exists on SLNG’s own layer, and the
per-connection settings below are only exposed by the SLNG plugins.
agent.yaml
Put the models near your callers
Network time is charged on every span above, not once per call: the turn detector, the transcriber, the reasoning model and the speech model each pay a round trip. Coval’s guide to measuring voice AI latency explains why that adds up faster than people expect. Choose a nearby endpoint or location for each STT, TTS and LLM provider when its integration supports one. With SLNG, set a world part on each speech model to choose its API gateway. See Regional infrastructure for the full picture, including where the agent itself runs.agent.yaml
Choose the transcriber on how fast it finalises
Not on its accuracy score. The turn detector reads the transcript to decide whether you have finished talking, so a transcriber that has not finalised yet holds the whole turn open: the agent is waiting on text, not on thinking. The number to compare is time from the end of speech to the final transcript, and it varies a lot between models that score similarly on accuracy. Two models can return the same words with very different waits. For independent, like-for-like comparisons across providers, see Coval’s voice AI benchmarks. Then confirm on your own audio, because accents, phone codecs and the length of a typical utterance all move the result. The same model reached two ways is also two different waits.deepgram/nova:3
and slng/deepgram/nova:3-en are the same vendor model, one proxied through SLNG
and one hosted by it, and the proxied route finishes noticeably sooner to the
final transcript. That is why the scaffold and the examples take it.
Speech to text has the numbers and the caveats.
Hold the TTS connection open
Off by default, and it works on both targets: on LiveKit since the SLNG plugin shipped it, and on Pipecat sincepipecat-slng 0.5.2. It takes the provider’s
session setup off the front of every segment, which shows up most on the first
segment of a call and on the fastest turns.
It holds a second connection open while an utterance runs. A route that
already reuses a healthy connection has no setup cost to remove, so it pays
for the spare and gains nothing. Measure your own route with it on and off.
agent.yaml
Set the pace, then the silence window
Two settings, and the order matters. Reach forpace first.
pace is the ceiling: the longest the agent keeps waiting before answering
regardless. snappy, balanced or patient, defaulting to balanced. A turn
that feels flat and long is sitting at the ceiling, and only this moves it.
endpointing_delay is the floor: how long you have to stay quiet before the
agent believes you finished. Lowering it also makes short replies finalise
sooner. The transcriber is only asked to finalise once the silence window
elapses, so the change pays twice on a “yes, that’s right”. But lowering the
floor alone does not shorten a long turn.
agent.yaml
local/silero is what Pipecat runs. LiveKit needs its own turn model, so a
package that ships to both overrides it in targets.yaml:
targets.yaml
pace. snappy will occasionally answer someone who
was pausing to think, and that failure never appears in a latency figure, so
check for it deliberately. patient reproduces the framework defaults exactly
and is the escape hatch for callers reading out digits.
See Turn taking for every legal value and what each
becomes on each target, and Turn detection for what
actually runs.
Resolve what you already know before the call
The cheapest turn is the one that never happens. A date, the number a call came from, and whatever your own records say about that number are all knowable before anybody speaks.prefetch: resolves them once, before the greeting, and lands
them in variables the prompt can name.
Two shapes cover most of what is worth moving: a step that collects a phone
number the call already carried, and a date that costs two chained tool hops
with nothing spoken over them.
agent.yaml
clock: entry works on any route, and so does a tool: entry whose
arguments you already hold, once you say writes: false on it. A source:
entry depends on the route: LiveKit’s two routes supply every fact, and
Pipecat’s two Twilio routes supply a smaller set, each in one direction only
for a phone number. unmute validate warns when an entry can never resolve on
a target. See Where it works for the full
grid.
A fact from the carrier is a proposal, not a settled value, so mark anything a
caller could dispute with confirm:. The step that used to collect it then reads
it back and asks for a yes, which is one round trip where collecting was several.
Deciding what qualifies is most of the work, and it has its own traps: a slow
lookup moves the wait to before the greeting, where the caller has nothing at all
to listen to. See Pre-fetch for how to think about it,
and writing one for the syntax.
Cut LLM round trips before tuning connections
The largest span, and every control hop costs a whole round trip. Collapsing a multi-step flow into one task, and asking for one piece of information instead of three, removes whole round trips, which usually outweighs every connection-level setting on this page combined. Look for: a task that could be one task instead of three, a confirmation step the tool already enforces, and a prompt long enough to slow the first token.agent.yaml
Speak before a tool runs
The tool is usually not the wait. A local handler returns in milliseconds; the caller is waiting through the second LLM round trip and the speech after it.announce: fills that gap with something to listen to.
This is for a fact that genuinely has to be fetched now. If it was knowable
before the call, pre-fetch it instead: a cover line shortens a round trip to sit
through, while a pre-fetch removes it.
tools/check_slots.yaml
Troubleshooting
A param you authored never reaches a target
If your package has atargets.yaml that overrides a model, a target’s
params: block replaces the base block rather than merging into it. A param
authored on the base model never reaches that target, and nothing warns you.
Fix: author it on the override.
The wait stays flat and long, turn after turn
That is usually the ceiling rather than the floor, and loweringendpointing_delay will not move it.
Fix: set pace on the turn binding. See
Turn taking.
Next
Reading the latency numbers
Where the time goes in a turn, and what to change when one number is too big.
Pre-fetch
Deciding which lookups to remove before the greeting.
Turn taking
pace and endpointing_delay, the two settings above, in full.Execution Layer
Caching and routing for speech, on SLNG’s own layer.
Context Router
Caching at the reasoning step.
Regional infrastructure
Putting the models near the caller.