自分のアプリで、壁紙の一括説明文づくりをエージェントに任せてみたときのことです。処理そのものは問題なく動いていました。困ったのは、応答が返るまで管理画面のボタンがずっと待ち状態のまま固まって見えることでした。40枚まとめて流すと、体感で1分近く沈黙します。その間、私は「本当に動いているのか」を確かめるすべを持っていませんでした。
2026-07-19 の更新で、Gemini API の Managed Agents に background: true が加わりました(公開プレビュー)。対話をサーバー側で非同期に走らせ、待たずに先へ進むための仕組みです。この一つのフラグで、さきほどの「画面が固まって見える」問題の輪郭がずいぶん変わりました。
background: true が返すのは、結果ではなくラン ID です
ふだんの対話呼び出しは、モデルが応答を作り終えるまでその場で待ちます。短い返答なら気になりませんが、ツールを何度も呼ぶエージェントや、長い入力をまとめて処理させる場面では、この待ち時間がそのまま UI の固まりになります。
background: true を付けた呼び出しは、ここが決定的に違います。返ってくるのは完成した応答ではなく、そのランを指す ID です。サーバー側では対話が動き続け、こちらは ID を手がかりに、あとから状態を尋ねにいきます。呼び出し側のプロセスは、その場に縛られません。
考え方としては、同期のリクエスト・レスポンスから、ジョブを投げてポーリングする形へ移す、という素直な転換です。長時間オペレーションや Batch API を扱った経験があれば、馴染みのある形だと思います。違いは、その対象がエージェントの一連の対話そのものになった点にあります。
ポーリングを最小構成で書く
まずは骨格です。ランを開始して ID を受け取り、完了するまで一定間隔で状態を確認します。公開プレビューのため、パラメータ名や取得方法は変わりうる前提で、最新のドキュメントと必ず突き合わせてください。
import time
from google import genai
client = genai.Client()
# 1. background: true でランを開始し、すぐに ID を受け取る
run = client.agents.runs.create(
agent="antigravity-preview-05-2026",
input="pending フォルダの画像40件に、日本語の説明文を1件ずつ付けてください。",
background=True,
)
run_id = run.id
print("started:", run_id)
# 2. 完了するまでポーリングする(指数バックオフで間隔を広げる)
delay = 2.0
while True:
current = client.agents.runs.get(run_id)
if current.status in ("succeeded", "failed", "cancelled"):
break
time.sleep(delay)
delay = min(delay * 1.5, 20.0) # 2s から最大20s まで広げる
# 3. 結果を取り出す
if current.status == "succeeded":
print(current.output)
else:
print("失敗:", current.status, getattr(current, "error", None))
ポイントは、待ち時間をこちら側で持たないことです。開始の呼び出しはすぐ返るので、Web アプリなら run_id をその場でクライアントへ返し、進捗の確認は別のエンドポイントに分けられます。ユーザーの画面は、投稿ボタンを押した直後に「処理を受け付けました」まで進めます。
間隔を固定の1秒にしなかったのには理由があります。40件のような処理は、序盤は明らかにまだ終わっていません。最初から短い間隔で叩くと、確認リクエストだけが無駄に積み上がります。2秒から始めて徐々に広げると、確認の回数を体感で半分以下に抑えられました。
個人開発で、どこに効くか
個人開発では、応答を待つ1分がユーザー体験をどれだけ削るかを、自分ひとりでかぶることになります。私の壁紙アプリの例で言えば、効いたのは「投稿フローと生成処理を切り離せた」ことでした。以前は、説明文の生成が終わるまで管理画面を離れられませんでした。非同期にしてからは、ランを投げたらすぐ次の画像の下準備に移れます。生成結果は、あとで一覧に戻ってきたときに反映されていれば十分です。
料金の見通しも立てやすくなりました。現行の gemini-flash-latest(実体は gemini-3.5-flash)は入力 $1.50/100万トークン・出力 $9.00/100万トークンです。ここに Managed Agents ではサンドボックスの稼働時間も乗ります。同期で待っていると、この稼働時間が「自分の待ち時間」と重なって見えて、コストの実感がつかみにくいのですが、非同期にすると生成は生成、UI は UI と分けて考えられます。稼働時間側の予算設計は別の記事に譲りますが、切り離せること自体が見通しに効きました。
| 観点 | 同期呼び出し | background: true |
|---|---|---|
| 返ってくるもの | 完成した応答 | ランを指す ID |
| UI のふるまい | 完了まで待ち状態 | すぐ次へ進める |
| 向く処理 | 短い単発の応答 | 多数件・多ステップの処理 |
| 結果の受け取り | その場で | ポーリング(または Webhook) |
件数が少なく、応答も短いなら、無理に非同期化する必要はありません。同期のままのほうが、コードは素直で読みやすく保てます。私自身、単発のちょっとした問い合わせは今も同期で書いています。私はこの切り替えを、ユーザーを待たせる秒数が我慢できる範囲を超えるかどうか、という一点で判断しています。
認証情報のリフレッシュと、間隔の落とし穴
非同期にすると、対話がこちらのプロセスの外で長く生き続けます。ここで気にしたいのが、対話をまたぐ認証情報の扱いです。同じ 2026-07-19 の更新で、Managed Agents は対話をまたいだ認証情報のリフレッシュに対応しました。外部サービスへのアクセストークンが途中で切れても、ランの側で更新を引き受けられるようになっています。長く走るランほど、この対応の有無が効いてきます。
もう一つ、リモートの MCP サーバー連携も同じ更新に含まれます。エージェントが外部ツールを呼ぶ構成を非同期で回すなら、ツール側の認証がランの生存期間より短くないかを、設計の段階で確かめておくと安心です。
ポーリング側で私がつまずいた点も残しておきます。
| つまずき | 起きたこと | 対処 |
|---|---|---|
| 間隔が短すぎる | 確認リクエストばかり増える | 指数バックオフで徐々に広げる |
| 終了条件の取りこぼし | failed を待ち続けて無限ループ | succeeded 以外の終了状態も必ず break 対象に |
| タイムアウト無し | 異常時にポーリングが止まらない | 上限回数か締切時刻を必ず設ける |
とくに三つ目は、私が実際にやってしまった失敗です。終了状態の分岐は書いていたのに、そこへ到達しない異常系を想定していませんでした。締切を1つ置くだけで、夜間の自動実行が静かに暴走する心配はなくなりました。
まとめ — まず1件だけ非同期にしてみる
background: true は、エージェントの対話を「投げて、あとで受け取る」形に変えるためのフラグです。返るのは結果ではなくラン ID で、完了はポーリングか Webhook で拾います。UI を止めたくない多数件・多ステップの処理に向き、短い単発なら同期のままで十分です。
次の一歩として、いま同期で待たせている処理を一つだけ選び、background: true を付けて ID を受け取るところまで試してみてください。ポーリングの骨格は上のままで動きます。画面が固まらなくなる感触を一度つかむと、どこを非同期にすべきかの判断が、ぐっと具体的になります。
公開プレビューの機能ですので、パラメータの詳細は Gemini API changelog で最新を確認しながら進めるのが安全です。長時間走行そのものの設計は Managed Agent の長時間走行がサンドボックス再生成で消える前に — チェックポイントと冪等リジュームの設計 に、稼働時間の予算の引き方は Managed Agents の請求はトークンだけでは読めない — サンドボックス稼働時間に予算境界を引く設計 にまとめています。
お読みいただきありがとうございました。私自身まだ試しながらの段階ですが、待たせない設計の心地よさは、共有する価値があると感じています。