自作のエージェント基盤で企業向けアプリを5本出荷—何が壊れたのか

Dev.to / 2026/5/3

💬 オピニオンDeveloper Stack & InfrastructureSignals & Early TrendsTools & Practical UsageModels & Research

要点

  • 著者は、Kubernetes(またはローカル開発向けのdocker-compose)で動作するオープンソースのマルチエージェント基盤Abenixを出荷しており、ワンコマンドで全スタックを起動できる。
  • Abenixには、意思決定分析、観光のKPI、Javaベースの請求審査、産業IoTなどの“実運用風”のエンドツーエンド企業向けショーケースアプリ5本が組み込まれ、実戦的な制約で基盤を試せる設計になっている。
  • Abenixは、ノートPCとクラスタで同一のコードパスを使って環境ごとの分岐を避けること、そして“おもちゃデモ”ではなく実ドメインのアプリを主な検証対象にすること、という主要な制約を中心に作られている。
  • 記事はn8n、LangChain、CrewAI、Dify、Flowiseなど既存ツールとの直接比較ではなく、ビルドの記録として位置づけられており、特にエージェントループの考え方など既存の成果に触発された点を強調している。
  • 開発の過程で「何が壊れたか」とその学びに焦点があり、厳密なJSONスキーマ出力、タブularツールと組み合わせたRAG、Javaを含むSDK対応、Kubernetes Job経由で実バイナリを実行して結果を読み戻すといった運用・検証手法が例示されている。

ここ数か月、オープンソースのマルチエージェントプラットフォームであるAbenix
外に向けて(オープンな場で)作り続けてきました。MITライセンスで、Kubernetes(または
ラップトップ開発用のdocker-compose)上で動作し、その上に5つの完全に作り込まれたアプリが付属しています。
これにより、実際のエージェントプラットフォームがエンドツーエンドでどのようなものかを確認できます。
単一のコマンドでスタック全体が立ち上がります:

bash scripts/dev-local.sh             # ラップトップ、~5-7分
bash scripts/deploy-azure.sh deploy   # AKS、~35分
 github.com/sarkar4777/abenix — MIT、Python + TS + Java SDK、
KEDAでスケール、Postgres + Neo4j + Redis + NATS、全12個のコンテナイメージ、
5つのショーケースアプリ。

技術的な話に入る前に、僕がずっと聞かれている問いがあります。n8n / LangChain / CrewAI / Dify / Flowiseが
既にあるのに、なぜこれを作るのか? これは妥当な問いですし、答えは本当に前向きです。
それらのプロジェクトはそれぞれ、狙っている領域に対して素晴らしく、そしてAbenixは
LangChainがエージェントループの姿を示してくれなければ存在しなかったでしょう。だから
この記事は比較バトルではなく、制作ログです。

なぜ作ったのか:最初に置いた制約
3つの制約が、あらゆる設計判断を駆動しました:

  1. ラップトップで動かせる軽量さと、クラスタで動かすのに十分な堅牢性——同じコードパス。
    bash scripts/dev-local.sh が、プロダクションが実行するのとまったく同じエージェント、KBコレクション、
    そしてパイプラインを立ち上げられるようにしたかったのです。ただし helm ではなく docker-compose で。
    「本番ではKafkaを使うが、ローカルは配列」みたいな分岐は無しです。シードスクリプト、
    SDKクライアント、キー整合(key reconciler)、そして移行システムは、
    どちらのモードでも同一の手順で動きます。

  2. おもちゃのデモではなく、5つの本物のアプリをテストの土台にする。
    僕は企業と仕事をしています(保険会社、エネルギートレーダー、観光局、製薬のコールドチェーン)。
    各領域には、それぞれ鋭いエッジがあります。だからこのリポジトリには、
    制作するプラットフォームにそれぞれ固有の課題を解かせることで、
    「このようなものを社内で作れる」ことを企業にデモできるように、
    5つの異なる“プロダクション向けの形”をしたアプリを出荷することにコミットしました:

OracleNet — 7エージェントの意思決定分析パイプライン(Historian、Stake- holder Sim、Contrarian、Synthesizer…)が、6つのタブを持つDecision Briefを生成。テストでは並列マージのオーケストレーション+JSONスキーマの厳密な出力。
Saudi Tourism — Vision-2030のKPIをCSV/PDFで、NLQチャット、5つのプリセットを持つシミュレータ。テストではRAG+表形式ツール+レポート生成。
ClaimsIQ — Java/Vaadinによる請求査定(claim-adjudication)で、ライブなSSE DAG。Java SDKのテスト(はい、Javaです。なぜなら、実際に請求部門が動かしているのはJVMだから)+マルチモーダルな写真入力。
Industrial-IoT — ポンプの予知保全+コールドチェーンの製薬。テストするのはコード資産(code-asset)プリミティブです:パイプラインは実際のGoバイナリをk8sのJobとしてデプロイし、結果を読み戻します。つまりエージェントはDSPを実行せず、計算を行うためにGoプロセスをスケジュールします。
ResolveAI — パーソナKBを用いたケース管理、先例の検索、そして承認ティア(承認段階)ポリシー。actAs(customer_id)の委譲、ポリシーに基づいた解決、SLA違反のスイープ(総点検)をテストします。

