AIエージェントに任せた作業で怖いのは、失敗が赤く出ることだけではありません。むしろ厄介なのは、エージェントの最終報告では「完了」と見えているのに、PRは作られていない、issueコメントだけ投稿されている、MCP toolの外部状態だけ進んでいる、監査ログには途中までしか残っていない、という状態ずれです。
この記事では、AIエージェントの state reconciliation を、実行後に必ず行う突合作業として整理します。途中停止後にどう安全にやり直すかは AIエージェント再実行設計ガイド、外部副作用をどう戻すかは AIエージェントrollback設計ガイド、実行中に何を記録するかは AIエージェント監査ログ設計ガイド、成果物の返し方を固定する話は AIエージェント出力契約設計ガイド に分けています。
今回はその後段にある、エージェントが言った状態と、実際のシステム状態を突き合わせる設計に絞ります。
先に結論: state reconciliation は「完了報告の検証」ではなく「状態台帳の突合」
私は state reconciliation を、次の5つを照合する作業として扱います。
| 見る対象 | 例 | ずれたときの危険 |
|---|---|---|
| planned state | 予定した変更、対象PR、対象issue、承認条件 | 作業範囲が勝手に広がる |
| observed state | tool result、command output、MCP response | 実行結果の読み違いに気づけない |
| persisted state | branch、commit、PR、issue comment、外部ticket | 「やったつもり」の副作用が残る |
| reported state | final answer、PR body、JSON output | 人間が誤った完了判断をする |
| audit state | trace、run id、tool call log、approval log | 後から説明できない |
ここを final answer の文章だけで判断しない方がよいです。OpenAI Agents SDK の docs でも、agents は tool、handoff、guardrail、session を含む複数 turn の状態を扱う前提になっています。さらに Result には new_items や guardrail result のような、ログや監査に使うべき実行中の材料があります。つまり、最後の自然文だけを真実にすると情報を捨てすぎます。
state reconciliation の目的は、エージェントを疑うことではありません。人間が受け入れる前に、状態の source of truth を決めることです。
retry、rollback、reconciliation を分ける
この3つを混ぜると設計が曖昧になります。
| 概念 | 問い | 主なタイミング |
|---|---|---|
| retry | 失敗した処理をもう一度実行してよいか | 実行中、失敗直後 |
| rollback | 起きた副作用を戻せるか | 事故時、却下時 |
| reconciliation | 現在の状態は予定と一致しているか | 完了前、再開前、引き継ぎ前 |
たとえば、エージェントが「PRを作成しました」と報告したが、実際にはcommitだけ作られてPRがないとします。これはretryではありません。いきなりもう一度作業させると、同じcommitを重ねたり、別branchを作ったりします。まず必要なのは、branch、commit、remote push、PR、issue comment、CI状態を見て、どこまで進んだかを突合することです。
同じように、外部APIに一部だけwriteされた場合も、最初にやるのはrollbackではありません。戻す前に、どのAPI callが成功し、どのcallが失敗し、どの状態が外部に残っているかを確定します。rollback plan はその後です。
最小の state ledger を作る
実務では、まず1 run につき小さな state ledger を作ります。大げさなDBでなくても、PR body、run artifact、workflow log、監査ログの1レコードで十分です。
agent_run:
run_id: agent_run_20260703_091500
goal: "Update docs and open a PR"
planned_state:
repository: nidoneko/HP
branch: article/example
files_expected:
- src/content/blog/example.md
- data/seo/keyword-plan.md
- public/llms.txt
- public/llms-full.txt
external_writes:
- github_pr_create
observed_state:
commands:
- name: pnpm seo:update-llms
status: passed
- name: pnpm run build
status: passed
tool_calls:
- tool: github.create_pull_request
status: succeeded
external_id: "PR#207"
persisted_state:
commit: abc1234
pr_url: "https://github.com/nidoneko/HP/pull/207"
reported_state:
final_status: ready_for_review
reconciliation:
status: matched
checked_at: "2026-07-03T09:40:00+09:00"
ポイントは、エージェントの「やりました」をそのまま保存しないことです。planned、observed、persisted、reported を分けるだけで、後から見たときに「何を意図し、何が実行され、何が外部に残り、何を報告したか」が追えます。
突合ルールは状態ごとに変える
state reconciliation は、全部を同じ方法で確認しません。状態の種類ごとに source of truth を決めます。
1. ファイル状態
ファイルは git を source of truth にします。
git status --short
git diff --stat
git diff --name-only origin/main...HEAD
git log --oneline -1
エージェントの最終報告に「3ファイル変更」と書いてあっても、実際のdiffが5ファイルなら、報告よりdiffを優先します。generated file がある repo では、生成元と生成物を分けて見ます。たとえばこのサイトなら、記事本体、data/seo/keyword-plan.md、data/seo/learnings.md、public/llms*.txt は役割が違います。
2. tool result
tool result は、成功/失敗だけでなく外部IDを残します。
{
"tool": "github.create_pull_request",
"status": "succeeded",
"external_id": "207",
"url": "https://github.com/nidoneko/HP/pull/207",
"idempotency_key": "agent_run_20260703_091500:create_pr"
}
外部IDがない成功ログは弱いです。PR、issue、ticket、deploy、Slack message など、外部状態を作るtoolは必ず外部IDとURLを残します。これがないと、再実行時に「もう作ったのか、まだ作っていないのか」を判断できません。
3. session / conversation state
OpenAI Agents SDK の session は、run 間の会話履歴を維持できます。一方で、docs は server-managed continuation と SDK-side session を重ねないよう注意しています。ここから分かるのは、session は便利な記憶であって、外部状態の台帳ではないということです。
私は session history を source of truth にしません。session は「何を話したか」の材料です。PRがあるか、commentがあるか、MCP taskが完了したかは、それぞれ GitHub、MCP server、監査ログ側で確認します。
4. long-running task
MCP の tasks 仕様では、task は durable state machine として扱われ、taskId、status、result retrieval、cancellation、related-task metadata が定義されています。これは state reconciliation と相性がよいです。
long-running tool call を使う場合、reconciliation では次を確認します。
- taskId が ledger に残っているか
- status が terminal か
- terminal result を取得したか
- cancellation 後に成功扱いしていないか
- related-task metadata が別操作と混ざっていないか
特に重要なのは、working や input_required を成功扱いしないことです。途中状態のtaskは「進行中」であって、完了ではありません。
完了前チェックを自動化する
私は agent run の最後に、次のような reconciliation step を必ず置きます。
Before reporting completion, reconcile state:
1. Compare planned files with git diff.
2. Confirm every external write has an external ID or URL.
3. Confirm every required verification command has a captured status.
4. Confirm pending approvals, pending MCP tasks, and failed tool calls are listed.
5. If any mismatch exists, report "needs reconciliation" instead of "complete".
この step は、エージェントの final answer に書かせるだけでは足りません。可能なら runner、CI、GitHub Action、workflow script 側でもチェックします。
たとえば PR 作成系の agent なら、完了条件をこう固定します。
completion_gate:
required:
- git_diff_not_empty
- commit_sha_present
- pr_url_present
- build_status_present
- external_write_list_present
fail_if:
- pending_approval_exists
- untracked_files_unexplained
- mcp_task_non_terminal
- final_report_without_pr_url
「完了しました」と言っているのに pr_url がないなら、完了ではありません。これは文章表現の問題ではなく、状態の不一致です。
ずれたときの分岐を決める
state mismatch を見つけた後の分岐も決めておきます。
| mismatch | 例 | 次の動き |
|---|---|---|
| planned > observed | 予定した検証が実行されていない | 追加検証 or 人間確認 |
| observed > reported | tool失敗が報告に出ていない | final report を修正 |
| persisted > planned | 想定外の外部writeがある | rollback候補として隔離 |
| reported > persisted | PR作成と報告したがPRがない | PR作成だけ再開 or 未完了報告 |
| audit < persisted | 外部writeの証跡がない | 監査ログ補完、次回からblock |
大事なのは、ずれを見つけた瞬間に全自動でretryしないことです。ずれの種類によっては、retryすると悪化します。たとえば、外部writeが二重投稿される、同じissueに複数コメントが付く、別branchにPRが増える、といった事故です。
私なら次の順にします。
- mismatch を分類する
- 追加で読むべき source of truth を1つだけ読む
- 外部writeが増える操作は止める
- 人間に
needs reconciliationとして返す - 再実行する場合は idempotency key と既存 external ID を渡す
実務で使う reconciliation prompt
エージェントに作業させる prompt へ、最初から reconciliation を含めます。
You must reconcile state before finalizing.
Track:
- planned files
- commands run and their outcomes
- external writes and their URLs
- pending approvals
- failed or skipped tool calls
Before final answer:
- compare git diff with planned files
- list verification results
- list every external URL created
- say "needs reconciliation" if any planned action is missing
- do not claim complete without a PR URL when the task requires a PR
この prompt は強いですが、万能ではありません。prompt injection を受けたtool resultや、壊れたMCP serverの結果をそのまま信用する危険は残ります。だからこそ、source of truth はtool result本文ではなく、Git、PR、task status、audit log のような検証可能な状態に寄せます。
よくある失敗
1. final answer を監査ログ代わりにする
final answer は人間向けの要約です。監査ログではありません。tool call、approval、external ID、verification result は別に残します。
2. session memory を状態台帳にする
session memory は会話の継続には便利ですが、外部状態の正規台帳ではありません。外部writeは外部IDで確認します。
3. success status だけを見る
workflow や agent run の成功は、アプリケーション上の目的達成と同じではありません。run がgreenでも、PRがない、buildを走らせていない、外部commentが重複している、という状態は普通に起こります。
4. mismatch を見つけた瞬間に自動retryする
retry は状態を増やします。reconciliation で状態を確定する前にretryすると、二重writeや枝分かれしたbranchを増やします。
最小導入手順
最初から大きな orchestration 基盤を作る必要はありません。私は次の順で始めます。
- agent run に
run_idを付ける - planned files と external writes を先に列挙する
- final answer に verification と external URL を必須化する
- PR body に
Reconciliationセクションを置く - pending approval / pending task / failed tool call があれば
completeを禁止する - mismatch の分岐表をチームの AGENTS.md や runbook に置く
この程度でも、AIエージェント運用の事故はかなり減ります。特に、PR作成、issue更新、MCP write、deploy request のような外部状態を作る作業では、state reconciliation を入れない方が危険です。
まとめ
AIエージェントの state reconciliation は、エージェントの出力を疑うための儀式ではありません。計画、tool結果、外部状態、報告、監査ログを同じ台帳で突き合わせ、人間が受け入れてよい状態かを判断するための設計です。
retry はやり直し、rollback は戻し方、reconciliation は現状確認です。この3つを分けるだけで、無人実行やMCP writeを含むワークフローはかなり扱いやすくなります。
私なら、最初は run_id、planned files、external write、verification result、PR URL だけから始めます。完了報告に必要な状態が揃っていないなら、遠慮なく needs reconciliation と返す。これを徹底する方が、便利なAIエージェントを安全な運用に近づけます。