GEMINI LABJP
ROBOTICS — Gemini Robotics ER 2 is in public preview, covering spatial reasoning, agentic code execution, and multi-robot coordinationSTREAMING — gemini-robotics-er-2-streaming-preview targets real-time streaming over the Live API, with bidirectional audio and video inputSUNSET — Shutdown dates are close: Imagen models on August 17 (gemini-3.1-flash-image is the successor), the Grok 4.1 family on the 20th, and gemini-robotics-er-1.6-preview on the 31stSAMPLING — temperature, top_p, and top_k are now deprecated. If your app exposes them as user settings, that surface needs rethinkingLOGS — The Interactions API now supports developer logs, viewable from the AI Studio dashboardMODELS — Gemini 3.1 Pro remains in preview. 3.6 Flash reached GA on July 21, and 3.5 Flash-Lite suits high-volume subagent workROBOTICS — Gemini Robotics ER 2 is in public preview, covering spatial reasoning, agentic code execution, and multi-robot coordinationSTREAMING — gemini-robotics-er-2-streaming-preview targets real-time streaming over the Live API, with bidirectional audio and video inputSUNSET — Shutdown dates are close: Imagen models on August 17 (gemini-3.1-flash-image is the successor), the Grok 4.1 family on the 20th, and gemini-robotics-er-1.6-preview on the 31stSAMPLING — temperature, top_p, and top_k are now deprecated. If your app exposes them as user settings, that surface needs rethinkingLOGS — The Interactions API now supports developer logs, viewable from the AI Studio dashboardMODELS — Gemini 3.1 Pro remains in preview. 3.6 Flash reached GA on July 21, and 3.5 Flash-Lite suits high-volume subagent work
Articles/API / SDK
API / SDK/2026-08-08Advanced

A Timeout Was Never Evidence of Failure — Designing Around Blocking Function Calls

When a tool call cannot return until the real-world effect finishes, two habits reverse on you: parallel dispatch and generous timeouts. Measured numbers from a sandbox harness, plus the design that replaces retries with observation.

Gemini API207Function Calling17Agent DesignTimeouts2Idempotency3

Premium Article

I was reading a robotics model card when one sentence stopped me. Function calls, it said, support blocking behavior aligned with physical robot actions.

Nothing to do with me, I thought. I own no robot arms. But the sentence would not let go.

Plenty of my own automation has to wait until the thing is actually done.

Submitting a build to App Store Connect. Batch-converting wallpaper assets. Verifying a purchase receipt. None of them finish at the moment you call them, and while the caller waits, the outside world keeps changing. Structurally, that is the same problem as a moving arm.

So I built a harness and measured it. The short version: the two habits I had trusted most — parallel dispatch and comfortably generous timeouts — both worked against me for this class of tool.

Tools that cannot return early are everywhere

"Blocking behavior" is robotics vocabulary, but the conditions behind it are ordinary once you abstract them.

ConditionWhat it meansExample from indie development
The effect lives outsideThe point is the external state change, not the return valueBuild submission, email delivery, payment capture
It cannot be recalledOnce started, the caller has no way to stop itAsset conversion jobs, push notification fan-out
It cannot overlapTwo of them must never touch the same resource at onceWriting the same file, reserving the same inventory

Any tool matching two or more of these, I now tag as a blocking tool and treat separately. Its design assumptions have nothing in common with an ordinary tool — search, summarize, look up.

An ordinary tool that fails can simply be called again. For a blocking tool, calling it again is itself the incident.

Reproducing it locally with a minimal harness

I wanted this structure isolated, without a robot or a cloud bill. The harness writes to a file instead of moving anything, and counts how often two supposedly exclusive intervals overlap.

import asyncio, time
 
class Actuator:
    """Stands in for a physical action: observably busy from start to finish."""
    def __init__(self):
        self.busy = False
        self.overlaps = 0   # times two exclusive intervals overlapped
        self.effects = 0    # effects that actually landed in the outside world
 
    async def move(self, action_id: str, dur: float) -> None:
        if self.busy:
            self.overlaps += 1
        self.busy = True
        try:
            await asyncio.sleep(dur)          # the real motion / external job
            self.effects += 1
            with open("effects.log", "a") as f:
                f.write(action_id + "\n")
        finally:
            self.busy = False
 
 
async def blocking_call(act: Actuator, action_id: str, dur: float, timeout: float | None = None) -> str:
    """A tool call. The timeout only ends our waiting; it does not stop the action."""
    task = asyncio.create_task(act.move(action_id, dur))
    if timeout is None:
        await task
        return "ok"
    try:
        # shield is the whole point. Plain wait_for cancels the task it waits on,
        # but real physical actions and external jobs cannot be cancelled.
        await asyncio.wait_for(asyncio.shield(task), timeout)
        return "ok"
    except asyncio.TimeoutError:
        return "timeout"    # the call returns; the action continues

That asyncio.shield line matters more than it looks. Written naively, asyncio.wait_for cancels the task it is waiting on — which would simulate a world where giving up on the wait also stops the work.

A real arm keeps moving after you stop watching. So does a payment capture, and so does a build upload. Shield is how I made the code honest about that asymmetry.

This was also the trap I fell into first. Measured without shield, the harness reports zero duplicate executions across the board — which looks like proof that the design is safe.

All it proves is that the test obeys cancellation. In production, aborting an HTTP request does not stop the server from finishing the work, and giving up on a subprocess does not stop the process. Cancellation only ever applied to the local await.

So my recommendation, whenever you touch a blocking tool, is to suspect your own abstraction before you trust the result. When the numbers look good from the very first run, the model is usually the thing that is too forgiving.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Across three timeout thresholds, 97-100% of fired timeouts turned out to be actions that had already succeeded
Dispatching four resource-sharing tools in parallel cut wall clock from 1203ms to 301ms — and produced 3 forbidden overlaps
Swapping retry for observation took duplicate executions from 30 to 0, at a median cost of 67ms of extra waiting
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API / SDK2026-07-18
Keeping a Long-Running Managed Agent Alive Across Sandbox Recycling — Durable Checkpoints and Idempotent Resume
A Managed Agents sandbox can be recycled out from under you. Before 40 minutes of work resets to zero, we design a durable checkpoint that pushes progress outside the sandbox and an idempotent resume that never runs a side effect twice. With working SQLite code.
API / SDK2026-06-30
Fire-and-Forget on a Cron That Never Loses a Result: Reclaiming Gemini Background Executions with a Submission Ledger
A design for running the Interactions API's background execution safely from a cron-driven runner. We reserve a row in a ledger by idempotency key before submitting, then reclaim only outstanding handles on the next tick — shown with working code.
API / SDK2026-05-18
Why Your Apps Script Stops Mid-Batch When Calling the Gemini API — UrlFetchApp Timeouts and the 6-Minute Execution Limit
When Apps Script calls the Gemini API, two limits collide: UrlFetchApp's response timeout and the 6-minute script runtime cap. Here is how to tell them apart and how I work around them with chunking, checkpoints, and time-based triggers.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →