第3回:AIエージェントの暴走を防ぐ — heartbeat設計ミスから学んだ制御の要点

シリーズ「AIエージェントは本当に業務を動かせるのか」 Part 3

2025年後半から注目を集めているAIエージェント基盤「OpenClaw」。本シリーズでは、OpenClawが業務で使い物になるのか、セキュリティ面で気をつけるべき点は何か、クライアント案件での試行錯誤を記録しています。これからOpenClawや類似基盤の導入を検討される方の判断材料になれば幸いです。

本記事(Part 3)では、深夜に誰も見ていない時間帯にAIエージェントが勝手に動き出して2時間36分で1.29ドルを消費したインシデントを取り上げます。原因は「空のファイル」という、人間には何もない指示でした。AIにとっての「空」が「自分で考えて動け」に読み替えられてしまう現象、フィードバックループの怖さ、そして24時間無人で動かす場合に設計段階で必ず潰しておきたい点を整理します。

前回の記事はPart 2: 3モデルのコード生成力を検証するをご参照ください。


1. インシデントの概要

指標
日時2026/3/29 02:47 - 05:24(深夜)
セッションheartbeat(30分間隔の死活確認用)
メッセージ数23(user 2, assistant 21)
トークン消費4.3M prompt, 2.4M total
平均トークン/msg102.8K
ツール呼び出し19回(exec 7, read 6, session_status 2, edit 2, memory_search 1, write 1)
モデルGemini 3 Flash
コスト$1.29(期待値 $0.03/日 の約43倍)

深夜帯に、エージェントが勝手にファイルを読み、コマンドを実行し、ファイルを書き換えていました。


2. 暴走のメカニズム — なぜ「空」が問題を招くのか

根本原因

HEARTBEAT.mdが空(テンプレートコメントのみ)だったため、エージェントが「自分で仕事を探す」行動に出ました。

3つの設計ミスの連鎖

設計ミス1: HEARTBEAT.mdが空

<!-- HEARTBEAT.md -->
<!-- テンプレートコメントのみ、指示なし -->

「空」は人間にとっては「何もするな」を意味しますが、AIエージェントにとっては「明示的な指示がない = 自分で判断して動け」と解釈される可能性があります。

設計ミス2: AGENTS.mdに「proactiveに動け」と記載

# AGENTS.md(当時の設定)
heartbeat:
  - HEARTBEAT_OKだけ返すな
  - proactiveにタスクを見つけて実行せよ
  - email/calendar/weatherをチェックせよ

この指示とHEARTBEAT.mdの「空」が組み合わさり、エージェントは以下の行動を開始しました。

  1. HEARTBEAT.md を読む → 空
  2. 「proactiveにやれ」に従い自分で仕事を探す
  3. SOUL.md, USER.md, MEMORY.md, daily memoryを全読み(read x6)
  4. weatherチェック、git status等を実行(exec x7)
  5. メモリメンテナンス(edit x2, write x1)

設計ミス3: SOUL.mdの自動session_statusルール

# SOUL.md(当時の設定)
- 毎レスポンス後に session_status を実行
- 200kトークンを超えたらアーカイブを実行

これがフィードバックループを形成しました。

session_status実行 → コンテキスト膨張 → アーカイブ処理 → さらに膨張 → session_status実行 → ...

結果として、21回のassistant応答で2.4Mトークン、$1.29を消費しました。


3. heartbeatがmainセッションで実行される構造

OpenClawのセッション構造を理解することが、この問題の背景を把握するうえで重要です。

agent:main
├── :main              ← 常駐セッション(Web UI・直接対話)
├── :cron:xxxxx        ← isolated(使い捨て)
├── :telegram:xxxxx    ← 独立セッション
└── heartbeat          ← mainセッション内で実行

heartbeatは独立セッションではなく、mainセッション内で実行されます。 つまり毎回mainの全コンテキスト(当時138K+)を読み込んでからheartbeatタスクを処理します。

heartbeatが30分ごとに発火するたびに、「HEARTBEAT_OKと返す」だけの処理に138Kトークン分のAPI費用がかかる計算になります。

チャネル費用
heartbeat(停止前)$2.17(数時間で)
heartbeat(停止後)$0.04(以降変化なし)

4. 対策の実施

即時対策

