zoanthrope について
zoanthropeは、協働するエージェント群のファイル要求、稼働状況、作業意図、監査イベントを管理するMCP調整サーバーです。ファイル内容のフィンガープリントとファイルシステムのリースを組み合わせ、共有ファイルの編集競合を抑えます。
主な機能
- 01ファイルの読み取り・編集要求を管理
- 02エージェントの稼働状況を追跡
- 03作業意図の投稿と更新
- 04ファイルシステムのリースで上書きを防止
- 05監査イベントの記録と再参加
活用シーン
- 01共有ワークスペースを編集するエージェントの調整
- 02ファイルの保持者と要求目的の確認
- 03エージェント再起動後の協調状態の復元
- 04古い要求やファイル競合の調査
MCP 対応
stdio JSON-RPCでMCPサーバーを提供し、ファイル要求、エージェントの稼働状況、意図の投稿、再参加、監査のツールを公開します。要求、解放、再参加、状態確認に使うCLIもあります。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
zoanthrope
ハイブマインドの意志を群れに伝えるティラニッドの生物 — zoanthrope は調整の中継役です。誰がどこで作業し、何を 約束し、何が起きたのかを伝えます。
群れの調整を担う MCP サーバーです。 zoanthrope は、連携するエージェントのワークスペースにおける「誰が何に取り組んでいるのか?」という問いに答えます。読み取り/編集の意図を伴うファイルの確保、エージェントのプレゼンス、作業予定を公開する掲示板、上書きを防ぐパス単位のファイルシステムリース、離れて戻ってきたエージェントの再参加手段、上限付き監査リングを備えています。エージェント同士が互いの作業を妨げないようにし、誰が何を約束したのかを記録に残します。
概要
- stdio JSON-RPC を介した小規模な MCP サーバー(
zoanthrope mcp) - フックから利用する CLI(
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?}]} |
競合の形式
このプロダクトの中核です。別のエージェントが記録した意図と競合する確保は、すべて拒否され、妨げとなっている相手が明確に示されます。
{"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 クレームは、そのパスを保持する他のエージェントのクレームすべてと競合します。同時に書き込みできるのは常に1つだけです。
- read クレームは、他のエージェントの edit クレームとは競合しますが、読み取り側どうしは問題なく共存できます。
- エージェントがパスに対して記録する意図は、そのエージェントが保持する中で最も強いものです。後から追加された弱いクレームによって、強いクレームが隠されることはありません。弱めるには、いったん解放してから再度クレームする必要があります。
クレームは呼び出しごとに全か無かで処理されます。バッチ内で1つでも競合があれば、何も許可されません。プレゼンス TTL を過ぎても応答がない保持者は stale となり、そのクレームは held_by_stale になりますが、引き続きブロックします。クレームが自動解放されることはありません。クレームを強制的に解放するかどうかは、この製品では決めていないポリシーです。
クレームはファイルの内容にも結び付けられます。クレーム時(または再クレーム時)に内容のフィンガープリントを記録します。常に mtime+size を記録し、256 KiB 以下のファイルでは sha256 も記録するため、粗い精度のファイルシステムで同じサイズの内容が書き換えられた場合も検出できます。保護対象の書き込み(フック群)はすべて、まずクレームのフィンガープリントとファイルを照合します。クレーム中にファイルが変更されていた場合(範囲外からの追記やジェネレーターの実行など)、書き込みは stale-claim の拒否でブロックされます。この拒否では保持者が自分自身となり、エージェントに再読み込みして再クレームするよう伝えます。再クレームするとフィンガープリントが現在の内容に結び付き直され、release すると破棄されます。意図的な引き継ぎの仕組みはありません。クレームロックは単なる書き込みロックではなく、内容を保証するものです。
クレームには呼び出し元のスタンプも付けられます。これは、自分で計算した不透明な {name, value} のペア(シンボルのフィンガープリント、ハッシュなど)で、claim_files の stamps または CLI の --stamp name=value で指定します(繰り返し指定できます)。zoanthrope は呼び出し元のスタンプを保存し、自身のファイルスタンプのみを再確認します。呼び出し元のスタンプの検証は呼び出し元側の責任です。これらのスタンプは、許可結果、競合情報(待機中のエージェントには保持者が固定した内容が見えます)、list_claims、who_am_i に含まれます。スタンプを指定して再クレームすると、保存されているスタンプ一式が置き換わります。スタンプを指定せずに再クレームすると、既存の固定情報が保持されます。release すると、クレームとともにスタンプも破棄されます。
ファイルシステムリース(上書き防止)
許可された edit クレームは、.zoanthrope/locks/ にファイルシステムリースも取得します。パスごとに nonce 名のセンチネルファイルを作成し、短時間の O_EXCL ガードの下で取得します(これにより、同時に取得しようとした複数のプロセスが保持者のスキャンを両方通過することを防ぎます)。解放時は、別の場所への移動と検証によって解放します(これにより、遅れて実行された解放処理が、後から取得した保持者のファイルを削除することはありません)。有効な外部リースがある場合、レジストリが空いていてもクレームは拒否されます。リースだけを保持する書き込み側(エディターの保存や、意図しないプロセスなど)にも、そのファイルが確保されます。read クレームではリースを取得しません。
リースにはハートビートの mtime が記録されます。60秒を超えて古いリース(ZOANTHROPE_LEASE_TTL_SECS で変更可能)は stale となり、同じく別の場所への移動と検証を経て引き継げます。status はリースのレイヤーを表示するため、孤立したセンチネルを簡単に見つけられます。
掲示板
投稿は、群れをデバッグする人が読める、エージェントの公開された意図です。post_intention で投稿を作成すると、状態は open になります。update_post で状態を taken → done と進めるか、計画が変わった場合は dropped にします。投稿が互いに置き換わることはありません。エージェントは複数の投稿を持つことができます。投稿を編集または削除できるのは、投稿したエージェントだけです。
再参加
離脱して戻ってきたエージェント(再起動、スリープ、切断など)は、rejoin_agent を呼び出します。これによりプレゼンスが更新され、エージェント自身のクレームが再確認され(ファイルシステムリースも更新または再取得されます)、掲示板が再公開され、現状が報告されます。書き換えは行われません。レジストリ上では呼び出し元のクレームとして記録されていても、別のエージェントが有効なリースを保持している場合、そのクレームは claims_at_risk として返されます。報告はされますが、奪い返されたり解放されたりすることはありません。再参加によって、自分自身または他者のクレームが破られることはありません。
クイックスタート
$ cargo build --release
$ ./target/release/zoanthrope mcp
次に、stdio 経由で(1 行につき JSON-RPC メッセージ 1 つ):
{"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"}]}}}
別のエージェントを同じファイルに向けると、その呼び出しは上記の競合形式で "holder": "alpha" を返します。
編集前フックでは、MCP ではなく CLI ミラーを使用します(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" }] }
]
}
}
各エージェントの環境変数に ID を設定する必要があります(ZOANTHROPE_AGENT=alpha など)。ID の解決順序と、他のエージェントフレームワークへのフックの移植方法については、hooks/README.md を参照してください。
1 つの設定で両方のモードに対応します。 アドバイザリとブロッキングの違いは実行時のモードであり、フックが別々にあるわけではありません。この設定は両方のモードで動作します。claim-guard.sh はツール呼び出しごとにモードを解決し、優先順位の高いものを適用します:
-
ZOANTHROPE_MODE環境変数(advisory|blocking)— エージェントごとの上書き設定です。これにより、スウォーム内のボットはブロックし、1 つのエージェントはアドバイザリのままにできます。 -
ワークスペースルートの
.zoanthrope/config.json(CLI と同様に git のトップレベルまで遡って検出します。このファイルは省略可能です):{"mode": "advisory"} -
デフォルト:
blocking(実験で有効性が確認された動作)。
advisory モードでは、ガードはファイルをロックし、ロック/競合の JSON を stderr に出力して、常に終了コード 0 を返します。permissionDecision: deny は出力しません。blocking モードでは、競合があるとツール呼び出しを拒否します。終了コードは 2 で、理由にはロックしているエージェントが示されます(変更なし)。無効なモード値が指定された場合は、stderr に警告を 1 行出力して blocking にフォールバックします。フックがクラッシュすることはありません。pre-edit.sh は従来のアドバイザリエイリアスとして引き続き使用できます。release-guard.sh と watch.sh は拒否を行わないため、モードは適用されません。
エージェント向けの指示
これをスウォームのプロンプトまたは 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.
相談やデバッグ(誰が何をロックしているか、古いエージェント、孤立したロック)には、エージェントに skills/zoanthrope/SKILL.md を渡してください。
状態
すべての情報はワークスペースルートの .zoanthrope/state.json に保存されます。ロック、エージェント、投稿、公開済みボード(誰が何をロックしているかを、世代を示す epoch とともに記録)、および最大 1000 件のイベントを保持する追記専用監査リングが含まれます。書き込みはアトミックに行われ(一時ファイルへの書き込み後にリネームし、このリネームで世代を公開します)、ロックファイルによって直列化されます。異なる形式バージョンの状態ファイルは拒否され、手順が表示されます。新しく開始するには .zoanthrope/ を別の場所に移動してください。
プレゼンス TTL のデフォルトは 15 分です。ZOANTHROPE_TTL_SECS で上書きできます。
パフォーマンス
ナノ秒単位で判断し、マイクロ秒単位で永続化し、書き込みを失うことなく 128 のエージェントを同時にスケールできます。 この保証は、保護されたフック(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 |
スウォームが拡大しても防止機能は維持されます。明記しておく制限が 1 つあります。事後的な上書き検出器は、書き込みが約 10⁵/s を超えると、観測できる速度よりも速い頻度でサンプリングします。保証となるのは防止機能(ロック)であり、監査証跡はマシンガンのような速度ではベストエフォートです。
実際のエージェントによる A/B テスト — 共有ファイル上で 3 つのエージェントを同時に実行し、ロックありの条件と保護なしの対照条件を比較しました(完全なプロトコルと実行ごとのデータは experiments/zoo-ab-2026-10-12.md を参照)。ロックありの条件では、壁時計時間が同等(53.1 s 対 52.9 s)のまま、作業の損失は 0、保持者を明示した拒否が 3 件で、いずれも再試行で回復しました。対象範囲をカバレッジとして示すと、ツールを介した編集(Edit/Write)はホストハーネス独自の CAS/OCC を利用し、24 回のエージェント実行で作業の損失が 0 であることを検証済みです。そのため、このロックはそれらのツールをすり抜ける経路、つまり Bash による変更、codemod、ジェネレーター、スクリプト向けに設計されています。この経路では、第 3 ラウンドの表は損失が 0 対 1 であり、対照条件では実行中の上書き 6 件が自己修復されました。
形式的保証
調整コアは formal/ の TLA+ モデルに照らして検証されています。
PresenceClaims— claim の仲裁:競合する保持者が存在しないこと、読み取りの共同保持、最も強い意図の記録。FsLease/FsLeaseGuard— ファイルシステムリースによる排他制御。上書き防止の中核(退避して検証する解放処理、O_EXCLによる取得ガード)。RejoinProtocol— 復帰するエージェントが、自身の claim も他者の claim も壊さずに再主張すること。
formal/check.sh を介して TLC で検証:11/11 の構成 — 4 つの不変条件の構成がすべて成功し、7 つのネガティブコントロール構成では反例を再現します(仕様が対象とする障害モード、つまり仲裁の破損、ガードされていないリース、再参加時の漏れを、仕様が確実に検出することを示しています)。再検証は次の 1 コマンドで実行できます。
$ cd formal && ./check.sh
(TLA2TOOLS_JAR は tla2tools.jar を指し、TLA+ tools 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 サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。