16
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ゲーム制作における Herdrとgit worktreeの自立型マルチエージェント環境

16
Posted at

注意:

この記事は人間が読むことを想定していません。

はじめに

Claude Code や Codex のようなコーディングエージェントを複数並列で走らせて Godot のゲームを作る、という開発を続けてきました。並列化して最初に壊れるのはコードではなく調整です。誰が何をしているか分からなくなり、同じファイルを二人が触り、報告が口頭(プロンプト)で流れて消え、リーダー役が「たぶん出来てるだろう」で受け入れてしまう。

この記事では、その調整を仕組みで固定するために作った 2 つのツールと、それらを Herdr + git worktree の上で動かす環境の全体を、再現できるレベルで解説します。

  • MyKanban — リーダーエージェントがチケットを起票し、worktree ごとの worker エージェントに指令を出し、報告書をレビューして完了/差し戻しを判断する、Kanban + Jira 的ワークフローの Claude Code / Codex スキル + CLI
  • GodotPlayOnCodexOnHerdr (allow-godot-window) — サンドボックスの中の worker が自力ではできない唯一の検証=「実機の見た目」を worker 自身に返す、Godot GUI ブローカーの Herdr プラグイン + スキル

全体像

役割は 2 つだけです。

leader(リーダー) worker(サブエージェント)
いる場所 main チェックアウト 自分専用の git worktree
担当 ボード全体 チケット 1 枚だけ
やること 計画・起票・dispatch・レビュー判定・統合 実装・検証・報告書の提出
やらないこと プロダクトコードを自分で書かない done に動かさない・他の worktree を触らない
終点 done review

判定を worker に任せると自己採点になり、実装を leader が始めると全体が止まります。この境界を仕組みで守るのが MyKanban の役目です。

登場するもの

Herdr — エージェント用ターミナルワークスペースマネージャ

Herdr は「Agent multiplexer that lives in your terminal」を名乗る OSS(Apache-2.0)で、tmux 的な workspace / pane の管理に加えて、pane 上のエージェントをソケット API から操作できるのが特徴です。

brew install herdr
herdr            # セッション起動
herdr status     # server: running を確認

この環境で使っている API はたとえばこのあたりです。

herdr pane split --pane <ID> --direction right --ratio 0.5 --cwd <worktree> --no-focus
herdr agent start kan-001 --kind claude --pane <PANE> -- <エージェント CLI への引数...>
herdr agent prompt <PANE> "指示..." --wait --until working
herdr agent read <PANE> --source visible     # いま見えている画面を読む
herdr worktree create --branch ... --base main   # worktree + 別 workspace を一発で

--kind は integration 名(claude, codex, copilot, cursor, ...)で、リーダーを Claude Code、worker を Codex のような混成もできます。

git worktree — worker の隔離

worker 1 人に worktree 1 つ。~/.herdr/worktrees/<repo名>/<チケット名> にチェックアウトを切り、ブランチは kan/<チケットID>-<slug> で統一します。リポジトリの隣に置かないのは、ビルド成果物やエディタ設定が main チェックアウトと混ざらないようにするためです。

そして worktree にはもう 1 つ重要な性質があります。linked worktree の中では .git はファイルですが、git rev-parse --git-common-dir は常に共有の .git を指す。MyKanban はここにボードを置くことで、「全 worktree から見える単一の調整領域」をコミット履歴を汚さずに手に入れています(後述)。

MyKanban — タスク管理と申し送りのスキル

構成はこれだけです。Claude Code と Codex は同じ SKILL.md 規約(frontmatter の name / description)なので、1 つのディレクトリを両方に symlink できます。SKILL.md の全文は付録 Aにあります。

MyKanban/
├── SKILL.md              # スキル本体(役割分担とリーダーの進め方)
├── bin/kanban            # ボード操作 CLI(Python 3 標準ライブラリのみ)
└── references/
    ├── workflow.md       # 状態機械、DoR/DoD、レビュー判定基準、差し戻しの書き方
    ├── herdr.md          # herdr 連携、手動 dispatch、トラブル対応
    └── cli.md            # 全サブコマンドとボードのファイル構成

GodotPlayOnCodexOnHerdr — 実機ウィンドウを出す Godot プラグイン

Herdr プラグイン(GUI ブローカー)+ それをエージェントに使わせるスキル allow-godot-window のセットです。詳細は後半で。

GodotPlayOnCodexOnHerdr/
├── SKILL.md                  # スキル本体
├── agents/openai.yaml        # Codex 用の表示メタデータ
├── bin/setup                 # symlink 登録 + Swift ヘルパのビルド + herdr plugin link
├── herdr-godot-gui-probe/    # Herdr プラグイン本体
│   ├── herdr-plugin.toml     #   launch / capture / stop のアクション定義
│   ├── scripts/              #   ブローカー(Python)とキャプチャ(Swift/ScreenCaptureKit)
│   └── bin/capture-window    #   配置先の Mac で swiftc ビルドされる
└── installer/                # クローンを置かない Mac へ配る tar.gz 版

どちらもリポジトリからダウンロードして使う前提にはしていません。スキル本体である SKILL.md は 2 本ともこの記事の付録に全文を書き下してありますkanban CLI(4,099 行)と GUI ブローカー(511 行)は全文の代わりに、同等品を実装できる粒度の仕様と主要部の抜粋を載せます。

セットアップ

前提: macOS、git、Python 3.8+(CLI は標準ライブラリのみ)、Herdr 0.7.5+、Godot プラグインを使うなら /Applications/Godot.app と Xcode Command Line Tools。

1. MyKanban を書き下して登録

スキルの実体はディレクトリ 1 つです。ダウンロードするものはなく、ファイルを自分で置けば動きます。

