
zoanthrope
io.github.spuentespPublished by GitHub account spuentespv0.1.6Updated Oct 24, 2026
Coordination relay for agent swarms: file claims, presence, postboard intentions, leases, audit.
Overview
A local coordination relay that lets a swarm of coding agents claim files, announce intentions and see who is working on what.
- What you can do
- Claim, release and list file claims with read or edit intent, refused all-or-nothing on conflict with the holder named and filesystem leases taken against clobbering. List claims and active agents, register an agent and look up its own identity, claims and posts. Post, list, update and remove public postboard intentions with open, taken, done or dropped status. Rejoin after a restart to re-assert presence and own claims and see what changed. Read a capped audit log of coordination events.
- When to use it
- Use it when several agents or sessions edit the same workspace and you want to prevent overlapping writes, or when you need a readable trail of who promised what. It is aimed at multi-agent coding setups rather than single-agent use.
- Requirements
- Runs as a local stdio process, launched with npx from the npm package; the README also describes building from source with Cargo. It keeps state in a .zoanthrope directory at the workspace root, found by walking up to the git toplevel unless a workspace is given. No authentication or environment variables are declared in the manifest; the README mentions optional settings such as ZOANTHROPE_AGENT for hook wiring.
Installation
In SourceWeft
- Open zoanthrope in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
zoanthrope
The Tyranid creature that relays the Hive Mind's will to the swarm — zoanthrope is the coordination relay: who works where, what was promised, what happened.
A swarm coordination MCP server. zoanthrope answers one question — who is working on what? — for a workspace of collaborating agents. File claims with read/edit intents, agent presence, a public postboard of intentions, per-path filesystem leases against clobbering, a rejoin path for agents that went away and came back, and a capped audit ring. It keeps a team of agents out of each other's way and leaves a trail of who promised what.
What it is
- a small MCP server (
zoanthrope mcp) over stdio JSON-RPC - a hook-facing CLI (
zoanthrope claim/release/rejoin/status) - a state registry at
.zoanthrope/state.jsonplus filesystem leases at.zoanthrope/locks/— zero daemons, nothing to install and keep alive
Tool surface
All tools take and return JSON objects. Success bodies carry
"ok": true; a refused claim is a successful call whose body has
"ok": false plus the conflict shape.
The conflict shape
The heart of the product: a claim that clashes with another agent's recorded intent is refused all-or-nothing and says exactly who is in the way:
Claim semantics
A claim carries an intent (read or edit):
- an edit claim conflicts with anything another agent holds on the path — exactly one writer at a time;
- a read claim conflicts with another agent's edit, but readers co-hold happily with each other;
- an agent's recorded intent on a path is the strongest it holds — a later weaker claim never hides a stronger one. Only release (then re-claim) downgrades.
Claims are all-or-nothing per call: one conflict in a batch means
nothing is granted. A holder silent past the presence TTL is stale,
its claims are held_by_stale, and they still block — claims are
never auto-released; forcing one open is a policy this product did not
make.
A claim also binds the file's content: at claim (or re-claim) time
it records a content fingerprint — mtime+size always, plus sha256
for files ≤ 256 KiB, so same-size rewrites on coarse filesystems are
still caught. Every guarded write (the hook family) checks the file
against the claim's fingerprint first: if the file changed under the
claim — an out-of-band append, a generator run — the write is refused
with a stale-claim denial (holder is self) telling the agent to
re-read and re-claim. Re-claiming re-binds the fingerprint to the
current content; release drops it. No deliberate takeover exists: a
claim lock is a content promise, not just a writer lock.
A claim can also carry caller stamps: opaque {name, value} pairs
you computed yourself — a symbol fingerprint, a hash, anything —
attached with stamps on claim_files or --stamp name=value on the
CLI (repeatable). zoanthrope stores caller stamps and re-checks only
its own file stamp; verifying caller stamps is the caller's business.
They come back on grants, in conflicts (a waiting agent sees what the
holder pinned), in list_claims, and in who_am_i. A re-claim that
passes stamps replaces the stored set outright; one without keeps the
pins already set; release drops them with the claim.
Filesystem leases (anti-clobbering)
A granted edit claim also takes a filesystem lease under
.zoanthrope/locks/: a nonce-named sentinel file per path, acquired
under a short O_EXCL guard (so concurrent acquirers cannot both pass
the holder scan), released by move-aside-and-verify (so a delayed
release can never delete a replacement holder's file). A live foreign
lease refuses a claim even when the registry is free — a lease-only
writer (an editor's save, a stray process) still gets the file to
itself. Read claims take no lease.
Leases carry a heartbeat mtime; one older than 60s
(ZOANTHROPE_LEASE_TTL_SECS overrides) is stale and may be taken
over, again move-aside-and-verify. status prints the lease layer so
orphaned sentinels are easy to spot.
Postboard
Posts are the agents' public intentions — one line a human can read
while debugging a swarm. post_intention opens one (status: open);
update_post moves it through taken → done, or dropped if the
plan changed. Posts do not replace each other; an agent may carry
several. Only the posting agent may edit or remove its post.
Rejoin
An agent that went away and came back (restart, sleep, disconnect)
calls rejoin_agent. It refreshes presence, re-asserts the agent's own
claims (filesystem lease refreshed or re-taken), republishes the board,
and reports — never rewrites. A claim the registry still credits to the
caller but where another agent now holds a live lease comes back as
claims_at_risk: reported, not stolen back, not released. Rejoin never
breaks claims, own or foreign.
Quickstart
Then, over stdio (one JSON-RPC message per line):
Point another agent at the same file and its call returns the conflict
shape above with "holder": "alpha".
Pre-edit hooks use the CLI mirror instead of MCP (see hooks/):
Exit codes: 0 granted/ok, 1 conflict (or ok: false), 2 usage or
IO error. --workspace is optional everywhere; without it the root is
found by walking up from the current directory for .git.
Wire the hooks (Claude Code)
The guard family claims every file a tool call would mutate and, in
blocking mode, turns conflicts into tool-call denials. This is the
exact wiring that ran the recorded collision experiments — copy it
into your workspace's .claude/settings.json (adjust the paths to
where you keep the repo):
Each agent needs its identity in the environment (ZOANTHROPE_AGENT=alpha,
…) — see hooks/README.md for the identity resolution order and how
to port the hooks to other agent frameworks.
One wiring, both modes. Advisory vs blocking is a runtime mode,
not a different hook: this wiring serves both. claim-guard.sh
resolves the mode per tool call, highest priority wins:
-
ZOANTHROPE_MODEenv var (advisory|blocking) — the per-agent override, so a swarm can block its bots and leave one advisory actor; -
.zoanthrope/config.jsonat the workspace root (found by walking up to the git toplevel, like the CLI; the file is optional): -
default:
blocking(the experiment-proven behavior).
In advisory mode the guard claims the file, prints the
claim/conflict JSON to stderr, and always exits 0 — it never emits a
permissionDecision: deny. In blocking mode a conflict denies the
tool call: exit 2, reason naming the holder (unchanged). An invalid
mode value falls back to blocking with one warning line on stderr —
a hook never crashes. pre-edit.sh remains as the legacy advisory
alias; release-guard.sh and watch.sh never deny, so the mode does
not apply to them.
Instructions for agents
Paste this into your swarm's prompt or AGENTS.md — it is the protocol
the collision experiments validated:
For consultation and debugging (who holds what, stale agents, orphaned
claims), give your agents skills/zoanthrope/SKILL.md.
State
Everything lives in .zoanthrope/state.json at the workspace root:
claims, agents, posts, the published board (who holds what, stamped
with a generation epoch), and an append-only audit ring capped at
1000 events. Writes are atomic (temp file + rename — the rename is the
generation publish) and serialized by a lock file. A state file from a
different format version is refused with instructions; move
.zoanthrope/ aside to start fresh.
Presence TTL defaults to 15 minutes; ZOANTHROPE_TTL_SECS overrides.
Performance
Decide in nanoseconds, persist in microseconds, scale to 128
concurrent agents without losing a write. The guarantee covers write
paths that go through the guarded hooks (Edit/Write/MultiEdit
and recognized Bash mutations); unparseable commands are audited as
unclaimed-mutation rather than blocked, and generator scripts
(node script.js) are not automatically covered unless they write
through recognized idioms — the audit trail remains the safety net.
All numbers measured on this machine; each family reproduces with the
command named.
Latency — cargo run --release --bin zoo-bench:
Persistence is the cost, the decision is not. The contended-8 p95 is 7.3 ms; under the earlier fixed 20 ms poll the same tail reached 101 ms, and lock acquisition now backs off exponentially to bring it down. Postboard creation runs at 614 posts/s.
Clobber chaos — cargo test --release --test clobber_chaos -- --ignored (scale with ZOO_CHAOS_WORKERS):
Prevention holds as the swarm grows. One documented limit, plainly: the post-hoc stomp detector samples faster than it can observe when writes exceed ~10⁵/s — prevention (the lock) is the guarantee; the audit trail is best-effort at machine-gun rates.
The A/B with real agents — 3 concurrent agents on shared files,
locked arm vs an unguarded control (full protocol and per-run data in
experiments/zoo-ab-2026-10-12.md): the locked arm finished with
0 lost work, 3 denials that named the holder, all recovered on
retry, at equal wall time (53.1 s vs 52.9 s). Scope, framed as
coverage: tool-mediated edits (Edit/Write) ride the host harness's
own CAS/OCC — verified 0 lost work in 24 agent runs — so the lock
is engineered for the vector that escapes those tools: Bash
mutations, codemods, generators, scripts. On that vector the
round-3 table reads 0 vs 1 lost, with +6 in-flight clobbers
self-healed in the control.
Formal guarantees
The coordination core is checked against TLA+ models in formal/:
PresenceClaims— claim arbitration: no conflicting holders, read co-holding, and strongest-intent recording;FsLease/FsLeaseGuard— filesystem lease exclusion, the anti-clobbering core (move-aside-and-verify release,O_EXCLacquire guard);RejoinProtocol— returning agents re-assert without breaking claims, own or foreign.
Verified with TLC via formal/check.sh: 11/11 configurations — 4
invariant configurations green, and 7 negative-control configurations
reproduce their counterexamples (the specs demonstrably catch the
failure modes they claim to: broken arbitration, unguarded leases,
leaky rejoin). One command to re-verify:
(TLA2TOOLS_JAR points at a tla2tools.jar, TLA+ tools v1.8.0; the
spec→code mapping lives in formal/README.md.)
Development
tests/mcp_smoke.rs spawns zoanthrope mcp and runs the full
initialize → tools/list → claim → conflict → postboard round trip →
rejoin → release over stdio. npm-shim/ is the @spuentesp/zoanthrope
node launcher (thin, no download machinery — it forwards to a binary on
PATH or ZOANTHROPE_BIN).
License
MIT — see LICENSE.
Source: README.md at commit ef15afc
Tools
0Version history
2- v0.1.6LatestOct 14, 2026
- v0.1.2Oct 13, 2026
Score
50% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 days25 pts
Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.
- Auth declared10 pts
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts