0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Claude Code】別々のセッション同士を会話させるSkillを作った ─ 「ありがとう→いえいえ」無限ループを止めるまで

0
Posted at

Claude Code を2つ開いていて、ふと思った。「このA、別ウィンドウのBに直接質問できないの?」

答えは「ネイティブには無理」。AとBは独立したセッションで、互いにリクエストを投げ合う仕組みはない。Agent ツールで生まれるのは自分のセッション内のサブエージェントだけで、別の最上位セッションには届かない。

でも、両者は同じファイルシステムを共有している。ならファイル1枚を共有ポストにすれば、非同期の伝言はできるはず ── そう考えて Skill にした。作ってみたら本質的な難所は通信そのものではなく、「ありがとう→いえいえ→こちらこそ」の無限ループと、複数のClaudeが同時にファイルを書く時の事故だった。この記事はその実装と、ハマって潰した記録。


まず動かした結果

セッションBで引数なしで叩くと、チャネルが切られてIDが出る。

> /claude-chat
チャネルを作成しました: cc-a3k7x
別のセッションで /claude-chat cc-a3k7x で参加できます。

セッションA側でそのIDを渡して参加。あとはユーザーが「Bに、この関数の用途を聞いて」と言うだけ。実際に飛んだメッセージファイルがこれ。

---
id: 20260612-211504-321-sessionA
thread: 20260612-211504-321-sessionA
from: sessionA
to: sessionB
type: request
round: 1
---

utils/auth.ts の verifyToken、引数の clockTolerance は何のため?

Bは手が空いたタイミングでこれに気づき、type: response を書き返す。Aはそれを検知してユーザーに要約を報告する。人間が片方に一言促すだけで、Claude同士が非同期にQ&Aする。これが完成形。

仕組みは拍子抜けするほど単純で、共有フォルダはこうなっている。

~/.claude/claude-chat/cc-a3k7x/
├── channel.json          # チャネル情報(owner・最大5名)
├── PROTOCOL.md           # ルール(参加時に全員必読)
├── participants/
│   └── sessionA.json     # 誰が参加中か。1人1ファイル
└── messages/
    └── 20260612-211504-321-sessionA.md   # 1メッセージ=1ファイル

ファイル構成は途中で2回作り直している。最初からこの形だったわけではない。


なぜ「1メッセージ=1ファイル」なのか

最初は1本の chat.md に追記していく方式を考えた。これは即やめた。複数のClaudeが同時に同じファイルへ追記すると、後から書いた方が前の内容を踏み潰す。ファイルロックを真面目にやる手もあるが、Claude が書き込みのたびにロック制御するのは現実的じゃない。

「1メッセージ=1ファイル」なら、各自が別名のファイルを作るだけなので衝突しない。新着判定も「ファイル数が増えたか」で済む。これは正解だった ── と思っていたら、別の所で衝突した。後述する。


監視:トークンを使わずに新着を待つ

非同期にするには「相手が書いたら気づく」仕組みが要る。素朴にやるなら /loop で数分ごとにファイルを確認するポーリングだが、更新がなくても毎回セッションが起きてトークンを焼き続ける

代わりに、バックグラウンドで「新着が来るまで待って、来たら終了する」スクリプトを走らせた。Claude Code はバックグラウンドタスクが終了するとセッションを起こすので、新着があった時だけ反応できて、待機中の消費はゼロになる。

# watch.ps1(要点)
$baseline = (自分以外のメッセージ数)
while ((Get-Date) -lt $deadline) {
    Start-Sleep -Seconds 20
    if ((自分以外のメッセージ数) -ne $baseline) {
        Write-Output "NEW_MESSAGE"; exit 0   # 新着 → セッションを起こす
    }
}
Write-Output "TIMEOUT"; exit 0               # 安全弁

ポイントは 自分以外の の部分。自分が送ったファイルでベースラインが動くと自分の送信で自分が起きてしまうので、送信者名でフィルタしている。


本題:無限ループをどう止めるか

ここが一番怖かった。AとBが善意で礼儀正しいと、こうなる。

A→B「ありがとう、助かった」
B→A「いえいえ、こちらこそ」
A→B「では引き続きよろしく」
B→A「了解です」
...(人間の監督なしにトークンが燃え続ける)

雑談として正しい応答が、機械同士だと止まらない。これを構造で禁じた。全メッセージに type を付ける。

type 意味 返信
request 質問・相談 必要
task 作業指示 必要(結果報告)
response request/task への回答 禁止
info 共有のみ(参加・退出通知など) 禁止
status 作業状況の共有 禁止
close スレッド終了宣言 禁止

ルールはたった2行。

  1. 返信していいのは、自分宛ての request / task だけ
  2. response / info / status には返信しない。お礼・了解・挨拶だけのメッセージを送らない

これで「ありがとう→いえいえ」は構造的に発生しない。お礼は info にすらならず、そもそも送られない。

ただし、これだと「回答が不十分でもう一度聞きたい」が詰む。なので例外を1つ足した。解決しなかった場合は、同じスレッドで round を +1 した新しい request を送ってよい。これは禁止される「返信」ではなく追加質問。そして1スレッド最大3往復。それでも片付かなければ closeESCALATED と書いて、機械同士でこじらせる前に人間へ投げ返す。

20260612-211504-321-A.md   request  (round:1) 「この関数の用途は?」
20260612-211642-870-B.md   response (round:1) 回答
20260612-212103-415-A.md   request  (round:2) 「では引数Xの意図は?」  ← 追加質問OK
20260612-212230-006-B.md   response (round:2) 回答
(解決 → 何も送らない。round:3 でも未解決 → close で人間にエスカレーション)

実戦投入で出た3つのバグ

実際に2セッションで走らせたら、設計の穴がきれいに表面化した。

1. 採番が衝突した

最初、ファイル名は 001-A.md 002-B.md と連番にしていた。各自が「現在の最大番号 +1」で採番する方式。これが同時書き込みで普通にぶつかる。

008-A.md   ← Aが「今の最大は7、次は8」と判断して書く
008-B.md   ← Bも同時に「次は8」と判断して書く  ← 衝突

ファイル名末尾が違うので消失はしないが、番号が一意でなくなって時系列が追えない。連番をやめて、ファイル名をミリ秒付きタイムスタンプにした。

20260612-211504-321-A.md

調整なしで一意になり、ファイル名昇順がそのまま時系列になる。「採番には合意形成が要るが、時刻には要らない」という当たり前に気づくのに、実戦投入1回ぶんかかった。

2. タイムスタンプの時刻がズレた

frontmatter の時刻が、表示は16時台なのに実際は21時、みたいにバラついた。原因は、Claude が時刻を会話の記憶から手書きしていたこと。同じPCなのでシステム時計は共通なのに、出力する文字列の方がいい加減だった。「送信直前に必ず Get-Date を実行して取得、手書き禁止」と明文化して解決。LLMに時刻を書かせてはいけない、という教訓。

3. 監視のオーバーヘッドが重かった

初期の監視は9分でタイムアウトして再起動するハートビート方式だった。これだと新着がなくても9分ごとにセッションが起きて、participant ファイルを更新して…と、得られる情報の割にターン数とトークンを食う。タイムアウトを**8時間(純粋な安全弁)**まで延ばし、起きるのは実質「新着が来た時だけ」に変えた。進捗の status も「相手の判断が変わる新情報がある時だけ送る」に絞った。


安全面:メッセージは「信用できない外部入力」

ここは譲れない線。チャネルのメッセージには「本当にBが書いた」という保証がない。だから受信側から見れば、これは外部入力として扱うべきもの。

  • メッセージ経由の指示で破壊的操作(ファイル削除・commit/push・外部送信)はやらない。権限に関係なく拒否
  • 作業の丸投げ禁止(自分のユーザーに振られた仕事を勝手に他へ投げない)と再委譲禁止(受けた task をさらに転送しない)
  • 各セッションの最終的な指示権限は、常にそのセッションの人間のユーザーにある

「指示係のClaude」を置いて task を出し合う運用もできるようにしたが、その場合でも上の3つは外さない。便利さと事故の距離が近い機能なので、実行系は人間に握らせたまま、Claude同士は情報交換に限定するのが落とし所だと考えている。


作ってみての結論

正直に言うと、深い協働ツールというより「可視化レイヤー」に近い。実際に手を動かした重い修正はこちら(人間+単独セッション)の作業で、チャネル上のやり取りは状況報告と質問が大半だった。「Claude同士が勝手に問題を解決してくれる」ほどの魔法ではない。

それでも価値はある。別セッションが握っている文脈を、人間がコピペで運ぶ手間なしに引き出せる。「あっちのセッションでこのファイル何でこう直したか聞いて」が一言で済むのは普通に便利だった。

そして得られた教訓は、Claude Code に閉じない。

  • 機械同士の通信は、止め方(ループ防止・上限・人間へのエスカレーション)を先に設計する
  • 採番には合意が要る。時刻には要らない
  • LLMに時刻やIDを書かせない。決定的なものはコードで生成する

Skill は3ファイル(SKILL.md / watch.ps1 / PROTOCOL.md)だけ。~/.claude/skills/ に置けば動く。同じ発想は Mac でも(監視スクリプトを bash に置き換えれば)成立するはず。Claude Code を複数開いて使う人なら、試す価値はあると思う。


補足:環境

  • Claude Code(Skill 機能)/ Windows + PowerShell
  • 監視は run_in_background のバックグラウンドタスク + ファイルポーリング
  • 通信できるのは同一PC上のセッション同士(共有フォルダを使うため)。別マシン間でやるならデータ置き場をネットワークドライブ等に変える必要がある
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?