Skip to main content
ExecutionConfig retry settings re-run retryable tool failures and guardrail validation errors with exponential backoff and jitter (for tools that have not declared themselves unsafe to re-run — see Retrying Non-Idempotent Tools). For tools, max_retry_limit / retry_* are translated into the effective RetryPolicy — the single tool-retry budget shared with ToolConfig(retry_policy=...).
The user requests a report; transient tool failures retry with exponential backoff and jitter until success or the limit is reached.

Quick Start

1

Enable with defaults

Retries with backoff are automatic when you set max_retry_limit:
2

Fine-tune backoff

Use ExecutionConfig for delay, factor, and jitter:

How It Works

Total attempts = 1 + max_retry_limit. Default max_retry_limit=2 → up to 3 attempts. Delay: min(initial_delay × factor^(attempt−1), 60s) + random(0, jitter × base). The translation maps max_retry_limitmax_attempts (= limit + 1), retry_initial_delay (seconds) → initial_delay_ms, retry_backoff_factorbackoff_factor, and retry_jitter (fraction) → jitter=True + jitter_factor. The translated policy sets max_delay_ms = max(60000, initial_delay_ms), so ExecutionConfig users keep the historical 60-second delay cap (a plain RetryPolicy defaults to a 30-second cap).

Choosing Your Settings


Configuration

These fields are one spelling of the tool-retry budget. An explicit ToolConfig(retry_policy=...) (or per-tool @tool(retry_policy=...)) wins over the ExecutionConfig alias. See Tool Retry Policy.

What Gets Retried

A wall-clock timeout is retried only when the tool is idempotent — unknown tools default to non-idempotent (safe). Opt in with .idempotent = True; see Tool Timeouts → Retry-on-timeout is opt-in.
A tool that explicitly declares itself unsafe (restart_safe=False or .idempotent = False) is vetoed from every retryable error, not just timeouts. Merely sharing a name with an entry in MUTATING_TOOLS does not count as a declaration — undeclared tools always retry per policy. MUTATING_TOOLS still gates the wall-clock-timeout retry (see Tool Timeouts).

Retrying Non-Idempotent Tools

Fixed in PR #4283. Between PRs #4257 and #4283, an undeclared tool whose name happened to match an entry in MUTATING_TOOLS (e.g. write_file, store_memory, mkdir) silently ran once instead of retrying. Only explicit restart_safe=False / .idempotent = False declarations veto the retry now.
Async parity fixed in PR #4299 (issue #4298). Before #4299, @tool(restart_safe=False) / .idempotent = False was ignored on the async retry path — agent.achat() / agent.astart() / agent.execute_tool_async() re-ran the tool body up to max_attempts times (default: 3× on a card-charging tool, an email tool, a POST). The declaration was already honoured on the sync path. Both paths now run declared-unsafe tools exactly once.
A tool that declares itself unsafe to re-run is executed exactly once, even when the error would otherwise be retryable — the body already ran and may have completed its side effect before raising, so retrying would duplicate it. Declare it on the decorator and the retry budget is ignored for that tool:
The declaration is discovered in this order (first match wins):
  • tool.idempotent set to a bool on the tool object.
  • tool.restart_safe — the public @tool(restart_safe=...) / BaseTool.restart_safe contract.
  • When ToolConfig(allow_global_tools=True) is set, a matching global-registry tool’s idempotent / restart_safe attribute (parity with idempotency lookup).
What is not a declaration: merely sharing a name with an entry in MUTATING_TOOLS (a 73-name registry used elsewhere for the escalation loop guard) is not an author declaration and does not disable retries. To opt an unsafe tool out of retries, mark it explicitly with @tool(restart_safe=False) or .idempotent = False.
This is the same declaration honoured by durable resume: an in-flight restart_safe=False tool returns NotSafelyResumable, and a failing one is not re-driven here. See Durable Runs → Restart-Safety Contract.
Task-scoped tools inherit the veto. A tool supplied only through the task’s tools_override (never registered on the agent) still has its restart_safe=False / idempotent=False declaration honoured. Task-scoped tools shadow same-named agent tools, so if you attach a safer version at task time it wins over the agent-level tool. (PR #4299)

Common Patterns

Disable retries:
Rate-limited APIs:
Low-latency tools:

Best Practices

Jitter spreads retry timing across agents and reduces thundering-herd spikes on shared APIs.
ValueError, TypeError, and AttributeError are treated as code bugs and are not retried.
A tool that sends, writes, charges, or deletes should declare @tool(restart_safe=False) (or set .idempotent = False). The tool body runs before it can raise, so retrying after a transient error would duplicate the side effect. Declared-unsafe tools run exactly once regardless of max_retry_limit.
Very large backoff factors cannot exceed a 60-second base delay per attempt. The cap only matters when retries actually happen — declared-unsafe tools do not retry.
Guardrail validation retries use the same ExecutionConfig backoff values. This is independent of tool retry — the fields still feed guardrail-retry backoff even when a ToolConfig(retry_policy=...) overrides the tool budget.
For tools, ExecutionConfig.retry_* is an alias translated into the effective RetryPolicy — the same budget as ToolConfig(retry_policy=...). Setting an explicit retry_policy overrides the ExecutionConfig spelling.

ExecutionConfig

Full execution configuration reference

Guardrails

Input and output validation

Loop Guardrails

Cap tool calls per turn

Structured LLM Errors

LLM-level retry and error handling