ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-03-30上級

Antigravity × Durable Execution: 障害に強い長時間AIタスクの設計パターン

Durable Execution パターンで Antigravity のエージェントを長時間タスクに投入する実装ガイド。チェックポイント・べき等性・自動リトライに加え、5,000 万 DL の個人開発で実際に救われた障害事例と、本番で踏みやすい 7 つの落とし穴まで具体的にまとめます。

antigravity435durable-execution2ai-agent17typescript26fault-toleranceworkflow37

プレミアム記事

ある朝、AdMob の前日収益を集計する 1 時間半のパイプラインがあと 10 分というところで落ちていて、最初からやり直すのを諦めて出社の電車の中で同じ処理を再度キックした、という経験があります。私は 2014 年から個人開発でアプリを公開し続けていて、累計 5,000 万 DL を越える壁紙アプリや癒し系アプリの収益を AdMob と App Store / Google Play のデータで毎日突合させているのですが、Durable Execution を導入する前は、こうしたパイプラインの「あと一歩で落ちた」状況がとにかく痛かった。AdMob API のレートリミット、Cloudflare Workers のタイムアウト、Supabase 側の一時切断、どれもこちらの設計次第で「ゼロからやり直し」になっていました。

廣川政樹(@dolice)です。アーティスト活動と並行して個人開発を続けるなかで、Antigravity のエージェント機能を使った長時間タスクを安定運用させる必要に迫られ、Durable Execution の設計パターンを実戦投入してきました。このページでは、その実装コードと、5,000 万 DL の現場で実際に救われた障害事例、そして公式ドキュメントが触れない 7 つの落とし穴まで、課金して読んでいただく価値のある範囲で具体的にまとめます。

Durable Execution の 3 つの基本原則

このパターンを正しく理解するために、3 つの基本原則を押さえておきましょう。Antigravity でのエージェント設計にも、そのまま転用できる骨格です。

チェックポイントによる状態保存

ワークフローの各ステップが完了するたびに、その結果を永続ストレージ(データベースやキュー、Cloudflare KV など)に保存します。障害が発生した場合、最後に保存されたチェックポイントから処理を再開できます。私が運用している AdMob 集計パイプラインでは、再実行時間が 47 分 → 4 分(約 11 倍の短縮)にまで縮みました。

べき等性(Idempotency)の確保

同じ処理が複数回実行されても、結果が変わらないことを保証する設計です。リトライが発生したときに、二重にデータが書き込まれたり、メールが複数回送信されたりすることを防ぎます。べき等性の欠落は、特に決済・通知系の処理で取り返しのつかない事故に直結します。

自動リトライとバックオフ

一時的な障害(ネットワークタイムアウト、レート制限など)に対して、指数バックオフ付きで自動的にリトライします。永続的な障害と一時的な障害を区別し、適切に対処する点が肝心です。AdMob API の場合、UNAVAILABLERESOURCE_EXHAUSTED は再試行価値あり、PERMISSION_DENIED は即時打ち切りといった具体的な分岐を入れています。

Antigravity で Durable Execution を実装する最小コード

Antigravity のエージェント機能を使って、実際に Durable Execution パターンを実装してみましょう。ここでは TypeScript で、外部 API からデータを取得し、加工して保存する長時間ワークフローを構築します。

// durable-workflow.ts
// Durable Execution パターンの基本実装
 
interface WorkflowState {
  currentStep: string;
  completedSteps: string[];
  data: Record<string, unknown>;
  retryCount: number;
  lastCheckpoint: string;
}
 
class DurableWorkflow {
  private state: WorkflowState;
  private storePath: string;
 
  constructor(workflowId: string) {
    this.storePath = `./workflow-state/${workflowId}.json`;
    this.state = this.loadState();
  }
 
  // チェックポイントから状態を復元
  private loadState(): WorkflowState {
    try {
      const fs = require("fs");
      if (fs.existsSync(this.storePath)) {
        const saved = JSON.parse(fs.readFileSync(this.storePath, "utf-8"));
        console.log(`Resuming from checkpoint: ${saved.lastCheckpoint}`);
        return saved;
      }
    } catch (e) {
      console.warn("No checkpoint found, starting fresh");
    }
    return {
      currentStep: "init",
      completedSteps: [],
      data: {},
      retryCount: 0,
      lastCheckpoint: "",
    };
  }
 
  // チェックポイントを保存
  private saveCheckpoint(stepName: string): void {
    const fs = require("fs");
    const path = require("path");
    fs.mkdirSync(path.dirname(this.storePath), { recursive: true });
    this.state.lastCheckpoint = stepName;
    this.state.completedSteps.push(stepName);
    fs.writeFileSync(this.storePath, JSON.stringify(this.state, null, 2));
    console.log(`Checkpoint saved: ${stepName}`);
  }
 
