0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PR: データブリックス・ジャパン株式会社
Omnigentによるメタハーネス入門(1)基礎編

Omnigentのコスト制御を設定して、予算が実際にどう効くか確かめた

0
Posted at

はじめに

2026年7月に、Databricksからオープンソースのメタハーネス「Omnigent」が公開されました。Claude Code、Codex、Cursorといった複数のコーディングエージェントの上に、共通の操作・ガバナンス層を置くという位置づけの製品です。

そのOmnigentのブログで、2026年9月3日にコスト制御の記事が公開されました。

ルーティング、段階的な予算、使用量トラッキングの3本立てで、LLMの支出を管理するという内容です。読んで気になったのは、「予算を設定する」と書かれているときに、実際には何が保証されるのかという点でした。上限に達したら止まるのか、止まるとしたらいつなのか、複数のエージェントを使っていたら合計で締まるのか。

設定して動かしてみたので、そのあたりを書いていきます。今回の検証はomnigent 0.14.0、macOSで行いました。

検証で確かめたかったこと

3つに絞りました。

  1. 1本のポリシー設定で、Claude CodeとCodexの両方に同じコスト制限をかけられるか
  2. ハーネスごとに個別に設定するより手間が減るか
  3. かかったコストを、ハーネスをまたいだ1つのビューで見られるか

いずれも、単体のコーディングエージェントでは扱えない範囲です。Claude Codeには、Codexが今日いくら使ったかを知る手段がありません。ここがメタハーネスという層を置く理由になっているはずなので、その主張が実際に成立するかを見ます。

検証環境を作る

ポリシーはサーバー設定ファイルに書く

Omnigentのポリシーは3階層あります。

階層 設定する人 設定場所 評価順
セッション 利用者 UIまたはチャットで依頼 1番目
エージェント仕様 開発者 エージェントのYAML 2番目
サーバー全体 管理者 サーバー設定YAML、または管理者用REST API 3番目

今回使うのは3番目のサーバー全体です。すべてのセッションに効きます。

作業用のディレクトリに設定ファイルを作ります。

# server_config.yaml
policies:
  session_budget:
    type: function
    handler: omnigent.policies.builtins.cost.cost_budget
    factory_params:
      max_cost_usd: 0.50
      ask_thresholds_usd: [0.10, 0.25]
      expensive_models: ["opus", "gpt-5"]

金額は検証用にかなり低くしています。実際に閾値を踏まないと何も観測できないので、数ターンで到達する額にしておくのが要点です。

ここが最初のハマりどころでした。このファイルは --config で明示的に渡したときだけ読まれます。

omnigent server --config server_config.yaml

~/.omnigent/config.yaml にも同じ policies: キーを書けますが、そちらはサーバーの既定ポリシーとしては読み込まれません。ファイル名が同じなので混同しやすいところです。私は最初これに気づかず、「設定したのに何も起きない」状態でしばらく回していました。

なお、設定の書き方には2通りあります。

# factory_params 形式
session_budget:
  type: function
  handler: omnigent.policies.builtins.cost.cost_budget
  factory_params:
    max_cost_usd: 0.50

# function: {path, arguments} 形式
session_budget:
  type: function
  function:
    path: omnigent.policies.builtins.cost.cost_budget
    arguments:
      max_cost_usd: 0.50

この2つは等価です。同じポリシーに両方書くとエラーになります。ブログとドキュメントで使われている形式が違うので、設定例を参照するときは混ざらないよう気をつけてください。

2つのハーネスを同じサーバーにつなぐ

ターミナルを3つ使います。1つ目でサーバーを起動し、残りでハーネスを起動します。

# ターミナル1
omnigent server --config server_config.yaml

# ターミナル2
omnigent claude

# ターミナル3
omnigent codex

ポート、DB、アーティファクト保存先をすべて既定のままループバックアドレスで起動した場合、このサーバーはマシン共通のローカルサーバーとして ~/.omnigent/local_server.pid に登録されます。後続の omnigent claude はこれを再利用します。--port や --database-uri を明示すると専用サーバー扱いになり、再利用されません。検証中はオプションを足さないのが無難です。