もしプラットフォームが退行したら、5つすべてがそれぞれ異なるやり方で退行します。
真似(偽装)しにくい。

  1. 退屈なエンタープライズ向けの土台(配管)をまず第一級にする。単なる後付けにしない。
    規制された買い手の調達(procurement)を通すのに必要な機能は、デモの見せ場ではありません——
    それは監査トレイル、ツールの許可リスト(tool allow-list)、マルチテナントの分離、そしてコスト台帳(cost ledger)です。
    だからそれらは拡張機能ではなく、プラットフォームのコアプリミティブにしています。詳細は後述します。

目立つもの——僕がそれから離れていないときに、思わず取りたくなる(そして恋しくなる)もの
正直に言うと、必要なら他の場所で作り直すだろうと思うようなものがここにはあります。
なぜなら、一度手に入れると、それなしで生きたくなくなるからです:

actAs(subject_id) — プリミティブとしての委譲
すべてのエージェント実行には、APIキーだけでなく“行為主体(acting subject)”が付きます。
ResolveAIが、customer_42によって開かれたケースに対してポリシー調査(policy-research)エージェントを起動するとき、SDKはこう呼びます:

forge.execute(
    "resolveai-policy-research",
    message=ticket_text,
    act_as=ActingSubject(subject_type="resolveai", subject_id="customer_42"),
)

実行の行には acting_subject_id が持ち運ばれます。
(knowledge_search のような)データを読むツールは、プラットフォームのサービスアカウントの権限ではなく、
その“主体”の許可をコレクションに対して確認します。
悪いエージェントでも、ユーザーが見られないデータへエスカレーションして辿り着くことはできません。
なぜなら、エージェントは主体に対して永続的にスコープされているからです。

バージョン管理されたYAMLとしてのパイプライン。ブート時にlintチェック

slug: oraclenet-synthesizer
output_schema:
  type: object
  properties:
    confidence: { type: number, minimum: 0, maximum: 100 }
    stakeholders:
      type: array
      items:
        properties:
          sentiment: {enum: [positive, negative, neutral] }
    risks:
      type: array
      items:
        properties:
          severity: {enum: [low, medium, high, critical] }

scripts/lint-agent-seeds.py はデプロイ時(Phase 4)に実行され、
pipeline_configが不正にネストされていたり、ツール名がレジストリに存在しないようなシードYAMLを拒否します。
YAMLの読み込み時にバグを見つける方が、モデルがseverity: "extreme" のような値を返した後の
午前3時で見つけるより1000倍安いからです。

検証(validation)だけでなく、正規化(normalization)を伴う出力スキーマの強制
検証だけだと、最初のあいまいなenumが出た時点でプロダクションがクラッシュします。
エンジンは、すべてのエージェント出力に対して post_process.py のステップを実行します:
output_schemaに対して検証し、既知のズレを正規化します(混在 → neutral、extreme → critical)、
そして SSEの完了イベントに validation_warnings を出力します。UIは決してクラッシュせず、
ズレは可視化されます。なのでフロントエンドを壊さずに、プロンプトをより厳密にできます。

エージェントタイプごとのKEDAキュー深度オートスケーリング
エージェントによってコストプロファイルは異なります。oraclenet-synth- esizer は遅くて重い。resolveai-triage は速くて安い。
それらは別々のエージェント実行用プールで動き、それぞれ独自のKEDAスケーラを持ちます。

返却形式: {"translated": "翻訳されたHTML"}

(agent-runtime-default, agent-runtime-chat, agent-runtime-heavy- reasoning, agent-runtime-long-running)。シンセサイザーのキューが
詰まったときにスケールするのは、そのプールだけです — チャット
トラフィックは自動スケーリングの課金対象になりません。

ツールレジストリにシード時の許可リスト
すべてのツールは apps/agent-runtime/engine/tools/ で宣言されます。各
エージェントの seed YAML には、使用できるツールがどれかが宣言されています。lint の検査では、レジストリにないツールを呼び出そうとする
エージェントを拒否します。エージェントは文字通り、シード時に許可リストに入っていない何かを呼び出せません — 「メールツールを使ってパスワードを私に送って」といった
プロンプトインジェクションであっても、エージェントがアクセスできないツールを見つけることには
つながりません。

