config.analysis, and it is on
by default.
It is what makes a list of calls answerable. Without it you have durations and
close reasons; with it you have “47 calls, 31 booked, and here are the six
where the caller asked for a refund”.
What it costs and where it runs
Voice and video only. A text conversation is an idle window rather than a
whole conversation, so analysing one would summarize the same thread several
times over. Analysis never runs on
channel: text.
Nothing analysis does can break a call. It runs after the caller has hung up
and it never raises into the finalize path.
The configuration
boolean
default:"true"
Analyse calls after they end.
boolean
default:"true"
Write two or three sentences saying why the person called, what the agent did
and how it ended. This is the
summary shown on the calls list.object
Judge each call. Takes one field,
prompt (required, non-empty): what a
successful call looks like for this agent. Leave outcome unset and calls
are not judged at all — there is deliberately no inferred default, because a
success rate whose definition you never wrote is a number you cannot act on.array
Values to pull out of the transcript. Flat, and at most 25.
object
An LLM to run the analysis on instead of the agent’s own — the same
{ provider, model, reasoning_effort } shape the agent’s llm slot takes.
Naming one means the workspace must hold that provider’s key. The priority
lane is refused here: analysis runs after the caller has hung up, so it would
cost more and save nobody any waiting.One extracted field
summary, outcome and outcome_rationale are reserved names. They are
built-in outputs, so a field of the same name would be unreachable; using one is
a save-time error. So is defining the same name twice.
Every field is nullable. A call that never mentions the value comes back null,
by design — a required scalar pushes a model into inventing one, which is the
failure that matters here. A value the model answers in a shape the field
cannot hold also comes back null rather than costing you the rest of the
reply.
Setting it
config is the whole agent definition, not a patch, so read it, change the
analysis block and send it all back. Then publish — analysis runs from the
published version.
The outcome is three-valued
success, failure or unknown — and unknown is not a bug. A judge that
cannot say “I don’t know” invents a verdict for a wrong number, a hangup on the
greeting and a call that ended before anything was settled. The model is told
explicitly that a call which ended too early to tell is unknown, not a
failure.
Read your success rate over the calls that were actually judged, not over every
call. That is why GET /v1/calls/stats reports judged_calls beside
successful_calls, and why the observability summary rates success_rate over
judged_sessions. See observability.
outcome_rationale comes back with it: one or two sentences citing what in the
transcript decided the verdict. It is the field that tells you whether your
outcome.prompt says what you meant.
Reading the result
session.analysis on GET /v1/calls/{session_id}, and the identical analysis
object inside a session.completed webhook.
string
none, pending, completed, failed or skipped.string | null
Set only when
status is skipped.string | null
The prose summary, when
summary was on.string | null
success, failure or unknown. Null when no outcome was configured.string | null
Why that verdict.
object
Your extracted values, keyed by field name. Always present;
{} when no
fields were configured. A value the call never established is null.Every status
Every skip reason
A skip is not a failure, and nothing was charged for one.
A turn counts if the caller said something or sent an image. A call made
entirely of photos is a call where the caller communicated plenty, and it is
analysed.
Trying a definition before you save it
POST /v1/calls/{session_id}/analysis/preview runs an analysis definition
against one past call and returns what it found. It writes nothing — not the
summary, not the outcome, not the fields — which makes it the way to tune an
extraction prompt without shipping it blind and waiting a day.
It is still a real model call, so it costs one. That is the trade: a few cents
to find out that party_size comes back as "four" instead of 4.
Pass agent_id to judge against a different agent’s prompt. Omit it and the
call is judged against the definition it actually ran, which is what you want
when tuning a spec for an agent that already exists. The response carries
provider and model so you can see which model produced the result before
committing to the spec.
Re-analysing calls that already happened
POST /v1/calls/analysis/backfill applies each call’s saved agent
definition to calls that ran under an older one, or were never analysed. It is
also the only retry path for a failed or a stuck pending analysis.
Analysis is not userdata
Extracted fields are never merged into a call’suserdata, and this is
deliberate.
Merging them would let a guess overwrite a tool-confirmed value, with nothing
downstream able to tell which was which. Read them separately:
userdata for
facts the agent acted on, analysis.fields for facts you want to report on.
See userdata.
Where analysis feeds back into calls
An agent withconversation.context: "summary" opens each new call with
summaries of that person’s recent conversations. Those summaries are the ones
post-call analysis writes — nothing else produces them.
So publishing an agent that asks for "summary" while analysis or its summary
is off is refused:
Other refusals at publish
See validation.
In the dashboard
The call detail page has a Call analysis panel: the outcome as a badge, the summary, why that verdict, and the extracted fields as a table. When there is nothing to show it says which of the reasons above applies rather than rendering an empty box. The reload control in its corner opens a dialog with both operations — Re-run uses the agent’s saved definition and writes the result, and Try current draft previews the draft you are still editing and writes nothing. Both are hidden on a call that is still running and on one whose content has been erased, because neither has a finished transcript to read.Calls
The record analysis is written onto, transcript and all.
Webhooks
Push the summary, outcome and fields to your own systems.