はじめに

先日、Acsim 開発に使用している query-db というスキルを postgres-mcp 前提の構成から、Deno ベースの read-only query フローに刷新しました。

これは単に使うツールを入れ替えた話ではありません。「手作業が多い運用タスクを、Agent Skills でどう扱うべきか」というタスクの設計方針の延長にある変更です。 すべてをエージェントの自律性に丸投げするわけではなく、エージェントが自律的に判断する部分とコードで厳密に守る部分をうまく分けることで、柔軟かつ安全な運用ができるのではないか、というのがこの取り組みの根底にある考え方です。

Agent Skills はその境界面として非常に使いやすく、この考えのもとに、Acsim では Agent Skills でできることを広げている最中です。 今回の query-db はその具体例にあたります。


背景: 複数環境を横断するDB調査の煩雑さ

Acsim は SaaS として staging / production の環境を持つのに加え、セキュリティ要件の高いお客様向けにシングルテナント環境を複数運用しています。エラー調査や顧客からの要望によるデータ確認では、これらの環境をまたいで DB (Amazon RDS) を参照する必要がある場面が少なくありません。

今までは PostgreSQL MCP サーバーである postgres-mcp を使って DB に接続していました。しかしこの postgres-mcp ベースの運用では、接続先を切り替えるたびに MCP の設定ファイルを書き換える必要がありました。 staging で調べたあとに production を見て、次はシングルテナント環境を確認して、という流れの中で、毎回設定を変更するのは手間が大きい。加えて、設定ミスで意図しない環境に接続してしまうリスクもあります。

MCP の設定を複数個作ることにより複数 DB にアクセスできるようにしても良いのですが、MCP サーバーごとにツール定義がコンテキストに載るため、環境を増やすごとにコンテキストウィンドウの消費が大きくなるのが懸念です。

「たまに必要だが、手でやると煩雑」。こういった運用タスクこそ、エージェントベースでの改善が効きやすい領域だと感じていました。 そして「自分たちのニーズに合わせて本当に必要な仕組みを素早く DIY できること」。それが AI の上手な活かし方の一つであると考え、今回は自作することにしました。


問題意識: エージェントに全部任せたいわけではない

とはいえ、DB 操作をすべて自然言語で指示してエージェントに任せたいかというと、そうではありません。「production のデータを見たい」という指示の解釈や、どのテーブルをどういう条件で引くかといった判断はエージェントが得意な領域です。一方で、「本当に read-only な操作であること」「タイムアウトが適切に設定されていること」「危険な SQL が実行されないこと」は、人間の判断やエージェントの出力に依存すべきではありません。

ここで重要になるのが、エージェントの自律動作の部分とコードが担う決定論的な動作の部分の分離です。

  • エージェントの仕事 → ユーザーの調査意図を理解する。適切な SQL を組み立てる。結果を解釈して報告する。環境の切り替え判断をする。
  • コードの仕事 → SQL が安全であることを検証する。接続が read-only であることを保証する。タイムアウトを強制する。実行ログを記録する。

Agent Skills はこの分離をうまく実現できる仕組みでした。スキルの SKILL.md でエージェントへの指示を記述しつつ、スキルが呼び出す CLI ツール側でコードによる安全性を担保する。エージェントが柔軟に判断する層と、コードが厳密に制御する層を、自然に分けられます。


今回やったこと

プロファイルベースの接続切り替え

接続先の管理を <PROFILE>_AGENT_DB_URL という環境変数の命名規則に統一しました。

STAGING_AGENT_DB_URL=postgres://readonly_user:...@127.0.0.1:15432/...
PRODUCTION_AGENT_DB_URL=postgres://readonly_user:...@127.0.0.1:15432/...
FOO_BAR_AGENT_DB_URL=postgres://readonly_user:...@127.0.0.1:15432/...

