Read the run before theorizing
Open the call. Almost every question on this page is answered by three things that are already onGET /v1/calls/{session_id}:
snapshot— the verdict.snapshot.okistruewhen nothing was found;snapshot.issuesis a list of plain sentences naming every problem, and it is written from the run’s own timings, errors and tool activity rather than from anything you have to interpret.transcript— what was said, with every tool call and its result inline. When a model did something unexpected, the reason is usually the line above it.session.userdata— the state the call ended with. A tool that wrote the wrong value shows up here before it shows up anywhere else.
events sits beside them: the durable runtime trace, with agent.ready,
tool.started / tool.ended, provider.failed / provider.recovered,
hold.started / hold.ended, session.error and more. It is where to look
when the transcript reads fine and the call still went wrong.
Everything below assumes you have that page open. See
calls for the full field reference.
Finding the calls worth opening
outcome is the verdict post-call analysis
reached; close_reason filters by how a call ended. To see which endings
dominate rather than to list them, read endings from GET /v1/observability.
The playbook
The agent said something wrong, or made something up
The agent said something wrong, or made something up
Look at: the
transcript, top to bottom, and specifically the
function_call_output items above the bad line.The field that answers it: whether the wrong fact was already in the tool
result. A tool that returned stale or empty data is a tool problem; a tool that
returned the right data and an agent that said something else is a prompt
problem.Then check, in this order:- Did a tool return it? If
function_call_outputcarries the wrong value, fix the tool, not the prompt. Test it in isolation — see testing and publishing. - Did the agent have anything to answer from? If the fact should have come
from a knowledge base, look for a
knowledge_fetchcall in the transcript. No call at all means the model did not think to make one: the knowledge map’s entry titles and summaries are what it routes on. If the call happened and came back without the fact, the page it needed may never have been transcribed, or may be sittingunplaced. See using knowledge in agents and editing. - Did the prompt invite it? An instruction that says what to do but never says what to do when the information is missing gets a plausible invention. Say “if you do not have it, say so” explicitly. See prompting.
- Was it a variable that never resolved? Check
session.userdataand the call’svars. A template that resolved to nothing produces a sentence with a hole in it. See variables.
The agent was slow
The agent was slow
Look at:
snapshot.response_latency — avg_ms, p95_ms, max_ms and
samples. The p95 is the one that matters. A call that was sometimes slow is
what callers complain about, and a mean hides it.The threshold ships on the response: snapshot.slow_response_threshold_ms
is 3500. Past that, snapshot.issues says
Slow responses: p95 time to first word was ….Then read the four stages, which are averaged independently and add up to it:Anything left over is transport and orchestration. A realtime agent has no
separate STT or TTS leg, so those two stages are null on one — see
realtime.Also check
snapshot.slow_tool_calls_in_silence. A tool over
slow_tool_threshold_ms (8000) with nothing being spoken over it is a caller
sitting in silence. The fix is usually not a faster tool but a say operation
before the slow step, or long_running_task on the tool. See
speech operations and
tools overview.snapshot.connection splits connection quality into agent and caller. A
poor reading on the caller’s side is their network, not your stack, and it is
reported separately for exactly that reason.The agent called the wrong tool, or called one with bad arguments
The agent called the wrong tool, or called one with bad arguments
Look at: the
function_call item in the transcript. data.name is the tool
it picked and data.arguments is what it decided to send — not what your schema
said, but what the model actually chose.The field that answers it: the tool’s description, and each property’s
description in its json_schema. Those are the only instructions the model gets
about a tool. If two tools’ descriptions could both plausibly cover the request,
the model will pick one at random and you will see it here.The fixes, in order of how often they work:- Sharpen the descriptions. Say when to use the tool and when not to. Say the format each argument takes and what it means. See schema and description.
- Split the tool. One capability per tool. A tool that does three things has a description that describes three things, and a model that guesses among them.
- Look for an overlap. Two tools whose purposes shade into each other is the usual cause of “it called the wrong one”. A knowledge base plus a lookup tool that both answer “what are your hours” is the same problem.
- Say it in the prompt. Ordering and preconditions (“always confirm the
spelling before calling
create_ticket”) belong in the system prompt, not in the tool description. See prompting.
agent_start_failed instead, the model never got to choose:
two attached tools presented the same name. Read session.error, which says
which.A tool failed
A tool failed
Look at: the
function_call_output item, where data.is_error is true and
data.output is what the model was told. Then events, for the
tool.started / tool.ended pair with its timing.The counts are on the snapshot:The fixes:
- Read what the caller heard. The model is handed the error text and speaks
its own version of it. If the caller was told something misleading, the fix is
the tool’s
on_errorhandling, not the prompt. See tool failures. - Reproduce it outside a call.
POST /v1/tools/{tool_id}/runexecutes the tree with arguments you supply and shows every operation’s result. See testing and publishing. - Check the credential. A
{{secrets.NAME}}that does not exist raises rather than substituting an empty string, which is deliberate. See secrets. - Watch for the detached failure. A
long_running_tasktool acknowledges immediately and fails afterwards, so the model has already told the caller it is working on it. That failure appears on the trace and in thetool.failedwebhook, not in the tool’s own return. See tool failures.
snapshot.provider_failures and provider_recoveries are the different
question of a model provider failing over to its backup mid-call. See
model fallbacks.The agent hung up early
The agent hung up early
Look at:
session.close_reason first. It answers who ended the call, and
the answer is not what the name suggests.If it was the agent (
user_initiated or task_completed), find the
end_call in the tool tree that fired and read the condition above it in the
transcript. An end_call operation reached by a branch whose condition is too
loose is the usual cause — see end call. Check also that
the goodbye is not the only thing on that path: an end_call with nothing
before it hangs up mid-sentence.If it was the caller, read the two turns before the disconnect. A caller
hanging up on the greeting is usually a wrong number or an unwanted call, and
call analysis will have skipped it as
too_short rather than counting it against your success rate.If it was transferred, nobody hung up: the call went to a person and our
session ended there. session.transfer.outcome says whether it connected. Both
the duration and the recording measure our part only. See
transfers.The full list is in close reasons.The call never connected
The call never connected
Look at:
session.close_reason and its bucket. This is the one symptom
where the bucket does most of the work, because it says whose problem it is.The four
configuration reasons worth knowing by sight:missing_published_definition/unpublished_agent_failed— publish the agent. A draft cannot answer a call. See versions.insufficient_credits— top up. See pricing and credits.prepare_failed— the definition would not compile for this call. Runvalidateon the agent; see validation.agent_start_failed— it compiled and died before its first word. Readsession.erroron the call.
readiness — live is the only value that answers calls — and
that a published voice agent is assigned to it. See
phone numbers.The caller was cut off mid-sentence
The caller was cut off mid-sentence
Look at:
snapshot.interruptions, snapshot.false_interruptions and
snapshot.false_interruptions_resumed.A false interruption is the agent stopping because it thought the caller had
started speaking when they had not — a cough, a door, background speech.
false_interruptions_resumed counts the ones where the agent picked its own
sentence back up, which is what resume_false_interruption is for. A high
false_interruptions with a low resumed count is the setting being off. See
turn handling.Then check the audio path:snapshot.noise_cancellation_failed— whentrue, the enhancer turned itself off and the call ran on raw audio, which makes every endpointing decision worse. It appears insnapshot.issuesasNoise cancellation failed and the call ran on raw audio. See audio processing.snapshot.connection—poororloston the caller’s side explains clipping that has nothing to do with your configuration.
snapshot.pace.talk_over_turns and pace.longest_agent_turn_ms. A very long
agent turn is a monologue callers interrupt; shortening it in the prompt fixes
more of this than any endpointing knob.If the whole call was cut off, check the duration. A PSTN call is force-ended
at three hours, and a leg with no media for more than 120 seconds is dropped by
the carrier side — which is what a transfer left on hold too long looks like.The agent did not remember a returning caller
The agent did not remember a returning caller
Look at:
config.conversation.context on the agent that answered. It
defaults to none, which starts every call clean. Nothing else about memory
matters until this is set.Then check, in order:
- Is
contact_keythe same? Memory is keyed to the person, andcontact_keyis your stable id for them, notconversation_id— which is new on every call. Two calls with differentcontact_keyvalues are two different people as far as the platform is concerned. See conversations. - Is analysis writing summaries?
context: "summary"reads the summaries post-call analysis writes, and nothing else produces them. Publishing an agent that asks for"summary"with analysis or its summary switched off is refused — but analysis can be switched off after publishing, and then the summaries quietly stop. See call analysis. - Is
initialize_userdataon? It is what carries what the agent learned about the person into the next call. It applies tosummaryandtranscriptonly. - Was the history erased?
session.content_deleted_aton the old calls, or a retention policy that has passed. Erasing a person’s last remaining call also erases what your agents remember about them. See data retention.
Costs are higher than expected
Costs are higher than expected
Look at: the
cost section of GET /v1/observability before any single
call. It splits the range by kind, provider and model, and the answer is
normally one line dominating the rest.Then the things that surprise people:
- Voicemail is billed as an answered call. There is no answering-machine detection, so an outbound call that reaches a greeting connects, runs, records and bills.
- Hold time is inside the bill.
snapshot.pace.hold_msis time parked on hold music during a bridged transfer — inside the duration, inside the recording, and silent. - A re-analysis is charged again. Backfilling analysis inserts a new usage row rather than replacing the old one, because both model calls really happened. The platform fee does not move; your provider bill does.
- Provider cost is not debited from credits. If your credit balance is falling faster than you expect, it is the per-minute platform fee, not model spend — that goes to your own providers directly.
When the call record itself is the problem
Calls
Every field on the record this page reads.
Close reasons
Every ending, its bucket and its fix.