Quick Start
1
Enable with defaults
Enable retry for all tools with safe defaults:
2
Tune attempts and backoff
Configure specific retry behavior:
3
Override per tool
Different tools may need different retry strategies:
How It Works
An error is tagged
rate_limit when its message contains either "rate…limit" or "too many requests" — the latter catches HTTP 429 responses whose bodies use the standard phrasing without the word “limit”:
Sync / async parity
RetryPolicy applies identically on the sync path (agent.execute_tool(...), agent.start(...)) and the async path (agent.execute_tool_async(...), agent.achat(...)) — including tools that raise exceptions.
Fixed in PR #4141. Before that release,
agent.achat(...) would silently not retry a raised exception, even when ToolConfig(retry_policy=RetryPolicy(max_attempts=N)) was set. The sync path already retried; the async path returned on the first attempt.
On the async path a raised exception is flattened into an error dict carrying the private
_praison_retryable verdict, then the retry loop honours it and strips it before returning:
Your tool’s own retryable field is safe
Many HTTP-API wrappers return payloads shaped like {"error": "...", "retryable": true}. That field is the tool’s payload and is preserved untouched on the surfaced result. The framework’s own verdict lives on the private _praison_retryable key, which is stripped before the result reaches the model or the caller — the two never collide.
To make a returned error retry, tag its error type via RetryPolicy(retry_on=...). A word in the tool’s payload is not how retry is controlled.
Returned error dicts run once
A tool that returns an error dict has already reached its own decision — the body ran to completion. Re-running it would duplicate side effects (charge money, send a message, write a row) for a decision the tool already made. The sync outer loop therefore surfaces such a dict exactly once, regardless of what fields it contains. To make a returned error retry-eligible, register its error type viaRetryPolicy(retry_on={...}) — the inner loop honours that classification, and the outer loop will not double-drive it.
RetryPolicy is the single budget
The effectiveRetryPolicy is the one budget for tool-body runs. RetryPolicy.max_attempts genuinely caps how many times a tool runs, and the ExecutionConfig spelling (max_retry_limit / retry_*) is translated into that same policy in one place.
ExecutionConfig.retry_* and ToolConfig(retry_policy=...) are two spellings of one budget, not two separate loops. See Tool Retry & Backoff and ExecutionConfig for the ExecutionConfig spelling.Precedence Ladder
When noretry_policy is set, the fallback is the policy translated from ExecutionConfig (max_retry_limit / retry_*), which defaults to RetryPolicy(max_attempts=3) when ExecutionConfig is untouched. Users who never set retry_policy= see no behaviour change — the translated policy reproduces max_retry_limit + 1 attempts and the same backoff.
retry_policy=None on @tool(...) means use the fallback — the tool-level slot is treated as empty, not as “no retries”. To disable retries for a specific tool, pass an explicit RetryPolicy(max_attempts=1) instead.
Tool-level (highest priority):
ExecutionConfig fallback (lowest priority):
Choosing a Retry Policy
Configuration Options
Non-retryable error types (always short-circuit):
approval_denied,permission_denied,approval_error,circuit_open,loop_blocked- Python exceptions:
ValueError,TypeError,AttributeErrorfrom tool code - Raised
ValueError/TypeError/AttributeErrorfrom tool code are terminal on both sync and async paths. - Argument-binding errors (missing/extra parameters resolved from the schema) are terminal — they never retry regardless of policy.
Common Patterns
Per-tool override for unreliable API
YAML configuration
In YAML the field name is still
tool_retry_policy:; in Python pass retry settings through tool_config=ToolConfig(retry_policy=…). The standalone tool_retry_policy kwarg on Agent(...) was removed and raises TypeError.CLI usage
If the retry backend isn’t available (the CLI accepted the flags but the runtime dependency is missing), you’ll now see a
tool_retry_policy requested but retry backend unavailable: <ImportError> warning in the logs instead of the settings being silently dropped. Install the retry backend or drop the --tool-retry-* flags to clear the warning.Hook Integration
Monitor retry attempts with hooks:OnRetryInput:
tool_name: Name of the failing toolattempt: Current attempt number (1-based)max_attempts: Maximum attempts configureddelay_ms: Delay before this retry in millisecondserror_type: Classified error type (timeout,rate_limit, etc.)error: Original exception object
Best Practices
Keep max_attempts small (3-5)
Keep max_attempts small (3-5)
Large retry counts mask real failures. If a tool fails 10+ times, there’s likely a deeper issue that retrying won’t solve. Use monitoring instead.
Always set jitter=True for rate-limited APIs
Always set jitter=True for rate-limited APIs
Without jitter, multiple agents retrying simultaneously create a “thundering herd” that can overwhelm rate-limited services. Jitter spreads out retry attempts.
Set narrower retry_on for expensive tools
Set narrower retry_on for expensive tools
Don’t retry LLM tools on
connection_error if every attempt costs money. Use specific error types that indicate transient failures.Use tool-level override sparingly
Use tool-level override sparingly
Agent-level retry policy keeps configuration DRY. Only override at the tool level for genuinely special cases like unreliable third-party APIs.
Related
Tool Configuration
Consolidated tool configuration with ToolConfig
Concurrency
Parallel tool execution and timeouts
Hooks
Monitor and intercept agent behavior
Hook Events
Complete reference of hook events