Screenshot 2026-09-18 at 16.24.07.png

Webの画面 (http://localhost:6767) を開くと、Claude CodeとCodexのセッションが並んで表示されます。ここまで来れば、1つのサーバー設定が両方のセッションに効く状態です。

セッション単位の予算

ソフト閾値で承認を求められる

トークンを消費する依頼を投げていくと、ask_thresholds_usd に指定した金額を最初に超えたタイミングで承認を求められます。

Screenshot 2026-09-18 at 16.37.55.png

ターミナルにポップアップが出て [y] / [n] で答える形です。Escで閉じてもWeb UIから答えられます。[n] は「拒否してセッションを停止」なので、単に見送りたい場合とは操作が異なります。

一度承認すると、その閾値はセッション状態に記録され、再度は聞かれません。0.10ドルを承認したあと0.25ドルを超えたときは、0.25ドルの分だけが提示されました。拒否した場合は閾値が記録されないので、次に同じ閾値を超えたときにまた聞かれます。

同じことがCodex側のセッションでも起きます。ハーネスごとに個別の設定をしていないのに、両方で同じポリシー名の承認が出ます。 確かめたかったことの2番目は、ここで答えが出ました。

上限に達すると高いモデルがブロックされる

max_cost_usd に達すると、ターンがモデルに届く前にブロックされます。Claude Code側ではこう出ました。

UserPromptSubmit operation blocked by hook:
Blocked by the session cost-budget policy: spend $0.95 reached the $0.50 limit.
You've hit the $0.50 cost budget. High-cost models (opus, gpt-5) are blocked
over budget — switch to a cheaper model to continue.

Codex側は別の経路で止まります。

PreToolUse hook (blocked)
Blocked by the session cost-budget policy: spend $1.31 reached the $0.50 limit,
and tool calls are blocked while on a high-cost model.

Claude CodeはUserPromptSubmitフック、CodexはPreToolUseフックです。それぞれのハーネスが持つフック機構を使ってポリシーを評価しているので、止まる場所が違います。

メッセージにあるとおり、/model で安いモデルに切り替えれば作業を続けられます。実際にSonnetに切り替えたら通りました。max_cost_usd は「これ以上使わせない額」ではなく、「これを超えたら高いモデルを使わせない額」です。 切り替えたあともコストは加算され続けます。

なお、/model で切り替えたモデルは新規セッションの既定としても保存されます。一時的に下げたつもりが以降のセッションにも効くので、検証が終わったら戻しておくとよいです。

上限の検知はターンの境界で行われる

上の出力で気づいた方もいると思いますが、止まった時点の金額は上限の0.50ドルではありません。Claude Code側で0.95ドル、Codex側で1.31ドルでした。

これは、ポリシーがコストを見るタイミングによるものです。チェックが入るのはターンの境界と、ツール呼び出しの直前です。1つのターンの中で長く生成が続く場合、その間はチェックが入りません。当然といえば当然で、モデルが応答を生成している最中に割り込む仕組みではないためです。

私の場合、最初の1ターンで仕様書を書かせたところ3分ほど走り、終わった時点で0.95ドルになっていました。ターン開始時のコストは0.00ドルだったので、0.10ドルと0.25ドルの警告閾値も含めて、そのターンの中では一度もチェックされずに通過しています。

Codex側はターンの途中でツール呼び出しが挟まる分、警告閾値は発火しました。ただし0.30ドルで承認した直後のターンの中で1.31ドルまで進んでいるので、超過の幅はこちらのほうが大きくなっています。チェックの間隔が空くほど超過が大きくなるという点は、どちらのハーネスでも同じです。

これらは1回ずつの計測で、実行内容とモデルによって大きく変わります。倍率そのものに意味はありません。ただ、予算として設定する値は、守らせたい額よりかなり低くしておく必要があるという設計上の示唆は共通です。コーディングエージェントでは1ターンが長くなるのが普通なので、実務ではここが効いてきます。

expensive_models の指定で挙動が変わる

expensive_models は、上限に達したあとブロックする対象を指定するパラメータです。指定の仕方で挙動が変わります。

明示的にリストを渡した場合 (["opus", "gpt-5"])、モデルがOpus 5のとき:

High-cost models (opus, gpt-5) are blocked over budget —
switch to a cheaper model to continue.

パラメータを省略した場合、モデルがSonnetのとき:

All model calls are blocked over budget.

省略時は、安いモデルを含めてすべてのモデルがブロックされます。メッセージ自体が2通りに分岐しているので、どちらの経路を通ったかは実行時に判別できます。

expensive_models の値は、大文字小文字を無視した部分一致で判定されます。"opus" と書けば system.ai.claude-opus-5 にも当たります。

この省略時の挙動について、docs/POLICIES.md のパラメータ表と、factoryのdocstringおよびテストで記述が分かれていました。docstringとテストは「省略時は全モデル停止」で一致していて、今回の実測もそちらと同じです。ドキュメント側を直す形になると考えて、issueとして報告しています。

読者が設定するときは、省略すれば全モデル停止、明示リストを書けばそのリストだけ停止 (安いモデルに切り替えれば続行可能) と覚えておけば間違いありません。

セッションをまたいだ予算

ここまで使っていた cost_budget が見ているのは、1セッションの累積コストです。サブエージェントを含むセッションツリー全体が対象になります。親がサブエージェントの完了を待っている間は親自身がツールを呼ばないので、ツリー単位で見ないと超過を検知できないためです。

裏を返すと、別々に起動したセッションは、それぞれが独立した枠を持ちます。 今回の検証では、上限0.50ドルの設定に対して2つのセッションの合計が2.49ドルになりました。

セッション ハーネス コスト
Codex codex-native 1.31ドル
Claude Code claude-code-native 1.19ドル
合計 2.49ドル

セッションを増やせば枠も増える、という素直な挙動です。ハーネスをまたいだ合計を1本で締めたい場合は、別のポリシーを使います。

policies:
  daily_budget:
    type: function
    handler: omnigent.policies.builtins.cost.user_daily_cost_budget
    factory_params:
      max_cost_usd: 2.00
      expensive_models: ["opus", "gpt-5"]

user_daily_cost_budget が見るのは、そのユーザーのUTC 1日分の全セッション累積コストです。ASKとダウングレードゲートの挙動自体は cost_budget と同じです。

これを設定した状態で、その日の累積が2.64ドルある状態から新しいセッションを作って最初のプロンプトを送ると、こうなります。

Blocked by the per-user daily cost-budget policy: local's spend $2.64 reached
the $2.00 limit. You've hit the $2.00 daily cost budget. High-cost models
(opus, gpt-5) are blocked over budget — switch to a cheaper model to continue.

このセッション自体はまだ0.00ドルしか使っていません。 参照しているのはユーザーの今日の累積で、Claude CodeとCodex両方の消費を合算した値です。確かめたかったことの1番目は、ここで答えが出ました。Claude Code単体でもCodex単体でも、相手側の消費を知る手段がないので、この判定はメタハーネスの層でしか成立しません。

注意点が2つあります。

  • 集計はUTC基準です。日本時間では午前9時に日付が変わります
  • 認証を有効にしていない場合、ユーザーは local の1人だけです。メッセージにも local's spend と出ます。ユーザー単位で効かせるには、複数ユーザー構成 (OMNIGENT_AUTH_ENABLED=1) が前提になります

つまり user_daily_cost_budget は、管理者と利用者が別にいる環境を想定した機能です。個人のラップトップで動かす場合は、自分で決めて自分で変えられる上限になります。

ハーネスによってフェーズの届き方が違う

ここからが、今回一番時間をかけた部分です。

Omnigentのポリシーは、フェーズごとに発火します。request (ユーザーの入力がモデルに届く前)、tool_call (ツール呼び出しの直前)、llm_request (LLM呼び出しごと) などです。

コスト予算のポリシーはrequestとtool_callで動きます。この2つはネイティブハーネスのフック機構から届くので、omnigent claude や omnigent codex でも効きます。ここまで見てきたとおりです。

一方、ブログで紹介されているもう1つの機能、簡単な依頼を高いモデルに流さないルーティングポリシーは llm_request フェーズで動きます。

llm:
  model: databricks-gpt-5-4-nano
  profile: DEFAULT

policies:
  deny_trivial:
    type: function
    handler: omnigent.policies.builtins.routing.deny_trivial_to_expensive_model
    factory_params:
      expensive_models:
        - system.ai.claude-opus-5

このポリシーは、ユーザーのメッセージをTRIVIALかCOMPLEXかに分類し、TRIVIALなら高いモデルへの呼び出しを拒否します。分類には別のモデルを使うので、サーバー設定に llm: ブロックが必要です。

expensive_models は完全一致で判定されます。 cost_budget は部分一致だったので、同じキー名でも判定方法が違います。ここは設定するときに注意が必要です。

llm.model にはサービングエンドポイント名を書きます。Unity Catalog上のモデル名 (system.ai. で始まるもの) ではありません。私はここを取り違えて404を出しました。

databricks serving-endpoints list

で確認できます。databricks- で始まる名前なら databricks/ の接頭辞は自動で付きます。

2つのハーネスで同じ設定を試す

同じサーバー、同じポリシーで、ハーネスだけを変えて「今日は何日?」と聞いてみました。

omnigent claude (claude-code-native) の場合、ブロックされずに応答が返りました。1問で30.8kトークン、0.31ドルです。まさにこのポリシーが防ごうとしているケースですが、通っています。

omnigent run --harness claude-sdk の場合はブロックされました。サーバーログにはこう出ます。

policies.builtins.routing | deny_trivial_to_expensive_model:
classified as TRIVIAL — denying call to expensive model system.ai.claude-opus-5

server.routes.sessions | policy_eval_verdict: phase=llm_request action=deny
policy=deny_trivial

claude-code-nativeのほうは、該当するログが1行も出ませんでした。ポリシーが呼ばれていないということです。ネイティブモードはベンダーのTUIをそのまま動かし、フック経由でポリシーを評価する構成なので、LLM呼び出しごとのフェーズがそこを通らないためだと思われます。

まとめるとこうなります。

ポリシー 発火フェーズ claude-sdk claude-code-native
cost_budget request, tool_call 効く 効く
deny_trivial_to_expensive_model llm_request 効く ポリシーが呼ばれない

ポリシーを設定する前に、使っているハーネスとポリシーの発火フェーズの組み合わせを確認しておくとよさそうです。 普段 omnigent claude を使っている場合、ルーティングポリシーは設定しても動かないことになります。

分類に失敗した場合は素通りする

もう1つ、把握しておきたい挙動があります。分類用のモデル呼び出しが失敗した場合、ポリシーは判定を返さずに素通りします。フェイルオープンです。ポリシー側の障害で作業が止まらないようにするための設計だと思われます。

私は llm.model のエンドポイント名を取り違えていた期間があり、そのあいだは分類が404で失敗していました。ターミナル上は何事もなく応答が返るので、ポリシーが効いていないことに気づけません。 設定できているつもりで、実際には何も止まっていない状態でした。

気づけたのはサーバーログを見たときです。

grep -i "deny_trivial\|classification" ~/.omnigent/logs/server/server-*.log | tail

読み分けはこうなります。

  • 何も出ない: ポリシーが呼ばれていない。そのハーネスからフェーズが届いていない
  • event['llm_client'] is None: llm: ブロックが読まれていない
  • classification call failed: 分類用モデルの呼び出しに失敗している
  • classified as TRIVIAL — denying call to...: 正常に判定してブロックしている

ルーティングポリシーを設定したら、期待どおりにブロックされることを一度ログで確認しておくのが確実です。ここが今回一番時間を溶かしたところでした。

使用量を見る

CLIから確認できます。

omnigent usage
omnigent usage --limit 25
omnigent usage --json

サマリー (今日 / 直近7日 / 直近30日 / 全期間) と、セッションごとのコストおよびモデル別の内訳が出ます。集計はユーザー単位の日次ロールアップからで、コストが発生したUTC日付に紐づきます。

ハーネス別の内訳を見たい場合は、Webの画面を使います。こちらは既定では無効なので、環境変数で有効にしてサーバーを起動します。

OMNIGENT_FEATURES=usage_page omnigent server --config server_config.yaml

Screenshot 2026-09-18 at 17.15.47.png

サイドバーにUsageが現れ、日次コストの推移、ハーネス別コスト、モデル別コスト、セッション一覧が見られます。ハーネス別の内訳はこのフラグを立てたときだけAPIから返ってくるので、確かめたかったことの3番目はここで答えが出ました。

1点、読み方の注意があります。モデル別の内訳がセッション合計と一致しないことがあります。ネイティブハーネスはセッション全体の累積コストを1つの値で報告し、それを当時アクティブだったモデルに紐づけるためです。セッション途中でモデルを切り替えると、各モデルの実際の消費ではなくスナップショットが並びます。この点はCLIのヘルプにも明記されています。

コストの数字自体も、omnigent usage の冒頭に出るとおり best-effort な見積もりです。サブスクリプションで動かすハーネスとAPIキーで動かすハーネスでは数字の意味も変わるので、部門へのチャージバックのような用途にそのまま使うものではありません。

まとめ

Omnigentのコスト制御を実際に設定して動かして分かったことをまとめます。

  • サーバー全体のポリシーは omnigent server --config <file> で渡したときだけ読まれる。~/.omnigent/config.yaml とは読み込み経路が別
  • 1つのサーバー設定が、Claude CodeとCodexの両方のセッションに効く。ハーネスごとの設定は不要
  • max_cost_usd は「これ以上使わせない額」ではなく「これを超えたら高いモデルを使わせない額」。安いモデルに切り替えれば作業は続けられる
  • コストのチェックはターンの境界とツール呼び出しの直前に入る。1ターンが長いと、そのあいだはチェックされない。予算は守らせたい額よりかなり低く設定する必要がある
  • cost_budget はセッション単位。セッションを増やせば枠も増える
  • ハーネスをまたいだ合計を締めるには user_daily_cost_budget を使う。UTC 1日単位・ユーザー単位で、消費ゼロの新規セッションでもブロックされる
  • expensive_models を省略すると全モデル停止、明示リストを書けばそのリストだけ停止。cost_budget は部分一致、ルーティングポリシーは完全一致で判定が違う
  • ポリシーの発火フェーズとハーネスの組み合わせで、効く効かないが変わる。コスト予算はネイティブモードでも効くが、ルーティングは llm_request フェーズなので効かない
  • ルーティングポリシーは分類用モデルの呼び出しに失敗すると素通りする。設定後に一度ログで確認しておく
  • ハーネス別のコスト内訳を見るには OMNIGENT_FEATURES=usage_page が必要

一番の収穫は、「予算を設定する」という一行が保証する範囲を、自分の目で確かめられたことでした。上限に達したことを検知できるのはターンの境界なので、上限そのものは通過します。この性質を知らずに金額を決めると、期待と実際がずれます。一方で、複数のコーディングエージェントの消費を合算して1本の線を引けるのは、それぞれのツール単体では手が届かない範囲です。複数のエージェントを並行して使っている環境なら、ここは効いてくると思います。

書籍のご案内

Omnigentについては、2026年9月30日に『Omnigentによるメタハーネス入門』が翔泳社から刊行されます。翔泳社電書部の第1弾として、電子書籍で出ます。私も著者の一人として参加しています。

全3巻の構成と、どの巻から読むとよいかについては別記事にまとめています。

そもそもメタハーネスという層が何を解こうとしているのか、という話はこちらです。今回の記事で扱ったコスト制御も、その層があって初めて成立する機能の一つです。

参考リンク

はじめてのDatabricks

はじめてのDatabricks

Databricks無料トライアル

Databricks無料トライアル

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?