This is the builder's guide.
Use it when you are writing an app, an agent workflow, or a local-first tool on top of git-warp and you want the main patterns without reading a substrate manual.
- If you are brand new, start with Getting Started.
- If you want the public read model, use Readings And Optics.
- If you want exhaustive method-by-method detail, use the API Reference.
- If you want replay, trust, performance, and substrate internals, use the Advanced Guide.
- If you want terminal workflows, use the CLI Guide.
- If you want the canonical meaning of core nouns like
Worldline,Observer,Aperture, orCoordinate, use GLOSSARY.md.
The most important thing to understand is state before methods.
openWarpGraph()returns a frozen capability bag — the root you build on.- Capabilities are organized into namespaces:
graph.patches,graph.query,graph.strands, etc. - A
Worldlineis a pinned read coordinate. - An
Aperturedefines what is visible. - An
Observeris a filtered read-only view through that aperture. - A
Strandis a speculative write lane branched from an observation.
If you understand those nouns, the rest of the API becomes much easier to reason about.
Backward compatibility: the legacy
open()entry points still work but are deprecated and will be removed in v18. New code should useopenWarpGraph().
import { openWarpGraph, GitGraphAdapter } from '@git-stunts/git-warp';
import GitPlumbing from '@git-stunts/plumbing';
const plumbing = new GitPlumbing({ cwd: './team-repo' });
const persistence = new GitGraphAdapter({ plumbing });
const graph = await openWarpGraph({
persistence,
graphName: 'team',
writerId: 'alice',
});
// graph is the frozen capability bag for this graphUse graph.patches.patch(...) for normal live writes.
const patchSha = await graph.patches.patch((p) => {
p.addNode('task:auth')
.setProperty('task:auth', 'title', 'Implement OAuth2')
.setProperty('task:auth', 'status', 'in-progress');
});
// patchSha = 'abc123...'This commits one atomic WARP patch after the callback finishes. It updates refs/warp/<graph>/writers/<writerId>. It does not touch your normal Git worktree or create a source-tree commit on the current branch.
Use the writer API when you want a multi-step session before committing.
const writer = await graph.patches.writer();
const session = await writer.beginPatch();
session.addNode('task:review');
session.setProperty('task:review', 'status', 'todo');
session.addEdge('task:review', 'task:auth', 'depends-on');
const patchSha = await session.commit();
// patchSha = 'def456...'Nothing is written until session.commit() runs.
Use a Strand when you want reviewable or transferable work that should not land in live truth yet.
const strand = await graph.strands.createStrand({
strandId: 'review-auth',
owner: 'alice',
scope: 'OAuth review',
});
// strand = { strandId: 'review-auth', ... }
await graph.strands.patchStrand('review-auth', (p) => {
p.setProperty('task:auth', 'status', 'ready-for-review');
});
const reviewLane = graph.query.worldline({
source: { kind: 'strand', strandId: 'review-auth' },
});
// reviewLane is a Worldline pinned to the strand overlayUse strands for speculative work. Use ordinary patches for live truth.
For the deeper substrate story behind strands, braids, and transfer planning, use Advanced Guide -> Strands and braids.
Start from a worldline when you want stable application reads.
const worldline = graph.query.worldline();
const task = await worldline.getNodeProps('task:auth');
// { title: 'Implement OAuth2', status: 'in-progress' }Add an observer when the caller should not see everything.
const userAperture = {
match: ['user:*', 'task:*'],
redact: ['email', 'ssn'],
};
const view = await worldline.observer('public-users', userAperture);
const users = await view.query().match('user:*').run();
// users = {
// stateHash: 'abc123...',
// nodes: [
// { id: 'user:alice', props: { name: 'Alice', role: 'lead' } },
// ],
// }Pin an explicit coordinate when you need to ask what the graph looked like earlier.
const historical = graph.query.worldline({
source: {
kind: 'coordinate',
frontier: { alice: 'patch-tip-sha' },
ceiling: 12,
},
});
const taskAtTick12 = await historical.getNodeProps('task:auth');
// { title: 'Implement OAuth2', status: 'todo' }Read a strand through the same worldline abstraction you use for live truth.
const reviewLane = graph.query.worldline({
source: { kind: 'strand', strandId: 'review-auth' },
});
const reviewTask = await reviewLane.getNodeProps('task:auth');
// { title: 'Implement OAuth2', status: 'ready-for-review' }Use one canonical graph example when you are learning the query and traversal surface:
flowchart TD
epic["epic:auth"]
task1["task:oauth<br />status=in-progress<br />points=5"]
task2["task:review<br />status=todo<br />points=2"]
task3["task:docs<br />status=todo<br />points=1"]
user1["user:alice"]
user2["user:bob"]
epic -->|"contains"| task1
epic -->|"contains"| task2
epic -->|"contains"| task3
task1 -->|"assigned-to"| user2
task2 -->|"assigned-to"| user1
task2 -->|"depends-on"| task1
task3 -->|"depends-on"| task2
const tasks = await worldline.query()
.match('task:*')
.run();
// tasks = {
// stateHash: 'abc123...',
// nodes: [
// { id: 'task:oauth', props: { status: 'in-progress', points: 5 } },
// { id: 'task:review', props: { status: 'todo', points: 2 } },
// { id: 'task:docs', props: { status: 'todo', points: 1 } },
// ],
// }const downstream = await worldline.query()
.match('epic:auth')
.outgoing('contains', { depth: [1, 2] })
.run();
// downstream = {
// stateHash: 'abc123...',
// nodes: [
// { id: 'task:oauth', props: { status: 'in-progress', points: 5 } },
// { id: 'task:review', props: { status: 'todo', points: 2 } },
// { id: 'task:docs', props: { status: 'todo', points: 1 } },
// { id: 'user:bob', props: {} },
// { id: 'user:alice', props: {} },
// ],
// }const summary = await worldline.query()
.match('task:*')
.where({ status: 'todo' })
.aggregate({ count: true, sum: 'props.points', avg: 'props.points' })
.run();
// summary = {
// stateHash: 'abc123...',
// count: 2,
// sum: 3,
// avg: 1.5,
// }const dependencyPath = await worldline.traverse.shortestPath('task:docs', 'task:oauth', {
dir: 'out',
labelFilter: 'depends-on',
});
// dependencyPath = {
// found: true,
// path: ['task:docs', 'task:review', 'task:oauth'],
// length: 2,
// }For the exhaustive query surface, use the API Reference.
The simplest sync is Git push and pull plus the WARP refspecs for your graph:
git fetch origin 'refs/warp/team/*:refs/warp/team/*'
git push origin 'refs/warp/team/*:refs/warp/team/*'Automate those refspecs in team tooling or Git config once the workflow is established.
The easiest way to understand CRDT behavior is to look at outcomes, not theory.
| Alice writes | Bob writes | Outcome |
|---|---|---|
add node task:auth |
add node task:auth |
one visible node; duplicate adds converge |
set task:auth.status = "todo" |
set task:auth.status = "done" |
one winning value by Lamport order, then writer tie-break |
add edge task:review -> task:auth |
remove same edge without seeing add | concurrent add wins |
| remove node after observing current edge set | set property on removed node concurrently | tombstoned node stays hidden in live view |
The inspection APIs are valid tools here. What you should avoid is rebuilding your own graph engine above git-warp when the substrate already knows how to replay, query, and traverse.
If you know a write was superseded and need the reason, inspect the provenance for the entity and load the contributing patches.
const patchShas = await graph.provenance.patchesFor('task:auth');
for (const patchSha of patchShas) {
const patch = await graph.provenance.loadPatchBySha(patchSha);
console.log(patchSha, patch.ops.length);
}Use this pattern when you need to explain a lost race or build higher-level conflict UX. For the deeper replay and provenance model behind receipts, use Advanced Guide -> How replay converges. For the app-facing read contract, use Readings And Optics.
Reach for individual capability namespaces when you intentionally need:
graph.query— live and pinned reads through worldlines, observers, traversal, and query buildersgraph.provenance— provenance and patch inspection, backward-cone tracinggraph.comparison— coordinate comparison and transfer planninggraph.checkpoint— checkpoint creation, GC metricsgraph.sync— programmatic sync, serve endpointsgraph.subscriptions— reactive push-based change notification
What to avoid is not the inspection API itself. The thing to avoid is exporting that data into a second app-local graph or writing your own traversal/query semantics above the substrate.
- Readings And Optics: public read model and app-facing read patterns
- API Reference: exhaustive methods, flags, and examples
- Advanced Guide: patch anatomy, replay, trust, GC, and performance
- CLI Guide: operator workflows and live-repo inspection
- Conceptual Overview: the WARP mental model and Git substrate story