PRODUCT NOTE / AI エージェント・技術

社内 AI エージェントの設計

素の API 1 往復から始めて、いまは Python 約 17 万行・ツール数十本・テーブル百数十種・定時ジョブ数十本・管理画面十数枚で動いている社内エージェントの記録。


この文書は「何ができるか」ではなく、どこで形が壊れ、だから何をどう決め直したかを残した開発ノートです。同じ題材を通す側・止める側から見たノートが対になります。

ステータス実運用中(社内・非公開)。題材として書籍にまとめた Vol.3『走りながら描く AIエージェントの設計図』は発売中止、Vol.3.5 として作り直し中(刊行未定)
規模Python 約 17 万行 / ツール数十本 / テーブル百数十種 / 定時ジョブ数十本 / 管理画面十数枚 / プロンプト約 16 万字。この数週間だけでも数百件規模の変更が入るペースで動いている
入口呼ばれ方は 7 通り、判断は 1 か所。同期で 3 秒以内という制約は非同期の形で吸収する
このノートの範囲設計の判断だけ。実コード・DDL・図・固有名は載せない(理由は 匿名化 の判断に書いた)
最終更新2026-10-03
関連Vol.3.5『走りながら描く AIエージェントの設計図』 ・ AI エージェント案件の進め方(PM 観点) ・ エージェント実行テレメトリ(実測)
TL;DR

ひとことで

エージェントの芯は 1 本のループでしかない。

難しくなるのは数が増えてからで、壊れる場所はだいたい決まっている ——ツールの説明文が毎ターンの費用になる/入口ごとに判断が分裂する/同じ事実が三重になる/走らなかったジョブは鳴らない/検査が通ったまま結論だけ逆を向く/対策を入れたつもりが本番には一度も効いていなかった。

このノートは、その 6 つに対して何を決めたかを残す。設計図は成果物ではなく、現場で更新し続ける道具として扱っている。

Problem

背景・解きたい課題

このシステムは、要件定義書から始まっていない。現場に張り付いて他人の一日を分で数え、手が止まっている場所から作るものを決めた。その進め方は速いが、代償が 1 つある —— 図が 1 枚も無いまま約 17 万行まで来た。

以下は、この 4 つに対して「どこまで図を描き、どこで線を引いたか」の記録です。

Core Loop

芯は 1 ターン、増えるのはその外側

最初に決めたのは「何を足すか」ではなく「何を芯に置くか」だった。芯は素の API 1 往復で、モデルを呼ぶ・結果を読む・ツールを実行する・もう一度呼ぶの 1 本のループだけ。ここに機能を書かない。

17万
Python 行数(概数)
数十
ツール本数
百数十
テーブル種別
7
呼ばれ方(入口)
十数
管理画面の枚数
16万
プロンプト字数(概数)

数え方: 題材システムの実測値を概数へ丸めたもの。内訳(行数・ツール・エンドポイント・テーブル・ジョブ・画面)は書籍側の付録が持つ。

増えるのはループの外側 ——ツール・入口・状態・画面・ジョブの 5 方向。この 5 つが、それぞれ別の壊れ方をする。

Solution & Scope

最初に描く図を 3 枚に絞る

背骨の 3 枚(これが無いと次の判断ができない)

  • 1 ターンのフロー図 — どこでモデルを呼び、どこで止まるか
  • ER 図 — どの事実をどこに置くか
  • 承認境界の構成図 — 人が必ず手を入れる位置

描かないと決めたもの

  • 網羅的なクラス図(増やす直前に、その範囲だけ描く)
  • シーケンス図の全パターン(1 ターンのフローで足りる)
  • インフラ構成の全体図(壊れ方の説明に使えなかった)
  • 将来構想の図(更新されないので嘘になる)

図を網羅しようとすると止まる。「これが無いと次の判断ができない」を基準に 3 枚だけ先に描き、残りは必要になった箇所で描いて捨てる、という扱いにした。

Decision Log

主要な意思決定ログ

各判断の裏にある「なぜ」を、決定・仮説・結果の 3 点で残す。うまくいかなかったことも書く。

境界 ツールの説明文を「ドキュメント」として書くのをやめた
DECISION

ツール定義の説明文を、毎ターン課金されるプロンプトの一部として扱うことにした。説明文には「いつ呼ぶか・いつ呼ばないか」だけを書き、引数の意味や背景の解説は外のカタログへ出す。

失敗時の戻り値は文章で説明せず、全ツールで同じ型に固定した。

WHY / 仮説

ツールが 1 個のときは説明文の長さが問題にならない。数十本になると、説明文の総量がそのまま毎ターンの費用になる。

さらに、長い説明を足した箇所のほうが選択を誤りやすかった。条件を書き足すほど、モデルが読む分岐が増えるからだと考えた。

=説明文は「読み物」ではなく「選択のための最小の手がかり」に寄せるべき、と判断した。

