ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-05-12上級

Antigravity エージェントの判断を追跡する — 意思決定ログと説明可能性の実装設計

AIエージェントが「なぜその判断をしたか」を記録・分析する意思決定ログの設計を、動作するPythonコード例とともに解説します。本番環境での予期しない動作を根本から追跡し、品質改善サイクルを実装します。

antigravity435agents90explainabilitylogging2production59design-patterns2

プレミアム記事

2014年に個人でアプリ開発を始めてから、私が最もよく口にするようになったフレーズがあります。「どうしてこうなった」です。

最初の数年間はコードが原因でした。自分で書いたロジックが予期しない状態に陥る。デバッガでステップを追えば必ず理由が見つかる。プログラムは正直で、実行した通りにしか動きません。バグは人間の思い込みから生まれますが、原因は必ずコードの中にあります。

AIエージェントを使うようになってから、その「正直さ」の性質が変わりました。エージェントが下した判断の理由は、コードのどこにも直接書かれていません。数百行の内部状態と、会話の文脈と、ツールの呼び出し履歴の中に、「なぜか」が溶け込んでいます。

去年、あるタスクを処理するエージェントが、想定外のファイルを上書きしました。バックアップがあったので被害はゼロでしたが、「なぜ上書きしたのか」を突き止めるのに2時間かかりました。最終的に判明した原因は、プロンプトの中の「最新のファイルで置き換えてください」という一文でした。私はそれを特定のファイルへの指示として書いたつもりでしたが、エージェントはより広いスコープに適用したのです。

この経験から、私はエージェントの「説明可能性」を設計することを開発プロセスに組み込むようになりました。

今回は、Antigravityのエージェントにおける意思決定ログの実装と、それを品質改善サイクルに活用するための設計パターンをお伝えします。2014年の個人開発スタートから累計5,000万ダウンロードを超えたアプリ事業を一人で運用してきた経験から言えば、「なぜ動いているか」を理解できないシステムは、必ずどこかで想定外の動作をします。規模が大きくなるほど、その代償も大きくなります。

可観測性と説明可能性の違い — 何を設計するのか

まず、似ているようで異なる2つの概念を整理しておきたいと思います。

**可観測性(Observability)**は、システムが今どのような状態にあるかを外部から観察する能力です。レスポンスタイム、エラーレート、リソース使用量 — これらはSREやインフラの領域でよく語られます。Langfuse、OpenTelemetryのようなツールがここをカバーします。

**説明可能性(Explainability)**は、「なぜその出力が生まれたか」を人間が理解できる形で記述する能力です。エージェントが特定のツールを選んだ理由、中間的な推論のステップ、判断に影響した文脈の要素 — これらは標準的な監視ツールだけでは捉えられません。

具体的に言うと、可観測性パイプラインが教えてくれるのは「エージェントがwrite_fileを呼んだ」という事実です。説明可能性が教えてくれるのは「エージェントが複数のファイルを検討した結果、foo.tsを変更対象として選んだ理由」です。

この2つを混同してしまうと、ログを集めているのに問題の原因が分からないという状況に陥ります。エラーが起きた後に「ログを見ても何も分からなかった」という経験は、おそらく多くの開発者が持っているのではないでしょうか。

私が設計を始めたきっかけも、まさにその経験でした。OpenTelemetryでエージェントのトレースを記録していたのに、問題の根本原因が分かりませんでした。ツール呼び出しの順序は記録されていましたが、「なぜその順序になったか」が追えなかったのです。

一般的な可観測性パイプラインはエージェントの動作を記録できますが、「なぜそう判断したか」の構造化されたログは自分で設計する必要があります。Antigravityでは、この2つの層を分けて実装するのが実用的です。

ハーネスエンジニアリング入門: 安定的なAIエージェントの設計手法では安定性の確保について詳しく解説されていますが、ここでは「なぜ安定していないか」を追跡するための記録設計にフォーカスします。

判断ログの基本設計 — DecisionLogger の実装

最もシンプルな判断ログは、エージェントの各思考ステップを構造化して記録するものです。設計の核心は「アクション記録」と「理由記録」を分離することにあります。

大切にしている設計方針が3つあります。

1つ目はステップ単位での記録です。タスク全体ではなく、判断の1ステップごとにエントリを作ります。問題が起きた場合に「何ステップ目で何が起きたか」を特定できるようにするためです。

2つ目は信頼度の自動推定です。エージェント自身に「この判断は確かだ/不確かだ」と評価させるのではなく、ツール入力の「理由フィールドの充実度」から外部で推定します。エージェントの自己評価は楽観的になりやすいため、客観的な指標を設ける点が肝心です。

3つ目はコンテキスト要因の記録です。その判断に影響したと思われる文脈を記録します。後の分析で「このコンテキストがある場合に判断が乱れる」というパターンを発見できます。

import json
import time
from datetime import datetime
from typing import Any
 
class DecisionLogger:
    """エージェントの判断ステップを記録するロガー"""
 
    def __init__(self, session_id: str):
        self.session_id = session_id
        self.entries = []
        self.start_time = time.time()
 
    def log_decision(
        self,
        step: int,
        reasoning: str,
        action: str,
        tool_name: str | None = None,
        tool_input: dict | None = None,
        confidence: str = "medium",  # high / medium / low
        context_factors: list[str] | None = None
    ) -> None:
        entry = {
            "session_id": self.session_id,
            "step": step,
            "timestamp": datetime.utcnow().isoformat(),
            "elapsed_sec": round(time.time() - self.start_time, 2),
            "reasoning": reasoning,
            "action": action,
            "tool": {
                "name": tool_name,
                "input": tool_input
            } if tool_name else None,
            "confidence": confidence,
            "context_factors": context_factors or []
        }
        self.entries.append(entry)
        print(f"[Step {step}] {action} → confidence: {confidence}")
 
    def export(self) -> str:
        return json.dumps(self.entries, ensure_ascii=False, indent=2)
 
    def summary(self) -> dict:
        """ログのサマリーを返す"""
        tool_calls = [e for e in self.entries if e.get("tool")]
        confidence_dist = {}
        for e in self.entries:
            c = e["confidence"]
            confidence_dist[c] = confidence_dist.get(c, 0) + 1
        return {
            "total_steps": len(self.entries),
            "tool_calls": len(tool_calls),
            "tools_used": list({e["tool"]["name"] for e in tool_calls}),
            "confidence_distribution": confidence_dist,
            "elapsed_total_sec": round(time.time() - self.start_time, 2)
        }

このロガーを組み込んだエージェントループの実装例です。注目してほしいのは、システムプロンプトで「判断理由の言語化」を義務付けている点と、その理由の充実度から信頼度を自動推定している点です。

from anthropic import Anthropic
 
client = Anthropic()
 
def run_agent_with_logging(task: str, session_id: str) -> dict:
    """判断ログ付きエージェントの実行"""
    logger = DecisionLogger(session_id)
 
    tools = [
        {
            "name": "read_file",
            "description": "ファイルの内容を読み込む",
            "input_schema": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "ファイルパス"}
                },
                "required": ["path"]
            }
        },
        {
            "name": "write_file",
            "description": "ファイルに内容を書き込む。副作用があるため reason を必ず記入する",
            "input_schema": {
                "type": "object",
                "properties": {
                    "path": {"type": "string"},
                    "content": {"type": "string"},
                    "reason": {
                        "type": "string",
                        "description": "このファイルを書き換える理由(50文字以上で具体的に記述)"
                    }
                },
                "required": ["path", "content", "reason"]
            }
        }
    ]
 
    # 判断理由の言語化をシステムプロンプトで義務付ける
    system = """あなたはファイル操作エージェントです。
 
重要なルール:
- ツールを呼び出す前に「なぜこのツールを使うか」を内部で言語化してください
- write_file を使う場合は reason に具体的な理由を50文字以上で記入してください
- 曖昧な指示は、最も保守的な(影響範囲の小さい)解釈を採用してください
- 不確実な場合は、read_file で確認してから判断してください"""
 
    messages = [{"role": "user", "content": task}]
    step = 0
 
    while True:
        step += 1
        response = client.messages.create(
            model="claude-opus-4-6",
            max_tokens=4096,
            system=system,
            tools=tools,
            messages=messages
        )
 
        if response.stop_reason == "tool_use":
            for block in response.content:
                if block.type == "tool_use":
                    # reason の充実度から信頼度を推定
                    reason_text = block.input.get("reason", "")
                    if len(reason_text) < 20:
                        confidence = "low"
                    elif len(reason_text) < 50:
                        confidence = "medium"
                    else:
                        confidence = "high"
 
                    logger.log_decision(
                        step=step,
                        reasoning=reason_text or "(理由未記録)",
                        action=f"{block.name} を呼び出す",
                        tool_name=block.name,
                        tool_input=block.input,
                        confidence=confidence
                    )
 
                    tool_result = execute_tool(block.name, block.input)
                    messages.append({"role": "assistant", "content": response.content})
                    messages.append({
                        "role": "user",
                        "content": [{
                            "type": "tool_result",
                            "tool_use_id": block.id,
                            "content": str(tool_result)
                        }]
                    })
        else:
            logger.log_decision(
                step=step,
                reasoning="全ステップ完了",
                action="タスク終了",
                confidence="high"
            )
            break
 
        if step > 20:
            break
 
    return {
        "result": response.content[-1].text if response.content else "",
        "decision_log": logger.export(),
        "summary": logger.summary()
    }

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
判断ログを JSONL と SQLite の二段構えで保存し、個人開発の規模で破綻しない記録基盤を作る実装コード
low_confidence_rate と不確実フラグ密度から「エージェントにどこまで任せるか」を3段階で線引きする実運用基準
reason フィールドの形式化を防ぐ具体性スコアなど、6ヶ月の運用で効果のあった改善サイクルの設計
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

Agents & Manager2026-07-05
エージェント開発スタックの『既知の正常』を1枚のロックファイルで守る — 可動部が同時に動く時代の変更予算設計
IDEビルド・CLI・モデル・依存が同時に動くと、回帰の原因が特定できなくなります。既知の正常を1枚のロックファイルに固定し、一度に動かす軸を絞る変更予算の設計を、実装コードと運用ログで整理しました。
Agents & Manager2026-05-29
Antigravity 長時間実行エージェントの監視設計 — watchdog と段階的リカバリ
AdMob 収益最適化を 8 週間バックグラウンドエージェントに任せて見えた、長時間タスクが静かに停止する 3 つの故障モード。watchdog タイマーと段階的リカバリの実装メモを残します。
Agents & Manager2026-05-27
Antigravity Agent の Record & Replay — 失敗を3分で再現する本番運用パターン
Antigravity の自律エージェントが本番で失敗した瞬間を、後からオフラインで決定的に再生する Record & Replay の設計と実装。Dolice Labs 4 サイトを並行運用してきた経験から、ストレージ設計・PII マスク・ハーネスのコードまで具体的に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →