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_xxx24 alphanumerics- // dashboard
/chat/apicookie, camelCase- // public
/v1/chatsbearer, snake_case- // auto
x_context_forkSSE 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.
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.
- Open the chat toolbar and click the Branches button (the branch icon).
- Click the New Branch button at the bottom of the panel.
- Enter a display name for the branch. The name is required.
- Optionally select a system prompt template to apply to the new branch.
- 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.
- Hover over any message in the conversation to reveal its action buttons.
- Click the fork icon (git-fork) on the message.
- The fork modal opens with the fork point pre-set to that message's sequence number.
- Enter a display name and optionally select a system prompt template.
- 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:
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.
{
"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 https://api.xerotier.ai/chat/api/550e8400-e29b-41d4-a716-446655440000/branches \
-H "Cookie: session=your_session_token"
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
| 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 -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
}'
{
"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 -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.
| Method | Path | Purpose |
|---|---|---|
| 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 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.
| Field | Type | Description |
|---|---|---|
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.
| Column | Type | Description |
|---|---|---|
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.