unmute dev displays activity and values as they arrive, so you can inspect
finished work while a later step is still running.
This page is about reading the numbers. For the settings that move them,
see Optimizing your agent.
- Where the time goes - the parts of one turn
- What is making your agent slow - symptom by symptom
- How Unmute measures it - where the numbers come from
- What each number means - reading the dev page
- Reading a slow turn - a worked example
- What this does not measure - the honest limits
Where the time goes
Speech recognition, model generation and speech synthesis stream. Some work starts before an earlier stage has finished, and source measurements can cover more than one part of the wait. Their intervals can overlap. Do not add first-response values to reconstruct reply latency or total model time.Tool calls can add model requests
A common tool flow needs the model once to choose a tool and again to use its result:LLM 1, LLM 2, and later rows instead of
assuming every tool reply has exactly two model calls. The tool’s duration
covers its own work; separate model measurements cover the requests around it.
Two overlapping calls to the same tool keep separate rows and outcomes.
returned means the tool supplied a result. It does not prove the business
operation succeeded. Handoff and task controls are excluded from business
tool timing: their duration can include a whole sub-conversation.
What is making your agent slow
Compare the reported intervals and their source details to find a useful place to investigate.Turn detection is a wait you chose
A repeatedturn_detection value can point to a configured wait. Compare
several turns and the source’s definition before changing a model.
Three settings matter: the detector you bind, semantic_endpointing, and
pace. On LiveKit the detector also decides where its work happens:
turn-detector-mini runs on your machine, and turn-detector runs in LiveKit
Cloud. semantic_endpointing: off removes the turn model on LiveKit and the
end-of-turn analyzer on Pipecat; any other value keeps it running.
The floor and the ceiling are different numbers
endpointing_delay sets the silence floor. pace selects the endpointing
ceiling, taking snappy, balanced or patient on a turn binding and
defaulting to balanced.
If a turn is hitting the ceiling, lowering the floor alone will not shorten
that wait. Compare repeated low and high values, then check both settings.
Turn taking explains every legal value, what it
becomes on each target, and how to choose between them.
Or let the transcriber decide, and answer early
On Pipecat, a Deepgram Flux or Cartesia Turns listener can end the turn itself (turn: provider: listen), and with eager: true the reply is generated while
the transcriber is still confirming the caller stopped. The wait you see under
turn_detection then belongs to the transcriber, the ceiling is its own
end-of-turn timeout, and the early answer is dropped if the caller goes on.
Turn detection has the shape and what it refuses.
The model can think before it speaks
On a reasoning model,reasoning_effort controls how much reasoning happens
before the answer. The shared example profiles and unmute init write none.
Raising it trades more reasoning for more work before the answer is available.
A first-response measurement can precede visible words, so read its source
label alongside the streamed conversation.
The prompt is re-read every turn
Instructions go with each request, along with the conversation so far. A long instructions file can affect later requests as well as the first one. For tasks and handoffs you control how much is carried across withcontext.history, max_messages, and summarizer. Carrying the full history
into a task is a choice, not a default you have to keep.
How Unmute measures it
The generated agent observes native SDK activity, lifecycle events and timing reports. Each quantity keeps its source and scope. Model-call counts come from observed SDK invocations on both targets, not from the number of timing values. Hidden network retries are not counted as separate SDK invocations.1
The agent prints each update
The generated
dev_metrics.py prints a marked JSON record when text,
activity or a measurement changes. Available values do not wait for a
completed-turn report.2
The dev server forwards the record
The local server reads the run’s output and forwards identified records to
the page, with a bounded replay buffer for reconnects.
3
The page updates the matching item
Call, operation and measurement identities keep late values on the right
item. Repeated records add no duplicate rows. Unknown associations remain
unassigned.
unmute dev enables UNMUTE_DEV_METRICS; the producer stays inert when it is
not enabled.
Raw updates, including recognized and generated transcript text, are kept in
build/<target>/dev.log. The browser’s measurement filter shows activity and
values with their call IDs, and omits repeated text fragments. To inspect raw
measurement records:
At a glance and debug details
Each reply shows its reported reply latency and observed model-call count. Under it, eachLLM N row shows its own first-response time and request
duration as soon as they arrive. TTS first-audio time and tool duration also
stay visible. There is no need to expand a reply to compare model requests.
Debug details holds turn detection, transcription delay, text aggregation,
node timings, playback delay, and speech duration. The collapsed Call
diagnostics footer holds call first-speech timing and activity or measurements
with no proven exchange. These do not interrupt the conversation.
It also holds model, provider, request IDs, and source names. Use these when the
main timings do not explain the wait. Definitions live here, rather than
repeating under every value in the conversation.
What each number means
Only captured measurements are shown. Missing and pending values have no
placeholder. A real zero is
0ms; positive values below one millisecond
show as <1ms. Values use milliseconds until rounding reaches one second,
then seconds with two decimal places.
Expand Debug details for model, provider, request and source names, even
when timing is unavailable. A reply-level or node-level value stays separate from a
request-level value. For example, LiveKit’s
llm_node_ttft is labelled LLM node
TTFT, and tts_node_ttfb is labelled TTS node TTFB. Neither
creates a model request or replaces that request’s own timing.
A known count says how many SDK model calls were observed for the reply, with
so far while it is open. A reply-level report alone shows no request count.
Partial source coverage or a lost slice of history labels the count as observed.
Where a reply’s time went, on Pipecat
Pipecat splits each measured reply into the parts that make it up, and the dev page shows them in time order under the reply latency, in the call diagnostics:
The first line says what the interval was measured from: the caller falling
silent, or the client connecting. A greeting is anchored on the second, so it is
never compared with a reply to someone who spoke.
This breakdown carries no reply identity, so it sits with the unassigned values
rather than being attached to a reply by timing. LiveKit reports its own per
request timings instead and has no equivalent split.
Reading a slow turn
A reply might show:failed with the kind
of error its handler raised, or timed_out when it ran past its deadline. A call
that ends because something broke names the service that stopped it. The messages
themselves are in build/<target>/dev.log, because an error’s text quotes what
was being worked on and these records carry no prompts, tool arguments or
results.
Every model call after the first follows a tool call or a control, and the row
above it says which. A HANDOFF row is a call into a task, or a handoff to
another agent. It carries no duration, because it hands control over rather
than returning a result, and on LiveKit that call stays open until the task
finishes.
The three LLM rows describe three requests. Their first-response values do not
add up to reply latency. The later request-duration value also does not replace
the first-response value for LLM 1. Inspect each request and tool before
choosing which part to change.
A long speech duration can exceed reply latency without a problem: the caller
is listening during that speech. Keep it separate from the wait for the first
audio.
What this does not measure
- Delivery to the listener. Runtime timings exclude the trip to your browser or through a phone carrier. Provider request timings can still include the worker’s network trip to its model service. Verify the phone route on a deployed agent.
- Unavailable source detail. Targets and services expose different quantities and associations. A value without a proven reply stays unassigned, even when it arrives while another reply is visible.
- Complete history after a gap. Feed loss does not restart audio. If the replay cannot recover all records, the incomplete-history label and observed count remain until a new call, even after individual values become fresh.
Where to go next
Turn detection
The settings behind turn detection, which can be a large part of the wait.
Pre-fetch
Remove a round trip instead of waiting it out.
SLNG Execution Layer
What the speech models a scaffolded package binds run on.
Tools
Every kind of tool a turn can call, and what each costs you.