プロファイル名は正規化されます(foo-barFOO_BAR)。エージェントが自然言語で受け取った環境名をそのまま渡せるようになっており、環境変数の解決はコード側が行うので、接続文字列の取り扱いをエージェントに見せる必要がありません。 .env にプロファイルを並べておけば、「foo-bar 環境で」と指定するだけで接続先が切り替わります。

ポートフォワーディングの分離

RDS は Private Subnet 上に配置しているため、ローカル環境から接続するためにポートフォワーディングが必要になります。この準備手順を db-port-forward という別スキルに切り出しました。

分けた理由は、関心の分離です。ポートフォワーディングはインフラの準備であり、クエリ実行とは責務が異なります。query-db スキルは「接続可能な状態であること」を前提にして、クエリの実行と安全性に集中します。エージェントが「staging に接続したい」と判断したとき、ポートフォワーディングが未設定なら db-port-forward スキルを案内し、接続準備が済んでいれば query-db で即座にクエリを実行する。この導線をスキルが持つことで、人が手順を覚えておく必要がなくなります。

スキルとしての再設計

query-db は MCP サーバーに SQL を投げるだけの薄い構成でした。今回はスキル内に Deno スクリプトを内包し、責務ごとにモジュールを分割しています。

.agents/skills/query-db/
├── SKILL.md          # エージェント向けの指示(いつ・どう使うか)
├── query-db.ts       # エントリポイント
└── src/
    ├── cli.ts            # 引数パース、実行モードの振り分け
    ├── profile.ts        # プロファイル名の正規化、環境変数からの接続設定解決
    ├── safe-sql-gate.ts  # AST ベースの SQL 検証(SELECT / EXPLAIN のみ許可)
    ├── query-runner.ts   # read-only transaction 内でのクエリ実行、監査ログ記録
    └── types.ts          # 型定義

SKILL.md がエージェント向けの指示を担い、残りの Deno スクリプトがコードによる安全性を担います。エージェントは SKILL.md に記載された手順に従って CLI を呼び出しますが、CLI の内部でプロファイル解決、SQL 検証、read-only transaction の実行、監査ログの記録が行われるため、エージェントはこれらの詳細を知る必要がなく、「どの環境で何を調べたいか」に集中できます。

余談ですが、こういった設計を AI に丸投げしても、狙った構造にまとまるとは限りません。 人間側が意図を持って設計する方が、意図通りの責務分離を素早く実現できるなと感じています。


設計の肝: 安全性はコードで担保する

本番環境の DB をエージェントに触らせる以上、安全性の担保は重要です。 なお、read replica に調査クエリを分離する構成も選択肢としてありますが、現時点ではインフラコストと運用負荷を踏まえて採用していません。その前提で production 参照時の安全性を担保するため、今回のガードレールをコードで実装しています。 今回は 4 層の安全対策で構成しています。

  1. DB 権限: 運用上接続には read-only 権限の DB ユーザーを使い、DB レベルで書き込み権限を持たせない
  2. read-only transaction: クエリごとに BEGINSET TRANSACTION READ ONLY → 実行 → ROLLBACK で閉じる
  3. タイムアウト: statement_timeout / lock_timeout / idle_in_transaction_session_timeout をトランザクション内で設定
  4. AST ベースの SQL gate: pgsql-parser で SQL を解析し、SELECT / EXPLAINANALYZE を除く)のみ許可
export const buildTransactionGuardrailStatements = (config: QueryDbConfig): string[] => [
  `set local statement_timeout = ${config.statementTimeoutMs}`,
  "set local lock_timeout = '1s'",
  "set local idle_in_transaction_session_timeout = '5s'",
  "set transaction read only",
];

重要なのは、安全性の本体は 1〜3 にある、という点です。AST ベースの SQL gate は「補助線」として位置づけています。

