Skip to main content
Version: 0.3.36

Agent Memory with Branches

RaisinDB can give each AI agent its own branch: a versioned copy of the data where the agent reads, writes and reasons without touching other agents or production content. Every write is a revision with an actor and a message, so you can see what an agent did, roll it back, and merge the result the way a developer merges a feature branch.

Why branches for agent memory

ConcernFlat namespaceRaisinDB branches
Agent A overwrites agent B's workPossibleEach agent writes to its own branch
Debugging what an agent didNo historyPer-node history and a branch diff
Rolling back bad agent outputManual cleanupMove the branch head to an earlier revision
Running agents in parallelCoordination neededOne branch per agent
Auditing agent decisionsNot built inEvery revision has an actor, message and timestamp

The pattern: branch, work, merge

1. Create a branch for the task

CREATE BRANCH 'agent/research-task-42' FROM 'main';
await db.branches().create('agent/research-task-42', { fromBranch: 'main' });
curl -X POST http://localhost:8080/api/management/repositories/default/myrepo/branches \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name": "agent/research-task-42", "upstream_branch": "main", "created_by": "orchestrator"}'

The branch is a copy of main at that revision, schema included. The agent can read everything that existed and its writes land only on the branch.

2. The agent stores findings

Hand the agent a branch-scoped handle so every tool call targets the branch:

const agentDb = db.onBranch('agent/research-task-42');
const findings = agentDb.workspace('findings').nodes();

await findings.create({
type: 'research:Finding',
path: '/research/market-analysis',
properties: { title: 'Market Analysis Q1', summary: 'Revenue grew 15%.', confidence: 0.92, sources: ['report-a'] },
});

Or over SQL, addressing the branch in the URL (POST /api/sql/myrepo/agent%2Fresearch-task-42):

INSERT INTO 'findings' (path, node_type, name, properties)
VALUES ('/research/competitors', 'research:Finding', 'competitors',
'{"title":"Competitor Landscape","summary":"Three new entrants.","confidence":0.87}'::jsonb);

To attach a message to a group of writes, wrap them in a SQL transaction:

BEGIN;
UPDATE 'findings' SET properties = '{"title":"Market Analysis Q1","confidence":0.95}'::jsonb WHERE path = '/research/market-analysis';
COMMIT WITH MESSAGE 'Agent: completed market analysis' ACTOR 'research-agent-v2';

3. Review, then merge or discard

The orchestrator (or a human) reads the branch and asks what changed:

await agentDb.executeSql("SELECT path, properties->>'confidence' AS confidence FROM 'findings' WHERE node_type = 'research:Finding'");
await db.branches().diff('agent/research-task-42', 'main');
// { common_ancestor: '...', added: [{ path: '/research/market-analysis', ... }, ...], modified: [], deleted: [] }

Merge it into main:

await db.branches().merge('agent/research-task-42', 'main', { message: 'Merge research task 42' });
// { success: true, revision: 1788720003593, conflicts: [], fast_forward: false, nodes_changed: 2 }

Or delete it if the results are not needed:

await db.branches().delete('agent/research-task-42');

Multi-agent coordination

Run several agents in parallel, one branch each, and merge them in sequence:

main ─────────────────────────────────────────► main (merged)
│ ▲
├── agent/researcher ──── findings ─────────────┤
├── agent/fact-checker ── verifications ────────┤
└── agent/summarizer ──── summary ──────────────┘
CREATE BRANCH 'agent/researcher' FROM 'main';
CREATE BRANCH 'agent/fact-checker' FROM 'main';
CREATE BRANCH 'agent/summarizer' FROM 'main';

-- each agent works on its branch ...

MERGE BRANCH 'agent/researcher' INTO 'main' MESSAGE 'Researcher';
MERGE BRANCH 'agent/fact-checker' INTO 'main' MESSAGE 'Fact checker';
MERGE BRANCH 'agent/summarizer' INTO 'main' MESSAGE 'Summarizer';

Agents that write to different nodes merge without conflict. If two agents change the same node, the merge stops and reports the node with both versions; you complete it with a resolution. See Merging Changes.

Three ways to bring results in:

  • Sequential merge: merge one agent at a time, resolving anything that overlaps.
  • Review then merge: inspect the diff and the findings first; merge or delete.
  • Selective promotion: copy only the roots worth keeping with db.branches().copyNodes(agentBranch, 'main', { workspace, roots }) and drop the rest.

History as agent memory

Every write is a revision. A node's history lists what the agent did to it:

await agentDb.workspace('findings').nodes().historyByPath('/research/market-analysis');
// [{ revision: '1788720366608-0', updated_at: '...', updated_by: 'system', deleted: false,
// message: 'Agent: completed market analysis', is_system: false },
// { revision: '1788720274636-0', ..., message: 'Created node: ...' }]

Read the workspace as it was at any of those revisions:

SELECT path, properties FROM 'findings' WHERE __revision = '1788720274636-0';

The repository-wide log (GET /api/management/repositories/{tenant}/{repo}/revisions?branch=agent%2Fresearcher) lists the branch's commits with actor, message and the nodes each one changed.

Rolling back agent mistakes

Move the branch head to a known-good revision. Later revisions stay in history for debugging.

await db.branches().updateHead('agent/researcher', '1788720274636-0');
curl -X PUT http://localhost:8080/api/management/repositories/default/myrepo/branches/agent%2Fresearcher/head \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"revision": "1788720274636-0"}'

Practical patterns

Conversational memory

One branch per conversation lets an agent accumulate context in isolation:

CREATE BRANCH 'conversations/conv-abc123' FROM 'main';
-- on that branch:
INSERT INTO 'conversations' (path, node_type, name, properties)
VALUES ('/conv-abc123/turn-1', 'chat:Turn', 'turn-1',
'{"role":"user","content":"What is our Q1 revenue?","timestamp":"2026-03-31T10:00:00Z"}'::jsonb);

Scratchpad

A branch the agent experiments in and that is deleted afterwards. Promote just the final output with copyNodes if it is worth keeping.

Reviewed knowledge-base updates

A nightly agent enriches a knowledge base on a dated branch (agent/nightly-2026-03-31); a human reads the diff the next morning and merges or drops it.

A/B testing agent strategies

Run two strategies on two branches from the same main and compare with the same query on each:

for (const b of ['agent/strategy-a', 'agent/strategy-b']) {
const r = await db.onBranch(b).executeSql(
"SELECT COUNT(*) AS n, AVG(properties->>'confidence'::String) AS avg_confidence FROM 'findings' WHERE node_type = 'research:Finding'");
console.log(b, r.rows[0]);
}

Embeddings are stored per branch, and forking a branch copies the source's embeddings along with its content. A KNN or HYBRID_SEARCH query issued on an agent's branch therefore searches what that agent can see, including the nodes it added there:

-- issued against the agent's branch
SELECT path, name FROM KNN('what did I learn about caching', 10, workspaces => 'findings');

See Embeddings and Vector Search.

Next steps