ANTIGRAVITY LABJP
Articles/Agents & Manager
Agents & Manager/2026-04-13Advanced

Resilient AI Agents in Antigravity — Retry, Circuit Breakers, and Fallback Strategies for Production

Build fault-tolerant AI agents in Antigravity with retry strategies, circuit breakers, model fallback chains, and checkpoint recovery — plus a measured look at why exponential backoff alone leaves retry storms completely intact at 200-agent scale.

antigravity456agents143error-handling2production71resilience9circuit-breaker3retry8

Premium Article

Why AI Agents Break in Production

Everything works fine when you're testing your Antigravity AI agents in development. Then you deploy to production, and things break in ways you never imagined. The LLM API returns a 429. Responses get cut off mid-stream. Token limits get hit and the agent halts in an inconsistent state.

I confronted this problem head-on three days after deploying a multi-agent pipeline to production. At 2 AM, Gemini API rate limits kicked in and five agents failed in a cascade. Recovery took four hours.

This guide shares every resilience pattern I built after that incident. Circuit breakers, retry strategies, model fallback, checkpoint design — everything you need to keep AI agents stable in production, with working code you can drop into your Antigravity project.

Designing Retry Strategies — Exponential Backoff and Jitter Done Right

AI agent API calls demand different retry strategies than traditional web services. LLM APIs take seconds to tens of seconds to respond, so naive retries cause wait times to explode. When multiple agents retry simultaneously, you get "retry storms" that make the API situation worse.

Why Fixed-Interval Retries Fail

Fixed-interval retries (say, 3 seconds every time) cause all agents to hit the API at the same instant during an outage. This is the classic "Thundering Herd" problem, and it actively delays API recovery.

Exponential Backoff with Jitter is the standard solution. You increase wait time exponentially while adding randomness to spread retries across time.

// Retry utility for Antigravity projects
// Wraps agent API calls with automatic retry logic
 
interface RetryConfig {
  maxRetries: number;        // Maximum retry attempts
  baseDelayMs: number;       // Initial wait time (milliseconds)
  maxDelayMs: number;        // Wait time ceiling
  jitterFactor: number;      // Jitter coefficient (0–1)
  retryableErrors: number[]; // HTTP status codes eligible for retry
}
 
const DEFAULT_CONFIG: RetryConfig = {
  maxRetries: 5,
  baseDelayMs: 1000,
  maxDelayMs: 60000,
  jitterFactor: 0.5,
  retryableErrors: [429, 500, 502, 503, 504],
};
 
async function withRetry<T>(
  operation: () => Promise<T>,
  config: Partial<RetryConfig> = {}
): Promise<T> {
  const cfg = { ...DEFAULT_CONFIG, ...config };
  let lastError: Error | null = null;
 
  for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error: unknown) {
      lastError = error instanceof Error ? error : new Error(String(error));
 
      // Non-retryable errors fail immediately
      const status = (error as { status?: number }).status;
      if (status && !cfg.retryableErrors.includes(status)) {
        throw error;
      }
 
      if (attempt === cfg.maxRetries) {
        break; // Exhausted all retries
      }
 
      // Exponential Backoff + Full Jitter
      const exponentialDelay = cfg.baseDelayMs * Math.pow(2, attempt);
      const cappedDelay = Math.min(exponentialDelay, cfg.maxDelayMs);
      const jitter = cappedDelay * cfg.jitterFactor * Math.random();
      const finalDelay = cappedDelay - (cappedDelay * cfg.jitterFactor / 2) + jitter;
 
      console.warn(
        `[Retry ${attempt + 1}/${cfg.maxRetries}] ` +
        `Waiting ${Math.round(finalDelay)}ms before retry. ` +
        `Error: ${lastError.message}`
      );
 
      await new Promise(resolve => setTimeout(resolve, finalDelay));
    }
  }
 
  throw new Error(
    `Operation failed after ${cfg.maxRetries} retries. Last error: ${lastError?.message}`
  );
}
 
// Usage: Apply retry logic to a Gemini API call
const response = await withRetry(
  () => fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-goog-api-key': process.env.GEMINI_API_KEY ?? '',
    },
    body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }),
  }),
  { maxRetries: 3, baseDelayMs: 2000 }
);
// Expected: On 429, retries at ~2s → ~4s → ~8s intervals, returns response on success

Exponential Backoff Alone Did Not Spread Anything Out

The code above has a jitter flag. When I first wrote it, I treated that flag as decoration. If the wait grows 2s to 4s to 8s, surely that spread is enough on its own — that was my reasoning.

I wanted to check that assumption, so I counted. The script below assumes 200 agents fail at the same moment, each retrying up to five times, and tallies retry arrival times into 250 ms buckets.

// retry-arrival.mjs — count how tightly retries cluster
// Run: node retry-arrival.mjs
const AGENTS = 200, MAX_RETRY = 5, BASE = 1000, CAP = 30000, BUCKET = 250;
 
