# Resuming sessions AI Transport uses a channel-oriented model where sessions persist independently of individual connections. Both users and agents can disconnect and rejoin without ending the session. When users or agents rejoin, they need to resume the session from where they left off. An agent or user might resume an existing session when: - A user goes offline or navigates away before returning, expecting to see the latest conversation state - An agent goes offline and comes back online when the user returns - An agent resumes after a failover or service restart ## Hydrating presence When you attach to a channel, Ably automatically syncs the complete current presence set to your client. You can then query the presence set or subscribe to presence events without any additional hydration steps. This works the same way for both users and agents. For details on obtaining the synced presence set, see [Viewing who is online](https://ably.com/docs/ai-transport/sessions-and-identity/online-status.md#viewing-presence). ## User resumes a session Users resume by reattaching to the same session channel and hydrating the conversation transcript, in-progress model output, or other session state. ### Hydrating conversation history The hydration strategy you choose depends on your application model and your chosen approach to token streaming. Clients typically hydrate conversation state using one of these patterns: - Hydrate entirely from the channel: Use [rewind](https://ably.com/docs/channels/options/rewind.md) or [history](https://ably.com/docs/storage-history/history.md) to obtain previous messages on the channel. - Hydrate in-progress responses from the channel: Load completed messages from your database and catch up on any in-progress responses from the channel. For detailed examples of hydrating the token stream, see the token streaming documentation: - [Message-per-response hydration](https://ably.com/docs/ai-transport/token-streaming/message-per-response.md#hydration) - [Message-per-token hydration](https://ably.com/docs/ai-transport/token-streaming/message-per-token.md#hydration) ## Agent resumes a session When an agent restarts, it needs to resume from where it left off. This involves two distinct concerns: 1. Recovering the agent's execution state: The current step in the workflow, local variables, function call results, pending operations, and any other state needed to continue execution. This state is internal to the agent and typically not visible to users. 2. Catching up on session activity: Any user messages, events, or other activity that occurred while the agent was offline. These are separate problems requiring different solutions. Agent execution state is handled by your application and you choose how to persist and restore the internal state your agent needs to resume. Ably provides access to channel message history, enabling agents to retrieve any messages sent while they were offline. When your agent comes back online, it reattaches to the same channel and catches up on messages it missed. This channel-oriented model provides several key benefits: - Guaranteed message delivery: Clients can continue publishing messages even while the agent faults and relocates since the channel exists independently of the agent - Reliable catch-up: The agent can retrieve any messages published during the interim when it comes back online - Ordered delivery: Messages are delivered in the order they were published, ensuring agents process events in the correct sequence - Channel-based addressing: The agent only needs the channel name to reconnect, no need to track individual client connections or manage connection state ### Catching up on messages using history When an agent resumes, it needs to retrieve messages published while it was offline. Use [channel history](https://ably.com/docs/storage-history/history.md) with the [`untilAttach` option](https://ably.com/docs/storage-history/history.md#continuous-history) to catch up on historical messages while preserving continuity with live message delivery. #### Persisted session state Your agent should persist the following state to enable resumption: - Channel name: The channel the agent was processing - Last processed timestamp: The timestamp of the last message successfully processed by the agent This state allows the agent to reconnect to the correct channel and retrieve only the messages it missed. #### Catching up with continuity The recommended pattern uses `untilAttach` to paginate backwards through history while maintaining continuity with live message delivery. This ensures no messages are lost between history retrieval and subscription. ```javascript // Agent code import * as Ably from 'ably'; const ably = new Ably.Realtime({ key: process.env.ABLY_API_KEY, clientId: 'agent:assistant' }); // Load persisted session state const channelName = await loadChannelName(); const lastProcessedTimestamp = await loadLastProcessedTimestamp(); // Use a channel in a namespace with persistence enabled // to access more than 2 minutes of message history const channel = ably.channels.get(channelName); // Subscribe to live messages (implicitly attaches the channel) await channel.subscribe('prompt', (message) => { // Process the live message processMessage(message); // Persist the timestamp after successful processing saveLastProcessedTimestamp(message.timestamp); }); // Fetch history up until the point of attachment, starting from last checkpoint let page = await channel.history({ untilAttach: true, start: lastProcessedTimestamp, direction: 'forwards' }); // Paginate through all missed messages while (page) { for (const message of page.items) { // Process the historical message await processMessage(message); // Persist the timestamp after successful processing await saveLastProcessedTimestamp(message.timestamp); } // Move to next page if available page = page.hasNext() ? await page.next() : null; } ``` ```python # Agent code import os from ably import AblyRealtime ably = AblyRealtime( key=os.environ.get("ABLY_API_KEY"), client_id="agent:assistant" ) # Load persisted session state channel_name = await load_channel_name() last_processed_timestamp = await load_last_processed_timestamp() # Use a channel in a namespace with persistence enabled # to access more than 2 minutes of message history channel = ably.channels.get(channel_name) # Subscribe to live messages (implicitly attaches the channel) def on_prompt(message): # Process the live message process_message(message) # Persist the timestamp after successful processing save_last_processed_timestamp(message.timestamp) await channel.subscribe("prompt", on_prompt) # Fetch history up until the point of attachment, starting from last checkpoint page = await channel.history( until_attach=True, start=last_processed_timestamp, direction="forwards" ) # Paginate through all missed messages while page: for message in page.items: # Process the historical message await process_message(message) # Persist the timestamp after successful processing await save_last_processed_timestamp(message.timestamp) # Move to next page if available page = await page.next() if page.has_next() else None ``` ```java // Agent code import io.ably.lib.realtime.AblyRealtime; import io.ably.lib.realtime.Channel; import io.ably.lib.types.ClientOptions; import io.ably.lib.types.Message; import io.ably.lib.types.PaginatedResult; import io.ably.lib.types.Param; ClientOptions options = new ClientOptions(); options.key = System.getenv("ABLY_API_KEY"); options.clientId = "agent:assistant"; AblyRealtime ably = new AblyRealtime(options); // Load persisted session state String channelName = loadChannelName(); long lastProcessedTimestamp = loadLastProcessedTimestamp(); // Use a channel in a namespace with persistence enabled // to access more than 2 minutes of message history Channel channel = ably.channels.get(channelName); // Subscribe to live messages (implicitly attaches the channel) channel.subscribe("prompt", message -> { // Process the live message processMessage(message); // Persist the timestamp after successful processing saveLastProcessedTimestamp(message.timestamp); }); // Fetch history up until the point of attachment, starting from last checkpoint Param[] params = new Param[] { new Param("untilAttach", "true"), new Param("start", String.valueOf(lastProcessedTimestamp)), new Param("direction", "forwards") }; PaginatedResult page = channel.history(params); // Paginate through all missed messages while (page != null) { for (Message message : page.items()) { // Process the historical message processMessage(message); // Persist the timestamp after successful processing saveLastProcessedTimestamp(message.timestamp); } // Move to next page if available page = page.hasNext() ? page.next() : null; } ``` This pattern provides guaranteed continuity between historical and live message processing by ensuring that: 1. The subscription starts receiving live messages immediately when you subscribe 2. History retrieval stops exactly at the point the channel attached 3. No messages are lost between the end of history and the start of live delivery