【設定・環境構築】herdr を実際に使ってみる — 「エージェントの状態が見える」は何ができることなのか

この記事でわかること
- herdr の中身は agent_status という1つの値で、pane → tab → workspace に集約されること
- 「人の入力を待っている(blocked)」を herdr が自分で判定するので、画面の静止から推測しなくてよくなること
- agent explain で「どのルールが何にマッチしたか」まで読めること
- agent wait / agent prompt --wait で「終わるか、止まったら返す」を1行で書く方法
- ペインが小さいと入力待ちを idle と誤判定する、という実地で踏んだ条件
herdr 0.8.0 / macOS / Claude Code を日常的に使っていること。前の2記事(どの層で戦っているか・どう積むか)を読んでいると繋がりますが、本記事単体でも読めます。
前の記事で Ghostty + herdr という構成に決めたんですが、正直に言うと、そのあとしばらく tmux と同じ使い方しかしていませんでした。 ペインを割って、Claude Code を動かして、たまに pane read で覗く。それだけなら tmux でよかったわけで。
herdr を入れる理由は「エージェントの状態が見える」ことのはずなんですが、その「見える」が具体的に何をしてくれるのかを分かっていなかったんですよね。なので今回は実際に手を動かして、そこだけを掘りました。
結論を先に言うと、herdr の中身は agent_status というたった1つの値でした。そしてこれが、tmux で自分が書いていた「画面が3秒止まったら入力待ちとみなす」というヒューリスティックを、丸ごと不要にしてくれます。
この記事でやること
agent_statusの状態遷移を、実際に Claude Code を動かしながら観測する- herdr が「なぜその状態だと判断したか」を読む(
agent explain) - 状態で待つ(
agent wait/agent prompt --wait)を tmux のやり方と比べる - 実際に踏んだ、検知が当てにならなくなる条件
手を動かす前の状態
環境は herdr 0.8.0、brew services で常駐させています。この記事の操作はすべて CLI(herdr <subcommand>)から叩いていて、画面をキーボードで操作した部分はありません。
まず気づいたのが、サーバーが動いていればワークスペースもペインも CLI だけで作れることでした。TUI を開く必要がない。
herdr workspace create --cwd ~/path/to/repo --label "herdr-demo" --no-focus返ってくる JSON がこれで、いきなり本題が出てきます。
{
"root_pane": { "pane_id": "w2:p1", "agent_status": "unknown", ... },
"tab": { "tab_id": "w2:t1", "agent_status": "unknown", ... },
"workspace": { "workspace_id": "w2", "agent_status": "unknown", ... }
}agent_status がペイン・タブ・ワークスペースの3階層すべてに付いている。この時点で「あ、これが本体か」と分かりました。
状態を実際に動かしてみる
ペインにエージェントを起動します。herdr agent start は起動するコマンドを知っていて、--kind で種類を指定します。
herdr agent start demo-claude --kind claude --pane w2:p1--kind に渡せる値が21種類ありました。
pi, claude, codex, gemini, cursor, devin, agy, cline, omp, mastracode,
opencode, copilot, kimi, kiro, droid, amp, grok, hermes, kilo, qodercli, maki起動直後に状態を見ると、こうなっていました。
demo-claude w2:p1 blocked画面を覗くと、Claude Code の workspace trust ダイアログで止まっていました。こちらが何も教えていないのに、herdr が「人の入力を待っている」と判定している。 ここが tmux との決定的な差だと思っていて。tmux でこれをやろうとすると、画面が変化しなくなったことから推測するしかないんですよね。
Enter を送って、状態が変わるのを待ちます。
herdr agent send-keys demo-claude Enter
herdr agent wait demo-claude --until idle --timeout 60000wait が返るまで 558ms でした。自分が書いていた tmux 用のヘルパーは「画面が3秒静止したら入力待ちとみなす」という判定なので、そもそも 3秒待たないと何も分からない。しかも「入力待ち」なのか「まだ考えている」のかは区別できません。
そのあとプロンプトを投げて、状態を1秒おきに追ってみました。
[01:41:53] idle
[01:41:56] working
[01:41:58] working
…
[01:42:08] doneworking の次が idle ではなく done なのが面白くて。ターンが終わって出力が出た直後だけ done になり、そのあと idle に落ちます。「まだ読んでいない結果がある」状態を分けて持っているんですね。
3つのエージェントを別々の状態にして並べると、サイドバーはこうなります。

