Skip to content
Beta. This surface may change before GA; breaking changes are dated in the changelog.

Branches and Proposals

A branch is an isolated working copy of a Brain. Give an agent a branch-scoped key to let it write records there without granting access to main. Main’s state does not change when the branch changes.

Read that branch with the branch coordinate on /context, /search, or /query. Documents submitted with branch and extract: false are pinned as staged evidence to that branch and remain invisible on main. Compare includes document versions alongside record changes. A Proposal stages the combined delta; approval and publication through the ChangeSet lifecycle make its selected content visible on main. Branch reads and their receipts stay scoped to the branch head they served.

Reads never wait on a branch’s writer. Any number of /context, /search, /query, document, receipt and compare reads run on one branch at the same time, and beside a write to it. Each read is served from one committed branch head and its receipt names that head.

Create a branch with mode: "read_only" to pin it at its fork point:

{ "mode": "read_only" }

POST /v1/brains/{handle}/branches with that body forks the current main state exactly as a writable branch does, then refuses every write to it with 409: records, documents, and opening a Proposal (it has nothing to publish). A key scoped to it can hold only read; minting or widening one with more is a 409. It can still be read by any number of callers at once, compared with main, archived, and destroyed. The mode is fixed at creation; retrying the same Idempotency-Key with a different mode is a 409.

Use it as the pinned view for an evaluation: point the evaluation’s reads at the read-only branch and re-run them later against the knowledge as it was when the branch was created, while main keeps changing.

POST /v1/brains/{handle}/branches/{branchId}/archive stops everything a branch does. It refuses reads and writes, emits no date.reached events, and runs no index rebuilds. Its data is kept. POST /v1/brains/{handle}/branches/{branchId}/restore resumes it: date crossings that came due while it was archived fire on the next sweep, and parked rebuilds continue.

DELETE /v1/brains/{handle}/branches/{branchId} destroys an archived branch. In the same transaction that records the destruction, it removes everything the fork and the branch’s writes created: record and typed-field indexes, pending and emitted date crossings, document pins, staged documents nothing else references, read watermarks and cursors. It also closes the branch’s open Proposals. Nothing about the branch fires or answers afterwards. The Brain’s event history, read receipts, and the closed Proposals remain. Retrying the delete is safe.

GET /v1/brains/{handle}/branches/{branchId}/compare returns the branch’s changes since it forked, applied onto main as main is now. It stages nothing, records no admission decision, and waits on no writer. Run it whenever you need a fresh preview.

Compare is three-way. Changes main made after the fork are not reported as branch changes, so they are never reverted. Each record and document is compared at three points: main at the fork, main now, and the branch.

  • A record the branch changed and main did not is a change.
  • If the branch and main changed different fields of the same record, they merge: the proposal keeps main’s new values and adds the branch’s.
  • If both changed the same field to different values, the record is a conflict.
  • If main deleted a record the branch changed, or both created a record with the same id, the record is also a conflict.
  • A document follows the same rules for its served version. If the branch pins a new version and main now serves a different one, the document is a conflict.
{
"data": {
"summary": {
"nodes": { "upserts": 1, "deletions": 0 },
"edges": { "upserts": 0, "deletions": 0 },
"conflicts": 1
},
"diff": {
"records": [
{
"id": "gamma",
"kind": "company",
"operationId": "3121a944-e1a0-81d3-aadb-e2d489e867bd",
"main": "changed",
"conflict": true,
"changes": [
{
"part": "field",
"field": "name",
"fork": "Gamma Co",
"main": "Gamma Co (main)",
"branch": "Gamma Co (branch)",
"conflict": true
}
]
}
],
"documents": []
}
}
}

main says what main did to the record since the fork: unchanged, changed, deleted, or created. Each entry in changes is a field (or part: "body") the branch changed. It lists main’s value at the fork, main’s value now, and the branch’s value. A value is omitted when that side does not have the field. A document entry lists the version the branch pins (versionId), the version main served at the fork (forkServed), and the version main serves now (expectedServed). Publication requires main to still serve expectedServed.

POST /v1/brains/{handle}/proposals with a branch source pins that delta as a Proposal and stages a review ChangeSet:

{ "source": { "kind": "branch", "branchId": "branch-id" }, "intent": "Update the company catalog" }

The response gives the Proposal and its first immutable revision. The revision includes changeIds and the pinned diff, which has the same records and documents that compare returns. You can open a Proposal that has conflicts. Each operation waits in review until someone approves it. A conflicting operation carries the branch’s value, and its review item names main’s value at the fork, main’s value now, and the branch’s value. To publish only the clean changes, select their operationIds and leave the conflicts as the remainder. The conflicts stay pending until a reviewer approves or rejects them. Approving one publishes the branch’s value over main’s. GET /proposals lists them; filter with sourceKind or status. GET /proposals/{id} returns every revision and the ChangeSets inside it. A branch-scoped key sees only Proposals from its own branch.

When the branch advances, POST /proposals/{id}/revise pins its new delta in a new revision. POST /proposals/{id}/close closes an open Proposal without applying it. Applying still uses the existing ChangeSet select, approve, and publish operations under main’s policy. A Proposal creates no shortcut to accepted state. Direct agent writes staged by admission and source snapshot reviews also appear as Proposals with sources direct and snapshot.

proposal.opened, proposal.revised, proposal.applied, and proposal.closed appear on the event stream and can be delivered to a webhook. The Brain MCP endpoint offers read-only Proposal listing and inspection.