> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xpander.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversations

> Send messages to an agent in a conversation, stream its replies, queue or steer follow-ups, stop a turn, and read the thread back.

The **conversation API** is how you talk with an agent over several messages. It lives under `/v1/agents/{agent_id}/conversations/...` and takes your normal API key (`x-api-key`) or an OAuth2 JWT. Xpander Chat uses these same routes.

Use [Invoke Agent](/api-reference/v1/agents/invoke-sync) for a single one-shot request. Use a conversation when you want a thread: send a message, get the reply, send the next message with the same `conversation_id`, add follow-ups while a reply is being written, steer or stop the current turn, edit an earlier message, or answer a question the agent asked.

## How a conversation works

Each message you send is one **turn**: the agent reads your message, works, and replies. The first message creates the conversation and returns its `conversation_id`; every later message carries that id and continues the same thread. The agent keeps the full history, so you never resend earlier messages.

Within a conversation you can:

* **Send a message and get the reply** with [Run Conversation Turn](/api-reference/v1/agents/conversations/run-turn) (waits for the reply) or its [streaming variant](/api-reference/v1/agents/conversations/run-turn-stream) (events as they happen). Pass `id` = your `conversation_id` to continue; omit it to start a new conversation.
* **Add a follow-up while a turn runs** with [Send Conversation Message](/api-reference/v1/agents/conversations/send-message): the message waits in the conversation's **queue** for the next turn, or **steers** the turn that is already running.
* **Check the state** with [Run State](/api-reference/v1/agents/conversations/run-state): is a turn running, can it take a steer, how many follow-ups are queued.
* **Run queued follow-ups** with [Drain Queue](/api-reference/v1/agents/conversations/drain-stream) when the conversation is idle.
* **Stop** the running turn with [Stop](/api-reference/v1/agents/conversations/stop); queued follow-ups stay unless you clear them.
* **Edit** an earlier message with [Edit Message](/api-reference/v1/agents/conversations/edit-message): everything after it is dropped and the rewritten message runs as a new turn.
* **Answer** a question card with [Submit Answers](/api-reference/v1/agents/conversations/submit-answers), or approve or decline a confirmation card with [Confirm](/api-reference/v1/agents/conversations/confirm).

## The conversation record

A conversation has one record. [Get Conversation](/api-reference/v1/agents/conversations/get-conversation) returns it together with the full thread, token usage and the record of every sub-task the agent created. The same record is also reachable with [Get Task](/api-reference/v1/tasks/get-task) on the `conversation_id`, because the record uses the `Task` schema: `id` on that object is the `conversation_id`.

| Field               | Meaning                                                                                                                    |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `id`                | the `conversation_id`; the record's `conversation_id` field holds the same value                                           |
| `conversation_kind` | `direct` for every conversation you create; `gateway` on conversations recorded before September 2026, which are read-only |
| `is_gateway`        | `true` on every conversation record; kept for compatibility, read `conversation_kind` instead                              |
| `title`             | set from the first message and refined once the first reply lands                                                          |
| `status`, `result`  | while a turn runs, its status; once it finishes, the last reply                                                            |
| `sub_executions`    | ids of sub-tasks the agent created on other agents while working                                                           |
| `parent_execution`  | null on a conversation; on a sub-task, the conversation that created it                                                    |

To continue a conversation recorded before September 2026, start a new conversation with the same agent.

## Sub-tasks

A turn runs on the agent you addressed. When that agent hands part of the work to another agent, it creates a **sub-task**: a separate record with its own thread, listed under the conversation in the Task Manager and on [Get Conversation](/api-reference/v1/agents/conversations/get-conversation). Whether the agent waits for the sub-task or lets it run on is decided when the agent creates it. A sub-task that outlives the turn reports into the conversation when it finishes (a `sub_task_finished` event on a live stream). A sub-task never creates background sub-tasks of its own.

The `sub_tasks` query parameter on [Run Conversation Turn](/api-reference/v1/agents/conversations/run-turn) and its [streaming variant](/api-reference/v1/agents/conversations/run-turn-stream) sets how the agent's sub-tasks run inside the turn, not whether your call waits: `sync` runs every sub-task inline, `async` runs every sub-task in the background, `auto` lets the agent choose per sub-task. The setting sticks to the conversation until you change it. Turns started from Slack, Teams, WhatsApp or email always use `sync`, because a result landing after the channel reply has no way back to the channel.

## Sending messages while a turn runs (queue and steer)

A conversation runs **one turn at a time**. A follow-up sent while a turn is running never runs in parallel: it either waits in the **queue** for the next turn, or it **steers** the turn that is already running.

[Send Conversation Message](/api-reference/v1/agents/conversations/send-message) takes a `mode`:

* `mode=auto` (default): queue the message if a turn is running. If the conversation is idle, the response is `started`, which means you run the turn yourself with [Run Conversation Turn](/api-reference/v1/agents/conversations/run-turn-stream) passing `id` = `conversation_id`.
* `mode=queue`: always queue, even when idle.
* `mode=steer`: hand the message to the running turn so the agent takes it into account at its next skill call, without waiting for the turn to finish. [Run State](/api-reference/v1/agents/conversations/run-state) tells you whether a steer is possible right now (`is_steerable`); the response echoes `steer_target`.
* `mode=interrupt`: stop the running turn and run this message as the next turn.

