关于 zoanthrope
zoanthrope 是面向协作 Agent 群组的 MCP 协调服务,管理文件读写声明、在线状态、任务意向和审计记录。它结合文件内容指纹与文件系统租约,减少 Agent 同时修改共享文件造成的冲突。
主要功能
- 01管理文件读写声明与冲突
- 02登记 Agent 在线状态
- 03发布并更新任务意向
- 04使用文件系统租约防止覆盖
- 05记录审计事件并支持重新加入
适用场景
- 01协调多个 Agent 修改同一工作区
- 02确认文件持有者及声明用途
- 03Agent 重启后恢复协作状态
- 04排查过期声明和文件冲突
MCP 相关
通过 stdio JSON-RPC 提供 MCP 服务,暴露文件声明、Agent 状态、意向公告、重新加入和审计等工具。另提供用于声明、释放、恢复和查看状态的 CLI。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
zoanthrope
传递蜂巢意志、指挥虫群的泰伦虫族生物—— zoanthrope 是协调中继:谁在哪儿工作、承诺了什么、发生了什么。
一款虫群协调 MCP 服务。 zoanthrope 为协作 Agent 的工作区解答一个问题——谁在做什么?——它支持带有读取/编辑意图的文件认领、Agent 状态、公开的意向公告板、针对每个路径的文件系统租约以防止相互覆盖、供离开后又回来的 Agent 使用的重新加入流程,以及有容量上限的审计环形日志。它让一组 Agent 避免互相妨碍,并留下谁承诺了什么的记录。
它是什么
- 一个通过 stdio JSON-RPC 运行的小型 MCP 服务(
zoanthrope mcp) - 一个面向钩子的命令行工具(
zoanthrope claim/release/rejoin/status) .zoanthrope/state.json中的状态注册表,以及.zoanthrope/locks/中的文件系统租约——无需守护进程,无需安装和保持运行
工具接口
所有工具都接收并返回 JSON 对象。成功响应体包含
"ok": true;被拒绝的认领是一次成功调用,其响应体包含
"ok": false 和冲突结构。
| 工具 | 参数 | 返回值 |
|---|---|---|
claim_files | agent_id、mode(claim | release | list,默认 claim)、files(路径或 {path, purpose?})、purpose?、intent?(默认 edit | read)、stamps?(由调用方固定的 {name, value} 数组) | 认领:{ok, granted: [{path, claimed_at, purpose, intent, caller_stamps}]} 或冲突结构;释放:{ok, released: [path]};列表:{ok, claims} |
list_claims | agent_id? | {ok, claims: [{path, agent, intent, claimed_at, purpose, held_by_stale, caller_stamps}]} |
who_am_i | agent_id | {ok, agent_id, name, kind, last_seen, stale, claims, posts} |
list_active_agents | include_stale? | {ok, agents: [{agent_id, name, kind, last_seen, stale, claims_count}]} |
register_agent | name、kind? | {ok, agent_id, name, kind, last_seen} |
post_intention | agent_id、title、note、files? | {ok, post_id, post: {post_id, agent_id, title, files, note, status, created_at, updated_at}} |
list_posts | agent_id?、status? | {ok, posts: [...]},按更新时间从新到旧排列 |
update_post | agent_id、post_id、status?(open|taken|done|dropped)、title?、note?、files? | {ok, post_id, post} |
remove_post | agent_id、post_id | {ok, post_id, removed: true} |
rejoin_agent | agent_id、files? | {ok, agent_id, claims_kept, claims_at_risk, view_epoch, board} |
get_audit_log | limit?(默认 200)、since_unix? | {ok, events: [{ts, agent, action, path?, purpose?}]} |
冲突结构
这是产品的核心:与其他 Agent 已记录意图冲突的认领会被整体拒绝,并明确指出造成冲突的对象:
{"ok": false, "holder": "beta", "path": "a.rs", "claimed_at": 1791780567.1, "purpose": "fix the parser", "holder_stale": false, "intent": "edit", "caller_stamps": [{"name": "symbol:parse_token", "value": "…"}]}
声明语义
声明携带一种意图(read 或 edit):
- 一项 edit 声明会与其他 Agent 在该路径上的任何声明冲突——同一时间恰好只能有一个写入者;
- 一项 read 声明会与其他 Agent 的 edit 声明冲突,但多个读取者可以顺利共持声明;
- Agent 在某条路径上记录的意图是它持有的最强意图——之后较弱的声明不会隐藏较强的声明。只有先释放再重新声明,才能降级。
每次调用中的声明要么全部成功,要么全部失败:批量操作中只要有一项冲突,就不会授予任何声明。持有者超过在线状态 TTL 没有回应时会被标记为 stale,其声明会标记为 held_by_stale,而且仍会阻止其他操作——声明绝不会自动释放;是否强制解除由策略决定,本产品不负责制定此策略。
声明还会绑定文件的内容:在声明(或重新声明)时记录内容指纹——始终记录 mtime+size,对于 ≤ 256 KiB 的文件,还会记录 sha256,因此即使在时间戳精度较低的文件系统上,同尺寸重写也能被发现。每次受保护的写入(整个 hook 系列)都会先根据声明的指纹检查文件:如果文件在声明期间发生变化(例如被外部追加内容或运行了生成器),写入将被拒绝,并返回 stale-claim 拒绝信息(持有者为自身),提示 Agent 重新读取并重新声明。重新声明会将指纹重新绑定到当前内容;release 会将其丢弃。不存在有意接管:声明锁是一项内容承诺,而不只是写入者锁。
声明还可以携带调用方标记:由你自行计算的不透明 {name, value} 对,例如符号指纹、哈希等,可通过 claim_files 上的 stamps 或 CLI 中的 --stamp name=value 附加(可重复指定)。zoanthrope 会存储调用方标记,并且只重新检查自身的文件标记;验证调用方标记是调用方的职责。授予声明、冲突信息(等待中的 Agent 可以看到持有者固定的标记)、list_claims 和 who_am_i 都会返回这些标记。通过标记重新声明会直接替换已存储的标记集合;不带标记重新声明则会保留现有标记;release 会随声明一并丢弃这些标记。
文件系统租约(防止覆盖)
成功授予的 edit 声明还会在 .zoanthrope/locks/ 下获取文件系统租约:每条路径对应一个以随机数命名的哨兵文件,并通过短暂的 O_EXCL 守卫获取(确保并发获取者无法同时通过持有者扫描),通过移至旁处再验证的方式释放(确保延迟的释放操作绝不会删除替代持有者的文件)。即使注册表中没有记录,仍会拒绝有其他持有者的有效租约——仅持有租约的写入者(编辑器保存操作、意外进程)仍能独占该文件。Read 声明不获取租约。
租约带有心跳 mtime;超过 60s 的租约(可通过 ZOANTHROPE_LEASE_TTL_SECS 覆盖)会被视为过期,并可通过移至旁处再验证的方式接管。status 会显示租约层信息,便于发现遗留的哨兵文件。
公告板
公告是 Agent 的公开意图——调试 Agent 群组时,人类可以直接阅读的一行文字。post_intention 会新建一条公告(status: open);update_post 会将其依次更新为 taken → done,如果计划有变则更新为 dropped。公告不会互相替代;一个 Agent 可以同时持有多条公告。只有发布公告的 Agent 才能编辑或删除自己的公告。
重新加入
离开后又重新上线的 Agent(重启、休眠、断开连接)会调用 rejoin_agent。该操作会刷新在线状态,重新确认 Agent 自己的声明(刷新或重新获取文件系统租约),重新发布公告板,并报告状态——绝不会重写数据。如果注册表仍将某项声明记在调用方名下,但此时另一 Agent 持有该文件的有效租约,该声明会以 claims_at_risk 返回:只报告,不会夺回,也不会释放。重新加入绝不会破坏任何声明,无论是自己的还是其他人的声明。
快速开始
$ cargo build --release
$ ./target/release/zoanthrope mcp
然后,通过 stdio(每行一条 JSON-RPC 消息):
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"demo","version":"0"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"claim_files","arguments":{"agent_id":"alpha","files":[{"path":"src/a.rs","purpose":"fix the parser"}]}}}
让另一个 Agent 指向同一文件,其调用会返回上文所示的冲突结构,其中 "holder": "alpha"。
预编辑钩子使用 CLI 镜像,而非 MCP(参见 hooks/):
$ zoanthrope claim --files a.rs,b.rs --agent alpha --purpose "fix bug" --intent edit
$ zoanthrope release --files a.rs,b.rs --agent alpha
$ zoanthrope rejoin --agent alpha
$ zoanthrope status [--workspace DIR]
退出码:0 表示已获准/正常,1 表示冲突(或 ok: false),2 表示用法或 IO 错误。--workspace 在任何地方都是可选的;省略时,会从当前目录向上查找 .git 来确定根目录。
接入钩子(Claude Code)
守护钩子系列会认领工具调用将要修改的每个文件,并在阻止模式下将冲突转为工具调用拒绝。这正是运行已记录冲突实验时使用的接线配置——将其复制到工作区的 .claude/settings.json 中(按仓库所在位置调整路径):
{
"hooks": {
"PreToolUse": [
{ "matcher": "Edit|Write|MultiEdit|Bash",
"hooks": [{ "type": "command", "command": "/path/to/zoanthrope/hooks/claude-code/claim-guard.sh" }] }
],
"PostToolUse": [
{ "matcher": "Edit|Write|MultiEdit|Bash",
"hooks": [{ "type": "command", "command": "/path/to/zoanthrope/hooks/claude-code/release-guard.sh" }] },
{ "matcher": ".*",
"hooks": [{ "type": "command", "command": "/path/to/zoanthrope/hooks/claude-code/watch.sh" }] }
]
}
}
每个 Agent 都需要在环境中设置自己的身份(ZOANTHROPE_AGENT=alpha,……)——有关身份解析顺序以及如何将钩子移植到其他 Agent 框架,请参阅 hooks/README.md。
**一套接线,两种模式。**建议模式与阻止模式是运行时模式,而非不同的钩子:这套接线适用于两者。claim-guard.sh 会为每次工具调用解析模式,优先级从高到低如下:
-
ZOANTHROPE_MODE环境变量(advisory|blocking)——按 Agent 设置的覆盖值,因此一个群组可以阻止其机器人,同时让一个参与者保持建议模式; -
工作区根目录下的
.zoanthrope/config.json(与 CLI 一样,通过向上查找到 git 顶层目录;该文件为可选):{"mode": "advisory"} -
默认值:
blocking(经实验验证的行为)。
在 advisory 模式下,守护钩子会认领文件,将认领/冲突 JSON 打印到 stderr,并始终以 0 退出——绝不会发出 permissionDecision: deny。在 blocking 模式下,冲突会拒绝工具调用:以 2 退出,原因中会指出持有者(保持不变)。无效的模式值会回退到 blocking,并在 stderr 输出一行警告——钩子绝不会崩溃。pre-edit.sh 仍作为旧版建议模式别名;release-guard.sh 和 watch.sh 绝不会拒绝调用,因此模式不适用于它们。
给 Agent 的说明
将以下内容粘贴到群组的提示词或 AGENTS.md 中——这是经冲突实验验证的协议:
A coordination service (zoanthrope) is active in this workspace.
- Before touching shared files, post your intention (`post_intention` /
`zoanthrope claim --files … --agent <you> --purpose "…"`).
- An edit may be DENIED with a reason naming the holder. That means
another agent owns the file right now: wait briefly, then retry
(up to 3 times). If still denied, work on something else and record
it — never work around the lock (no out-of-band writes).
- Prefer append/insert on shared files; if you must rewrite a file
whole, read it back after writing and merge anything that appeared.
- Post what you plan to do on the postboard before you start, and
update the post when done (`update_post`).
- If you return after being away, call `zoanthrope rejoin --agent <you>`
once — it re-asserts your claims and tells you what changed.
如需咨询和调试(谁持有哪些资源、失效的 Agent、孤立认领),请将 skills/zoanthrope/SKILL.md 提供给你的 Agent。
状态
所有内容都存储在工作区根目录下的 .zoanthrope/state.json 中:认领、Agent、帖子、已发布的状态板(谁持有哪些资源,并附有代数 epoch 标记),以及最多包含 1000 个事件的仅追加审计环。写入是原子操作(临时文件 + 重命名——重命名即发布代数),并由锁文件串行化处理。若状态文件的格式版本不同,程序会拒绝读取并给出说明;将 .zoanthrope/ 移至其他位置即可重新开始。
存在状态的 TTL 默认为 15 分钟;ZOANTHROPE_TTL_SECS 可覆盖此值。
性能
*以纳秒为单位做出决策,以微秒为单位持久化,可扩展至 128
个并发 Agent,且不会丢失写入。*此保证涵盖通过受保护钩子(Edit/Write/MultiEdit
和可识别的 Bash 修改操作)执行的写入路径;无法解析的命令会记录为
unclaimed-mutation,而不会被阻止;生成器脚本
(node script.js)不会自动纳入保护,除非它们通过可识别的惯用方式写入——审计记录仍是安全保障。
所有数据均在此机器上测得;每组数据都可通过所列命令复现。
延迟 — cargo run --release --bin zoo-bench:
| 路径 | p50 | p99 |
|---|---|---|
| 内存中的 claim+release 对 | 0.30 µs | 0.92 µs |
磁盘上 state.json 的 claim+release 对 | 387 µs | 695 µs |
| 热文件上的 contended-8(修复退避后) | 355 µs | 14.5 ms |
对 1000 条帖子执行 list_posts | 3.86 ms | — |
成本在于持久化,而非决策。contended-8 的 p95 为 7.3 ms;此前采用固定 20 ms 轮询时,同一尾延迟达到 101 ms;现在锁获取采用指数退避,将其降了下来。Postboard 创建速度为每秒 614 条帖子。
覆盖写入混沌测试 — cargo test --release --test clobber_chaos -- --ignored(通过 ZOO_CHAOS_WORKERS 调整规模):
| 工作线程 | 迭代次数 | 违规数 | 吞吐量 | 已解决冲突 |
|---|---|---|---|---|
| 16 | 9,600 | 0 | — | — |
| 64 | 12,800 | 0 | 588 ops/s | 9,809 |
| 128 | 19,200 | 0 | 591 ops/s | 16,205 |
随着群组规模扩大,预防机制依然有效。有一个已记录的限制,明确说明如下: 当写入速度超过约 10⁵/s 时,事后覆盖写入检测器的采样速度赶不上观察速度——锁是预防机制,也是保障;在机关枪般的写入速率下,审计记录只能尽力而为。
使用真实 Agent 进行 A/B 测试 — 3 个并发 Agent 操作共享文件,
加锁组与未加保护的对照组(完整协议和每次运行的数据见
experiments/zoo-ab-2026-10-12.md):加锁组最终
没有丢失任何工作,有 3 次拒绝操作明确指出了占用者,均在重试后成功恢复;两组耗时相当(53.1 s 对 52.9 s)。范围应从覆盖情况来界定:通过工具执行的编辑(Edit/Write)依赖宿主 harness 自带的 CAS/OCC——已在 24 次 Agent 运行中验证没有丢失任何工作——因此,这把锁专门针对那些会绕过这些工具的操作:Bash
修改操作、codemod、生成器、脚本。针对这类操作,第 3 轮的表格显示丢失数为 0 对 1;对照组中有 6 次进行中的覆盖写入自动修复。
形式化保证
协调核心通过 formal/ 中的 TLA+ 模型进行验证:
PresenceClaims— 占用声明仲裁:不存在冲突的持有者,允许共享读取,并记录最强意图;FsLease/FsLeaseGuard— 文件系统租约互斥,防止覆盖写入的核心机制(通过移至旁处并验证来释放,使用O_EXCL获取保护);RejoinProtocol— 返回的 Agent 重新声明占用,且不会破坏自身或他人的占用声明。
通过 formal/check.sh 使用 TLC 验证:11/11 种配置——4 种不变式配置通过;7 种负向对照配置均复现其反例(这些规范确实能捕获其声称能捕获的故障模式:仲裁失效、租约无保护、重新加入时泄漏)。重新验证只需一条命令:
$ cd formal && ./check.sh
(TLA2TOOLS_JAR 指向 tla2tools.jar,TLA+ 工具版本为 v1.8.0;规范到代码的映射见 formal/README.md。)
开发
$ cargo test
$ cargo fmt --check
$ cargo clippy --all-targets -- -D warnings
tests/mcp_smoke.rs 会启动 zoanthrope mcp,并通过 stdio 完整运行 initialize → tools/list → claim → conflict → postboard 往返 → rejoin → release 流程。npm-shim/ 是 @spuentesp/zoanthrope
Node 启动器(轻量,不包含下载机制——会转发到 PATH 或
ZOANTHROPE_BIN 中的二进制文件)。
许可证
MIT — 详见 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。