The Toolpath format
Three objects. One DAG. Full provenance.
Toolpath defines three object types at decreasing levels of granularity. Every Toolpath JSON document is externally tagged with exactly one top-level key:
{ "Step": { "step": {...}, "change": {...} } }
{ "Path": { "path": {...}, "steps": [...] } }
{ "Graph": { "graph": {...}, "paths": [...] } }
Step
The core primitive. A step records a single change to one or more artifacts by one actor.
Every step has three top-level keys:
| Key | What it holds |
|---|---|
step |
Identity and lineage: id, parents, actor, timestamp |
change |
The actual modifications: artifact URLs mapped to perspectives |
meta |
Everything else: intent, refs, actors, signatures (optional) |
Parents
The step.parents array establishes the DAG:
| Parents | Meaning |
|---|---|
[] or omitted |
Root step (no parents) |
["step-001"] |
Single parent (linear history) |
["step-A", "step-B"] |
Merge (derived from parallel work) |
Example
{
"Step": {
"step": {
"id": "step-003",
"parents": ["step-002"],
"actor": "agent:claude-code",
"timestamp": "2026-01-29T15:30:00Z"
},
"change": {
"src/auth/validator.rs": {
"raw": "@@ -1,5 +1,25 @@\n use std::error::Error;\n+..."
}
},
"meta": {
"intent": "Add email validation to prevent malformed input",
"refs": [{ "rel": "fixes", "href": "issue://github/repo/issues/42" }]
}
}
}
Path
A path collects steps and provides a root context. Think of it as a PR, a coding session, or a branch.
| Key | What it holds |
|---|---|
path |
Identity, base context, and head reference |
steps |
Array of step objects (the full DAG, including dead ends) |
meta |
Path-level metadata: title, actors, signatures (optional) |
Base context
The path.base anchors the path to a specific state:
{
"base": {
"uri": "github:myorg/myrepo",
"ref": "abc123def456"
}
}
| URI scheme | Meaning |
|---|---|
github:org/repo |
GitHub repository |
file:///path |
Local filesystem |
toolpath:path-id/step-id |
Branch from another path's step |
Dead ends
Dead ends are implicit. A step is a dead end if it has no descendants leading to path.head. No explicit marking required — the DAG structure determines it.
active_steps = ancestors(path.head)
dead_ends = all_steps - active_steps
Example
{
"Path": {
"path": {
"id": "path-pr-42",
"base": {
"uri": "github:myorg/myrepo",
"ref": "abc123def456"
},
"head": "step-004"
},
"steps": [
{
"step": { "id": "step-001", "actor": "human:alex", "timestamp": "..." },
"change": { "src/main.rs": { "raw": "..." } },
"meta": { "intent": "Initial change" }
},
{
"step": {
"id": "step-002a",
"parents": ["step-001"],
"actor": "agent:claude-code",
"timestamp": "..."
},
"change": { "src/validator.rs": { "raw": "..." } },
"meta": { "intent": "Regex approach (abandoned)" }
},
{
"step": {
"id": "step-002",
"parents": ["step-001"],
"actor": "agent:claude-code",
"timestamp": "..."
},
"change": { "src/validator.rs": { "raw": "..." } },
"meta": { "intent": "Custom error type approach" }
},
{
"step": {
"id": "step-003",
"parents": ["step-002"],
"actor": "tool:rustfmt",
"timestamp": "..."
},
"change": { "src/validator.rs": { "raw": "..." } },
"meta": { "intent": "Auto-format" }
},
{
"step": {
"id": "step-004",
"parents": ["step-003"],
"actor": "human:alex",
"timestamp": "..."
},
"change": { "src/validator.rs": { "raw": "..." } },
"meta": { "intent": "Refine error messages" }
}
],
"meta": {
"title": "Add email validation"
}
}
}
Step step-002a is a dead end: it forks from step-001 but has no descendants leading to the head (step-004).
Graph
A graph collects related paths. Think of it as a release, a sprint, or a project.
| Key | What it holds |
|---|---|
graph |
Identity (id) |
paths |
Array of inline Path objects or $ref references |
meta |
Graph-level metadata: title, actors, signatures (optional) |
Paths can be inline or referenced externally:
{
"Graph": {
"graph": { "id": "graph-release-v2" },
"paths": [
{ "path": {...}, "steps": [...] },
{ "$ref": "https://archive.example.com/toolpath/path-44.json" }
],
"meta": { "title": "Release v2.0" }
}
}
Artifacts
Artifact keys in change are URLs. Bare paths are relative to path.base:
| Key format | Interpretation |
|---|---|
src/foo.rs |
Relative to path.base root |
file:///abs/path |
Absolute file path |
https://... |
Web resource |
s3://... |
S3 object |
Change perspectives
Each artifact maps to one or more perspectives on the modification:
| Perspective | Description | Example |
|---|---|---|
raw |
Unified Diff (same as diff -u / git) |
@@ -1,5 +1,10 @@\n... |
structural |
Language-aware AST operations | {"type": "rust.add_items", ...} |
Consumers use the perspective they understand. A dumb text tool uses raw. An IDE might use structural.
Actors
Actors follow the pattern type:name:
human:alex
agent:claude-code
tool:rustfmt/1.7.0
ci:github-actions/workflow-123
Full actor definitions (identity, keys) live in meta.actors.
Signatures
Toolpath supports multi-party, scoped cryptographic signatures using JCS (RFC 8785) canonicalization:
| Scope | What it attests |
|---|---|
author |
"I authored this change" |
reviewer |
"I reviewed and approved this" |
witness |
"I observed this happened" |
ci |
"CI verified this" |
release |
"This is an official release" |
Full specification
The complete format specification is in the RFC. A JSON Schema is available at schema/toolpath.schema.json.