コードアセットのプリミティブ — パイプラインは実バイナリをデプロイ
これは他のどのエージェント基盤でも見たことがありません。エージェントのパイプライン
のノードは、入力として zip 化された Go(または Node、Python、Rust、Ruby、Java)プロジェクトを
受け取り、k8s Job としてデプロイし、構造化入力で実行し、
構造化出力を読み戻すことができます:

- id: pump_dsp
  type: code_asset
  asset_id: pump-dsp-corrector
  inputs:
    rpm: 2400
    samples: "${windows[i].vibration}"
  outputs: 
    fault_scores: object

Industrial-IoT のポンプパイプラインでは、これを FFT + ベアリング共振の
解析(Go)に使用し、次に fault_scores を診断用の LLM エージェントへ
連鎖させます。実コードはサンドボックス化され、プラットフォームによりスケジュールされ、
その結果がエージェントの推論へスレッド(スレッド処理)として
返ってきます。

1つのスタック:エージェント + Atlas(グラフ)+ KB(ベクタ)+ ツール
Atlas はプロジェクトの名前付きエンティティ / オントロジーのグラフ(Neo4j をバックに持つ)で、
KB はドキュメントコレクションのストア(pgvector)です。エージェントは両方を
ツール経由で呼び出します(atlas_describe、atlas_traverse、knowledge_search)。ほとんどの
プロジェクトではこれらを3つのベンダから組み合わせますが、ここではテナントの
スコープ、権限モデル、デプロイ経路を共有します。

3つのランタイムの SDK
Python — 正統派(canonical)。API + すべてのスタンドアロン API
で使用。TypeScript / Node — すべてのスタンドアロン Web フロントエンドで使用。
Java — ClaimsIQ(Vaadin)で使用。6段階の裁定パイプライン + ライブ SSE DAG 表示により実証済み。
同じ actAs、同じ待機セマンティクス、同じ実行用の行の形(row shape)。もし
企業のスタックが JVM なら、Python に書き換える必要はありません。

セルフチェック用エンドポイント + 冪等なブートストラップ
GET /api/agents/{slug}/self-check は次を返します:

{
  "agent": "oraclenet-synthesizer",
  "checks": {
    "no_top_level_keys_leaked_into_model_config": "ok",
    "pipeline_has_nodes": "ok",
    "pipeline_nodes_well_formed": "ok",
    "model_declared": "ok",
    "tools_registry_loadable": "ok",
    "tools_all_known": "ok",
  }
}

エージェントが壊れた?ログの末尾を追ってデバッグする代わりに、50ms で理由が分かります。

1コマンドでデプロイ(冪等)

フェーズ 0 — SDK のドリフト事前確認(5 つの vendored コピー vs 正統派ハッシュ)
フェーズ 1 — RG + ACR + AKS の準備
フェーズ 2 — ビルド + 12 イメージのプッシュ
フェーズ 3 — Helm インストール + alembic による head へのアップグレード
フェーズ 4 — エージェント、ユーザー、ポートフォリオのスキーマ、KB、スタンドアロンキーをシード
フェーズ 5 — Ingress(nip.io のマジックドメインで DNS に悩まない)
どのフェーズでも失敗したら、同じコマンドを再実行します。すべてのステップが
冪等です。各アプリごとにキーを発行 + ローテーションし、k8s シークレットへ反映する、
スタンドアロン API キーのレコンシラーも含めて冪等です。

エージェント・エコシステムの他の部分との関係
私は、この領域のすべてのプロジェクトが好きです。どれも
ゼロサムの意味では競合ではありません — 異なるオーディエンスと、AI プロジェクトの異なる
フェーズを狙っています。Abenix を含めて、それぞれが
購買判断のどこに当てはまるかについて、私の考え方はこうです:

やりたいこと、そして(たぶん)その用途を満たすものは…
LLM 呼び出し + ツールを組み立てるライブラリ。そこから完全に自作する —> LangChain / LlamaIndex
50 行でのマルチエージェント役割ごっこプロトタイプ —> CrewAI / AutoGen
オペレーション + 自動化のためのビジュアルワークフロービルダー。多数の統合 —> n8n / Make / Zapier
ビジュアル LLM アプリビルダー。セルフホスト可能。RAG-first —> Dify / Flowise
既存の LangChain アプリのためのホスティング型 LLM 観測性 + トレーシング —> LangSmith
エンタープライズ基盤 + おそらくカスタマイズ用のプレイグラウンドも:マルチテナント、監査証跡、KEDA スケール、マルチ言語 SDK、YAML としてデプロイパイプライン、コピーして使えるリファレンスアプリ —> Abenix ← 私が作ったもの

