Branching & Time Travel
Git-style version control for your entire knowledge graph. Fork, commit, branch, and time-travel through your data.
No other vector database offers Git-style branching. Fork your entire knowledge graph in <100ms using copy-on-write technology.
Overview
While the Versioning API tracks individual entities, the Branching API provides workspace-level version control. Create isolated branches for experiments, track commits with full state snapshots, and query historical data.
How It Works: Copy-on-Write (COW)
Brainy uses Copy-on-Write technology for instant forks. When you fork:
- HNSW Index: Shallow copy (~10ms for 1M+ nodes)
- Metadata Index: Fast rebuild from shared storage (<100ms)
- Graph Index: Fast rebuild from shared storage (<500ms)
- Storage: Shared blobs, copied only on write
Result: Fork 1M entities in <100ms, with 10-20% memory overhead.
Methods
fork(branch?, options?)
Create an instant clone of your knowledge graph. Changes in the fork don't affect the parent.
import { Brainy, NounType } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// Add some data
await brain.add({ data: 'Alice', type: NounType.Person })
await brain.add({ data: 'Bob', type: NounType.Person })
// Fork instantly (<100ms)
const experiment = await brain.fork('experiment')
// Make changes in the fork
await experiment.add({ data: 'Charlie', type: NounType.Person })
// Original is untouched
console.log((await brain.find({})).length) // 2 (Alice, Bob)
console.log((await experiment.find({})).length) // 3 (Alice, Bob, Charlie)
// Fork with metadata
const backup = await brain.fork('backup-2024-01', {
author: 'admin@example.com',
message: 'Monthly backup',
metadata: { backupType: 'monthly' }
})
listBranches()
List all branches in the workspace.
const branches = await brain.listBranches()
console.log(branches) // ['main', 'experiment', 'backup-2024-01']
getCurrentBranch()
Get the current branch name.
const current = await brain.getCurrentBranch()
console.log(current) // 'main'
checkout(branch)
Switch to a different branch.
// Switch to experiment branch
await brain.checkout('experiment')
console.log(await brain.getCurrentBranch()) // 'experiment'
// Switch back to main
await brain.checkout('main')
commit(options?)
Create a commit with the current state. Commits capture a full snapshot for time-travel.
// Simple commit
const hash = await brain.commit()
// Commit with message
const hash2 = await brain.commit({
message: 'Add user data',
author: 'dev@example.com'
})
// Commit with full state capture (for time-travel)
const snapshot = await brain.commit({
message: 'Before migration',
captureState: true // Serialize ALL entities + relationships
})
// Commit with custom metadata
const tagged = await brain.commit({
message: 'Release v2.0',
metadata: {
version: '2.0.0',
releaseNotes: 'Major update'
}
})
asOf(commitId)
Create a read-only snapshot at a specific commit. This is time-travel - query historical data!
// Make some changes and commit
await brain.add({ data: 'Version 1 data', type: NounType.Document })
const commit1 = await brain.commit({ captureState: true })
// Make more changes
await brain.add({ data: 'Version 2 data', type: NounType.Document })
await brain.commit({ captureState: true })
// Time travel to commit1
const snapshot = await brain.asOf(commit1)
// Query historical state (full Triple Intelligence works!)
const results = await snapshot.find({ query: 'data' })
console.log(results.length) // 1 (only Version 1)
// Get historical relationships
const relations = await snapshot.getRelations()
// IMPORTANT: Close when done to free memory
await snapshot.close()
Snapshots from asOf() are read-only. Attempting to add, update, delete, or commit will throw an error. Always call snapshot.close() when done to free memory.
getHistory(options?)
Get commit history for the current branch.
// Get last 10 commits
const history = await brain.getHistory({ limit: 10 })
history.forEach(commit => {
console.log(`${commit.hash}: ${commit.message}`)
console.log(` by ${commit.author} at ${new Date(commit.timestamp)}`)
})
// Paginate through history
const page2 = await brain.getHistory({ limit: 10, offset: 10 })
// Filter by author
const myCommits = await brain.getHistory({
author: 'dev@example.com'
})
streamHistory(options?)
Stream commit history for large histories (memory-efficient).
// Stream through thousands of commits
for await (const commit of brain.streamHistory({ limit: 10000 })) {
console.log(`${commit.timestamp}: ${commit.message}`)
}
// Stream with time filter
for await (const commit of brain.streamHistory({
since: Date.now() - 86400000 // Last 24 hours
})) {
// Process commit
}
deleteBranch(branch)
Delete a branch.
await brain.deleteBranch('old-experiment')
Use Cases
Safe Migrations
async function safeMigration(brain, migrationFn) {
// Create a commit before migration
const beforeCommit = await brain.commit({
message: 'Pre-migration snapshot',
captureState: true
})
try {
// Run migration
await migrationFn(brain)
// Commit after migration
await brain.commit({
message: 'Post-migration',
captureState: true
})
console.log('Migration successful!')
} catch (error) {
// Restore to pre-migration state
const snapshot = await brain.asOf(beforeCommit)
console.log('Migration failed, state preserved at:', beforeCommit)
throw error
}
}
A/B Testing Features
// Create branches for A/B testing
const branchA = await brain.fork('feature-a')
const branchB = await brain.fork('feature-b')
// Apply different configurations
await branchA.add({ data: 'Config A', type: NounType.Document })
await branchB.add({ data: 'Config B', type: NounType.Document })
// Route users to different branches
function getBrainForUser(userId) {
return userId % 2 === 0 ? branchA : branchB
}
Audit Trail
async function auditedUpdate(brain, entityId, changes, userId) {
// Get state before change
const before = await brain.get(entityId)
// Make the change
await brain.update({ id: entityId, ...changes })
// Commit with audit info
await brain.commit({
message: `Updated ${entityId}`,
author: userId,
captureState: true,
metadata: {
entityId,
action: 'update',
changes: Object.keys(changes),
timestamp: Date.now()
}
})
}
// Query audit history
async function getEntityAuditTrail(brain, entityId) {
const history = await brain.getHistory()
return history.filter(c => c.metadata?.entityId === entityId)
}
Performance Characteristics
| Operation | Performance |
|---|---|
fork() |
<100ms @ 10K entities, <500ms @ 100K |
commit() |
~50ms without captureState |
commit({ captureState: true }) |
~150ms @ 1K entities, ~8s @ 1M entities |
asOf() |
~200ms @ 10K entities (rebuilds indexes) |
| Memory overhead (fork) | 10-20% (shared storage) |
See Also
- Versioning API - Entity-level version control
- Storage Adapters - Configure storage for branching