常駐Bot監視の落とし穴
AIエージェントの監視で、毎回失敗しているのにハートビートは緑だった。beatを打つ位置ひとつで監視は嘘をつく。その穴を再現コードで示す。
常駐の Bot を監視するときの4つの不変条件は、「AIエージェントをVPSで24時間動かす」で紹介しました。おさらいすると、次の4つです。
- 監視は、監視対象と別のホストに置く。
- 異常がなくても、毎日 OK を送る。
- 判定と通知を分ける。
- 「意図して止めている」状態(inert)を、正常とも故障とも別に扱う。
PR:以下の段落にアフィリエイトリンクを含みます。
監視用の別ホストは、小さなプランで足ります。国内なら ConoHa VPS
や XServer VPS
の最小構成を、本体とは別の契約で用意すると「一緒に落ちる」を避けられます。
この記事では、4つを守っていても残っていた穴を書きます。再現コードは、編集部の検証環境(Python 3.13.5、2026-10-16)で動かしたものです。
AIエージェントの監視とbeatの位置
ハートビート(「生きている」の合図)を、処理の開始時に打つか、完了後に打つか。どちらにも穴があり、向きが逆です。
def job(i): raise RuntimeError("always fails") # 毎回失敗する処理
beats_start, beats_end = [], []
for i in range(3):
beats_start.append(i) # A: 開始時に beat
try: job(i); beats_end.append(i) # B: 完了後に beat
except Exception: pass
A(開始時beat) 回数: 3 → healthy に見える
B(完了後beat) 回数: 0 → 失敗と停止の区別がつかない
- 開始時に打つと、毎回失敗していても健全に見えます。
- 完了後に打つと、失敗していても、意図して止めていても、同じ「beat なし」になります。
片方を塞ぐと、もう片方の穴が開きます。だから、ok でも error でもない3つ目のラベルが必要になります。
def label(last_ok_age, paused):
if paused: return "inert"
return "ok" if last_ok_age < 600 else "error"
# label(30,False)→ok / label(9999,False)→error / label(9999,True)→inert
初期値が ok のヘルスキーは、恒久的な嘘になる
状態を表すキーを、最初から "ok" で作っていました。その後、キーを更新していた処理を消しました。キーは "ok" のまま残り、誰も更新しないのに緑を表示し続けました。再現コードでも、更新元を消したあとに読むと health key: ok が返ります。
ヘルスキーの初期値は "unknown" にします。値と一緒に「最後に更新した時刻」を持たせて、古くなったら error として扱います。
初回の観測値を、黙って基準値にしない
新しく監視のレーンを足したとき、最初に取った値を、そのまま基準値(baseline)にしました。ところが、その時点ですでに、検知したかった異常が起きていました。異常な値が基準になってしまい、通知は一度も出ませんでした。初回の値は、人が見て「正常」と確かめてから基準にします。
「正常な沈黙」と「故障」を分ける
出力が無いことには、2つの意味があります。処理する対象が無かっただけなのか、出力を作る処理が止まったのか、です。両方を「0件」で表すと区別できません。対象が無かったときも、「0件を確認した」という記録を残します。
失敗が人に届くまでを設計に含める
ログインのセッションが切れて、その日の処理が失敗していたのに、誰も気づかなかったことがあります。失敗は、ログには残っていました。自動化の設計は、失敗が人の目に届くところまでを範囲に含めます。
成功に見える静かな失敗の型は「AIの「完了しました」を信じない仕組み」にまとめました。
外部への副作用は3値で扱う
Bot が外部の API に書き込むなら、結果は「成功」と「失敗」の2値では足りません。
- タイムアウトは「結果不明」:相手の側では処理が通っている可能性があります。サーバーが失敗とはっきり答えたもの以外は、失敗として終わりにしません。
- 空配列は「記録なし」とは限らない:読み取り側の API が、障害のときに空の配列を返すことがありました。それを「まだ送っていない」と読むと、二重に送ります。
- 冪等キーは、発行したときの文脈から作る:壁時計の時刻から作ると、日をまたいで再実行したときに別のキーになり、二重に送ります。キーを使い回すと、次の別のイベントを「処理済み」として飛ばします。
型は正しいのに中身が違う、という同じ形の問題は、LLM の出力でも起きます。その確かめ方は「LLMは「選ぶ・抜く」だけにする」にまとめました。
まとめ
- beat の位置で、監視の穴の向きが変わる。ok/error のほかに inert を持つ。
- ヘルスキーの初期値は unknown にし、更新時刻と一緒に読む。
- 初回の観測値は、人が確かめてから基準値にする。
- 外部への書き込みは、成功・失敗・結果不明の3値で扱う。