Multi-agent orchestration using the filesystem.
Directories are stages. Files are state. Symlinks are data flow.
❓ The Problem · 🧠 Why · ⚡ Quick Start · ✨ Highlights · 🕸️ Contribution Graph · 🖥️ CLI · 🏗️ Architecture
AI agent runs don't remember each other. Every session re-learns which configs diverge, re-walks dead-end branches, and re-derives results a previous run already proved — and the usual fix, bolting a database and message broker onto the pipeline, replaces one kind of infrastructure with two more.
Zerochain solves this by making the filesystem the shared memory. Workflow state is plain directories and files, so agents hand off work with nothing more exotic than a write. On top of that, every run is recorded in an append-only, content-addressed contribution graph — what was tried, what worked, what failed, and who reproduced what — with each record committed automatically into jujutsu (jj) version control. Agents and humans then build on prior runs instead of rediscovering them, every claimed result traces back to the exact artifacts and lineage that produced it, and the full history can be audited, replayed, or rolled back like any repository: zerochain graph for the research view, jj log for the audit view.
Zerochain implements multi-agent AI workflows as files and folders — no databases, no brokers, no network stacks. Just the filesystem, content-addressed storage, and async Rust.
Every AI session starts from scratch. It re-learns which config diverged, re-walks down dead-end branches, and never benefits from what yesterday's run proved. Run thirteen agents and you get thirteen amnesiacs: more compute, same duplicated search.
NVIDIA's Agora: Git as Shared Memory for Collective AutoResearch (Zhang et al., 2026) showed the fix. Thirteen coding agents sharing one append-only research DAG in Git — no assigned tasks, no central planner — published 1,703 contributions, reproduced one another's results 165 times, and found a weight-transfer recipe whose 145-commit ancestry spans 15 of their accounts. Shared memory, not smarter agents, was the multiplier.
Zerochain is that idea, filesystem-native. A workspace keeps an append-only graph of typed contributions — setup, result, insight, hypothesis, verification, report — where every record is a content-addressed markdown file naming the records it builds on. Workflows chain to prior runs instead of starting cold; verifications supersede instead of arguing; and because the workspace lives under jujutsu (jj), every contribution is committed for you. The jj operation log becomes the lab notebook: jj log reviews the trail, jj undo rewinds a bad turn, jj op log shows every operation ever — no audit database, no extra infrastructure.
zerochain init --name run-2 --parent c-9f3a21c7d4e8b601 # build on a prior run
zerochain run run-2 # stages publish results into the graph
zerochain graph --view leaders # see the frontier
jj log # the same history, as commits# Install (requires Rust nightly 1.90+)
# Recommended: clone with jj to see the audit-trail philosophy in action
jj git clone https://github.com/awdemos/zerochain.git
cd zerochain
# Or clone with git (jj works on top of Git — you can add it later)
# git clone --depth 1 https://github.com/awdemos/zerochain.git
# cd zerochain
cargo build --release --workspace
# Configure
export OPENAI_API_KEY="sk-..."
# Create and run a workflow
zerochain init --name my-task
zerochain run my-taskThat's it. zerochain creates a stage directory, calls the LLM, writes the result to output/result.md — and commits the run to the workspace graph and jj history.
| 📁 Filesystem-native | No databases needed. Directories are stages, files are state. CLI or HTTP daemon. |
| 🕸️ Collective memory | Workspace-level, append-only contribution graph inspired by Agora. Every run chains to prior runs; verifications supersede; everything is content-addressed and lands as a jj commit. |
| 🏛️ Auditable | Because state is files, every mutation is a file operation. jj underneath gives an immutable, queryable audit trail — jj op log, jj undo — with zero extra infrastructure. |
| 🔒 Content-addressed | Blake3 hashing. Every artifact identified by its content hash. |
| 💥 Crash-safe | Atomic writes, PID-based stale lock detection, automatic recovery. |
| 🎯 Deterministic LLM | Config derived from content hash. Same input, same execution. |
| 🔌 Provider-agnostic | Any OpenAI-compatible API — OpenAI, Ollama, Moonshot, and more. |
| 🧬 Lua stage scripts | Optional: configure a stage with CONTEXT.lua instead of YAML frontmatter, and run sandboxed on_validate/on_complete hooks — e.g. skip a stage dynamically based on upstream output. |
| 🦀 Zero unsafe | Pure safe Rust. Async I/O with tokio. Every fallible op returns Result. |
The graph is the heart of zerochain — Agora-style shared memory, implemented as files and recorded in jj.
What gets published. Every zerochain init publishes a setup node. Stages with index_output: true publish result nodes chained to their workflow's lineage, carrying the stage's metric and content-addressed b3: artifact references. Agents publish insight, hypothesis, and verification records mid-run via the contribute, verify, and graph_query tools (list them in a stage's tools: frontmatter). Humans use the CLI or HTTP API.
How it's stored. Each contribution is a typed markdown record under .zerochain/graph/contributions/, content-addressed as c-<hash> and linked by parent edges — every record names what it builds on, so any claim traces back to the exact artifact and lineage that produced it. A verification names exactly one target and a verdict (confirmed/partial/failed); each verifier's newest verdict supersedes their older ones at query time, and both stay in the history. Then jj seals it: every publish is followed by an automatic jj commit, so the DAG is replayable, undoable, and auditable like any other VCS history.
A real session, no staging:
$ zerochain init --name smoke-test
initialized workflow: smoke-test
$ zerochain contribute --type insight --body "bigram priors beat slice-copying" --parent c-aad12a1bb2ebeae0
published contribution: c-2e61ccec49628abb
$ zerochain verify c-aad12a1bb2ebeae0 --verdict confirmed --body "reproduced on this machine"
published verification: c-c0535472edc70aca
$ zerochain graph --view recent
c-c0535472edc70aca verification human:a reproduced on this machine
c-2e61ccec49628abb insight human:a bigram priors beat slice-copying
c-aad12a1bb2ebeae0 setup zerochain/0.2.0 smoke-test
$ cd workspace && jj log --limit 3
○ graph: verification c-c0535472edc70aca
○ graph: insight c-2e61ccec49628abb
○ graph: setup c-aad12a1bb2ebeae0(The graph lives in the workspace's own jj repo, initialized automatically on first zerochain init.)
# Link a new workflow to prior contributions
zerochain init --name run-2 --parent c-9f3a21c7d4e8b601
# Human surfaces (actor recorded as human:$USER)
zerochain contribute --type insight --body "donor ensembling helps" --parent c-9f3a21c7d4e8b601
zerochain verify c-9f3a21c7d4e8b601 --verdict confirmed --body "reproduced on H100"
zerochain graph --view leaders # recent | leaves | open_hypotheses | unverified | negative | leadersThe same graph is exposed over HTTP (GET /v1/graph, POST /v1/graph/contributions, POST /v1/graph/verifications) behind the daemon's bearer auth for non-Rust clients.
Stage outputs can carry a metric via CONTEXT.md frontmatter — metric: {name: bpb, value: 1.899, direction: lower} — which the engine attaches to the auto-captured result node.
Typical CLI workflow: help → init → inspect prompt → run → read result → status → list.
# Initialize a workflow from a Backlog.md task
zerochain init --name my-task --path ./backlog.md
# Run the next pending stage
zerochain run my-task
# Run a specific stage
zerochain run my-task --stage 02_design
# Check workflow status
zerochain status my-task
# List all workflows
zerochain list
# Approve a stage waiting for human review
zerochain approve my-task 03_reviewRun zerochain as a stateless HTTP daemon with full audit trails via jj:
# Build locally
docker build -t zerochaind .
docker run -d \
-p 8080:8080 \
-e OPENAI_API_KEY="sk-..." \
-v zerochain-data:/workspace \
zerochaind
# Or build and push to a registry with Dagger
dagger call publish --registry ttl.sh/$USER-zerochaind:1h| Method | Endpoint | Description |
|---|---|---|
POST |
/v1/workflows |
Initialize workflow |
POST |
/v1/workflows/{id}/run |
Run next pending stage |
GET |
/v1/workflows/{id} |
Workflow status |
GET |
/v1/workflows/{id}/output/{stage} |
Read result |
GET |
/v1/workflows/{id}/subvolumes |
List Btrfs subvolumes (Btrfs-only) |
GET |
/v1/workflows/{id}/export-okf?output=<dir> |
Export workflow as OKF v0.2 bundle |
GET |
/v1/graph |
Query the collective contribution graph (views, tags, actors, metric filters) |
POST |
/v1/graph/contributions |
Publish a contribution (setup/result/insight/hypothesis/report) |
POST |
/v1/graph/verifications |
Publish a reproduction verdict against a target contribution |
Because zerochaind is filesystem-native, every workflow mutation is a file operation. jj op log gives you a complete, immutable timeline of every operation — no audit database, no extra infrastructure. The VCS is the audit log. We use the same jj workflow to develop ZeroChain itself; see CONTRIBUTING.md.
zerochain emits self-describing knowledge concepts for every stage output. Each output/result.md now includes YAML frontmatter (type, generated, status) so downstream tools can trace provenance without parsing zerochain internals.
# Export a completed workflow as a portable OKF bundle
zerochain export-okf my-task --output ./my-task-okfThe bundle contains:
index.md— workflow-level OKF concept with a stage manifestconcepts/*.md— each stage'soutput/result.md(already OKF-wrapped)log.md— human-readable stage status log
Override the default actor string (zerochain/<version>) with:
export ZEROCHAIN_OKF_ACTOR="my-org/1.0"On Btrfs filesystems, zerochain can create each workflow and stage as an isolated subvolume. This enables true zero-copy snapshots and per-stage rollback.
# Workflow root is a subvolume; stages are plain directories inside it.
ZEROCHAIN_BTRFS_SUBVOLUME_MODE=workflow zerochaind
# Workflow root and every stage are independent subvolumes.
ZEROCHAIN_BTRFS_SUBVOLUME_MODE=stage zerochaindThe effective mode is persisted to {workflow_root}/.subvolume-mode when the workflow is created, so the workflow keeps its isolation semantics even if the environment variable changes later. Use GET /v1/workflows/{id}/subvolumes to inspect the subvolumes for a workflow.
Zerochain represents every workflow as an explicit execution graph. Stages are nodes and dataflow/ordering are edges. For the common case, the graph is still derived from NN_name/ directory ordering, but the internal model is typed and testable.
- No hidden ordering rules. Stage dependencies are explicit edges in
zerochain-core/src/graph.rs, not side effects of directory names. - Loops. A stage can be declared as a
Loopnode with a bounded body. Loops terminate when the body emits a control record. - Control records. A stage ends a loop by writing one of these strings on the first line of
output/result.md:zerochain.control.v1.return— loop succeeds with the current iteration's outputzerochain.control.v1.escalate— loop stops and the workflow continues past itzerochain.control.v1.fail— loop failszerochain.control.v1.await— loop pauses for human approval
Loops are not exposed as a separate directory layout yet. They are constructed in code via the WorkflowGraph API. A typical loop looks like this:
use zerochain_core::graph::{WorkflowGraph, LoopExhaustion, ControlOutcome};
use zerochain_core::stage::StageId;
let mut graph = WorkflowGraph::new();
let body = graph.add_stage(StageId::parse("02_review").unwrap());
graph.add_loop(
StageId::parse("03_review_loop").unwrap(),
body,
5,
LoopExhaustion::Fail,
).unwrap();When the 02_review stage writes zerochain.control.v1.return as the first line of output/result.md, the loop ends. If it never returns within five iterations, the loop fails according to LoopExhaustion::Fail.
You can also build arbitrary directed acyclic graphs by adding stages and declaring dependencies explicitly:
let spec = graph.add_stage(StageId::parse("00_spec").unwrap());
let analyze = graph.add_stage(StageId::parse("01_analyze").unwrap());
graph.add_dependency(analyze, spec).unwrap();The actor runtime (zerochain-engine) executes the graph while keeping zerochain's filesystem-native state, symlinks, and per-workflow actor model unchanged.
Stages can be given tools — reusable capabilities the LLM may invoke mid-run, with results fed back into the conversation in a bounded tool loop. Enable them per stage in CONTEXT.md frontmatter:
---
tools: [read_file, write_file, shell, contribute, verify, graph_query]
tool_loop_max_iterations: 8
---| Tool | What it does |
|---|---|
read_file / write_file |
Read and write files inside the workflow workspace |
shell |
Run a shell command |
http |
Make an HTTP request |
memory_store / memory_query |
Store chunks and search vector memory semantically |
contribute / verify / graph_query |
Publish and query collective contribution graph records |
Tools are registered in zerochain-tools; the engine injects workflow context (workspace paths, graph lineage, actor) into every call.
Content-addressed storage. All artifacts stored by Blake3 hash. No filenames matter — content identity is the hash.
Copy-on-write snapshots. Each stage gets a CoW snapshot of the previous stage's output.
Deterministic LLM config. LLMConfig::deterministic() derives a Blake3 seed from the content CID for reproducible execution.
What is an agent? Zerochain does not define a separate Agent abstraction. In this codebase, an agent is a workflow stage: a directory (NN_name/) containing a CONTEXT.md prompt, an input/ directory, and an output/ directory. A multi-agent workflow is simply a pipeline of stages that pass state through the filesystem. Stages can also exchange messages across pods via the optional broker.
| Crate | Purpose |
|---|---|
zerochain-cas |
Blake3 content-addressed storage with atomic writes |
zerochain-fs |
Copy-on-write filesystem, advisory locks, Btrfs subvolumes |
zerochain-llm |
Provider-agnostic LLM backend with profiles |
zerochain-core |
Workflow/stage model, execution graph, Lua config, jj integration, frontmatter (tools, metric), OKF |
zerochain-memory |
Vector memory and semantic search, plus the collective contribution graph (records, store, index, embeddings) |
zerochain-tools |
Tool registry with built-in file, shell, HTTP, memory, and graph tools |
zerochain-broker |
Message broker abstraction for cross-pod agent communication |
zerochain-error |
Shared error types for the workspace |
zerochain-daemon |
CLI binary (zerochain) |
zerochain-server |
HTTP daemon (zerochaind) |
ZeroChain is developed with jj — a version-control system that treats the working copy as a commit and gives you an immutable operation log. We dogfood the same workflow the contribution graph gives your agents:
# See what changed
jj diff
# Create a commit
jj describe -m "feat: add stage isolation"
jj new
# Review the operation log
jj op logWe use Git as the wire protocol (GitHub for issues, PRs, and CI), but jj as the local workflow. You don't need to give up GitHub to get the benefits of jj — they are fully compatible. See CONTRIBUTING.md for the full workflow.
Zerochain uses Dagger for reproducible local CI — no GitHub Actions, no CI YAML drift. The Makefile wraps the Dagger module so you don't have to remember long CLI invocations.
# Run the full pipeline before pushing
make ci
# Individual steps
make lint
make test
make build
make dockerThe underlying Dagger commands (if you prefer them raw):
# Run the full pipeline (lint, test, build)
dagger call all --source=. --progress=plain
# Individual steps
dagger call lint --source=. --progress=plain
dagger call test --source=. --progress=plain
dagger call build --source=.
# Build the zerochaind container image
dagger call docker --source=. -o zerochaind-image.tarThe module mounts cargo cache volumes for incremental builds, so repeated runs are fast. Same source, same pipeline, anywhere Dagger runs.
- Chainguard container execution for stage isolation
- Btrfs copy-on-write snapshots (zero-copy isolation)
- OpenCode TypeScript plugin
- Dagger CI module
- Template registry for common workflow patterns
- Collective contribution graph (Agora-style shared memory)
© 2026 Andrew White · MIT License