function arrivals(kind) {
  const out = [];
  for (let a = 0; a < AGENTS; a++) {
    let t = 0, prev = BASE;
    for (let n = 0; n < MAX_RETRY; n++) {
      let d;
      if (kind === 'fixed') {
        d = 3000;                                            // fixed interval
      } else if (kind === 'exp') {
        d = Math.min(CAP, BASE * 2 ** n);                    // exponential, no jitter
      } else if (kind === 'full') {
        d = Math.random() * Math.min(CAP, BASE * 2 ** n);    // full jitter
      } else {
        d = Math.min(CAP, BASE + Math.random() * (prev * 3 - BASE)); // decorrelated jitter
        prev = d;
      }
      t += d;
      out.push(t);
    }
  }
  return out;
}
 
function peak(list) {
  const bucket = {};
  for (const t of list) {
    const k = Math.floor(t / BUCKET);
    bucket[k] = (bucket[k] || 0) + 1;
  }
  return Math.max(...Object.values(bucket));
}
 
for (const kind of ['fixed', 'exp', 'full', 'decorr']) {
  let sum = 0;
  for (let i = 0; i < 20; i++) sum += peak(arrivals(kind));
  console.log(kind.padEnd(7), 'max concurrent arrivals per 250ms:', (sum / 20).toFixed(1));
}

Averaged over 20 runs:

fixed   max concurrent arrivals per 250ms: 200.0
exp     max concurrent arrivals per 250ms: 200.0
full    max concurrent arrivals per 250ms: 77.0
decorr  max concurrent arrivals per 250ms: 36.0

The two jitter rows depend on randomness, so they shift by a few between runs — on my machine, full landed between 74 and 78, decorrelated between 35 and 38. The fixed and exp rows contain no randomness, so they come out identical every time.

The exp row is where my assumption fell apart. Adding exponential backoff left the peak at exactly 200 — identical to fixed intervals. Of course it did: every agent evaluates the same formula and produces the same delay, so they all come back together.

It gets slightly worse than that. Measured as peak relative to mean arrival density, exponential-only is worse than fixed intervals, because the arrivals compress into a handful of sharper spikes.

StrategyMax concurrent arrivals / 250msPeak relative to mean density
Fixed interval (3s)20012.0
Exponential backoff only20024.8
Full jitter778.5
Decorrelated jitter3612.9

Decorrelated jitter has the lowest absolute peak but scores worse than full jitter on the ratio column. That is because it spreads arrivals over a longer window, which lowers the mean density in the denominator. Read the absolute column when you care about instantaneous load hitting the API; read the ratio column when you are setting monitoring thresholds.

What spreads the load is the jitter, not the exponent. Exponential backoff limits how many calls a single agent makes; jitter keeps multiple agents from landing on top of each other. They solve different problems, and I had been treating them as one.

If you have code running with jitter: false right now, flipping it to true is worth doing today. That one-line change cuts the instantaneous peak load reaching your API by more than half.

Retries and Idempotency

Here's a subtlety that's easy to miss. Retrying means potentially executing the same operation multiple times. LLM queries themselves are idempotent, but when agents perform side effects — writing files, calling external APIs — retries can cause duplicate executions.

The solution is recording checkpoints before executing actions, then checking "was this already done?" on retry. We'll cover this in detail in a later section.

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
You'll be able to solve the problem of agents silently failing in production by implementing circuit breakers and intelligent retry strategies
You'll master model fallback chains, checkpoint recovery, and graceful degradation patterns that you can apply to your own products immediately
You'll build a monitoring foundation that detects and recovers from agent failures automatically — no more 2 AM incident response
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

Antigravity 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

Agents & Manager2026-05-22
Designing a 4-Tier Fallback Architecture for Antigravity Agents — Catching Model Degradation, API Outages, and Cost Overruns Across Layers
How to design a 4-tier fallback hierarchy for production AI agents on Antigravity, drawn from 24 months of running 11 agents across 6 indie apps. Includes the decision logic, code, and real demotion statistics.
App Dev2026-04-23
Keeping the Antigravity Python API Stable in Production — Retries, Timeouts, and Circuit Breakers That Actually Work
A deeply practical guide to keeping Python services built on the Google Gen AI SDK alive under real traffic. We cover retry, timeout, circuit breaker, rate limit, and cost budgeting patterns with runnable code from an Antigravity workflow.
Agents & Manager2026-07-05
Protecting Your Agent Stack's Known-Good State with a Single Lockfile — Change-Budget Design for an Era of Simultaneously Moving Parts
When the IDE build, CLI, model, and dependencies all move at once, you can no longer tell which one caused a regression. Here is a change-budget design that pins your known-good state to one lockfile, with working code and operational logs.
📚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