SQL gate があることで、明らかに危険なクエリ(DELETEUPDATE)は DB に届く前にはじけます。しかし、文字列や AST の検査だけで安全を完全に担保するのは困難です。だからこそ、DB 権限と read-only transaction が本質的なガードレールであり、SQL gate はその前段で早期に弾くためのフィルタという役割分担にしました。

SQL gate では、トップレベルに SELECT / EXPLAIN 以外が来たら拒否し、CTE 内に INSERT / UPDATE / DELETE が入れ子になっているケースも検出します。加えて dblinkpg_terminate_backend のような副作用を持つ関数も拒否対象にしています。

const forbiddenFunctionNames = new Set([
  "dblink", "dblink_connect",
  "pg_terminate_backend", "pg_cancel_backend",
  "pg_reload_conf", "set_config",
  "lo_import", "lo_export", "dblink_exec",
]);

さらに、すべてのクエリ実行は監査ログとして記録されます。タイムスタンプ、プロファイル、SQL のハッシュ、成否、行数、実行時間が JSONL 形式で残り、何が実行されたかを後から追跡できるようにしています。


なぜ Deno だったか

今回 Deno を採用した最大の理由は、チーム内にすでに Deno の導入実績があったことです。メンバーが使える前提技術を増やすのはコストが高いため、すでに経験のあるランタイムを選びました。

実務上のメリットとしては以下が大きかったです。

  • npm パッケージをそのまま使える: pgsql-parserpostgresnpm: 指定子で直接インポートできる。bundler やビルドステップが不要
  • コマンド一発で実行できる: deno run でそのまま動く。スキル内包の小さなツールにはこの手軽さが合っている
  • テストが組み込み: deno test でテストも完結する。CI に別途テストランナーを用意する必要がない

スキルに内包する CLI ツールは、アプリケーション本体と切り離された小さなスクリプトです。TypeScript で書けてビルドなしに動く Deno は、この用途にちょうどよい選択でした。


この変更で得られたもの

調査導線が単純になりました。 「どの環境でどう接続するか」をスキル群が知っているので、エージェントに「staging のユーザー数を確認して」と言えば、必要に応じて db-port-forward で接続準備を案内し、その後 query-db でクエリ実行まで進められます。

運用知識をスキルに寄せられました。 複数のシングルテナント環境が存在する Acsim では、環境ごとの接続方法や注意点が個人の記憶に依存しがちでした。これをスキルに落とし込んだことで、ドキュメントとしてではなく「実行可能な運用」として共有できるようになりました。セキュリティ要件の高い顧客環境の調査を安全に扱えるようになったのは、実務上の大きな改善です。

実際に、Acsim にはこの query-db を利用する別のスキルが複数あります。例えばエラーの調査時に、Sentry のイベント情報と DB のデータ状態を突き合わせるフローの中で query-db を呼び出します。スキル同士が連携することで、より複雑な調査タスクもエージェントが一貫して進められるようになっています。

一つのことを上手にこなすスキルを組み合わせて、複雑な調査にも対応する。とても UNIX 的で気に入っています。


おわりに

エージェント活用は「全部 AI に任せる」ことではありません。

DB 調査のようなタスクでは、環境選択やクエリ組み立てといった判断はエージェントが得意ですが、安全性の担保は人間の判断やエージェントの出力に頼るべきではありません。DB 権限、read-only transaction、タイムアウト、AST gate。これらをコードで固めた上で、エージェントには判断と実行の導線を任せる。この分担が、実用に耐える運用の形だと考えています。

Agent Skills はこの「判断はエージェント、安全はコード」という分担を自然に表現できる仕組みです。スキルの定義でエージェントへの指示を記述し、スキルが呼び出すコードで制約を強制する。この境界がはっきりしているからこそ、新しいスキルを追加するときにも「何をエージェントに任せ、何をコードで守るか」を意識的に設計できます。

複数環境の運用や調査系のタスクは、この設計と特に相性がよい領域です。今後も Acsim ではこの方向で Agent Skills の適用範囲を広げていく予定です。