From c58d8c1b444aca6e0ea6c1fb00a013ef9a472307 Mon Sep 17 00:00:00 2001 From: matt423 Date: Mon, 5 Jan 2026 11:38:46 +0000 Subject: [PATCH 1/8] chore: update message annotations terminology to include appends --- content/partials/types/_message.textile | 131 ++++++++++++++++++ .../token-streaming/message-per-response.mdx | 16 +-- 2 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 content/partials/types/_message.textile diff --git a/content/partials/types/_message.textile b/content/partials/types/_message.textile new file mode 100644 index 0000000000..272d7b8b28 --- /dev/null +++ b/content/partials/types/_message.textile @@ -0,0 +1,131 @@ +A @Message@ represents an individual message that is sent to or received from Ably. + +h6(#name). + default: name + csharp: Name + +The event name, if provided.
__Type: @String@__ + +h6(#data). + default: data + csharp: Data + +The message payload, if provided.
__Type: @String@, @StringBuffer@, @JSON Object@@String@, @ByteArray@, @JSONObject@, @JSONArray@@String@, @byte[]@, @plain C# object that can be serialized to JSON@@String@, @Binary@ (ASCII-8BIT String), @Hash@, @Array@@String@, @Bytearray@, @Dict@, @List@@String@, @Binary String@, @Associative Array@, @Array@@NSString *@, @NSData *@, @NSDictionary *@, @NSArray *@@String@, @NSData@, @Dictionary@, @Array@@String@, @Map@, @List@__ + +h6(#extras). + default: extras + csharp: Extras + +Metadata and/or ancillary payloads, if provided. Valid payloads include "@push@":/docs/push/publish#payload, "@headers@" (a map of strings to strings for arbitrary customer-supplied metadata), "@ephemeral@":/docs/pub-sub/advanced#ephemeral, and "@privileged@":/docs/platform/integrations/webhooks#skipping objects.
__Type: @JSONObject@, @JSONArray@plain C# object that can be converted to JSON@JSON Object@@Hash@, @Array@@Dict@, @List@@Dictionary@, @Array@@NSDictionary *@, @NSArray *@@Associative Array@, @Array@__ + +h6(#id). + default: id + csharp: Id + +A Unique ID assigned by Ably to this message.
__Type: @String@__ + +h6(#client-id). + default: clientId + csharp: ClientId + ruby: client_id + python: client_id + +The client ID of the publisher of this message.
__Type: @String@__ + +h6(#connection-id). + default: connectionId + csharp: ConnectionId + ruby: connection_id + python: connection_id + +The connection ID of the publisher of this message.
__Type: @String@__ + +h6(#connection-key). + default: connectionKey + csharp,go: ConnectionKey + ruby,python: connection_key + +A connection key, which can optionally be included for a REST publish as part of the "publishing on behalf of a realtime client functionality":/docs/pub-sub/advanced#publish-on-behalf.
__Type: @String@__ + +h6(#timestamp). + default: timestamp + csharp: Timestamp + +Timestamp when the message was first received by the Ably, as milliseconds since the epocha @Time@ object
.__Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@__ + +h6(#encoding). + default: encoding + csharp: Encoding + +This will typically be empty as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the @data@ payload.
__Type: @String@__ + +blang[jsall]. + + h6(#action). + default: action + + The action type of the message, one of the "@MessageAction@":#message-action enum values.
__Type: @int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }@__ + + h6(#serial). + default: serial + + A server-assigned identifier that will be the same in all future updates of this message. It can be used to add "annotations":/docs/messages/annotations to a message or to "update or delete":/docs/messages/updates-deletes it. Serial will only be set if you enable annotations, updates, deletes, and appends in "channel rules":/docs/channels#rules .
__Type: @String@__ + + h6(#annotations). + default: annotations + + An object containing information about annotations that have been made to the object.
__Type: "@MessageAnnotations@":/docs/api/realtime-sdk/types#message-annotations__ + + h6(#version). + default: version + + An object containing version metadata for messages that have been updated or deleted. See "updating and deleting messages":/docs/messages/updates-deletes for more information.
__Type: "@MessageVersion@":#message-version__ + +h3(constructors). + default: Message constructors + +h6(#message-from-encoded). + default: Message.fromEncoded + +bq(definition). + default: Message.fromEncoded(Object encodedMsg, ChannelOptions channelOptions?) -> Message + +A static factory method to create a "@Message@":/docs/api/realtime-sdk/types#message from a deserialized @Message@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedMsg := a @Message@-like deserialized object.
__Type: @Object@__ +- channelOptions := an optional "@ChannelOptions@":/docs/api/realtime-sdk/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +A "@Message@":/docs/api/realtime-sdk/types#message object + +h6(#message-from-encoded-array). + default: Message.fromEncodedArray + +bq(definition). + default: Message.fromEncodedArray(Object[] encodedMsgs, ChannelOptions channelOptions?) -> Message[] + +A static factory method to create an array of "@Messages@":/docs/api/realtime-sdk/types#message from an array of deserialized @Message@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedMsgs := an array of @Message@-like deserialized objects.
__Type: @Array@__ +- channelOptions := an optional "@ChannelOptions@":/docs/api/realtime-sdk/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +An @Array@ of "@Message@":/docs/api/realtime-sdk/types#message objects + +h3(#message-version). + default: MessageVersion + +h4. Properties + +|_. Property |_. Description |_. Type | +| serial | An Ably-generated ID that uniquely identifies this version of the message. Can be compared lexicographically to determine version ordering. For an original message with an action of @message.create@, this will be equal to the top-level @serial@. | @String@ | +| timestamp | The time this version was created (when the update or delete operation was performed). For an original message, this will be equal to the top-level @timestamp@. | @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@ | +| clientId | The client identifier of the user who performed the update or delete operation. Only present for @message.update@ and @message.delete@ actions. | @String@ (optional) | +| description | Optional description provided when the update or delete was performed. Only present for @message.update@ and @message.delete@ actions. | @String@ (optional) | +| metadata | Optional metadata provided when the update or delete was performed. Only present for @message.update@ and @message.delete@ actions. | @Object@ (optional) | diff --git a/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx b/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx index a501f562a6..dab2c838f5 100644 --- a/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx +++ b/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx @@ -3,7 +3,7 @@ title: Message per response meta_description: "Stream individual tokens from AI models into a single message over Ably." --- -Token streaming with message-per-response is a pattern where every token generated by your model for a given response is appended to a single Ably message. Each complete AI response then appears as one message in the channel history while delivering live tokens in realtime. This uses [Ably Pub/Sub](/docs/basics) for realtime communication between agents and clients. +Token streaming with message-per-response is a pattern where every token generated by your model is appended to a single Ably message. Each complete AI response then appears as one message in the channel history while delivering live tokens in realtime. This uses [Ably Pub/Sub](/docs/basics) for realtime communication between agents and clients. This pattern is useful for chat-style applications where you want each complete AI response stored as a single message in history, making it easy to retrieve and display multi-response conversation history. Each agent response becomes a single message that grows as tokens are appended, allowing clients joining mid-stream to catch up efficiently without processing thousands of individual tokens. @@ -22,10 +22,10 @@ Standard Ably message [size limits](/docs/platform/pricing/limits#message) apply ## Enable appends -Message append functionality requires "Message annotations, updates, deletes and appends" to be enabled in a [channel rule](/docs/channels#rules) associated with the channel. +Message append functionality requires the "Message annotations, updates, deletes, and appends" [channel rule](/docs/channels#rules) enabled for your channel or [namespace](/docs/channels#namespaces). To enable the channel rule: @@ -34,7 +34,7 @@ To enable the channel rule: 2. Navigate to the "Configuration" > "Rules" section from the left-hand navigation bar. 3. Choose "Add new rule". 4. Enter a channel name or namespace pattern (e.g. `ai:*` for all channels starting with `ai:`). -5. Select the "Message annotations, updates, deletes and appends" option from the list. +5. Select the "Message annotations, updates, deletes, and appends" rule from the list. 6. Click "Create channel rule". The examples on this page use the `ai:` namespace prefix, which assumes you have configured the rule for `ai:*`. @@ -102,7 +102,7 @@ Subscribers receive different message actions depending on when they join and ho - `message.create`: Indicates a new response has started (i.e. a new message was created). The message `data` contains the initial content (often empty or the first token). Store this as the beginning of a new response using `serial` as the identifier. - `message.append`: Contains a single token fragment to append. The message `data` contains only the new token, not the full concatenated response. Append this token to the existing response identified by `serial`. -- `message.update`: Contains the whole response up to that point. The message `data` contains the full concatenated text so far. Replace the entire response content with this data for the message identified by `serial`. This action occurs when the channel needs to resynchronize the full message state, such as after a client [resumes](/docs/connect/states#resume) from a transient disconnection. +- `message.update`: Contains the complete response up to that point. The message `data` contains the full concatenated text so far. Replace the entire response content with this data for the message identified by `serial`. This action occurs when the channel needs to resynchronize the full message state, such as after a client [resumes](/docs/connect/states#resume) from a transient disconnection. ```javascript @@ -132,14 +132,14 @@ await channel.subscribe((message) => { ``` -## Client hydration +## Client hydration When clients connect or reconnect, such as after a page refresh, they often need to catch up on complete responses and individual tokens that were published while they were offline or before they joined. The message per response pattern enables efficient client state hydration without needing to process every individual token and supports seamlessly transitioning from historical responses to live tokens. ### Using rewind for recent history @@ -275,7 +275,7 @@ for await (const event of stream) { #### Hydrate using rewind From 6d6dd45571d4c1aa6dce49b0a1662f6a27398fbb Mon Sep 17 00:00:00 2001 From: Mittul Madaan Date: Tue, 6 Jan 2026 13:52:11 +0000 Subject: [PATCH 2/8] Add citations feature documentation for AI Transport --- src/data/nav/aitransport.ts | 9 + .../features/advanced/citations.mdx | 337 ++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 src/pages/docs/ai-transport/features/advanced/citations.mdx diff --git a/src/data/nav/aitransport.ts b/src/data/nav/aitransport.ts index b3ed8937ce..5f9c48cada 100644 --- a/src/data/nav/aitransport.ts +++ b/src/data/nav/aitransport.ts @@ -81,6 +81,15 @@ export default { name: 'OpenAI token streaming - message per response', link: '/docs/guides/ai-transport/openai-message-per-response', }, + ] + }, + { + name: 'Advanced', + pages: [ + { + name: 'Citations', + link: '/docs/ai-transport/features/advanced/citations', + }, ], }, ], diff --git a/src/pages/docs/ai-transport/features/advanced/citations.mdx b/src/pages/docs/ai-transport/features/advanced/citations.mdx new file mode 100644 index 0000000000..6508229b78 --- /dev/null +++ b/src/pages/docs/ai-transport/features/advanced/citations.mdx @@ -0,0 +1,337 @@ +--- +title: "Citations" +meta_description: "Attach source citations to AI responses using message annotations" +meta_keywords: "citations, references, RAG, retrieval augmented generation, source attribution, message annotations, AI transparency, source tracking, annotation summaries, model-agnostic, LLM-neutral" +--- + +AI agents often draw information from external sources like documents, web pages, or databases. Citations enable users to verify information, explore sources in detail, and understand where responses came from. Ably's [message annotations](/docs/messages/annotations) provide a model-agnostic, structured way to attach source citations to AI responses without modifying the response content. It enable clients to append information to existing messages on a channel. + +This pattern works with both single message publishing and the [message-per-response](/docs/ai-transport/message-per-response) approach using message appends. + +## Why citations matter + +Citations serve several critical purposes in AI applications: + +**Transparency**: Users can verify claims and understand the basis for AI responses. This builds trust and allows users to fact-check information independently. + +**Source exploration**: Citations enable users to dive deeper into topics by accessing original sources. This is particularly valuable for research, learning, and decision-making workflows. + +**Attribution**: Proper attribution respects content creators and helps users understand which sources informed the AI's response. + +**Audit trails**: For enterprise applications, citations provide an audit trail showing which information sources were consulted during AI interactions. + +## How it works + +Citations use Ably's [message annotations](/docs/messages/annotations) feature to attach source metadata to AI response messages without modifying the response content. + +The annotation publishing workflow: + +1. **Publish response**: Agent publishes an AI response as a single message or builds it incrementally using [message appends](/docs/ai-transport/message-per-response) +2. **Publish citation annotations**: Agent publishes one or more citation annotations, each referencing the response message serial +3. **Aggregate summaries**: Ably automatically aggregates annotations and generates summaries showing total counts and groupings (e.g., by domain) +4. **Subscribe citations**: Clients receive citation summaries automatically and can optionally subscribe to individual annotation events for detailed citation data + +Annotations are associated with a message's serial identifier. This works with: + +- **Single message publish**: Complete response published as one message +- **Message appends**: Response built incrementally by appending tokens to a single message (see [message-per-response](/docs/ai-transport/message-per-response)) + +## Setup + +Message annotations require the "Message annotations, updates, and deletes" [channel rule](/docs/channels#rules) enabled for your channel or [namespace](/docs/channels#namespaces). This rule automatically enables message persistence. + +To enable the channel rule: + +1. Go to the [Ably dashboard](https://www.ably.com/dashboard) and select your app. +2. Navigate to the "Configuration" > "Rules" section from the left-hand navigation bar. +3. Choose "Add new rule". +4. Enter a channel name or namespace pattern (e.g. `ai:*` for all channels starting with `ai:`). +5. Select the "Message annotations, updates, and deletes" rule from the list. +6. Click "Create channel rule". + +The examples in this guide use the `ai:` namespace prefix, which assumes you have configured the rule for `ai:*`. + +## Citation data model + +### Annotation type + +Use the `citations:multiple.v1` annotation type for citation features. It provides: + +- **Automatic grouping**: Citations are grouped by the `name` field (for example, grouping by domain) +- **Count aggregation**: Ably counts how many citations come from each source +- **Efficient summaries**: Clients receive grouped summaries without processing individual events + +### Citation payload + +Each citation is carried in the Ably annotation `data` field and should include: + + +```json +{ + "url": "https://example.com/article", + "title": "Example Article Title", + "startOffset": 120, + "endOffset": 180, + "snippet": "Optional short excerpt from source", + "sourceId": "optional-stable-identifier" +} +``` + + +**Field descriptions**: + +- `url`: The source URL (required) +- `title`: Human-readable source title (required) +- `startOffset`: Character position in response where citation applies (optional) +- `endOffset`: End character position for citation range (optional) +- `snippet`: Short excerpt from the source (optional) +- `sourceId`: Stable identifier for deduplication (optional) + +Character offsets allow UIs to attach inline citation markers to specific portions of the response text. + +## Publishing citations from agents + +After publishing the response (or during streaming, if citations are available), publish citation annotations referencing the message serial. For incremental streaming using message appends, see [message-per-response](/docs/ai-transport/message-per-response). + +### Publishing a single citation + + +```javascript +const channel = ably.channels.get('ai:{{RANDOM_CHANNEL_NAME}}'); + +// Publish the AI response +const responseText = "The James Webb Space Telescope launched in December 2021."; +const { serials: [messageSerial] } = await channel.publish('ai-response', { + data: responseText +}); + +// Publish a citation annotation +await channel.annotations.publish(messageSerial, { + type: 'citations:multiple.v1', + name: 'science.nasa.gov', + data: { + url: 'https://science.nasa.gov/mission/webb/', + title: 'Webb Mission Overview - NASA Science', + startOffset: 0, + endOffset: 56, + snippet: 'The James Webb Space Telescope launched on December 25, 2021 from Europe\'s Spaceport in French Guiana.' + } +}); +``` + + +### Publishing multiple citations + + +```javascript +const channel = ably.channels.get('ai:{{RANDOM_CHANNEL_NAME}}'); + +// Publish multiple citations +async function publishCitations(messageSerial, sources) { + for (const source of sources) { + await channel.annotations.publish(messageSerial, { + type: 'citations:multiple.v1', + name: new URL(source.url).hostname, + data: { + url: source.url, + title: source.title, + startOffset: source.startOffset, + endOffset: source.endOffset, + snippet: source.snippet + } + }); + } +} + +// Publish response with citations +async function publishResponseWithCitations() { + // Publish the AI response + const responseText = "The James Webb Space Telescope launched in December 2021 and captured its first images in July 2022."; + const { serials: [messageSerial] } = await channel.publish('ai-response', { + data: responseText + }); + + // Define citation sources + const sources = [ + { + url: 'https://science.nasa.gov/mission/webb/', + title: 'Webb Mission Overview - NASA Science', + startOffset: 0, + endOffset: 56, + snippet: 'The James Webb Space Telescope launched on December 25, 2021 from Europe\'s Spaceport in French Guiana.' + }, + { + url: 'https://en.wikipedia.org/wiki/James_Webb_Space_Telescope', + title: 'James Webb Space Telescope - Wikipedia', + startOffset: 61, + endOffset: 107, + snippet: 'The telescope captured its first images in July 2022, revealing unprecedented detail...' + } + ]; + + // Publish citations + await publishCitations(messageSerial, sources); + + return messageSerial; +} +``` + + +## Subscribing citations for clients + +Clients subscribe citations from Ably in two ways: + +1. **Summary view** (default): Aggregate counts from Ably `message.summary` events +2. **Raw view** (on demand): Individual citation details from Ably annotation events + +### Summary view + +Subscribe to Ably channels normally to receive automatic annotation summaries: + + +```javascript +const channel = ably.channels.get('ai:{{RANDOM_CHANNEL_NAME}}'); + +// Track responses +const responses = new Map(); + +// Subscribe to receive messages and summaries +await channel.subscribe((message) => { + switch (message.action) { + case 'message.create': + // New response started + responses.set(message.serial, message.data); + break; + + case 'message.summary': + // Citation summary + const citations = message.annotations?.summary?.['citations:multiple.v1']; + if (citations) { + console.log('Citation summary:', citations); + } + break; + } +}); +``` + + +**Citation summary structure:** + +The summary is included in an `annotations.summary` field within the message and is an object whose keys are the annotation types and whose values describe the annotation summary for that type. + + +```json +{ + "citations:multiple.v1": { + "science.nasa.gov": { + "total": 1, + "clientIds": { + "test-publisher": 1 + }, + "totalUnidentified": 0, + "totalClientIds": 1, + "clipped": false + }, + "en.wikipedia.org": { + "total": 1, + "clientIds": { + "test-publisher": 1 + }, + "totalUnidentified": 0, + "totalClientIds": 1, + "clipped": false + } + } +} +``` + + +**Key fields:** +- `total`: Total count of annotations for this group +- `clientIds`: Breakdown showing which clients published annotations +- `clipped`: Whether the summary was truncated due to size limits + +Ably summary view provides: + +- **Total citation count**: Sum of all citation counts across groups +- **Group breakdown**: Count of citations per group (e.g., per domain) +- **Efficient updates**: Ably summaries update automatically as citations are added + +### Raw view + +To access individual citation details from Ably, subscribe to annotation events: + + +```javascript +// Enable ANNOTATION_SUBSCRIBE mode +const channel = ably.channels.get('ai:{{RANDOM_CHANNEL_NAME}}', { + modes: ['ANNOTATION_SUBSCRIBE'] +}); + +// Subscribe to annotation events +await channel.annotations.subscribe((annotation) => { + if (annotation.action === 'annotation.create' && + annotation.type === 'citations:multiple.v1') { + const citation = annotation.data; + if (citation) { + console.log('Citation data:', citation); + } + } +}); +``` + + +**Example raw annotation structure:** + +When you subscribe to raw annotations, each annotation event has the following structure: + + +```json +{ + "action": "annotation.create", + "clientId": "test-publisher", + "type": "citations:multiple.v1", + "serial": "01767705527528-000@108rFDTSQBxhtu98297114:000", + "messageSerial": "01767638186693-000@108SP4XcgBxfMO07491612:000", + "connectionId": "Y8CqupU0-E", + "name": "en.wikipedia.org", + "count": 1, + "encoding": null, + "data": { + "url": "https://en.wikipedia.org/wiki/James_Webb_Space_Telescope", + "title": "James Webb Space Telescope - Wikipedia", + "startOffset": 61, + "endOffset": 107, + "snippet": "The telescope captured its first images in July 2022, revealing unprecedented detail of the early universe." + }, + "timestamp": 1767705527528, + "id": "Y8CqupU0-E:1:0" +} +``` + + +**Key fields in raw annotations:** + +- `action`: Always `"annotation.create"` for new annotations +- `type`: The annotation type (`citations:multiple.v1`) +- `messageSerial`: The serial of the message this citation is attached to +- `name`: The grouping key (e.g., domain name) +- `data`: Your citation payload with URL, title, offsets, snippet +- `clientId`: The client that published the annotation + +Ably raw citations provide: + +- **Full citation metadata**: All fields from the citation data payload +- **Character offsets**: For placing inline citation markers +- **Group name**: The `name` field used for grouping (e.g., domain) +- **Individual events**: Each citation arrives as a separate Ably event + + + +## Related topics + +- [Message annotations](/docs/messages/annotations) - Core Ably feature for attaching metadata to messages +- [Message per response](/docs/ai-transport/message-per-response) - Streaming pattern using Ably message appends +- [Token streaming](/docs/ai-transport/token-streaming) - Alternative approach with granular Ably history From 64bfda33d239329719aae9dc4b0610b5711dde9b Mon Sep 17 00:00:00 2001 From: Mittul Madaan Date: Tue, 13 Jan 2026 13:31:51 +0000 Subject: [PATCH 3/8] AIT-133 - Fixed review comments --- .../features/advanced/citations.mdx | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/pages/docs/ai-transport/features/advanced/citations.mdx b/src/pages/docs/ai-transport/features/advanced/citations.mdx index 6508229b78..4955a3c9bd 100644 --- a/src/pages/docs/ai-transport/features/advanced/citations.mdx +++ b/src/pages/docs/ai-transport/features/advanced/citations.mdx @@ -4,7 +4,7 @@ meta_description: "Attach source citations to AI responses using message annotat meta_keywords: "citations, references, RAG, retrieval augmented generation, source attribution, message annotations, AI transparency, source tracking, annotation summaries, model-agnostic, LLM-neutral" --- -AI agents often draw information from external sources like documents, web pages, or databases. Citations enable users to verify information, explore sources in detail, and understand where responses came from. Ably's [message annotations](/docs/messages/annotations) provide a model-agnostic, structured way to attach source citations to AI responses without modifying the response content. It enable clients to append information to existing messages on a channel. +AI agents often draw information from external sources such as documents, web pages, or databases. Citations to those sources enable users to verify information, explore sources in detail, and understand where responses came from. Ably's [message annotations](/docs/messages/annotations) provide a model-agnostic, structured way to attach source citations to AI responses without modifying the response content. It enable clients to append information to existing messages on a channel. This pattern works with both single message publishing and the [message-per-response](/docs/ai-transport/message-per-response) approach using message appends. @@ -31,14 +31,14 @@ The annotation publishing workflow: 3. **Aggregate summaries**: Ably automatically aggregates annotations and generates summaries showing total counts and groupings (e.g., by domain) 4. **Subscribe citations**: Clients receive citation summaries automatically and can optionally subscribe to individual annotation events for detailed citation data -Annotations are associated with a message's serial identifier. This works with: +Annotations are associated with a message's `serial` identifier. This works with: - **Single message publish**: Complete response published as one message - **Message appends**: Response built incrementally by appending tokens to a single message (see [message-per-response](/docs/ai-transport/message-per-response)) ## Setup -Message annotations require the "Message annotations, updates, and deletes" [channel rule](/docs/channels#rules) enabled for your channel or [namespace](/docs/channels#namespaces). This rule automatically enables message persistence. +Message annotations require the "Message annotations, updates, deletes, and appends" [channel rule](/docs/channels#rules) enabled for your channel or [namespace](/docs/channels#namespaces). This rule automatically enables message persistence. To enable the channel rule: @@ -63,7 +63,7 @@ Use the `citations:multiple.v1` annotation type for citation features. It provid ### Citation payload -Each citation is carried in the Ably annotation `data` field and should include: +Each citation is proposed to be carried in the Ably annotation `data` field and should include: ```json @@ -72,8 +72,7 @@ Each citation is carried in the Ably annotation `data` field and should include: "title": "Example Article Title", "startOffset": 120, "endOffset": 180, - "snippet": "Optional short excerpt from source", - "sourceId": "optional-stable-identifier" + "snippet": "Optional short excerpt from source" } ``` @@ -82,16 +81,17 @@ Each citation is carried in the Ably annotation `data` field and should include: - `url`: The source URL (required) - `title`: Human-readable source title (required) -- `startOffset`: Character position in response where citation applies (optional) -- `endOffset`: End character position for citation range (optional) -- `snippet`: Short excerpt from the source (optional) -- `sourceId`: Stable identifier for deduplication (optional) +- `startOffset`: Character position in the LLM generated response where this citation begins to apply, enabling clients to associate citations with specific portions of the response text (optional) +- `endOffset`: Character position where the citation’s applicability ends, used together with `startOffset` to define a citation range (optional) +- `snippet`: Short excerpt from the source content, intended for preview, tooltip, or summary displays without requiring a full page fetch (optional) Character offsets allow UIs to attach inline citation markers to specific portions of the response text. ## Publishing citations from agents -After publishing the response (or during streaming, if citations are available), publish citation annotations referencing the message serial. For incremental streaming using message appends, see [message-per-response](/docs/ai-transport/message-per-response). +Agents publish citations as annotation messages that reference the `serial` of the response message they relate to. This allows clients to associate citations with the correct response message. + +Citations can be published once the response has been sent, or progressively during streaming if citation data becomes available earlier. For incremental streaming using message appends, see [message-per-response](/docs/ai-transport/message-per-response). ### Publishing a single citation @@ -313,8 +313,8 @@ When you subscribe to raw annotations, each annotation event has the following s **Key fields in raw annotations:** - `action`: Always `"annotation.create"` for new annotations -- `type`: The annotation type (`citations:multiple.v1`) -- `messageSerial`: The serial of the message this citation is attached to +- `type`: The annotation `type` (`citations:multiple.v1`) +- `messageSerial`: The `serial` of the message this citation is attached to - `name`: The grouping key (e.g., domain name) - `data`: Your citation payload with URL, title, offsets, snippet - `clientId`: The client that published the annotation @@ -328,6 +328,8 @@ Ably raw citations provide: ## Related topics From f769b0a2fa90b14280c0f53d5946823d7bbea1c0 Mon Sep 17 00:00:00 2001 From: Mittul Madaan Date: Tue, 13 Jan 2026 14:05:29 +0000 Subject: [PATCH 4/8] AIT-133 - fix lint issue + resolve conflicts --- content/partials/types/_message.textile | 131 ------------------ src/data/nav/aitransport.ts | 2 +- .../token-streaming/message-per-response.mdx | 18 +-- 3 files changed, 10 insertions(+), 141 deletions(-) delete mode 100644 content/partials/types/_message.textile diff --git a/content/partials/types/_message.textile b/content/partials/types/_message.textile deleted file mode 100644 index 272d7b8b28..0000000000 --- a/content/partials/types/_message.textile +++ /dev/null @@ -1,131 +0,0 @@ -A @Message@ represents an individual message that is sent to or received from Ably. - -h6(#name). - default: name - csharp: Name - -The event name, if provided.
__Type: @String@__ - -h6(#data). - default: data - csharp: Data - -The message payload, if provided.
__Type: @String@, @StringBuffer@, @JSON Object@@String@, @ByteArray@, @JSONObject@, @JSONArray@@String@, @byte[]@, @plain C# object that can be serialized to JSON@@String@, @Binary@ (ASCII-8BIT String), @Hash@, @Array@@String@, @Bytearray@, @Dict@, @List@@String@, @Binary String@, @Associative Array@, @Array@@NSString *@, @NSData *@, @NSDictionary *@, @NSArray *@@String@, @NSData@, @Dictionary@, @Array@@String@, @Map@, @List@__ - -h6(#extras). - default: extras - csharp: Extras - -Metadata and/or ancillary payloads, if provided. Valid payloads include "@push@":/docs/push/publish#payload, "@headers@" (a map of strings to strings for arbitrary customer-supplied metadata), "@ephemeral@":/docs/pub-sub/advanced#ephemeral, and "@privileged@":/docs/platform/integrations/webhooks#skipping objects.
__Type: @JSONObject@, @JSONArray@plain C# object that can be converted to JSON@JSON Object@@Hash@, @Array@@Dict@, @List@@Dictionary@, @Array@@NSDictionary *@, @NSArray *@@Associative Array@, @Array@__ - -h6(#id). - default: id - csharp: Id - -A Unique ID assigned by Ably to this message.
__Type: @String@__ - -h6(#client-id). - default: clientId - csharp: ClientId - ruby: client_id - python: client_id - -The client ID of the publisher of this message.
__Type: @String@__ - -h6(#connection-id). - default: connectionId - csharp: ConnectionId - ruby: connection_id - python: connection_id - -The connection ID of the publisher of this message.
__Type: @String@__ - -h6(#connection-key). - default: connectionKey - csharp,go: ConnectionKey - ruby,python: connection_key - -A connection key, which can optionally be included for a REST publish as part of the "publishing on behalf of a realtime client functionality":/docs/pub-sub/advanced#publish-on-behalf.
__Type: @String@__ - -h6(#timestamp). - default: timestamp - csharp: Timestamp - -Timestamp when the message was first received by the Ably, as milliseconds since the epocha @Time@ object
.__Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@__ - -h6(#encoding). - default: encoding - csharp: Encoding - -This will typically be empty as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the @data@ payload.
__Type: @String@__ - -blang[jsall]. - - h6(#action). - default: action - - The action type of the message, one of the "@MessageAction@":#message-action enum values.
__Type: @int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }@__ - - h6(#serial). - default: serial - - A server-assigned identifier that will be the same in all future updates of this message. It can be used to add "annotations":/docs/messages/annotations to a message or to "update or delete":/docs/messages/updates-deletes it. Serial will only be set if you enable annotations, updates, deletes, and appends in "channel rules":/docs/channels#rules .
__Type: @String@__ - - h6(#annotations). - default: annotations - - An object containing information about annotations that have been made to the object.
__Type: "@MessageAnnotations@":/docs/api/realtime-sdk/types#message-annotations__ - - h6(#version). - default: version - - An object containing version metadata for messages that have been updated or deleted. See "updating and deleting messages":/docs/messages/updates-deletes for more information.
__Type: "@MessageVersion@":#message-version__ - -h3(constructors). - default: Message constructors - -h6(#message-from-encoded). - default: Message.fromEncoded - -bq(definition). - default: Message.fromEncoded(Object encodedMsg, ChannelOptions channelOptions?) -> Message - -A static factory method to create a "@Message@":/docs/api/realtime-sdk/types#message from a deserialized @Message@-like object encoded using Ably's wire protocol. - -h4. Parameters - -- encodedMsg := a @Message@-like deserialized object.
__Type: @Object@__ -- channelOptions := an optional "@ChannelOptions@":/docs/api/realtime-sdk/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ - -h4. Returns - -A "@Message@":/docs/api/realtime-sdk/types#message object - -h6(#message-from-encoded-array). - default: Message.fromEncodedArray - -bq(definition). - default: Message.fromEncodedArray(Object[] encodedMsgs, ChannelOptions channelOptions?) -> Message[] - -A static factory method to create an array of "@Messages@":/docs/api/realtime-sdk/types#message from an array of deserialized @Message@-like object encoded using Ably's wire protocol. - -h4. Parameters - -- encodedMsgs := an array of @Message@-like deserialized objects.
__Type: @Array@__ -- channelOptions := an optional "@ChannelOptions@":/docs/api/realtime-sdk/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ - -h4. Returns - -An @Array@ of "@Message@":/docs/api/realtime-sdk/types#message objects - -h3(#message-version). - default: MessageVersion - -h4. Properties - -|_. Property |_. Description |_. Type | -| serial | An Ably-generated ID that uniquely identifies this version of the message. Can be compared lexicographically to determine version ordering. For an original message with an action of @message.create@, this will be equal to the top-level @serial@. | @String@ | -| timestamp | The time this version was created (when the update or delete operation was performed). For an original message, this will be equal to the top-level @timestamp@. | @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@ | -| clientId | The client identifier of the user who performed the update or delete operation. Only present for @message.update@ and @message.delete@ actions. | @String@ (optional) | -| description | Optional description provided when the update or delete was performed. Only present for @message.update@ and @message.delete@ actions. | @String@ (optional) | -| metadata | Optional metadata provided when the update or delete was performed. Only present for @message.update@ and @message.delete@ actions. | @Object@ (optional) | diff --git a/src/data/nav/aitransport.ts b/src/data/nav/aitransport.ts index 5f9c48cada..ffd6638878 100644 --- a/src/data/nav/aitransport.ts +++ b/src/data/nav/aitransport.ts @@ -81,7 +81,7 @@ export default { name: 'OpenAI token streaming - message per response', link: '/docs/guides/ai-transport/openai-message-per-response', }, - ] + ], }, { name: 'Advanced', diff --git a/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx b/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx index dab2c838f5..d62390904f 100644 --- a/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx +++ b/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx @@ -3,7 +3,7 @@ title: Message per response meta_description: "Stream individual tokens from AI models into a single message over Ably." --- -Token streaming with message-per-response is a pattern where every token generated by your model is appended to a single Ably message. Each complete AI response then appears as one message in the channel history while delivering live tokens in realtime. This uses [Ably Pub/Sub](/docs/basics) for realtime communication between agents and clients. +Token streaming with message-per-response is a pattern where every token generated by your model for a given response is appended to a single Ably message. Each complete AI response then appears as one message in the channel history while delivering live tokens in realtime. This uses [Ably Pub/Sub](/docs/basics) for realtime communication between agents and clients. This pattern is useful for chat-style applications where you want each complete AI response stored as a single message in history, making it easy to retrieve and display multi-response conversation history. Each agent response becomes a single message that grows as tokens are appended, allowing clients joining mid-stream to catch up efficiently without processing thousands of individual tokens. @@ -22,10 +22,10 @@ Standard Ably message [size limits](/docs/platform/pricing/limits#message) apply ## Enable appends
-Message append functionality requires the "Message annotations, updates, deletes, and appends" [channel rule](/docs/channels#rules) enabled for your channel or [namespace](/docs/channels#namespaces). +Message append functionality requires "Message annotations, updates, deletes and appends" to be enabled in a [channel rule](/docs/channels#rules) associated with the channel. To enable the channel rule: @@ -34,7 +34,7 @@ To enable the channel rule: 2. Navigate to the "Configuration" > "Rules" section from the left-hand navigation bar. 3. Choose "Add new rule". 4. Enter a channel name or namespace pattern (e.g. `ai:*` for all channels starting with `ai:`). -5. Select the "Message annotations, updates, deletes, and appends" rule from the list. +5. Select the "Message annotations, updates, deletes and appends" option from the list. 6. Click "Create channel rule". The examples on this page use the `ai:` namespace prefix, which assumes you have configured the rule for `ai:*`. @@ -102,7 +102,7 @@ Subscribers receive different message actions depending on when they join and ho - `message.create`: Indicates a new response has started (i.e. a new message was created). The message `data` contains the initial content (often empty or the first token). Store this as the beginning of a new response using `serial` as the identifier. - `message.append`: Contains a single token fragment to append. The message `data` contains only the new token, not the full concatenated response. Append this token to the existing response identified by `serial`. -- `message.update`: Contains the complete response up to that point. The message `data` contains the full concatenated text so far. Replace the entire response content with this data for the message identified by `serial`. This action occurs when the channel needs to resynchronize the full message state, such as after a client [resumes](/docs/connect/states#resume) from a transient disconnection. +- `message.update`: Contains the whole response up to that point. The message `data` contains the full concatenated text so far. Replace the entire response content with this data for the message identified by `serial`. This action occurs when the channel needs to resynchronize the full message state, such as after a client [resumes](/docs/connect/states#resume) from a transient disconnection. ```javascript @@ -132,14 +132,14 @@ await channel.subscribe((message) => { ``` -## Client hydration +## Client hydration When clients connect or reconnect, such as after a page refresh, they often need to catch up on complete responses and individual tokens that were published while they were offline or before they joined. The message per response pattern enables efficient client state hydration without needing to process every individual token and supports seamlessly transitioning from historical responses to live tokens. ### Using rewind for recent history @@ -275,7 +275,7 @@ for await (const event of stream) { #### Hydrate using rewind @@ -409,4 +409,4 @@ while (page) { + \ No newline at end of file From 5b334161518b07e41fbcabff1535f4ece3fa396d Mon Sep 17 00:00:00 2001 From: Mittul Madaan Date: Tue, 13 Jan 2026 14:09:40 +0000 Subject: [PATCH 5/8] AIT-133 - nit fix --- .../features/token-streaming/message-per-response.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx b/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx index d62390904f..a501f562a6 100644 --- a/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx +++ b/src/pages/docs/ai-transport/features/token-streaming/message-per-response.mdx @@ -409,4 +409,4 @@ while (page) { \ No newline at end of file + From e4aca26ea86535101d7eccbbae7d3b03f2e588c0 Mon Sep 17 00:00:00 2001 From: Mittul Madaan Date: Tue, 13 Jan 2026 14:56:35 +0000 Subject: [PATCH 6/8] Fixed review comments. --- src/pages/docs/ai-transport/features/advanced/citations.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/docs/ai-transport/features/advanced/citations.mdx b/src/pages/docs/ai-transport/features/advanced/citations.mdx index 4955a3c9bd..8e3c3b1bfa 100644 --- a/src/pages/docs/ai-transport/features/advanced/citations.mdx +++ b/src/pages/docs/ai-transport/features/advanced/citations.mdx @@ -63,7 +63,7 @@ Use the `citations:multiple.v1` annotation type for citation features. It provid ### Citation payload -Each citation is proposed to be carried in the Ably annotation `data` field and should include: +Each citation is proposed to be carried in the Ably annotation `data` field and should include, for example: ```json From 3583f6b09e8dbb24389076b63c873c56cb1f3bd3 Mon Sep 17 00:00:00 2001 From: Mittul Madaan Date: Wed, 14 Jan 2026 11:10:50 +0000 Subject: [PATCH 7/8] Update src/pages/docs/ai-transport/features/advanced/citations.mdx Co-authored-by: Paddy Byers --- src/pages/docs/ai-transport/features/advanced/citations.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/docs/ai-transport/features/advanced/citations.mdx b/src/pages/docs/ai-transport/features/advanced/citations.mdx index 8e3c3b1bfa..6007d504f4 100644 --- a/src/pages/docs/ai-transport/features/advanced/citations.mdx +++ b/src/pages/docs/ai-transport/features/advanced/citations.mdx @@ -29,7 +29,7 @@ The annotation publishing workflow: 1. **Publish response**: Agent publishes an AI response as a single message or builds it incrementally using [message appends](/docs/ai-transport/message-per-response) 2. **Publish citation annotations**: Agent publishes one or more citation annotations, each referencing the response message serial 3. **Aggregate summaries**: Ably automatically aggregates annotations and generates summaries showing total counts and groupings (e.g., by domain) -4. **Subscribe citations**: Clients receive citation summaries automatically and can optionally subscribe to individual annotation events for detailed citation data +4. **Subscribe citations**: Clients receive citation summaries automatically and can optionally subscribe to individual annotation events for detailed citation data as part of the realtime stream. Alternatively, clients can obtain annotations for a given message via the REST API. Annotations are associated with a message's `serial` identifier. This works with: From bfc21a50d4103d89c97241ca3435dc53fac926bb Mon Sep 17 00:00:00 2001 From: Mittul Madaan Date: Wed, 14 Jan 2026 11:18:59 +0000 Subject: [PATCH 8/8] AIT-133 - Moved Citations under Messaging --- src/data/nav/aitransport.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/data/nav/aitransport.ts b/src/data/nav/aitransport.ts index ffd6638878..606f7988b4 100644 --- a/src/data/nav/aitransport.ts +++ b/src/data/nav/aitransport.ts @@ -68,6 +68,10 @@ export default { name: 'Chain of thought', link: '/docs/ai-transport/features/messaging/chain-of-thought', }, + { + name: 'Citations', + link: '/docs/ai-transport/features/advanced/citations', + }, ], }, { @@ -83,15 +87,6 @@ export default { }, ], }, - { - name: 'Advanced', - pages: [ - { - name: 'Citations', - link: '/docs/ai-transport/features/advanced/citations', - }, - ], - }, ], api: [], } satisfies NavProduct;