RESULT
ツールカタログ(名前・用途・引数・失敗時の戻り値)を人間向けに別管理へ分け、モデルへ渡す定義はテンプレート 1 枚に固定した。失敗の戻り値を揃えたことで、ループ側の例外処理が 1 か所に集まった。
入口 呼ばれ方は 7 つに広げ、判断は 1 か所に寄せた
DECISION

入口(チャット・Webhook・定時起動・管理画面・外部連携など)は広げてよい。ただし「何をするか」を決めるコードは 1 か所しか置かない。

入口は受け取って正規化するところまでで終わり、判断には関与させない。

WHY / 仮説

入口ごとに判断を書くと、同じ修正を 7 回することになり、必ずどれかが取り残される。取り残されても動くので、気づくのは「あの入口からだと挙動が違う」と言われたときになる。

入口が増える速度のほうが、直す速度より速かった。

RESULT
入口 7 つに対して判断 1 か所に落ちた。副作用として、入口ごとの差(認証の強さ・返せる情報の範囲)を「正規化の段で落とす」形に統一できた。
3 秒 同期で 3 秒という制約に、機能を削って合わせるのをやめた
DECISION
一部の入口に「3 秒以内に応答を返さないと切れる」制約がある。ここに合わせて処理を削るのではなく、受付だけ返して、結果は後から別経路で届ける非同期の形に寄せた。
WHY / 仮説
制約に合わせて削ると、入口ごとに機能差が出る。機能差は「判断は 1 か所」という決めと正面からぶつかる。制約は入口の性質で、判断の内容とは関係がない —— なら吸収すべきは入口側だと考えた。
RESULT
遅い処理は全入口で同じ扱い(受付 → 後で通知)になった。代わりに「いつ終わるか分からない処理が増える」という別の問題が出たので、進行状況を人が見る先として管理画面を足している。
状態 同じ事実を三重に持たないと決め、現物の DDL から ER 図を起こした
DECISION

テーブルが百数十種になってから、散らばった DDL を機械的に集めて ER 図を後付けで起こすことにした。

そのうえで「同じ事実(誰が・いつ・何を)を複数の場所に持たない」を規則にし、例外として許すキャッシュは「消しても再生できるもの」に限った。

WHY / 仮説

同じ事実を三重に持つと、食い違ったときにどれが正かを判断する場所が無い。そして食い違いはエラーとして出ない。

出力の品質がなんとなく落ちる、という形でしか見えないので、原因として疑うまでに時間がかかる。手で ER 図を描き直すのは現実的でないので、現物から起こす側に倒した。

RESULT
収集スクリプトで ER 図を起こし、4 群に割れた。三重化していた箇所はこの時点で初めて目に見えた。以後、新しいテーブルを足すときは「この事実はどこが正か」を先に決める手順にしている。
欠測 「走らなかったこと」を鳴らす仕掛けを別に作った
DECISION
ジョブの失敗通知とは別系統で、「所定の時刻までに走った記録が無い」を検出する側を作った。検出の対象はジョブそのものではなく、ジョブが残すはずの記録の有無。
WHY / 仮説

失敗は鳴る。走らなかったことは鳴らない。

定時ジョブが数十本になると、止まっていることに気づく経路がどこにも無い。実際に、止まっていたことに気づいたのは「最近この数字が動かない」と言われてからだった。

失敗通知と同じ系統に作ると、系統ごと落ちたときに両方黙る。

RESULT
欠測の検出が通知の主役になった。失敗通知のほうは件数が多くて読まれなくなっていたので、結果として見る場所が 1 つに減った。
送信口 判定を真偽の二値でなく三値にし、UNKNOWN を送信口で止めた
DECISION
外へ出る前の判定を STOP GO UNKNOWN の三値にした。UNKNOWN は通さず、送信口で止めて人に回す。
WHY / 仮説

二値にすると「判定できなかった」が GO 側に混ざる。混ざり方が静かで、検査は通り、ログは成功、根拠として挙げた材料も正しいのに、結論だけが逆を向いて外へ出ていく。

この壊れ方は緑のまま起きるので、監視では見つからない。判定できなかったことを、失敗とは別の状態として持つ必要がある、と考えた。

RESULT
UNKNOWN を人の確認へ回す経路を作った。止まる件数は増えたが、外へ出てから気づく件数が減った。止めた件のログが、プロンプトに注意書きを足す代わりの材料になっている。同じ三値の考え方は、のちに別の通知の優先度判定にも転用した。
委譲 1 体に積むか複数へ割るかを、性能ではなく承認の線で決めた
DECISION
分割の基準を能力や性能に置かず、「誰宛の結果か」に置いた。自分宛(社内の依頼者自身に返るもの)は自動で通す。他人宛(第三者に届くもの)は必ず人が承認する。
WHY / 仮説

モデルは変わる。性能を基準に分割すると、モデルが変わるたびに線を引き直すことになり、そのたびに安全側の設計が緩む方向へ動く。

承認の線は性能が上がっても動かない —— 誰に届くかはシステムの性能と無関係だから。

