Skip to content

awdemos/zerochain

Repository files navigation

⛓️ zerochain

Build AI Agents with mkdir

Rust License: MIT Zero Unsafe

Multi-agent orchestration using the filesystem.
Directories are stages. Files are state. Symlinks are data flow.

⚡ Quick Start · ✨ Highlights · 🖥️ CLI · 🌐 HTTP API · 🏗️ Architecture


🎯 In One Sentence

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.


✨ Highlights

📁 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.

⚡ Quick Start

# 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-task

That's it. zerochain creates a stage directory, calls the LLM, and writes the result to output/result.md.


🎬 Demo

Typical CLI workflow: help → init → inspect prompt → run → read result → status → list.

Typical zerochain workflow


🖥️ CLI

# 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_review

🌐 Container (zerochaind)

Run 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)

🔍 Audit Trails

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.


🧊 Btrfs Stage Isolation

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 zerochaind

The 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.


🔄 Workflow Graphs and Loops

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.

Why this matters

  • 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 Loop node 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 output
    • zerochain.control.v1.escalate — loop stops and the workflow continues past it
    • zerochain.control.v1.fail — loop fails
    • zerochain.control.v1.await — loop pauses for human approval

Using loops in a workflow

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.


🏗️ Architecture

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 Structure

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)

🔀 Developed with jj

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 log

We 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.


🔄 Local CI with Dagger

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 docker

The 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.tar

The module mounts cargo cache volumes for incremental builds, so repeated runs are fast. Same source, same pipeline, anywhere Dagger runs.


🗺️ Roadmap

  • 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

About

This is a Multi-agent orchestration tool using text files as Agents

Topics

Resources

License

Contributing

Stars

5 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors