ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-04-28上級

Antigravity Agent 本番障害対応 Runbook 設計 — 検知から復旧・再発防止までの実践フレームワーク

Antigravity Agent の本番障害に備える Runbook 設計の完全ガイド。検知・トリアージ・復旧・ポストモーテムまでを実装コード付きで体系化します。

antigravity345agents67incident-responsesre2runbookproduction53

プレミアム記事

import RelatedArticles from "@/components/RelatedArticles";

「夜中の 2 時に Slack 通知が鳴って、Antigravity Agent が同じツールを 200 回呼び出して止まらなくなっていた」— これは私が実際に体験した一夜の話です。半分寝ぼけながら Manager Surface を開き、どのトレースから見ればいいのか分からず 30 分溶かしました。Runbook さえあれば、5 分で原因特定まで辿り着けたはずでした。

AI Agent の本番運用は、従来の Web サービスとは別物の障害パターンを生み出します。CPU は元気でも「思考が壊れている」状態が起きるのです。ここでは私が複数のプロダクトで失敗を重ねながら整えてきた Antigravity Agent 専用の Runbook フレームワーク を、コード付きで丸ごとお渡しします。

個人開発から数十エージェントを束ねる本番環境まで、規模に応じてスケールする設計にしています。読み終える頃には、Slack に届いた最初のアラートから 5 分以内にトリアージを終え、ユーザー影響を最小化する具体的な手順が手元に揃っているはずです。

なぜ Antigravity Agent には専用 Runbook が必要なのか

通常の Web サービスの Runbook は「リクエスト数が急増した」「DB 接続が切れた」といった外形的な指標に紐づいています。一方、Antigravity Agent の障害は次のような特徴を持ちます。

  • 「正常に動いているのに間違っている」状態が頻発する: ツール呼び出しは成功、API は 200 を返す、しかし出力が業務要件を満たしていない
  • 失敗が伝播しにくい: マルチエージェント構成では Worker Agent が壊れていても Manager Agent が「問題ありません」と返してしまうケースがある
  • コストが障害指標になる: トークン消費が急増した時点で、すでに数百ドル分の損害が出ている可能性がある
  • 再現性が低い: 同じ入力で 2 回試して再現しないことが普通にある

つまり、HTTP ステータスや CPU 使用率だけを見る Runbook では網羅できません。Antigravity 固有の runIdtraceIdagentSpanId を中心に据えた、AI Agent 専用の対応フローが必要になります。

私は当初、既存の SRE Runbook テンプレートをそのまま使っていましたが、トリアージ初動で「どのログを見ればいいのか」を毎回考えてしまい、夜中の対応で頭が回らない時に致命的でした。Agent 専用フレームワークに切り替えてから、初動時間が平均 18 分から 4 分に短縮されました。

Runbook の 4 階層モデル — 個人開発でも回せる軽量設計

私が辿り着いたのは、Runbook を 4 つの階層に分ける構造です。重いプロセスは続かないので、個人開発者が一人で回せる軽さを優先しています。

  • L0: 検知 (Detection) — 何かがおかしいと最初に気づく層。アラート定義とトリガー条件を集約
  • L1: トリアージ (Triage) — 5 分以内に「ユーザー影響あり/なし」「自動復旧可能/不可」を判定
  • L2: 抑制 (Mitigation) — ユーザー影響を止めるための即時アクション。Kill switch・フォールバック・トラフィック遮断
  • L3: 復旧と再発防止 (Recovery & Postmortem) — 根本原因の修正、Runbook 更新、再発防止策の組み込み

各階層には専用のチェックリストとコードスニペットを用意します。深夜に冷静に判断できる人間はいないので、Runbook が考える代わりに動いてくれる必要があります。

L0: 検知の設計 — 4 種類のアラートを使い分ける

まず Antigravity Agent で監視すべき指標を 4 つに整理します。1 つのダッシュボードに混ぜると見落とすので、必ず分けてください。

// monitoring/agent-alerts.ts
// Antigravity Agent 用のアラート定義(OpenTelemetry + PromQL ベース)
import { Counter, Gauge, Histogram } from "@opentelemetry/api";
 
// ① 動作系: そもそも Agent が動いているか
export const agentRunCount = new Counter({
  name: "antigravity_agent_run_total",
  help: "Total number of Agent runs by status",
  labelNames: ["agent_id", "status"], // status: success | failure | timeout
});
 
// ② 品質系: 出力が業務要件を満たしているか
export const agentEvalScore = new Histogram({
  name: "antigravity_agent_eval_score",
  help: "Eval score (0-1) for Agent outputs",
  labelNames: ["agent_id", "eval_type"],
  buckets: [0.5, 0.7, 0.8, 0.9, 0.95],
});
 
