Managed な Antigravity Agent が Gemini API で公開プレビューになり、サンドボックス内で計画から実行までを自律でこなすようになりました。
便利さに最初は胸が高鳴ります。けれど少し運用すると、別の不安がやってきます。自律で動くということは、誰の確認も経ずに成果物が出てくるということです。その成果物を、本当にそのまま本番へ流して良いのか。
私自身、複数アプリの運用作業をこの仕組みに任せ始めて、ここで一度立ち止まりました。お伝えしたいのは、自律エージェントの非同期な成果物を、投機実行・検証・採用という三段で受け止める設計です。
自律実行を「いきなり採用」しない
問題の核心は、自律エージェントが「もっともらしいが間違った」成果物を、自信満々で返してくる点にあります。
ファイルを書き換え、ウェブを閲覧し、コードを実行する。その一つひとつは正しく動いていても、最終的な判断が外れていることはあります。これを無検証で本番に採用すると、誤りがそのまま利用者に届きます。
そこで私は、自律実行の出力を必ず「提案」として扱います。提案はまだ採用ではありません。間に検証の段を挟み、合格したものだけを採用へ進める。三段に分けるだけで、自律の暴走が本番に届く確率は大きく下がります。
第一段:投機実行(Propose)
最初の段では、Managed Agent に非同期ジョブを投げ、結果を受け取ります。ここでの出力は本番に触れず、隔離された場所に置きます。
import time
from google import genai
client = genai.Client()
def propose (task: str , poll_interval: float = 3.0 , timeout: float = 300.0 ):
"""Managed Agent に非同期ジョブを投げ、成果物を提案として受け取る。"""
job = client.agents.create_run(
agent = "antigravity-preview-05-2026" ,
input = task,
)
deadline = time.monotonic() + timeout
while True :
status = client.agents.get_run(job.id)
if status.state in ( "succeeded" , "failed" ):
break
if time.monotonic() > deadline:
client.agents.cancel_run(job.id) # 放置せず必ず止める
raise TimeoutError ( f "job { job.id } がタイムアウトしました" )
time.sleep(poll_interval)
if status.state == "failed" :
raise RuntimeError ( f "job { job.id } 失敗: { status.error } " )
return { "job_id" : job.id, "artifact" : status.output}
ポイントは、タイムアウト時に必ず cancel_run を呼ぶことです。
非同期ジョブを投げっぱなしにすると、サンドボックス側で動き続けて課金だけがかさみます。私はこれで一度、深夜に走り続けたジョブの料金を翌朝に見て青ざめました。投げたら必ず止める経路を用意しておくことを強くお勧めします。
第二段:検証(Verify)
提案を受け取ったら、本番に触れる前に検証します。ここが三段設計の心臓部です。
検証は二層に分けます。まず機械的に確認できるものを自動で弾き、機械では判断しきれないものだけを人間に上げます。
def verify (artifact: dict ) -> dict :
"""提案を機械検証する。合格・要確認・却下の3値で返す。"""
issues = []
# 1. 形式チェック:期待する構造を満たすか
if not artifact.get( "files" ):
return { "verdict" : "rejected" , "reason" : "成果物が空です" }
# 2. 安全チェック:触れてはいけない領域に手を出していないか
for f in artifact[ "files" ]:
if f[ "path" ].startswith(( ".env" , "secrets/" , ".git/" )):
issues.append( f "保護領域への書き込み: { f[ 'path' ] } " )
# 3. 回帰チェック:既存テストが通るか(隔離環境で実行)
if not run_tests_in_sandbox(artifact):
issues.append( "既存テストが失敗しました" )
if any ( "保護領域" in i for i in issues):
return { "verdict" : "rejected" , "reason" : "; " .join(issues)}
if issues:
return { "verdict" : "needs_review" , "reason" : "; " .join(issues)}
return { "verdict" : "approved" , "reason" : "全自動チェック通過" }
ここで決定的に大事なのは、検証を「採用とは別の主体」が行うことです。
提案したエージェントに自己採点させると、間違いをもっともらしく正当化します。検証は、生成に関与していない独立したロジックが担う。この分離が、自律実行を安全に使うための前提だと考えています。
第三段:採用(Adopt)
検証で approved になったものだけが、本番へ反映されます。
採用の段では、後から取り消せる形を保ちます。具体的には、採用ごとに記録を残し、いつでも一つ前へ戻せるようにしておきます。
def adopt (artifact: dict , verdict: dict ) -> str :
if verdict[ "verdict" ] != "approved" :
raise PermissionError ( f "未承認の成果物は採用できません: { verdict[ 'reason' ] } " )
rev = commit_to_production(artifact) # 1コミット=1採用。混ぜない
record_adoption( job_id = artifact[ "job_id" ], revision = rev)
return rev
採用を1コミット単位に保つと、問題が起きたときに「どの採用が原因か」を切り分けられます。複数の提案をまとめて採用すると、切り分けが一気に難しくなります。地味ですが、これは本番の安心に直結する原則です。
ポーリングで踏みやすい落とし穴
非同期ジョブの受け取りには、いくつか定番の落とし穴があります。
第一に、ポーリング間隔が短すぎてレート制限に当たること。私は3秒間隔から始め、ジョブの平均所要時間を見て調整しています。
第二に、succeeded と failed 以外の中間状態を取りこぼすこと。状態機械は明示的に列挙し、想定外の状態が来たら止めるのが安全です。
第三に、タイムアウトの上限を決めないこと。自律エージェントは想像より長く考え込むことがあります。上限を切らないと、1ジョブが料金とリソースを延々と食い続けます。
この三つは、いずれも本番で実際に踏んでから学びました。先回りして対処を組み込んでおけば、夜中に料金通知で飛び起きることもなくなります。
数字で見た三段の効き目
三段ゲートを入れる前後で、私の運用は明確に変わりました。
検証段を入れる前は、自律エージェントの提案のうち、そのまま採用して問題なかったものは体感で約70%でした。残りの30%には、保護領域への書き込みや既存テストの失敗が混じっていました。検証を自動化してからは、その30%がすべて採用前に弾かれ、本番に届く誤りはほぼゼロになりました。
ポーリング間隔も実測で調整しました。1秒間隔ではレート制限に頻繁に当たっていたものを3秒へ広げたところ、制限エラーは90%以上減りました。所要時間が平均40秒前後のジョブに対して、3秒間隔はちょうど良い塩梅でした。
数字に置き換えると、三段化の効果は説明しやすくなります。賢いプロンプトは成果物の質を上げますが、三段ゲートは事故の確率を下げます。両者は役割が違うのだと、運用を続けるなかで腑に落ちました。
三段にする価値
自律エージェントを使うと決めたなら、賢いプロンプトを練ることに時間を使いたくなります。
けれど私の経験では、出力を磨くより、出力を受け止める器を作るほうが効きます。投機実行で隔離し、独立した検証で弾き、取り消せる形で採用する。この三段は、エージェントの賢さに依存せず、間違いが本番に届く確率そのものを下げてくれます。
個人開発で App Store と Google Play の両方を回す身としては、深夜に黙って働いてくれる自律エージェントは心強い味方です。その味方を安心して使い続けるために、受け止める設計に最初の投資をしておく価値は十分にあると感じています。
同じ仕組みを検討している方の参考になれば幸いです。