I first noticed the problem while handing off bulk caption writing for my wallpaper app to an agent. The processing itself worked fine. What bothered me was that the admin button sat in a waiting state the whole time, looking as if it had locked up. Push forty images through at once and it goes quiet for close to a minute. During that minute, I had no way to tell whether anything was actually happening.
The 2026-07-19 update added background: true to Managed Agents in the Gemini API (public preview). It runs a conversation server-side, asynchronously, so you can move on without waiting. That single flag reshaped the "frozen screen" problem I described above.
background: true Returns a Run ID, Not the Result
An ordinary conversation call waits in place until the model finishes composing its response. For short replies you never notice, but for an agent that calls tools repeatedly, or a job that chews through a large input, that wait becomes a frozen UI.
A call with background: true behaves differently in one decisive way. What comes back is not the finished response but an ID that points to the run. The conversation keeps going on the server, and you go back later to ask about its state using that ID. The calling process is no longer tied to the spot.
Conceptually it is a plain shift from synchronous request/response to fire-a-job-and-poll. If you have worked with long-running operations or the Batch API, the shape will feel familiar. The difference is that the thing being deferred is now an agent's whole sequence of turns.
Writing the Polling Loop at Its Minimum
Here is the skeleton. Start a run, receive an ID, and check its state at intervals until it finishes. Because this is public preview, treat parameter names and retrieval methods as things that may change, and always check them against the current docs.
import time
from google import genai
client = genai.Client()
# 1. Start a run with background: true and get an ID immediately
run = client.agents.runs.create(
agent="antigravity-preview-05-2026",
input="Add a Japanese caption to each of the 40 images in the pending folder.",
background=True,
)
run_id = run.id
print("started:", run_id)
# 2. Poll until it finishes (widen the interval with backoff)
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) # grow from 2s up to 20s
# 3. Take the result
if current.status == "succeeded":
print(current.output)
else:
print("failed:", current.status, getattr(current, "error", None))
The point is to hold no wait time on your side. The start call returns right away, so in a web app you can hand run_id straight back to the client and split progress checks into a separate endpoint. The user's screen can advance to "we've accepted your request" the moment the button is pressed.
There is a reason I did not use a fixed one-second interval. A forty-item job is clearly nowhere near done early on. Hammering with short intervals from the start just piles up wasted status requests. Starting at two seconds and widening gradually cut my check count by more than half in practice.
Where It Pays Off in Solo Development
In my wallpaper app, the win was that I could decouple the posting flow from the generation work. Previously I could not leave the admin screen until caption generation finished. Once it was async, I could fire a run and move on to prepping the next image. Having the results reflected when I came back to the list later was enough.
The cost picture also got easier to read. The current gemini-flash-latest (which resolves to gemini-3.5-flash) is $1.50 per million input tokens and $9.00 per million output tokens. On top of that, Managed Agents also bill for sandbox runtime. When you wait synchronously, that runtime overlaps with "your own wait time," which makes the cost hard to feel. Async lets you think of generation as generation and the UI as the UI. I will leave the runtime-budget design to a separate article, but simply being able to separate them helped the forecast.
| Aspect | Synchronous call | background: true |
|---|---|---|
| What you get back | The finished response | An ID for the run |
| UI behavior | Waits until done | Moves on immediately |
| Suited for | Short, one-off replies | Many items, many steps |
| Receiving results | In place | Polling (or webhook) |
If the item count is small and the reply is short, there is no need to force async. Keeping it synchronous leaves the code plainer and easier to read. I still write one-off little queries synchronously myself. My single criterion for switching is whether the seconds you make a user wait cross the line of what is tolerable.
Credential Refresh, and a Polling Pitfall
Going async means the conversation lives on well outside your process. That makes credential handling across turns worth attention. The same 2026-07-19 update gave Managed Agents credential refresh that spans conversations. Even if an access token to an external service expires partway through, the run side can take on the refresh. The longer a run lives, the more this matters.
Remote MCP server integration landed in the same update as well. If you run a setup where the agent calls external tools asynchronously, it is worth confirming at design time that the tool-side credential does not outlive shorter than the run itself.
Here are the polling-side snags I hit, kept for the record.
| Snag | What happened | Fix |
|---|---|---|
| Interval too short | Status requests kept piling up | Widen gradually with backoff |
| Missed terminal states | Waited forever for a failed run | Break on every terminal state, not just succeeded |
| No timeout | Polling never stopped on an anomaly | Always set a max count or a deadline |
The third one is a mistake I actually made. I had written the terminal-state branch, but I had not imagined the abnormal path that never reaches it. Placing a single deadline removed any worry of a nightly job quietly running away.
Wrapping Up — Make Just One Thing Async First
background: true is a flag for turning an agent conversation into a "fire it, receive it later" shape. What returns is a run ID, not the result, and completion is picked up by polling or a webhook. It suits many-item, many-step jobs where you do not want to freeze the UI; for short one-offs, synchronous is plenty.
As a next step, pick a single job you currently make users wait on, add background: true, and try it as far as receiving the ID. The polling skeleton above works as-is. Once you feel the screen stop freezing, your judgment about what to make async gets far more concrete.
Since this is a public-preview feature, checking the Gemini API changelog for the latest parameter details is the safe way to proceed. For the design of long runs themselves, see Before a Managed Agent's Long Run Vanishes on Sandbox Recycle — Checkpoints and Idempotent Resume, and for drawing a budget around runtime, see Managed Agents Billing Isn't Just Tokens — Drawing a Budget Boundary Around Sandbox Runtime.
Thank you for reading. I am still in the trying-it-out stage myself, but the ease of a design that doesn't make people wait feels worth sharing.