  // べき等なステップ実行
  async executeStep<T>(
    stepName: string,
    fn: () => Promise<T>,
    maxRetries = 3
  ): Promise<T> {
    // 既に完了済みのステップはスキップ
    if (this.state.completedSteps.includes(stepName)) {
      console.log(`Skipping completed step: ${stepName}`);
      return this.state.data[stepName] as T;
    }
 
    this.state.currentStep = stepName;
 
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const result = await fn();
        this.state.data[stepName] = result;
        this.saveCheckpoint(stepName);
        return result;
      } catch (error) {
        const waitMs = Math.min(1000 * Math.pow(2, attempt), 30000);
        console.error(
          `Step "${stepName}" failed (attempt ${attempt}/${maxRetries}):`,
          error
        );
        if (attempt === maxRetries) throw error;
        console.log(`Retrying in ${waitMs}ms...`);
        await new Promise((r) => setTimeout(r, waitMs));
      }
    }
    throw new Error(`Step "${stepName}" exhausted all retries`);
  }
}
 
// 使用例: 大量データの変換パイプライン
async function runDataPipeline() {
  const workflow = new DurableWorkflow("data-pipeline-001");
 
  // Step 1: データ取得(障害時はチェックポイントから再開)
  const rawData = await workflow.executeStep("fetch-data", async () => {
    const res = await fetch("https://api.example.com/large-dataset");
    return res.json();
    // 期待する出力: { records: [...], total: 10000 }
  });
 
  // Step 2: データ変換
  const transformed = await workflow.executeStep("transform", async () => {
    return rawData.records.map((r: any) => ({
      id: r.id,
      normalized: r.value.toLowerCase().trim(),
      processedAt: new Date().toISOString(),
    }));
    // 期待する出力: [{ id: "1", normalized: "...", processedAt: "..." }, ...]
  });
 
  // Step 3: バッチ保存
  const result = await workflow.executeStep("save-batch", async () => {
    const batchSize = 100;
    let saved = 0;
    for (let i = 0; i < transformed.length; i += batchSize) {
      const batch = transformed.slice(i, i + batchSize);
      await saveBatch(batch); // DB書き込み(べき等な upsert)
      saved += batch.length;
    }
    return { totalSaved: saved };
    // 期待する出力: { totalSaved: 10000 }
  });
 
  console.log("Pipeline complete:", result);
}
 
async function saveBatch(batch: any[]) {
  // べき等な upsert 処理(同じIDのレコードは上書き)
  console.log(`Saving batch of ${batch.length} records`);
}
 
runDataPipeline().catch(console.error);

このコードでは、各ステップの完了時にファイルシステムにチェックポイントを保存しています。プロセスが中断されても、再実行時には完了済みのステップを自動的にスキップし、未完了のステップから処理を再開します。サーバーレス環境ではローカル FS が揮発するため、本番では後述する KV / RDB バックエンドへの差し替えを推奨します。

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

この記事の続きを読む

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

この記事で得られること
5,000 万 DL アプリの AdMob 集計パイプラインで Durable Execution が救った 3 つの障害ログ(再実行時間 47 分 → 4 分)
公式ドキュメントには書かれていない、本番運用 7 つの落とし穴(チェックポイント肥大 / 二重通知 / レートリミット / 部分復旧 / 時刻ドリフト / Cold Start / 監視盲点)
Antigravity に Durable Workflow を生成させる時のプロンプト分割テンプレート(4 工程 / 失敗列挙 / ジョブ単位の独立化)
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

Agents & Manager2026-03-26
Antigravity マルチエージェント実践ガイド — 複数AIで開発を加速する方法
Antigravity のマルチエージェント機能を活用して開発速度を飛躍的に向上させる方法を解説。Manager Surface の使い方からエージェント間の連携パターンまで実践的に紹介します。
Agents & Manager2026-07-15
ls には見えるのに、エージェントが開けない — 日本語ファイル名が macOS と Linux で別物になるとき
エージェントが「そのファイルは存在しません」と言い、ターミナルの ls には確かに写っている。三日かけて同期を疑った末に辿り着いたのは、濁点が一文字ではなかったという事実でした。検出スクリプトと三層ゲートまで残します。
Agents & Manager2026-06-21
Background Agent に夜間作業を任せて、朝に後悔しないために — 無人実行のガードレール設計
Antigravity の Background Agent に夜間リファクタを任せると、朝には便利さと同じだけの不安が届きます。被害範囲・完了条件・静かな劣化の検出という3点から、無人実行を安心して回すためのガードレール設計を運用視点でまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →