Google I/O 2026 で発表された Managed Agents が、Gemini API の公開プレビューとして使えるようになりました。1回の API 呼び出しで、Google がホストする隔離された Linux サンドボックスの中にエージェントが立ち上がり、推論・ツール実行・コード実行までこなして結果を返してくる、という触れ込みです。
私自身、個人開発のかたわらでブログの定期更新や画像資産の整理といった処理を、自前のエージェントループとスケジュール実行で回しています。発表を読んだときの率直な気持ちは、期待半分、警戒半分でした。「ループの面倒な部分を全部引き受けてくれるなら、それに越したことはない」という期待と、「動いている自動化を安易に動かすと、たいてい痛い目を見る」という経験則です。
そこで、いま手元で動いている処理を一つずつ眺めながら、「これは Managed Agents に移せるのか、移すべきなのか」を考えてみました。結論から言うと、全部を移す判断にはなりませんでしたが、判断の軸は3つの質問に集約できました。ここから先は、その整理の記録です。
自前のエージェントループは、実は「ループ」以外が本体です
エージェントループそのものは、書いてみると意外なほど短いコードです。モデルを呼び、function call が返ってきたら対応する関数を実行し、結果を渡してまた呼ぶ。骨格だけなら30行ほどで書けます。
次のコードは、リリースノートを確認するツールを1つだけ持つ最小のループです。Gemini が必要に応じてツールを呼び、最終的な報告テキストを返したら終了します。
from google import genai
from google.genai import types
client = genai.Client() # GEMINI_API_KEY を環境変数から読み込みます
def check_release_notes (product: str ) -> dict :
"""リリースノートの最新エントリを返します(実際は RSS や DB を参照します)"""
return { "product" : product, "latest" : "1.4.2" , "breaking_changes" : False }
tool = types.Tool( function_declarations = [
types.FunctionDeclaration(
name = "check_release_notes" ,
description = "製品名からリリースノートの最新エントリを取得します" ,
parameters = types.Schema(
type = types.Type. OBJECT ,
properties = { "product" : types.Schema( type = types.Type. STRING )},
required = [ "product" ],
),
)
])
contents = [types.Content(
role = "user" ,
parts = [types.Part( text = "依存ライブラリ foo の最新リリースに破壊的変更がないか確認し、1段落で報告してください" )],
)]
for _ in range ( 5 ): # 暴走防止の上限つきループ
response = client.models.generate_content(
model = "gemini-3.5-flash" ,
contents = contents,
config = types.GenerateContentConfig( tools = [tool]),
)
if not response.function_calls:
print (response.text)
break
contents.append(response.candidates[ 0 ].content)
for call in response.function_calls:
result = check_release_notes( ** call.args)
contents.append(types.Content(
role = "user" ,
parts = [types.Part.from_function_response( name = call.name, response = result)],
))
骨格をこれだけ短く見せたのには理由があります。実運用では、この骨格の周りに「本体」と呼ぶべき層が付いてくるからです。指数バックオフ付きのリトライ。途中経過のログと永続化。実行環境(cron なりコンテナなり)の維持。API キーの管理。タイムアウトと多重起動の防止。私の手元のループも、エージェントの思考に関わる部分より、この運用層のコードのほうがずっと長くなっています。
ループ周辺の本番設計は ADKに頼らない Gemini API カスタムエージェントループ設計ガイド — ツール呼び出し・メモリ・並列実行の本番実装 に詳しく書きましたが、一言でまとめると「ループは簡単、運用が本体」。これが今回の出発点になります。
Managed Agents が肩代わりするのは運用層のどこまでか
公開されている説明を読み解くと、Managed Agents が引き受けてくれるのは次の範囲です。エージェントの実行環境、つまり隔離された Linux サンドボックスの用意と破棄。推論からツール実行・コード実行までを繰り返すループの進行管理。そして実行中の状態の保持。先ほど「本体」と呼んだ運用層のうち、実行環境の維持とループの進行管理が、まるごと API の向こう側に移ることになります。
一方で、こちら側に残るものもはっきりしています。タスクの定義、つまり何をさせたいかの記述。結果の受け取りと、出てきたものが正しいかどうかの検証。失敗したときのハンドリング。そしてコストの管理。エージェントが「どう動くか」は任せられても、「何のために動かし、結果をどう使うか」は引き続き自分の仕事です。
この線引きを眺めたとき、私は cron の管理から解放されることよりも、「検証と失敗時のハンドリングは残る」という事実のほうが重要だと感じました。自動化の運用で実際に時間を取られるのは、環境の維持ではなく、失敗したときの調査だからです。だからこそ、移す・移さないは「便利そうかどうか」ではなく、次の3つの質問で判断することにしました。
質問1 — その処理は、実行環境そのものに意味がありますか
最初の質問は、処理がどこで動く必要があるか、です。
私の手元には、ローカルのワークスペースにあるファイルを読み書きする処理がいくつもあります。画像資産のフォルダを走査して分類する。リポジトリを clone して記事を push する。実行ログを所定のフォルダに追記する。こうした処理は、実行環境が自分の管理する場所にあること自体に意味があります。サンドボックスへ移すには資格情報やファイルの受け渡しを設計し直す必要があり、私の見立てでは移行コストが利得を上回ります。
逆に、「入力を渡す → 調べる・変換する → 結果を返す」で完結する処理は、どこで動いても同じです。公開リリースノートの監視、Web 上の情報の調査と要約、データの変換とレポート生成。この種の処理は Managed Agents のサンドボックスと相性が良く、コード実行を伴う変換処理ではむしろ、隔離環境で動くこと自体が安全性の向上になります。モデルが生成したコードを自分のマシンでそのまま実行するのは、それなりに神経を使う行為ですから。
質問2 — 状態の正本はどちらに置きますか
2つ目の質問は、エージェントが持つ「状態」を誰が所有するか、です。
Managed Agents はステートフルなエージェントを構築できるとされています。会話や作業の文脈をサンドボックス側が保持してくれるのは、複数ステップにまたがる長いタスクでは大きな利点です。ただ、私は公開プレビュー段階のサービスに「状態の正本」を預ける気にはなれません。プレビューの API は仕様が変わるものです。つい先日も、Interactions API の旧スキーマが廃止され、outputs から steps への移行 が必要になったばかりでした。
そこで私の基準はこうなりました。状態が一過性のもの、つまりそのタスクの実行中だけ意味を持つなら、サンドボックスに任せてよい。状態が資産、つまり次回以降の実行や他の処理から参照されるなら、正本は自分のデータベースやファイルに置き、エージェントには毎回必要な分だけを渡す。後者の設計を守っておけば、仮に Managed Agents 側の仕様が変わっても、失うのは実行中のタスク1回分で済みます。
質問3 — 失敗したとき、誰がどの粒度で拾えますか
3つ目の質問が、私にとっては決定打でした。失敗時にどこまで細かく回収できるか、です。
自前のループは、失敗の粒度が細かく見えます。どのツール呼び出しで、どんな引数で、何が返って止まったのか。ログを仕込んだ分だけ追跡できますし、途中から再開する設計も自由に作れます。つい先日の Gemini の大規模障害のときも、手元のパイプラインはリトライ、フォールバック、それでも駄目ならログに記録して静かに終了、という段階的な退避で乗り切れました。これはループの各段に自分のフックを持っていたからできたことです。
Managed Agents に移すと、この粒度は API が見せてくれる範囲に制約されます。エージェント内部のステップをどこまで観測できるかは今後の進化に期待したいところですが、少なくとも設計の前提としては「中間状態には介入できない」と置くのが安全だと考えています。そうすると、移してよいのは失敗してもまるごとやり直せる処理、つまり冪等にできる処理に絞られます。外部に副作用を出しながら進む処理のように、途中まで進んだ状態に価値や危険が残るものは、回収手段を自分で持てる自前ループに残します。
なお、Managed Agents のような「投げて完了を待つ」型の処理を増やすほど、完了通知の設計が効いてきます。長時間処理の完了待ちをポーリングからイベント駆動へ切り替える設計は 深夜のポーリングをやめる — Gemini Batch API を Webhook 駆動に作り替えた設計記録 に書きましたので、併せて参考にしていただければと思います。
プレビュー段階のものを本番の隣に置くための小さな工夫
3つの質問を通過して「移せる」と判定した処理についても、プレビュー段階のうちは一枚だけ防御層を挟むことにしています。といっても大げさなものではなく、Managed Agents の呼び出しを薄いラッパー関数に閉じ込め、そこに2つの仕事だけを持たせる構成です。1つは、呼び出しが失敗したら従来の自前ループで同じタスクを実行するフォールバック。もう1つは、返ってきた結果のスキーマ検証です。
この形にしておくと、プレビュー特有の仕様変更が来ても影響範囲はラッパー1ファイルに収まります。また、同じタスクを Managed Agents と自前ループの両方で実行して結果を突き合わせる、という品質比較も同じ場所でできます。料金についても、プレビュー期間中は想定外の課金パターンがないか、他の処理と分けて集計できるようにしておくと安心です。
実装 — フォールバックとスキーマ検証を備えた薄いラッパー
先ほど「薄いラッパーに2つの仕事だけを持たせる」と書きましたが、ここではその実体をコードで示します。やることは2つです。Managed Agents を呼び、返ってきた結果を Pydantic で検証する。そして、呼び出しか検証のどちらかが失敗したら、同じタスクを自前ループへ流す。これだけです。
ポイントは、フォールバックを「例外を握りつぶす」ためでなく「同じ契約を別の実装で満たす」ために置くことです。両者の戻り値の型を1つに揃えておけば、呼び出し側はどちらが動いたかを気にせずに済みます。プレビュー段階のうちは、この薄いラッパーを必ず挟むことを推奨します。
from pydantic import BaseModel
class ReleaseReport ( BaseModel ):
product: str
summary: str
has_breaking_change: bool
def run_via_managed_agent (task: str ) -> ReleaseReport:
"""Managed Agents(公開プレビュー)でタスクを実行し、結果を検証して返します。"""
resp = client.agents.run( # プレビュー段階のため、呼び出し名は今後変わり得ます
model = "gemini-3.5-flash" ,
instructions = task,
response_schema = ReleaseReport,
)
return ReleaseReport.model_validate_json(resp.output_text)
def run_via_self_hosted (task: str ) -> ReleaseReport:
"""同じ契約を自前ループで満たすフォールバックです(中身は既存のループを再利用します)。"""
text = run_existing_agent_loop(task) # 本記事冒頭の最小ループの本番版
return ReleaseReport.model_validate_json(text)
def run_task (task: str ) -> tuple[ReleaseReport, str ]:
"""まず Managed Agents、駄目なら自前ループ。どちらで動いたかも返します。"""
try :
return run_via_managed_agent(task), "managed"
except Exception as e: # プレビュー中は呼び出し失敗も検証失敗も広めに捕捉します
log.warning( "managed agent failed ( %s ); falling back to self-hosted" , e)
return run_via_self_hosted(task), "self_hosted"
この構成にしておくと、プレビュー特有の仕様変更が来ても、修正するのは run_via_managed_agent の1関数だけで済みます。run_task を呼ぶ側も、ReleaseReport を受け取るほかの処理も、いっさい書き換えずに済みます。「どちらで動いたか」を一緒に返しているのは、次のコスト集計と品質比較でこのフラグを使うためです。
冪等キーで「まるごとやり直し」を安全にする
質問3で「移してよいのは冪等にできる処理だけ」と書きました。ただ、頭の中で「これは冪等です」と言うのと、再実行が本当に安全な状態を作るのは別の話です。Managed Agents は失敗時に中間状態へ介入できない前提なので、フォールバックや手動の再実行で「同じタスクが二重に走る」ことは普通に起こります。そこで、タスクの入力から決まるキーを1つ用意し、同じキーの結果が既にあれば処理本体をスキップする、という薄い番人を挟みます。
import hashlib, json, sqlite3
db = sqlite3.connect( "agent_runs.db" )
db.execute( "CREATE TABLE IF NOT EXISTS runs(key TEXT PRIMARY KEY, result TEXT)" )
def idempotency_key (task: str , inputs: dict ) -> str :
"""タスク内容と入力を正規化してキー化します(順序揺れを防ぐため sort_keys を指定)。"""
payload = json.dumps({ "task" : task, "inputs" : inputs}, sort_keys = True , ensure_ascii = False )
return hashlib.sha256(payload.encode( "utf-8" )).hexdigest()
def run_idempotent (task: str , inputs: dict ) -> ReleaseReport:
key = idempotency_key(task, inputs)
row = db.execute( "SELECT result FROM runs WHERE key = ?" , (key,)).fetchone()
if row:
return ReleaseReport.model_validate_json(row[ 0 ]) # 既に成功済み。本体は動かしません
report, _ = run_task( f " { task }\n 対象: { json.dumps(inputs, ensure_ascii = False ) } " )
db.execute( "INSERT OR REPLACE INTO runs(key, result) VALUES (?, ?)" ,
(key, report.model_dump_json()))
db.commit()
return report
ここで効いてくるのは、キーの作り方です。sort_keys=True を外すと、同じ入力でも辞書の並び順が変わるだけでキーが変わり、冪等性が静かに壊れます。私は以前これを別のキャッシュ処理でやってしまい、ヒット率が想定より低い原因を半日探したことがあります。地味ですが、本番運用で見落としやすい落とし穴でした。入力に時刻や乱数を混ぜないこと、JSON を正規化することの2つを守るだけで、再実行は安全側に倒れます。副作用を伴う処理を Managed Agents に出すのが怖い、という質問3の懸念は、この番人で実用上はかなり和らぎます。
コストを処理単位でタグ付けして、プレビュー期間の課金を監視する
プレビュー段階で一番こわいのは、機能の不具合よりも「気づかないうちに課金が膨らむ」ことです。Managed Agents は1回の呼び出しの裏で複数ステップを回すため、トークン消費が自前ループより読みにくくなります。そこで、run_task が返す「どちらで動いたか」のフラグと一緒に、1タスクあたりの実測を記録しておきます。
import time
def metered (task: str , inputs: dict ) -> ReleaseReport:
started = time.monotonic()
report = run_idempotent(task, inputs)
db.execute(
"INSERT INTO meter(tag, impl, seconds, ts) VALUES (?, ?, ?, ?)" ,
(inputs.get( "tag" , "untagged" ), "managed" , time.monotonic() - started, time.time()),
)
db.commit()
return report
夜間にこのテーブルを集計し、Managed Agents 経由のタスクが自前ループ経由より2〜3倍のコスト・時間がかかっていないかを毎日眺めるようにしています。短いタスクほどサンドボックス起動の固定費が効くため、ここの比率は素直に開きます。下の目安は、私が並行運用を始めるときに置いた監視の観点です。
監視する値 なぜ見るか 異常の合図
managed / self の所要時間比 サンドボックス起動の固定費を測る 短いタスクで比が大きく開く
1タスクあたりの呼び出し回数 内部ステップの増減を間接的に掴む 同じ入力で日によって跳ねる
フォールバック発生率 プレビューの安定度を測る 急に上がったら仕様変更を疑う
この3つを分けて見ておくと、プレビュー特有の「いつの間にか高い・遅い」に早く気づけます。料金が読めないサービスを本番の隣で評価するときは、便利さよりも先に、こうした計測の足場を作っておくほうが結局は安く済むと感じています。
まずは「サンドボックス向きの1本」を選ぶところから
整理を終えて、私の手元で最初の移行候補になったのは「公開リリースノートの監視と要約」でした。実行環境に依存せず(質問1)、状態は毎回使い捨てで(質問2)、失敗しても次の実行でやり直せばよい(質問3)。3つの質問をすべて「移せる」で通過する、ほとんど教科書のような候補です。
もしお手元に自前のエージェントループやスケジュール処理があるなら、次の一歩としておすすめしたいのは、処理の一覧にこの3つの質問への答えを書き込んでみることです。全部移すか全部残すかの二択ではなく、「移せる1本」を見つけて並行運用から始める。動いている自動化を壊さずに新しい仕組みを評価するには、私はこの進め方が一番堅実だと考えています。
同じように自動化の置き場所を考えている方の参考になれば幸いです。