ブログ
アーキテクチャ

公開日: 更新日

AragとShouryamaan

AIエージェントの永続実行:チェックポイント、再試行、復旧

Markdownで読む

永続実行により、AIエージェントはプロセスのクラッシュ、タイムアウト、デプロイ、長時間の待機後も、完了済みの副作用を繰り返さずに複数手順のタスクを再開できます。永続化された進捗、繰り返しても安全な書き込み、結果不明の呼び出しに対する復旧ルールが必要です。

再試行ループだけでは永続実行になりません。レスポンス消失後にメール送信や支払いを再試行すると、復旧しようとしていた障害そのものを引き起こす可能性があります。

外部への効果を中心にワークフローをモデル化する

請求書を作成して送信し、タスクを完了にするワークフローを考えます。送信は成功したものの、成功を記録する前にworkerがクラッシュした場合、次の実行は成功とも失敗とも安全に仮定できません。

sequenceDiagram
    participant W as Workflow
    participant L as Operation ledger
    participant E as Email provider
    W->>L: Reserve send_invoice:task_442
    W->>E: Send with operation key
    E-->>W: Response lost
    Note over W: Worker restarts
    W->>L: Read operation status
    W->>E: Reconcile by provider ID or key
    E-->>W: Already sent
    W->>L: Mark completed
    W->>W: Continue from checkpoint

ワークフロー記録はシステムの意図を示します。プロバイダーまたは正式なバックエンドは実際に起きたことを示します。復旧では、呼び出しを繰り返すか決める前に両者を照合します。

ワークフローとactivityを分ける

ワークフローは手順、待機、分岐、再試行を調整します。activityはAPI呼び出しやファイル書き込みなど、外部への効果を実行します。replay型エンジンではワークフローの判断を決定論的に保ち、ネットワーク呼び出し、時計、乱数、プロバイダーSDKをactivity内へ隔離します。

Temporalの永続実行モデルはイベント履歴を記録し、決定論的replayで障害後のワークフロー状態を復元します。このエンジンを採用しても、operations tableとqueueで小規模なstate machineを実装しても構いません。要件は同じです。永続化した履歴が再開地点を決定します。

再試行可能な書き込みをすべて冪等にする

一つの論理的効果に一つのキーを割り当て、client、worker、providerの再試行で同じキーを再利用します。最初の終了結果を保存し、異なるparameterでの再利用を拒否します。

Stripeの冪等なリクエスト契約は、失敗も含め、キーに対する最初のリクエストのstatus codeとbodyを保存します。保持動作はStripe固有ですが、設計原則は共通です。反復リクエストが同じ操作かどうかは、モデルではなくサーバーが判断します。

プロバイダーが冪等性に対応していない場合、adapterを操作台帳で囲みます。自身の再試行による重複は減らせますが、タイムアウトしたプロバイダー呼び出しが失敗したことまでは証明できません。照合用lookupを追加するか、影響の大きい未解決の結果は人間にレビューさせます。

検証済みの事実をcheckpointする

有用なcheckpointは、完了した操作IDと次の有効なtransitionを記録します。効果を検証した後に書き込み、可能なら自身のデータベース書き込みと状態遷移をatomicにします。

モデルのメッセージだけをcheckpointしないでください。会話履歴内の「メールを送信した」は、プロバイダーの結果ではなくモデルの主張かもしれません。プロバイダーのメッセージIDまたは操作記録への参照を保存します。

永続状態ガイドでは、タスクの進捗をメモリとビジネスデータから分けています。

各再試行の担当レイヤーを決める

モデルSDK、HTTP client、queue、workflow engine、tool server、providerがすべて再試行する可能性があります。掛け合わされると、設定上は3回でも数十回の呼び出しになります。

各操作について次を文書化します。

  • 再試行可能な失敗
  • 再試行を行うレイヤー
  • 最大試行回数と経過時間
  • backoffとプロバイダーのrate limit signal
  • 試行間で再利用する冪等性キー
  • 上限到達後の最終状態

検証エラーと認可エラーでは停止します。rate limitでは待機できます。書き込みのネットワークタイムアウトは、再試行前に照合します。モデルにエラー文字列からこれらのルールを即興で判断させてはいけません。

待機を永続状態として扱う

人間の承認、予定時刻、webhook、長時間デプロイは、sleeping processではなくワークフローの状態です。待機条件とwake-up signalを永続化します。重複するsignalを安全に処理し、遅延した承認から再開するときには、代理対象のユーザーがまだ権限を持つことを確認します。

キャンセルを状態遷移として公開します。キャンセルは以後の手順を停止しますが、完了済みの副作用は消せません。請求書の無効化などの補償は、キャンセルと別に定義します。

曖昧な時間帯をテストする

成功経路のテストでは最も難しい状態を見落とします。サーバーがリクエストを完了した可能性がある一方、クライアントがレスポンスを受け取っていない状態です。外部呼び出しの直後、完了記録の前に障害を注入します。別workerで再起動し、結果が一度だけ照合されることを確認します。

承認中、backoff中、キャンセル後にも中断します。元の試行と再開後の実行を、同じworkflow IDとoperation IDで一つのトレースに記録します。障害復旧ガイドでは、これらを完全なrunbookに変換します。

以下のプロンプトは、本番環境に触れず最初の中断テストを設計します。期待する結果が「エージェントが何とかする」なら、まだ復旧契約はありません。