この記事の音声は準備中です。
音声の一覧・再生リストへ非同期・長時間タスクの設計(耐久実行)
この記事の目的
数分〜数日かかるタスクや人の承認を挟むタスクを、「プロセスが生きている前提」を捨てて設計できるようになります。チェックポイントと再開、冪等性、承認待ちの永続化、進捗の可視化という設計要素と、それらをまとめて提供する耐久実行(durable execution)という実行モデルの考え方・採用判断を扱います。
対象読者
- 長時間・多段のタスクを実行する Agent で、クラッシュ・再起動のたびに「全部やり直し」になっている状態を直したいエンジニア
- 人の承認を挟むフローを本番品質(数日待てる・重複しない・監査できる)にしたいアーキテクト
前提知識
- メモリと状態管理 — 作業状態を構造化して外部に持つという原則(本記事はその執行系)
- エラー処理・リトライ設計 — 呼び出し単位の失敗処理と冪等性の基礎
- Human-in-the-Loop 設計 — 承認ゲートの設計(本記事は「待ち」を永続化する側)
本文
概要: 「プロセスが生きている」前提を捨てる
短い対話型 Agent は、1 つのプロセスの中でループが回りきる前提で書けます。しかしタスクが数分を超え、承認待ちが数日に及ぶと、その前提は必ず破れます — デプロイ・スケールイン・クラッシュ・ネットワーク断は日常だからです。長時間タスクの設計とは、実行のどの瞬間にプロセスが死んでも、途中から再開できるように状態と副作用を配置することです。
図を元の大きさで表示
図の中をスクロールして読めます。キーボードでは図の領域へ Tab で移動し、矢印キーを使います。読み終わったら、この見出しをもう一度選ぶと閉じます。
なお、キュー・ワーカー・状態ストアといったインフラ構成と容量設計は デプロイとスケーリング が正本です。本記事はその上で動く実行モデルの設計(何を永続化し、どう再開し、どう待つか)を扱います。
同期の限界と非同期化の判断
非同期化(ジョブ化)を判断する基準は 3 つです。
| 基準 | 同期のままでよい | 非同期化する |
|---|---|---|
| 実行時間 | p99 が UI・ゲートウェイの許容内(目安: 数十秒) | p99 が許容を超える、または入力次第で読めない |
| 人の関与 | ない(または即時応答できる) | 承認・確認・追加入力の待ちが入る |
| やり直しコスト | 失敗しても最初から再実行で許容できる(短い・安い) | 途中失敗の全やり直しが時間・トークン費用として痛い |
1 つでも「非同期化する」側に該当したら、ジョブ ID を返す非同期 API + 進捗通知の形へ寄せます。中間形として「同期で始めて、長引いたら非同期へ昇格」という設計もありますが、2 つの経路を両方保守するコストが掛かるため、迷ったら最初から非同期に寄せる方が総コストは小さくなります。
チェックポイントと再開
チェックポイント(checkpoint)とは、「ここまで完了した」を再開可能な形で永続化することです。メモリと状態管理の「作業状態」を、障害を前提に運用する設計と言えます。
- 何を保存するか: 完了したステップと結果(特にツール実行の結果)、会話・推論の状態(継続に必要な履歴、または明示的に要約して再開するための要約 + 決定事項)、中間成果物の参照(本体は外部ストレージ)、次にやることの計画
- どこで切るか: 意味のある単位で切ります。基本は「ツール実行の完了ごと」と「フェーズ(計画 → 実行 → 検証)の境界」です。細かすぎるチェックポイントは書き込みコストとレイテンシを増やし、粗すぎると再開時のやり直しが増えます
- 再開の設計: 継続中の会話を再開する場合は、API が要求する履歴・思考ブロック・未完了ツールを保持します。明示的に会話を圧縮して新しく再開する場合は「チェックポイントの構造化状態 + 経緯の要約」から再構築します。要約によるコスト削減だけでなく、キャッシュ再利用とプロトコル上の継続条件も比較して選びます
- コストの視点: Agent のやり直しは計算のやり直しであると同時にトークン課金のやり直しです。チェックポイントは信頼性の仕組みであると同時に、コスト制御の仕組みでもあります(コスト管理)
耐久実行(durable execution)という考え方
チェックポイント・再開・再試行を毎回手作りする代わりに、これを実行モデルとして提供するのが耐久実行(durable execution)です。中核のアイデアは次の 3 点で、主要なエンジン・フレームワークに共通しています。
- 結果の永続ログ: 実行の各ステップの「操作と結果」を永続ログに記録する
- 決定的な再実行: 障害後はログを頼りに実行を再現し、完了済みステップはスキップして続きから走る
- 非決定処理の隔離: LLM 呼び出し・ツール実行・現在時刻などの非決定的な処理は、「結果が記録される単位」に隔離する(オーケストレーション複数の LLM 呼び出しや Agent の実行順序とデータの流れを制御すること。直列・並列・ルーティングなどの定番パターンがある。部分のコードは決定的に保つ)
この 3 点目が Agent にとって決定的に重要です。LLM 呼び出しは非決定的なので、再開のたびに再実行すると「毎回違う計画で動き直す」Agent になります。耐久実行では完了済みの LLM 呼び出しは保存された結果が再利用されるため、再開しても同じ軌跡の続きを走れます。2026-07 時点で主要エンジンはいずれも「LLM 呼び出しを結果が永続化される実行単位に置く」ことを公式の標準解としています。
呼称はエンジンごとに違いますが、対応は次のとおりです(2026-07 時点の概観。一次情報発信源そのものの情報(論文の原典・公式ドキュメント・公式ブログ・規格本文・法令)。解説・まとめ(二次情報)と区別し、根拠は一次情報に置くのが情報リテラシーの基本。二次情報は「存在を知る」までに使い、一次まで遡って確かめる。の記録は research/professional/durable-execution.md)。
| 提供元(例) | 永続ログの呼称 | 非決定処理の隔離単位 | 承認・外部イベント待ち |
|---|---|---|---|
| Temporal | Event History(replay) | Activity | Signal / Update |
| Restate | journal | ctx.run | Awakeable |
| Inngest | step のメモ化 | step.run / step.ai | step.waitForEvent |
| Azure(Durable Functions / Durable Task) | オーケストレーション履歴 | アクティビティ関数 | 外部イベント待ち |
| Cloudflare(Workflows / Agents) | step 状態 + Durable Object 内ストレージ | step | イベント待ち・承認ゲート |
| AWS Step Functions | 状態マシンの実行履歴 | Task 状態 | タスクトークン待ち |
| LangGraph(フレームワーク側) | checkpointer(グラフ状態のスナップショット) | ノード / task | interrupt + 再開コマンド |
採用判断と注意点です。
- 自前かエンジンか: 「ジョブキュー + DB チェックポイント」の自前実装は、ステップ数が少なく承認待ちが単純なうちは十分です。ステップの分岐・並列・長い待機・再試行ポリシーが増えてきたら、エンジンの採用を検討します(自前の再開ロジックはバグの温床になりやすい領域です)
- 決定性要件という新しい制約: 耐久実行のオーケストレーションコードには「実行中のインスタンスがある状態でコードを変えると再現が壊れる」という制約が付きます。長時間実行と頻繁なデプロイを両立するには、ワークフローのバージョニング戦略が必須になります(バージョニング・デプロイ・モデル更新追従の考え方の適用先です)
- 履歴・状態のサイズ上限: エンジンの実行履歴・状態には上限があり、会話履歴やツールの大きな出力をそのまま状態に載せると上限を圧迫します。大きなデータは外部ストレージに置いて参照だけを状態に持つ、履歴は要約して持つ、という メモリと状態管理 の外部化原則がここでも効きます
冪等性と重複実行
再実行と重複は「起きるかもしれない」ではなく「必ず起きる」前提で設計します。再試行・再開・イベントの再配送(多くの仕組みは at-least-once 配送)のすべてが重複の源で、対策の軸は冪等性(idempotency)— 同じ操作を何度実行しても結果が変わらない性質 — の担保です。
- 受信側が冪等キーに対応する場合: 同じ業務操作の再試行には同じキーを使います。受信側がキーの記録と副作用を一体として扱い、重複時には同じ操作の結果を返す契約を確認します。キーの有効期間・スコープ・同じキーで異なる引数を送った場合の扱いも必要です(AWS の解説 )
- 自分の DB 内で完結する場合: キーの一意制約による重複排除と業務データの更新を、同じトランザクションで確定します。「未実行かを読む → 更新する」を分離すると、2 ワーカーが両方とも未実行と判断できます
- 外部 API が冪等キーに非対応の場合: 外部での成功後・ローカルの完了記録前に落ちると、結果が不明になります。事前チェックやローカルのロックだけではこの隙間を閉じられません。業務 ID による結果照合を行い、成否を確定できるまでは自動再送を止めて人間へ引き継ぎます(エラー処理・リトライ設計)
- 承認イベントの重複排除: 承認・差し戻しにも一意 ID を持たせ、処理済み ID の記録と状態遷移を同じトランザクションで行います。イベントの重複排除と、承認後に呼ぶ外部 API の冪等性は別に設計します
キーはジョブ ID + ステップ ID などから作れますが、再試行ごとに作り直してはいけません。ジョブを作り直す再受付でも同じ業務操作なら同じキーへ対応させ、別の操作には新しいキーを発行します。耐久実行エンジンのチェックポイントだけで、任意の外部副作用が 1 回限りになるわけではありません。
承認待ちの永続化
Human-in-the-Loop 設計で設計した承認ゲートを長時間タスクに組み込むには、「待ち」自体を永続化する必要があります。プロセス内の sleep やメモリAgent が「覚えている」ように見えるデータの総称。短期記憶(コンテキスト)・作業状態・長期記憶(外部ストレージ)の 3 層に分けて設計する。上の待機フラグは、再起動で消えます。
設計する要素は 4 つです。
- 待機状態の永続化: 「何を・誰の承認で・いつから待っているか」を状態として保存し、プロセスを占有せずに待ちます(耐久実行エンジンはこれをプリミティブとして提供します。前掲の対応表)
- 通知とリマインド: 待ち状態を作ったら承認者に通知し、放置に対してはリマインドを送ります。「気づかれない承認待ち」は事実上の障害です
- タイムアウトの設計: 無期限の待機を作らないでください。期限超過時の挙動(代理承認者へのエスカレーション / 自動キャンセル / 縮退実行)を決め、タイマーとして実装します
- 監査記録: 誰がいつ何を承認・却下したかを、タスクの実行記録と紐づけて残します
進捗の可視化
非同期にした瞬間、「今どうなっているのか」が見えなくなる問題が生まれます。利用者と運用者の両方に対して可視化を設計します。
- 状態モデルを明示する: ジョブは「受付済み / 実行中(ステップ n/m)/ 承認待ち / 完了 / 失敗 / キャンセル」のような有限の状態を持たせ、API・UI から照会できるようにします
- 進捗イベントを流す: 長い沈黙は失敗と区別が付きません。ステップ完了ごとに進捗イベントを発行し、UI へはストリーミングLLM の応答を生成と同時に逐次受信・表示する方式。Agent の体感品質を左右する基本技術。・ポーリングで届けます(ストリーミングと Agent の UX 実装パターン)
- スタック検知: 「実行中のまま進捗が止まっている」を検知する仕組み(ハートビート・最終進捗からの経過時間アラート)を用意します。承認待ちの滞留も同様に監視します(可観測性とトレーシング)
- キャンセルを最初から作る: 長時間タスクには必ず「途中でやめる」操作が必要になります。キャンセル時の後始末(実行中ステップの扱い・部分結果の保存)まで含めて設計します
非同期ツール API と耐久実行を分ける
非同期ツール使用LLM が外部機能の呼び出し要求を出し、アプリケーションまたはプロバイダーの実行基盤が検証・権限に従って実行し、結果をモデルへ返す仕組み。実行主体と制御点を区別する Agent の中核機構。(async tool calling)は「モデルが待機中にも別の作業をする」機能です。たとえば 2026-09-10 確認の OpenAI Astra は async: true のツールに対応し、アプリは結果を元の call_id に返します。これは、ワーカーの再起動後も処理を復元する耐久実行の保証とは別です。
ジョブ側には、呼出し ID、外部操作の冪等キー、引数の版、結果、キャンセル要求、承認の対象を永続化します。指示変更後に古い処理が完了した場合は、新しい計画へ使えるかを判定します。承認待ちに入る前後で対象や引数が変わったなら、以前の承認を流用しません。WebSocket の継続処理を使う場合も、切断時の復旧と外部操作の結果照会を自分たちのジョブ契約で定義します。
実務での注意点
アンチパターン
- 長時間タスクをプロセス内の sleep・メモリ上のフラグで待たせる → デプロイ・再起動のたびに待機中のタスクが消える → 待機状態を永続化し、プロセスを占有しない待ちにする
- チェックポイントなしで多段タスクを回す → 終盤の失敗で全ステップやり直しになり、時間とトークン費用を二重に払う → ツール実行完了・フェーズ境界でのチェックポイントを最初から入れる
- 再開時に完了済みの LLM 呼び出しを再実行する → 毎回違う計画で動き直し、済んだはずの副作用も重複する → 完了済みステップの結果を保存・再利用し、副作用には冪等キーを付ける
- 承認待ちに期限を付けない → 承認者の異動・見落としでタスクが永遠に滞留し、待機中の権限・データだけが残り続ける → タイムアウト + エスカレーション + 自動キャンセルを設計する
- 実行中インスタンスを残したままオーケストレーションのコードを変更する → 再開・再現が壊れ、長時間タスクが不定状態で失敗する → ワークフローのバージョニング戦略(旧定義の並存)を用意してからデプロイする
チェックリスト
- 非同期化の判断(p99 実行時間・承認の有無・やり直しコスト)を根拠付きで行った
- チェックポイントの単位(ツール完了・フェーズ境界)と保存内容が定義されている
- 継続再開と要約再開を区別し、思考ブロック・未完了ツール・キャッシュの契約を壊さずに復元できる
- 完了済みステップ(LLM 呼び出し含む)の結果が再開時に再利用される
- 副作用ごとに受信側の冪等契約または同一 DB 内の原子性が確認され、結果不明時の照合・自動再送停止条件がある
- 同じ業務操作の再試行でキーが維持され、承認イベントの重複排除も原子的に行われる
- 承認待ちが永続化され、通知・リマインド・タイムアウト・監査記録がある
- ジョブの状態モデルと進捗イベントが利用者・運用者から見える
- スタック検知(進捗停止・承認滞留)とキャンセル操作がある
- 実行中インスタンスとコード変更の共存方法(バージョニング)が決まっている
関連トピック
- メモリと状態管理 — 作業状態の外部化という本記事の土台
- Human-in-the-Loop 設計 — 承認ゲートの設計(本記事はその永続化)
- エラー処理・リトライ設計 — 呼び出し単位の失敗処理・冪等性の基礎
- デプロイとスケーリング — キュー・ワーカー・状態ストアのインフラ側
- ストリーミングと Agent の UX 実装パターン — 進捗をユーザーに届ける実装
- バージョニング・デプロイ・モデル更新追従 — 実行中インスタンスとコード変更の共存
- 常駐エージェントのライフサイクル管理 — 長期間動き続ける個体の劣化・世代交代・退役
- チャットを超える UI — バックグラウンド実行の UX 面(投げて後で受け取る)
参考資料
-
Async tool calling — 本文の仕様例(アクセス日: 2026-09-10)
-
Making retries safe with idempotent APIs(AWS Builders’ Library) — クライアント指定キー、更新とキー記録の原子性、遅延した再試行の扱い(アクセス日: 2026-09-10)
-
Understanding Temporal — 耐久実行の定義と Event History・replay・Activity の関係(アクセス日: 2026-07-06)
-
Restate: AI use cases — LLM 呼び出しの耐久化と Durable Agents / HITL 承認のパターン集(アクセス日: 2026-07-06)
-
LangGraph: Persistence — フレームワーク側のチェックポイントと interrupt による HITL(アクセス日: 2026-07-06)
-
Durable agents with Microsoft Agent Framework — 会話状態の永続化と状態サイズ上限という制約の実例(アクセス日: 2026-07-06)