1つのリポジトリで複数の Claude。ロック、ハンドオフ、そして1982年のメール
著者: Anastasiia Butova, ComfyUI・拡散モデルエンジニア(ベオグラード) · 公開 2026-04-23 · 更新 2026-09-23
英語版からの翻訳: English
私は3つの Pro サブスクリプションで Claude Code を並行して動かしていて、同僚も自分たちのものを動かしています。しばしば同じファイルを、同じ日に触ります。この記事では、そこから出てきた3つの調整の問題を、出てきた順に扱います。あるセッションから次のセッションへ文脈を渡すこと、1つのセッションに資源を排他的に使わせること、そして生きている2つのセッションが互いに話せるようにすること。どれも、40年ほど前からある分散システムのパターンだと分かりました。追記専用のログ、ハートビートつきのロックファイル、そしてメールです。最後に、3つがどう mclaude としてパッケージ化されたか、実際の使い方、そしてどこで壊れるかを書きます。
ハンドオフが最初でした。新しいチャットのたびに、物事の現状を説明し直すのにうんざりしたからです。ロールアップは、長いプロジェクトごとにハンドオフが80件ずつ溜まったときに生まれました。メールは、同僚の Claude のステータスを、メッセンジャー経由で中継し続けることにうんざりしたときに生まれました。
第1層:ハンドオフ、「終わった。続きをどうぞ」
最初の試みは、セッションを閉じるときに、すべてを1つの HANDOFF.md に書くことでした。チャットが1つより多くなった瞬間に壊れます。最後に書いた人が勝ち、他の全員が上書きされるからです。
そこで追記専用に移りました。すべてのセッションが、独自の名前を持つ自分のファイルを書きます。
.claude/handoffs/
├── 2026-04-09_14-32_373d1618_drift-validator.md
├── 2026-04-09_16-47_b858f500_dashboard-refactor.md
├── 2026-04-10_11-20_a1c2d3e4_auth-fix.md
└── INDEX.md (append-only log)
名前には、日付、時刻、セッション ID の最初の8文字、タスクのスラッグが入ります。2つのチャットが同じ名前を作ることは不可能です。同じ秒でも、セッション ID が違うからです。レースは、構造上起こりえません。ロックもブローカーも要りません。
INDEX.md も追記専用です。新しい行は一番下に足されるだけで、上の行を編集することはありません。ステータスを ACTIVE から CLOSED に移すには、古いレコードを編集するのではなく、新しいレコードを追記します。データベースの write-ahead log も、まさに同じ仕組みで動いています。同じ理由です。書き込みの途中でプロセスが死んでも、ログは一貫した状態のままです。
ハンドオフは日記ではなく、次のセッションとの契約です。5つのセクションがあり、それぞれが1つの問いに答えます。目標、何をしたか(パスつき。何も作り直さなくて済むように)、何がうまくいかなかったか、その理由、現在の状態(動作中、壊れている、ブロックされている)、そして次の一手です。以下は、実際のカラーチェッカーのセッションから、各セクションを1行ずつ抜き出したものです。
- 目標:「Colour checker: CNN sweep + diffusion, first results」
- 完了:「CNN baseline median 1.99 deg, 11M params, 21 MB」
- うまくいかなかった:「lr=3e-4, NaN after epoch 10-13, no gradient clipping」
- 壊れている:「Diffusion training OOM at bs=16」
- 次の一手:「Inference script for diffusion + visual sheets」
同じプロジェクトのハンドオフの全文は、My CLAUDE.md is 582 linesにあります。
役に立つのは「うまくいかなかったこと」のセクションです。4日間で27件のハンドオフが溜まりましたが、同じ行き止まりに2度入ることはありませんでした。すべての新しいセッションが警告を見たからです。ハンドオフは約1,500トークンに収まり、生のセッションの文脈は約10万トークンです。つまり約67倍小さく、続きをやるのに重要なものは何も失われていません。
書くことは自動化されています。Stop フックが、セッションの長さを確認し、十分に長くて何も記録されていなければ、ハンドオフを保存するかどうか尋ねます。さらに長いセッションでは、モデル自身が、続ける前にハンドオフを書くことを申し出ます。通常は、大きな作業の後か、文脈が70〜80%埋まったときです。私のフックには、そのための長さのチェックはありません。モデル自身の判断です。読むのは、次のセッションの冒頭で1文です。「colour-checker のハンドオフを拾って」、またはファイルへのリンクです。
ロールアップ。ハンドオフが溜まったとき
1つのことに1〜2か月取り組むと、数十件のハンドオフが溜まり、すでに歩いた行き止まりや、すでに反証された仮説でいっぱいになります。新しいセッションは、最新の数件を読み、それ以上深くは見ず、2週間前に私が反証したアプローチを、落ち着いて提案します。
解決策は、古いものを畳むことです。1つのロールアップが、それ以前の20件のハンドオフを要約し、境界の日付を示し、それがカバーする各ハンドオフに、ロールアップのファイル名を指す rolled_up_into: の行を押します。新しいセッションは、1つのロールアップと、その境界より後の日付のハンドオフだけを読みます。残りは歴史として残りますが、文脈には入りません。
ロールアップのファイル名には rollup_ のセグメントが入るので、INDEX.md で目立ちます。そのフロントマターには、何をカバーしているかが並びます(mclaude の README の形式を短くしたもの)。
---
type: rollup
covers:
- 2026-03-01_12-00_session-01_kickoff
- 2026-03-02_14-30_session-02_auth-refactor
- 2026-03-15_18-00_session-12_ci-pipeline
through: 2026-03-15 18:00
---
本文は、通常のハンドオフと同じですが、1セッションではなく20セッションを記述しています。このパターンは私のものではありません。PavelMuntyan/MF0-1984 が、同じ問題を SQLite のスキーマで解いていました。covered_until_message_id という列を持っていたのです。私はそのテーブルを Markdown のフロントマターに置き換えました。特別なことは何もありません。「前の章の要約」です。
第2層:ロック、「この資源は今、私のもの」
次の問題は、リモートのサーバー上の学習の実行で現れました。ハンドオフは追記しかしないので、誰も他人を上書きしません。しかし、実際の資源は違います。2つのセッションが1つのモジュールを同時にリファクタリングすることはできません(マージの衝突)。同時にステージングへデプロイすることもできません(2つ目が1つ目を上書きする)。同時に1つの GPU で学習することもできません(2つ目がメモリ不足になる)。どちらか1つが、資源を排他的に保持しなければなりません。
資源ごとに1つのファイルがあり、資源の名前が付きます。auth-middleware.lock、staging-deploy.lock、gpu-host-a_gpu2.lock などです。中には、誰が取ったか、いつ、何のためにかが入っています。新しいセッションはフォルダを一覧して、何が取られているかを確認し、私に尋ねずに、空いているものを選べます。
トリックは、ファイルの作り方にあります。os.open(path, O_CREAT | O_EXCL) はアトミックです。ファイルがすでに存在すれば、呼び出しは失敗します。2つのプロセスが同時に同じファイルを作ることはできません。カーネルがそれを保証するからです。/var/lock や pid ファイルの背後にある、1980年代からの同じプリミティブです。
ハートビートのないロックは、最初のクラッシュまでしか持ちません。セッションが死ねばロックは残り、全員が永遠に待つことになります。そのため、ファイルの規約では、生きているセッションは30〜60分ごとにタイムスタンプを更新し、ハートビートが約4時間より古いロックは、死んでいる可能性があるとみなされます。可能性があるのであって、確実ではありません。引き継ぐ前に、資源そのものを確認します。SSH 越しの ps、GPU の nvidia-smi、デプロイのキュー、モジュールの git status。資源が本当に使われているのか、それとも単に解放されなかっただけなのかを教えてくれるものなら何でもです。その後で初めて取ります。Chubby と ZooKeeper も、同じ理由で、引き継ぐ前に外から検証します。
各ロックの隣には、JSON のメタデータがあります。タスクのスラッグ、セッション ID、取った時刻、説明、ファイルのリストです。新しいセッションは .claude/locks/ を読み、auth-middleware が ani によって、リファクタリングの2時間目のために保持されていること、GPU 2 が埋め込みの学習で忙しいことを、1秒で知ります。「ここで何が起きているのか?」という問いは、もう出てきません。
最初の直感は、コマンドを解析し、リソースが忙しいと自動でマークする PreToolUse フックでした。行き止まりです。コマンドはあまりに多様で、正規表現のパーサーは誤検知を作る機械になってしまいます。正しい順序は逆でした。まず規約とファイル、自動化は後、しかも繰り返し現れるパターンに対してだけ。
第3層:メール、「アルトム、私のコードを見て」
ハンドオフとロールアップは、時間をまたいで文脈を運びます。ロックは資源を調停します。3つ目の仕事は、どちらにも当てはまりません。2つの生きたセッションを同期させることです。
典型的な1日です。私は同僚の Claude に auth-middleware のリファクタリングをさせ、自分のチャットでプラグインを書きに行きました。1時間後、その進捗と、詰まっていないかを知りたくなりました。以前のやり方では、メッセンジャーを開いて同僚に書き、同僚は自分のチャットを見て、テキストをコピーして返してくれました。毎晩、複数のプロジェクトで、人を3段階中継していたのです。
必要だったのは、同時にオンラインであるとは限らないセッション同士の、宛先つきのメッセージで、配達が保証されているものでした。読まれなければ、次のセッションで読まれる。考えてみると、私が描いていたのはメールでした。
.claude/mailbox/
├── ani/
│ ├── inbox/
│ ├── sent/
│ └── archive/
├── artem/
│ └── ...
└── all/ (broadcast)
各メッセージは、YAML のフロントマター(from、to、type、subject、message_id、in_reply_to、status)を持つ独立した Markdown ファイルで、その下に本文があります。単純な例を挙げます。ani のセッションが Artem に、件名「Re: cmake libsodium」、本文「libsodium のリンクを CMakeLists で変えましたか? target_link_libraries のところでビルドが失敗します」という質問を送ります。1時間後、Artem が自分のマシンで Claude Code を開くと、UserPromptSubmit のフックが、最初のプロンプトの前に受信箱を確認し、未読のメッセージを見つけて、文脈に入れます。彼はその場で返事をし、返信は私の受信箱に届きます。
スレッドは RFC 822 のように in_reply_to でつながります。配達の確認は、別のファイルです。to: * のメッセージは all/ に入り、全員が確認します。重複を避けるために、フックは .watcher_state.json で、すでに表示したメッセージを覚えているので、同じものが文脈に2回入ることはありません。ポーリングも、ブローカーも、バックグラウンドのデーモンもありません。すべては、正しいタイミングで発火するフックに頼っています。各機能は、古典的なメールに1対1で対応します。
| メール(RFC 822、1982年) | エージェントのメールボックス |
|---|---|
| To / Cc | 特定のセッションを宛先にする |
| From / Reply-To | 返信のための戻り先 |
| Subject | 本文を読まずにトリアージする |
| In-Reply-To | メッセージのスレッド |
| Date | 依頼がどれくらい新しいか |
| Read / Unread | 見たかどうか |
| ユーザーごとの受信箱 | 受信者ごとのキュー |
| 送信済みフォルダ | 送信者の監査証跡 |
| 配達の確認 | 「見ましたか?」と聞かずに確認を得る |
mclaude:3つの層を1つのツールに
これらの層を、オープンソースのツール mclaude にパッケージ化しました。Python で、コアには依存関係がなく、193のテストがあり、バージョンは 0.6.0 です。私が書いた時点では、6つの層がありました。基本の3つと、後から加えた3つです。
| 層 | 役割 | コード行数 |
|---|---|---|
| ロック | アトミックな資源の取得 + ハートビート | 469 |
| ハンドオフ | セッションごとの要約、追記専用の INDEX | 371 |
| メモリグラフ | 決定とはまりどころの階層的なストア | 約400 |
| アイデンティティのレジストリ | セッションの名前(ani、artem、nastya) | 246 |
| メッセージとアクティブメール | セッション間のメッセージング、スレッドつき | 400 + 236 |
| コードインデクサー | AST のスキャナー → code-map.md + llms.txt | v0.6.0 で新規 |
インデクサーは、プロジェクトを歩いて、エージェント向けに2つのファイルを書きます。モジュール、クラス、関数の地図である code-map.md、そして素早い読み込みのためのコンパクトな索引である llms.txt です。新しいセッションは、最初の数分をコードの読み直しに使いません。地図がすでに、ハンドオフの隣のリポジトリにあるからです。
その上に、5つのフックがあります。Claude Code のイベント(SessionStart、PreToolUse、UserPromptSubmit、Stop)に4つ、そして git の pre-commit のガードが1つです。このガードは、他人がロックしているファイルに触れるコミットを拒否します。MCP サーバーには、20を超えるツールがあり、構造化された JSON を Claude Code に直接返します。CLI の出力を解析する必要はありません。
コマンドラインからロックを見るとこうなります。mclaude の README からです。
$ mclaude lock claim --slug fix-auth-middleware-race \
--description "Race condition when two requests write the same session key" \
--files src/auth/middleware.py src/auth/session.py
[lock] claimed fix-auth-middleware-race
# a second session tries the same slug
$ mclaude lock claim --slug fix-auth-middleware-race --description "same bug I guess"
[lock] fix-auth-middleware-race already held
by session: 665a44027f424db7
doing: Race condition when two requests write the same session key
設計の規則は、ハブはアクセラレーターであって、依存関係ではないということです。すべてはファイルの上で動きます。ハブが起動していれば、メッセージは WebSocket でリアルタイムに流れます。ハブが落ちていれば、ブリッジは同じ .claude/messages/inbox/ に、同じ形式で書きます。受信者は、git pull の後に、まったく同じものを見ます。mclaude のパッケージを削除しても、.claude/ のファイルは読めたままです。物語は Markdown、メタデータは JSON です。
mclaude demo --no-pause は約30秒かかります。2つのシミュレートされたセッション、ani と vasya を層に通し、何が起きたかの Mermaid の図を表示します。それは、プルリクエストや記事の中でも、そのままレンダリングされます。
エコシステムの空白
それから、ほかに誰がこれを解いているのかを見に行きました。隔離には、多くの答えがあります。git worktree(parallel-cc)、サンドボックス(Kmux)、エージェントごとの別ポートと、転送される環境変数、そして Anthropic の Agent Teams(タスクキューとクレームつき)です。どれも、「セッション同士が邪魔し合わない」を、共有状態がないようにすることで解決しています。
共有状態の調整は、ほとんど空っぽでした。ファイルベースのロックを持つのは、Dicklesworthstone の claude_code_agent_farm だけでしたが、それは tmux の中で20以上のエージェントを動かすためのオーケストレーションで、1つのプロジェクトに多数のチャットがある場合のものではありません。Claude Code 自身のトラッカーは、反対側からそのギャップを示しています。#19364 は、セッションのロックファイルを求めるリクエストでしたが、実装されることはありませんでした。#29217 は、複数のセッションからの同時書き込みによって .claude.json が壊れることを記録しています。
エコシステムは、共有状態を避けることには成熟していますが、それを調整することにはほとんど何もありません。
どれも新しいものではありません。ハートビートのタイムアウト、資源の名前が付いたロックファイル、追記しかしないログ、古いロックを引き継ぐ前の外部の確認、共有テーブルではなく資源ごとに1つのファイル。Google の Chubby、ZooKeeper、そしてこの40年の調整システムはすべて、同じことを土台にしています。AI エージェントは、私たちがすでに理解している分散システムの中の、もう1種類のプロセスにすぎません。
RFC 822 が合うのも同じ理由です。メールは、まさにこの抽象的な問題を解いています。同時に動いているとは限らないプロセス同士の、非同期の配達です。スレッドと配達の確認つきです。分散システムの世代が変わるたびに、人はメールが正しかったことを再発見します。2010年代ならメッセージキュー、RabbitMQ と Kafka でした。2026年なら AI エージェントです。
実際の使い方
3人、1つの C++ のプラグインプロジェクト、6つの並行する Claude Code のセッション。各人が自分の部分を担当します。アーキテクチャ、推論のパイプライン、UI です。
あるエージェントが、スクランブルモジュールの鍵導出を変えることに決め、mailbox/all/ を通じてブロードキャストを送りました。別のエージェントは、次のセッションの開始時に、コードを書く前にそれを見て、変更を考慮に入れました。メールボックスがなければ、非互換のフォーマットに対してコードを書いていたでしょう。マージの衝突がよくて、データの破損が最悪でした。
あるサブスクリプションで始めて別のもので続けるハンドオフは、文脈を作り直すのに本来かかっていた約4時間を節約しました。
どこで壊れるか、そして私が知らないこと
- 規模。 テストしたのは、3〜6の並行するセッション、2〜3人、1つの C++ プロジェクト、そしてウェブサイトの多数のセッションです。10のエージェントによる20以上のセッションは測定していません。受信箱が最初に壊れると疑っています。
.watcher_state.jsonによる重複の排除は、メッセージ数に対して線形で、数百のメッセージでは遅くなるでしょう。 - ハブなしの複数マシン。 ファイルのコアは、共有リポジトリを通じて同期されるので、相手側はメッセージを
git pullの後に見ます。数秒ではなく数分です。ハブは WebSocket で数秒を提供しますが、動かすプロセスがもう1つ増えますし、本番で試してはいません。 - ネットワークファイルシステムでのロックのレース。
O_CREAT | O_EXCLは、ローカルのファイルシステムではアトミックです。NFS や SMB では、その保証は実装次第で、.claude/locks/をそこに置く人は、レースに遭遇するかもしれません。 - 古いロックの引き継ぎは半自動です。
nvidia-smi、ps、git statusによるチェックは、人のために書かれています。自動化するには、資源の種類ごとに確認方法を知っているハンドラーが必要です。 - メールはセッションを起こさない。 フックだけでは、メッセージはアクティブなセッションが何かを入力したときに、UserPromptSubmit で現れるだけです。本物のプッシュ配達はありません(下の更新を参照)。
- 人がまだループの中にいる。 6つのエージェントが同時に、auth-middleware を書き直す必要があると決めたら、メールで交渉するでしょう。しかし、誰がそれを取るかは、まだ人間の判断です。mclaude が与えるのは交渉のための道具であって、自律的なオーケストレーションではありません。
4月以降の変化
2026年9月の更新。2026年9月23日に確認した、3つの訂正と2つの追加です。
- インストール。 4月に書いた
pip install mclaudeは動きません。パッケージは PyPI に公開されておらず、そこでは404が返ります。代わりに、リポジトリのクローンからインストールしてください(下のとおり)。 - issue #19364。 私はこれを開いたままのリクエストとして書いていました。しかし、この記事が公開される前の2026年2月28日に、stale bot が「not planned」としてすでにクローズしていました。実装されなかったというこのセクションの指摘は正しいままですが、開いていたわけではありませんでした。
- issue #29217。 私は「7日間で315件の壊れたバックアップ」と引用していました。その数字は、今のスレッドには出てきません。元の報告は、約40分で165件の破損したバックアップを数えていて、あるコメント投稿者は、7日間で少なくとも423件の破損を数えました。この issue は、2026年5月25日に不活発のためクローズされました。
- 第7の層。 mclaude の README には、この記事では扱っていないファイルベースのセッションのハートビートも書かれています。また、このパッケージは、上の手作業の規約よりもロックを厳しく扱います。30秒ごとのハートビートで、3分間沈黙すると、ロックは古いものとみなされます。
- セッション中のメール。 元のロシア語の原文の末尾にあった最初の計画は、その日のうちに出荷されました。記事の公開から数時間後、2026年4月23日の17:22 UTC にマージされた pull request #5 は、Claude Code の Monitor ツールのためのスクリプトを追加しました。それは30秒ごとに受信箱をポーリングし、新しいメッセージを、誰もプロンプトを入力しなくても、動いているセッションに渡します。ですから、上の「メールはセッションを起こさない」という限界は、フックだけのセットアップにのみ当てはまります。
メインブランチは、そのマージ以降コミットがありません。ロールアップのアイデアの出典だった MF0-1984 のリポジトリは、今は404を返します。
試してみる
git clone https://github.com/AnastasiyaW/mclaude.git
cd mclaude
pip install -e .
mclaude hooks install --apply
mclaude demo --no-pause
MIT ライセンス、Python 3.9 以上です。その背後にある原則は、別に原則18、複数セッションの調整として書かれていて、それは codex-claude-code-config のページで説明している設定リポジトリにあります。ツールのプロジェクトページは mclaude です。アプリ内蔵のセッション履歴より、なぜ自分のリポジトリのハンドオフを頼るのかは、見ることのない Claude Code のセッション45 MBで書いています。
ロシア語の原文は Habr に掲載されました。