Pizza、エージェントループをイベントログに置換
- •Pizzaは可変エージェントループを、真実の源泉となるEventStoreログに置き換える
- •Event行はメッセージ、ツール呼び出し、結果、ファイル編集、caused_byポインタ、thread_id値を記録する
- •著者はイベントログがフォーク、リプレイ、マルチエージェントのtellイベント、デバッグを支える一方、データベース上のトレードオフがあると述べた
Tomは8月18日、Dev.toへの投稿で、多くのAIエージェント実行環境がなお脆い`while (true)`ループに依存していると主張した。LLMが可変の`state`から計画し、ツールが動き、stateが更新され、`isDone(state)`がtrueを返したときだけループが終了する仕組みだ。Claude Code、Codex、Pi、多くのオープンソースエージェントをこの基本骨格を共有する例に挙げ、デモではうまく動くが、長時間の利用では実務上の欠陥が出ると述べた。
投稿によれば、主な問題は1つの可変stateオブジェクトが会話履歴全体を表そうとする点にある。ユーザーがターンの途中でプロセスを終了したり、ツールが停止したり、モデルが確認を求めたりすると、実行環境には未完了の反復が残る。ツール失敗には`try/catch`分岐、再試行の可能性、モデルへ戻すエラーメッセージ、追加のstate更新が必要になる。`read`や`grep`のような並列ツール呼び出しは、次の`llm.plan()`の前に作業を順番に処理するか、promiseを組み直すことをループに強いる。巻き戻しも難しい。可変stateでは、モデルが実際に何を見たのかを示したり、過去の地点から分岐したりしにくいからだ。
Tomは、自身の個人オープンソースエージェントPizzaが別の設計を使うと説明した。ログが真実の源泉であり、stateはそのログの投影にすぎない。すべてのメッセージ、ツール呼び出し、結果、ファイル編集は`EventStore`の1行になり、`sequence INTEGER PRIMARY KEY`、`event_id`、`type`、`payload_json`、`caused_by`、`thread_id`などのフィールドを持つ。実行環境はログの末尾を読み、次のイベントをハンドラーへ渡し、結果を再び追加する。これにより、1つのターンは別のループ反復ではなく、状態遷移になる。
Pizzaは同じログをUI、LLMコンテキスト、セッションツリーに使う。ユーザーはイベントを調べて何が起きたかを確認し、過去の`sequence`から新しい`thread_id`を開始して会話を分岐し、イベントを再適用してリプレイできる。この設計では、過去のメッセージ、編集、ツール呼び出しが数日、数週間、数年にわたって検索可能なまま残るため、硬い「新規チャット」の境界もなくなる。Pizzaは`read_file`、`write_file`、`grep`、`git`のような多数の個別ツールではなく、1つの`cli`ツールを公開する。組み込みコマンドは構造化ハンドラーへ送り、`grep`、`sed`、`git`、`npm`、`python`、`ls`などのコマンドはユーザーのシェルへ渡す。
投稿によれば、Pizzaのイベントモデルは、すべてのメッセージが`caused_by`ポインタを持つため、Gitのようなセッションツリーを作る。TUI、デスクトップアプリ、JSON-RPCサーバー、ワンショットCLIはいずれも同じ`SessionFacade`イベントストリームを消費する。Pizzaエージェントはワークスペース間で`tell`イベントを送ることもできる。ワークスペースBが自分のログ内でタスクを処理し、プロジェクトファイルやコンテキストをワークスペースAへ漏らさずに結果を返す仕組みだ。任意で有効にする`pizza-self-optimization`スキルは、ローカルのイベントログを読み、Pizzaリポジトリをフォークし、バグを再現し、テストを書き、PRを開ける。
トレードオフは、データベースコスト、リプレイコスト、ログサイズ、スナップショット化である。Tomは、数千イベントを含むセッションではコンテキスト再構築が遅くなり得ると述べ、定期的なマテリアライズドスナップショットが次に欠けている要素だとした。`while(true)`はチュートリアルの標準として有用なままだが、EventStoreは長時間動くエージェントにおいて、フォーク、リプレイ、マルチエージェント協調、デバッグを通常のデータベース操作にする、とTomは述べた。