History and replay

Your users see the conversation when they come back to it. AI Transport loads history from the session itself, with gapless continuity into the live stream.

History comes from the session itself. Every message (user prompts, agent responses, lifecycle events) persists on the Ably channel that backs it. Clients load history on connect and paginate backward through the conversation. No separate database is required.

Diagram showing a client loading conversation history from the channel and paginating older messages on scroll

On the client, a minimal history-loading hook:

JavaScript

1

const { messages, hasOlder, loadOlder } = useView({ limit: 30 });

How it works

useView loads history using view.loadOlder() with the untilAttach parameter. untilAttach accounts for every message between the historical window and the latest live message, so history joins the live subscription with no gap.

loadOlder() expands the view window and resolves to the page it revealed (the older messages, oldest-first). The view's messages array also updates with those messages, so you can either read the return value or re-render from messages. The fetch size per channel round trip is set by historyPageSize on the session options (default 100), independent of the limit you reveal per call.

JavaScript

1

2

3

4

5

const { messages, hasOlder, loadOlder } = useView({ limit: 30 });

if (hasOlder) {
  await loadOlder();
}

Implement scroll-back

Load more messages when the user scrolls toward the top of the conversation:

JavaScript

1

2

3

4

5

6

7

const { messages, hasOlder, loading, loadOlder } = useView({ limit: 30 });

function handleScrollToTop() {
  if (hasOlder && !loading) {
    loadOlder();
  }
}

loading is true while a history page is being fetched. Use it to show a spinner at the top of the conversation. When hasOlder is false, the user has reached the beginning of the conversation.

History and branching

History includes branch information. Messages carry parent and forkOf headers that indicate which conversation branch they belong to. When history is loaded, the conversation tree reconstructs branches from these headers and places each message on the correct branch.

Loading history rebuilds the full tree structure rather than a flat list of messages, including every point where the conversation forked because of edits, regenerations, or explicit branching.

Edge cases and unhappy paths

  • Channel history is bounded by your retention policy. A client connecting after retention expires sees only the live stream. Persist completed turns to your own store if you need longer-term retention.
  • loadOlder() while loading is already true is a no-op. Guard against double-trigger from rapid scroll events.
  • A late joiner that arrives mid-stream receives the streamed message in its accumulated form rather than as a replay of every token. The view renders it correctly through the lifecycle tracker.
  • A client without history capability cannot load anything beyond the live subscription window. Capability scoping is part of authentication.
  • A regenerated branch shows up in history with its forkOf header. The view's branch selection determines which sibling renders.

FAQ

Do I need a database for chat history?

Not for AI Transport's own behaviour. The session is the source of truth within retention. Add a database for analytics, search, longer retention, or external integrations.

How do I retain history longer than the channel keeps it?

Persist completed runs to your own store as they end, then hydrate from the database and reconcile that stored history with the live session on reload. The session handles live and in-progress activity.

What does loadOlder() return?

It resolves to the page of older messages it revealed, oldest-first, and [] when nothing was revealed (history exhausted or a load already in flight). The view's messages array updates in step, so you can drive a UI from either the return value or the messages re-render.

Does history include cancelled turns?

Yes. Cancelled messages keep their partial content with a status of cancelled. The view renders them in place.

Can I paginate forward as well as backward?

useView is backward-only because the live subscription handles forward delivery. New messages arrive in realtime without an explicit fetch.