# 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.
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 is stored.
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 earlier 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. 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.
On the agent, 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.
### 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 is carried on the channel, because the transport's internal `codecMessageId` never leaves the channel.
`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 session.
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 session is the source of truth and [history and replay](https://ably.com/docs/ai-transport/features/history.md) loads everything from the session 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 session?
That is the expected state on reload. The seam is the newest message your store holds, and `loadUntil` returns the session messages strictly newer than it. Composing the seed with that tail fills the gap, so a store that lags the session 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 session backward within retention and never touches a database. Database hydration seeds the conversation from your own store and reconciles the store with the session 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 session 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 session.
## Related Topics
- [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 realtime.
- [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.
- [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.
- [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 realtime.
- [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.
- [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.
- [Durable execution](https://ably.com/docs/ai-transport/features/durable-execution.md): Run AI Transport agents inside a durable workflow engine. Adopt an in-flight run from a fresh process, retry a failed step under a stable stepId, and let the retry supersede the failed attempt on the channel.
- [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.
- [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.
- [Interruption and steering](https://ably.com/docs/ai-transport/features/interruption-and-steering.md): Let users change direction mid-response in Ably AI Transport. Three patterns: steer the active run with a follow-up prompt, cancel and re-prompt, or send alongside as a concurrent run.
- [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.
- [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 realtime.
- [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.
- [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.
- [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.
- [Token streaming](https://ably.com/docs/ai-transport/features/token-streaming.md): Stream AI-generated tokens to clients in realtime using AI Transport. Tokens are appended to a single durable message, and the full response is served to clients that join later.
- [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.
## 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.