エージェントが3体。どれが僕を待っているのか、分からない
案件を3つ並行で回していて、それぞれ別のフォルダでAIコーディングエージェントを動かしています。1つは調べもの、1つは実装、1つはビルド待ち。
で、別の作業から戻ってくると分からないんです。どれが終わっていて、どれが僕の承認を待って止まっているのか。
タブを1つずつ開いて確かめる。3回とも「まだ動いてた」。これが地味に効いてきます。
並列で走らせること自体は、もう簡単になりました。難しいのは走らせたあと見張るほうです。
なので作りました。Herdr というターミナルマルチプレクサが持っているエージェント状態APIを叩いて、止まっている1体を見つけて通知する常駐スクリプトです。
動きました。動いたんですが、そのあと同じ機能が標準で用意されていることを知りました。これは、ちょっと恥ずかしかった。
順に書きます。環境は Windows 11 / Herdr 0.8.2 です。
Herdrとは? ざっくり3行
Herdr はターミナルマルチプレクサです。1つのウィンドウの中にターミナルを何枚も並べて管理する道具で、古株が tmux、最近だと zellij。Herdr はそこに後から来た新顔です。
- マウスで操作できる — ショートカットの暗記が要りません。クリックとドラッグでペインを分割・リサイズできます
- 閉じても死なない — ウィンドウを×で閉じても中のプロセスは生き続けます。次に開くとレイアウトごと戻ってきます
- エージェントの状態を持っている — 今回の主役はここです
開発は herdrdev/herdr で公開されていて、更新はだいたい X の @herdrdev から流れてきます。動きの速いプロダクトなので、ここは追っておくと早いです。
0.9.0 is here, and it brings the most wanted herdr feature:
— herdr (@herdrdev) September 8, 2026
all your machines running herdr, in a single client 🎉
control your agents and projects across local and remote machines without jumping between terminal tabs.
実際、この記事を書いている最中にも 0.9.0 が出ました。ローカルとSSH先のマシンを1つのウィンドウにまとめて、エージェント一覧も横断で出せるようになっています(v0.9.0 のチェンジログ)。
以下は 0.8.2 での記録です。0.9.0 のチェンジログには Windows と WSL まわりの修正も入っているので、後半の罠がそのまま再現するとは限りません。追試はしていないので、そこは先に断っておきます。
Windowsで効くのは2番目までの話で、tmux も zellij も基本 WSL を経由しないと動きません。Herdr はネイティブのWindowsに対応しているので、PowerShell や cmd.exe のペインをそのまま並べられます。ただしWindows対応のドキュメントはURLが windows-beta で、名前のとおりベータ扱いです。後半の罠はだいたいそこから来ています。
画面を読まずに「止まっている」が取れた
tmux でも同じことができないか、最初に考えました。落としたのは、tmux から取れるのがペインのテキストだけだからです。「承認ダイアログが出ている」を判定するには画面を正規表現で殴ることになり、エージェントのUIが少し変わるたびに壊れます。
Herdr はそこが違いました。
herdr api snapshot
セッション全体がJSONで返ります。実物を整形して抜き出すとこうです。
{
"agents": [
{
"agent": "claude",
"agent_status": "idle",
"pane_id": "w1:p1",
"workspace_id": "w1",
"cwd": "C:\\work\\project-a",
"terminal_title_stripped": "Claude Code"
}
]
}
ポイントは agent_status。画面のテキストではなく、構造化された状態として持っています(公式ドキュメント)。
| 値 | 意味 |
|---|---|
working | 実行中 |
blocked | 承認・質問のUIを検知して止まっている |
done | バックグラウンド作業が完了 |
idle | 入力待ち |
unknown | 分類できず |
blocked を Herdr 側が判定してくれる。正規表現を書かずに済む理由がこれです。
実装した。見るのは状態じゃなくて遷移
数秒おきにスナップショットを取り、前回と状態が変わったものだけ通知する。Node.js で書きました。
const tracked = new Map()
async function tick() {
const snap = JSON.parse((await run(HERDR, ['api', 'snapshot'])).stdout).result.snapshot
const now = Date.now()
for (const a of snap.agents) {
const prev = tracked.get(a.pane_id)
if (!prev || prev.status !== a.agent_status) {
tracked.set(a.pane_id, { status: a.agent_status, since: now, notifiedAt: 0 })
// 初回観測は「既存状態の報告」なので鳴らさない
if (prev && WATCHED[a.agent_status]) await notify(a)
continue
}
// blocked が続いているなら経過時間つきで再通知
if (WATCHED[a.agent_status] && now - prev.notifiedAt >= RENOTIFY_MS) {
await notify(a, now - prev.since)
prev.notifiedAt = now
}
}
}
判断が3つ入っています。
- 状態そのものではなく遷移で鳴らす — 毎周期で鳴らすと通知が意味を失います
- 初回観測は鳴らさない — 起動時にすでに
blockedなものは「今起きたこと」ではありません。ここを分けないと、常駐を再起動するたびに全部鳴ります - 滞留したら再通知する — 見落としたときに1回きりだと気づけないので、10分続いたら経過時間を付けて鳴らし直します