The response is `{ action, message_id, queue_depth, steer_target }`: `queued` with the new depth, `steered`, `interrupted`, or `started` when idle.

```mermaid theme={"dark"}
sequenceDiagram
  participant C as Client
  participant A as Conversation
  C->>A: run turn (id = conversation_id), stream open
  C->>A: POST /messages (follow-up while the turn runs)
  A-->>C: 200 { action: "queued", queue_depth: 1 }
  Note over A: current turn finishes
  A-->>C: gateway_queue_updated (drain), then the next turn's events
  Note over A: queue drains in order until empty
```

How queued messages run:

* If a **stream is open** on the running turn, the queue drains on that stream: when the turn finishes, the next message runs as the following turn. Each drain is announced with a `gateway_queue_updated` event (`last_action: "drain"`).
* If **no stream is open** (you queued over plain REST and disconnected, or queued while idle), call [Drain Queue](/api-reference/v1/agents/conversations/drain-stream) to run the pending messages and stream them. A queue left without a stream is also picked up on its own within a few minutes.

Manage the queue with [Run State](/api-reference/v1/agents/conversations/run-state) (`queue_depth` and previews), [Cancel Queued Message](/api-reference/v1/agents/conversations/cancel-queued) (drop one) and [Clear Queue](/api-reference/v1/agents/conversations/clear-queue) (drop all). [Stop](/api-reference/v1/agents/conversations/stop) cancels the running turn and keeps the queue by default.

## Reading the reply

Three ways, depending on how you called:

1. **From the stream.** On any streaming endpoint, `turn_finished` closes the turn and carries the reply in `result`. Sub-task results arrive as `sub_task_finished` events.
2. **From the conversation.** [Get Conversation](/api-reference/v1/agents/conversations/get-conversation) returns the record (with `status` and `result` of the latest turn), the full thread with every turn's messages and skill calls, token usage, and every sub-task's record, thread and usage.
3. **From the task routes.** The `conversation_id` and every sub-task id work with [Get Task](/api-reference/v1/tasks/get-task) (status and result) and [Get Task Thread](/api-reference/v1/tasks/get-thread) (the thread).

### Without holding a stream

For a request/response integration:

1. Send the message with [Run Conversation Turn](/api-reference/v1/agents/conversations/run-turn). It waits for the reply and returns the record with `result`; its `id` is the `conversation_id` to continue with.
2. Add follow-ups with [Send Conversation Message](/api-reference/v1/agents/conversations/send-message) using `mode=queue`, then run them with [Drain Queue](/api-reference/v1/agents/conversations/drain-stream).
3. Check progress any time with [Run State](/api-reference/v1/agents/conversations/run-state) (`is_running`, `queue_depth`) and read replies with [Get Conversation](/api-reference/v1/agents/conversations/get-conversation).

## Streaming

The streaming endpoints ([run a turn](/api-reference/v1/agents/conversations/run-turn-stream), drain, answers, confirm, edit-stream) return **Server-Sent Events**. The stream opens with a `connected` event as soon as the request lands; each following `data:` line is a JSON `TaskUpdateEvent`, the same shape as [Invoke Agent (Stream)](/api-reference/v1/agents/invoke-stream). In order:

| Order | Event           | Carries                                                                                                                                                                                                                                                                                                                                      |
| ----- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1     | `task_created`  | the conversation record (`data.id` = `conversation_id`) with `input` = this turn's message; opens every turn                                                                                                                                                                                                                                 |
| 2     | `turn_started`  | `{turn, task_id, conversation_id, status}`; `turn` counts from 1                                                                                                                                                                                                                                                                             |
| 3..   | progress events | `chunk` (a piece of the reply text), `tool_call_request` and `tool_call_result` (a skill call and what came back), `task_updated` (status or title change), `plan_updated`, `ask_user_questions`, `tool_approval_requested` and `tool_approval_resolved`, `process_state_changed`, `process_update`, `sub_task_created`, `sub_task_finished` |
| n     | `turn_finished` | `{turn, task_id, conversation_id, status, result, awaiting_input, ended}`; closes the turn. `result` is the reply; `awaiting_input` is true when the agent is waiting on you (a question, an approval)                                                                                                                                       |
|       | `task_finished` | may arrive shortly before or after `turn_finished` with the record as it stands; settle on `turn_finished`                                                                                                                                                                                                                                   |

Every event carries `task_id` = `conversation_id`. The reply text streams as `chunk` events for every agent. A client that opens the stream without `Last-Event-ID` receives the latest turn from its first event and the stream ends at that turn's `turn_finished`; reconnect with `Last-Event-ID` set to the last `id:` you received to get the events after it. `sequence` on an event grows across the whole conversation. Chunks are not stored; your messages, skill calls, plan and process updates and one agent message per turn are kept in the thread in order, so reading the conversation back later shows the same text, skill call, text sequence the stream showed.

## Auth and identity

Every route is scoped to the agent in the path and authorized by your API key's access to that agent, like the rest of the v1 API. There is no end-user session, so message and answer bodies accept an optional `user` object when you want to attribute the turn to a person; without it the turn runs unattributed, and a continued conversation keeps whatever user it already carries.
