// Features

Chat Branching

Fork a chat at any message and run alternate paths in parallel. A/B test prompts, recover from a bad turn, or hold the original intact while the model tries again. Workspace and uploads stay shared; histories diverge.

// id
branch_xxx 24 alphanumerics
// dashboard
/chat/api cookie, camelCase
// public
/v1/chats bearer, snake_case
// auto
x_context_fork SSE event

Every chat starts with one branch (the default branch). You can create as many additional branches as needed. Common use cases include:

  • A/B testing prompts, fork after your initial setup and try different approaches in parallel branches without losing either result.
  • Exploring alternatives, ask the model to try a different approach in a new branch while keeping the original intact.
  • Rolling back, fork before a risky or experimental direction so you can return to the fork point if things go wrong.
  • Isolating concerns, use separate branches for different subtopics in a long-running project.

Each branch is identified by a unique external ID in branch_xxx format and has a user-supplied display name. Branches record which branch they were forked from (their parent) and at which message sequence number the fork occurred.

Note: Documents uploaded to the workspace are shared across all branches within a chat. A document uploaded on one branch is searchable on all other branches.

// fork tree
graph LR
    M["branch_main
seq 0..12"]:::active --> A["branch_ABC
fork at seq 8"] M --> B["branch_XYZ
fork from end"] A --> C["branch_DEF
fork at seq 5"] classDef active fill:#070F12,stroke:#f4a261,color:#FFF5EE

Creating a Branch

You can create a new branch at any time from the Branches panel in the chat toolbar. This creates a branch forked from the end of the current active branch, all current messages are included in the new branch's history.

  1. Open the chat toolbar and click the Branches button (the branch icon).
  2. Click the New Branch button at the bottom of the panel.
  3. Enter a display name for the branch. The name is required.
  4. Optionally select a system prompt template to apply to the new branch.
  5. Click Create. The branch is created and you are switched to it automatically.

The branch selector in the toolbar updates to show the new branch as the active selection.

Fork from a Message

You can also fork a branch at a specific point in the conversation history by clicking the fork icon on any individual message. This creates a branch that diverges at that message, messages after the fork point on the current branch are not included in the new branch's history.

  1. Hover over any message in the conversation to reveal its action buttons.
  2. Click the fork icon (git-fork) on the message.
  3. The fork modal opens with the fork point pre-set to that message's sequence number.
  4. Enter a display name and optionally select a system prompt template.
  5. Click Create. The new branch starts from that point in the history.

The forkAfterSequence field on the branch record stores the sequence number at which the fork occurred, giving you a precise record of the branch's origin in the parent's history.

Switching Branches

The branch selector dropdown in the toolbar always shows all available branches for the current chat. Select a branch from the dropdown to switch to it immediately. The message list clears and reloads with the selected branch's history.

The branch's system prompt override (if any) is applied to the client-side canvas state when you switch. This restore is performed in the browser; the server's message-list response carries messages only, not the prompt.

You can also switch branches from the Branches panel by clicking a branch name in the tree list.

Branch Panel

The Branches panel provides a visual overview of all branches in the chat using a git-tree style layout. Open it by clicking the branch icon in the toolbar.

The panel renders branches as a flat tree. Each branch is displayed as a row with:

  • A colored lane indicator dot (each branch lane has a distinct color).
  • The branch display name as a clickable button that switches to that branch.
  • A delete button (available when more than one branch exists).

The currently active branch is visually highlighted. Child branches are indented under their parent to reflect the parent-child tree structure.

A New Branch button at the bottom of the panel opens the fork modal to create a branch from the current position in the active branch.

Branch System Prompts

Each branch can carry an optional system prompt override. When a branch has a system prompt set, that prompt replaces the chat-level system prompt during context assembly for all messages on that branch.

Branch system prompts are set at fork time by selecting a template from the template selector in the fork modal. Templates are fetched from GET /chat/api/templates and appear as options in the dropdown. If no template is selected, the branch inherits the chat's default system prompt.

Switching branches updates the client-side canvas state to use the target branch's system prompt so subsequent messages on that branch are composed with the correct prompt. The restore is purely client-side; the server does not emit a prompt-restore event.

Note: System prompt overrides apply per-branch. Changing the chat-level system prompt does not affect branches that have an override already set.

Automatic Forking

The system can automatically fork the conversation when the context window approaches its limit. When this happens, the active branch is forked automatically into a new branch with a generated name, and the user is notified by a toast message:

Toast Example
Context limit approaching, forked to "Branch Name"

This automatic fork is triggered by a server-sent x_context_fork event in the streaming response. The branch selector is updated automatically and the active branch switches to the new fork without requiring any user action.

x_context_fork payload
{ "type": "x_context_fork", "branchId": "branch_XYZabc987654321098765432", "branchName": "Auto-fork at 92%", "messageCount": 47, "reason": "Context utilization at 92%" }

Each branch maintains a rolling summary of older messages and a summaryThroughSequence counter so that summarized history can be reconstructed efficiently within the token budget.

Deleting Branches

Branches can be deleted from the Branches panel. Click the delete icon on any branch row to remove it. Deleting a branch requires confirmation.

The last remaining branch cannot be deleted, at least one branch must always exist in a chat. If you attempt to delete the last branch, a toast error is shown and the operation is cancelled.

Deleting a branch also deletes all of its sub-branches (child branches that were forked from it). The confirmation dialog notes this.

Branches are soft-deleted in the database. The record remains with a deleted_at timestamp but is excluded from all list queries. If the active branch is deleted, the UI automatically switches to the first remaining branch.

Branch API (Dashboard)

The following endpoints back the in-app dashboard UI. They live under /chat/api/... and authenticate using the browser session cookie issued at login. They are not intended for programmatic clients; see Public API (Bearer Auth) below for the bearer-token surface.

List Branches

GET /chat/api/{chatId}/branches

Returns all active branches for the chat ordered by creation date.

curl
curl https://api.xerotier.ai/chat/api/550e8400-e29b-41d4-a716-446655440000/branches \ -H "Cookie: session=your_session_token"
Response
{ "branches": [ { "id": "branch_ABCdef123456789012345678", "name": "Main", "messageCount": 12, "createdAt": "2026-04-07T10:00:00Z", "isActive": true, "parentId": null, "systemPrompt": null, "starterMessage": null, "parentMessageId": null }, { "id": "branch_XYZabc987654321098765432", "name": "Experiment A", "messageCount": 3, "createdAt": "2026-04-07T10:15:00Z", "isActive": false, "parentId": "branch_ABCdef123456789012345678", "systemPrompt": "You are a concise assistant.", "starterMessage": null, "parentMessageId": "msg_abc123" } ] }

The fields messageCount, isActive, starterMessage, and parentMessageId are always present. isActive is the only signal that identifies the currently-selected branch in the dashboard; parentMessageId is required to render inline-thread anchors.

Create Branch

POST /chat/api/{chatId}/branch

Creates a new branch forked from the specified (or active) branch. Returns HTTP 201 Created on success.

Request Body

FieldTypeRequiredDescription
namerequired string Yes Display name for the new branch. Must be 1 to 64 characters.
branchoptional string No External ID of the parent branch to fork from. When omitted, the server defaults to the chat's first (oldest) branch.
atSequenceoptional integer No Fork after this message sequence number. When omitted, the fork point is the end of the parent branch. Negative values return HTTP 400; values past the parent's last message are clamped to the available maximum.
templateIdoptional string No System prompt template ID to apply to the new branch.
parent_message_idoptional string No External ID of the message that anchors the new branch as an inline thread. When set, the dashboard renders the branch under that message in the parent conversation; the referenced message must belong to the same chat.
curl
curl -X POST https://api.xerotier.ai/chat/api/550e8400-e29b-41d4-a716-446655440000/branch \ -H "Cookie: session=your_session_token" \ -H "Content-Type: application/json" \ -d '{ "name": "Experiment A", "branch": "branch_ABCdef123456789012345678", "atSequence": 12 }'
Response (201 Created)
{ "id": "branch_XYZabc987654321098765432", "name": "Experiment A", "messageCount": 12, "createdAt": "2026-04-07T10:15:00Z", "isActive": false, "parentId": "branch_ABCdef123456789012345678", "systemPrompt": null, "starterMessage": null, "parentMessageId": null }

Delete Branch

DELETE /chat/api/{chatId}/branch/{branchId}

Soft-deletes a branch and all of its sub-branches. Attempting to delete the main (parentless) branch returns HTTP 400 with the cannot_delete_main error code. Returns HTTP 204 on success.

curl
curl -X DELETE https://api.xerotier.ai/chat/api/550e8400-e29b-41d4-a716-446655440000/branch/branch_XYZabc987654321098765432 \ -H "Cookie: session=your_session_token"

Public API (Bearer Auth)

Different wire shape. This surface is not a rename of the dashboard API. Fields are snake_case, the create body takes from_message_id and title, and the response DTO carries chat_id, fork_point_message_id, and is_main. Pick one surface per client.

Programmatic clients use the project-scoped public API, which authenticates with a bearer API key. The routes live under /{project_id}/v1/chats/{chat_id}/... and return OpenAI-style { "object": "list", "data": [...] } envelopes.

MethodPathPurpose
GET /v1/chats/{chat_id}/branches List branches for the chat.
POST /v1/chats/{chat_id}/branches Create a branch.
DELETE /v1/chats/{chat_id}/branches/{branch_id} Soft-delete a branch.
POST /v1/chats/{chat_id}/auto_fork Trigger an auto-fork of the active branch.
curl
curl https://api.xerotier.ai//v1/chats/550e8400-e29b-41d4-a716-446655440000/branches \ -H "Authorization: Bearer $XEROTIER_API_KEY"

Both surfaces manipulate the same underlying records; the divergence is field naming and response shape, not state.

Data Model

The dashboard API serialises a subset of the persisted record. The two tables below separate the wire schema from the database columns; do not assume DB-only fields are reachable through the API.

Dashboard API Response Schema

These are the fields returned by GET /chat/api/{chatId}/branches and POST /chat/api/{chatId}/branch. Field names are camelCase on the wire.

FieldTypeDescription
id string External branch identifier in branch_xxx format (24 alphanumeric characters after the prefix).
name string User-supplied display name for the branch.
messageCount integer Number of non-deleted messages currently on the branch.
createdAt string ISO 8601 timestamp when the branch was created.
isActive boolean True when this branch is the caller's currently-selected branch for the chat. Only one branch per response has this set.
parentId string (nullable) External ID of the parent branch this was forked from. Null for the root branch.
systemPrompt string (nullable) Optional system prompt override for this branch. When set, replaces the chat-level system prompt during context assembly.
starterMessage string (nullable) Optional opening message rendered when the branch is empty, copied from the template (if any) at fork time.
parentMessageId string (nullable) External ID of the message that anchors an inline-thread branch under a specific message in the parent conversation. Null when the branch is a top-level fork.

Database Schema (server-internal)

The persisted chat_branches record stores additional columns that are not exposed on the dashboard API. They are documented here for operator and integrator reference only; do not write client code that depends on these being present in API responses.

ColumnTypeDescription
forkAfterSequence integer (nullable) Sequence number after which the fork occurred. Populated as an integer on every fork (including forks taken from the end of the parent, which record the parent's current max sequence). Null only for the root/main branch.
rollingSummary string (nullable) Periodically regenerated summary of older messages on this branch, used to keep context within the token budget.
summaryThroughSequence integer (nullable) The highest sequence number covered by the rolling summary. Messages after this point are included verbatim in context.
contextBreakdown json (nullable) Cached breakdown of token usage by category (history, summary, system prompt, etc.) produced after each assistant response on the branch.
lastResponseId string (nullable) Most recent Responses API response ID for conversation chaining. Updated after each completed assistant response on this branch.
storageSizeBytes integer Computed storage footprint of the branch's messages, used for workspace storage accounting.
deletedAt string (nullable) ISO 8601 timestamp when the branch was soft-deleted. Null for active branches; excluded from all list responses.

Related Pages

  • Chat Memory, saving and recalling per-workspace knowledge across conversations.
  • Document Workspace, uploading documents that are shared across all branches.