エージェントにファイルを作らせて、承認ダイアログが出たところで放置します。

idle → working → blocked の3遷移とも取りこぼしなし。ここは期待どおりでした。
罠1:通知が黙って捨てられる
通知はHerdr自身のコマンドを使いました。検証で3回連続で叩いたら、こうなりました。
{"result":{"reason":"shown","shown":true, ...}}
{"result":{"reason":"busy","shown":false, ...}}
{"result":{"reason":"busy","shown":false, ...}}
すでにトーストが表示中だと、次は受け付けられません。しかも終了コードは正常です。例外も飛びません。await して結果を捨てていると、通知が消えたことに気づけません。
見張り役としては、これが一番まずい壊れ方です。鳴らないのに動いているように見える。返ってきた reason をログに残し、busy なら2秒待って最大5回まで粘るようにしました。

罠2:ok と言われたのに、効いていない
通知を有効にするには ui.toast.delivery の設定が要ります。既定は off です(設定リファレンス)。
ホームに .herdr というディレクトリができるので、そこに config.toml を置きました。効きません。あれはバイナリの配布物置き場でした。正しくは %APPDATA%\herdr\config.toml。ソケットやログと同じ場所です。
で、ここで時間を溶かした原因は、検証コマンドのほうにありました。
herdr config check
=> config: ok
設定が効かないので、まずこれを叩きました。ok が返る。だから設定は正しいと思い込んで、別の原因を探しに行ってしまいました。
疑って、存在しないキー bogus_key_test = 1 を書いてみました。それでも config: ok でした。
要するにこのコマンド、打ち間違いも未知キーも検出しません。「設定が効かない」の切り分けには使えないということです。
ついでにもう1点。設定を書き換えてウィンドウを閉じて開き直しても反映されません。Herdrの長所の裏返しで、サーバーが生き続けているからです。herdr server reload-config が要ります。
罠3:ドキュメントどおりに書くと、プロセスが無言で死ぬ
ここが一番はまりました。Windowsで Herdr のプラグインを書く人には確実に効く話です。ドキュメントにはこう書かれています。
ランタイムコマンドは、プラグインディレクトリを作業ディレクトリとして実行されます。相対パスはプラグインルートから解決されます。
なので素直に command = ["node", "banken.mjs"] と書きました。起動すると成功が返り、ペインIDまで発行されます。
{"result":{"type":"plugin_pane_opened","plugin_pane":{
"pane":{"pane_id":"w1:p4","label":"番犬",
"cwd":"\\\\?\\C:\\work\\herdr-banken\\"}}}}
ところが数秒後には消えています。
herdr plugin pane focus w1:p4
=> {"error":{"code":"plugin_pane_not_found","message":"plugin pane not found"}}
厄介なのは失敗の仕方です。エラーはどこにも出ません。herdr plugin log list も空。「開いた」という成功応答だけが残って、実体だけが消えます。
手がかりは上のJSONの cwd に写っていました。\\?\ で始まる拡張長パス形式です。Microsoft のドキュメントを読んだら、はっきり書いてありました。
これらは、パスを最小限の変更でシステムに渡す必要があることを示しています。つまり、パスの区切り記号としてスラッシュを使用することも、現在のディレクトリを表すピリオドや親ディレクトリを表す二重ドットを使用することもできません。
Microsoft Learn「パスの最大長の制限」
そこか。\\?\ は「解釈するな、そのまま渡せ」という指示なので、そもそも相対パスの解決と相性が悪い。厳密にはHerdr内部の受け渡しまでは追えていないので断定は避けますが、絶対パスで書き直したら一発で生存しました。
command = ["node", "C:\\work\\herdr-banken\\banken.mjs"]
つまりドキュメントの「相対パスは解決される」が、Windowsでは成り立っていないことになります。
もう1つ、切り分けの途中で見つけたもの。プラグインが所有するペインは herdr api snapshot にも herdr pane list にも現れず、herdr pane read <id> は pane_not_found を返します。生存確認は herdr plugin pane focus <pane_id> という別経路が要ります。プラグイン側で何かを監視する設計にしていたら成立しません。
この「動くまでに壁を数える」タイプの記事は前にも書いています。
▼関連記事
Cloudflare OSを実際に動かしてみた — 動くまでに5つの壁と、2円でできた業務アプリ
そのあと、標準機能を見つけた
ここまで書いた原稿を読んだ人から、こう言われました。
それ、Claude Code アプリでも似たようなことができるのでは?
調べました。できました。
Claude Code には claude agents で開く Agent View があり、セッションを状態別にグルーピングして一覧表示します。
Needs input
✻ power-up design double jump or wall climb? 1m
Working
✽ collision detection Adding swept-AABB checks 2m
Completed
✻ title screen result: menu, options done 9m
黄色の ✻ が「入力待ち(質問・許可決定)」。ターミナルのタブタイトルにも 2 awaiting input · claude agents と出ます。僕が作った状態ボードそのものです。
しかも通知まであります。
While agent view is open, Claude Code also sends a notification through your configured terminal notification channel when a local background session starts needing your input, finishes, or fails.
Claude Code ドキュメント「Agent view」
さらに Notification フックには agent_needs_input と agent_completed というマッチャーがあります。僕が「Herdrにしかない」と思っていた blocked と done の区別が、そのまま用意されていました。
要するに、僕が2つスクリプトを書いて実現したことは、コマンドを1つ打てば済む話でした。
じゃあ、無駄だったのか
正直に差分を見積もると、残るのは2つだけです。
- 対象がClaude Codeのセッションに限られる — 同じ画面で Codex や別のCLIエージェントを走らせていたら、Agent View には映りません。Herdr はターミナル層にいるので、認識できるエージェントなら種類を問わず拾えます
- 通知の条件が「Agent View を開いている間」 — 閉じていると鳴りません
で、この2つは、わざわざ自作するほどの差ではありません。複数ツールを混ぜていないなら、標準機能で足ります。素直にそう書いておきます。
ただ、作ったこと自体が無駄だったかというと、そうでもなくて。
1つは、この記事の前半に書いたWindowsの罠3つ。これは作らないと出てきませんでした。Herdr でプラグインを書く人には、そのまま時間の節約になるはずです。
もう1つ。自分で blocked / done / working を扱ってみたので、Agent View の画面を見たときに何が起きているか即座に分かりました。「ああ、これは状態を構造化して持ってるんだな」と。作っていなければ、たぶん便利な一覧としか見ていません。
とはいえ、順番としては先に標準機能を調べるほうが圧倒的に正しい。そこは弁護しません。
道具を替えるかどうかで毎回同じことを考えているな、と思って、以前その判断基準を書き出したことがあります。今回はその手前、「そもそも要るのか」で間違えた形です。
▼関連記事
最新モデル、いつ現場で使う? リリースが自分の画面に降りてくる順番
持ち帰り
- 「不便だ」と思ったら、作る前に標準機能を探す。自分が困っていることは、たいてい他の人も困っていて、もう解決されている
- 通知は到達を確認する。終了コードは何も保証しない。
busyで黙って捨てられる実装は普通にある - 鳴らすのは状態ではなく遷移。そして起動直後の初回観測は鳴らさない
- 「検証コマンドが ok を返した」は、設定が正しい証拠にならない。存在しないキーを1つ混ぜて、それを検出できるか先に試す
- Windowsで
\\?\を見たら疑う。ツールが内部でパスを正規化していると、相対パスの解決が黙って壊れる
エージェントを並列で走らせる場面は、これから増えます。そのとき効いてくるのは実行の速さより、「どれが人間の入力を待っているか」を機械が持っているかどうかです。Herdr では agent_status、Claude Code では Agent View。呼び名は違っても、同じものを各所が用意し始めている、ということだと思います。
まず、お使いのツールにそれが無いか見てみてください。たぶん、あります。
※ 本文の検証は Herdr 0.8.2 / Windows 11 での記録です(冒頭に書いたとおり、2026年9月8日に 0.9.0 が公開されています)。Windows対応はベータ段階のため、挙動はバージョンによって変わります。Claude Code の機能も同様に変わるため、最新は公式ドキュメントをご確認ください。
そもそもエージェントを何体も同時に走らせるという話は、以前に別の角度でも書いています。
▼関連記事
AIが9体、同時に走るコマンドがあった — Claude Codeで実務を回して分かったこと
お知らせ
弊社では、AIエージェントを実務に組み込むための研修と、開発の伴走を行っています。
- DX-NoCodeBootCamp® — 「使う側」から「つくる側」に回るための実践研修です
- 福岡・大名カンファレンス登壇のお知らせ — 「生成AI時代、地域の中小企業はどう変わる?」に代表の宮崎が登壇します
- 読める化のはじめ方 — 中小企業のための無料相談 — 何から手を付けるか、から一緒に整理します


