Outcomes
The ordered rules that give each of an agent's sessions one outcome in the console. How to read, replace and reset them, and how the defaults are worked out from the agent's deployment.
Every session in the console's Sessions list shows one outcome, such as Booked, Transferred or Answered. Each agent has its own ordered list of outcomes. Each rule is a name and one condition. The last rule whose condition holds names the session, and the fallback names a session that no rule matches. These routes read and change that list. They change only what the console shows: nothing here changes what the agent does.
| Route | What it does | Success |
|---|---|---|
GET /agents/{slug}/outcomes | The list in force: the one saved for the agent, or its defaults | 200 { "outcomes": Outcomes, "source" } |
PUT /agents/{slug}/outcomes | Replace the whole list | 200 { "outcomes": Outcomes, "source": "stored" } |
DELETE /agents/{slug}/outcomes | Remove the saved list, so the agent is on its defaults again | 200 { "outcomes": Outcomes, "source": "defaults" } |
All three need the
manage-agents permission, count
against the agent's rate budget, and answer
404 NOT_FOUND for an agent that is not the organization's or is
retired. The MCP
server's outcome tools call the same handlers.
The Outcomes object
{
"fallback": "Answered",
"rules": [
{ "name": "Booked", "when": { "tool": "book_appointment" } },
{ "name": "Message taken", "when": { "tool": "take_message" } },
{ "name": "Transferred", "when": { "event": "transferred" } }
]
}| Field | Type | Description |
|---|---|---|
fallback | string | Names a session no rule matches, the outcome every conversation starts as. 1 to 40 characters once trimmed. Answered until you change it |
rules | array | At most 30 rules, in order. The last one whose condition holds names the session |
rules[].name | string | The outcome as the console shows it. 1 to 40 characters once trimmed, and no two the same, ignoring case |
rules[].when | object | Exactly one condition |
Every answer pairs it with source:
source | Meaning |
|---|---|
stored | Someone saved this list, here or in the console. A stored list never changes by itself |
defaults | Nobody has. The list is worked out when it is read, so it follows the agent's deployment and its tools |
Conditions
| Condition | Holds when |
|---|---|
{ "tool": "<tool id>" } | The session has a successful call of that tool. The id is 1 to 64 letters, digits, _ and - |
{ "event": "transferred" } | The transfer tool handed the call to a number |
{ "event": "voicemail" } | The call reached voicemail |
{ "event": "hung_up" } | The caller hung up before the agent ended the call |
{ "event": "agent_ended" } | The agent ended the call |
A tool rule holds on any channel, and a successful call is one that returned rather than threw. The tool need not be one the deployment has now: a rule on a tool the agent does not run is stored, and never matches.
The four events are about phone calls only. They come from the session.ended
event a deployment sends when a call ends, from
kit 0.35.0. A text,
chat or email session matches only tool rules, or takes the fallback.
For a transfer, a voicemail or the agent ending a call, prefer the event to the
tool. The kit refuses or undoes each of those in some calls by answering the
tool call rather than failing it: a transfer with no number to go to, a
voicemail a person picked up during, an end_call its voicemail guard refused,
or a hangup or transfer the caller spoke over. Each still counts as a successful
call of its tool, so a tool rule would name those calls wrongly, and the event
would not.
The last rule that holds wins
The list reads as a conversation going deeper. Every conversation, a call, a text thread, a chat or an email, starts as the fallback, and each rule below is further along, so the lowest rule whose condition holds names the session.
With the list above, a call where the agent booked an appointment and then transferred the caller is Transferred, because Transferred comes after Booked. A call that only booked is Booked, and one that did neither is Answered.
The console decides each session's outcome when it reads the session, from the agent's list as it is then. So a change relabels the agent's past sessions too, as far back as their events go. Two kinds of session show no outcome at all: one from a deployment on a kit before 0.35.0, which does not report when a session starts, and a call whose end has not arrived yet.
The defaults
An agent is on its defaults until a list is saved for it. They are worked out each time they are read, never written, and come from the agent's deployment alone:
- The fallback,
Answered. - Then one rule per entry in
deployment.outcomes, the ordered list the deployment's describe document announces from kit 0.35.0, in its order, with itswhenas given. - Each entry is kept only when the agent runs the tool behind it on at least
one channel: its
toolId, or with none, the tool itswhennames.
On a kit 0.35.0 deployment whose project maps book_appointment to booked,
and whose lifecycle names its end-call, voicemail and transfer tools, an agent
that runs all five tools has these defaults:
{
"outcomes": {
"fallback": "Answered",
"rules": [
{ "name": "Call ended", "when": { "event": "agent_ended" } },
{ "name": "Booked", "when": { "tool": "book_appointment" } },
{ "name": "Escalated", "when": { "tool": "escalate_to_human" } },
{ "name": "Voicemail", "when": { "event": "voicemail" } },
{ "name": "Transferred", "when": { "event": "transferred" } }
]
},
"source": "defaults"
}| Rule | Detail |
|---|---|
| Names | From the announced outcome: underscores become spaces and the first letter is capitalised, so message_taken becomes "Message taken" |
| Each name once | An outcome whose name is already in the list, ignoring case, is left out, so the first one announced keeps it |
| At most 30 | The defaults never pass the limit, so they can always be saved as they are |
| Which tools the agent runs | Its tool choices, by the kit's rule. On opt-in tools, each channel's locked tools and its enabledTools. Otherwise, each channel's tools minus its disabledTools, with locked tools always on |
| Where the description comes from | The copy the console stored when it last read the deployment, never a live read. The console works its defaults out from the same copy, so both show an agent the same list. After a deploy, the defaults follow the new build once the console has read its description, which refreshing the deployment in the console does |
| No description | An agent that names no deployment, or whose deployment has no description stored, has the fallback alone. So does one served by a kit before 0.35.0, which announces no outcomes |
Nothing is built in. Every rule in the defaults is one the deployment announced. A locked tool always runs, so the kit's own escalation tool gives Escalated to every agent of a deployment that announces it.
Read the outcomes
GET /agents/{slug}/outcomescurl -H "Authorization: Bearer $SUBCORE_API_KEY" \
"https://api.subcore.ai/agents/clinic-one/outcomes"The answer is { "outcomes": Outcomes, "source" }: the saved list with
source: "stored", or the defaults with source: "defaults".
Replace the outcomes
PUT /agents/{slug}/outcomesThe body is an Outcomes object, { "fallback", "rules" }, and nothing else.
curl -X PUT -H "Authorization: Bearer $SUBCORE_API_KEY" \
-H "Content-Type: application/json" \
"https://api.subcore.ai/agents/clinic-one/outcomes" \
-d '{
"fallback": "Answered",
"rules": [
{ "name": "Hung up", "when": { "event": "hung_up" } },
{ "name": "Booked", "when": { "tool": "book_appointment" } },
{ "name": "Message taken", "when": { "tool": "take_message" } },
{ "name": "Voicemail", "when": { "event": "voicemail" } },
{ "name": "Transferred", "when": { "event": "transferred" } }
]
}'The rules:
- It replaces the whole list. A rule you leave out is removed, so start from the list in force and send it back with your change.
- Every name is stored trimmed, and the answer is the list as stored, with
source: "stored". - A stored list no longer follows the deployment, even one equal to the
defaults.
DELETEputs the agent back on them. - A list is refused whole, by its first problem, and nothing is written. The
answer is
400 INVALID_OUTCOMES, and its message names the field, such as"rules[2].name" is "booked", which "rules[0]" already names: names are unique, ignoring case. A list is refused when:fallbackor a name is not 1 to 40 characters once trimmed, or holds a NUL character or an unpaired surrogate. Characters are counted as the database counts them, so an emoji is one;rulesis not a list, or holds more than 30 rules;- a rule is not exactly
{ "name", "when" }; - two names are the same, ignoring case and the space around them;
- a
whenis not exactly one condition.
- A body that is not JSON is
400 INVALID_JSON, one that is not an object is400 INVALID_BODY, one with a field other thanfallbackandrulesis400 UNKNOWN_FIELD, and one over 32 KiB is413 PAYLOAD_TOO_LARGE.
PUT /agents/{slug} does not
take outcomes. Sent there, it is 400 UNKNOWN_FIELD.
Saves here are last-write-wins. The console's editor checks that the list has
not changed since it loaded it, and this route does not, so a PUT replaces
whatever is stored, an edit made in the console a moment earlier included.
Go back to the defaults
DELETE /agents/{slug}/outcomescurl -X DELETE -H "Authorization: Bearer $SUBCORE_API_KEY" \
"https://api.subcore.ai/agents/clinic-one/outcomes"Removes the saved list, so the agent is on its defaults again,
and answers 200 with those defaults and source: "defaults". An agent
already on them gets the same answer, so a repeated call is safe.
In the console
The console edits the same list, under Configuration → Categories →
Outcomes, for org admins. Its editor offers only tool conditions, and only
for the tools the agent's deployment describes. A rule on an event, stored here
or arriving through the defaults, is kept and shown there as a sentence, such as
"When the caller hangs up". Reset to defaults in its header does what
DELETE does.
Prompt files
What goes inside a stored file. Transcluding another file, binding a variable, and the mistakes the deployment refuses.
Agent config
The read a running deployment makes on every call. Its payload, its refusals, the three settings that connect a deployment to it, what it reports for each session's direction and outcome, how it offers each agent relay or realtime voice, and how many quiet minutes end each agent's text conversations.