# Branching, edit, and regenerate Your users can edit any message or regenerate any response, and the conversation forks instead of overwriting. AI Transport stores every branch in the conversation tree, with one-line UI hooks for sibling navigation. Your users can revise an earlier message. The original message stays while the new content forks alongside it, and your UI can flip between branches. AI Transport handles the tree shape on the wire; you build the edit, regenerate, and navigation controls. > Branching, edit, and regenerate need a durable session, because a branch is a sibling node in the conversation tree. ![Diagram showing a branching conversation tree with edit and regenerate forks creating sibling branches from anchored messages](https://raw.githubusercontent.com/ably/docs/main/src/images/content/diagrams/ait-concepts-conversation-tree.png) On the client, a minimal regenerate: #### Javascript ``` await view.regenerate(assistantMessageId); ``` ## How it works Every node on the channel carries `parent` and (optionally) `fork-of` or `msg-regenerate` headers. The [conversation tree](https://ably.com/docs/ai-transport/durable-sessions/conversation-tree.md) reads these to build the branching structure: an edit produces a sibling `InputNode` whose `forkOf` points at the original user message's `codec-message-id`; a regenerate produces a same-parent sibling `RunNode` whose `regeneratesCodecMessageId` points at the original assistant message. The branch decision is anchored to a `codecMessageId` rather than a `runId`. Your UI can render navigation arrows next to each message bubble: the user's prompt for edit forks, the assistant message for regenerate groups. The view resolves the branch state on demand. Call `view.branchSelection(codecMessageId)` for any message and get back a handle: `{ hasSiblings, siblings, index, selected, select }`. Call the handle's `select(index)` to switch. ## Regenerate Regenerate creates a sibling run of an assistant message and starts a fresh turn from the same user prompt. The original response stays in the tree. On the client: ### Javascript ``` const { regenerate } = useView(); await regenerate(assistantMessageId); ``` The SDK publishes a `Regenerate` well-known input variant that points at the assistant `codecMessageId`. The agent receives the [invocation](https://ably.com/docs/ai-transport/streaming/runs-and-steps.md#invocations), creates a new run with `regeneratesCodecMessageId` set, and streams the alternative response. ## Edit a user message Edit replaces a user message and starts a new turn from that point. The original user message and everything below it stays in the tree as a separate branch. On the client: ### Javascript ``` const { edit } = useView(); await edit(messageId, { kind: 'user-message', message: { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Make it 5 days, focused on food.' }], }, }); ``` `edit` delegates to `send`, so it carries the same rule: at most one input in the array may introduce a new message, and the rest must be wire-only references to messages that already exist. An edit that replaces one prompt with two rejects with `InvalidArgument` before anything is published: ### Javascript ``` // Client-side. Throws: an edit replaces one message with one message. await view.edit(messageId, [ { kind: 'user-message', message: { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Make it 5 days.' }] } }, { kind: 'user-message', message: { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'And focus on food.' }] } }, ]); ``` Put the whole revised prompt in one message instead. The SDK publishes the replacement as a fresh `UserMessage` with the `forkOf` header pointing at the `codecMessageId` being replaced, and the agent forks the run and starts a fresh response from the edited content. ## Navigate between siblings When a message has siblings, render arrows that switch between them. `branchSelection` returns a `BranchHandle` for any `codecMessageId`: ### Javascript ``` function BranchNav({ codecMessageId, view }) { const branch = view.branchSelection(codecMessageId); if (!branch.hasSiblings) return null; const { siblings, index } = branch; return (
{index + 1} of {siblings.length}
); } ```
`branchSelection` always returns a usable object: `hasSiblings: false` for messages that aren't branch anchors, `index: 0` for unknown ids. You can call it on every rendered bubble without conditionals. When the user selects a different sibling, the view recomputes the visible branch. Every message below the selection point re-renders to reflect the chosen path. ## Side-by-side branches On the client, multiple views over the same conversation tree have independent branch selections, so different parts of the UI can render different branches simultaneously: ### Javascript ``` const view1 = useCreateView(); const view2 = useCreateView(); // view1 shows branch A, view2 shows branch B; both read the same tree. ``` This is the pattern for comparison UIs where the user wants to see two regenerated responses side by side. ## Agent-side handling The agent doesn't usually need bespoke branching logic. The invocation arrives, `createRun` returns a run pinned to the right branch, and draining `run.view` walks the ancestor chain along the selected branch automatically: ### Javascript ``` const invocation = Invocation.fromJSON(await req.json()); const run = session.createRun(invocation, {}, { signal: req.signal }); try { // Rebuild the conversation from run.view before run.start(): draining pages in // this run's triggering input (otherwise run.start() awaits it arriving live). while (run.view.hasOlder()) { await run.view.loadOlder(); } const conversation = run.view.getMessages().map(({ message }) => message); await run.start(); const result = streamText({ model: anthropic('claude-sonnet-4-20250514'), messages: conversation, abortSignal: run.abortSignal, }); const { reason } = await run.pipe(result.toUIMessageStream()); await run.end({ reason }); } catch (err) { await run.end({ reason: 'error' }); throw err; } finally { await session.end(); } ``` `run.view` is pinned to this run's branch, so draining it yields the branch in order: the ancestor turns whose runs completed successfully, and the current user input. Pass those messages straight to the LLM. ## Edge cases and unhappy paths - Editing or regenerating mid-stream cancels nothing automatically. Call `session.cancel(runId)` on the active run first if you do not want both to run. - Branch selection is per-view. Two devices on the same session can see different branches simultaneously; the tree is shared, the selection is per-view. - Deeply branched trees can have many siblings. A user can navigate forever; cap or hide branch navigation in your UI if your app has a preferred branch. - A regenerate against an edited prompt still works. The new branch attaches at the same anchor regardless of what siblings exist. - The visible branch recomputes when a sibling is selected through the branch handle's `select`. Avoid heavy work in the render path; updates are frequent during streaming. - An optimistic edit is merged with the published edit. The SDK [reconciles the local insertion with the wire confirmation](https://ably.com/docs/ai-transport/durable-sessions/optimistic-updates.md). ## FAQ ### Does edit overwrite the original message? No. The original stays in the tree. A new sibling branch is created with the edited content. Users navigate between branches with the `view.branchSelection(codecMessageId)` handle's `select()`. ### How is regenerate different from sending a new prompt? Regenerate creates a sibling response for the same user prompt. Sending a new prompt adds a new exchange below the current branch. Use regenerate to compare alternative responses to the same question; use a new prompt to continue the conversation. ### Can two clients edit at the same time? Yes. Each edit creates a sibling, so concurrent edits produce two new siblings. The tree merges cleanly; each device's view selection determines what it renders. ### What if the LLM produces the same response on regenerate? You get a sibling with the same content. Use temperature or sampling settings to encourage variety, or add a "regenerate again" button. ### How deep can the tree get? Tree depth is bounded by the conversation length. Branch breadth grows with edits and regenerations. There's no fixed limit; render performance is the practical constraint. ## Related features - [Conversation tree](https://ably.com/docs/ai-transport/durable-sessions/conversation-tree.md): the data structure that holds branches. - [Multi-device and fan-out](https://ably.com/docs/ai-transport/streaming/multi-device.md): edits and regenerations sync across devices. - [Optimistic updates](https://ably.com/docs/ai-transport/durable-sessions/optimistic-updates.md): how local-first edits reconcile with the published tree. ## Related Topics - [Overview](https://ably.com/docs/ai-transport/durable-sessions.md): A drop-in durable session layer for AI applications. AI Transport holds the conversation and the message state your UI renders, including branching, and you render from its React hooks. - [Sessions](https://ably.com/docs/ai-transport/durable-sessions/sessions.md): Understand sessions in AI Transport: persistent, shared conversation state that exists independently of any connection, and the ClientSession and AgentSession objects that attach to it. - [Conversation tree](https://ably.com/docs/ai-transport/durable-sessions/conversation-tree.md): Understand how AI Transport organises messages into a branching conversation tree, and how views give each client its own linear path through it. - [Optimistic updates](https://ably.com/docs/ai-transport/durable-sessions/optimistic-updates.md): User messages appear instantly in Ably AI Transport. Optimistic insertion with automatic reconciliation when the server confirms. - [Tool calling](https://ably.com/docs/ai-transport/durable-sessions/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/durable-sessions/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. - [Database hydration](https://ably.com/docs/ai-transport/durable-sessions/database-hydration.md): Hydrate an AI conversation from your own database with AI Transport and reconcile it with the live Ably channel, with no gaps and no duplicate messages. - [Migrate from Streaming](https://ably.com/docs/ai-transport/durable-sessions/move-up-from-streaming.md): What changes in an application already streaming with Ably AI Transport when it moves the conversation into a durable session, and what stays exactly as it is. ## 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.