# Agent Performance (/docs/agents/performance)

> For the complete documentation index, see [llms.txt](https://docs.tandemcx.ai/llms.txt). Any page on this site can be fetched as Markdown by appending `.md` to its URL.

<Callout type="info">
    **This page is being rolled out.** If **Agent Performance** isn't under **Insights** in your
    sidebar, it isn't enabled for your organization yet. Contact support.
</Callout>

**Agent Performance** compares your AI agents on the same measures over the same date range. It
answers questions like "which agent hands off the most conversations?" and "did last week's prompt
change help?". Open it from **Insights → Agent Performance**.

It covers conversations an AI agent handled and finished. A conversation appears once it ends.
Use [Supervisor](/docs/agents/live-conversations) for what is happening now.

Every number on the agent's page opens the conversations it counts. See
[Drill-downs](#drill-downs).

## The agent list

The page has eight tiles for all agents together, then a table with one row per agent. Each tile
compares against the **prior period**, the range of equal length immediately before the one you
selected. The tiles are **Conversations**, **Containment**, **Resolution**, **Negative
sentiment**, **Transfer rate**, **Take-over**, **Avg handle time**, and **Repeat in 7 days**.

Agents are sorted by conversation count. Click a column heading to sort by it. Click a tile to
rank the agents by that measure instead: the agents with the lowest containment or resolution
come first, and for every other measure the highest value comes first. Agents with no value for
the measure come last.

A rate that trails the organization's by 10 percentage points or more is flagged with a warning
icon, once the rate rests on at least 30 conversations. For resolution and negative sentiment the
30 are judged conversations. For repeat in 7 days they are the conversations old enough to check.
Click a flagged value to open that agent's page with the measure selected.

The filters are the same as on the other Insights pages: a date range (UTC) and, when your account
has more than one channel in range, a **Channel** filter. See [Insights &
Reporting](/docs/workflows/insights-and-reporting) for channels and for why dates are UTC. The
longest range you can select is 90 days.

An agent that handled conversations in the prior period but none in this one is still listed, with
zero conversations, so a drop to zero is visible.

Click an agent's name, or anywhere on its row, to open its [detail page](#the-agent-detail-page).

## What each number means

Every rate is a count divided by a stated number of conversations. Hover over a value to see the
counts behind it (for example, "42 of 60").

| Measure                | Calculation                                                                                                               |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Conversations**      | Conversations the agent handled that started in the range and have ended                                                  |
| **Containment**        | Conversations that were not transferred, not taken over, and did not end in a fault, divided by all conversations         |
| **Resolution**         | Conversations whose outcome is `success`, divided by conversations whose outcome is `success` or `failure`                |
| **Negative sentiment** | Conversations with `negative` sentiment, divided by conversations with `positive`, `neutral`, or `negative` sentiment     |
| **Transfer rate**      | Conversations the agent transferred to a person or number, and where the transfer connected, divided by all conversations |
| **Take-over**          | Conversations a supervisor took over, divided by all conversations                                                        |
| **Avg handle time**    | Average duration of conversations that have a recorded duration, shown as `m:ss`                                          |
| **Repeat in 7 days**   | Conversations followed by another from the same caller within 7 days, divided by conversations old enough to check        |

An agent that handles the same conversation twice, for example when a workflow hands the caller
back to it, counts twice in **Conversations**: once for each time it answered.

A value shows **—** when its denominator is zero. A dash means there was nothing to measure. It
does not mean 0%.

Totals across agents add up the counts and then divide. A two-conversation agent therefore does not
weigh as much as a two-thousand-conversation agent.

### Containment counts a hang-up as contained

A caller who hangs up is contained, because the agent did not hand the conversation to anyone. A
caller who hangs up in frustration still counts. Read containment next to **Resolution** and
**Negative sentiment**.

Two other endings are contained too:

- **The agent hands the caller back to a workflow.** The workflow continues the call, so no
  person took it over.
- **The agent switches the caller to another AI agent.** This is not a transfer and does not count
  toward the transfer rate.

A call that ends before the agent could answer, usually because the agent could not be reached,
counts as a fault, so it is not contained. The end-reason breakdown shows it as **Disconnected**.

### Webchat conversations

Chat agents report no transfers, so their transfer rate is 0%. A chat ends only when the agent
ends it or the conversation reaches its turn limit. A chat the visitor leaves without an ending
never finishes, so it is not counted. Together these make containment for a chat agent read
higher than for a voice agent, and abandoned chats do not appear.

### Resolution and negative sentiment are judged by AI

An AI model judges these two after each call, from the conversation's [post-call
analysis](/docs/agents/voice-agents/post-call-analysis). Wherever they appear, a sparkle icon
marks them as AI-judged. Hover over it to read "Judged by AI after each call". The mark is on the
tiles, the table headings, the chart title, the sheet headlines, and each conversation row that
shows a verdict.

Resolution requires the **Call outcome** built-in field to be on for the agent. Negative sentiment
requires **Caller sentiment**. A conversation with no analysis, or with an `unknown` result, is
left out of the denominator.

The tile footer shows how much of the data the rate rests on, for example "AI-judged 38/60", the
judged conversations over all conversations. A high resolution rate on a small share of
conversations is weak evidence. Turn on the Call outcome field and write a clear definition of
success to raise the share.

The analysis does not store a reason for either verdict. A conversation row shows the verdict and
opens the conversation on its **Analysis** tab, where the call summary is. Voice calls only have a
summary.

### Repeat in 7 days

A conversation counts as a repeat when the same caller contacts your organization again within 7
days after it, through any agent or workflow. The follow-up matches when it has the same contact
record or the same phone number as the measured conversation. A conversation with neither is not
counted either way.

Calls your organization placed (outbound calls) are left out on both sides: an outbound
conversation is not checked for a repeat, and an outbound call to the same caller is not a
follow-up.

Only conversations that started at least 7 days ago can be checked, since a newer one has not had
time to be followed up. Conversations from the past 7 days are therefore left out of both sides
of the rate, whatever range you select. A range that ended a month ago excludes nothing, and a
range that ends within the past week can show **—**. The follow-up can be on any channel. The
Channel filter applies to the conversations being measured, not to the follow-up.

### First-agent attribution

A conversation is credited to the agent that answered it. If that agent switches the caller to a
second AI agent, the second agent has no conversation of its own, and the whole conversation
counts for the first agent. The tools the second agent runs count for the first agent too, so
they appear in the first agent's **Tool errors**.

A switch to another AI agent is not a transfer. The detail page counts it separately, in a line
under **How conversations ended**.

## The agent detail page

The detail page shows one agent over the same range and filters. From the top, it has:

- **The header.** The agent's name, a badge for each channel you filtered by, and a badge such as
  `v2.1 live` for the agent's published version. **Open Agent** opens the agent's editor. **View
  Coaching** opens the agent's page under [Coaching](/docs/agents/coaching), where Tandem
  recommends changes to the agent. **Back to Agent Performance** returns to the list and keeps the
  range and channel.
- **Eight stat tiles.** The same eight measures as the list, for this agent. The tiles are also
  the metric selector. A tile flagged with a warning icon trails the organization.
- **The metric panel.** A chart, with **Changes in range** under it, then **What moved it**.
- **Where conversations go.** How conversations ended, where they transferred, and which tools
  failed.

With no conversations in the range, the page shows "No conversations for this agent in this range"
and **Changes in range**, which still lists any change to the agent.

### The Overview and a focused metric

The panel opens on the **Overview**: one chart with all six rates as lines on a percent axis
(Containment, Resolution, Negative sentiment, Transfer rate, Take-over, and Repeat in 7 days) and
daily conversation volume as bars on a second axis. Each tile's color is its line's color on the
chart. Avg handle time is in seconds, so it is not on the Overview.

Click a tile to focus the panel on that metric. The chart then shows that metric alone, a gray
line marks the organization's current value (for example "Org 62.0%"), and a sentence under the
title states the change from the prior period, for example "Transfer rate rose 3.1 pts, from 10.9%
to 14.0%." Rates change in points. Handle time changes in `m:ss`. Click the focused tile again, or
**Show all**, to return to the Overview. The metric is part of the page address, so a link opens
on the same metric.

**Chart** and **Table** switch between the chart and a table with one row per day. Days are UTC. A
day with no conversations leaves a gap in the chart.

### Changes in range

**Changes in range** lists what changed on the agent between the start and end of the range,
newest first, up to 200 entries. It sits under the chart, whose vertical lines mark the same
changes. Each entry shows the UTC date, what happened, and who did it when the name is available.

- **A version publish** reads "Version 2.1 published", followed by the configuration sections that
  differ from the previous published version, for example "Prompt, Greeting changed". The common
  sections are **Prompt**, **Greeting**, **Voice & models**, **Tools**, **Knowledge**, **Post-call
  analysis**, **Keypad input**, **Voice activity detection**, **Interruptions**, **Idle handling**,
  and **Data retention**. Any other section shows its setting name in words, such as **Turn
  detection** or **Data collection**. A publish that changed none shows "No configuration changes".
  The version's description appears under the entry.
- **A rollback** is a publish of a lower version than the one it replaced. It reads "Rolled back to
  version 2.0 from 2.1".
- **A coaching change** reads "Coaching applied:" and the recommendation's title. It is listed when
  you mark a [Coaching](/docs/agents/coaching) recommendation as applied.
- **An older version** reads "Version 1.3" and "Published (recorded before change history began)".
  Tandem rebuilds these from version history, so the date and description are known and the changed
  sections are not. A note below the list says so. Rollbacks before change history began show as
  ordinary publishes.

Versions created when versioning was first introduced for an agent are not listed, because
nothing changed on that day.

The chart draws one vertical line for each UTC day with a change. A publish is labeled with its
version, such as `v2.1`, a rollback with an arrow, such as `v2.0 ↩`, and a coaching change with
**Coaching**. Several changes on one day share a line. A change outside the chart's days draws no
line. If the history fails to load, the section shows "Couldn't load this agent's change history."

Click an entry, or a marker, to open its [change sheet](#what-each-click-opens).

### What moved it

**What moved it** breaks the change in the focused metric into the segments that moved it. On the
Overview it explains one metric, and the heading names it, for example "What moved containment".
The Overview picks the rate with the largest change in points. It picks **Avg handle time**
instead when handle time moved at least 5% and its change in percent is larger. It picks
**Resolution** only when both periods have at least 30 judged conversations.

The panel needs conversations in the range and an earlier period to compare with. Without an
earlier period it shows "No earlier period to compare with." When both periods have data but no
segment splits the change, for example a voice agent that took every call directly with no
version split, it shows the sentence and a note that no segment split the change.

It explains Containment, Resolution, Negative sentiment, Transfer rate, Take-over, and Avg handle
time. **Conversations** shows **Volume by segment** instead: how many conversations each segment
had, this period and the prior one, and each segment's share. **Repeat in 7 days** has a chart and
a sheet but no breakdown: the panel says "No breakdown is available for repeat contacts."

A **contribution** is the part of the change that one segment accounts for. Each segment is measured
against the agent's overall value, the average of its prior and current values. A segment
contributes when its own rate moved, or when its share of conversations moved while its rate sat
above or below the overall value. A growing segment with a lower rate than the overall value pulls
the metric down, so its contribution is negative. Two segments with the same rate as the agent
overall contribute nothing, however their shares shift. The contributions of all segments in a
dimension add up to the change.

A contribution shows where a change came from and says nothing about why. A segment can move the
number because something else changed at the same time, such as the callers or the campaign.

**Top contributors** lists the five largest contributions across all dimensions. Each row shows the
segment, its dimension, its movement from the prior to the current value, and its contribution.
**Other** is left out of this list. Below it, a tab for each dimension shows a table with the
columns **Segment**, **Conversations** (in the current period), **Prior → Current**,
**Contribution**, and **Driven by**.

**Driven by** says which part of the contribution is larger:

- **Performance**: the segment's own rate moved.
- **More of this traffic** or **Less of this traffic**: the segment's share of conversations moved.
  This is called mix.

The dimensions are:

| Dimension                | Segments                                                                                                  | Shown for                      |
| ------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------ |
| **Channel**              | Voice and Web chat                                                                                        | All metrics                    |
| **Started by**           | The workflow that invoked the agent, or **Called directly**                                               | All metrics                    |
| **End reason**           | How the conversation ended                                                                                | Resolution and Avg handle time |
| **Transfer destination** | Each destination, or **No destination name**                                                              | Transfer rate only             |
| **Version**              | The agent version that handled the conversation, such as `v2.1`, or **Unknown**                           | All metrics                    |
| Analysis fields          | The values of a field from the agent's [post-call analysis](/docs/agents/voice-agents/post-call-analysis) | All metrics                    |

End reason is left out for Containment and Transfer rate because the end reason is part of how
both are counted, so splitting by it would repeat the metric. For analysis fields, the page shows
at most 5, the ones present on the most conversations, and only fields with 12 or fewer distinct
values across both periods. A conversation with no value for a field falls in **Not set**. A
dimension is shown only when it has at least two segments besides **Other**. With one, that
segment would carry the whole change, which the sentence already states. **Unknown** in Version
covers conversations with no recorded version, such as those from before versions were recorded.

A segment with fewer than 10 conversations in both periods folds into **Other**, so a handful of
calls does not produce a large swing. A segment that has conversations in only one period has no
rate in the other, and its whole contribution counts as mix. When every conversation moved from one
version to the next, each of the two versions accounts for half of the change.

Transfer destination works differently. A row's **Prior → Current** is the destination's share of
all conversations, for example "8.0% → 12.5% of calls", and its contribution is the change in that
share. Conversations that were not transferred are not listed.

Click a contributor, or a segment row, to open its [segment sheet](#what-each-click-opens).

### Where conversations go

**Where conversations go** has up to three cards. A card is left out when it has no rows.

- **How conversations ended** counts conversations for each end reason, with each one's share.
  **Handed back to workflow** means the agent passed the caller to a workflow. **Switched to
  another agent** means the call went on with another AI agent, however it ended after that.
  **Disconnected (session ended)** and **Disconnected (connection lost)** mean the call ended
  before the agent could answer, and both count as faults. The other labels are **Completed**,
  **Caller hung up**, **Transferred**, **Fault**, **Taken over**, and **Turn limit reached**. An
  end reason Tandem does not list shows as stored, and one with no value shows as **Unknown**.
  When the agent switched any conversation to another AI agent in the range, a line under the
  heading says so, for example "3 conversations were handed to another agent." A conversation
  counts once however many times the agent switched in it.
- **Transfers** shows attempts, connected transfers, and success rate for each destination, when
  the agent attempted any. A transfer with no destination name shows as **Unlabeled**. Switches to
  another AI agent are not listed.
- **Tool errors** lists the tools with at least one failed call, with calls, errors, and error
  rate. It lists up to 10 tools, including tools run by an agent this one switched the caller to.
  What counts as a failed call is under [Tool errors](#tool-errors).

Click a row in any of the three to open its sheet.

## Drill-downs

Each number on the agent's page opens a sheet beside the page with the conversations behind it.
The page stays where it is, and the sheet's address is in the browser's address bar, so you can
paste the link into a ticket and it opens the same sheet. From 1800 pixels of window width the
sheet sits beside the page. On a narrower window it covers part of the page. Close it with the
close button or the Escape key. The browser's Back button also closes it.

### What each click opens

| You click                                      | The sheet is for                                   |
| ---------------------------------------------- | -------------------------------------------------- |
| The **View N ...** button on a focused metric  | The conversations that make up that metric's count |
| A day on the chart, or a row in the table view | That UTC day                                       |
| A marker on the chart, or an entry in Changes  | A version publish or an applied coaching change    |
| A contributor or a segment row                 | One segment of the change                          |
| A row in **How conversations ended**           | One end reason                                     |
| A row in **Transfers**                         | One transfer destination                           |
| A row in **Tool errors**                       | One tool                                           |

The button on the focused metric reads, for example, "View 14 Uncontained". The count is the
numerator of that metric's rate: **Uncontained** is the conversations not contained, **Unresolved**
and **Negative** are the judged ones, and **Timed Conversations** is the sample behind the average
handle time. A marker shared by several changes opens the earliest of that day. The other changes
that day are listed on the day's sheet.

Every sheet has the same parts. The headline gives an exact count, with what it is a share of
underneath. A breakdown comes next, then the matching conversations, with **Load More** for
further pages. Each conversation shows when it ran, its channel, and its duration, with a line of
evidence for what the sheet is about. The footer has **Open these conversations in Call Logs**,
which opens [Call Logs](/docs/workflows/insights-and-reporting#call-logs) with the same
conversations. A sheet that carries no conversation filter reads "Open all this agent's calls in
Call Logs".

### What each sheet shows

- **Metric sheet.** The headline is the count behind the tile, for example "14 not contained" with
  "of 60 conversations (76.7% contained)". Containment adds **Why they weren't contained**: the
  transferred, taken over, and faulted conversations. Transfer rate adds a **By destination**
  table. Conversations adds **By channel** and **By started by** tables. Resolution and Negative
  sentiment carry the AI mark and list the conversations judged unresolved or negative. Take-over
  lists the conversations a supervisor took over. Repeat lists conversations that were followed by
  another, with a link to each follow-up conversation. Avg handle time lists every conversation in
  the range, longest first, and says so, because the average rests on the timed ones only.
- **Day sheet.** The conversation count for the day and a table of that day's values against the
  range's values, for the focused metric or for each rate on the Overview. It lists any change
  that landed that day, and the day's conversations.
- **Change sheet.** For a version publish: when it was published, by whom, the version it replaced,
  its release note, what changed in the configuration, a before and after, and the conversations
  that version handled. For a coaching change: the recommendation, its reason, the change it made,
  the conversations it cites as evidence with a quote from each, and a before and after. **Open
  Version in Agent** and **Open Coaching** go to the version and to Coaching. A change from outside
  the selected range says so, and the sheet asks you to widen the range before it compares
  anything.
- **Segment sheet.** The segment's prior and current value, its contribution, and what it is
  **Driven by**, then its conversations. A segment made of **Other** lists no conversations when it
  folds too many small segments for one request, and says so. Open one of the segments from the
  table instead. Conversations with no recorded channel cannot be listed by channel.
- **End reason sheet.** The count and share of that end reason, and its conversations. For **Caller
  hung up**, each row carries the AI resolution verdict, because a hang-up counts as contained and
  the verdict says whether the caller was helped.
- **Destination sheet.** Attempts, connected, and failed transfers. A **Failed only** switch limits
  the list, and each row is marked **Connected** or **Failed**. Tandem does not record why a
  transfer failed, so the sheet shows no cause.
- **Tool sheet.** See [Tool errors](#tool-errors).

### Before and after a change

A change sheet shows the focused metric, or each rate on the Overview, for the seven days before
the change and the seven days after it. Each side shows its `n`, the number of conversations the
value rests on. If the week after has not finished, the sheet says its numbers are partial.

The comparison makes no causal claim. When at least one row differs between the two sides, the
sheet says "This change coincided with the difference. Other events in the same two weeks could
explain it." A different mix of callers or a campaign in the same fortnight moves the numbers too.
When every row is equal, it says "No difference in the seven days on either side." Read a small
`n` as weak evidence.

### Opening a conversation at the moment that explains it

Click a conversation in a sheet to open it. The conversation page opens at the moment the sheet is
about. A tool, transfer, or agent-switch row opens at the call with that tool name and opens it. On
a chat the page picks the call nearest in time to the one the sheet listed. On a voice call, whose
turns have no time, it picks the first failed call of that name, or the first call of that name when
none failed. A take-over row opens at the line that marks the take-over, "X took over the call", and
highlights it. A row with no such moment opens at the top. A resolution or sentiment row opens the
**Analysis** tab instead. The **Back to Agent Performance** button returns to the same sheet.

The moment needs the conversation's transcript, which Tandem keeps for 90 days and only when the
agent stores transcripts. See [Transcript
retention](/docs/agents/voice-agents/safety#transcript-retention). Without one, the page opens at
the top with a note: "This conversation's transcript isn't available, so the page opens at the
top." If the moment cannot be matched, the note reads "Couldn't find that moment in the transcript,
so the page opens at the top." Neither is an error.

## Tool errors

The **Tool errors** card and the tool sheet count failed tool calls. A failure is one of these:

- A **timeout**, including an HTTP 408 or 504 answer.
- A **server error**, an HTTP 5xx answer.
- An **exception**, a tool that threw an error.
- **Invalid parameters**, a call the tool refused because its arguments failed validation.

An integration's 4xx answer returned as data, for example a 404 "not found", is recorded but is
not counted as a failure. The agent can handle a "no" from the other system, so it does not raise
the error rate. A tool that reports its own error is a failure whatever the HTTP status, so a 4xx
exchange with an error returned counts. A tool whose only errors are 4xx answers returned as data
is not listed in the card.

Each failed call, and each 4xx answer, records a kind, and the tool sheet's **By error kind** table groups calls by it:

| Kind                                      | Meaning                                                | Counted as a failure                  |
| ----------------------------------------- | ------------------------------------------------------ | ------------------------------------- |
| **Timed out** (`timeout`)                 | The call ran out of time, or the answer was 408 or 504 | Yes                                   |
| **Returned 4xx** (`http_4xx`)             | The integration answered with a 4xx status             | No, unless the tool returned an error |
| **Server error (5xx)** (`http_5xx`)       | The integration answered with a 5xx status             | Yes                                   |
| **Invalid parameters** (`invalid_params`) | The tool refused the call's arguments                  | Yes                                   |
| **Threw an exception** (`exception`)      | The tool threw an error                                | Yes                                   |
| **Unknown** (`unknown`)                   | A failed call with no recorded kind                    | Yes                                   |

A 4xx call that returned an error has its own row, **Returned 4xx with an error**, and counts as
a failure. The row **Returned 4xx (not counted as failure)** holds only the 4xx answers that did
not fail.

Conversations recorded before error kinds existed read as **Unknown**: their failed calls have no
kind.

The tool sheet opens with "N of M calls failed" and the number of conversations they hit. It then
has the by-kind table, the **Failing conversations** list, **Failures by day** as small bars with a
table behind **Show as table**, and a **By version** table. The **Include 4xx answers** switch
appears when the tool had any 4xx answers that did not fail. It adds a second list headed "Conversations where the
tool returned 4xx". A conversation whose 4xx call failed appears in both lists. **Open Tool in Agent** opens the agent's editor, since there is
no link to a single tool's settings. Each failing conversation opens at the failed call. The sheet itself shows no error
message text, since only the kind is recorded. Error messages stay in the conversation's
transcript.

## Troubleshooting

- **Resolution is —.** No conversation in the range has an outcome of `success` or `failure`.
  Turn on **Call outcome** in the agent's post-call analysis.
- **Negative sentiment is —.** No conversation in the range has a sentiment. Turn on **Caller
  sentiment** in the agent's post-call analysis.
- **Repeat in 7 days is —.** No conversation in the range is both old enough and tied to a contact
  or phone number.
- **A change is missing from Changes in range.** Only publishes and applied coaching
  changes are listed. Edits to a draft that were never published do not appear.
- **A tool I expect is missing from Tool errors.** The card lists tools with at least one failed
  call. A tool that only returned 4xx answers has none.
- **A conversation opens at the top.** Its transcript was not stored, is older than 90 days, or
  does not contain the moment. The note at the top of the page says which.
- **An agent is missing.** The list shows agents that handled a finished conversation in the
  selected range and channels, in this period or the prior one.
- **The page shows an error after you open a link.** The date picker offers presets of up to 90
  days, so a longer range comes only from a URL that was edited or pasted. Pick a preset from the
  date range menu.

## Next steps

- [Post-call analysis](/docs/agents/voice-agents/post-call-analysis)
- [Coaching](/docs/agents/coaching)
- [Insights & Reporting](/docs/workflows/insights-and-reporting)