// ③ コスト系: トークン消費が予算内か
export const agentTokenSpend = new Counter({
  name: "antigravity_agent_token_spend_usd",
  help: "Cumulative USD spend per Agent",
  labelNames: ["agent_id", "model"],
});
 
// ④ ループ系: 同じツールを暴走呼び出ししていないか
export const agentToolCallStreak = new Gauge({
  name: "antigravity_agent_tool_call_streak",
  help: "Consecutive identical tool calls (potential loop)",
  labelNames: ["agent_id", "tool_name"],
});

私が実装で痛感したのは、コスト系アラートを最初に設計しないと取り返しがつかない ことです。一晩で $300 溶かした経験があります。agentTokenSpend のような累積カウンターを 1 時間あたり $X 超えたら即時通知する仕組みを、最初の 1 日目に組み込んでください。

PromQL でのアラートルール例も載せます。Cloudflare や Grafana Cloud に置く前提です。

# monitoring/alerts.yml
# Antigravity Agent の本番アラートルール
groups:
  - name: antigravity_agent_alerts
    interval: 30s
    rules:
      # ① 動作系: 失敗率が 5 分間で 20% 超
      - alert: AgentFailureRateHigh
        expr: |
          (
            sum(rate(antigravity_agent_run_total{status="failure"}[5m])) by (agent_id)
            / sum(rate(antigravity_agent_run_total[5m])) by (agent_id)
          ) > 0.2
        for: 5m
        labels:
          severity: page
          runbook: agent-failure-rate
        annotations:
          summary: "Agent {{ $labels.agent_id }} failure rate > 20%"
 
      # ② コスト系: 1 時間で $20 超え(個人開発スケール)
      - alert: AgentTokenSpendBurst
        expr: |
          increase(antigravity_agent_token_spend_usd[1h]) > 20
        for: 5m
        labels:
          severity: page
          runbook: agent-cost-burst
        annotations:
          summary: "Agent {{ $labels.agent_id }} burned ${{ $value }} in 1h"
 
      # ③ ループ系: 同じツールを 30 回連続呼び出し
      - alert: AgentToolLoopDetected
        expr: antigravity_agent_tool_call_streak > 30
        for: 1m
        labels:
          severity: page
          runbook: agent-tool-loop
        annotations:
          summary: "Agent {{ $labels.agent_id }} stuck on {{ $labels.tool_name }}"
 
      # ④ 品質系: Eval スコアが過去 1 時間で 0.7 を下回る
      - alert: AgentQualityDegraded
        expr: |
          histogram_quantile(0.5, sum(rate(antigravity_agent_eval_score_bucket[1h])) by (le, agent_id)) < 0.7
        for: 15m
        labels:
          severity: ticket
          runbook: agent-quality-drop
        annotations:
          summary: "Agent {{ $labels.agent_id }} median eval score < 0.7"

severity: page はオンコールに即時通知、severity: ticket は翌営業日対応で十分なものに分けています。全部 page にすると本当に必要な時に反応できなくなるので、痛い目を見ながら線引きしてきました。

L1: トリアージの 5 分間プロトコル

アラートを受け取ったら、まず 5 分以内に次の 3 つを答える ことを目標にします。

  1. ユーザー影響は出ているか?(出ていれば即 L2 へ)
  2. 自動復旧の余地はあるか?(あればまず再試行)
  3. 横展開リスクはあるか?(他の Agent や下流サービスへの影響)

このフローを Runbook に落とし込んだ Markdown テンプレートが以下です。Slack の Workflow Builder や Notion にそのまま貼って使ってください。

## トリアージ・チェックリスト(5 分以内に完了)
 
- [ ] **alert ID** を取得 (例: `AgentFailureRateHigh-2026-04-28-02-15`)
- [ ] Manager Surface で該当 `agent_id``runId` を上から 3 件開く
- [ ] エラーメッセージのパターンを判定:
  - [ ] `ToolTimeout` → L2-A: タイムアウト緩和フロー
  - [ ] `RateLimitExceeded` → L2-B: バックオフ延長フロー
  - [ ] `MaxIterationsReached` → L2-C: ループ検出フロー
  - [ ] `EvalScoreDrop` → L2-D: モデルロールバックフロー
  - [ ] その他 → L2-E: Kill switch 発動を検討
- [ ] ユーザー影響を `cmd+K``is_user_facing` フィールド検索:
  - 「あり」: 60 秒以内に L2 を発動
  - 「なし」: 15 分内で L3 へ移行
- [ ] 横展開リスクの確認:
  - [ ] 同じ tool を使う他の Agent の失敗率 (PromQL: `rate(antigravity_agent_run_total{status="failure", tool_name="<name>"}[5m])`)
  - [ ] 下流サービス (DB, 外部 API) の応答時間
- [ ] **#incidents-agent** チャネルに `[L1完了] agent_id=xxx pattern=ToolTimeout user_impact=あり` を投稿

このチェックリストは、私が実際に 5 分タイマーを使ってリハーサルしながら磨いてきたものです。「Manager Surface で runId を上から 3 件開く」 のように、具体的な GUI 操作を書くのがコツです。「ログを確認する」だけだと、夜中に頭が回らない時に固まります。

L2: 抑制パターン集 — Kill Switch を 1 行で打てるように

ユーザー影響が出ている場合、根本原因の調査より先に 「血を止める」 ことが優先です。Antigravity Agent には次の 5 種類の Mitigation パターンを用意しておきます。

// runbook/mitigations.ts
// 本番障害の即時抑制ライブラリ
import { ConfigStore } from "./config-store";
 
interface MitigationContext {
  agentId: string;
  reason: string;
  operator: string; // 実行者の名前
  durationMinutes?: number;
}
 
export class AgentMitigator {
  constructor(private config: ConfigStore) {}
 
  // パターン A: Kill Switch — Agent を完全停止
  async killSwitch(ctx: MitigationContext): Promise<void> {
    await this.config.set(`agent:${ctx.agentId}:enabled`, false, {
      ttlMinutes: ctx.durationMinutes ?? 60,
      audit: { reason: ctx.reason, operator: ctx.operator },
    });
    console.log(`[KILL] ${ctx.agentId} stopped for ${ctx.durationMinutes ?? 60}min`);
    // 通知も忘れずに
    await this.notifySlack(`🚨 ${ctx.agentId} を ${ctx.durationMinutes ?? 60}分間停止しました (${ctx.operator}: ${ctx.reason})`);
  }
 
  // パターン B: フォールバック — 単純な決定論的処理に切り替え
  async fallbackToDeterministic(ctx: MitigationContext): Promise<void> {
    await this.config.set(`agent:${ctx.agentId}:mode`, "fallback", {
      ttlMinutes: ctx.durationMinutes ?? 30,
      audit: { reason: ctx.reason, operator: ctx.operator },
    });
    await this.notifySlack(`🔄 ${ctx.agentId} をフォールバックモードに切替`);
  }
 
  // パターン C: モデルダウングレード — 安定版に戻す
  async downgradeModel(ctx: MitigationContext, fallbackModel: string): Promise<void> {
    await this.config.set(`agent:${ctx.agentId}:model`, fallbackModel, {
      ttlMinutes: ctx.durationMinutes ?? 1440,
      audit: { reason: ctx.reason, operator: ctx.operator },
    });
    await this.notifySlack(`⬇️ ${ctx.agentId} を ${fallbackModel} にダウングレード`);
  }
 
  // パターン D: トラフィック制限 — 同時実行数を絞る
  async throttle(ctx: MitigationContext, maxConcurrent: number): Promise<void> {
    await this.config.set(`agent:${ctx.agentId}:max_concurrent`, maxConcurrent, {
      ttlMinutes: ctx.durationMinutes ?? 60,
      audit: { reason: ctx.reason, operator: ctx.operator },
    });
    await this.notifySlack(`🐌 ${ctx.agentId} の並列度を ${maxConcurrent} に絞りました`);
  }
 
  // パターン E: ループ強制脱出 — 進行中のループを中断
  async breakLoop(ctx: MitigationContext, runId: string): Promise<void> {
    await this.config.set(`run:${runId}:cancel`, true, {
      ttlMinutes: 5,
      audit: { reason: ctx.reason, operator: ctx.operator },
    });
    await this.notifySlack(`✂️ runId=${runId} のループを強制終了`);
  }
 
  private async notifySlack(message: string): Promise<void> {
    const webhookUrl = process.env.SLACK_INCIDENT_WEBHOOK_URL;
    if (!webhookUrl) {
      console.warn("SLACK_INCIDENT_WEBHOOK_URL not set, skipping notification");
      return;
    }
    try {
      await fetch(webhookUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ text: message, channel: "#incidents-agent" }),
      });
    } catch (e) {
      console.error("Slack notify failed:", e);
      // 通知失敗で抑制処理を止めないこと(ここが重要)
    }
  }
}
 
// 使い方の例: Kill Switch を 1 行で打つ
// const mitigator = new AgentMitigator(configStore);
// await mitigator.killSwitch({ agentId: "code-reviewer", reason: "暴走検知", operator: "masaki" });

ここで意識しているのは、Slack 通知の失敗で抑制処理を止めないこと です。本番障害中に Slack まで落ちている可能性は普通にあります。try/catch でくるんで、抑制自体は完遂させてください。

それから TTL 必須 にしているのも重要なポイントです。手動で enabled = false にして、そのまま忘れて翌週まで止めっぱなし、という事故を防ぎます。最大 24 時間で自動復活させて、必要なら更新する設計にしましょう。

