ブログに戻る

並列開発でのブロックされたタスクのトリアージ

10体のAIコーディングエージェントを並列で実行できるようになった。それぞれにイシューを渡し、共有のbeadsデータベースに向けて作業させる。エージェントはサブタスクを作成し、発見したバグを登録し、ステータスを更新し、完了したらイシューをクローズする。本当に生産的だ。

何かがブロックされるまでは。

人間がブロックされたら、何かを言う。Slackに投稿し、スタンドアップで報告し、誰かのデスクまで歩いていく。エージェントはそのどれもしない。エージェントは解決できない依存関係にぶつかると、黙って停滞するか、問題を回避しようとして新たな問題を生む方法で作業を始める。3体のエージェントが同じ未解決の上流タスクで詰まっていても、4時間何も出荷されていない理由を不思議に思うまで気づかない。

これがエージェント開発におけるトリアージの問題だ。「よりよいスタンドアップの運営方法」ではなく、「文句を言わない自律ワーカーのフリート全体で、何が詰まっているかをどう見るか」という問題だ。Beadboxを構築して学んだことを紹介する。beadsのリアルタイムダッシュボードで、エージェントが何をしているか、何にブロックされているか、何が利用可能になったかを正確に表示する。

関心のあるセクションに直接ジャンプ:

エージェントがブロッキングチェーンを作る仕組み

エージェントは人間チームとは異なる方法で依存関係の問題を生む。障害モードを理解することが重要だ。トリアージの対応が異なるためだ。

エージェントは依存関係を事前にモデル化しない。 人間のアーキテクトはフィーチャーをタスクに分解し、順序について考える。コーディングエージェントはタスクを受け取り、作業を開始し、実装の途中でまだ存在しないものが必要だと気づく。その依存関係のために新しいイシューを作成するかもしれない。インラインで構築しようとして混乱を作るかもしれない。単に止まるかもしれない。これらの結果はどれも、イシューデータベースを監視していない限り見えない。

エージェントは依存関係グラフの更新より速く作業する。 Agent-3がAgent-7が待っていたタスクをクローズするが、Agent-7は10分前にブロッカーをチェックしたので知らない。その間Agent-7はまだアイドル状態か、より低い優先度のタスクに取り組んでいる。ブロック解除は起きたが、情報が伝搬していない。

並列分解から循環依存が生まれる。 複数のエージェントが同時に作業を分解すると、単一のエージェントには見えないサイクルを作成できる。Agent-1がTask Bに依存するTask Aを作成。Agent-2がTask Cに依存するTask Bを作成。Agent-3がTask Aに依存するTask Cを作成。各依存関係はローカルでは理にかなっていた。サイクルは上から見ないと見えない。

リソース競合は見えない。 2つのエージェントが同じファイルの修正を必要としている、またはどちらもステージング環境が必要、または共有ライブラリが安定状態にある必要がある。依存関係は登録されていない。どちらのエージェントも相手の存在を知らないからだ。両方とも遅くなるだけで、どちらもその理由を報告しない。

共通するのは: エージェントは報告するより速くブロッキング状況を生む。スーパーバイザー(あなた)には、誰かがフラグを上げるのを待つのではなく、ブロックを自動的に表面化させるツールが必要だ。

自動依存関係検出

修正は、タスク作成時に作られ継続的にチェックされる、明示的でクエリ可能な依存関係データだ。beadsで、自分たちのエージェントフリートが使っているGitネイティブイシュートラッカーでどう見えるかを示す。

エージェントがタスク作成時に依存関係を記録:

# エージェントがタスクを作成し、まだ存在しないAPIが必要だと発見
API_TASK=$(bd create \
  --title "Implement /api/v2/orders endpoint" \
  --type task --priority 2 --json | jq -r '.id')

# エージェントが自分のタスクを作成し、依存関係を宣言
UI_TASK=$(bd create \
  --title "Build order history page" \
  --type task --priority 2 --json | jq -r '.id')

bd dep add "$UI_TASK" "$API_TASK"

bd dep addはCLIの1コマンド。どのAIコーディングエージェント(Claude Code、Cursor、Copilot Workspace)でも実行できる。APIクライアントライブラリも認証のダンスもない。依存関係は構造化データになり、他のエージェントやスクリプトからクエリ可能になる。

サイクル検出が自動実行:

# beadsが依存関係グラフ全体のサイクルをチェック
bd dep cycles