mkdir -p ~/src/MyKanban/bin ~/src/MyKanban/references
  • ~/src/MyKanban/SKILL.md付録 A の全文をそのまま保存します。これがスキル本体で、リーダー/worker の振る舞いはすべてここに書かれています。
  • ~/src/MyKanban/bin/kanban — ボード操作 CLI(Python 3 標準ライブラリのみ。chmod +x を忘れずに)。4,099 行あるため記事には全文を載せられませんが、付録 C にサブコマンド仕様とデータ構造を書き下します。SKILL.md + 付録 C を仕様として渡せば、コーディングエージェントに同等の CLI を実装させられる粒度にしてあります。
  • ~/src/MyKanban/references/*.md — レビュー判定基準(workflow.md)、herdr 連携の詳細(herdr.md)、CLI リファレンス(cli.md)。この記事の本文と付録 C がその要点のダイジェストになっています。無くてもスキルは動きます(SKILL.md からの参照が読めないだけ)。

Claude Code と Codex は同じ SKILL.md 規約(frontmatter の name / description)なので、同じディレクトリを両方に symlink します。以後の修正は 1 箇所で済みます。

mkdir -p ~/.claude/skills ~/.codex/skills
ln -sfn ~/src/MyKanban ~/.claude/skills/MyKanban
ln -sfn ~/src/MyKanban ~/.codex/skills/MyKanban

# CLI に PATH を通す(スキル本文からも自分のシェルからも呼ぶ)
echo 'export PATH="$HOME/src/MyKanban/bin:$PATH"' >> ~/.zshrc
exec $SHELL -l
kanban --help

2. allow-godot-window を書き下して登録

こちらは「スキル + Herdr プラグイン」の 2 枚組です。置くファイルは 4 つ。

mkdir -p ~/src/GodotPlayOnCodexOnHerdr/herdr-godot-gui-probe/{scripts,bin}
  • SKILL.md付録 B の全文を保存。
  • herdr-godot-gui-probe/herdr-plugin.toml — プラグイン定義。「Godot プラグイン」の章に全文があります(30 行)。
  • herdr-godot-gui-probe/scripts/godot_broker.py — ブローカー本体(Python 511 行)。設計と主要部の抜粋を同じ章で解説します。
  • herdr-godot-gui-probe/scripts/capture_window.swift — ScreenCaptureKit で「指定した 1 ウィンドウだけ」を PNG に落とす 95 行のヘルパ。アーキテクチャ依存のバイナリになるので、リポジトリにはコミットせず配置先の Mac でビルドします。
cd ~/src/GodotPlayOnCodexOnHerdr/herdr-godot-gui-probe
swiftc -O -o bin/capture-window scripts/capture_window.swift

# スキルの symlink 名は frontmatter の name に揃える(リポジトリ名ではなく)
ln -sfn ~/src/GodotPlayOnCodexOnHerdr ~/.claude/skills/allow-godot-window
ln -sfn ~/src/GodotPlayOnCodexOnHerdr ~/.codex/skills/allow-godot-window

# プラグインをその場で link(コピーの二重管理をしない)
herdr plugin link ~/src/GodotPlayOnCodexOnHerdr/herdr-godot-gui-probe

最後に allowlist(Godot を起動してよいプロジェクトの置き場所)を書きます。実際にプロジェクトを置く場所だけに絞るのが要点です。

CONFIG_DIR=$(herdr plugin config-dir local.godot-gui-probe)
cat > "$CONFIG_DIR/config.json" <<'EOF'
{
  "allowed_roots": [
    "/Users/your-name/Documents/my_godot_projects",
    "/Users/your-name/.herdr/worktrees"
  ],
  "capture_retention": 10,
  "default_ttl_seconds": 180
}
EOF
herdr plugin list                                        # local.godot-gui-probe が enabled
herdr plugin action list --plugin local.godot-gui-probe  # launch / capture / stop が並ぶ

3. プロジェクトでボードを初期化

cd /path/to/your/godot-project
kanban init --wip-in-progress 3 --base main
initialised board at /path/to/your/godot-project/.git/kanban
vcs: git   base: main   WIP: {'in_progress': 3, 'review': 5}

これで Herdr の中で Claude Code を立ち上げ、「MyKanban で並列に進めて」と言えばスキルが発火し、リーダーとして動き始めます。

設計の核: ボードは .git/kanban に置く

ボードの実体はただのテキストファイル群です。

<repo>/.git/kanban/
├── board.json          # 設定: prefix, 採番カウンタ, WIP 上限, base ブランチ
├── HANDOFF.md          # リーダーのセッション申し送り(手で書く)
├── events.jsonl        # 追記専用の監査ログ
├── tickets/KAN-001.md  # チケット本体(frontmatter + 本文)
├── briefs/KAN-001-r1.md   # worker 向け作業指示書(ラウンドごと)
├── reports/KAN-001-r1.md  # worker の完了報告(ラウンドごと)
└── reviews/KAN-001-r1.md  # leader の判定記録(ラウンドごと)

.git/ 配下に置くことには 2 つの意味があります。

  1. 全 worktree から同じボードが見える。 linked worktree の .git はファイルですが、git rev-parse --git-common-dir は常に共有 .git を指します。CLI がこれを解決するので、worker は自分の worktree で kanban を叩くだけです。
  2. コミットにも push にも混ざらない。 ボードは「このマシン上の進行中の調整状態」であって成果物ではありません。残したい決定はコードやコミットメッセージに落としてから done にする、という規律にもなります。

状態機械は Jira を簡略化したこの形です。

 backlog ──▶ ready ──▶ in_progress ──▶ review ──▶ done
    ▲          ▲            ▲             │
    │          │            ├──◀──────────├──▶ changes_requested ─┐
    │          │            │             ├──▶ needs_info ────────┤
    │          └── blocked ◀┘             └──▶ (spike 起票)       │
    └──────────────────────────────────────────────────────────────┘

worker が動かせるのは review まで。done に動かせるのは leader だけです。完了判定を worker にさせない(自己採点の禁止) が、このワークフロー全体でいちばん効いているルールだと思っています。

実際に回してみる

ダミーの Godot プロジェクト「StarShooter」で 1 チケットを一巡させます。

起票 — 受入基準が指示書の本体

kanban new --title "プレイヤーのダッシュ移動を実装" \
  --type feature --priority P1 --size M \
  --goal "Shift 押下中は移動速度を 2.5 倍にし、残像エフェクトを出す" \
  --ac "Shift 押下中の速度が通常の 2.5 倍になっている(player.gd の定数と実測ログ)" \
  --ac "ゲーム実行時に SCRIPT ERROR を含む行が 0 件" \
  --scope "scripts/player.gd とそのテスト" \
  --out-of-scope "敵の挙動、UI、効果音" \
  --context "移動処理は scripts/player.gd。体感目標: ダッシュは『危険を突っ切る』手触り" \
  --verify "godot --headless --quit-after 60 2>&1 | grep -c 'SCRIPT ERROR' || true"

差し戻しの大半は起票時の手抜きが原因なので、AC は機械判定できる文言まで落とします。「エラーが出ない」ではなく「SCRIPT ERROR を含む行が 0 件」。grep できる言い方にしておくと、worker が判定基準の解釈に時間を溶かしません。

一方で数値にできない狙い(「危険を突っ切る手触り」のような体感目標)は --context に 1 行で入れます。worker が仕様の穴を踏んだときの判断基準になります。実運用では、体感目標の無い起票で worker が差し戻し覚悟の独自判断を迫られた記録が残っています。

--verify の検証コマンドは指示書にそのまま載り、kanban ready --check配る前にリーダーの環境で 1 回走らせます。通るかは問いません(まだ実装されていないので通らないのが正常)。見ているのは「存在しないコマンド」と「返ってこないコマンド」で、そのまま配れば worker 全員が同時に同じ罠にかかるものです。

依存は --depends KAN-001 で張ります。依存が未完了のチケットは kanban next に出てこないので、順序を頭で覚えておく必要がなくなります。

kanban ready KAN-001 KAN-002
kanban board
BACKLOG (1)
  KAN-003 [P2] 敵の出現テーブルを調整 ⟵KAN-001

READY (2)
  KAN-001 [P1] プレイヤーのダッシュ移動を実装
  KAN-002 [P1] スコア表示 HUD を追加

IN_PROGRESS (0/3)
  —
...

dispatch — worktree 作成からエージェント起動まで一発

kanban preflight              # 最初の dispatch の前に 1 回。後述
kanban dispatch KAN-001       # worktree 作成 → ペイン分割 → エージェント起動 → 指示書送信
kanban dispatch KAN-002 --kind codex   # worker の CLI はチケット単位で選べる

dispatch は既定でリーダー自身のタブを分割して worker を並べます。別 workspace に散らすと止まっている worker に気付くのが遅れるからで、1 画面で全員を見張れるのがこのレイアウトの狙いです。

┌──────────────────────┬──────────────────────┐
│ leader (Claude Code) │ worker KAN-001       │
│ kanban board を見て  │ (claude)             │
│ レビューと統合に専念 │ worktree #1 で実装中 │
│                      ├──────────────────────┤
│                      │ worker KAN-002       │
│                      │ (codex)              │
│                      │ worktree #2 で実装中 │
└──────────────────────┴──────────────────────┘

分割規則は決め打ちです。「一番面積の大きいペインを、長い辺の側で半分に割る」。worker が 1・3・7 人のとき全ペインが正確に同じ大きさになり、途中の人数でも面積比は 2 倍以内に収まります。80x20 セルを確保できなくなったら同じ workspace の新しいタブへフォールバックするので、画面の広さが実質的な並列度の上限になります。

dispatch が Herdr に対してやっていることは、手動ならこうです(抜粋)。

git -C "$REPO" worktree add -b kan/kan-001-dash "$WT" main
herdr pane split --pane "$HERDR_PANE_ID" --direction right --ratio 0.5 --cwd "$WT" --no-focus
sleep 1    # ← ペイン内のシェルが rc を読み終わるまで待つ。飛ばすと起動行が食われる
herdr agent start kan-001 --kind claude --pane "$PANE" \
  -- --add-dir "$BOARD" --allowedTools "Bash($(command -v kanban):*)"
herdr agent prompt "$PANE" "作業指示書 $BRIEF を読んで従ってください" --wait --until working

地味な sleep 1 に注釈が付いているのは、実際に踏んだ罠だからです。Herdr は端末を確保した時点で pane split に答えますが、中のシェルはまだ rc を読んでいて、この間に打ち込んだ文字は食われます。エラーは出ず、ペインは空のまま・ボードは dispatch 済みという一番嫌な壊れ方をします。kanban dispatch はこの待ちを必ず入れます(board.jsonpane_settle)。

指示書(brief) — プロンプトに埋め込まず、ファイルで渡す

dispatchbriefs/KAN-001-r1.md を書き出し、worker には「これを読め」とだけ送ります。数百行の日本語をシェル引数で渡すとクォートで静かに壊れるのと、worker が後から読み返せる場所に残したいからです。実際に生成された指示書の冒頭:

# 作業指示書 / Work Order — KAN-001

あなたは MyKanban の **worker エージェント**です。このチケット 1 件だけを担当します。

| 項目 | 値 |
|---|---|
| チケット | `KAN-001` (feature, P1, size M) |
| ブランチ | `kan/kan-001-dash` |
| worktree | .../wt-kan-001 |
| ボード | .../demo/.git/kanban |

## 守ってほしいこと

- **何よりも先に、着手を宣言する。** この指示書を読んだら、作業を始める前に実行する:
      kanban ack KAN-001
  リーダーはこの宣言だけを「指示が届いた」証拠として扱う。端末の見た目は当てにされない。
- **作業は自分の worktree の中だけで行う。** 他の worktree や main チェックアウトには触らない。
- **`status: done` に自分で動かさない。** 完了判定はリーダーの仕事。あなたの終点は `review` まで。
- **報告書には主張ではなく証拠を書く。** 「テストは通った」ではなく、実行したコマンドと
  その出力を貼る。証拠のない報告は内容が正しくても調査差し戻しになる。
- **できなかったことは、できなかったと書く。** 環境の制約で実行できない検証がある場合、
  それを「実質やったのと同じ」に言い換えない。

このあとにチケット本文(目的 / AC / scope / 検証コマンド)、これまでの経緯、報告書の出し方が続きます。そして Godot プロジェクトで allow-godot-window が入っている場合は、実機キャプチャの手順が自動で挿入されます(後述)。

worker 側 — ack して、実装して、証拠つきで報告する

worker が最初にやるのは着手宣言です。

$ kanban ack KAN-001
KAN-001: 着手を宣言した

なぜこれが要るのか。Herdr が端末の見た目から返す working は、起動途中の TUI でも「入力欄に置かれたまま未送信のプロンプト」でも返ることがあり、この表示を信じたまま 21〜28 分の空転が繰り返し起きました。kanban ack はボードを通る唯一の着手信号なので、忙しそうに見えるだけの TUI には作れません。宣言が猶予(既定 180 秒)を超えて無ければ、リーダー側の kanban wait が「指示未達の疑い」として即座に報告します。

実装とコミットが済んだら、報告書のひな形を埋めて提出します。

kanban report KAN-001 --template > /tmp/starshooter-KAN-001-report.md
# 全セクションを埋めて…
kanban report KAN-001 --file /tmp/starshooter-KAN-001-report.md

ひな形の要求は「主張ではなく証拠」です。

## 受入基準の検証 / AC Verification

| # | 受入基準 | 判定 | 証拠 |
|---|---|---|---|
| 1 | <AC の文言> | 満たす / 未達 / 対象外 | <実行したコマンド or ファイル:行> |

## 実行した検証 / Evidence

<実際に打ったコマンドと、その出力を貼る。
 「動作確認済み」だけの記述は証拠にならず、調査差し戻しになる。>

セクションを省くと提出時に弾かれます。今回のデモでも実際に弾かれました。

$ kanban report KAN-001 --file .../demo-KAN-001-report.md
kanban: 報告書に必須セクションが足りない:
  - ## 残課題 / Follow-ups
  リーダーはこれらが揃っていないと accept/差し戻しを判断できない。
  `kanban report KAN-001 --template` をひな形にすること。

leader 側 — ボードで待ち、差分を先に見て、3 つの問いで判定する

kanban wait KAN-001 KAN-002 --status review --status blocked --timeout 3600

待つのはボードであってターミナルの状態ではありません。エージェントの idle は完了を意味しません(質問して止まっているだけかもしれない)。逆に、ボードが二度と動かなくなる 3 つの事実——worker が消えた / 承認 UI で止まっている / ack が来ない——は wait が毎ポーリングで確かめ、当てはまればタイムアウトを待たずに終了コード 4/5/6 で中断して次の一手を出します。

review に来たら、報告書を読む前に自分で差分を見ます

$ kanban diff KAN-001 --stat
 scripts/player.gd | 8 ++++++++
 1 file changed, 8 insertions(+)

報告書を先に読むと、その筋書きに沿って差分を眺めてしまうからです。順序を逆にするだけで見落としが減ります。

判定は 3 つの問いをこの順で通します。

  1. 主張は検証可能か? 証拠(コマンドと出力、ファイル:行)が付いているか → 無い ⇒ --needs-info(調査の差し戻し。コードは触らせない)
  2. AC を満たしているか? AC 1 件ごとに証拠が対応しているか、範囲外の変更が無いか → 満たさない ⇒ --rework(実装の差し戻し)
  3. 自分に判断材料があるか? 報告は妥当だが設計判断を決められない → 決められない ⇒ --spike(調査チケットを自動起票し、親を blocked に)

証拠のない報告から AC 充足は判断できないので、1 を飛ばして 2 を判断すると「印象で受け入れる」ことになります。差し戻すときは何をすれば受け入れられるかを必ず書きます。指摘だけだと次のラウンドでも同じ報告が返ってきます。

kanban review KAN-001 --needs-info \
  --note "AC2 の判定が『実装済み』としか書かれていない。実際に失効 token を投げた
コマンドと出力を貼ること。コードは変更しないこと。"

accept と merge は別 — 「受け入れた」と「本流に入った」は違う事実

$ kanban review KAN-001 --accept --note "AC 2 件を証拠つきで確認。差分は scope 内。"
KAN-001: done ✅
  ⚠ 未統合のコミットが 1 件ある (kan/kan-001-dash):
      60373c8 KAN-001: dash movement (2.5x while Shift held)
  次: kanban merge KAN-001

$ kanban merge KAN-001
→ 1 件のコミットを統合: git merge --no-ff kan/kan-001-dash
KAN-001: 統合完了 ✅  次: kanban cleanup KAN-001

$ kanban cleanup KAN-001
  git worktree .../wt-kan-001 を削除
KAN-001: 片付け完了

統合の入力はコミットだけにしています。worktree からファイルを直接コピーするのは一見速くて最も危険な近道です。並行中の worker のファイルは任意の時点で中間状態にあり、コード以外の成果物(生成した音声、シーン、アセット)は差分に見えないまま落ちます。実際に「効果音が 1 本も入っていないビルドを完成として通知した」事故と「worker が書き直す前の草稿を統合していた」事故が、同じ原因で起きました。コミットは worker が「ここが完成形だ」と宣言した唯一の地点なので、そこからしか取りません。

cleanup も、未統合のコミットや未取り込みの報告が残っていれば止まります。「消える可能性のあるものは消させない」が原則です。

全経緯は events.jsonl に追記され、kanban log で追えます。

$ kanban log KAN-001
...  KAN-001  acked              starshooter-kan-001
...  KAN-001  transition         starshooter-kan-001  from=in_progress to=review note=round 1 提出
...  KAN-001  transition         leader     from=review to=done note=AC 2 件を証拠つきで確認。
...  KAN-001  merged             leader     branch=kan/kan-001-dash commits=1
...  KAN-001  cleanup            leader

サンドボックスとの戦い

並列エージェント運用で一番手強いのは、herdr でも git でもなくエージェント CLI 自身のサンドボックスでした。ここは同じ環境を組む人が必ず踏むので、対処ごと書いておきます。

worker はボードに書けない(のがデフォルト)

エージェント CLI は書き込みを自分のワークスペースに閉じ込め、さらにリポジトリの .git/ を別扱いで守ります(履歴や hook を書き換えられないための保護)。ボードは .git/kanban にあるので必ずこれに引っかかります。しかも読み取りは通るため指示書は読めてしまい、失敗するのは最後の kanban report だけという一番たちの悪い壊れ方をします。

CLI 何が止めるか 逃がし方
claude 作業ディレクトリ外への書き込み拒否 + .git/** は承認必須 --add-dir <ボード>--allowedTools "Bash(<kanban>:*)"
codex --sandbox workspace-write の書き込み可能ルートが checkout だけ。共有 gitdir は EPERM。同じ理由で linked worktree の git commit も落ちる --sandbox workspace-write --add-dir <共有ディレクトリ>(2 つはセット)

kanban dispatch は kind ごとにこの引数を worker の起動コマンドに足します(board.jsonagent_args)。それでも渡せなかったときのために、アウトボックスという受け皿があります。ボードに書けなかった報告は worker の worktree の .kanban-outbox/ に退避され、リーダー側の kanban が次に走ったとき自動で取り込まれます。worker から見ると:

KAN-001: 報告書をアウトボックスに退避した (ボードに書き込めないため)

これは成功です。指示書にも「同じ報告を出し直さない」「退避を消さない」「諦めて黙って終了しない」と明記してあります。

preflight — 配る前に 1 回、worker と同じ経路を流す

kanban preflight

使い捨てのチェックアウトを作り、worker を起こすのと同じコマンドでプローブを流して、「チェックアウトに書けるか・そこでコミットできるか・ボードに届くか」を確定させます。これが要るのは、失敗がいちばん遅く現れるからです。worktree はできる、指示書は読める、実装も終わる——そして最後の git commitkanban report だけが落ちる。実運用では、4 体に配ってから気づいて 4 体が同時に同じ 3 枚の壁にぶつかり、3 体が独立に同じ回避策を発明した記録があります。急いでいるときほど、配る前に測る。

同じ発想で board.json にはこんな設定があります。

  • setup_cmd — 新しいチェックアウト直後に走るコマンド。Godot の .godot import キャッシュや node_modules など「コミットされないが無いと動かないもの」を配ります。これが無いと worker には全アセットが壊れて見え、製品バグか環境かの切り分けに時間が溶けます。
  • brief_notes — 全指示書に差し込む共通文。プロジェクト共通の制約と実測値(「ビルの実寸は 1.29m」)を書きます。リーダーの未検証の仮定は、並列体制では人数ぶんに増幅されるからです。

Godot プラグイン: worker に「見た目の検証」を返す

ここからが Godot 固有の話です。

数値の AC は「実装されたこと」しか検証できません。「意図が満たされたこと」は見ないと分かりません。ところが worker はサンドボックスの中にいて、自力ではウィンドウを出せない。放っておくと**「コード上は正しいので確認済み」という報告**が返ってきます。ゲーム開発ではこれが致命的です。

そこで作ったのが GUI ブローカー(GodotPlayOnCodexOnHerdr)です。設計はこうです。

  • エージェントに渡すのは 3 つの型付きアクションだけ。 launch / capture / stop を Herdr プラグインアクションとして公開し、実行パス・argv・環境変数・キャプチャ先はすべてブローカー側が決めます。スキル(SKILL.md)は opensh -c などの代替経路を明示的に禁じています。シェルのサンドボックスは緩めないまま、窓を出す能力だけを貸し出す形です。
  • 起動できるのは allowlist 配下の project.godot を持つプロジェクトだけ。
  • 撮るのは自分が起こした窓だけ。 ScreenCaptureKit のヘルパに渡るのはブローカーが握っているウィンドウで、画面全体は撮りません。他のワークスペースの中身は写りません。
  • インスタンスは (workspace, project) ごと。 worktree を分けた並列 worker が同時にそれぞれの窓を出せます。TTL(既定 180 秒)を過ぎたら SIGTERM → SIGKILL で自分のプロセスグループだけを回収します。
  • プロジェクトはフォーカスされたペインから解決する。 プラグインアクションは引数を取れない(herdr plugin action invoke--plugin のみ)ので、ハンドルではなく「呼んだペインのプロジェクト」で対象を引きます。

プラグインの実体

Herdr プラグインは「toml でアクションを宣言し、コマンドを叩かせる」だけのシンプルな仕組みです。herdr-plugin.toml の全文:

id = "local.godot-gui-probe"
name = "Godot GUI Probe"
version = "0.3.0"
min_herdr_version = "0.7.5"
description = "Launch, capture, and stop one Godot window per allowlisted project"
platforms = ["macos"]

[[actions]]
id = "inspect-context"
title = "Inspect Godot GUI probe context"
contexts = ["workspace"]
command = ["python3", "scripts/probe_context.py"]

[[actions]]
id = "launch"
title = "Launch Godot for the current allowlisted workspace"
contexts = ["workspace"]
command = ["python3", "scripts/godot_broker.py", "launch"]

[[actions]]
id = "capture"
title = "Capture the broker-owned Godot window"
contexts = ["workspace"]
command = ["python3", "scripts/godot_broker.py", "capture"]

[[actions]]
id = "stop"
title = "Stop the broker-owned Godot window"
contexts = ["workspace"]
command = ["python3", "scripts/godot_broker.py", "stop"]

command に引数を渡す口が無いことに注目してください。エージェントが指定できるのは「どのアクションを呼ぶか」だけで、実行パス・argv・環境変数・キャプチャ先はすべてブローカー側の決定です。

ブローカー本体(godot_broker.py、511 行)の骨格は「Herdr が渡すコンテキスト(HERDR_PLUGIN_* 環境変数と focused pane 情報)からプロジェクトを解決し、(workspace, project) ごとの状態ファイルで 1 インスタンスを管理する」です。要になっているプロジェクト解決の部分を抜粋します。

GODOT_EXECUTABLE = Path("/Applications/Godot.app/Contents/MacOS/Godot")
DEFAULT_TTL_SECONDS = 180
MAX_TTL_SECONDS = 1800

def require_allowed_project(context: dict, config: dict) -> tuple[str, Path, Path, str]:
    workspace_id = context["workspace_id"]
    cwd, source = context_cwd(context)   # focused_pane_cwd → worktree → workspace_cwd の順
    allowed_roots = [
        root for root in config["allowed_roots"] if path_is_within(cwd, root)
    ]
    allowed_root = max(allowed_roots, key=lambda root: len(root.parts), default=None)
    if allowed_root is None:
        raise BrokerError("workspace_not_allowed",
            f"{source} for workspace {workspace_id} is outside every configured allowed_root")

    # フォーカスされたペインの cwd から、最も近い project.godot を持つ祖先へ遡る。
    # allowed_root より上には出ない。
    project = cwd
    while not (project / "project.godot").is_file():
        if project == allowed_root:
            raise BrokerError("project_missing",
                f"no project.godot from {source} up to allowed_root")
        project = project.parent
    return workspace_id, project, allowed_root, source

launch は同一 (workspace, project) の 2 枚目を拒否します。

    state_path = instance_path(workspace_id, project)
    if state_path.exists():
        prior = read_state(state_path)
        # PID が使い回されていたら前のインスタンスは消えているので、
        # 緩い判定で stale なファイルがこのプロジェクトを塞ぎ続けるのを防ぐ
        if process_is_owned(prior, strict=False):
            raise BrokerError("already_running",
                f"a broker-owned Godot is already running for {project}; stop it first")
        state_path.unlink()

このほかに、自分が起こしたプロセスかを検証してから SIGTERM → SIGKILL でプロセスグループごと回収する stop、TTL 超過を同じ経路で回収する watchdog、ブローカーが握っているウィンドウ ID だけを bin/capture-window(Swift / ScreenCaptureKit)に渡して撮る capture が続きます。エラーはすべて {"error": {"code": ..., "message": ...}} の JSON で返り、worker はコードをそのまま報告書に貼れます。

worker から見た使い方は、指示書に自動挿入されるこの手順そのままです。

# 撮り直しのときは前の窓が残っているので、まず止める
herdr agent focus "$HERDR_PANE_ID"
herdr plugin action invoke stop --plugin local.godot-gui-probe

# 起動 → 描画が落ち着くまで待つ
herdr agent focus "$HERDR_PANE_ID"
herdr plugin action invoke launch --plugin local.godot-gui-probe
sleep 15

# 撮る → PNG のパスは log の capture (status=succeeded) の "path"
herdr agent focus "$HERDR_PANE_ID"
herdr plugin action invoke capture --plugin local.godot-gui-probe
herdr plugin log list --plugin local.godot-gui-probe

# 終わったら必ず止める
herdr agent focus "$HERDR_PANE_ID"
herdr plugin action invoke stop --plugin local.godot-gui-probe

しつこく繰り返される 2 行には、それぞれ実際の事故が対応しています。

  • アクションの直前に毎回 herdr agent focus worker は 1 つの workspace の分割ペインに並んでいて、ブローカーはフォーカスされたペインからプロジェクトを解決します。飛ばすと別の worker の worktree を起動して撮ります。しかも撮れた画はそれ自体としては正しく見えるので、間違いに気づく手がかりがありません。
  • 撮り直す前に必ず stop 前のインスタンスが生きていると launch が素通りし、capture前の窓の古い PNG を同じパスで返します。「直したのに直っていないように見える」(逆にも見える)ので、修正の検証が丸ごと無効になります。

MyKanban 側との統合

kanban dispatch は、リポジトリに project.godot があり、allow-godot-window スキルが入っていて、worker がエージェントのペインに載っている——の 3 条件が揃ったときだけ、この手順を指示書に差し込みます。しかも worker の kind で書き分けます。スキルを名前で起動できる($allow-godot-window)のは Claude Code だけなので、codex や grok にはブローカーを直接叩く生コマンドを出します。

ここを一律にスキル構文で書いた回では、非 Claude の worker が「そんなスキルは無い」と窓を諦め、5 体全員が見た目を未検証のまま返してきました。経路は最初から通っているのに、教え方だけが Claude 専用だったせいで失われる——いちばん惜しい失敗だったので、書き分けは CLI 側に押し込みました。リーダーは --kind を選ぶだけで済みます。

指示書には報告の作法も入ります。「撮れた PNG は自分の目で開いて確認し、パスと何を確認したかを書く。撮れなかったら返ってきたエラーをそのまま書く。『コード上は正しいので確認済み』に言い換えない。撮れていないと分かってさえいれば、リーダーが代わりに撮れる」。

運用してみて分かったこと

最後に、数字や規律の根拠になった実測・実感をまとめます。

  • **並列度は「同時に何人動かせるか」ではなく「同時に何件まともにレビューできるか」。**WIP 上限 3〜4 を超えると、レビュー待ちが溜まり、worktree 間のコンフリクトが増え、リーダーの吟味が雑になります。分割レイアウトでは画面の広さ(floor(幅/80) × floor(高さ/20) ペイン)も同じ上限を示してくれます。
  • review の滞留が最も高くつく。 worker は次の仕事を持てず、worktree は生きたまま、main はその間も動く。review は最優先で捌きます。
  • 端末の見た目を信じない。 idle は完了ではなく、working は着手ではない。完了はボードの review、着手はボードの ack だけで判定します。この置き換えで、21〜28 分かかっていた「プロンプト未達」の発見が数分に縮みました。
  • エージェント名にはプロジェクト接頭辞を付ける(starshooter-kan-001)。Herdr のエージェント名前空間はセッション全体で 1 つなので、素の kan-003 は並行プロジェクトと衝突します。実際、別プロジェクトの worker に指示書が配達された事故がありました。
  • worker の難易度に合わせて kind とモデルを変える。 機械検証で完結する size S の chore は安いモデル、デバッグや複数モジュール跨ぎはコードに強い kind か上位モデル。ただしレビュー基準は下げない。差し戻し 1 往復は dispatch 1 回より高いので、往復が始まった時点で安いモデルの節約は消えています。
  • リーダーの文脈もボードに残す。 セッションの申し送りは $(kanban path)/HANDOFF.md。文脈が切れた新しいリーダーは kanban boardHANDOFF.mdkanban show の順で復帰できます。

まとめ

  • 並列エージェント開発で壊れるのは調整。全 worktree から見える 1 つのボード(.git/kanban)に固定する
  • leader は判定、worker は実装と報告。終点を分ける(worker は review まで)ことで自己採点を防ぐ
  • 報告書は主張ではなく証拠。レビューは「検証可能か → AC → 判断材料」の順
  • accept と merge は別の事実。統合の入力はコミットだけ
  • サンドボックスとは戦わず、必要な許可を dispatch が渡し、駄目ならアウトボックスで受ける
  • Godot の「見た目の検証」は、型付きアクション 3 つだけの GUI ブローカーで worker 自身に返す

Kanban も Jira も、もともと「人間どうしの調整が壊れないための仕組み」でした。相手がエージェントになっても壊れ方は驚くほど同じで、処方箋もほぼ同じ——ただし「端末の見た目を信じない」「証拠を要求する」あたりの締め付けは、人間相手より強めにする必要がある、というのがここまでの実感です。

参考

  • Herdr — terminal workspace manager for AI coding agents(brew install herdr)
  • git worktree
  • Godot Engine — https://godotengine.org
  • MyKanban / GodotPlayOnCodexOnHerdr のスキル本体は下の付録に全文があります

付録 A: MyKanban SKILL.md 全文

~/src/MyKanban/SKILL.md として保存してください。frontmatter の description はエージェントがスキルを発火させる条件そのものなので、削らないでください。

MyKanban/SKILL.md(528 行、クリックで展開)
---
name: MyKanban
description: herdr と git worktree で複数のエージェントを並行させる開発の、タスク管理と申し送りを行うカンバンボード。リーダーが実装計画をチケット化し、worktree ごとのサブエージェントに指令を出し、返ってきた報告書を吟味して「完了」「実装の差し戻し」「調査の差し戻し」を判断する。ボードは .git/kanban に置かれ全 worktree から共有される。マルチエージェント開発、並列エージェント、サブエージェントへの作業指示・進捗管理・引き継ぎ・申し送り、worktree ごとの分担、エージェントからの報告レビューや差し戻し判断を扱うときは必ずこのスキルを使う。「herdr で並列に開発したい」「worktree ごとにエージェントを立てて分担」「サブエージェントに指示を出して」「報告をレビューして完了か差し戻しか決めて」「タスクを整理して並行で進めて」「作業を引き継ぎたい」「カンバン」「チケット」「MyKanban」といった依頼が該当する。単一エージェントで完結する小さな変更には使わない。
---

# MyKanban

複数のコーディングエージェントを git worktree に分けて並行稼働させるとき、壊れるのは
コードではなく**調整**です。誰が何をしているか分からなくなり、同じファイルを二人が
触り、報告が口頭で流れて消え、リーダーが「たぶん出来てるだろう」で受け入れてしまう。

MyKanban はその調整を、全 worktree から見える 1 つのボードに固定します。

## 前提となる 2 つの役割

この仕組みは役割が分かれていることで成立します。**自分がどちらなのかを最初に確定**
してください。

| | **leader(リーダー)** | **worker(サブエージェント)** |
|---|---|---|
| いる場所 | main チェックアウト | 自分専用の worktree |
| 担当 | ボード全体 | チケット 1 枚だけ |
| やること | 計画・起票・dispatch・**レビュー判定**・統合 | 実装・検証・**報告書の提出** |
| やらないこと | プロダクトコードを自分で書かない | `done` に動かさない・他の worktree を触らない |
| 終点 | `done` | `review` |

判定を worker に任せると自己採点になり、実装を leader が始めると全体が止まります。
この境界を曖昧にしないことが、並列化が破綻しない唯一の条件です。

**あなたが leader の場合** → 下の「リーダーの進め方」へ。
**worker として起動された場合**`briefs/*.md` を読めと言われた)→ その指示書に従う。
迷ったら「作業指示書を渡されたか?」で判断する。渡されていなければ leader です。

## ボードの場所

```sh
kanban path     # => /path/to/repo/.git/kanban
```

`.git/kanban`**main チェックアウトの `.git`** に置かれます。リンク worktree の中では
`.git` はファイルですが、`git rev-parse --git-common-dir` は常に共有の `.git` を指すため、
どの worktree からでも同じボードが見えます。CLI がこれを解決するので、worker は自分の
worktree で `kanban` を叩くだけで済みます。

ボードは git 管理外(`.git/` の中)なので、コミットにも push にも混ざりません。これは
意図的です。ボードはこのマシン上の**進行中の調整状態**であって、成果物ではありません。
リポジトリを clone し直すとボードは失われるので、残したい決定事項はコードやドキュメント、
コミットメッセージに落としてから `done` にします。

## セットアップ

`kanban` はこの SKILL.md と同じディレクトリの `bin/kanban`(Python 3 標準ライブラリのみ、
他に依存なし)。フルパスで呼ぶか PATH に通します。

```sh
# 置き場所は環境による。Claude Code なら ~/.claude/skills/MyKanban、Codex なら ~/.codex/skills/MyKanban
SKILL_DIR=$(ls -d ~/.claude/skills/MyKanban ~/.codex/skills/MyKanban 2>/dev/null | head -1)
export PATH="$SKILL_DIR/bin:$PATH"

cd /path/to/repo
kanban init --wip-in-progress 3 --base main
```

以降このドキュメントでは `kanban` と書きます。PATH に通していなければ
`"$SKILL_DIR/bin/kanban"` に読み替えてください。worker に渡す作業指示書には
`kanban` の絶対パスが埋め込まれるので、worker 側で PATH 設定は不要です。

`--wip-in-progress` は同時に走らせる worker の上限です。3〜4 を超えると、レビュー待ちが
溜まり、worktree 間のコンフリクトが増え、リーダーの吟味が雑になります。並列度は
「同時に何人動かせるか」ではなく「**同時に何件まともにレビューできるか**」で決めます。

worker をリーダーの画面に並べる既定の運用では、画面の広さも上限になります。目安は
`floor(幅 / 80) × floor(高さ / 20)` 枚(リーダー自身の分を含む)。200x50 の端末なら
リーダー + worker 3 人です。これを超える dispatch はペインではなく同じ workspace の
新しいタブに置かれます。

## リーダーの進め方

### 1. 計画をチケットに落とす

実装計画をそのまま渡さず、**1 worker が 1 worktree で完結できる単位**に割ります。良い分割は
触るファイルが重ならないこと。重なるならチケットを分けずに 1 枚にまとめるか、依存で直列化します。

```sh
kanban new --title "JWT リフレッシュ endpoint を追加" \
  --type feature --priority P1 --size M \
  --goal "アクセストークンを再取得できるようにする" \
  --ac "POST /auth/refresh が有効な refresh token で 200 と新しい access token を返す" \
  --ac "失効・改竄された token では 401 を返し、スタックトレースを漏らさない" \
  --scope "src/auth/ とそのテスト" \
  --out-of-scope "フロントエンドの呼び出し側、token 保存方式の変更" \
  --context "既存の発行処理は src/auth/issue.py:42"
```

**受入基準(AC)が指示書の本体**です。ここが曖昧だと worker は推測で実装し、その推測を
レビューで否定することになります。差し戻しの大半は起票時の手抜きが原因なので、
AC は「何を実行すれば満たしたと分かるか」が読み取れる文にします。

文言は**機械判定できる形**まで落とします。「エラーが 1 件も出ない」はエンジン警告を含むかで
worker が判断に時間を溶かすので、「`SCRIPT ERROR` を含む行が 0 件」のように grep できる
言い方にします。数値にできない狙い(「KO はダッシュ衝突で取れること」のような**体感目標**)
と、隣のチケットとの**境界の責務**(「R キーの入力処理はこのチケット」)は、それぞれ 1 行で
`--context` に入れます。どちらも worker が現場で仕様の穴を踏んだときの判断基準になります。
実際、体感目標の無い起票で worker が差し戻し覚悟の独自判断を迫られた記録があります。

その「何を実行すれば」を実際のコマンドで書けるなら `--verify` に入れます。

```sh
kanban new ... --verify "pytest tests/auth -q" --verify "curl -s -o /dev/null -w '%{http_code}' localhost:8000/auth/refresh"
```

指示書にそのまま載り、`kanban ready --check`**配る前にリーダーの環境で 1 回走らせます**。
通るかは問いません(まだ実装されていないので通らないのが正常)。見ているのは走るかどうか
だけで、**存在しないコマンド****返ってこないコマンド**をここで落とします。どちらも、
そのまま配れば worker 全員が同時に同じ罠にかかります。

依存があるものは `--depends KAN-001` を付けます。依存が未完了のチケットは `kanban next`
に出てこないので、順序を自分で覚えておく必要がなくなります。

### 2. Ready にする

```sh
kanban ready KAN-001 KAN-002
kanban ready KAN-001 --check      # 検証コマンドも実際に走らせる
```

Definition of Ready(目的・AC・size・scope が埋まっているか)を検査して落とします。
落ちたら埋めてください。`--force` はありますが、ここで通した曖昧さは必ず後で差し戻しとして返ってきます。

### 3. 配る前に 1 回だけ疎通を見る

```sh
kanban preflight              # 使い捨ての worktree で、worker と同じ経路を実際に走らせる
```

使い捨てのチェックアウトを作り、worker を起こすのと同じコマンドでプローブを流して、
**worker が実際に何をできるのか**を確定させます —— チェックアウトに書けるか、
**そこでコミットできるか**、ボードを読めるか・書けるか、アウトボックスに書けるか、
`kanban` を叩けるか。終わったらチェックアウトは消えます。

これが要るのは、失敗が**いちばん遅く**現れるからです。worktree はできる、指示書は読める、
実装も終わる——そして最後の `git commit``kanban report` だけが落ちる。4 体に配ってから
気づけば、4 体ぶんの時間を同じ発見に使うことになります。実際、4 体が同時に同じ 3 枚の壁に
ぶつかり、3 体が独立に同じ回避策を発明した記録があります。

`board.json``setup_cmd` を設定しておくと、**新しいチェックアウトを作った直後**に走ります。
コミットされないが無いと動かないもの(Unity の `Library`、Godot の `.godot` import キャッシュ、
`node_modules`)を配る場所です。これが無いと、worker には**全アセットが壊れて見え**、
製品のバグか環境かの切り分けに時間が溶けます。

`board.json``brief_notes` は、すべての指示書に差し込まれる文章です。プロジェクト共通の
制約と**実測値**(「ビルの実寸は 1.29m」「コミットはリーダーが代行する」)はここに書きます。
リーダーの未検証の仮定は、並列体制では人数ぶんに増幅されます。**急いでいるときほど、配る前に測る。**

**Godot プロジェクトで `allow-godot-window` が入っていれば、指示書に実機キャプチャの手順が
自動で入ります。** worker はサンドボックスの中にいるので自力ではウィンドウを出せず、放って
おくと「コード上は正しいので確認済み」という報告が返ってきます(数値の AC は「実装された
こと」しか検証できず、「意図が満たされたこと」は見ないと分かりません)。ブローカー経由なら
worker 自身が窓を出して自分の成果を見られます。指示書には次の 3 点が入ります:

- アクションの直前に **`herdr agent focus "$HERDR_PANE_ID"`** を実行すること。worker は
  1 つの workspace の分割ペインに並んでいて、ブローカーは**フォーカスされたペイン**を
  基準にプロジェクトを解決します。ここを飛ばすと**別の worker の worktree を撮ります**——
  しかも撮れた画は正しく見えるので、間違いに気づけません。
- **撮り直す前に必ず窓を止めること。** 前のインスタンスが生きていると起動が素通りし、
  **前の窓の古い PNG が同じパスで返ります**。直したのに直っていないように見える(逆にも
  見える)ので、修正の検証が丸ごと無効になります。
- 撮れなかったときは、返ってきたエラーをそのまま報告書に書くこと。

**窓は worker ごとに持てます。** ブローカーのインスタンスは worktree ごとに分かれるので、
並列に走らせた数だけウィンドウが同時に前へ出ます(どれも TTL 既定 180 秒で閉じます)。
拒否されるのは同じプロジェクトの 2 枚目だけなので、上の「撮り直す前に止める」は引き続き
必要です。

**手順の書き方は worker の kind で変わります。** スキルを名前で起動できるのは Claude Code
だけなので、`--kind claude` の worker には `$allow-godot-window` を、それ以外(codex, grok,
gemini …)には**ブローカーを直接叩く生コマンド**`herdr plugin action invoke
launch|capture|stop --plugin local.godot-gui-probe`)を出します。ここを一律にスキル構文で
書くと、非 Claude の worker は「そんなスキルは無い」と言って窓を諦め、**5 体全員が見た目を
未検証のまま返してきます**——実際にそうなった記録があります。経路は最初から通っているのに、
教え方だけが Claude 専用だったせいで失われる、いちばん惜しい失敗です。

**非対話 worker(`--exec`)はこの経路を使えません。** ブローカーはエージェントとして解決
できるペインを要求しますが、`--exec` のペインはコマンドを走らせているだけで herdr の
エージェントではありません。その場合の指示書は「使えないので、見た目の確認はリーダーに
依頼し、未検証と明記する」に切り替わります。見た目が効く成果物では、`--exec` の速さと
実機確認のどちらを取るかを**チケット単位で**決めてください。

### 4. dispatch する

```sh
kanban next              # 依存が解けていて WIP に空きがあるチケット
kanban dispatch KAN-001  # worktree 作成 → ペイン分割 → エージェント起動 → 指示書を送信
kanban dispatch KAN-001 --exec   # 対話 TUI を使わず非対話で走らせる
```

`dispatch` は worker を **リーダー自身のタブを分割したペイン**に置きます(既定)。
worktree を作り、エージェントを起動し、`briefs/KAN-001-r1.md` を書き出して
「これを読め」と送ります。指示書を直接プロンプトに流し込まないのは、長文のシェル
クォートが壊れやすいのと、worker が後から読み返せる場所に残しておきたいからです。

**分割の仕方は決められています。** 一番面積の大きいペインを、長い辺の側で半分に割る
(同着なら左上優先)。worker が 1・3・7 人のとき全ペインが正確に同じ大きさになり、
途中でも最大と最小の面積比は 2 倍を超えません。ターミナルのセルは縦横比およそ 1:2 な
ので、幅が高さの 2 倍あたりを境に縦割り/横割りを切り替えます。分割しても 80x20 セルを
確保できないときは、同じ workspace に新しいタブを作ります(`--on-full error` で中止に
変更可)。

```sh
kanban dispatch KAN-001 --no-split   # 別画面(workspace)に置く旧来の挙動
```

**ペインを作った直後の 1 秒は `dispatch` が待ちます。** herdr は端末を確保した時点で
`pane split` に答えますが、その中のシェルはまだ rc を読んでいて、この間に打ち込んだものは
食われます。`agent start``agent_pane_busy` を返してくれるとは限らず、成功を返したまま
起動行だけが消えて、**ペインは空のまま・ボードは dispatch 済み**になることがあります。
`dispatch` がペインを作る唯一の経路なので、**この待ちを気にする必要はありません**。
長さは `board.json``pane_settle`(既定 1.0 秒)、`--pane-settle` で上書きできます。
手作業でペインを割るときだけは自分で `sleep 1` を入れてください(`references/herdr.md`)。

**指示は「送った」ではなく「着手した」まで確認されます。** herdr の `agent prompt` は
テキストを入力欄に置いたまま送信されないことがあり、そうなると worker はボード上は
dispatch 済みのまま何もしません。`dispatch` / `recall``working` への遷移を確認し、
駄目なら Enter を追送し、それでも駄目なら警告してチケットの申し送りに残します。
**警告が出たら画面を見てください**`herdr agent read <PANE> --source visible`)。

**「着手した」は端末からの推測ではなく、worker 自身がボードに書きます。** herdr が端末の
見た目から返す `working` は、起動途中の TUI でも、入力欄に置かれたまま未送信のプロンプト
でも返ることがあり、この表示を信じたまま 21〜28 分の空転が繰り返し起きています。そこで
指示書は worker に、作業より先に `kanban ack <ID>`**着手宣言**)を書かせます。ボード
(またはアウトボックス)を通る唯一の着手信号なので、忙しそうに見えるだけの TUI には
作れません。宣言が `ack_grace`(既定 180 秒)を超えて無ければ、`wait` が指示未達として
検知します(次節)。`dispatch` は補助として、送信確認の **20 秒後にエージェントがまだ
生きているか**も見届け、消えていれば申し送りに残して終了コード 4 で落ちます
(`--settle 0` で無効化)。`doctor` にも同じ検査があります。

**dispatch は worker にボードへの到達手段を渡します。** ボードは共有メタデータ領域
(`.git` / `.jj/repo`)にあり、そこは worker の worktree の外で、かつエージェント CLI が
最も強く守る場所です。`dispatch` は kind ごとに必要な引数を worker の起動コマンドに足します
(`board.json``agent_args`、既定は claude なら `--add-dir <ボード> --allowedTools
"Bash(<kanban>:*)"`、codex なら `--sandbox workspace-write --add-dir <共有ディレクトリ>`)。
**これは herdr や git の制約ではなく、エージェント CLI 自身の制約への対処です。**
引数を拒否されたら引数なしで起動し直すので、dispatch がこれで失敗することはありません
(その場合 worker はアウトボックス経由になります)。`--agent-arg` で差し替え、
`--no-agent-args` で素の起動に戻せます。

**codex では `--sandbox workspace-write` と `--add-dir` は必ずセットです。** `--add-dir` は
「追加で**書けるようにする**場所」の指定なので、書き込みが許可されていない既定の権限では
黙って無視されるか、起動そのものが失敗します。そして共有ディレクトリに書けないと、
linked worktree の index と refs はそこにあるため **`git commit` が落ちます****TUI が落ちるなら `--exec` に切り替えてください。** ペインで対話 TUI を起こす経路は、
指示書を読む前に落ちることがあり、そのとき**終了コードもログもセッション記録も残りません**
(何が起きたかを後から知る手段が無い、という意味です)。`--exec` は起動スクリプトを
`briefs/<ID>-r<N>.run.sh` に書いて流すので、出力は `logs/<ID>-r<N>.log` に、終了コードは
その末尾に残ります。生存確認もペインのプロセスを見る方式に自動で切り替わります。
毎回そうしたいなら `board.json``dispatch_launch``"exec"` にします。
`board.json``exec_commands` で kind ごとのコマンドを変えられます(既定は codex なら
`codex exec --sandbox workspace-write --add-dir {shared} -C {worktree} - < {brief}`、
grok なら `grok --always-approve --no-alt-screen -p "$(cat {brief})"`)。

既定は `board.json``dispatch_layout``split` / `workspace`)です。リーダーが
herdr のペインの外にいる(`$HERDR_PANE_ID` が無い)ときは自動で `workspace` に
落ちます。詳細と手作業でやる場合は `references/herdr.md` を参照。

**worker の名前にはプロジェクト名が付きます**`goodgame23-kan-003`)。herdr のエージェント
名前空間はセッション全体で 1 つなので、接頭辞が無いと `kan-003` は複数のプロジェクトで
同じ名前になります。実際、あるプロジェクトの指示書が、並行して動いていた別プロジェクトの
worker に配達された事故が起きています。`board.json``agent_prefix` で変えられます。

#### 難易度に合わせて kind とモデルを選ぶ

worker は全員同じでなくて構いません。**kind(どのエージェント CLI か)とモデル(その中の
どの性能帯か)はチケット単位で選べます。** 判断材料は起票時に付けた `size` / `type` と
AC の性質で、dispatch の直前に次の順で問います。

1. **機械的に検証し切れるか?** AC が `--verify` のコマンドだけで判定でき、設計判断が
   入らない(size S の chore、rename、定型修正、ドキュメント)→ **安いモデル**で十分。
2. **実装の難所があるか?** デバッグ、アルゴリズム、複数モジュールを跨ぐ変更 →
   **コードに強い kind(例: codex)か上位モデル**3. **迷ったら既定のまま。** 選定に悩む時間がモデル差の節約を食い潰したら本末転倒です。

kind は `--kind` で替えます。ボード到達の引数・`--exec` のコマンド・Godot キャプチャ手順の
書き分けは kind に追従するので、リーダーは選ぶだけで済みます。

```sh
kanban dispatch KAN-001 --kind codex
```

モデルは worker CLI の起動引数で渡します。**`--agent-arg``board.json``agent_args` を
丸ごと置き換える**ので、ボード到達用の既定引数も一緒に再掲します(`{board}` `{kanban}`
`{shared}` `{worktree}` は展開されます)。

```sh
# 対話 TUI: claude を安いモデルで
kanban dispatch KAN-003 --kind claude \
  --agent-arg=--model --agent-arg=haiku \
  --agent-arg=--add-dir --agent-arg='{board}' \
  --agent-arg=--allowedTools --agent-arg='Bash({kanban}:*)'

# 非対話 (--exec): コマンドごと差し替えるほうが素直
kanban dispatch KAN-003 --exec \
  --exec-command 'codex exec -m <安いモデル> --sandbox workspace-write --add-dir {shared} -C {worktree} - < {brief}'
```

決めた基準(「size S の chore は haiku」など)は `HANDOFF.md` に書いておきます。チケット
ごとに考え直すものではなく、セッションを跨いで同じ基準で配るためのものです。毎回同じ
組み合わせなら `board.json``agent_args` / `exec_commands` にモデル指定込みで書けますが、
これは kind ごとの既定なので、同じ kind の中で難易度別に変えるのは dispatch 時の上書きです。

**レビュー基準は下げない。** 安いモデルは報告書の証拠が薄くなりがちですが、レビューの
3 つの問い(検証可能か → AC → 判断材料。下の 6 節)は据え置きます。下げると「安く実装して高く直す」ことに
なります。差し戻しが出て、原因が起票ではなくモデルの力不足にありそうなら、粘らずに
`kanban cleanup` で片付けてから `--force` 付きの dispatch で一段上のモデル(または別 kind)に
配り直します。差し戻し 1 往復は dispatch 1 回より高いので、往復が始まった時点で安いモデルの
節約は消えています。

### 5. 待つ

```sh
kanban wait KAN-001 KAN-002 --status review --status blocked --timeout 3600
```

**ボードを見て待ちます。ターミナルの状態では判断しません。** エージェントが idle に
なったことは完了を意味しません(質問して止まっているだけかもしれない)。`review` に
移ったことだけが「報告が出た」という信号です。

**逆に、ボードが二度と動かなくなる事実は 3 つあり、`wait` は毎ポーリングで全部を確かめ、
当てはまればタイムアウトを待たずに中断して、どの worker かと次の一手を出します。**

- worker が**消えた**(終了コード 4。`--ignore-missing-workers` で切れる)
- **承認や質問の UI で止まっている**(終了コード 5。誤検出を避けるため 2 回連続で観測
  してから。`--ignore-blocked-workers` で切れる)
- TUI worker の**着手宣言(ack)が猶予を超えて無い**(終了コード 6。`--ack-grace` /
  `--ignore-unacked-workers` で調整)。指示が届いていない疑いです。前の 2 つは herdr が
  端末の見た目から出す答えなので、「エージェントは居て忙しそうに見えるのにプロンプトが
  届いていない」障害には盲目です。この検査だけがそれを映し、21〜28 分かかっていた発見を
  数分に縮めます。画面を確認して(承認 UI などが塞いでいたら先に対処し)、
  `kanban recall <ID> --force` で再送します。この再送の文面は「差し戻しではない」と
  明記されるので、worker が存在しないレビュー指摘を探しに行くことはありません。

止まっていた worker に答えたら、その内容は `kanban handoff` でボードにも残してください。

worker がサンドボックスでボードに書けなかった場合、報告は worker の worktree の
`.kanban-outbox/` に退避されています。`kanban wait` は毎ポーリングでそれを取り込むので、
待ち方は変わりません。取り込みが起きると `📥 outbox 取り込み: ...` と出ます。

反応がないときの調べ方は `references/herdr.md` の「様子を見る」へ。

### 6. 報告を吟味する ← ここが本番

```sh
kanban show KAN-001                       # 経緯と申し送り
cat "$(kanban path)/reports/KAN-001-r1.md"  # 報告書
```

**結論を読む前に、自分で差分を見ます。**

```sh
kanban diff KAN-001 --stat    # 大きさと範囲
kanban diff KAN-001           # 中身
```

チケットに記録された worktree と base ブランチを見て `git diff` を組み立てます
(実際に走るコマンドは `--print-cmd`)。

報告書を先に読むと、その筋書きに沿って差分を眺めてしまいます。順序を逆にするだけで
見落としが減ります。安ければテストも自分で走らせます。

判定は 3 つの問いを**この順で**通します。

1. **主張は検証可能か?** 証拠(実行したコマンドと出力、ファイル:行)が付いているか。
   → 付いていない ⇒ `--needs-info`**調査の差し戻し**2. **AC を満たしているか?** AC 1 件ごとに証拠が対応しているか、範囲外の変更が混ざっていないか。
   → 満たしていない ⇒ `--rework`**実装の差し戻し**3. **自分に判断材料があるか?** 報告は妥当だが、設計判断や影響範囲が分からず受入を決められないか。
   → 決められない ⇒ `--spike`**判断のための調査差し戻し**4. 3 つとも問題なし ⇒ `--accept`

順序が重要です。証拠がない報告から AC 充足は判断できないので、1 を飛ばして 2 を
判断すると、印象で受け入れることになります。

```sh
# 完了
kanban review KAN-001 --accept --note "AC 3 件を証拠つきで確認。差分は scope 内。"

# 実装の差し戻し(コードが違う)
kanban review KAN-001 --rework \
  --note "AC2 未達。無効 token で 401 ではなく 500 になる。src/auth/refresh.py:88 で例外を握りつぶしている。修正後、無効 token を投げた実際の出力を報告書に貼ること。"

# 調査の差し戻し(コードは触らず、証拠を揃えさせる)
kanban review KAN-001 --needs-info \
  --note "AC2 の判定が『実装済み』としか書かれていない。実際に失効 token を投げたコマンドと出力を貼ること。コードは変更しないこと。"

# 判断のための調査差し戻し(別チケットを起こして親をブロック)
kanban review KAN-001 --spike \
  --question "refresh token のローテーションは必須か。既存クライアントが再ログインを強いられないか確認したい。"
```

差し戻しには**何をすれば受け入れられるか**を必ず書きます。指摘だけ書いて受入条件を
書かないと、次のラウンドでも同じ報告が返ってきます。

`--spike` は調査チケットを自動で起票し、親を `blocked` にして依存を張ります。調査が
`done` になると親は自動で `ready` に戻ります。`--needs-info` との使い分けは、
**同じ worker がその worktree で答えられるか**です。答えられるなら `--needs-info`、
別の場所を調べる必要があるなら `--spike`。

判定基準の詳細、差し戻しの書き方、やってはいけないこと(些細な指摘での往復、
2 回差し戻しても直らないときの扱い)は **`references/workflow.md`** を読んでください。

### 7. 統合して片付ける

`--accept` した時点でチケットは `done` になりますが、**マージは自動ではありません。**
受け入れたことと、成果物が本流に入っていることは別の事実です。`--accept` は未統合の
コミットが残っていればその場で数えて見せます。

```sh
kanban merge KAN-001       # ブランチを base に統合する(git merge --no-ff)
kanban cleanup KAN-001     # worker のペイン(別画面なら workspace)と worktree を削除
```

**統合の入力はコミットだけにします。** worktree からファイルを直接コピーして統合するのは、
一見速くて、実際には最も危険な近道です。並行して動いている worker のファイルは任意の時点で
中間状態にあり、コピーはそれをそのまま拾います。さらにコード以外の成果物(生成した音声、
シーン、アセット)は視野から外れやすく、落としても差分に見えません。実際に、**効果音が
1 本も入っていないビルドを「完成」として通知した**事故と、**worker が書き直す前の草稿を
統合していた**事故が、同じ原因で起きています。コミットは worker が「ここが完成形だ」と
宣言した唯一の地点なので、そこからしか取りません。

`cleanup` が消すのは worktree とペインだけで、**worker のコミットはブランチに残ります**。
ただし 2 つの場合には止まります。どちらも、消すと戻せないものが worktree の中にあるからです。

- **未取り込みの報告が `.kanban-outbox/` に残っている** — サンドボックス下の worker が唯一
  書けた場所ごと消えます。`--force` で続けると、消す前にボードの `rescued/` へ退避します。
- **`done` なのに未統合のコミットがある** — 先に `kanban merge` を促します。

`kanban doctor` も同じ検査をするので、片付け忘れは後からでも見つかります。

分割モードの worker を片付けると、そのペインが閉じて残りのペインが広がります。
次の dispatch はその広がった場所を割るので、詰まってきたら**終わった worker から
片付ける**のが画面を保つコツです。

コンフリクトしたら、解決を worker に返すほうが安全です(文脈を持っているのは worker です)。
`kanban move KAN-001 changes_requested --note "main と衝突: src/auth/router.py を rebase して再提出"`
としてから `kanban recall KAN-001`## worker として動くとき

`briefs/*.md` の作業指示書が渡されているはずです。書かれていること:

- 何よりも先に `kanban ack <ID>`**着手を宣言**する。リーダーはこれだけを
  「指示が届いた」証拠として扱う(端末の見た目は当てにされない)。
- 作業は自分の worktree の中だけ。他の worktree と main を触らない。
- `done` に動かさない。終点は `report` による `review` まで。
- 仕様に疑問が出たら推測で進めず、`kanban handoff <ID> --note "..."` に書いて止まる。
- 報告書には**主張ではなく証拠**を書く。

```sh
kanban report KAN-001 --template > /tmp/myapp-KAN-001-report.md   # ひな形(パスは指示書の指定に従う)
# 全セクションを埋める
kanban report KAN-001 --file /tmp/myapp-KAN-001-report.md         # 提出 → review へ
```

一時ファイルの名前は**プロジェクト識別子で始めます**(指示書が具体的なパスを指定します)。
チケット ID は別プロジェクトでも同じ番号が振られ、`/tmp` は共有なので、ID だけの名前は
過去プロジェクトの残骸と衝突します。

必須セクションが欠けた報告書は提出時に弾かれます。リーダーがそれ無しでは判定できない
項目だけを必須にしてあるので、埋める手間はレビュー 1 往復より安く付きます。

### ボードに書けなかったとき

worker を動かしているエージェント CLI は、書き込みを自分のワークスペースの中に閉じ込め、
さらに**リポジトリの `.git/` を別扱いにします**(エージェントが履歴や hook を書き換えない
ための保護)。ボードは `.git/kanban` にあるので必ずこれに引っかかります。**worktree の
内か外かは関係なく、main チェックアウトで作業していても同じです。** 読み取りは通るので
指示書は問題なく読め、失敗するのは最後の `kanban report` だけ——という一番たちの悪い
壊れ方をします。`dispatch` は必要な許可を渡して起こしますが(`agent_args`)、
worker を手で起こしたときや CLI 側が引数を受け付けなかったときは、この道に落ちます。

そのため**書き込みは失敗しません**。書けないときは自分の worktree の `.kanban-outbox/` に
退避され、こう表示されます:

```
KAN-001: 報告書をアウトボックスに退避した (ボードに書き込めないため)
```

**これは成功です。** リーダー側の `kanban` が次に走ったときに自動で取り込まれ `review` に移ります。

- 同じ報告を**出し直さない**。退避は 1 回で足ります。
- `.kanban-outbox/`**消さない・コミットしない**`.git/info/exclude` で既に除外済み)。
- 報告を諦めて黙って終了**しない**。報告が届かなければリーダーは `review` を待ち続けて止まります。
- `kanban` が「アウトボックスにも書けない」と言って失敗したときだけは自動退避もできません。
  そのときは**報告書の全文を自分の応答本文に書く**こと。リーダーは端末越しにそれを読めます。

## 申し送り

- **チケットの申し送り**: 全ての状態遷移・割当・レビューが各チケットの
  `## 申し送り / Handoff Log` に自動で積まれます。任意の補足は
  `kanban handoff KAN-001 --note "..."`。担当を替えても経緯は失われません。
- **セッションの申し送り**: リーダーが自分の文脈を次のセッションに渡す場所が
  `$(kanban path)/HANDOFF.md`**セッションを終える前と、長い作業の区切りで更新します。**
  文脈が切れた新しいリーダーは、まず `kanban board``HANDOFF.md` → 気になるチケットの
  `kanban show` の順で復帰できます。

## 迷ったときの一覧

```sh
kanban board              # 全体像
kanban next               # いま出せるもの
kanban list --status review   # 自分が判断すべきもの
kanban log KAN-001        # 何が起きたか
kanban preflight          # worker が動ける環境か(最初の dispatch の前に 1 回)
kanban merge KAN-001      # 受け入れたブランチを統合する
kanban doctor             # 壊れているところ(迷子の worktree、WIP 超過、宙に浮いた依存、
                          #   未統合のまま done になったチケット、取り残された報告)
```

## リファレンス

- **`references/workflow.md`** — 状態機械の全体、Definition of Ready / Done、
  **レビュー判定の詳細な基準と差し戻しの書き方**、行き詰まったときの扱い。
  レビューで迷ったら必ずここを読む。
- **`references/herdr.md`** — herdr の具体的なコマンド、worker の置き方(ペイン分割 /
  別 workspace)と分割の規則、`kanban dispatch` を使わない手動 dispatch、worker の
  様子の見方、詰まったときの対処。
- **`references/cli.md`**`kanban` 全サブコマンドとボードのファイル構成。
  ボードを手で直したいときはここ。

付録 B: allow-godot-window SKILL.md 全文

~/src/GodotPlayOnCodexOnHerdr/SKILL.md として保存してください。symlink 名は frontmatter の name に合わせて allow-godot-window にします。

allow-godot-window/SKILL.md(105 行、クリックで展開)
---
name: allow-godot-window
description: Explicitly authorize one brokered Godot GUI session for the current Herdr workspace, including launching Godot, capturing only its owned window for visual QA, and stopping it safely. Use only when the user invokes $allow-godot-window or /allow-godot-window, or explicitly asks to allow/open/show the current project in a Godot window through the Herdr GUI broker.
---

# Allow Godot Window

Treat invocation as authorization for one Godot GUI instance for the current
project in the current Herdr workspace. Use only an approved typed Herdr GUI
broker backend; keep the shell sandbox in place.

Other projects and other workspaces may hold their own instances at the same
time. That is expected with parallel worktrees; it is never a reason to touch an
instance other than the one belonging to the current pane's project.

## Enforce the authorization boundary

- Require a Herdr-managed workspace. Prefer the current `HERDR_WORKSPACE_ID` or
  equivalent trusted session context.
- Limit the launch to `app_id: "godot"` and the allowlisted `autostart` profile.
- Use the current pane and its worktree only. Do not select another pane or
  workspace without a new explicit user request.
- Let the broker resolve the executable, arguments, environment, working
  directory, capture destination, and process identity.
- Never use `open`, `launchctl`, Apple Events, a Godot executable path, a raw
  socket client, `sh -c`, or a relaxed shell sandbox as a fallback.
- Never pass arbitrary executable paths, argv, environment variables, PIDs, or
  screenshot paths.
- Do not enable or reconfigure the broker, grant macOS permissions, or allow
  remote invocation. This skill authorizes use of an already configured broker,
  not administrative changes.

## Run the window lifecycle

1. Select one available typed broker backend:
   - Prefer MCP operations corresponding to `herdr_gui_launch`,
     `herdr_gui_capture`, and `herdr_gui_stop`.
   - Otherwise, use the linked and enabled Herdr broker plugin only when
     `herdr plugin action list --plugin local.godot-gui-probe` exposes
     `launch`, `capture`, and `stop`. These fixed plugin actions are an approved
     broker backend, not a direct Godot launch fallback.
   - If neither backend is available, stop and explain that the Herdr GUI broker
     must be enabled and connected.
2. Launch once with:
   - `app_id: "godot"`
   - the current trusted workspace ID
   - `profile: "autostart"`
   - a bounded TTL; use the broker default unless the user requests a shorter
     duration
3. Retain the opaque handle returned by the broker. Do not treat the returned PID
   as an authority or use it for later operations. With the Herdr plugin
   backend the actions carry no arguments, so the broker resolves the instance
   from the focused pane's project; report the handle it returns and confirm it
   matches the one from launch.
4. If the user requested visual inspection, capture by handle after the window is
   ready and inspect the returned image. On a specific window-not-ready state,
   retry only a small bounded number of times. Do not retry permission,
   invalid-handle, exited-process, or GUI-session errors indefinitely.
5. Stop by handle when QA finishes, when later work fails, or when the user
   cancels. Use the broker's normal grace period unless the user has a concrete
   reason to request another bounded value.

For the Herdr plugin backend:

- Require `HERDR_ENV=1` and a non-empty `HERDR_WORKSPACE_ID`.
- Require a non-empty `HERDR_PANE_ID`. Immediately before every action, focus
  the trusted current agent pane with `herdr agent focus "$HERDR_PANE_ID"`
  because split panes in one Herdr workspace may point at different worktrees
  and plugin actions receive the focused pane context.
- If the current pane cannot be focused as an agent target, stop instead of
  silently falling back to the workspace's main checkout.
- Invoke only `local.godot-gui-probe.launch`,
  `local.godot-gui-probe.capture`, or `local.godot-gui-probe.stop`.
- Read the corresponding asynchronous result with
  `herdr plugin log list --plugin local.godot-gui-probe`. Treat the action as
  successful only when its log status is `succeeded`.
- After capture, inspect the broker-returned PNG path. The plugin stores captures
  only below its Herdr-managed state directory.
- Do not edit the plugin's allowlist configuration unless the user explicitly
  asks to authorize additional project roots.

If the user asked only to display the window for manual inspection, leave it
running and report that it remains bounded by the broker TTL. Otherwise, perform
stop as cleanup and report whether shutdown was graceful or forced.

## Handle failures

- For `screen_recording_permission_required`, explain that launch may still have
  succeeded but capture requires macOS Screen Recording permission. Do not change
  TCC settings.
- For `gui_session_unavailable`, explain that an Aqua login session is required.
- For `already_running`, report that this project already holds an instance in
  this workspace. Capture it or stop it; do not launch a second one and do not
  reach for another project's instance.
- For `handle_not_found`, report that this project has no instance in this
  workspace. Launch one only if the user asked for a window.
- For disabled broker, unknown app/profile, disallowed workspace, missing
  `project.godot`, or remote-call rejection, report the broker error without
  bypassing it.
- If launch returned a handle before another failure, make one best-effort stop
  call with that handle unless the user explicitly asked to keep the window open.

Report the workspace, broker state, capture result when applicable, and cleanup
outcome. Do not expose or rely on implementation details beyond what the broker
returns.

付録 C: kanban CLI の仕様

bin/kanban は Python 3 標準ライブラリのみの単一ファイル(4,099 行)です。全文は記事に収まらないため、同等品を実装できる粒度で仕様を書き下します。SKILL.md(付録 A)が「いつ何を呼ぶか」を全部指示するので、CLI 側はこの契約を満たせば差し替え可能です。

大原則

  • ボードの場所は git rev-parse --git-common-dir で解決した共有 .git の下の kanban/ これにより全 worktree から同じボードが見える。jujutsu (jj) では .jj/repo 相当。VCS 依存はバックエンド 1 層に閉じ込める(worktree の作成/削除、diff/merge のコマンド組み立てだけが差し替わる)。
  • すべて素のテキスト。 チケットは frontmatter + 固定セクションの Markdown、設定は board.json、履歴は追記専用の events.jsonlcat で読め、緊急時は手で直せる。採番と WIP 判定だけ .lock の flock で排他する。
  • 書き込みは失敗させない。 ボードに書けない環境(エージェント CLI のサンドボックス)では、アクションを worker の worktree の .kanban-outbox/ に JSON envelope + 本文で退避し、リーダー側のあらゆるコマンド実行時に自動で取り込む。退避先は .git/info/exclude に登録してコミット対象から外す。
  • 状態遷移は検査する。 worker が done に動かす、DoR を満たさず ready にする、WIP 上限超過で in_progress にする——は拒否する。

ボードのファイル構成

本文「設計の核」の節のとおり。tickets/ briefs/ reports/ reviews/<ID>-r<ラウンド>.md の連番で、差し戻しのたびに r2, r3… と増え、過去の分は上書きしない。

チケットの frontmatter

---
id: KAN-001
title: JWT リフレッシュ endpoint を追加
type: feature          # feature|bug|chore|refactor|docs|spike|test
status: in_progress    # backlog|ready|in_progress|review|changes_requested|
                       # needs_info|blocked|done|cancelled
priority: P1           # P0|P1|P2|P3
size: M                # S|M|L|XL
assignee: starshooter-kan-001   # herdr のエージェント名。未割当は -
branch: kan/kan-001-jwt-refresh
worktree: /Users/you/.herdr/worktrees/repo/...
workspace: w3          # herdr workspace id
pane: w3:p1            # herdr pane id(recall の送り先)
layout: split          # split|tab|workspace(cleanup の畳み方が変わる)
launch: exec           # agent|exec(生存確認のしかたが変わる)
kind: codex            # herdr agent kind(recall で同じもので起こし直す)
dispatched_at: ...     # 指示を送った時刻
acked_at: ...          # worker が着手を宣言した時刻(無ければ未着手疑い)
depends_on: [KAN-004]
blocks: [KAN-002]
parent: -              # spike の場合は親チケット
rework_count: 2        # 差し戻された回数
round: 3               # 次に提出される報告のラウンド
---

本文セクションは固定: ## 目的 / Goal## 受入基準 / Acceptance Criteria## 作業範囲 / Scope(やること/やらないこと)、## 参考 / Context## 検証コマンド / Verification## 申し送り / Handoff Log(必ず最後。全遷移・割当・レビューが自動で追記される)。

board.json の主なキー

キー 意味
wip_limits in_progress / review の同時本数
base_branch / branch_prefix worktree を切る元と、ブランチ名の接頭辞(kan/)
agent_kind dispatch の既定 kind(claude, codex, ...)
agent_args kind ごとに worker 起動コマンドへ足す引数。{board} {shared} {worktree} {kanban} を展開。既定: claude は --add-dir {board} --allowedTools "Bash({kanban}:*)"、codex は --sandbox workspace-write --add-dir {shared}
exec_commands --exec 時のコマンド(kind ごと)。{brief} {log} も展開
dispatch_layout split(リーダーの画面を分割・既定)/ workspace(別画面)
dispatch_launch agent(対話 TUI・既定)/ exec(非対話)
agent_prefix worker 名の接頭辞(既定はリポジトリ名)。herdr の名前空間衝突対策
setup_cmd 新チェックアウト直後に走るコマンド(import キャッシュ配りなど)
brief_notes 全指示書に差し込む共通文(プロジェクト共通の制約と実測値)
pane_settle / pane_min / ack_grace ペイン起動待ち(1s)/最小セル(80x20)/ack 猶予(180s)

サブコマンド一覧

セットアップ:
  init      ボードを共有メタデータ領域に作る(--prefix --wip-in-progress --base ...)
  path      ボードのディレクトリを表示
  doctor    整合性検査(迷子の worktree、宙に浮いた依存、WIP 超過、
            未統合のまま done、未取り込みの outbox、消えた/未 ack の worker)

起票〜配布(leader):
  new       起票(--title --type --priority --size --goal --ac* --scope
            --out-of-scope --context --verify* --depends*)
  edit      フィールド更新(--set key=value)
  ready     DoR を検査して ready へ(--check で verify コマンドを試走、--force)
  next      依存が解けて WIP に空きがある、いま出せるチケット
  preflight 使い捨てチェックアウトで worker と同じ経路のプローブを流す
  brief     作業指示書を briefs/<ID>-r<N>.md に書き出す(--write)
  assign    担当/worktree/pane を記録して in_progress へ(手動 dispatch 用)
  dispatch  worktree 作成→ペイン分割→エージェント起動→指示書送信
            (--kind --exec --no-split --force --agent-arg* --exec-command
             --worktree-path --pane-settle --settle --on-full --prompt-timeout)
  recall    差し戻し/再送をいまのペインに送る(--force)

進行監視(leader):
  board     ボード全体を表示
  list      一覧(--status で絞り込み)
  show      チケットの中身(--json)
  log       events.jsonl の履歴
  wait      指定状態になるまでポーリング(--status* --timeout)。毎回、
            outbox 取り込み/worker 消失(exit 4)/入力待ち 2 回連続(exit 5)/
            ack 猶予超過(exit 6)を検査して早期中断
  diff      チケットの worktree と base の git diff(--stat --print-cmd)

worker:
  ack       着手を宣言する(指示書を読んだ直後、作業より先に)
  report    完了報告(--template でひな形、--file で提出→ review へ)。
            必須セクション欠けは拒否。ボードに書けなければ outbox へ退避
  handoff   申し送りをチケットに追記(--note)

判定〜統合(leader):
  review    --accept | --rework | --needs-info | --spike(--note / --question)。
            spike は調査チケットを自動起票して親を blocked に、
            調査 done で親を自動 ready 復帰。accept は未統合コミットを数えて警告
  move      任意の状態遷移(--note)。不正遷移は拒否
  merge     base へ git merge --no-ff で統合
  cleanup   ペイン/workspace と worktree を削除。未取り込みの outbox や
            未統合コミットが残っていれば停止(--force で rescued/ へ退避後に削除)

報告書の必須セクション

report --file はこの 6 セクションが揃っていないと受理しません。

## 概要 / Summary
## 受入基準の検証 / AC Verification     ← AC 1 件につき 1 行、証拠への参照つき
## 実行した検証 / Evidence              ← 実際のコマンドと出力
## 変更点 / Changes                     ← ブランチ、コミット、diff --stat
## 逸脱と判断 / Deviations & Decisions  ← 「特になし」で済ませない
## 残課題 / Follow-ups

dispatch が指示書に差し込む条件付きセクション

リポジトリに project.godot があり、allow-godot-window スキルが導入済みで、worker がエージェントのペインに載っている——の 3 条件が揃ったときだけ、実機キャプチャ手順(本文「Godot プラグイン」の章のコマンド列)を挿入する。書き方は kind で分岐: --kind claude はスキル起動構文、それ以外はブローカーを直接叩く生コマンド、--exec は「使えないのでリーダーに依頼し未検証と明記」。

16
12
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
16
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?