dispatch-agent について
dispatch-agent は Claude arm 向けのスキルで、分離された dispatch worker の起動から、制御されたコミット、取り込み、検証、後片付けまでを案内します。Doctrine の dispatch ワークフローを運用する調整役を対象としています。
主な機能
- 01指定したベースから worker を分岐
- 02起動前と worker の安全性を確認
- 03worker_commit による worker のコミットを案内
- 04worker の取り込み、検証、完了、回収
- 05ライブ worktree を使う代替手順
活用シーン
- 01ファイルが重ならないタスクで worker を並行起動
- 02コミット済みの変更を調整用ツリーに取り込む
- 03通常経路が使えない場合に未コミット変更を回収
MCP 対応
worker は worker_commit MCP ツールでコミットし、調整役は dispatch_import、dispatch_conclude_phase、dispatch_reap MCP ツールを使うよう案内します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ davidlee/ doctrine --skill dispatch-agentインストール後、Claude Code で/dispatch-agentを入力して読み込まれたか確認します。
詳細
ディスパッチ — claude arm
Agent ツールを使ってワーカーを起動します。ハーネス共通のファネルと駆動ループは
/dispatch ルーターにあります。このスキルは起動テンプレートと、このアーム固有の着地処理です。
起動前 — ベースを設定し、起動ディレクトリに移動する
ワーカーのワークツリーは、ハーネス本体ではなく、doctrine の WorktreeCreate フック
(doctrine worktree create-fork)によって作成されます。このフックは、ディスパッチワーカーを
位置によって判別します。Agent のペイロード cwd が設定ディレクトリ
<coord>/.doctrine/state/dispatch/spawn/ である場合に限り、起動はディスパッチワーカーになります。フォーク元のベースは 明示的に指定され、
そのディレクトリ内の base ファイルが使われます。cwd の HEAD が使われることはありません。cwd は
判別条件であり、ベースの参照元ではありません。
設定にはもう一つの役割があります。フックはワーカーの
DispatchRecord も用意します。登録時に worker_commit がワーカーの不透明なエージェント ID を照合するためのものです。設定されていない起動は、レコードのない無害なパススルーのワークツリーになります。そのため worker_commit は unknown-agent を返し、フォークコミットを着地させることはできません。
ベースクリーンの確認(arm-spawn の前)。 設定/起動の前に、またフォークのブランチを作成する前に main で、ベースがクリーンであることを確認します。doctrine check prove(変更を加えない fmt-check + lint)を実行します。RED のベースはベースの不備であり(オペレーターがフォーマットしてコミットするか、ワークツリーを準備します)、ワーカーの差分に決して含めず、自動修正もしません。これはファネルに記載された起動前の確認と同じです。バッチごとに一度実行します。
各起動(または並列バッチ)の前に:
doctrine dispatch arm-spawn --base <B> --slice <N> --phase PHASE-NN—<coord>/.doctrine/state/dispatch/spawn/base = <B>と、フォークをバインドする2つのスロットを書き込み、起動ディレクトリの絶対パスを出力します。冪等です。B′で再設定すると、3つすべてを書き換えます。バインドの両方の要素が必須です。片方だけの設定は何もバインドせず、設定時に拒否されます(half-arm:)。どちらも設定せずに起動することは許可されますが、警告が表示され、引き渡し時にフォークのworker_commitがunprovable-forkとして拒否します。ワーカーの作業時間を丸ごと無駄にすることになります。- その起動ディレクトリに
cdで移動します。これが設定の合図です。ペイロード cwd が起動ディレクトリの場合に限り、フックは B からフォークします。 - Agent の起動(以下)を実行します。ファイルが重複しない並列バッチの場合は、一度設定した後、起動ディレクトリから N 個の起動を実行します。すべて同じ B を読み取り、各フックはそれぞれの
nameから独自のbranch/dirを導出します。 - 起動後は coord ルートに
cdで戻ります(位置による設定解除)。coord ルートから実行された無害なisolation:worktree起動はパススルーします(ワークツリーは用意されますが、ワーカーとしてフォークされません)。起動ディレクトリ内からの起動のみがディスパッチワーカーになります。baseファイルが残っていても何も起きません。トリガーはファイルの有無ではなく cwd の位置です。
逐次駆動: 各フェーズで設定し直します。B で設定し、起動ディレクトリに移動して起動し、戻ります。ファネルのコミットで coord の HEAD が進みます。次のフェーズでは B′ で設定します。ベースは明示的なので、設定から作成までの間に coord の HEAD が変わっても影響しません。既定の cwd は coord ルートです。ワーカーを起動するときだけ起動ディレクトリに移動します。
配置の前提条件(ISS-031): 調整用ワークツリーとその設定ディレクトリは、プロジェクトルート内になければなりません。規約では .dispatch/SL-<n> を使います(dispatch setup --dir .dispatch/SL-<n>)。/workspace/<repo> をルートとする bubblewrap などの cwd 制限付きジャイルでは、外側の隣接ディレクトリへの cd は、次の Bash 呼び出し時にプロジェクトルートへ黙って戻されます。--dir がルートの外を指す場合、dispatch setup は claude arm でフェイルクローズします。信頼する前に、別の Bash 呼び出しで cd <spawn-dir> ; pwd をそのまま実行し、配置を確認してください。
Spawn
BASE GUARD — 読み取り/編集/コミットの前に、必ず最初に実行してください。チェックに失敗した場合は停止し、何も書き込まないでください:
- git status --porcelain → 空でなければなりません(作業ツリーがクリーン)
- git rev-parse --git-dir と --git-common-dir → 異なっていなければなりません(分離されたリンク済みワークツリーであり、メインツリーではない)
- git merge-base --is-ancestor HEAD → 終了コード 0 でなければなりません(HEAD がベース から派生している)
- grep prerequisite seams: → 存在しなければなりません いずれかのチェックに失敗した場合:停止し、作成もコミットもせず、"base-guard-failed: " と報告してください。
チェック #2 は、オーケストレーターの not-isolated ベルトに対応する、ワーカー側のミラーです —
ワーカーの git-dir と git-common-dir が同じ場合、分離されたワークツリーではありません。
subagent_type: dispatch-worker
isolation: worktree
prompt: <pre-distilled worker prompt, including the base-guard block above>
プロンプトでは、必ずセルフコミットを指示してください: 最後に worker_commit MCP ツールを呼び出すよう指示します。引数には、コミットメッセージとともにワーカー自身のワークツリー名(パスではありません)を指定し、構造化された引き継ぎ情報で返された oid(fork_tip)を報告してください。jail 内では生の git commit は失敗します(.git が読み取り専用)—
worker_commit がワーカーに許可された唯一のコミット手段です。Refused の結果になった場合、ワーカーは理由をそのまま報告して停止します。forbidden-zone/commit-gate-red による拒否を回避しようとして再試行してはいけません。
Post-spawn funnel — ワーカーがセルフコミットし、オーケストレーターが取り込む
Primary(B):ワーカーの差分は、ワーカー自身の dispatch/<agent> ブランチ上に、マージコミットではないコミット C として 1 つ到着します(C^ == B)。 ゲート付き worker_commit ツールがサーバー側で取り込みます(セキュリティ境界はベルトです:空でない差分 → forbidden-zone スコープ → HEAD == B → check commit ゲート)。そのため、この差分はフォーク先で永続化されます — ワークツリーが破棄されても残り、再開/修正作業はコミット済みの先端から続行できます。
順序を決めるのはオラクルであり、あなたではありません。 doctrine dispatch next --slice <N> が次に行うべきアクションを 1 つ指定し、ルーターがループを説明します。以下は、この経路における各指示の仕組みであり、固定された手順ではありません。
到着(ファネルが差分を認識する前)。
- フッター。 Agent の戻りフッターから
worktreePath:を読み取ります。 フッターがない/worktreePath:がない ⇒ 分離されたツリーが作成されていません(フックの中断またはメインツリーへのフォールバック)⇒ 中止し、ファネルに進まないでください。再ディスパッチするか、フックが失敗している場合はサブプロセス経路に切り替えてください。 - 識別情報。
worktreePath(規範となるデータであり、実行時に証明済み)から導出します:name = basename(worktreePath)、branch = dispatch/<name>。フッターのworktreeBranchフィールドは読み取らないでください — フックで作成されたツリーではundefinedです。 - ワーカーの検証。
doctrine worktree verify-worker --base <B> --dir <worktreePath> --branch <branch>拒否された場合は中止します:no-worker-head / not-isolated / unstamped / wrong-base / branch-mismatch。コミット後の HEAD を受け入れます(merge-base --is-ancestor B HEADを検査し、HEAD が B の子孫であることを確認します。HEAD == Bではありません)。--branchはディレクトリとブランチを対応付け、両方のベルトで同一のワーカー状態を検証できるようにします。
import — dispatch_import{slice, name: <branch>}(MCP。呼び出しは next が出力します)。スライスを使ってサーバー側で coord ツリーを特定し、classify_import スコープベルトを合成前の HARD ゲートとして実行します(.doctrine//.claude/ は拒否、未宣言スコープも拒否 — 拒否された場合は何も取り込まれません)。merge-tree(オブジェクト DB のみ、作業ツリーは使用しません)を使って coord-tip ⊕ worker-tip を合成し、ワーカーの AUTHOR を保持したマージコミットではないコミットを 1 つ作成して取り込みます。インポートとコミットは一体化されているため、オーケストレーターが別途コミットする手順はありません。Refused{reason} の場合は、報告して停止してください(merge-conflict、head-moved、undeclared-scope などを自動解決してはいけません)。差分はすでにコミットゲートを通過しています(worker_commit がゲートを実行済み)。そのため、この経路ではインポート後に個別の prove 実行は不要です。
verify — doctrine dispatch verify --slice <N> --phase PHASE-NN(CLI)。
調整ワークツリーでフェーズのテストスイートを実行し、合格/不合格の証跡を記録します。不合格の証跡があると、次の指示は triage-verify-failure になります。
conclude — dispatch_conclude_phase{slice, phase, code_start, code_end}
(MCP;next はファネル行からすべてのパラメーターを設定して表示します)。1 回の呼び出しで 2 つの処理を行います。Git で無視されるフェーズシートを completed に切り替え、コミット済みの (code_start, code_end) 境界行を記録します(フェーズ単位の UPSERT、作業ツリー不要)。この切り替えは、調整シート(次の実行可否を判断する権限を持つシート)と PRIMARY シート(ツリーの prepare-review が完了セットを読み取るシート)の両方に反映されるため、slice phase --status completed -p <primary> を手動で再実行する必要はありません。この coord→primary ミラーリングは、単一のライター set_phase_status(ISS-212)にあります。そのため、conclude 固有の処理ではありません。稼働中の dispatch/<slice> ワークツリー配下で coord ツリーのステータスを completed に切り替えると、このツール経由でも、直接 slice phase --status を実行した場合でもミラーリングされます。再試行時は冪等です。唯一の障害時の結果は、シートは切り替わったものの境界がコミットされていない状態です。この場合も再試行で再構成されます。
reap — dispatch_reap{slice, name: <branch>}(MCP)。パッチ ID による取り込み済み判定(git cherry)を実行します。パッチが coord の履歴にないフォークは拒否し、取り込み済みフォークのワークツリーとブランチを削除します。reap は必ずこの判定を通してください。未取り込みのツリーに対して git worktree remove --force を直接実行してはいけません(未取り込みの差分の唯一のコピーを削除してしまいます。このツールの拒否処理は、直接削除する場合にはない安全策です)。
フォールバック(A)— 稼働中ワークツリーからの取り込み
プライマリを実行できない場合の緊急時の手順です。MCP サーバーが停止している場合、またはワーカーがプロビジョニングされておらず(worker_commit → unknown-agent)、フォークのコミットが存在しない場合が該当します。ワーカーの未コミット差分は引き続きワークツリーに存在し、Agent の返却後も保持されます(WorktreeRemove フックがなく、ハーネスが自動で reap しません)。これはバイパス手段ではありません。forbidden-zone/commit-gate-red/undeclared-scope による拒否はワーカー差分の欠陥を示します。修正するか停止し、回避して取り込んではいけません。
- 上記のとおり
verify-workerを実行します(このモードでは HEAD == B — コミットはありません)。 doctrine worktree import --base <B> --from-worktree <worktreePath> --slice <N>— 稼働中の追跡対象および未追跡の差分を収集し、同じclassify_import判定を実行して、Bにコミットせず適用します。その後、プロセス内で拒否・停止の検証ゲートを実行します (フォーマット未適用または lint エラーのある差分はステージされた状態で停止し、自動修正はされません)。- 調整ブランチ上で自分で 1 件コミットし、境界を記録します。
doctrine dispatch record-boundary --slice <N> --phase PHASE-NN --code-start <B> --code-end <B+1>。 - 取り込み済みの差分を条件に reap します。この手順では coord のコミットが作成されるまでツリーが唯一のコピーです。削除処理は
&&でコミットの後に連結してください。
これが pi/サブプロセス側で使える唯一のモードでもあります。bwrap で隔離されたワーカーはコミットできません(/dispatch-subprocess)。claude 側で自己コミットするよう指示したり、期待したりしてはいけません。
ワーカーの隔離(SL-182)
claude ワーカーは、インストール済みの 2 つの PreToolUse フック(プラグインの
hooks.json → doctrine worktree pretooluse)によって隔離されます。Bash は
ネストされた bwrap の隔離環境に透過的に書き換えられます(worktree は rw、それ以外はすべて ro)。worktree 外での Edit|Write
は拒否されます。オーケストレーター(agent_id なし)はそのまま通過します。
隔離は構造によって実現されており、ワーカーが協力するためのフラグはありません。worker_commit は
この壁を意図的に、単一目的で回避する手段です。隔離されていないサーバー
がワーカーに代わってコミットし、このツールの安全策が、壁ではなくセキュリティ境界になります。
- ホットリロードなし。 プラグインの
hooks/の変更はセッション開始時にのみ読み込まれます (docs/claude/plugins-reference.md:394)。doctrine installの実行後(またはフックを編集した後)は、/reload-plugins(より軽量)を実行するか、セッションを再起動して変更を反映してください。そうしないと、古いフック(またはフックなし)が有効なままです。 - インストール時はフェイルクローズ。 インストールされたコマンドは解決済みの絶対パス
の exec に
|| exit 2ガードを付けているため、バイナリが見つからない、または古い場合は、ツールを隔離なしで実行せずに拒否します(PreToolUse は通常、フェイルオープンです。終了コード 2 の場合のみブロックします)。 - 回避策。 Bash ラッパーが壊れると Bash を使えなくなる場合がありますが、Edit/Write は Bash の制限を受けません。Edit を使って問題のフックを無効にし、その後
/reload-pluginsを実行してください。
危険信号
絶対にしないこと: まず arm-spawn を実行して spawn ディレクトリに cd してからでない spawn(coord ルートからの spawn は無害な素通りであり、dispatch worker にはなりません。また、その worker_commit は unknown-agent を拒否します);フッターの
worktreeBranch を読み取ること(フックが作成した tree では未定義です。代わりに
branch = dispatch/basename(worktreePath) を導出してください);worktreePath: フッターを返さなかったワーカーを funnel に流すこと;fork のコミットが存在する場合に稼働中の tree をインポートすること(差分はコミットです。dispatch_import で取り込んでください);worker_commit/dispatch_import の拒否を回避するためにフォールバック(A)を使うこと(拒否は不具合または停止を意味し、迂回してはいけません);未取り込みの tree に対して生の git worktree remove --force を実行すること(dispatch_reap の patch-id 判定を通じて回収してください);pi arm 上で自分自身がコミットしたと主張すること;dispatch setup --dir をルート外の兄弟ディレクトリに向けること(ISS-031);dispatch-worker 以外の subagent_type で spawn すること;ここで fork や bwrap を実行すること(それは /dispatch-subprocess の役割です);並行して取り込むと主張すること(v1 はベースごとに 1 つずつ取り込みます);記憶を頼りに取り込み用の動詞を手作業で順番に実行すること、または git を手作業で調べて funnel の状態を判断すること(doctrine dispatch next --slice <N> を実行してください)。
必ずすること: arm-spawn --base B --slice N --phase PHASE-NN(両方の結び付けを指定し、片方だけにしない)を実行してから、spawn の前に spawn ディレクトリへ cd し、spawn 後に coord ルートへ戻ること;subagent_type を dispatch-worker に固定すること;要約したワーカープロンプトに base-guard ブロックと worker_commit による自己コミットの指示を含めること;worktreePath から branch を導出すること;取り込み前に verify-worker を実行すること;dispatch next が指示する作業を 1 つだけ実行し、もう一度問い合わせること;drive cadence では router に戻ること。