Repository navigation
docs(agents): agent_join_timeout sessions are failed, not completed #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,9 +4,9 @@ | |||||
| icon: "clock-rotate-left" | ||||||
| --- | ||||||
|
|
||||||
| Every production session your agents handle (from the API, the console, [public agents](/agents/deploy/public-agents), or [phone calls](/agents/telephony/inbound-calls)) is queryable over REST: a lightweight list for browsing, a merged timeline of messages and tool activity per session, and per-speaker recordings when the agent [records audio](#what-gets-stored). You can query a session while the call is still in progress. | ||||||
|
|
||||||
| This is a server-side API: authenticate with your API key. The client SDKs deliberately expose no history interface; fetch history from your backend and pass it to your frontend as needed. | ||||||
|
|
||||||
| ## What gets stored | ||||||
|
|
||||||
|
|
@@ -67,8 +67,8 @@ | |||||
| "metadata": { "crm_ticket": "T-4821" } | ||||||
| } | ||||||
| ], | ||||||
| "has_more": true, | ||||||
| "next_cursor": "…" | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
|
|
@@ -76,7 +76,7 @@ | |||||
|
|
||||||
| ### Status and end reason | ||||||
|
|
||||||
| `status` says whether a conversation happened: `completed` means a participant connected and the call is over (it is billed even if the runtime ended it), `failed` means nobody ever connected, `active` means it is in progress, `pending` means the session was created and no one has connected yet, and `unknown` means the platform temporarily lost track of a live call. | ||||||
| `status` says whether a conversation happened: `completed` means the caller and an agent were both on the call and it is over (it is billed even if the runtime ended it), `failed` means no conversation took place (nobody connected, or the caller connected and no agent ever joined), `active` means it is in progress, `pending` means the session was created and no one has connected yet, and `unknown` means the platform temporarily lost track of a live call. On a `failed` session where the caller did connect, `started_at`, `ended_at` and `duration_seconds` describe how long they waited; nothing is billed. | ||||||
|
|
||||||
| `end_reason` says why it ended. It is `null` while a session is live, for a short moment after it ends, and on sessions that predate the field. | ||||||
|
|
||||||
|
|
@@ -87,8 +87,8 @@ | |||||
| | `escalated` | `completed` | The call was transferred to a human. See [Transfer items](#transfer-items) | | ||||||
| | `conversation_timeout` | `completed` | The call reached its maximum duration | | ||||||
| | `heartbeat_timeout` | `completed` | The agent runtime stopped responding mid-call | | ||||||
| | `agent_join_timeout` | `completed` | The caller connected but no agent joined the call | | ||||||
| | `internal_error` | `completed` | The agent runtime hit an internal error | | ||||||
| | `agent_join_timeout` | `failed` | The caller connected but no agent joined the call | | ||||||
| | `internal_error` | `completed` | The agent runtime hit an internal error (`failed` when it failed before the conversation started) | | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔎 Supported by static analysis
List both statuses for The prose allows Suggested fix
Committable suggestion
|
||||||
| | `llm_endpoint_failure` | `completed` | A [custom LLM](/agents/build/custom-llm) failed repeatedly and the agent hung up | | ||||||
| | `room_join_timeout` | `failed` | The caller never connected before the join deadline | | ||||||
| | `cancelled` | `failed` | The session was ended before anyone connected | | ||||||
|
|
@@ -196,7 +196,7 @@ | |||||
|
|
||||||
| Details worth knowing: | ||||||
|
|
||||||
| - **Role vocabulary**: the history API's `assistant` is the same speaker the [SDK's live events](/agents/deploy/web-sdk) call `agent`. | ||||||
| - **Order**: items are sorted by `created_at` ascending; a `tool_result`'s timestamp is its completion time, so long-running tools appear where they actually finished, with messages in between. Items sharing a timestamp order `message`, then `tool_call`, then `tool_result`. | ||||||
| - **`tool_source`**: where the tool ran, one of `client`, `webhook`, `builtin` (platform tools such as call transfer and hang-up), `mcp` (tools from a connected MCP server), `background` (work the agent delegated to a background task), or `unknown` (calls recorded before source attribution). Treat it as an open set. See [Tools](/agents/build/tools). | ||||||
| - **Payloads**: `input` and `output` are JSON strings, symmetric with the live SDK events, so one parser covers both. `output` and `error` are stored up to 256 KB; beyond that the text is cut and `output_truncated` is `true`. | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -105,16 +105,16 @@ | |
| "branch_id": "b7d4…", | ||
| "source": "phone", | ||
| "status": "completed", | ||
| "end_reason": "user_hangup", | ||
| "conversation_started_at": "2026-07-23T12:01:12Z", | ||
| "conversation_ended_at": "2026-07-23T12:04:16Z", | ||
| "duration_seconds": 184, | ||
| "end_user_id": "customer-42", | ||
| "metadata": { "order_ref": "SO-1042" }, | ||
| "agent_name": "Support agent", | ||
| "config_hash": "sha256:9c41…" | ||
| }, | ||
| "ended_reason": "user_hangup" | ||
| } | ||
| ``` | ||
|
|
||
|
|
@@ -185,13 +185,13 @@ | |
|
|
||
| </CodeGroup> | ||
|
|
||
| `ended_reason` is the same value as `session.end_reason` and uses the [sessions API vocabulary](/agents/monitor/conversation-history#status-and-end-reason): `user_hangup`, `agent_hangup`, `escalated`, `conversation_timeout`, `heartbeat_timeout`, `agent_join_timeout`, `internal_error`, or `llm_endpoint_failure`. `call.ended` is only sent for sessions in which a participant connected, so the never-connected reasons (`room_join_timeout`, `cancelled`, `dial_failed`) do not appear here; query the sessions API with `status=failed` for those. In the rare case the reason is not settled yet at delivery time, both fields are `null`. New values may be added; treat unrecognized values as informational rather than rejecting the event. | ||
| `ended_reason` is the same value as `session.end_reason` and uses the [sessions API vocabulary](/agents/monitor/conversation-history#status-and-end-reason): `user_hangup`, `agent_hangup`, `escalated`, `conversation_timeout`, `heartbeat_timeout`, `agent_join_timeout`, `internal_error`, or `llm_endpoint_failure`. `call.ended` is only sent for sessions in which a participant connected, so the never-connected reasons (`room_join_timeout`, `cancelled`, `dial_failed`) do not appear here; query the sessions API with `status=failed` for those. A caller who connected and waited for an agent that never joined does produce `call.ended`, with `session.status` set to `failed` and `ended_reason` set to `agent_join_timeout`. In the rare case the reason is not settled yet at delivery time, both fields are `null`. New values may be added; treat unrecognized values as informational rather than rejecting the event. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use second-person voice in the new webhook text. The new sentence starts with As per coding guidelines: MDX documentation must use second-person voice (
|
||
|
|
||
| `dial_status` is `answered`, `busy`, `no_answer`, or `failed`. `answered_by` is deprecated: answering-machine detection has been removed, so it is `unknown` once the call is answered and `null` before (older events may carry `human` or `voicemail`). Both facts also appear on the session, alongside `direction` (`inbound` or `outbound`) and `batch_call_id` (reserved, always `null` today); these session fields are present in every webhook payload, with the dial fields `null` on inbound sessions. The `phone_call.dial_finished` snapshot is taken when the dial resolves, so on an answered call `conversation_ended_at` and `duration_seconds` are still `null`. The final numbers arrive with `call.ended`. | ||
|
|
||
| `caller_number`, `dialed_number` and `sip_call_id` carry the same values as on the [sessions API](/agents/monitor/conversation-history): the E.164 numbers of the call (person-centric on outbound calls, so `caller_number` is the party you dialed) and the SIP `Call-ID` of the call's INVITE. All three are empty strings on web sessions, and `sip_call_id` is also empty on an unanswered outbound dial. How to use it against your carrier's records: [Match calls with your SIP records](/agents/telephony/byo-sip#match-calls-with-your-sip-records). | ||
|
|
||
| `call_sid` is the carrier's id of the call. Today that is Twilio's CallSid on an inbound call through a Twilio number. It is the same value the [inbound call webhook](#inbound-call-webhook) request carries, so you can join that request to the session. It is an empty string on web sessions, outbound calls, and calls through your own SIP trunk. | ||
|
Check warning on line 194 in agents/monitor/webhooks.mdx
|
||
|
|
||
| `end_user_id` and `metadata` are echoed exactly as you set them when creating the session. Use them to correlate the event with records in your own system. `branch_id` is an internal configuration-lineage identifier and is safe to ignore. What lands in `summary`, `data`, and `criteria_results` is defined by your [analysis configuration](/agents/monitor/post-call-analysis). | ||
|
|
||
|
|
@@ -283,14 +283,14 @@ | |
| | Fan-out | Every configured endpoint receives every event | | ||
| | Guarantee | At-least-once, per endpoint | | ||
| | Timeout | 10 seconds per attempt | | ||
| | Retries | 2 after the first attempt (3 attempts total) per endpoint, with backoff of 1s / 5s, then that delivery is dropped | | ||
| | Ordering | `phone_call.dial_finished` (outbound only) before `call.ended` before `call.analyzed` for the same session | | ||
|
|
||
| Endpoints are delivered in parallel and independently: each gets its own attempts, its own retry budget, and its own signature keyed with its own secret. An endpoint that is down and exhausts all three attempts has no effect on the others. | ||
|
|
||
| Respond with a `2xx` status within the timeout; a `500` response or a timed-out request counts as a failed attempt. Acknowledge first and process asynchronously. Slow handlers burn their own retry budget. | ||
|
|
||
| **Idempotency.** At-least-once delivery means the same event can arrive more than once. Retries of one delivery carry an identical body, so dedupe `call.ended` and `phone_call.dial_finished` on (`event`, `session.id`), and `call.analyzed` on (`event`, `session.id`, `analysis.finished_at`). The extra element matters because a skipped or failed analysis can be re-run from the console: the recovered result arrives as a fresh `call.analyzed` with a newer `finished_at`, superseding the earlier one. | ||
|
Check warning on line 293 in agents/monitor/webhooks.mdx
|
||
|
|
||
| <Note> | ||
| [Preview calls](/agents/test/preview-calls) made from the Builder never | ||
|
|
@@ -354,13 +354,13 @@ | |
| } | ||
| ``` | ||
|
|
||
| `caller_number` and `dialed_number` are E.164. `caller_number` is an empty string when the carrier withholds the caller's number, and `call_sid` is empty when the call did not come through a Twilio number. | ||
|
|
||
| To match this request with the call, keep `call_sid`. Once the call is connected, `GET /v1/agent/sessions?call_sid=…` returns its session, and post-call webhooks carry the same value. This works only when `call_sid` is not empty, that is for calls through a Twilio number. During the call, webhook tools can send the session id with `{{system.session_id}}`. New fields can be added to this request, see [Forward compatibility](#forward-compatibility). | ||
|
|
||
| ### Response | ||
|
|
||
| Respond with a `2xx` status and a JSON object whose `dynamic_variables` follow the same rules as on session creation: names match `[A-Za-z][A-Za-z0-9_]*`, values are strings (up to 100,000 characters), numbers, or booleans, and at most 500 entries totaling 1 MiB are read. | ||
|
|
||
| ```json Response | ||
| { | ||
|
|
@@ -392,7 +392,7 @@ | |
|
|
||
| ## Auto-ticket unresolved calls | ||
|
|
||
| `call.analyzed` closes the loop on conversations the agent couldn't: judge every call with a success criterion, and open a ticket in your helpdesk whenever the verdict isn't `success`. | ||
|
|
||
| First give the agent's [analysis configuration](/agents/monitor/post-call-analysis) a criterion that captures resolution: | ||
|
|
||
|
|
@@ -513,11 +513,11 @@ | |
|
|
||
| </CodeGroup> | ||
|
|
||
| `verifyWebhook` is the function from [Verify the signature](#verify-the-signature); `openTicket` stands in for your helpdesk's API. Escalating on anything but `success` includes `unknown` verdicts: the model couldn't judge the call, which usually deserves human eyes too. Tighten the check to `failure` only if unknowns prove noisy. | ||
|
|
||
| Edges worth handling: | ||
|
|
||
| - Calls with nothing to analyze arrive with `analysis.status: "skipped"`. The handler above tickets them as unjudged, so every call reaches the helpdesk without also watching `call.ended`. Drop that branch if silent calls don't belong in your queue. | ||
|
Check warning on line 520 in agents/monitor/webhooks.mdx
|
||
| - Payloads carry no transcript. To include one in the ticket, fetch `GET /v1/agent/sessions/{session_id}` from your handler. See [conversation history](/agents/monitor/conversation-history). | ||
| - Set `end_user_id` and `metadata` when creating sessions so tickets attach to the right customer record without a lookup. | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use second-person voice in the new status text.
The new prose uses third-person forms such as
completed means,The caller connected, andThe agent runtime hit. Rewrite the explanatory text withyouto follow the MDX writing standard. Keep API field names and status values unchanged.As per coding guidelines: MDX documentation must use second-person voice (
you).Also applies to: 90-91
Source: Coding guidelines