前回、Root AIが600秒考え続けてタイムアウトした話を書きました。
そのときに直したのは、AIが何を考えるかだけではありません。
もう一つ、かなり重要な問題がありました。
タイムアウトしたAIに、もう一度やらせるとき、何を「もう一度」やらせるのか。
一見すると単純な話です。
失敗したならretryすればいい。
APIでもジョブキューでも、retryという仕組み自体は珍しくありません。
でも、自分専用のAI基盤を作っていると、この「retry」という一語の中に、性質がまったく違うものが混ざっていることに気づきました。
AIにもう一度考えさせることと、AIが決めた処理をもう一度実行することは、同じではありません。
むしろ、ここを同じものとして扱う方が怖いです。
600秒後に、そのまま続きをやらせていいのか
前回の記事で触れたRoot AIのタイムアウトでは、最初は別の問題も残っていました。
AIのturnが600秒でタイムアウトすると、Run自体はいったん止まります。
ところが当時の実装では、タイムアウトしたturnそのものが完全には片付いておらず、App Serverのclientやprocessも残る可能性がありました。
つまり、こちらから見ると、
「600秒経ったので失敗しました」
なのですが、AI側の内部状態まで本当にきれいに終わっているとは限らない。
その状態で「もう一度」と言うと、前回の残骸を持ったまま続きを始める可能性があります。
実際、P35の検証中には、Rootが
turn_timeout:graph:no_items
で停止したあと、手動でresumeしたところ、今度はすぐに
turn_not_completed:failed:other
で止まるケースがありました。
このとき、Task GraphもChildもprovider callもcapability actionも発生していません。
外部への書き込みもありませんでした。
つまり、「何かを二重実行してしまった」という事故ではありません。
ただし、一度壊れたreasoning sessionをそのまま再利用するのは安全ではないということはかなり明確になりました。
そこで、timeoutや継続不能なturn failureの場合には、そのreasoner sessionを無効化するようにしました。
次にOwner側から明示的にresumeされたときには、同じRootを維持しながら、新しいCodex threadから考え直します。
Rootそのものを複製するわけではありません。
System TruthやControl Planeに保存された状態も維持します。
捨てるのは、安全に続きを考えられないAI側の会話状態だけです。
失敗したら全部捨てればいい、でもない
ここも少し面白いところでした。
何かエラーが起きたら、毎回sessionを全部捨てて最初からやり直せば安全そうに見えます。
でも、それも違いました。
たとえば出力されたJSONがschemaに合っていなかっただけなら、threadそのものはまだ正常かもしれません。
その場合まで毎回新しいthreadにしてしまうと、本来保持できた推論文脈まで失います。
そこで現在は、
この失敗は同じthreadで考え直してよいのか、それともthread自体を信用してはいけないのか
を分けています。
timeoutやturn_not_completedのような継続不能な失敗ならsessionを捨てる。
一方で、再利用可能なvalidation failureならsessionを残す。
「retryする/しない」ではなく、その前に何を再利用してよいのかを判定する必要がありました。
ここまでは「もう一度考える」の話
ただ、これより怖いのが実行側です。
AIにもう一度Task Graphを考えさせるだけなら、外の世界にはまだ何も起きていない場合があります。
少なくとも今回のRoot timeoutではそうでした。
しかし、AIがすでにtoolを呼び出した後だったら話が変わります。
たとえば、
WordPressに記事を公開する。
SNSへ投稿する。
GitHubのPRをマージする。
本番環境へdeployする。
メールを送る。
こうした処理でtimeoutが発生したとします。
ここで、
「返事が来なかったから失敗した」
とは限りません。
処理そのものは成功していて、結果を返す途中だけ失敗している可能性があります。
この状態でもう一度同じ命令を送れば、記事が2本できたり、SNSが二重投稿されたり、同じ操作を二度実行したりする可能性があります。
分散システムでは昔からある問題ですが、AIエージェントにtoolを持たせると、急に身近な問題になります。
「考え直す」と「やり直す」を同じretry loopに入れない
自分の中では、このあたりからretryの設計がかなり変わりました。
AIのreasoning側で失敗したのであれば、必要に応じてsessionを捨て、新しいthreadで同じRootについて考え直せばいい。
一方、外部への実行が始まった後は、
まず前回の処理が実際にどうなったか確認する。
それができないまま、自動的にもう一度実行しない。
この境界が必要です。
実際、今回のRoot recoveryでも一貫して、
no automatic retry
no duplicate Root
を安全条件として残していました。
自動で頑張って復旧する方が一見賢そうですが、どこまで実行されたか分からない状態で勝手にもう一度動くAIは、あまり安心できません。
止まるところでは止まった方がいい。
そのうえで、前回の状態を確認してから続きを決める。
AIエージェントでは「失敗」の意味が一つではない
普通のプログラムなら、
成功した。
失敗した。
という二値で考えたくなります。
でもAIエージェントを作っていると、その間にかなり多くの状態があります。
AIは考え終わっていない。
AIは考えたが、構造化出力だけ壊れている。
AIのturnは壊れたが、外部処理はまだ始まっていない。
toolを呼び出したが、結果を受け取れていない。
外部処理は成功したかもしれないが、その確認ができていない。
こうなると、単純な
except: retry()
では扱えません。
特に怖いのは、
「失敗した」のではなく、「成功したかどうか分からない」状態です。
この状態でのretryは、reasoningのretryとはまったく別物です。
AIに自律性を与えるほど、retryは慎重になる
AIエージェントを作り始めたころは、止まったAIを自動で復旧できればできるほど、自律性が高いと思っていました。
今は少し逆に考えています。
自律的にretryできることよりも、
自分が今retryしてよい状態なのかを判断できること
の方が重要です。
考えるだけなら、新しいthreadでもう一度考えればいいことがある。
でも現実世界に一度触れた後は、そう簡単ではない。
前回何が起きたのか。
どこまで確定しているのか。
同じ操作をしても二重にならないのか。
そこを確認できて初めて、「もう一度実行する」が選択肢になります。
AIエージェントのretry設計で一番怖いのは、失敗することそのものではないのかもしれません。
考え直しているつもりで、実は現実世界の操作までやり直してしまうこと。
だから今のPersonal AI Platformでは、「もう一度考える」と「もう一度実行する」を、できるだけ別のものとして扱うようにしています。
retryを賢くするというより、
何を勝手にretryさせないかを決める。
前回の「何を考えさせないか」と、少し似た話になりました。


コメント