「投げて終わり」が許されるのは、対話のときだけ
Antigravity 2.0 の Managed Agents API は、単一の API 呼び出しで隔離された Linux 環境にエージェントを起動し、その中で推論・ツール実行・コード実行までを完結させられます。手元で対話的に試すぶんには、結果が返るのを眺めていればよく、まさに「投げて終わり」で済みます。
ところが、これをバックグラウンドの常駐タスクとして無人で走らせ始めると、話がまったく変わります。誰も見ていない時間帯に、想定より長く回り続けたり、同じ処理を二重に起動したり、気づいたら使用量が跳ね上がっていたりする。私自身、個人開発で自動運用を回してきて、無人タスクで痛い目を見たのはたいてい「止める仕組みを入れていなかった」ときでした。
便利なものほど、止め方を先に設計しておく。これがバックグラウンドタスクの第一原則だと考えています。
無人タスクに必ず入れる3つのガードレール
無人で走らせるエージェントには、機能を書く前に安全装置を入れます。私が必ず用意しているのは次の3つです。
タイムアウト
エージェントは「もう少しで終わりそう」と判断して延々と試行を続けることがあります。壁時計での上限を外から課して、超えたら問答無用で打ち切ります。本番運用では、この外側からの打ち切りがないと、暴走したタスクが翌朝まで居座る事故が起きます。
予算上限
1回の起動で使える量に上限を設け、超えそうなら新しい呼び出しをさせない。コスト超過の多くは、1回の暴走ではなく、小さな呼び出しの積み重ねで起きます。これは気づきにくい落とし穴で、注意点として最初から上限を入れておくのが確実な回避策です。
冪等キー
再試行やスケジューラの二重起動で、同じタスクが二度走ることは必ず起きます。タスクに一意のキーを持たせ、すでに処理済みなら何もしないようにしておきます。二重起動はエラーとして表面化しないまま重複した副作用だけ残すので、特にやっかいです。
ガードレール 防ぐ事故 実装の勘所
タイムアウト 終わらないエージェントの常駐 壁時計上限を外部から課す
予算上限 小さな呼び出しの累積コスト超過 起動単位で使用量を打ち切る
冪等キー 二重起動による重複処理・副作用 一意キーで処理済みを判定
ガードレールを噛ませた起動の骨格
Managed Agents API を直接呼ぶのではなく、上の3つを噛ませたラッパー経由で起動します。擬似コードで骨格を示します。実際のエンドポイント名やパラメータ名は手元の SDK ドキュメントで確認してください。
import time, hashlib
def idempotency_key (task: dict ) -> str :
# タスク内容から安定した一意キーを作る
raw = f " { task[ 'type' ] } : { task[ 'target' ] } : { task[ 'date' ] } "
return hashlib.sha256(raw.encode()).hexdigest()[: 16 ]
def run_managed_agent (task: dict , * , max_seconds = 600 , budget_units = 50 ):
key = idempotency_key(task)
# 冪等チェック: 処理済みなら何もしない
if store.already_done(key):
return { "status" : "skipped" , "key" : key}
started = time.monotonic()
handle = agents.launch( # 隔離環境にエージェントを起動
prompt = task[ "prompt" ],
budget_units = budget_units, # 予算上限を起動時に渡す
)
while True :
state = agents.poll(handle.id)
if state.finished:
store.mark_done(key)
return { "status" : "ok" , "result" : state.result, "key" : key}
# タイムアウト: 壁時計で外から打ち切る
if time.monotonic() - started > max_seconds:
agents.cancel(handle.id)
store.mark_failed(key, reason = "timeout" )
return { "status" : "timeout" , "key" : key}
time.sleep( 5 )
ここで大切なのは、タイムアウトの判定を「エージェント自身の申告」ではなく「外側の壁時計」で行っていることです。暴走しているエージェントに「もう時間切れですか」と尋ねても正しい答えは返りません。打ち切りは必ず外から課します。冪等キーで起動前にはじく一手も、二重起動の事故を静かに防いでくれます。
観測できないものは、運用できない
ガードレールを入れても、何が起きているか見えなければ運用は続きません。無人タスクには、最低限「いつ起動し、どれだけ使い、どう終わったか」を残します。
def log_run (record: dict ):
# 構造化ログを1行で残す(後から集計しやすい形にする)
line = (
f "ts= { record[ 'ts' ] } key= { record[ 'key' ] } "
f "status= { record[ 'status' ] } elapsed= { record[ 'elapsed' ] } s "
f "units= { record[ 'units' ] } "
)
append_log(line)
私の自動運用では、毎日のタスクがどれだけ時間と使用量を費やしたかを必ず記録しています。これがあると、ある日とつぜん使用量が増えたときに、どのタスクがいつから変わったのかを後から追えます。逆に、記録がないと「なんとなく増えた気がする」で終わってしまい、原因にたどり着けません。観測は、暴走を止めるためというより、暴走に気づくためにあります。
段階的に無人化する
最初から完全無人で走らせるのは勧めません。私はいつも、次の順で慣らしていくことを推奨しています。
手元で1回だけ実行する : 出力と使用量を目で確認し、想定どおりかを見ます。
観測付きで半自動にする : ログを取りながら、自分がいる時間帯にだけ走らせます。ここで挙動の癖やつまずきどころを掴みます。
完全無人にする : 半自動で問題が出なくなってから、はじめてスケジューラに載せます。
Managed Agents API は強力で、単一の呼び出しで多くを任せられるぶん、任せきる前に「自分が見ていない時間に何が起きるか」を一度は自分の目で確かめておきたいのです。この段階を踏む場合は、各段階で最低でも数日は様子を見ることをお勧めします。
次に手を動かすなら、まずタイムアウトと冪等キーの2つだけでもラッパーに入れてみてください。この2つがあるだけで、無人タスクの夜が、ずいぶん静かなものになります。お読みいただきありがとうございました。