Chapter 14

Retry Only Where a Retry Is Safe

Not every error is cured by a retry. A 429, a temporary 5xx, or a network drop are usually retryable. A schema error, a wrong key, and a policy denial aren't fixed by a retry - retrying them only burns the budget. And every write gets an idempotency key and a verifiable business transaction, because "retry a payment" and "retry a read" are fundamentally different things.

Backoff with jitter is applied only to genuinely temporary errors.

TypeScript
function retryableStatus(status: number) {
  return status === 408 || status === 409 || status === 429 || status >= 500;
}

async function withBackoff<T>(operation: () => Promise<T>) {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    try {
      return await operation();
    } catch (error) {
      if (!isProviderError(error) || !retryableStatus(error.status)) throw error;
      if (attempt === 2) throw error;
      const jitter = Math.floor(Math.random() * 180);
      await delay(250 * 2 ** attempt + jitter);
    }
  }
  throw new Error("unreachable");
}

More important than the retry itself is what the runtime shows the model in response to different errors.

Error · Runtime action · What the model sees

  • Invalid tool arguments - Doesn't run the handler; invalid_arguments, at most one correction
  • Source temporarily unavailable - A bounded retry; A normalized tool error
  • Order doesn't belong to the actor - Returns found: false; No reason revealing someone else's order exists
  • Preview expired - Doesn't run commit; A new calculation through a new turn
  • Provider timeout - Closes the run, keeps the trace id; No repeated side effect

Note that someone else's order returns found: false without a reason that reveals its existence, and an expired preview never rolls through to commit - a new calculation goes through a new turn. This isn't about reliability but about security.

Keep the request ids. The official SDKs give typed errors and request identifiers. Record them next to your trace id, but don't show internal details to the end user.

The agent's core is assembled - contract, tools, loop, budget, journal, outcomes, streaming, errors. It's time to build a live product out of it: the store support agent.

Links