# Database hydration Store AI conversation history in your own database. Database hydration reconciles the history you have stored with the live AI Transport session. Database hydration is the pattern of treating your own database as the durable record of a conversation and reconciling it with the live session each time the conversation loads. Use it when your application already stores conversation history for its own features, such as search, analytics, or audit, or when conversations need to stay available longer than the channel [retention period](https://ably.com/docs/storage-history/storage.md). You keep the full AI Transport session for everything live. Resumable token streaming, multi-device continuity, automatic reconnection, and bidirectional control all still run over the session; database hydration changes only where long-term history lives. The session carries live activity, and your own database becomes the durable, queryable record of the conversation. Your agent persists the messages for each completed run to your database when the run completes. When a client or the agent later loads the conversation, it seeds from the database and then loads from the session only the messages newer than the last one it stored (the seam), joining the two into a single conversation with no gaps or duplicates. On the client, hydration is a single call. Give `useMessagesWithSeed` your session view and the stored seed, and it reconciles them with the live session: #### Javascript ``` const messages = useMessagesWithSeed({ view: session.view, seed, getMessageId: (m) => m.id }); ``` Hydration has two mirrored sides: the agent rebuilds the model context, and the client rebuilds the UI. Wire up the agent first, then the client. ## Hydrate the agent The agent rebuilds the model context from your store and the live session. Seed the prior conversation from your store, take the newest stored id as the seam, and call `run.view.loadUntil` to fetch the not-yet-stored tail. The view drives the paging itself, which also folds in this invocation's triggering input message from channel history: ### Javascript ``` const session = createAgentSession({ client: ably, channelName: invocation.sessionName }); await session.connect(); const run = session.createRun(invocation, { signal: req.signal }); // Seed the prior conversation from your store; the newest stored message is the seam. const seed = loadMessages(invocation.sessionName); const seamId = seed.at(-1)?.id; // loadUntil pages run.view back to the seam and returns only the messages newer // than it (the not-yet-stored tail). It drives the paging itself, which also // folds in this invocation's triggering input from channel history. const tail = await run.view.loadUntil((m) => m.message.id === seamId); await run.start(); const conversation = [...seed, ...tail.map((m) => m.message)]; // ...stream the model response with `conversation` as the message history... ``` ## Persist the completed run Persist a run once it completes, because a completed run is immutable. While a run is in flight its messages are still changing: tokens append, tool calls resolve, an assistant message grows. Once the run reaches a terminal state its messages can no longer be mutated, so it is the natural atomic unit to write to your store, and a write keyed by `message.id` is safe to repeat. After the model response streams, persist the run's messages. `run.messages` is a `BaseRun` accessor that returns the run's entire contribution: its triggering input plus all of its streamed output across any suspend and resume. A turn that suspends for a client-side tool result and resumes under the same run therefore still persists as one lossless unit. Persist only completed runs. ### Javascript ``` const runMessages = run.messages; await run.end(outcome); if (outcome.reason === 'complete') await appendMessages(invocation.sessionName, runMessages); ``` Every send introduces at most one new message and triggers exactly one run, so the union of all completed runs' messages reconstructs the conversation with no gaps and no duplicates. ## Hydrate the client The client reconciles the same way as the agent, over its own session view. Seed from your store, then drive `session.view.loadUntil` to fetch the tail newer than the seam and compose the two: ### Javascript ``` const seed = loadStoredMessages(conversationId); const seamId = seed.at(-1)?.id; const tail = await session.view.loadUntil((m) => m.message.id === seamId); const conversation = [...seed, ...tail.map((m) => m.message)]; ``` In React, `useMessagesWithSeed` wraps that walk. Pass your `session.view` and the stored seed, and it returns the composed conversation, kept current as new messages stream in: ### Javascript ``` const messages = useMessagesWithSeed({ view: session.view, seed, getMessageId: (m) => m.id }); ``` If you use the Vercel AI SDK's `useChat`, [`useMessageSync`](https://ably.com/docs/ai-transport/api/react/vercel/use-message-sync.md) runs the same reconciliation against `useChat`'s message state from a `messages` seed, so you keep hydration without leaving `useChat`. ## How reconciliation works Both sides reconcile against the same point, the seam: the newest message your store already holds. Its domain `message.id` is the only id shared by both sides. It is the last thing you persisted, and the same id rides on the channel, because the transport's internal `codecMessageId` never leaves the channel. That makes the domain id the one stable join key. `View.loadUntil(predicate, signal?)` pages the view backward (through `loadOlder` under the hood) until a message matches the predicate, then returns only the messages strictly newer than it. The seam is an exclusive floor: the matched message is not returned, because your store already holds it, so composing `[...seed, ...tail]` drops exactly one overlap and leaves no gap. When the store is empty there is no seam. The predicate never matches, so `loadUntil` pages the whole conversation, and hydration behaves exactly like loading history from the channel. Reconciliation relies on the conversation being linear: each run is persisted whole before the next is sent, and a concurrent send cancels the active run, so there are never two unpersisted runs at once. ## FAQ ### Do I need a database? Only to retain conversations beyond your Ably retention window. Within retention, the channel is the source of truth and [history and replay](https://ably.com/docs/ai-transport/features/history.md) loads everything from the channel with no store. Add a database when you need conversations to outlive the retention policy. ### What do I persist, the whole conversation or just the latest run? Persist each completed run's messages, which is `run.messages`: the run's triggering input plus its streamed output. The union of all runs reconstructs the whole conversation, so you never re-serialise the entire conversation on each run. ### What if the store is behind the channel? That is the expected state on reload. The seam is the newest message your store holds, and `loadUntil` returns the channel messages strictly newer than it. Composing the seed with that tail fills the gap, so a store that lags the channel by several runs still produces a gapless conversation. ### How does this differ from scroll-back history? Scroll-back through [history and replay](https://ably.com/docs/ai-transport/features/history.md) pages the channel backward within retention and never touches a database. Database hydration seeds the conversation from your own store and reconciles the store with the channel at the seam, so it covers the messages retention has already dropped. ### Why is the domain id the seam and not `codecMessageId`? `codecMessageId` is the transport's internal id and never leaves the channel, so it is not available in your store. The domain `message.id` is the id you control and persist, and the same id appears on the channel, which makes it the only stable join key between the two sides. ## Related features - [History and replay](https://ably.com/docs/ai-transport/features/history.md): load and paginate conversation history from the channel within retention. - [Multi-device sessions](https://ably.com/docs/ai-transport/features/multi-device.md): how the same hydrated conversation appears across a user's devices. - [`useMessagesWithSeed`](https://ably.com/docs/ai-transport/api/react/core/use-messages-with-seed.md): the core React hook that composes a seed with the live view. - [`useMessageSync`](https://ably.com/docs/ai-transport/api/react/vercel/use-message-sync.md): the Vercel hook that reconciles a `useChat` seed with the channel. ## Related Topics - [Token streaming](https://ably.com/docs/ai-transport/features/token-streaming.md): Stream AI-generated tokens to clients in realtime using AI Transport, with support for message-per-response and message-per-token patterns. - [Cancellation](https://ably.com/docs/ai-transport/features/cancellation.md): Cancel AI responses mid-stream with Ably AI Transport. Scoped cancel signals, server-side authorization, and graceful abort handling. - [Reconnection and recovery](https://ably.com/docs/ai-transport/features/reconnection-and-recovery.md): AI Transport streams survive connection drops automatically. Clients reconnect and resume from where they left off with no lost tokens. - [Multi-device sessions](https://ably.com/docs/ai-transport/features/multi-device.md): Share AI conversations across tabs, phones, and laptops with Ably AI Transport. All devices see the same session in real time. - [History and replay](https://ably.com/docs/ai-transport/features/history.md): Load conversation history from Ably channels with AI Transport. Paginated history, gapless continuity, and scroll-back patterns. - [Branching, edit, and regenerate](https://ably.com/docs/ai-transport/features/branching.md): Edit user messages, regenerate AI responses, and navigate branches with Ably AI Transport. The full history is preserved in the conversation tree. - [Interruption](https://ably.com/docs/ai-transport/features/interruption.md): Let users interrupt AI agents mid-stream with Ably AI Transport. Cancel-then-send and send-alongside patterns for responsive AI interactions. - [Concurrent turns](https://ably.com/docs/ai-transport/features/concurrent-turns.md): Run multiple AI turns simultaneously with Ably AI Transport. Independent streams, scoped cancellation, and multi-agent support. - [Tool calling](https://ably.com/docs/ai-transport/features/tool-calling.md): Stream tool invocations and results through Ably AI Transport. Server-executed and client-executed tools with persistent state. - [Human-in-the-loop](https://ably.com/docs/ai-transport/features/human-in-the-loop.md): Add human approval gates to AI agent workflows with Ably AI Transport. Approve tool executions and provide input across devices. - [Optimistic updates](https://ably.com/docs/ai-transport/features/optimistic-updates.md): User messages appear instantly in Ably AI Transport. Optimistic insertion with automatic reconciliation when the server confirms. - [Agent presence](https://ably.com/docs/ai-transport/features/agent-presence.md): Show agent status in your AI application with Ably Presence. Display streaming, thinking, idle, and offline states in real time. - [LiveObjects State](https://ably.com/docs/ai-transport/features/liveobjects.md): Give an AI agent live awareness of what the user is doing, and the user live awareness of what the agent is doing, with shared state on the AI Transport session channel via Ably LiveObjects. - [Push notifications](https://ably.com/docs/ai-transport/features/push-notifications.md): Notify users when AI agents complete background tasks with Ably Push Notifications. Reach users even when they're offline. - [Chain of thought](https://ably.com/docs/ai-transport/features/chain-of-thought.md): Stream reasoning and thinking content alongside responses with Ably AI Transport. Display chain-of-thought in real time. - [Double texting](https://ably.com/docs/ai-transport/features/double-texting.md): Handle users sending multiple messages while the AI is streaming with Ably AI Transport. Queue or run messages concurrently. ## 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.