コンテンツにスキップ

Dry-run & Commit / Plan-then-Apply|差分提示してから実行

一言で(TL;DR)

副作用を伴う操作を一気に実行せず、まず「何が変わるか」の差分(plan)を生成して提示し、人間またはバリデータの承認を得てから実際に適用(commit/apply)します。Terraform の plan → apply、データベースのマイグレーション・プレビュー、Git の diff → commit と同じ二相プロトコルをエージェントのツール呼び出しに適用するパターンです。

解決する問題

LLM はハルシネーションにより意図しないパラメータを生成する場合があります(F4)。さらにツール呼び出しは副作用を伴い(F8)、一度実行すると取り消せない操作も多いです。この2つが掛け合わさると、エージェントがハルシネーションしたパラメータで不可逆な副作用を実行し、ロールバック不能な障害を引き起こすリスクが生まれます。

dry-run なしで直接実行するシステムでは、人間が「エージェントが何をしようとしているか」を事前に確認する手段がなく、問題は事後にしか検出できません。本パターンは実行前に計画を可視化する層を挟むことで、ハルシネーションや誤パラメータを副作用の発生前に捕捉します。

選定条件(When to use / When NOT)

  • 使う条件
    • [reversibility] が低い操作(データ削除、外部API書き込み、課金処理、インフラ変更など)をエージェントが実行します。
    • [failure_cost] が中〜高で、誤操作が金銭的・法的・運用的な実害を生みます。
    • ユーザーまたは自動バリデータが差分を評価できるだけの [latency_budget] があります(概ね数秒〜数分の待機が許容されます)。
  • 使わない条件(=代替に倒す)
    • すべての操作が読み取り専用の場合です。C2 Read-Free / Write-Gated の Read-Free 層だけで十分であり、dry-run は不要です。
    • [reversibility] が高く [failure_cost] も低い操作(チャット応答生成、ログ出力など)の場合です。二相のオーバーヘッドが利益を上回るため、直接実行して構いません。
    • レイテンシ制約が極めて厳しく承認待ちが許容されない場合です。C4 冪等コマンド で安全に自動実行し、事後に監査する方式を検討します。

判定の目安として、[reversibility] が低いか [failure_cost] が高い場合のいずれかに該当すれば、本パターンの適用を検討すべきです。両方に該当する場合は必須と見なしてよいでしょう。

駆動変数とチューニング(程度)

目盛り 効かなすぎ ⇔ 効きすぎ 決め方 [駆動変数] 目安(出発点)
dry-run 対象の範囲 不可逆操作が素通り ⇔ 読取操作にも承認を求めUX劣化 [reversibility] が低い操作だけを対象にする 書込・削除・外部API変更・課金系を対象。読取は対象外
差分の粒度 粗すぎて判断不能 ⇔ 細かすぎて認知負荷過大 [failure_cost] が高いほど詳細な差分を提示 変更前後の値、影響行数、ロールバック手順の3点を含める
承認方式 自動承認で意味なし ⇔ 全件人間レビューでボトルネック [failure_cost] × [reversibility] で層別化 高リスク=人間承認、中リスク=ポリシー自動検証、低リスク=自動承認
plan の有効期限(TTL) 長すぎて状態乖離 ⇔ 短すぎて承認前に期限切れ [latency_budget] と外部状態の変化速度で決める 概ね5〜30分。状態変化が速い環境ほど短く

相反における立ち位置(相反)

  • F-15 読取専用 vs 書込可能 → hybrid(計画は読取、実行は承認後の書込)。本パターンは F-15 のハイブリッド戦略を具体化したものです。dry-run フェーズではシステムを一切変更せず読取のみで差分を計算し、commit フェーズでのみ書込を行います。[reversibility] が低いほど dry-run の網羅性を上げ、[failure_cost] が高いほど承認ゲートを厳格にします。

構造

flowchart TD
  Req[エージェント: ツール呼び出し要求] --> DR[Dry-run エンジン]
  DR --> Diff[差分 / Plan 生成]
  Diff --> Present[差分を提示]
  Present --> Gate{承認ゲート}
  Gate -->|却下| Feedback[フィードバック → エージェント再計画]
  Gate -->|承認| Validate[事前検証: スキーマ・ポリシー]
  Validate -->|違反| Feedback
  Validate -->|通過| Commit[Commit: 副作用実行]
  Commit --> Audit[実行結果を監査ログへ]
  Audit --> Result[結果をエージェントに返却]
  Feedback --> Req

