2026.07.06
tmuxを使ってAI同士で議論させよう

AIに実装させる前に、別のAIに計画を尋問させたら?
この記事で話すこと(結論ファースト)
- 課題:AIに実装させる前に、計画の曖昧さや前提ズレをどう見つけるか
- 提案:Handoffグリルで、実装担当AIから計画担当AIへ質問させる
- 結果:実装前・実装中に、実際に設計の穴や認識違いを検出できた
こんにちは。グループ研究開発本部 次世代システム研究室のY.Uです。
この記事では、Claude Code、tmux、git worktreeを使って、親Claudeが立てた計画を子Claudeが実装前に尋問する「Handoffグリル」という開発フローを紹介します。
使っている仕組みはシンプルで、tmuxの send-keys と capture-pane を使い、AIセッション同士で質問と回答を中継しているだけです。
目次
課題:AIに実装させる前に、計画のズレをどう見つけるか
Claude Codeで開発を自動化していると、ある問題に気づきます。
私の開発は1つのセッションが全部やるのではなく、親セッションが計画を立て、実装は別のgit worktreeで動く子Claudeセッションに委譲するという構成で回しています。子はworktreeごとに独立しているので、複数本を並行して走らせても衝突しません。実装のたびに親は子セッションを立ち上げ、計画(brief)を送り、子がそれを実装してPRまで持っていきます。
このとき起きるのが以下の問題です。
それは、そう。
briefに書けるのは「何をするか」であって、「なぜそうしたか」「何を却下したか」「どのコードを見ておくべきか」は書ききれません。結果として、AIが自信満々にコミットした後で「そこじゃない」と気づきます。修正のためにもう一度コンテキストを積み直すのは地味にコストが高いです。
この問題をなんとかするために、「計画完成後・実装開始前に、実装担当のAIが計画担当のAIに質問する」 フローを作りました。名前は「Handoffグリル」。今回はその仕組みと、実際に何が起きたかを紹介します。
提案:Handoffグリルで、実装担当AIに計画を尋問させる
grill-me:人間とAIで計画を詰める既存スキル
Claude Codeには /grill-me というスキルがあります。これはMatt Pocockのskillsリポジトリにある同名スキルを、自分用にアダプトしたものです(MIT license)。このリポジトリは「コード固有でない一般的なワークフローツール」を集めた productivity ディレクトリを持っていて、grill-meの他にも会話をハンドオフドキュメントに圧縮するhandoffや、ユーザーに新しい概念を教えるteachなど、似た方向性のスキルが並んでいます。作者自身の言葉を借りると、これらは "designed to be small, easy to adapt, and composable" — 小さく・改造しやすく・組み合わせやすく作られています。今回のHandoffグリルも、その小さな部品を自分の用途(別AIとの中継)に合わせて組み替えた一例と言えます。
起動するとClaudeが計画や設計について徹底的に質問してきます。
Claude: この設計でXはどう扱いますか?
推奨: AよりBの方が整合性があると思います。なぜなら…
自分: そうね、Bで
Claude: では派生してYの扱いは?
推奨: …
特徴は2つあります。
ひとつは推奨案つきで聞いてくることです。「どうしますか?」ではなく「こう解釈したが合ってるか?」という形なので、ずれていればすぐわかります。逆に、あっている場合はOKというだけです。
もうひとつはコードベースで確認できることは自分で調べてから聞くことです。調べれば済む質問はしません。
本来の使い方は「人間が計画をドキュメントに書き起こす前に、Claudeに尋問される」というものです。draft段階で穴を炙り出すのが目的で、コミット後に修正するより圧倒的に安く済みます。
Handoffグリル:質問先を人間ではなく別AIへ中継する
通常の /grill-me は 人間 ↔ AI の対話です。
Handoffグリルでは、これを 子Claude ↔ 親Claude に置き換えます。子Claudeは /grill-me を起動しますが、質問の送り先をtmux経由で親のペインに向けます。親は人間と同じ立場で答えを返します。
通常: /grill-me → 質問が自分に届く → 自分が答える
Handoff: /grill-me → 質問がtmux経由で親paneに届く → 親Claudeが答える
通常のgrill-meとHandoffグリルの違い
通常
Handoffグリル
人間の代わりに「計画の文脈を持つ別のAI」がいるだけで、grill-meの仕組みはそのまま使えます。
実装:tmuxの標準機能だけでAIセッション同士をつなぐ
tmuxは公式リポジトリで "a terminal multiplexer: it enables a number of terminals to be created, accessed, and controlled from a single screen" と説明されているターミナルマルチプレクサです。ISCライセンスで公開されていて、OpenBSD・FreeBSD・NetBSD・Linux・macOS・Solarisで動きます。公式マニュアルはman tmux、オンラインではOpenBSDのman.openbsd.org版が読みやすいです。
一般的なユースケースはだいたいこの3つです。
- detach / attach: リモートサーバーで長時間かかる作業を走らせておいて、SSH接続を切っても処理は続行させます。あとで
tmux attachすれば同じ画面に戻れます。 - ウィンドウ/ペイン分割: 1つのターミナルを複数ウィンドウ・複数ペインに分けて、エディタ・ログ監視・シェルなどを並べて操作します。
- セッション共有: 複数人が同じtmuxセッションにattachして同じ画面を見ます(ペアプロや障害対応の実況など)。
今回やっていること——別々のAIセッション同士でメッセージをやりとりする——は、正直このどれとも違います。tmuxを「人間(または複数人)が画面を共有・持続させるツール」として使っているわけではなく、tmuxがスクリプトから叩けるように公開している低レベルの操作(send-keysでキー入力を注入する、capture-paneで画面内容を取得する)を、人間の代わりにプログラムから呼んでいるだけです。
send-keys — ペインにキー入力を流し込む
マニュアル上のシンタックスは以下です。
send-keys [-FHKlMRX] [-c target-client] [-N repeat-count] [-t target-pane] [key ...]
「ウィンドウまたはクライアントにキーを送信する」コマンドで、指定したpaneに対してキーボードから打ち込んだのと同じ入力を注入できます。任意の文字列を送りたい今回の用途では、キー名のルックアップを無効化してリテラルなUTF-8文字列として扱う-lを使います。
capture-pane — ペインの表示内容を取得する
同じくマニュアル上のシンタックスです。
capture-pane [-aeFHLpPqCJMN] [-b buffer-name] [-E end-line] [-S start-line] [-t target-pane]
ペインの表示内容をバッファまたは標準出力(-p)に書き出すコマンドです。-Jは折り返された行を結合してくれるフラグで、これがないと長い行がターミナル幅で分断されてgrepしづらくなります。
# ペインにキー入力を流し込む
tmux send-keys -t %77 "メッセージ" Enter
# ペインの表示内容を取得する
tmux capture-pane -p -J -t %77 -S -40
send-keys でテキストを送り、capture-pane で読み返します。これだけでAIセッション同士がメッセージをやりとりする土台が揃います。 tmux自体は何も特別なことをしていません。ターミナルが持つ「画面に文字を入力する」「画面の内容を読む」という機能を、プログラムから操作しているだけです。
tmux-talkは3スクリプト
この2コマンドを薄くラップした tmux-talk というスクリプト群を作りました。
~/.claude/tmux-talk/
├── send.sh # paneにメッセージを送る
├── read.sh # paneの内容を取得する
└── list.sh # セッション一覧
tmux-talkの役割
send.sh
相手paneにメッセージを送る。AI同士の往復の主役。
read.sh
paneの状態を明示的に覗く。返信待ちのポーリングには使わない。
list.sh
tmuxセッションやpaneを確認する補助役。
やりとりの主役はsend.shだけです。 相手のpane(=相手のClaude Codeセッション)にsend-keysでテキストを流し込むと、人間が打ち込んでEnterを押したときと同じ扱いになります。相手のセッションはそれを新しい入力として受け取り、そのターンの応答を返します。だから質問への回答も、相手が能動的にsend.shで自分のpaneへ送り返してきます。待っている側がポーリングで取りにいく必要はありません。
read.shは往復のやりとりには使いません。返信を待つためにポーリングするのは無駄な呼び出しを重ねるだけで、そもそも返信は上記の通りsend.shで押し込まれてくるので取りに行く必要がありません。read.shを使うのは、たとえば人間側が「今このペインで何が起きているか」を明示的に覗きたいときのような、能動的に確認したい場面に限ります。
send.sh の実装上のポイントがひとつあります。load-buffer + paste-buffer で改行を含む長いテキストを一括送信した後、Enter送信のタイミングが早すぎると改行扱いとなって正しく送信できない問題があります。意図的に遅延を入れることで、より確実に送信できるような設計になっています。
printf '%s' "$MSG" | tmux load-buffer -b "$BUF" -
tmux paste-buffer -d -p -b "$BUF" -t "$TARGET"
sleep 0.5
tmux send-keys -t "$TARGET" Enter
CLIで動くAIなら、別モデルにも接続できる
tmux-talkはターミナルに「文字を入力する」「画面を読む」だけなので、paneで動いていれば何でも接続できます。
現状はClaude同士で動かしていますが、原理的には:
- Claude ↔ Codex
- Claude ↔ Antigravity CLI
- Claude ↔ ローカルLLM
という組み合わせも成立します。
同じモデルだと設計の盲点を共有している可能性があります。計画を立てたClaudeとは異なるモデルにgrillさせることで、「それは本当にそうか?」という切り口が変わる、いわゆるセカンドオピニオンとして有効に活用できる可能性があります。
[parent ALIGNED] まで実装を始めない
Handoffグリルの全体像
親Claude
計画と背景文脈を持つ
子Claude
別worktreeでbriefを受け取る
実装前の質問
子Claudeが計画の穴や曖昧さを確認する
親Claudeが回答
計画の全文脈を持つ側が判断を返す
実装開始
合意後に子Claudeがコミット・PRへ進む
子はgit worktreeを使った別の作業ツリーで動くので、並走しても衝突しません。往復ともsend.shだけで完結し、read.shは登場しません。
メッセージのタグ規約
自分の環境では、全メッセージをタグ付きで送受信しています。
子 → 親: [grill-me Q1/from %77] ここの設計判断の根拠は?
推奨: Aだと思います。なぜなら…
親 → 子: [parent A1/from %6] そう、Aで合ってる。ただし…
合意: [parent ALIGNED/from %6] proceed
[parent ALIGNED] ゲート
子Claudeは計画の疑問点を親Claudeへ投げる。未合意のままコミットしない。
[parent ALIGNED] を受け取ってから、子Claudeが実装へ進む。
親からの回答はsend.shで子pane自身に直接送り込まれます——人間が返信を打ち込んだのと同じ形で子の入力欄に入るので、子は次のターンでそのまま [parent A1] タグを処理すればいいだけです。[parent ALIGNED] を受け取るまでコミット・プッシュは禁止 というルールにして、整合してないまま進む事故を防止しています。
効果:実装者の視点だからこそ、計画の穴が見える
実装者の新鮮な目線が、計画者の盲点を補う
直感的には「上位AIが下位AIを審査する」方が正しそうですが、実際は逆が機能します。
計画側のAIはコードを見落とすなどした際に自分のミスになかなか気づけません。実装者の新鮮な目線でこそ、計画の穴が見えます。 briefを受け取ったAIが「ここどういう意図ですか?」と聞いた瞬間に、初めて前提が崩れていることに気づく、というパターンは実際よく起こります。
実際に起きたこと:見つかったズレの3パターン
Handoffグリルで見つかったズレの型
前提が噛み合わない
brief上の想定と、実際のinterfaceや設計制約が違っていた。
依存が存在しない
「当然ある」と思っていた仕組みが、このプロジェクトにはなかった。
実装中に再確認する
ALIGNED後でも、新しい懸念が出たら手を止めてre-escalateする。
運用していると、grillのたびに半分くらいの確率で以下のどれかが起きます。
パターン1:briefの前提がコードベースと噛み合っていなかった
「ある値をcallerが渡す」という設計をbriefに書きました。子がコードベースを確認したところ、親は実際の設計を見誤っており、現実にはその値に対応するフィールドが実際のinterfaceに存在しないことを発見しました。
grillがなければ、存在しないフィールドを参照するコードがコミットされていたはずです。
パターン2:依存コンポーネントが存在しなかった
briefで「Xを使って実装する」と書きました。子が実装しようとしたところ、そのXはこのプロジェクトでは使われておらず、別の仕組みで代替されていることを発見しました。
長く触っているコードベースほど「当然あるもの」という誤った前提を持ちやすいです。自分では見えていない盲点を、新鮮な目線が炙り出した形です。
パターン3:一度ALIGNEDしても、後で懸念が出れば聞き直せる
これまでの2パターンは最初のHandoffグリルの段階で見つかったものですが、これは少し違います。同じ概念を指す2つの定数がコードベース内に存在し、微妙に異なる値を持っていました。計画段階では「2つある」ことは把握していましたが「中身が違う」ことには気づいていませんでした。
発見したのは、子が実装中に両方の定数を触る必要が出てきたタイミングです。すでに[parent ALIGNED]が出て実装が始まった後でしたが、そこで手を止めて親に再確認し、どちらを正とするか判定して統一しました。
計画段階のレビューでは中身の差分まで確認しにくく、実装者が初めて気づく類の問題でした。Handoffグリルは着手前の一発勝負のゲートではなく、実装中に新しい懸念が出てきたら、その都度また聞き直せる——という例もあります。
運用:コストは10〜15分、ただしEnter事故には注意
運用コスト:10〜15分の確認で手戻りを減らす
# 1. 親ペインのIDを記録(briefに書く)
echo $TMUX_PANE # → %6
# 2. 子を召喚(worktree + tmuxセッション + Claude起動)
spawn-child.sh feature/my-task
# → CHILD_PANE=%77 CHILD_SESSION=my-task WORKTREE=/path MODE=created
# 3. briefを書いて送信
send.sh my-task "$(cat /tmp/brief.txt)"
# 4. [grill-me Q<N>] が自分のターンとして届くのを待つ
briefの作成に5分弱、Q&Aの往復が3〜5問ほどです。合計10〜15分程度のオーバーヘッドですが、コミット後に前提崩れを修正するコストと比べると、投資対効果は高いです。
運用上の注意:Enterキー同送のリスク
send-keysは文字列を送った後に必ずEnterも送ります。相手が何もしていない待機状態ならこれで問題ありませんが、相手が何か別のことをしている最中だと、そのEnterが事故を起こします。実際に踏んだのは次の3パターンです。
1. 確認ダイアログを開いている最中に着弾する
Claude Code標準のAskUserQuestion機能(選択肢から1つ選ぶような、ターミナルUIの確認ダイアログ)を開いて選択肢を吟味している最中に、子からのsend.shがそのpaneに着弾したケースです。文字列と一緒にEnterまで入るので、表示されている選択肢のどれかを問答無用で確定させてしまい、しかも子が送った本来の報告内容はダイアログの裏に隠れて消えてしまいます。
対策: 判断を求めるときはダイアログ形式ではなく平文で提示し、通常の返信として受け取るようにします。並行してtmux-talkの着弾がありうる状況では、選択式UIそのものが暴発の的になるので避けるのが一番確実です。
2. 人間が入力中に着弾する
人間が別の文章を書きかけている最中にsend.shが届くと、書きかけの内容とこちらが流し込んだ文字列が混ざった状態でEnterが押されてしまい、意図しない内容がまとめて送信されてしまいます。
対策: 送信前にcapture-paneで入力欄に書きかけの何かが無いか確認します。書きかけの内容があれば、送信を一時的に保留します。
3. サジェスト表示ONのままpaneを読む
Claude Codeには入力欄に補完候補(サジェスト)を表示する機能があります。これがオンだと、capture-pane/read.shでpaneを覗いた時にサジェストの文字列が入力欄に乗っていて、ユーザーが何か予期しない内容を送信しようとしているようにAI側が誤解してしまうことがあります。
対策: 子セッションは--prompt-suggestions falseで起動して、サジェスト機能自体を最初から出さないようにしておきます。
いずれも「Enterが自動で付いてくる」「補完候補も画面に表示される」という、tmux/Claude Code側の通常仕様を運用側で吸収する形の対策で、tmuxの機能そのものに罠があるわけではありません。
まとめ
tmuxのベースコマンドだけで、AIセッション間のメッセージバスが作れる
使っているのは send-keys と capture-pane。tmux自体に特別な拡張はしていません。
やりとりの往復は send.sh だけで完結する
相手paneへ入力を押し込むだけで、相手のターンが動きます。返信待ちのポーリングは不要です。
[parent ALIGNED] が、整合していないまま進む事故を防ぐ
合意が出るまでコミット・プッシュしないことで、実装前の確認をゲートにできます。
「子が親に質問する」方向が、計画の穴を炙り出す
計画者の慣れによる盲点を、実装者の新鮮な目線が補います。
CLIで動くAIなら、異モデルのセカンドオピニオンにも使える
Claude同士だけでなく、Codex・Antigravity CLI・ローカルLLMとの組み合わせも狙えます。
AIに作業を任せるほど、「何を伝えたか」より「何が伝わったか」が重要になります。Handoffグリルはその検証を、コードを書く前に済ませる仕組みです。
GMOインターネットグループでは、次世代システム研究室で一緒に働くエンジニアを募集しています!募集職種一覧は以下をご覧ください。
グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。
Follow @GMO_RD