L3: 復旧 — Trace ID から原因を辿る実装

血が止まったら、根本原因を特定して恒久対策を打ちます。Antigravity Agent の場合、traceId を起点に「思考の足跡」を再現できることが強力な武器になります。

// runbook/postmortem-data.ts
// 障害ポストモーテム用のデータ収集スクリプト
// 使い方: node postmortem-data.ts <traceId>
 
import { AntigravityClient } from "@antigravity/sdk";
import { writeFileSync } from "node:fs";
 
interface PostmortemBundle {
  traceId: string;
  agentId: string;
  startedAt: string;
  endedAt: string;
  totalTokens: number;
  totalUsd: number;
  toolCalls: Array<{
    spanId: string;
    toolName: string;
    durationMs: number;
    success: boolean;
    inputHash: string;
    outputHash: string;
  }>;
  modelMessages: Array<{
    role: string;
    contentSummary: string; // 最初の 200 文字
    tokens: number;
  }>;
  evalScores: Array<{ evalType: string; score: number }>;
}
 
async function collectBundle(traceId: string): Promise<PostmortemBundle> {
  const client = new AntigravityClient({
    apiKey: process.env.ANTIGRAVITY_API_KEY!,
  });
 
  try {
    const trace = await client.traces.get(traceId);
    const spans = await client.traces.spans(traceId);
    const evals = await client.traces.evals(traceId);
 
    return {
      traceId,
      agentId: trace.agentId,
      startedAt: trace.startedAt,
      endedAt: trace.endedAt ?? new Date().toISOString(),
      totalTokens: trace.totalTokens,
      totalUsd: trace.totalUsd,
      toolCalls: spans
        .filter((s) => s.type === "tool_call")
        .map((s) => ({
          spanId: s.id,
          toolName: s.attributes.tool_name,
          durationMs: s.durationMs,
          success: s.status === "ok",
          inputHash: s.attributes.input_hash,
          outputHash: s.attributes.output_hash,
        })),
      modelMessages: spans
        .filter((s) => s.type === "model_message")
        .map((s) => ({
          role: s.attributes.role,
          contentSummary: (s.attributes.content ?? "").slice(0, 200),
          tokens: s.attributes.tokens ?? 0,
        })),
      evalScores: evals.map((e) => ({ evalType: e.type, score: e.score })),
    };
  } catch (e) {
    console.error(`Failed to collect bundle for ${traceId}:`, e);
    throw new Error(`Trace ${traceId} not found or API error`);
  }
}
 
// CLI エントリポイント
const traceId = process.argv[2];
if (!traceId) {
  console.error("Usage: node postmortem-data.ts <traceId>");
  process.exit(1);
}
 
collectBundle(traceId)
  .then((bundle) => {
    const filename = `postmortem-${traceId.slice(0, 8)}-${Date.now()}.json`;
    writeFileSync(filename, JSON.stringify(bundle, null, 2));
    console.log(`✅ Saved bundle: ${filename}`);
    console.log(`   Tool calls: ${bundle.toolCalls.length}`);
    console.log(`   Token cost: $${bundle.totalUsd.toFixed(2)}`);
    console.log(`   Failures: ${bundle.toolCalls.filter((c) => !c.success).length}`);
  })
  .catch((e) => {
    console.error("❌ Bundle collection failed:", e.message);
    process.exit(1);
  });

このスクリプトを npx tsx postmortem-data.ts <traceId> で叩けば、ポストモーテム用のデータが 1 ファイルにまとまります。私はこれを Notion のポストモーテムページに添付して、後から見返せるようにしています。

実行結果の例はこんな感じです。

✅ Saved bundle: postmortem-abc12345-1714356123456.json
   Tool calls: 247
   Token cost: $18.42
   Failures: 12

Tool calls: 247 がすでに異常値だと一目で分かります。健全な実行は通常 5〜30 件に収まるからです。

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

この記事の続きを読む

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

この記事で得られること
深夜に Agent が暴走しても 5 分でトリアージできる Runbook テンプレートを手元に持てるようになります
検知 → 抑制 → 復旧の 3 段階を Antigravity 固有の Trace ID と Manager Surface に紐付ける具体的な実装パターンを習得できます
個人開発でも回せる「軽量ポストモーテム」フォーマットで、同じ障害を二度と起こさない仕組みを今日から構築できます
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

Agents & Manager2026-04-24
Antigravity エージェントの SRE を始める — SLO とエラーバジェットで『AIは気まぐれ』を本番運用に落とし込む
AI エージェントは確率的に動く以上、SRE の考え方なしに本番運用はできません。SLI/SLO/エラーバジェットを Antigravity エージェントにどう適用するか、実装コードと運用判断基準まで踏み込んで解説します。
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駆動開発
※ アフィリエイトリンクを含みます
もっと見る →