上から working(琥珀の●)、idle(緑の○)、blocked(赤の●)です。赤を探せばいい、というのがこの機能の実用上の意味だと思っていて。5つ走らせていて1つが許可待ちで止まっている、というのが画面を読まずに分かる。
全体だとこう見えます。左が状態一覧、右が3つのペイン(作業中の Claude Code、終わった Claude Code、信頼ダイアログで止まっている Claude Code)。

「なぜその状態なのか」が読める
ここが一番良かったところです。herdr agent explain を叩くと、判定の根拠が出ます。
agent: claude
state: idle
manifest: remote:~/.local/state/herdr/agent-detection/remote/claude.toml 2026.08.21.1
rule: live_prompt_box (region=prompt_box_body priority=950)
evidence: "❯\n"どのマニフェストの、どのルールが、何にマッチしたかまで返る。状態がおかしいときに「herdr がバグってる」で終わらせずに済みます。
マニフェストの実体は ~/.local/state/herdr/agent-detection/remote/ にあって、エージェント種別ごとの TOML が19個入っていました。claude.toml を覗くと、優先度付きのルールが16本。
[[rules]]
id = "osc_title_working"
state = "working"
priority = 1100
region = "osc_title"
regex = ['^[\x{2800}-\x{28FF}\x{25D0}-\x{25D3}] ']最優先(priority 1100)が OSC タイトルに出るスピナー文字を見るルールでした。Claude Code は端末のタイトルに点字ブロック(⠋⠙⠹…)のスピナーを書いているので、画面本文を読まずにタイトルだけで「動いている」と判定できる。うまいなと思いました。
region の種類も面白くて、こう分かれています。
| region | 何を見るか |
|---|---|
osc_title / osc_progress |
端末のタイトルやプログレス(本文を読まない) |
prompt_box_body |
入力欄の中身 |
after_last_horizontal_rule |
最後の区切り線から下(ダイアログが出る場所) |
bottom_non_empty_lines(N) |
末尾 N 行の空でない行 |
last_non_empty_above_prompt_box |
入力欄の直上 |
whole_recent |
直近の画面全体 |
blocked を判定するルールは5本あって、許可プロンプト・確認フォーム・動的ワークフローの確認などを個別に見ています。
[[rules]]
id = "bash_permission_prompt"
state = "blocked"
priority = 850
region = "whole_recent"
contains = ["do you want to proceed?"]
any = [
{ contains = ["bash command"] },
{ contains = ["tab to amend"] },
{ contains = ["ctrl+e to explain"] },
]つまり herdr は汎用的に賢いのではなく、エージェントごとの画面の癖を知識として持っているわけです。claude.toml だけ更新日が新しかった(他が8月14日で claude が8月21日)のも納得で、Claude Code の UI が動くたびに追従が要るということでもあります。ここは裏を返すとメンテに追随できなくなった瞬間に壊れるので、後述の落とし穴につながります。
状態で待つ
実用上いちばん効くのがこれです。プロンプトを投げて、終わるまで待つ、が1行になります。
herdr agent prompt demo-claude "app.py に docstring を1行足して" --wait --until idle --until blocked--until を複数書けるので、「終わったか、または許可待ちで止まったら返ってこい」が表現できます。実測で 13.4 秒後、idle で返ってきました。
これが tmux との差で、自分の tmux ヘルパーは結局こういう作りでした。
# 前面プロセスがシェルに戻ったか? → REPL や対話型エージェントでは戻らない
# 画面が IDLE_SEC 秒変化しないか? → 「入力待ち」と「熟考中」を区別できない推測をやめられるのが agent wait の価値だと思っていて。特に「許可待ちで止まった」を検知できるのが大きくて、投げっぱなしにして放置しても、止まったところで気づけます。
返ってきた JSON には Claude Code 側のセッション ID も入っていました。
"agent_session": { "agent": "claude", "kind": "id", "value": "da50f3b9-f03b-46a0-b6bb-bcafb59b5e9c" }ペインと Claude Code のセッションが紐づいているので、claude --resume 側の情報と突き合わせられます。
踏んだ落とし穴
ここからが実際に触らないと分からなかった部分です。
画面が小さいと検知が落ちる
一番効いたのがこれでした。クライアントを1つも繋いでいない状態だと、herdr のセッションは 54×23 という小さい既定サイズで動きます。 この状態でモデル選択ピッカーを開くと、herdr は idle を返しました。
state: idle
rule: osc_title_idle (region=osc_title priority=250)実際にはピッカーが開いていて入力待ちなのに、です。原因は単純で、ピッカーがペインより縦に長く、判定に必要な Esc to cancel などの行が画面外に押し出されていたから。優先度の高いルールがどれもマッチせず、優先度250の「タイトルが ✳ Claude Code だから idle」に落ちていました。
クライアントを繋いでペインが 48行になると、同じ画面でちゃんと別のルールを拾いました。
検知の根拠は「いま画面に出ている文字列」なので、ペインの大きさで結果が変わる。 agent wait で自動化を組むなら、ヘッドレスで小さいまま回さないほうがいいです。ここは正直、事前には想像していませんでした。
モデルピッカーは意図的に検知対象外
上のを大きい画面で試したら、今度はこうなりました。
state: unknown
rule: model_picker_menu (region=whole_recent priority=900)
skipped_update_reason: matched_rule:model_picker_menublocked ではなく unknown にして状態更新をスキップしています。モデルピッカーは「エージェントが人を待っている」のではなく「人が自分で開いた UI」なので、blocked 扱いしないという設計判断ですね。skipped_update_reason まで返してくれるので、これも explain で追えます。
agent wait は既定だと unknown を拾わない
--until を省略すると idle / done / blocked のいずれかで返ります。上のように unknown に落ちるケースがあるので、unknown で止まると wait が返ってこないことになります。--timeout は付けておいたほうが安全です。
worktree との組み合わせ
もう一つ実用的だったのがこれ。
herdr worktree create --cwd ~/path/to/repo --branch feature/greet-i18n1コマンドで git worktree を切って、そこを開いた新しいワークスペースまで作ります。
$ git worktree list
/…/herdr-demo f93248e [master]
/Users/…/.herdr/worktrees/herdr-demo/feature-greet-i18n f93248e [feature/greet-i18n]置き場所が ~/.herdr/worktrees/<repo>/<branch> で、リポジトリの外なのがポイントです。ちょうど前の記事で書いた Claude Code の Remote Control は .claude/worktrees/ とリポジトリ内に切るので、そこは逆になっています。.gitignore を気にしなくていい代わりに、git worktree list を見ないと存在に気づきにくい。
「1タスク1worktree」に別々のツールが独立に辿り着いているのは、それだけ並行作業で同じディレクトリを共有するのが事故りやすいということなんだろうなと。
で、結局どう使うのか
実際に触ってみて、自分の中ではこう整理できました。
効くのは、エージェントを2つ以上並行で回すとき。 1つしか動かしていないなら、その画面を見ていればいいので herdr の状態検知は要りません。3つ4つになった瞬間に「どれが止まっているか」を人間が巡回して確かめるのが辛くなって、そこで赤い●が効いてきます。
自動化を組むなら agent wait に寄せる。 「投げて、止まったら教えて」が1行で書けるので、pane wait-output で文字列を待つ書き方(前の記事で自己マッチの罠を書いたやつ)より事故りにくいです。文字列を待つのは、エージェント以外のコマンド(ビルドやテスト)に取っておくのがよさそう。
逆に、まだ使わなくていい場合もはっきりしました。SSH 先で作業するなら herdr は入っていないので tmux のままですし、エージェントが1つなら素の Ghostty で足ります。検知ルールがエージェントの UI 更新に追随する前提の仕組みなので、マイナーなエージェントを使っているなら期待しすぎないほうがいいとも思いました(19個のマニフェストのうち、更新が新しいのは claude.toml だけでした)。
まとめ
- herdr の中身は
agent_statusという1つの値。unknown/idle/working/done/blockedの5つで、pane → tab → workspace に集約される - 「人の入力を待っている(
blocked)」を自分で判定してくれるのが tmux との決定的な差。画面の静止から推測する必要がなくなる - 判定はエージェント種別ごとの TOML マニフェスト(優先度付きルール)で、
agent explainで根拠まで読める。最優先は OSC タイトルのスピナー - ただし根拠は画面上の文字列なので、ペインが小さいと取りこぼす。ヘッドレスの既定 54×23 で回すと入力待ちを
idleと誤判定した agent wait/agent prompt --waitで「終わるか、止まったら返す」が1行。--timeoutは付けておくherdr worktree createは worktree とワークスペースを同時に作る。置き場はリポジトリ外の~/.herdr/worktrees/
入れただけで放置していたのがもったいなかったな、というのが正直な感想です。特に explain があるおかげで、ブラックボックスに見えていた部分がほぼ全部読めるようになりました。


