loadContext to give the model that branch, and use actions to fork or switch it.
A fork copies the conversation up to a selected message. Later messages belong to that branch only. For example, one branch can keep Oslo as a destination while another changes it to Tokyo.
Store the active branch
Your database needs a chat record with anactiveBranchId, plus messages for each branch. You can store each branch as a snapshot or use parent pointers to share message prefixes. Persist the selected branch explicitly; choosing the newest leaf loses the selection when someone switches to an older branch.
Keep the transcript’s final flags, opaque runtime state, and stream cursors. A partial assistant response must stay partial after a reload. See transcript storage for the adapter contract and pagination helpers.
The examples below use application helpers from @/lib/branches. Implement them against your database with these guarantees:
Make repeated saves idempotent by message ID. Serialize branch changes with transcript writes so a turn can’t save its response into a different branch.
Load the selected context
A storage adapter withloadContext owns the model’s context on every turn and action. Include incoming user messages in the selected branch. Handle regeneration explicitly by removing trailing assistant messages before returning the context.
lib/branch-storage.ts
loadContext also receives previousMessages, including any tail recovered from the session stream. If your database missed a save, reconcile that tail with the active branch before returning context. Recovery needs to preserve branch ownership; appending every recovered message to whichever branch is selected can mix conversations.
Fork and switch through actions
Perform branch changes inonAction and update chat.history to match the selected branch. Returning without chat.turn() completes the action without asking the model for a response. The runtime still calls storage.save with reason action.
trigger/chat.ts
Update the frontend after the action
UseuseChatActions with useChat’s sendMessage. It consumes the action’s response stream and keeps request state in the chat hook. After the action completes, reload the selected transcript so the visible messages match the next model call.
app/chat/BranchPicker.tsx
loadTranscript and check ownership before reading the chat. Validate branch IDs on the server even when the frontend only offers branches from that chat. If you use transport.sendAction directly, consume its returned stream before sending another message.
Check branch isolation
- Tell the agent the destination is Oslo and wait for the response.
- Fork at that response, then change the destination to Tokyo.
- Switch to the original branch and ask for the destination. It should answer Oslo.
- Switch to the fork and ask again. It should answer Tokyo.
- Reload the page and repeat the checks. The selected branch and its messages should survive the reload.

