AIエージェントのオブザーバビリティ:エージェントが何をしたかを把握し、何を記録すべきか
AIエージェントのオブザーバビリティとは、エージェントが何をしたか、なぜそうしたか、何によって結果が正しいと裏付けられるかを、成果物をもとに再構成できる能力です。ランタイム(行動ログ、トレース、検査の出力)とプロセス(タスクの合意、受け入れ基準、根拠つきの報告)の2層から成ります。どちらかが欠けると、エージェントの「完了」という言葉は何の意味も持ちません。
AIエージェントのオブザーバビリティとは
AIエージェントのオブザーバビリティとは、記憶や、エージェントの言葉ではなく成果物をもとに、3つの問いに答えられる能力のことです。エージェントは何をしたのか。なぜそうしたのか、そしてそれは何によって裏付けられるのか。結果は正しいと言えるのか。20分の作業の後、エージェントが「完了しました。ただし2つのテストが落ちています。タイミングの問題かもしれません」と言い、「どのパスに影響したのか」と聞くと「コードを見せてください」と答えるなら、オブザーバビリティはありません。エージェントが愚かなのではありません。示せる材料がないのです。
問題はシステム全体にかかわります。同じエージェントが1週間に10回「完了しました」と誠実に言うかもしれませんが、その「完了」は毎回違う意味を持ちます。ある回ではすべてのテストが実行され、ある回では1つだけ、ある回では何もしていません。プロジェクトが長く続くほど、次のセッションを始めるコストは高くなります。毎回、発掘作業から始まるからです。
2つの層:ランタイムとプロセス
オブザーバビリティはログと混同されがちですが、ログは半分にすぎません。
| 層 | 答えること | 成果物 |
|---|---|---|
| ランタイム | 何が起きたか | エージェントの行動ログ、トレース、テストとビルドの出力、コミット |
| プロセス | 何を目指したのか、なぜそうしたのか | タスクの合意(範囲、受け入れ基準)、評価のルーブリック、エージェントの報告 |
ランタイムだけでは、何が失敗したかは分かっても、何を目指していたかが分かりません。プロセスだけでは、何を目指していたかは分かっても、なぜ失敗したかが見えません。両方が必要です。
エージェントに任せてもうまくいかない理由
「もっとよくログを取って」とエージェントに言っても、うまくいきません。理由は3つあります。
- 何を知らないかを知らない。 エージェントは、重要性に気づいていない信号を記録しません。
- 形式がばらつく。 あるセッションでは詳細に、別のセッションでは1行で書きます。セッションを比較したり集計したりできません。
- プロセスはログでは閉じない。「なぜそうしたのか」の答えは、コマンドの出力ではなく、作業の前に作られた文書の中にあります。
そのため、オブザーバビリティはハーネスに組み込みます。ハーネスとは、エージェントを取り巻く環境のことで、テンプレート、フック、スクリプト、必須の報告形式などを含みます。
何を記録するか:最小限のセット
良いログは、起きたことを逐一書き留めた記録ではありません。作業の流れを再現し、統計を取れるだけの事実の集まりです。
- 識別子: セッションの ID、タスクまたは機能の ID、エージェントのモデル。
- 副作用のある操作: どのコマンドを実行し、どのファイルを変更したか。ファイルの読み取りは通常、記録しなくてよいです。
- 検証の結果: どの検証コマンドか、終了コード、いくつのテストが成功・失敗したか。
- リトライ: 1つのステップに何回試行したか、それぞれ何が違ったか。
- 消費: ツールが報告するなら、トークンと時間。
- 結果: タスクが受け入れられたか、却下されたか、放棄されたか、その理由。
記録してはいけないものもあります。秘密情報、アクセストークン、コマンドの出力に含まれる個人データです。ログは共有されたり、再びモデルのコンテキストに入ったりする可能性があります。
Claude Code のフックによる行動ログ
Claude Code では、ログをフックで取るのが便利です。フックはモデルではなくツール自体が呼び出すコマンドなので、必ず実行されます。フックはイベントの情報を表す JSON を受け取ります。セッション ID、ツール名、そのパラメータなどです。Bash、Edit、Write の呼び出しのたびに、ログに1行を追記する設定の例は次のとおりです。.claude/settings.json に書きます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash|Edit|Write",
"hooks": [
{ "type": "command", "command": ".claude/hooks/log-action.sh" }
]
}
]
}
}
#!/usr/bin/env bash
# .claude/hooks/log-action.sh — 1行につき1つの JSON レコード
mkdir -p .agent/logs
jq -c '{ts: (now | todate), session: .session_id, tool: .tool_name,
cmd: .tool_input.command, file: .tool_input.file_path}' \
>> .agent/logs/actions.jsonl || true
JSONL形式(1行に1つの JSON レコード)は、jq で簡単にフィルターでき、どんな分析システムにも取り込めるので便利です。また、Claude Code はセッションの完全な履歴を ~/.claude/projects/ にローカル保存しています。推論や出力の全体を見たいときはそこを開けます。ただし統計を取るには、自分でまとめた小さなログのほうが扱いやすいでしょう。
トレース:セッションごとのトレース、タスクごとのスパン
エージェントが多数になったり、スケジュールで動いたりすると、テキストのログでは足りず、集計が必要になります。ここで役立つのが、分散トレースの一般的なツールと、OpenTelemetry という標準です。
- トレース:エージェントの1セッション。
- スパン:セッション内の1つのタスク。
- 入れ子のスパン:ビルド、テスト、E2E などの検証ステップ。
スパンに付けるべき属性の最低限は、機能の ID とセッションの ID、モデル、検証の結果、トークン数、リトライの回数です。これらのデータは Jaeger、Grafana Tempo、Honeycomb などで読め、1つのセッションからは見えない問いに答えてくれます。どの種類のタスクがリトライを多く必要とするか。どこでエージェントが最も多くのトークンを使うか。ハーネスのどの変更の後に、受け入れられるタスクの割合が落ちたか。Claude Code には、OpenTelemetry を通じてメトリクスとイベントを出力する機能が組み込まれています。環境変数で有効にでき、詳細はツールのドキュメントにあります。
コミットをログとして使う
最も安価で信頼できるログは、すでに手元にある git です。必要なのは、読めるようにすることだけです。
- 1コミットに1タスク。 そうすれば
git logは完了した作業の一覧と一致し、タスクの取り消しはgit revert1回で済みます。 - メッセージには何を、どう検証したかを書く。「fix」ではなく、何を変更し、なぜ、どのコマンドで検証したかを書きます。
- 検証が緑になってからコミットする。 そうすれば履歴のどの時点も動く状態になり、
git bisectで原因を見つけられます。
fix(payments): 割引の端数処理を銭単位に変更
割引を float で計算していたため、999.99 の金額で 1 銭がずれていた。
計算を整数(銭単位)に変更。
検証: make test(142 件成功)、make e2e checkout — 問題なし。
範囲外: レポートの端数処理は BACKLOG.md に記録。
エージェントの報告:印象ではなく証拠を
エージェントの最後のメッセージも、形式が固定されていれば、オブザーバビリティの成果物になります。テンプレートを AGENTS.md か CLAUDE.md に書いてください。
## タスク終了時の報告
- 何をしたか:変更の一覧(ファイル — 内容)
- どう検証したか:正確なコマンドと結果
- 検証していないこと、その理由
- タスクの合意からの逸脱
- 人への未解決の質問
最も重要なルールは、すべての主張が証拠を参照することです。証拠は、ファイルと行、テストの出力、ログの記録などです。「問題なさそう」や「たぶん大丈夫」は報告とは認められません。「検証していないこと」の項目は特に価値があります。確信の範囲を正直に示してくれるからです。
作業の前の合意、作業の後のルーブリック
プロセスの層は作業の前から始まります。タスクに関する短い合意(スプリントコントラクト)には、範囲、受け入れ基準、「範囲外」の項目、人が決めるべき未解決の質問を書きます。「範囲外」の項目があるだけで、結果をめぐる争いの半分は防げます。
作業の後、結果は印象ではなく、ルーブリックに照らして評価します。そうしないと、経験豊富なレビュアー2人が違う判定を下し、評価が抽選になってしまいます。
| 観点 | 問い | 証拠 |
|---|---|---|
| 正確さ | 受け入れ基準を満たしているか | テストの出力、E2E |
| 設計 | モジュールの境界を守っているか | ファイル:行 |
| テスト | 新しい振る舞いがテストされているか | 新規テストの一覧 |
| エラー処理 | 失敗やエッジケースを扱っているか | テストまたはファイル:行 |
| 観測性 | ログから何が起きているか分かるか | ログの抜粋 |
各観点は A/B/C/D で評価し、根拠のない評価は無効です。判定の簡単なルールは、D が1つ、または C が2つなら、直すべき点を添えて差し戻すことです。同じルーブリックを、別のエージェントによるレビューに使うこともできます。
セッションでエージェントが何をしたかを検証する手順
報告がない、または信頼できないなら、成果物を次の順に確認してください。
- 作業ツリーの状態:
git status。コミットされていない変更や、ゴミのようなファイルがないか。 - 履歴: セッションの初めからの
git log --stat。どのコミットとどのファイルか。 - 変更の規模:
git diff --stat main...HEAD、その後、重要なファイルの完全な差分。 - 行動ログ: どのコマンドが実行され、検証は何を返したか。
- 再検証: ビルドとテストを自分で実行する。エージェントの要約ではなく、コマンドの出力を信頼してください。
# 最後のセッションでエージェントが実行したコマンド
jq -r 'select(.tool=="Bash") | .cmd' .agent/logs/actions.jsonl | tail -n 30
# 最も多く編集されたファイル
jq -r '.file // empty' .agent/logs/actions.jsonl | sort | uniq -c | sort -rn | head
よくある間違い
- すべてをログに残す。 ギガバイトの記録は誰も読みません。副作用のある操作と検証の結果を書いてください。
- 自由形式の書式。 ログの形式をエージェントに任せると、集計はできなくなります。形式はハーネスが決めます。
- コマンドの出力なしに報告を信じる。「テストは緑です」と出力なしで書いてあるのは、意見にすぎません。
- 考えなしのリトライ。 失敗の原因が分からないまま、エージェントは無関係な箇所を直し始めます。まず、ログを根拠にした診断を求め、その後で修正させてください。
- 「後で片付ける」。 デバッグ用のファイルや未コミットの変更は、次のセッションを混乱させます。一時的なものと意図的なものを区別できなくなるからです。
まとめ
AIエージェントのオブザーバビリティは2つの層から成ります。ランタイムは、フックによる行動ログ、トレース、検証内容を書いたコミット。プロセスは、作業前の合意、ルーブリック、証拠を伴う報告です。この2つがそろえば、「完了」はどのセッションでも同じ意味になり、エージェントの作業の検証は数分で済みます。このテーマは、ハーネスエンジニアリングのコースで、決済バックエンドという通しのプロジェクトを使って実践的に扱っています。
よくある質問
AIエージェントのオブザーバビリティは、通常の監視とどう違いますか?
監視は、システムが今動いているかを確かめます。エージェントのオブザーバビリティは、さらにエージェントがなぜその判断をしたのか、結果が何によって裏付けられるのかに答える必要があります。そのためログに加えて、タスクの合意、ルーブリック、報告が必要になります。
Claude Code は、セッションの履歴をどこに保存していますか?
ローカルの ~/.claude/projects/ フォルダに、セッションごとの JSONL ファイルとして保存しています。統計を取るには、フックで作る小さな独自のログのほうが便利です。
エージェントの作業報告は信頼できますか?
すべての主張が検証可能な証拠に裏付けられている場合にのみ信頼できます。コマンドの出力、ファイルと行の参照、ログの記録などです。重要な検証は、自分で再実行するのが確実です。
1人の開発者に Jaeger や Grafana は必要ですか?
通常は不要です。JSONL のログ、コミットの規律、報告のテンプレートで足ります。トレースのシステムが元を取れるのは、エージェントが複数あったり、定期的に人の目なしで動いたりする場合です。
エージェントが報告をテンプレートどおりに書かないときは、どうすればよいですか?
テンプレートは短くし、指示ファイルの冒頭に置いてください。守られているかはフックで仕組み化するか、報告のない作業を差し戻す別のレビューエージェントで確認します。