# サイクルがある場合の出力:
# CYCLE DETECTED: beads-a1b -> beads-c3d -> beads-e5f -> beads-a1b

5,000件の依存関係グラフで約70ms。post-commitフックか5分間のcronで実行する。3体のエージェントが独立に依存関係サイクルを作成しても、3体すべてが停滞してから数時間後に発見するのではなく、数分でキャッチできる。

ブロックされたすべてのタスクを1コマンドで表面化:

bd blocked --json | jq -r '.[] |
  "BLOCKED: \(.id) \(.title)\n  waiting on: \(.blocked_by | join(", "))\n  assignee: \(.owner // "unassigned")\n"'

出力:

BLOCKED: beads-x7q Build order history page
  waiting on: beads-m2k Implement /api/v2/orders endpoint
  assignee: agent-3

BLOCKED: beads-r4p Deploy staging environment
  waiting on: beads-j9w Fix Docker build, beads-n1c Update TLS certificates
  assignee: agent-7

Agent-3とAgent-7が詰まっていること、何に詰まっているか、ブロック解除に何が必要かがわかる。このクエリ全体が1万件のイシューデータベースで30msかかった。

ブランチ命名規則からブロックされたPRを検出:

#!/bin/bash
# blocked-prs.sh: 依存関係がマージされていないPRを検出

for pr in $(gh pr list --json number,headRefName --jq '.[].headRefName'); do
  ISSUE_ID=$(echo "$pr" | grep -oE 'beads-[a-z0-9]+')
  [ -z "$ISSUE_ID" ] && continue

  BLOCKERS=$(bd show "$ISSUE_ID" --json | jq -r '.blocked_by[]' 2>/dev/null)
  for blocker in $BLOCKERS; do
    BLOCKER_STATUS=$(bd show "$blocker" --json | jq -r '.status')
    if [ "$BLOCKER_STATUS" != "closed" ]; then
      echo "PR ($pr) blocked: $ISSUE_ID waiting on $blocker ($BLOCKER_STATUS)"
    fi
  done
done

20行のシェルスクリプト。ローカルで実行、ローカルデータを読み、エージェントのどのPRがまだマージできないか、その理由を教えてくれる。

CLIファーストのトリアージワークフロー

エージェントワークフローにおけるトリアージはミーティングではない。ループで実行されるスクリプトだ。スーパーバイザー(人間またはエージェント)が詰まっているものを見て、各項目に対して判断を下す。

実際に実行しているトリアージスクリプト:

#!/bin/bash
# triage.sh: エージェントフリートのブロッカートリアージ

echo "========================================="
echo "TRIAGE REPORT: $(date +%Y-%m-%d %H:%M)"
echo "========================================="

