Toolpath at a glance
A story is made up of many paths, one step at a time.
A complete Toolpath document is small enough to read in one breath:
{
"graph": { "id": "graph-step-001" },
"paths": [
{
"path": { "id": "path-step-001", "head": "step-001" },
"steps": [
{
"step": {
"id": "step-001",
"actor": "human:alex",
"timestamp": "2026-01-29T10:00:00Z"
},
"change": {
"src/main.rs": {
"raw": "@@ -12,1 +12,1 @@\n- println!(\"Hello world\");\n+ println!(\"Hello, world!\");"
}
}
}
]
}
]
}
That's the canonical fixture step-01-minimal.json — one author, one timestamp, one file changed, one diff. Every Toolpath document looks like this. The objects nest the same way. Bigger documents just hold more of them.
The shape
Every document is a Graph. A Graph holds Paths. A Path holds Steps. A Step is one change.
| Layer | Holds | Represents |
|---|---|---|
graph |
paths[] |
A collection of stories — a release, a project, a bundle |
path |
steps[] |
One story — a PR, a coding session, a branch |
step |
change{} |
A single change to one or more artifacts |
Step and Path are inner types. They appear inside graph.paths and path.steps, but they're never the JSON root on their own — what used to be a bare Step or Path is now a single-path Graph. One root type, one parser path.
A step's change maps artifact URLs to perspectives — a unified diff under raw, a structural AST operation under structural, or both. The meta object is optional at every level: minimal documents need only step and change.
A real example
The lead example shows the shape. To see what Toolpath looks like when actual work has happened, open the exploration fixture in the visualizer. It's a single Path with seven steps and the four DAG features that recur everywhere:
path.baseanchors the document to a starting commit. Bare paths inchange(src/main.rs) are relative to that base.path.headpoints at the current tip —step-004. Walking back from the head gives the active history; everything else is exploration.step-002aandstep-002bboth branch fromstep-001— that's a fork. The Path keeps both, even though only one becomes part of the active history.step-002ais also a dead end — nothing on its descendant chain reachespath.head. Dead ends aren't marked anywhere in the document; they fall out structurally asall_steps − ancestors(head).step-004lists two parents (step-003b,step-003c) — that's a merge of two parallel branches.
→ Open it in the visualizer (it's the default example) and the structure clicks immediately.
File extensions
| Extension | Shape | Use it when |
|---|---|---|
.path.json |
Graph (canonical) | Sealed documents — PRs, releases, archived sessions |
.path.jsonl |
Graph (streaming) | Live capture — one Path appended line-by-line as work happens |
A .path.jsonl stream encodes exactly one inline Path and seals to a single-path Graph at the file boundary. Multi-path graphs and $ref-only entries can't be represented in JSONL — those require canonical .path.json.
Where to next
- Full specification — the normative details: signatures, perspectives, the
metaobject, ID uniqueness, JSONL streaming. - JSON Schema — authoritative shape; what
path validatechecks against. - Examples — the fixtures used throughout these docs and tested in CI.
- CLI —
path import,path render,path query,path validate. - Visualizer — paste a document, see the DAG.
- Design notes — why the format is shaped the way it is.