エージェントに 20 分の作業を投げて、別の作業をしていた。完了通知は来ない。出力ファイルを覗くと 0 B のまま。止まっているのか、まだ走っているのか、どちらも分からない。
もう 1 つ。AI に自分の PC のアプリを操作させていて、様子を見ようとウィンドウをクリックした瞬間、0.1 秒で画面が奪い返された。奪い返したのは、当方が「前面を奪われたら戻す」ために置いた見張りでした。
この 2 つは別の見張りの話で、どちらも「気をつける」では立ちませんでした。見張り 25 本の記録と、実機 3 セッションで数えた記録から、どこに何を立てるかを書きます。
この記事の位置づけ(2026-09-26 追記)
- この記事で新しく示すこと: 見張り 25 本の記録と、前面を奪い返す条件を 3 つにした状態機械(入力の直後に奪い返した 9/9)。
- 当てはまる範囲: Windows・当方の PC・実機 3 セッションです。
- 言葉: 本文の「器」=点検や処理のための自作スクリプト、「番人」=見張りのスクリプトのことです。
数字の元データと再現の手順は、検証の記録(Lab)にまとめています。
止まったとき: 完了は届くが、沈黙は届かない
Claude Code のエージェントは完了すると通知が来ます。来ないときに「まだ走っている」と「止まった」を区別する合図は、こちらには何も届きません。出力ファイルのサイズも更新時刻も試しましたが、この運用では 10 本とも 0 B のままで、mtime は作業中も動かなかった。中身を覗いて進捗を測る案は捨てました。
追加実装なしで使えた最低コストの合図は経過時間です。エージェントを起動したのと同じターンで、背景に 1 本立てる。
sleep 1200; echo "[見張り] watchdog:<agent>-<日時>: 起動から 20 分たっても完了通知が無い"
猶予を過ぎると、この echo が「完了通知」として届き、沈黙が破れます。完了通知が先に来たら TaskStop で見張りを止める。自分でエージェントを止めたときも、見張りを止める。対にするのが要点で、対にしなかった 1 回が誤報になりました。
見張り 25 本で何が起きたか
09-09〜10 の 3 セッションの転写から、見張りの起動・TaskStop・鳴った通知を機械で拾いました。
| 面 | 何を数えたか | 数 |
|---|---|---|
| 沈黙 | 見張りの起動 | 26 本=試験 1(60 秒・鳴った)+エージェント向け 25 |
| 沈黙 | エージェント向け 25 本の結果 | 完了が先で猶予内に止めた 16/転写から直接確認できた鳴動 0(記憶には誤報 1 の記帳)/特定できない 9 |
| 沈黙 | 止めたときの猶予までの残り秒(16 本) | 最小 8・中央値 525・最大 994 |
止めた 16 本のうち 1 本は残り 8 秒。あと 8 秒遅ければ、完了しているのに「止まった」と鳴っていた。猶予 20 分は所要の最長実測 19:15 の 45 秒上で、運用上の暫定値です。
9 本は転写から結果を特定できませんでした。通知の形が一定でなく、見張りに ID が無かったので、どの通知がどの見張りか対応づけられない。⟹ 見張りに watchdog:<agent>-<日時> の ID を付ける、が改善点で、率(16/25)で語ることはしません。
動いているとき: 前面は奪われる
実機の操作は PostMessage の背景経路で送っていて、その経路自体は前面化しません。それでも作業中に相手のアプリが前面に出る遷移が起きた(引き金は未特定)。「奪わない」だけでは足りず、「奪われたら戻す」番人を器を呼んだ時点で自動で立てるようにしました(札で 1 本・寿命 90 秒)。戻す条件は 2 つ、「印(機械が操作中)が 5 秒以内」AND「相手が前面」。
見張りが、人のクリックを 0.1 秒で奪い返す
外部の精査で「機械操作 → 2 秒後に人がクリック → 番人が奪い返す」逆転を指摘されました。印は新しい、相手は前面、2 条件とも真。番人から見れば「戻す」場面です。
本当に起きるかを実機 3 セッション(各約 330 秒)で数えました。(a) 機械が 90 秒操作する(3 秒ごとに背景送出・印を打つ・当方は触らない)→ (b) 「人の入力」の代用として、印の直後に SendInput で Alt を 1 打してから相手を前面にする(3 回)→ (c) 印を 20 秒古くして同じ前面化(2 回・陰性対照)。観測器は前面を動かさず、0.1 秒ごとに前面プロセス・印の鮮度・最後の入力イベントを記録。番人は現行の 2 条件のまま。
| 面 | 何を数えたか | 数 |
|---|---|---|
| 前面 | 実機 3 セッション(現行の 2 条件の番人) | A 奪われた(印が新しい)9/B 2 秒以内に戻った 9/C 入力の直後なのに戻した 9(0.1〜0.2 秒)/D 戻らず 0/E 印が古いのに前面 3 → 戻した 0 |
| 前面 | 番人自身のログ「戻した回数」 | 3・3・3=観測器の B と一致 |
| 前面 | 機械操作中(270 秒)に相手が自分から前面を奪った回数 | 0 |
| 前面 | 3 条件の判定(純関数) | 自己テスト 6/6 |
入力イベントの直後の前面化を 9 回すべて奪い返した。印が古いときは 3 回とも戻さなかった(番人は正しく動いている=条件が足りないだけ)。機械操作中に相手が自発的に前面を奪うことは 270 秒で 0 回でした。
3 つ目の条件と、それでも見分けられないもの
戻す条件に「機械操作の後に新しい入力イベントが観測されていない」(GetLastInputInfo の時刻が印より前)を足すと、上の 9 回は全部「戻さない」になります。判定は純関数にして、逆転事故の型を含む 6 例で自己テストしました(3 つ目を外すとその例が「戻す」に転ぶ)。本物の番人への実装はまだで、この記事で言えるのは「2 条件では逆転する」を数で確かめたところまでです。
限界も 2 つ。GetLastInputInfo は SendInput 由来のイベントも数えるので、代用が成り立つ代わりに「人の入力」を厳密に見分ける手はこの合図には無い。印(ファイルの mtime)と dwTime(tick)は別の時計なので、同じ軸に揃えるか、曖昧なら「戻さない」側へ倒す。
もう 1 つ、器を回す側の「前面化 OK/NG」の印字は当てにならなかった。SetForegroundWindow の直後に番人が 0.1 秒で戻すので GetForegroundWindow が相手でなく「NG」と出る。数えたのは観測器の CSV で、番人自身のログと突き合わせて 3・3・3 が一致したことを根拠にしています。
見張りの設計表(5 行 × 2 列)
| 止まったとき | 動いているとき | |
|---|---|---|
| 合図 | 経過時間 | 印(鮮度 5 秒)+相手が前面+機械操作の後に入力イベントが無い |
| 立て方 | 起動と同じターン・1 体 1 本・ID 付きの背景 sleep N
|
器を呼んだ時点で自動(札で 1 本) |
| 止め方 | 完了が先なら TaskStop/自分で止めたら見張りも止める |
寿命 90 秒(器を呼ぶたびに延びる) |
| 誤報の型 | 止め忘れ | 人が自分で見にきた(印が古い/入力が後) |
| 検出しないもの | 本体・端末・PC ごとの停止/猶予の中の停止 | 引き金の特定/同じ PC の外 |
経過時間が示すのは「N 分以内に完了しなかった」だけで、生存確認ではありません。端末や PC ごと止まった場合は見張り自体が鳴らない。
AI の利用について
見張りの集計は AI(Claude Code)が自分の転写を機械で数えたもので、実機 3 セッションも AI が器を回して観測しています。観測器・純関数・図・この下書きも AI 製。相手のアプリ名・案件名・機種は伏せています。
関連
- 本家(起動記録 CSV・実機 3 セッションの集計と生 CSV・観測器・純関数): https://sumitsuke.jp/via/qiita/lab/two-watchdogs-silence-and-foreground/
- 文書に書いたルールは守られない——発火点を器に置く: https://sumitsuke.jp/via/qiita/lab/rule-placement-document-vs-gate/
- 「新着 0」と「未返信 0」は分母が違う(沈黙の見え方): https://sumitsuke.jp/via/qiita/lab/flow-vs-state-unreplied/
Sumitsuke は、AI や外注で作ったものの点検と修理、業務自動化、検証を受託しています。AI に任せた作業が「止まった」「奪った」を数で検収する設計は → https://sumitsuke.jp/works/verification/
この記事は Zenn にも同じ内容を掲載しています。

