Many agents write at once
Tens of agent processes commit into one repository on one host. Object writes take no lock, metadata writes queue in the kernel for one lock, and refs move only by compare-and-swap.
Many writers, one store, one JSON envelope per command. SwarmOps is fully embedded, written in Rust: one program, no daemon, no external database, for 10 to 50 agent processes committing to one repository on one host.
$ so cat main:src/lib.rs {"ok":true,"op":"read_file","api":1, "result":{"blob":"f5f5…","size":14, "kind":"file","encoding":"utf8", "content":"pub fn f() {}\n","truncated":false}, "meta":{…,"resolved":{"main":"014c…"},…}} $ so commit -m "fix" --changes changes.json {"ok":false,"op":"commit","api":1, "error":{"code":"E_IDENTITY_REQUIRED","message":"…", "retryable":false,"exit":5,"details":{}, "hint":"pass --as <family>/<instance> …"},"meta":{…}}
Why SwarmOps
It needs other things from source control. SwarmOps is built around three of them.
Tens of agent processes commit into one repository on one host. Object writes take no lock, metadata writes queue in the kernel for one lock, and refs move only by compare-and-swap.
Every command prints one JSON envelope: a stable E_* code, a fixed exit code, whether retrying can help, machine-readable details and a hint, often the next command to run.
A commit returns only once its journal frame is on disk, a crash leaves no stale lock, and the next writer repairs what a crash cut short. Crash sweeps, injected I/O errors and simulated power losses test it through the real binary.
From just perf after M1's last merge (2026-10-16), with the writer lock that ships, on one busy development host (WSL2, ext4 on a virtual disk, 32 threads; load 14.6–16.4). docs/perf.md has every run, with its spread and load. No git baseline has been measured, and a side-by-side benchmark against git worktrees is not on the roadmap.
Inside the store
Every operation is a library function: swarmops-repo does the work, swarmops-ops exposes it as the StoreOps operations, and the binary parses one command line, calls one operation and prints its envelope.
swarmops-cliswarmops and so: the commands, and swarmops mcpswarmops-apithe contract: request and response types, E_* codes, the envelopeswarmops-opsLocalOps: the operations over a local storeswarmops-gitgit import and export: git's bytes, through gixswarmops-repothe repository engineswarmops-mergetree and line diffs and the three-way merge; no I/Oswarmops-storeobject files and the metadata storeswarmops-coreids, paths and the canonical codec; no I/Ostore/ ├─ FORMAT # the layout version, written last by init ├─ REPO # the repository's id and name ├─ objects/ # one self-verifying file per blob, │ ├─ 00/ … ff/ # tree and changeset: 256 fan-outs │ └─ tmp/ # staged, synced, then linked └─ meta/ ├─ journal.log # append-only, CRC-framed: the truth ├─ journal.off # each frame's offset ├─ journal.ckpt # how far it is materialized ├─ idx/ # the changeset index: its columns, ├─ parents.log # each changeset's parents, ├─ sorted/ # and its sorted views ├─ derived/ # derived values: a cache, never synced ├─ external_ids/ # git commit ids, imported and exported ├─ refs/ # one file per ref, moved by CAS └─ lock # one flock for every writer
SwarmOps and git
SwarmOps is not a general-purpose git replacement: it imports from git (M3) and exports to it (M8), so today's pipelines keep running. Where the two compare, this is where it stands.
| Dimension | SwarmOps today | Against git |
|---|---|---|
| Hash | BLAKE3-256 ids with the object's kind and layout version hashed in; every read re-hashes. | Ahead of git's default SHA-1: 128-bit collision resistance against at most 80 bits, by cryptanalysis, not by benchmark. Parity with git's opt-in SHA-256. |
| Order of writes | Every commit and ref move is one frame of one journal, under one lock: a single global order. | Ahead by design |
| Stale locks | The writer lock is an flock, released by the kernel when its holder dies. | Ahead by design A git process killed with SIGKILL while it holds a .lock file leaves the file behind, to be removed by hand. |
| Durability | Full fsync is the only mode: an object is synced before a journal frame names it, and a commit returns after its frame's fdatasync. | Ahead by design Git's defaults (2.43) sync packs, pack metadata and the commit graph, not loose objects, refs or the index. |
| Machine-readable contract | One JSON envelope per command, with a stable E_* code, an exit code, retryable, details and a hint; a JSON Schema for every request and response. | Ahead by design for errors, which git reports as free text on stderr. Git's plumbing and --porcelain formats narrow the gap for output. Known gap: four codes are not raised yet. |
| Commits from many writers | Concurrent commits all succeed, short of a lock wait past 10 s or, since M1.6, a snapshot that meets a new commit in each of its 32 rounds (the retryable E_BUSY). | Parity with agents in separate git worktrees, on lost commits and lock failures. |
| One ref by compare-and-swap | ref set --expect or --create, ref delete --expect; setting a ref to the target it holds is a replay. Since M1.6, commit --advance moves a ref in the commit's own transaction, or records nothing. | Parity with git update-ref, plus the replay. |
| Reading a path at a revision | cat REV:PATH and ls REV[:PATH], without a checkout. | Parity with git show and git ls-tree. |
| Offline, on one host | No network code: every operation runs against the host's own store. | Parity |
| Throughput of many writers | Writers take turns, each commit holding the store-wide lock across two journal fdatasyncs, and two fsyncs more when it moves a ref. | Behind by design Agents in git worktrees serialize nothing, and git's defaults sync none of a commit's files. |
| Refs beyond one compare-and-swap | main and release/<name> only, moved one per call. | Behind No multi-ref transactions, tags, free-form branch names or symbolic refs. |
| Breadth of reads | One file or one directory per call; since M2, a file from a byte offset or by a range of lines, and a directory with the directories below it to a depth, each file with its size; sparse workspaces come in M5. | Behind Git has batch reads, git grep <tree> and sparse checkouts. |
| Storage on disk | One file per object, zstd-compressed when that makes it smaller; no packs, no delta compression. | Behind by design in bytes and in files, against git's packs. |
| Conflicts | Since M3, E_GIT_IMPORT_DIVERGED signals a conflict when a git import's target and the git history have diverged. land merges three-way without a checkout and reports a conflict as E_CONFLICT with a JSON report: each conflicting file's kind, its hunks and the target's changeset and agent that last changed it. land --resolve resolves them without a checkout, path by path, against the report's theirs. | Parity with git merge-tree --write-tree, which merges without a checkout and reports typed conflicts. Resolving without a checkout is one request here, where git takes plumbing (git hash-object, a temporary index, git commit-tree). |
| Review | Nothing today; change requests, approvals and a landing gate come in M7. | Behind today Git ships patch-review tools, and Gerrit and GitHub add review on top. M7 is not yet comparable |
| Sync between hosts | Since M8, through a git remote, one writing host at a time: the writer exports, then pushes with git, and the others fetch with git, then import; two hosts that both write stop rather than merge. Offline-first fetch and push against one upstream come in M11. | Behind Git clones, fetches and pushes over any transport, and bundles without one. |
| Ecosystem | The CLI, swarmops schema, an MCP server, git import since M3 and git export since M8. No hooks, signing, submodules or LFS. | Behind after v1 too: git parity is not a v1 goal, and git has MCP servers as well. |
| Maturity | Pre-release: v0.1.0 is the first tag, and the first spec dates from 2026-10-15. | Behind Git has hosted itself since 2005. |
| Landing | land merges an agent's change onto main and publishes it as one changeset in one transaction, merging again and retrying by itself when main moves; a retry with the same key replays it. The landing model chosen by measurement comes after the pilot. | Ahead by design of git alone, whose push refuses a moved branch and leaves the fetch, the merge and the retry to the pusher; not yet measured under a swarm's load. |
Every dimension of the README's comparison, which gives each verdict's evidence. Ahead: built, with evidence. Ahead or behind by design: judged from the design and the code, never measured against git. Not yet comparable: not built yet. No git baseline has been measured; the verdicts come from the design, the code and git's own documentation and source.
Quick start
Linux, x86_64 or aarch64, glibc 2.34 or newer. A store must live on a native Linux filesystem. so is the short name of swarmops: the same program.
curl -fsSL https://get.swarmops.cc/install.sh | bash # swarmops and so, into ~/.local/bin
export SWARMOPS_REPO=~/.local/state/swarmops/repos/acme # or --repo <store> on each command export SWARMOPS_AGENT=agent/worker-1 # or --as <family>/<instance> so init "$SWARMOPS_REPO" --name acme printf '#!/bin/sh\necho hello\n' > run.sh # the client file the second change reads cat > changes.json <<'EOF' [{"path": "src/lib.rs", "op": "put", "content": {"inline": "pub fn f() {}\n"}}, {"path": "run.sh", "op": "put", "content": {"file": "./run.sh"}, "mode": "exec"}] EOF so commit -m "first" --changes changes.json --advance main # creates main at the new changeset so cat main:src/lib.rs so ls main:src so show main so log -n 20 so verify --deep
A change list is a JSON array of puts and deletes; a put's content is {"inline": …}, {"base64": …}, {"file": …}, a file the CLI reads, or {"blob": …}, a blob stored before with so blob put. --advance main moves main to the new changeset in the commit's own transaction, only if main still names the commit's parent (for this first commit, only if main does not exist yet); otherwise the commit records nothing and fails with E_CAS_MISMATCH. Every command, revision and exit code is in the README.
MCP
swarmops mcp serves the store to an agent on stdin and stdout. Each tool's arguments are the operation's request, and its result is the envelope the CLI prints. The server speaks MCP 2026-07-28, and 2025-11-25 and 2025-06-18 to clients that open with initialize.
claude mcp add swarmops -- \ so mcp --repo <store> --as <agent>
{"mcpServers": {"swarmops": {
"command": "swarmops",
"args": ["mcp", "--repo", "/path/to/store"],
"env": {"SWARMOPS_AGENT": "${SWARMOPS_AGENT:-}"}}}}
Roadmap
Pre-release: v0.1.0 is the first tag; no crate is published, and every crate of the workspace is at 0.1.0. The milestones follow the design specs.
verify findings, and commit --advance, the commit primitive M4's landing reusesUsable by one agent from M1; by a swarm, landing through MCP, from M4; on a git repository and back, from M8, in the first release; with workspaces from M5; with review from M7; as the complete v1 at M9; across hosts, offline-first, from M11.