◉GEMINI LABJP
●CLI 0.63.0 — Gemini CLI 0.63.0 (Oct 6) shows retry progress on reconnect, tells a missing MCP config from broken JSON, and caps tool output in long agent loops●10/29 — 22 days until the image model gemini-3.1-flash-image shuts down. Move to gemini-nano-banana-2.1, added on Oct 6●10/22 — 15 days until the three Veo 3.1 models shut down. The place to move is gemini-omni-1.1-flash●NOTICE — A Zenn post reports that a plugin's admin screen said nothing when an AI model was retired. How to avoid missing such notices is the real question●NEW — Three Veo 3.1 previews stop on 10/22. Before swapping them, here is how to inventory every place that calls them●CLAUDE — Zenn keeps getting posts on handing Codex or Claude Code writing to Gemini. The real question is which jobs to hand over and which to keep●CLI 0.63.0 — Gemini CLI 0.63.0 (Oct 6) shows retry progress on reconnect, tells a missing MCP config from broken JSON, and caps tool output in long agent loops●10/29 — 22 days until the image model gemini-3.1-flash-image shuts down. Move to gemini-nano-banana-2.1, added on Oct 6●10/22 — 15 days until the three Veo 3.1 models shut down. The place to move is gemini-omni-1.1-flash●NOTICE — A Zenn post reports that a plugin's admin screen said nothing when an AI model was retired. How to avoid missing such notices is the real question●NEW — Three Veo 3.1 previews stop on 10/22. Before swapping them, here is how to inventory every place that calls them●CLAUDE — Zenn keeps getting posts on handing Codex or Claude Code writing to Gemini. The real question is which jobs to hand over and which to keep
Articles/API / SDK
◈ API / SDK/2026-06-30Advanced

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.

Gemini API245Interactions API5Background ExecutionIdempotency3Automation14

✦ Premium Article

Background execution in the Interactions API reaching GA finally makes the "submit now, collect later" shape easy to write. I run an article-generation pipeline on a schedule as an indie developer, and there's one awkward fact baked into that setup: a cron-launched runner exits entirely after it submits its work.

So neither a long-lived process waiting on a webhook, nor a loop polling for done, fits a scheduled runner. A process that goes launch → submit → exit, over and over, has to answer one question: when and where does it ever collect the finished result? Skip that question and background execution just manufactures jobs that wander off and never come back.

Let's build a "reclaim ledger" that recovers results reliably across cron ticks — without any long-lived process and without a public webhook endpoint.

Why a ledger, not polling or webhooks

The three ways of collecting results each assume a different execution model. It's worth laying the differences out.

ApproachAssumed execution modelFit with a scheduled runner
Polling in a loopStay resident until the job finishesPoor (the runner exits, so it can't wait)
Receiving a webhookKeep a public endpoint listening at all timesPoor (a resident receiver — heavy to operate solo)
Record to a ledger, reclaim next tickKeep state outside the process; reconcile on each launchGood (launch → reconcile → exit, self-contained)

The idea is to keep a list of submitted jobs somewhere that survives the runner's exit, and on the next launch, look at that list and go fetch only the ones not yet collected. Where a webhook means "the other side tells you," a ledger means "you remind yourself when you next wake up." For solo operation, not having to keep a public receiver alive makes it noticeably harder to break.

The crux is the order of "submit" and "ledger write"

The naive version looks like this.

# Anti-pattern: submit first, then write to the ledger
op = client.interactions.create(model="gemini-flash-latest", input=payload, background=True)
ledger.insert(handle=op.name, status="submitted")   # <- what if it crashes here?

If the process dies between create succeeding and ledger.insert, you get a state where the job exists on the API side but the ledger has no handle for it. That's an orphan handle. The next tick looks at the ledger, doesn't find it, and never reclaims it. You're billed for work whose result is thrown away — exactly what we want to avoid.

So flip the order. Write a reservation row by idempotency key first, then submit, then update the reservation with the returned handle.

# Two-phase commit: reserve -> submit -> bind handle
idem = idempotency_key(job)          # same logical job -> same key
ledger.reserve(idem)                  # reserve with status="reserving" (skip if it exists)
op = client.interactions.create(..., background=True)
ledger.bind_handle(idem, op.name)     # status="submitted" + handle bound

With this order, the accounting holds no matter where it dies. If only the reservation remains and nothing was submitted, the recovery path detects "reserved but not submitted" and resubmits. If it was submitted but the handle was never bound, orphan recovery (below) picks it back up.

✦

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
✦A two-phase commit that writes a reservation row by idempotency key before submitting, preventing double submits and lost handles at once
✦A reclaim loop backed by a single SQLite file that, on each cron tick, queries only outstanding handles and hands finished results downstream exactly once
✦A recovery path that picks up orphan handles created in the gap between a successful submit and the ledger write
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 $15 for lifetime access
View Membership →

Related Articles

◈ API / SDK2026-08-08
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.
◈ API / SDK2026-09-29
The Size Column Said 'F6' and 'About A4' — I Let Gemini Read It, and Let a Table Do the Math
An artist's spreadsheet mixed 'F6', '41×31.8cm', and 'about A4' in one size column. Here is the small tool I built: Gemini reads each cell, a lookup table converts to centimeters, and the artwork image decides the orientation. Working code, step by step.
◈ API / SDK2026-09-22
When Function Calls Turn Into Plain Text, Suspect the Property Names in Your Tool Declarations
A record of chasing MALFORMED_FUNCTION_CALL after output limits and schema conflicts were ruled out, bisecting nine tool declarations down to one property name, and adding a linter that stops bad names before they ship.
📚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