私が関わる多くのチームでは、1つではなく、結局そのうち2つを使うことになります。
Abenix のエージェントの中に LangChain。Abenix のエンドポイントを呼び出す n8n。
LangSmith が Abenix の実行トレースを指し示しました。プラットフォームは
共存するために争う必要はありません。

私は、Abenix を特に最下段の行のために作りました — エンタープライズが「いいプロトタイプだ。
次は、5つのチームが共有するものとして、セキュリティ審査のもとで
本番対応にしてくれ」と言う、その瞬間です。私がずっと埋めたかった
ギャップはそこでした。

本番でのクローズまでの戦争物語
それを見つけるのに 1 週間かかり、この領域のプラットフォームが本番で
不安定に感じられる理由の 80% を説明できるバグ。

数か月前に KEDA のキュー深度スケーリングを追加しました。つまり
POST /api/agents/{id}/execute は async-by-default になる必要がありました — すぐに
{execution_id, mode: "async"} を返して、ワーカーにキュー処理を任せます。ブラウザクライアントには、ノード進捗の
ライブ SSE ストリームが届くようにしました。

Python SDK は、即時レスポンスから data["output"] を読み続けていました。
しかしそれは空でした。結果として、スタンドアロンアプリはすべて空の
エージェント応答を受け取りました。JSON のパースが爆発し、API は 500 を返し、UI には
汎用の「agent failure(エージェント失敗)」トーストが表示されました。

修正:サーバ側の待機(wait)パラメータを三値化し、SDK 呼び出し側(API キー認証)ではデフォルトを True、
ブラウザ呼び出し側(cookie 認証。ライブストリーム用の UI があります)ではデフォルトを False にしました。SDK は
いま wait: true を送るようになり、サーバがまだ async-mode を返してくる場合は
ポーリング /api/executions/{id} へフォールバックします。

さらに、フェーズ 0 のデプロイゲートも追加しました — scripts/sync-sdks.sh --check
は、すべてのデプロイの先頭で実行され、canonical な SDK のハッシュを 5 つの
vendored コピーと比較し、ドリフトが検出された場合は進行を拒否します:

返却形式: {"translated": "翻訳されたHTML"}

✓ in sync: packages/agent-sdk/abenix_sdk
✓ in sync: contractiq/api/sdk/abenix_sdk
✓ in sync: industrial-iot/api/sdk/abenix_sdk
✓ in sync: resolveai/api/sdk/abenix_sdk
✓ in sync: sauditourism/api/sdk/abenix_sdk
✓ All 5 SDK copies in sync with canonical.
噛まれた後で、この配管(plumbing)だけ追加しました。

あまり良くない点
現時点ではマネージド・クラウドのオプションがありません。K8S と VM のインストールは主に対応済みです。ご自身の AKS/GKE/EKS を持ち込むか、ラップトップで動かしてください。
Atlas + KB のグラウンディングは、一部の OOB エージェントには組み込み済みですが、すべてではありません。
KB ドキュメントのシーダー(seeder)は現在ノーオペレーションです。コンテンツは Cognify(チャンク化 + 埋め込み)を通りますが、シーダーはまだそれを駆動していません。Collections とエージェントの権限付与はシードで問題ありません。コンテンツはアップロード UI または POST /api/knowledge/collections/{id}/documents 経由で届きます。
Probes(プローブ)≠ tests(テスト)。全てのショーケースアプリの全リンクを辿ってスクリーンショットを撮る Playwright のプローブを6つ用意しています — 役には立ちますが、「スモークテスト + スクリーンショット」で、あまり広範なユニットテストではありません。現状あるものに加えて、もっとユニットテストが必要です。

あなたにとってのメリット
エージェントを作っている、または軽量な“自作のエージェント基盤”を作る遊びをしたい、かつ本番への引き渡しがそろそろ手痛くなってきているなら。このリポジトリは「さて、
エージェントと残りの自分のプロダクトの間の“継ぎ目(seam)”って実際どう見えるの?」を6か月かけて確かめてきたものです。尖った部分にしようとしたのが
私が試した5つの
ショーケースアプリです。フォークして、「insurance」を
あなたのドメインに置き換えて、出荷してください。

*PR は歓迎します。 *

Repo: https://github.com/sarkar4777/abenix
単一コマンドでのデプロイ: bash scripts/deploy-azure.sh deploy または
bash scripts/dev-local.sh

これを土台に何かを作ったり、実際のバグを見つけたら issue を開いてください — 私は
全て読みます。