1. HEARTBEAT.md: 空 → 「HEARTBEAT_OK」を明記

HEARTBEAT_OK

明示的に「HEARTBEAT_OKと返すこと」と記載しました。エージェントは「指示を読んだ → HEARTBEAT_OK → 返す」で処理を終えます。

2. AGENTS.md: proactive指示を全削除

# 修正後
heartbeat:
  - HEARTBEAT.mdに書いてあることだけやれ
  - それ以外のことは NEVER

3. SOUL.md: session_status自動実行ルールを削除

フィードバックループを形成していた「毎レスポンス後にsession_status」のルールを完全に削除しました。

根本対策: heartbeatエージェントの分離

即時対策後もheartbeat自体のコスト構造は残ります。mainセッション内で動く限り、全コンテキストの再送は避けられません。

そこで、heartbeatを独立エージェントとして分離し、ローカルLLM(Gemma3 4B)で実行する構成に変更しました。

{
  "id": "heartbeat",
  "model": "ollama/gemma3:4b",
  "heartbeat": {
    "every": "30m",
    "lightContext": true,
    "isolatedSession": true
  }
}
  • lightContext: true: HEARTBEAT.mdだけを読み込み、SOUL.md等をスキップ
  • isolatedSession: true: 毎回新規セッション(コンテキストの蓄積ゼロ)
  • Gemma3 4B: ローカルLLMのため APIコスト$0

放置テスト

heartbeat停止 + cron全停止の状態で30分以上放置した結果、次のようになりました。

放置前: $8.97
放置後: $8.97(変化なし)

バックグラウンドのAPI消費がゼロであることを確認できました。


5. OpenClaw特有の制御上の課題

--accept-risk で始まるリスク

OpenClawは --accept-risk フラグを要求する通り、ホスト上でフルアクセス権を持ちます。エージェントは自由にファイルを読み書きし、コマンドを実行できます。この前提を理解しないまま24時間稼働させると、今回のような事象が発生しやすくなります。

Claude Codeとの比較

観点Claude CodeOpenClaw
セッション数常に1つ複数が同時存在
コンテキスト管理自動compaction手動またはルール設定が必要
記憶の引き継ぎMEMORY.mdMEMORY.md + ベクトルDB
定期実行なしCron + HEARTBEAT(二重に注意)
暴走リスク低(人間が常に対面)相対的に高(24時間無人稼働)

暴走を検知する仕組みがない

現時点のOpenClawには、「エージェントが異常な量のトークンを消費している」ことを検知して停止する機能は用意されていません。APIプロバイダー側の月額上限設定で間接的に防ぐ必要があります。


6. この記事から整理できる教訓

  1. 「空」は曖昧な指示です。AIへの指示で「空欄」は「何もするな」を意味しません。明示的に「何もするな」「HEARTBEAT_OKとだけ返せ」と書く必要があります。
  2. フィードバックループを作らない。session_status → アーカイブ → さらにトークン増 → session_status、といった連鎖は設計段階では気づきにくく、実運用で初めて表面化します。
  3. 深夜帯の安全設計を優先する。今回のインシデントは02:47-05:24に発生しました。人間が監視できない時間帯こそ、事前の設計が重要です。
  4. heartbeatは死活確認のみにする。定期的な業務処理はcron jobに分離し、heartbeatには死活確認以外の仕事をさせないのが安全です。
  5. 「cost-zero」を疑う。OpenClawが「isolated cronでコストゼロ」と言っても、Gemini Flashで動いている時点でコストはゼロではありません。本当に$0にするにはローカルLLMが必要です。

7. 暴走防止チェックリスト

  • HEARTBEAT.mdに明示的な指示を記載する(空にしない)
  • AGENTS.mdに「proactive」「自分で仕事を探せ」系の指示がないか確認する
  • SOUL.mdに自動ループを形成する可能性のあるルールがないか確認する
  • heartbeatエージェントをローカルLLM(Gemma3等)に分離する
  • lightContext: true + isolatedSession: true を設定する
  • APIプロバイダーで月額上限を設定する
  • 放置テスト(30分以上)でバックグラウンドコスト増加がないことを確認する

次回 Part 4 では、1つのエージェントに全てを任せるとなぜ問題が起きるのか、5エージェント構成の設計と実装を紹介する予定です。

この記事をシェア

関連記事