Multi-agent orchestration using the filesystem.
Directories are stages. Files are state. Symlinks are data flow.
⚡ Quick Start · ✨ Highlights · 🖥️ CLI · 🌐 HTTP API · 🏗️ Architecture
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.
| 📁 Filesystem-native | No databases needed. Directories are stages, files are state. CLI or HTTP daemon. |
| 🔒 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. |
| 🦀 Zero unsafe | Pure safe Rust. Async I/O with tokio. Every fallible op returns Result. |
| 🏛️ Auditable | Because state is files, every mutation is a file operation. Layer jj underneath and you get an immutable, queryable audit trail for free — with jj op log, jj undo, and zero extra infrastructure. |
| 🧬 Self-modifying workflows | Optional Lua config engine. Stages can insert/remove subsequent stages at runtime. |
# 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, and writes the result to output/result.md.
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) |
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.
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 now 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.
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, stage markers |
zerochain-llm |
Provider-agnostic LLM backend with profiles |
zerochain-core |
Workflow engine, Lua config, Backlog.md parsing, execution graph |
zerochain-daemon |
CLI binary |
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 we recommend for audit trails:
# 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
© 2026 Andrew White · MIT License
