# Wire protocol AI Transport communicates through Ably channel messages. Every message carries headers that describe its role in the conversation: which turn it belongs to, who sent it, what kind of content it carries, and where it fits in the conversation tree. This page describes the wire format at a moderate level of detail. ## Transport headers Transport headers use the `x-ably-` prefix. They are managed by the SDK and carry the metadata that the transport layer needs to route, order, and reconcile messages. | Header | Description | | --- | --- | | `x-ably-turn-id` | Identifies the turn this message belongs to. All messages in a turn share the same turn ID. | | `x-ably-turn-client-id` | The client ID of the user who initiated the turn. | | `x-ably-turn-reason` | The reason a turn ended: `complete`, `cancelled`, or `error`. Present on `x-ably-turn-end` events. | | `x-ably-msg-id` | A unique identifier for the message. Used for optimistic update reconciliation and message identity. | | `x-ably-role` | The role of the message author: `user`, `assistant`, `system`, or `tool`. | | `x-ably-parent` | The message ID of this message's parent in the conversation tree. Establishes the tree structure. | | `x-ably-fork-of` | The message ID this message forks from. Present when an edit or regenerate creates a new branch. | | `x-ably-stream` | Whether the message is streamed (`true`) or discrete (`false`). | | `x-ably-stream-id` | Identifies the stream this message belongs to. All appends for the same streamed message share a stream ID. | | `x-ably-status` | The status of a streamed message: `streaming`, `finished`, or `aborted`. | | `x-ably-discrete` | Marks a message as a discrete (non-streamed) publish. Present on single-shot messages such as user prompts and tool results. | | `x-ably-amend` | Indicates this message amends (replaces) a previous message. Used for edits and regenerations. | ## Domain headers Domain headers use the `x-domain-` prefix. They carry framework-specific metadata that the transport layer passes through without interpreting. A Vercel AI SDK codec might use domain headers to carry tool call IDs, content part indices, or other framework-specific identifiers. Domain headers are defined by the codec, not the transport. The transport merges them into the outbound message alongside transport headers. ## Lifecycle events Lifecycle events signal the start and end of turns. They are published as Ably messages with specific names. | Event | Description | | --- | --- | | `x-ably-turn-start` | A new turn has begun. Published before any content messages for the turn. | | `x-ably-turn-end` | The turn is complete. Includes `x-ably-turn-reason` to indicate how it ended. | | `x-ably-cancel` | A cancel request from a client. Includes a filter specifying which turns to cancel. | | `x-ably-abort` | The turn was forcefully terminated. May include partial content from an `onAbort` hook. | | `x-ably-error` | An error occurred during the turn. The turn ends with reason `error`. | A normal turn follows the sequence: `x-ably-turn-start`, content messages, `x-ably-turn-end` with reason `complete`. A cancelled turn follows: `x-ably-turn-start`, content messages, `x-ably-turn-end` with reason `cancelled`. ## Content messages Content messages carry the actual conversation data: user prompts, assistant responses, tool calls, and tool results. They come in two forms. ### Discrete messages A discrete message is published as a single Ably message. The entire content is in one publish operation. User messages are typically discrete - the full text is known at publish time. #### Javascript ``` // A discrete user message channel.publish('message.create', { data: { role: 'user', content: 'What is the weather?' }, extras: { headers: { 'x-ably-msg-id': 'msg_001', 'x-ably-role': 'user', 'x-ably-turn-id': 'turn_001', 'x-ably-stream': 'false', } } }) ``` ### Streamed messages A streamed message is published incrementally as tokens arrive from the LLM. It uses three operations over its lifecycle: 1. **Create** (`message.create`): starts the streamed message. Sets `x-ably-stream` to `true` and `x-ably-status` to `streaming`. 2. **Append** (`message.append`): appends a token or chunk to the message. Published for each token in the stream. 3. **Close** (`message.update`): finalizes the message with a terminal status (`finished` or `aborted`). #### Javascript ``` // 1. Create the streamed message channel.publish('message.create', { data: { role: 'assistant', content: '' }, extras: { headers: { 'x-ably-msg-id': 'msg_002', 'x-ably-stream': 'true', 'x-ably-stream-id': 'stream_001', 'x-ably-status': 'streaming', } } }) // 2. Append tokens channel.appendMessage({ name: 'msg_002', data: 'The weather' }) channel.appendMessage({ name: 'msg_002', data: ' is sunny.' }) // 3. Close the stream channel.updateMessage({ name: 'msg_002', extras: { headers: { 'x-ably-status': 'finished' } } }) ``` Subscribers accumulate appends locally to build the complete message. If a subscriber joins mid-stream, the Ably message on the channel already contains all previous appends - only future appends arrive as live events. ## Message identity The `x-ably-msg-id` header is the primary identifier for a message in the conversation tree. It is distinct from the Ably message serial, which is assigned by the channel. ### Generation Message IDs are generated by the sender. When the client sends a user message optimistically, the client generates the ID. When the server publishes messages from the LLM, the server generates the ID. The codec may generate IDs for streamed messages when the stream starts. ### Stamping The server transport stamps the `x-ably-msg-id` header onto outbound messages. If the codec does not provide an ID, the transport generates one. This ensures every message on the channel has a consistent, unique identifier. ### Reconciliation When a client inserts a message optimistically, it assigns an `x-ably-msg-id`. The server publishes the same message with the same ID. When the client receives the published message through its subscription, it matches IDs and promotes the optimistic message to a confirmed one. The message keeps its position in the tree, but gains a real Ably serial for ordering. ## Related pages - [Codec architecture](https://ably.com/docs/ai-transport/internals/codec-architecture.md) - how the codec uses the wire protocol to encode and decode messages. - [Conversation tree](https://ably.com/docs/ai-transport/internals/conversation-tree.md) - how `x-ably-parent` and `x-ably-fork-of` build the tree structure. - [Optimistic updates](https://ably.com/docs/ai-transport/features/optimistic-updates.md) - reconciliation from the user's perspective. ## Related Topics - [Overview](https://ably.com/docs/ai-transport/internals.md): Under the hood of Ably AI Transport. Wire protocol, codec architecture, conversation tree, and transport patterns. - [Codec architecture](https://ably.com/docs/ai-transport/internals/codec-architecture.md): How the AI Transport codec bridges domain events to Ably messages. Encoder, decoder, accumulator, and lifecycle tracker internals. - [Conversation tree](https://ably.com/docs/ai-transport/internals/conversation-tree.md): How AI Transport maintains a branching conversation structure. Serial ordering, sibling groups, fork chains, and flatten algorithm. - [Transport patterns](https://ably.com/docs/ai-transport/internals/transport-patterns.md): Internal transport components in AI Transport. StreamRouter, TurnManager, pipeStream, and cancel routing. ## Documentation Index To discover additional Ably documentation: 1. Fetch [llms.txt](https://ably.com/llms.txt) for the canonical list of available pages. 2. Identify relevant URLs from that index. 3. Fetch target pages as needed. Avoid using assumed or outdated documentation paths.