# 1. 何がブロックされている?
echo -e "\n--- BLOCKED TASKS ---"
bd blocked --json | jq -r '.[] |
  "[\(.priority)] \(.id) \(.title)
    blocked by: \(.blocked_by | join(", "))
    assignee: \(.owner // "unassigned")\n"'

# 2. エージェントがピックアップできるものは?
echo -e "\n--- READY (unblocked, open) ---"
bd ready --json | jq -r '.[] |
  "[\(.priority)] \(.id) \(.title) (\(.owner // "unassigned"))"'

# 3. 静かになったエージェントは?
echo -e "\n--- STALE IN-PROGRESS (no update in 2h) ---"
CUTOFF=$(date -v-2H +%Y-%m-%dT%H:%M:%S 2>/dev/null || date -d '2 hours ago' --iso-8601=seconds)
bd list --status=in_progress --json | jq -r --arg cutoff "$CUTOFF" '.[] |
  select(.updated_at < $cutoff) |
  "STALE: \(.id) \(.title) (last update: \(.updated_at), assignee: \(.owner // "unknown"))"'

# 4. 依存関係の健全性
echo -e "\n--- DEPENDENCY CYCLES ---"
bd dep cycles 2>&1 || echo "No cycles detected."

echo -e "\n--- FLEET STATS ---"
bd stats

人間チームの場合、「stale」は48時間更新なしを意味する。エージェントの場合、進行中タスクが2時間沈黙していたら赤信号だ。エージェントが詰まっていて報告していないか、クラッシュしたかのどちらかだ。いずれにしても確認が必要だ。

各ブロック項目に対する判断ツリー:

  1. 別のエージェントがブロック解除できるか? ブロッキングタスクの優先度を上げ、利用可能なエージェントをアサインする。
  2. 依存関係は偽物か? エージェントは計画時に過度に保守的な依存関係を登録することがある。ブロックが本物でなければ削除: bd dep remove beads-x7q beads-m2k(x7qのm2kへの依存を削除し、x7qを即座にブロック解除)。
  3. 作業を分割できるか? 依存関係を必要としない部分をブロックされたエージェントに作業させる。残りはフォローアップタスクを作成する。
  4. 外部ブロックか? 人間だけが解決できるもの(APIキー、デザイン判断、アクセス付与)。タグ付けし、予想される解決を記録し、エージェントを他のレディな作業に再アサインする。

オプション2はエージェントで頻繁に発生する。計画時のコードベースの理解に基づいて依存関係をモデル化するが、実装が始まると作業の本当の形が明らかになり、依存関係の半分が不要だったとわかる。

エージェント作業のリアルタイム可視性

30分ごとのトリアージスクリプト実行ではギャップが残る。エージェントが速く作業するとき、チェックの間に多くのことが起きる。問いは: リアルタイムでブロックの形成を見られるか?

Beadboxのやり方:

beadsデータベースはファイルシステムの.beads/ディレクトリに存在する。エージェントが実行するbd updatebd createbd closeのすべてがそのディレクトリに書き込む。Beadboxfs.watch()で監視し、ミリ秒以内にWebSocket経由でUIに変更をプッシュする。

実用的な効果: Agent-5がターミナルでbd update beads-x7q --status=closedを実行する。Beadboxダッシュボードが即座にそのタスクをクローズド表示し、それにブロックされていたタスクが新たに利用可能として点灯する。コマンドを実行せずにカスケードが見える。

これが重要なのは、エージェント作業がバースト的に発生するからだ。エージェントが90秒で3つのタスクをクローズし、それぞれが異なる下流の作業をブロック解除するかもしれない。30秒のリフレッシュ間隔のポーリングベースのダッシュボードでは、混乱する中間状態が表示される。サブ秒の伝搬は、起きているままの全体像を見せる。

Beadboxを使わない場合でも、ファイルシステムウォッチは機能する:

# beadsデータベースの変更を監視し、新しいブロックをアラート
# 注意: fswriteは書き込みごとに発火する。本番ではデバウンスが必要
# (例: トリガーごとにsleep 2)。バースト書き込み時のノイズ回避のため。
fswatch -o .beads/ | while read; do
  BLOCKED_COUNT=$(bd blocked --json | jq length)
  if [ "$BLOCKED_COUNT" -gt 0 ]; then
    echo "$(date): $BLOCKED_COUNT tasks currently blocked"
    # ntfy、Slack webhook、またはその他の通知システムにパイプ
  fi
done

CIでループを閉じる:

# CIのpost-buildステップ: ビルド成功時にイシューを自動クローズ
if [ "$BUILD_STATUS" = "success" ]; then
  ISSUE_ID=$(echo "$BRANCH_NAME" | grep -oE 'beads-[a-z0-9]+')
  if [ -n "$ISSUE_ID" ]; then
    bd update "$ISSUE_ID" --status=closed
    bd comments add "$ISSUE_ID" --author ci \
      "Build passed. Commit: $COMMIT_SHA. Closing automatically."
  fi
fi

CIがそのイシューをクローズすると、それがブロックしていたすべてがブロック解除される。エージェントが新しい作業のためにbd readyを監視していれば、ブロック解除されたタスクを自動的にピックアップする。日常的なブロック解除に人間のループは不要だ。

これが、ステータスを追跡するツールとステータスを伝搬するツールの違いだ。大半のプロジェクト管理ソフトウェアは前者だ: カードを更新すると、カードの色が変わる。伝搬とは、下流の効果(依存先のブロック解除、利用可能な作業の表面化、進捗ロールの更新)が誰もクリックせずに起きることだ。

エージェントワークフロー向けトリアージツールの評価

エージェントフリートを管理するツールを探しているなら、要件は人間チームとは異なる。

必須: エージェントが呼べるCLI。 イシュートラッカーにWeb UIしかなければ、エージェントは使えない。シェルコマンドを実行する必要がある。bd createbd updatebd blockedはすべてワンライナーで、どのコーディングエージェントでもすでに実行方法を知っている。REST APIも機能するが、認証トークン、HTTPクライアント、エラーハンドリングが必要だ。Unixパイプのほうがシンプルだ。

必須: クエリ可能な依存関係グラフ。 ステータスラベルとしての「Blocked」は自動化には使えない。スクリプトがグラフを走査し、サイクルを検出し、何がレディかを計算できるように、AはBに依存するが構造化データとして必要だ。

必須: サブ秒のローカル読み取り。 エージェントが利用可能な作業をクエリするとき、レスポンスタイムが重要だ。クエリごとに2秒のAPIラウンドトリップを、1分ごとにポーリングする10体のエージェントで掛けると、測定可能なオーバーヘッドになる。beadsは1万件のイシューデータベースでbd readyの結果を30msで返す。すべてがローカルだからだ。

あると良い: リアルタイムの変更伝搬。 エージェントが1時間に50件のイシューを作成・解決する場合、リフレッシュ間隔ではなく、変化する状態をそのまま見る必要がある。

レッドフラグ: 「AI搭載のブロッカー検出」。 イシューの説明を分析してブロッカーを検出すると主張するツールは、偽陽性を生み、書かれていない本当のブロッカーを見逃す。明示的なbd dep add宣言が推論に勝る。

レッドフラグ: ブラウザでトリアージが必要なツール。 Web UIで1つのタスクをブロック解除するのに5-15秒のクリック。CLIではbd dep removeに18ms。50個のブロックされたタスクで、1分 vs 12分。速く動くエージェントを監督しているとき、トリアージ速度がボトルネックだ。

主要ツールのブロッキング対応比較

機能 Jira Linear GitHub Issues beads + Beadbox
依存関係トラッキング プラグイン(Advanced Roadmaps) Relations(部分的) Tasklistリファレンス ファーストクラスのbd dep add
ブロック状態の自動設定 手動 手動 手動 依存関係から自動
サイクル検出 なし なし なし 組み込み(bd dep cycles)
エージェント用CLI Jira CLI(サードパーティ) Linear CLI(限定的) gh(依存関係なし) フル(bd blockedbd ready)
リアルタイム伝搬 Webhook(サーバーサイド) Webhook(サーバーサイド) Webhook(サーバーサイド) fs.watch(サブ秒、ローカル)
オフライン/ローカル動作 不可 不可 不可 可(組み込みモード)
エージェントスクリプト可能 API + 認証トークン API + 認証トークン gh CLI bd CLI(Unixパイプ)

スーパーバイザーループ

1つのプロジェクトで10体以上のAIエージェントを管理する、日常的に実行しているワークフロー:

  1. エージェントがタスク作成時に依存関係を宣言。 前提条件のあるすべてのbd createに、即座にbd dep addが続く。タスクごとに追加のCLI呼び出し1回だ。

  2. スーパーバイザーエージェントが30分ごとにbd blockedを実行。 新しくブロックされたものがあれば、自分でブロッカーを解決する(優先度変更、再アサイン)か、人間にフラグを立てる。

  3. 人間の画面でBeadboxが動いている。 ダッシュボードがブロックされたタスクをハイライトした完全な依存関係グラフをリアルタイムで表示する。大半の場合、自動化が日常的なブロック解除を処理する。できないとき(外部依存、アーキテクチャ判断、アクセス付与)、人間が即座に問題を見て介入する。

  4. 古いタスクを積極的にフラグ。 2時間進行中タスクを更新していないエージェントは、詰まっているかクラッシュしたかだ。スーパーバイザーが確認し、エージェントをナッジするか、作業を再アサインするか、調査する。

  5. 偽の依存関係を継続的に剪定。 エージェントは計画中に依存関係を過剰に宣言する。実装が作業の本当の形を明らかにするにつれ、スーパーバイザー(またはエージェント自身)が不要だった依存関係を削除する。クリーンなグラフは有用なグラフだ。

根底にある原則: エージェントは速いが自己認識がない。他のエージェントが何をしているか知らず、ブロッカーが解決されたことに気づかず、詰まっていても文句を言わない。スーパーバイザーの仕事は、それらすべてを接続する神経系になることだ。自動的にクエリされ視覚的にレンダリングされる構造化された依存関係データが、それを可能にする。


Beadboxはベータ期間中無料。 エージェントが何をしているか、何がブロックされているか、何が利用可能になったかをリアルタイムで表示する。

brew tap beadbox/cask && brew install --cask beadbox

すでにbeadsを使っているなら、Beadboxはインポート手順なしで既存の.beads/ディレクトリを読む。試してみよう。