トラス構造が幾何学的に組まれた建築物のファサード
技術解説

AIエージェント定義をDBに置くapowerbの落とし穴と復旧設計

目次を見る

AIエージェント(自動でタスクを処理するAIプログラム)を複数チーム・多数エージェントで運用しているSREやインフラ担当者に向けて、エージェント定義の保存場所という一見小さな設計判断が可用性にどう響くかを整理します。エージェントをGitリポジトリではなくデータベースの行として管理するapowerbというOSS基盤を例に、状態の二重管理がもたらす障害モードと、その復旧の仕組み化について見ていきます。

apowerbはApache-2.0ライセンスで公開されているオープンソースの基盤で、FastAPIとGoogle ADK(Agent Development Kit、Googleが提供するエージェント構築用SDK)の上に構築されています。Docker Composeでセルフホストできる点も特徴です。

何が起きるか

通常のエージェント開発では、モデル指定・ツール接続・指示文(プロンプト)をPythonファイルに書き、Gitでコミットしてデプロイします。この方式はレビュー可能でテストしやすい一方、非エンジニアがプロンプトの文言を直したいだけの場面でもリリースが必要になります。エージェントが12チーム40体規模になると、この「文言修正=デプロイ」という構造がボトルネックになります。

apowerbはこの構造を反転させます。エージェントの定義(モデル・ツール・サブエージェント・ガードレール・出力スキーマ)はPostgresの1行として保存され、変更はUPDATE文で完結します。ランタイムがインポートするPythonファイルは、実質7行程度のスタブ(骨組みだけのファイル)で、内容を持たない生成物です。

ここで起きる落とし穴は、ファイルシステム上のagents_pool/ディレクトリとデータベースの内容が一致しなくなる「ドリフト(状態のズレ)」です。ボリュームだけを復元してデータベースを戻し忘れた場合、環境間でディレクトリだけ同期漏れした場合、パッケージ名を変更した際に古いスタブが残った場合。原因は複数あっても、ユーザーがそのエージェントに話しかけた瞬間にModuleNotFoundErrorが出る、という同じ症状に集約されます。

なぜ起きるか

原因を分解すると、根っこにあるのは「派生状態(derived state)」という考え方です。データベースの行が正であり、agent.pyはその行から都度生成される派生物にすぎません。

派生物であるということは、生成元と生成物の間に同期のタイミングギャップが必ず存在するということです。apowerbでは、エージェント作成時にadk_agent_builder.pyがデータベース行を書き込むと同時に、対応するスタブファイルをディスクに書き出します。両者は本来同時に存在するはずですが、片方だけを操作する運用(バックアップ復元・環境間同期・手動でのファイル編集)が入ると、この前提が崩れます。

さらに厄介なのは、「ファイルが存在するが内容が古い」ケースです。単純なos.path.existsチェックでは、ファイルがあれば健全と判定してしまいます。しかしスタブの中身が旧パッケージ名をインポートしていたら、存在していても実行時に落ちます。これは一般的なキャッシュ無効化の問題と同じ構造で、「あるかないか」だけでなく「新しいか古いか」まで見ないと検知できません。

自分の環境で該当するか確認する方法

apowerbやこの種のDB駆動エージェント基盤を検討・運用している場合、次の観点で自分の環境を確認できます。

  • エージェント定義が単一のデータストア(PostgresなどのRDB)に集約されているか、それとも設定ファイルとDBに分散しているか
  • バックアップ・リストア手順が、データベースとファイルシステム(永続ボリューム)の両方を同じタイムスタンプで復元しているか
  • CI/CDやIaC(Terraform・Pulumiなどのコードでインフラを管理する仕組み)で環境を複製する際、生成物であるファイルまで一緒にコピーしていないか

実際にPostgresの中身を見る場合は、次のようなクエリでエージェントの定義行を確認できます。

psql -d apowerb -c "SELECT agent_id, agent_name, agent_type, agent_model FROM agents;"

この結果とディスク上のagents_pool/agent<id>/agent.pyの内容を目視で突き合わせ、インポート先のモジュール名が現行のパッケージ構成と一致しているかを確認します。不一致があれば、それはドリフトが発生している証拠です。

対策の手順

apowerb自身が採用している対策は、「信頼せず毎回検証する」という起動時リコンサイル(reconcile、状態を正しい形に揃え直す処理)です。ensure_agent_modulesという関数が起動シーケンスの中で呼ばれ、全エージェントのスタブファイルを走査し、欠落しているものだけでなく「古い(stale)」ものも再生成します。

判定ロジックは、生成済みスタブが必ず持つはずの単一のimport行を読み取り、それが現在のヘルパーモジュールのパスと一致するかを見る仕組みです。一致しなければ、ファイルが存在していても「壊れている」と判定して再生成します。

この設計を自分たちの運用に取り込む場合、次のようなチェックリストで進められます。

  • 環境起動時(コンテナ再起動時)に、派生ファイルを自動で再生成するフックを用意する
  • 「ファイルの有無」だけでなく「ファイルの内容が最新か」を検証するロジックを別に持つ
  • バックアップ手順書に、データベースとボリュームの復元を同一タイムポイントで行う旨を明記する
  • SLO(サービスレベル目標)の観点では、この種の再生成処理にかかる時間を「起動時レイテンシ」として計測し、エージェント数が増えたときのスケール特性を事前に把握する

オブザーバビリティ(システム内部の状態を外から観測できるようにする仕組み)の観点も重要です。ModuleNotFoundErrorが発生した瞬間だけをアラートにするのではなく、起動時のリコンサイル処理が「何件のスタブを再生成したか」をログやメトリクスとして残しておくと、ドリフトの発生頻度そのものを追跡できます。頻発するようであれば、バックアップ・デプロイの運用フローそのものに問題があると判断できます。

まとめ

エージェント定義をリポジトリからデータベースに移す設計は、非エンジニアによる変更の即時反映という利点を持ちますが、その代わりにファイルシステムとデータベースという二つの状態源を持つことになります。

二つの状態源がある限り、ドリフトの発生は避けられない前提として設計に組み込む必要があります。apowerbの起動時リコンサイルのように、「存在確認」だけでなく「内容の鮮度確認」まで行う復旧処理を用意しておくことが実務上の分岐点になります。

自分の環境でこの種の基盤を採用する、あるいは似た構成(DBに設定を持ち、起動時にコードを生成するアーキテクチャ)を運用しているなら、まずはpsqlでエージェント定義の行数とagents_pool/配下のディレクトリ数が一致しているかを確認してみてください。そこから、バックアップ手順とヘルスチェックの見直しを進められます。

参考

Agents in the database, not in the repo: a tour of apowerb

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。