実装メモ

dry-run / commit の最小実装(擬似コード):

def execute_tool(agent_request: ToolRequest) -> ToolResult:
    # Phase 1: Dry-run(副作用なし)
    plan = dry_run(agent_request)
    # plan には変更前後の値、影響範囲、ロールバック手順を含む

    # Phase 2: 承認
    approval = request_approval(
        plan=plan,
        ttl=timedelta(minutes=15),
        mode=select_approval_mode(agent_request.risk_level),
    )
    if not approval.granted:
        return ToolResult(status="rejected", reason=approval.reason)

    # Phase 3: Commit(plan が TTL 内かつ前提条件が変わっていないか確認)
    if plan.is_expired():
        return ToolResult(status="expired", reason="Plan TTL exceeded, re-plan required")
    if not plan.preconditions_still_hold():
        return ToolResult(status="stale", reason="State changed since plan creation")

    result = commit(plan)
    audit_log.record(plan=plan, result=result, approval=approval)
    return result

落とし穴:

  • plan と commit の間に状態が変わる問題(TOCTOU)があります。plan 生成時の前提条件を commit 時に再検証します。Terraform が plan ファイルにステートハッシュを埋め込むのと同じ発想です。前提条件が崩れたら plan を再生成します。
  • dry-run が完全でない場合があります。外部APIが dry-run モードを提供していない場合は、パラメータ検証とシミュレーションで代替します。この場合、差分の「推定」であることを明示します。
  • plan の肥大化に注意してください。大量の変更を1つの plan にまとめると、レビューが困難になります。操作単位を適度に分割し、1つの plan が概ね10件以下の変更に収まるようにします。
  • 承認のバイパス防止が必要です。commit エンドポイントが plan ID なしで呼べてしまうと、dry-run を迂回できます。commit は必ず有効な plan ID と承認トークンを要求する設計にします。

効かせる力学(forces)

  • F4(ハルシネーション): LLM が生成したパラメータを直接実行せず、まず差分として可視化します。ハルシネーションによる異常値(存在しないリソースID、桁違いの金額など)を承認者またはバリデータが副作用の発生前に検出できます。
  • F8(ツール副作用): 副作用の実行を承認後の commit フェーズに限定することで、意図しない変更のリスクを大幅に低減します。plan が却下された場合は副作用がゼロのまま再計画に戻ります。

関連・代替

  • B1 決定論的な殻: 殻が dry-run / commit の判定ロジック(どの操作に dry-run を要求するか)を担います。本パターンは殻の中の副作用管理に特化した具体化です。
  • C2 Read-Free / Write-Gated: 読取と書込の権限分離です。本パターンは Write-Gated の「ゲート」を「差分提示→承認」という二相プロトコルとして実装したものです。
  • C4 冪等コマンド: commit フェーズでの安全網です。承認後の実行が冪等キー付きであれば、ネットワーク障害時のリトライでも重複実行を防げます。
  • E1 リスクベース承認: 承認ゲートのリスクレベル判定ロジックです。本パターンの承認方式を「全件人間」から「リスクに応じた段階的承認」に洗練します。
  • G2 エンドツーエンドトレース: plan 生成・承認・commit の各フェーズをトレーススパンとして記録し、事後監査を可能にします。

コーディングエージェント向け指示(machine-actionable)

このパターンを人間に提案するなら、同時に以下を提案/確認します:

  • [ ] dry-run 対象の操作一覧を [reversibility] から導き、なぜその操作を対象にするかを理由付きで示したか
  • [ ] 承認方式(人間 / ポリシー自動 / 自動)を [failure_cost] のレベル別に設計したか
  • [ ] plan の TTL を [latency_budget] から導き、TOCTOU 対策(前提条件の再検証)を設計に含めたか
  • [ ] commit エンドポイントの直接呼び出し防止(plan ID + 承認トークン必須)を設計したか
  • [ ] 不可逆な副作用があるなら E1 リスクベース承認 を併置したか
  • [ ] commit の冪等性を C4 冪等コマンド で担保する設計を含めたか
  • [ ] dry-run / 承認 / commit の各フェーズを G2 トレース で記録する設計を含めたか
  • [ ] 目盛り(上表)の値を [駆動変数] から導き、理由を添えて提示したか