Chat

Conversation Branching

Edit messages to create parallel conversation paths, just like ChatGPT and Claude.ai

Beta — This feature is in alpha. APIs may change before stable release.

Edit any user message to create a parallel conversation path, preserving the original. Navigate between variants with ← N/M → — the same UX as ChatGPT, Claude.ai, and Gemini.


Zero-Config Usage

If you use <CopilotChat />, branching is already active. No code changes needed.

  • Edit button (✏) appears on hover over any user message
  • ← 1/2 → navigator appears below user messages when variants exist
  • Regenerate creates a new branch instead of overwriting
// Nothing to add — branching works out of the box
<CopilotChat className="h-[600px]" />

New APIs

useCopilot() / useCopilotProvider

const {
  switchBranch,   // (messageId: string) => void
  getBranchInfo,  // (messageId: string) => BranchInfo | null
  editMessage,    // (messageId: string, newContent: string) => Promise<void>
  hasBranches,    // boolean — true if any fork exists
  getAllMessages,  // () => UIMessage[] — all branches, not just visible path
} = useCopilot();

BranchInfo type

interface BranchInfo {
  siblingIndex: number;    // 0-based — which variant this is
  totalSiblings: number;   // how many variants exist at this fork
  siblingIds: string[];    // ordered oldest-first
  hasPrevious: boolean;
  hasNext: boolean;
}

BranchNavigator component

Standalone navigator for custom message renderers:

import { BranchNavigator } from "@yourgpt/copilot-sdk/ui";

<BranchNavigator
  siblingIndex={info.siblingIndex}
  totalSiblings={info.totalSiblings}
  hasPrevious={info.hasPrevious}
  hasNext={info.hasNext}
  onPrevious={() => switchBranch(info.siblingIds[info.siblingIndex - 1])}
  onNext={() => switchBranch(info.siblingIds[info.siblingIndex + 1])}
/>

MessageTree (framework-agnostic)

import { MessageTree, type BranchInfo } from "@yourgpt/copilot-sdk";

const tree = new MessageTree(messages);
tree.getVisibleMessages(); // active path only (sent to AI)
tree.getAllMessages();     // all branches (for persistence)
tree.getBranchInfo(messageId); // BranchInfo | null
tree.switchBranch(messageId);
tree.hasBranches; // boolean

Manual Wiring (<Chat /> users)

Wire the three props from useCopilot():

function MyChat() {
  const { switchBranch, getBranchInfo, editMessage } = useCopilot();

  return (
    <Chat
      getBranchInfo={getBranchInfo}
      onSwitchBranch={switchBranch}
      onEditMessage={editMessage}
    />
  );
}

Custom Message Renderers

Use getBranchInfo + BranchNavigator in your own message components:

function MyMessage({ message }) {
  const { switchBranch, getBranchInfo } = useCopilot();
  const info = message.role === "user" ? getBranchInfo(message.id) : null;

  return (
    <div>
      <p>{message.content}</p>
      {info && (
        <BranchNavigator
          {...info}
          onPrevious={() => switchBranch(info.siblingIds[info.siblingIndex - 1])}
          onNext={() => switchBranch(info.siblingIds[info.siblingIndex + 1])}
        />
      )}
    </div>
  );
}

Programmatic Branching

// Edit a message (creates new branch from same parent)
await editMessage("msg-abc", "Updated question text");

// Navigate between variants
switchBranch("msg-xyz");

// Check if branches exist
if (hasBranches) {
  const info = getBranchInfo("msg-abc");
  console.log(info.totalSiblings, info.siblingIndex);
}

// Persist all branches (not just visible path)
const allMessages = getAllMessages();
await saveToServer(allMessages);

Persistence

New DB columns (optional)

Two new optional columns on your messages table:

ALTER TABLE messages
  ADD COLUMN parent_id TEXT REFERENCES messages(id),
  ADD COLUMN children_ids JSONB DEFAULT '[]';

These columns are optional. Existing rows without them are auto-migrated to a linear tree on load — no data loss, no required migration script.

What gets saved

When onMessagesChange fires, the payload now contains all messages across all branches. Each message carries:

{
  "id": "msg-abc",
  "role": "assistant",
  "content": "...",
  "parent_id": "msg-xyz",
  "children_ids": []
}
// ✅ Safe for branching — upsert by ID
await db.messages.upsert({ id: msg.id, ...msg });

// ⚠️ Loses inactive branches
await db.threads.update({ messages: visibleMessages });

Breaking Changes

None. All new fields and methods are optional. Existing usage is untouched.

ScenarioBehavior
Messages with no parentIdFalls back to insertion order (legacy linear)
regenerate() with no argsIdentical to before
sendMessage() with no optionsIdentical to before
onMessagesChange consumersPayload now includes all branches — shape unchanged

On this page