RESULT
承認境界の構成図が、背骨の 3 枚目として固定された。モデルの入れ替えで見直す対象が「精度の期待値」だけになり、権限の設計を触らずに済んでいる。
移植 許可を足すとき、対になる禁止も一緒に移すことにした
DECISION
新しいふるまいを許可する文を足すときは、それと対になる禁止の文を同じ場所に書く。見つかった漏れには、形式的な除去と、言い換えられた場合の検出を重ねる、二層の防御を持たせた。
WHY / 仮説
許可する文だけを新しい場所に足し、対になる禁止の文を移し忘れたことがあった。許可された範囲の中で、本来は答えてはいけないことまで答えてしまった。
RESULT
許可と禁止を近くに書く運用に変え、漏れを機械的に取り除く層と、自然な言い回しに変えられた場合を拾う層を重ねた。
所在 サーバーが答えをすでに持っている判断は、モデルに委ねないことにした
DECISION
出す・出さないをサーバー側がすでに決められる種類の制御は、モデルの生成に委ねない。生成物からは無条件に取り除き、必要ならサーバー側が機械的に足す。
WHY / 仮説
モデルに判断させると、同じ条件でも出たり出なかったりする。サーバーはすでに判定材料を持っているのに、わざわざ不確実な側に判断を預ける理由が無かった。
RESULT
対象の制御をサーバー側の決定的な処理に移した。先に生成を始めてから差し込む形にしたところ、取り除くはずのものがまるごと消える副作用が1つ見つかり、合わせて直した。
転用 既存の仕組みを新しい使い道に転用するとき、前提を書き出すようにした
DECISION
ある目的のために作った仕組みを別の目的に転用するときは、転用前にその仕組みが置いていた前提を書き出して確認する。
WHY / 仮説
定期処理向けに作った仕組みを常時監視に転用したとき、前提の1つを確認しないままにしたために、ある期間まるごと機能しない状態が続いた。失敗は毎回記録されていたが、失敗を飲み込んで動き続ける設計だったので誰も気づかなかった。
RESULT
転用のたびに前提を書き出す手順を加えた。あわせて、鳴らす対象の欠測検知に「動いた記録」だけでなく「狙った成果が出ているか」も加えた。
検出 検出するだけで止める力を持たない対策は、本番で動いていないのと同じだと考え直した
DECISION
同種の対策を複数回足したが、どれも検出してログに残すだけで、実際の出力を変える力を持っていなかった。既定を「検出したら機械的に書き換える」側に倒し、入れた対策が本番の設定に実際に反映されているかを別途確認する工程を持つことにした。
WHY / 仮説
検出だけの対策は、有効にし忘れても何も起きないので気づく手段が無い。同じ種類の対策を複数回足していたのに、どれも本番の設定には一度も反映されていなかったことが分かった。
RESULT
既定で本文を書き換える方式に変え、過去に反映され忘れていた分もまとめて本番へ入れた。「直したか」ではなく「本番で効いているか」を確認する工程を、以後の作業に組み込んだ。
匿名化 書籍 Vol.3 を発売中止にし、このノートは判断だけに留めた
DECISION

題材を特定できる記述を載せない、と決めた。書籍 Vol.3 は発売中止にして Vol.3.5 として作り直し中(刊行未定)。

このノートも同じ方針で、実コード・DDL・図・固有名は 1 つも置かない。規模は概数まで丸める。

WHY / 仮説

社名・人名・製品名は 1 つも書いていなかった。それでも実コードとスキーマを並べれば、題材のリポジトリは特定できる。

行数・テーブル数・ジョブ数のような数字も、組み合わせれば同じ働きをする。「実名が無いから大丈夫」は判定として成立していなかった、と考え直した。

RESULT

残すものを「判断と、その理由」だけに絞った。図とコードを外したぶん説明力は落ちるが、判断は題材が違っても当てはめ直せるので、残す価値があるのはこちら側だと整理した。

書籍側は体裁を教科書に組み直している。

Traps

気づきにくい壊れ方

上の判断は、どれも「静かに壊れたあと」に決めている。先に知っておきたかった順に並べる。

静かに壊れる 3 つ
  • 走らなかったジョブ — 失敗は鳴る。走らなかったことは鳴らない。記録の有無を別系統で見る。
  • 三重化した事実 — 食い違いはエラーにならず、出力の品質としてしか現れない。
  • 緑のまま出ていく結論 — 検査は通り、根拠も正しく、結論だけが逆を向く。二値の判定では捕まらない。

もう 1 つ、壊れ方ではないが効いたものとしてプロンプトの扱いがある。約 16 万字は設計した量ではなく、事故のたびに足した注意書きの総和だった。

足すほど費用が上がり、読む分岐が増えて選択が悪くなる。いまは「注意書きを足す」前に「送信口で止める」か「ツールの戻り値で表す」で解けないかを先に見る順にしている。

実行時に何にどれだけ渡しているかは、実測の側で数えた。

Roadmap / Open Questions

これから

References

関連

題材は社内で運用しているシステムです。社名・人名・製品名・リポジトリ名は記載していません。規模の数字は概数へ丸めてあり、実コード・スキーマ・図は掲載していません。記述は設計上の判断の整理であり、特定の構成を推奨するものではありません。