Retries and Tool Effects
Configure agent and queue retries and handle partial output safely.
Set retries on the Claude or Codex agent. Start with tools whose operations are safe to repeat.
from agno.agents.claude import ClaudeAgent
agent = ClaudeAgent(
id="support-agent",
model="claude-sonnet-5-5",
retries=2,
delay_between_retries=1,
exponential_backoff=True,
tools=[],
)
result = agent.run("Reply with OK.")
print(result.status)CodexAgent accepts the same retry settings. retries=2 allows the initial attempt plus two retries. retries=0 is the default. A returned run can have status ERROR; check status rather than treating a returned object as success. The response printers raise on error by default.
Combine agent retries with a durable queue
Agent retries happen inside a queue attempt. Queue retries can execute the run again after an interruption or a retryable failure.
| Configuration | Maximum adapter invocations |
|---|---|
Agent retries=0, queue max_attempts=1 | 1 |
Agent retries=2, queue max_attempts=1 | 3 |
Agent retries=2, queue max_attempts=3 | 9 |
The upper bound is max_attempts × (retries + 1) for retryable failures. Native SDK transport retries and multiple model/tool steps inside each invocation are additional work. This is not a token, cost or business-operation limit.
Permanent errors, including classified authentication and validation errors, are not retried. The run's error metadata carries retryability to the queue, including after a worker restart. Configure shared storage and the queue before relying on crash recovery.
For durable queued agent runs, the run-poll endpoint reports the queue ticket's status. A failed attempt awaiting another claim appears as PENDING; an executing attempt appears as RUNNING. The last component attempt's output and tool history remain available, with its status in metadata.queue.run_status.
Inspect metadata.queue.final: true means the ticket settled as completed, failed or cancelled; false means it has not settled. A PAUSED ticket requires input. null means finality is unknown, including an expired ticket on a run with a queue-attempt stamp. A queue-store read failure returns 503; retry the poll. Configure polling replicas with the shared queue store and retain tickets long enough for clients to observe the outcome. metadata.retryable describes the component error and does not prove another queue attempt will occur. An explicit operator requeue can reopen a settled ticket.
Handle streaming retries
A failed attempt can already have sent text and tool events to the client. Before retrying, the adapter emits an ExternalRunWarningEvent whose warning.type is retry. Its attempt identifies the failed attempt; attempts is the configured total, and delay is the next backoff.
For a client consuming Python events:
from agno.agents.base import ExternalRunWarningEvent
from agno.run.agent import RunCompletedEvent
for event in agent.run("Reply with OK.", stream=True):
if isinstance(event, ExternalRunWarningEvent) and event.warning:
if event.warning.get("type") == "retry":
print("The previous attempt failed; a new attempt is starting.")
elif isinstance(event, RunCompletedEvent):
print(event.content)For durable queue attempts, streamed RunError.additional_data.queue carries attempt, max_attempts and final:null. The worker has not committed its retry or settlement decision when it emits that error. Keep the stream open and poll for the durable outcome; an error event alone does not settle the queue.
In a product UI, mark the failed attempt's text as superseded or clear its provisional answer before rendering the new attempt. The service cannot retract events the client has already received. Preserve the tool audit trail: a retry warning does not undo tool effects. Use the terminal event or stored run for the final answer, and handle RunError and RunCancelled explicitly.
Make mutations idempotent
Create an operation ID in your application and preserve it across retries. The MCP service should store the operation ID, arguments and result atomically with the business mutation. Repeating the same ID and arguments returns that result; reusing the ID with different arguments should fail.
Retaining a tool result once in run history does not prove that the external operation happened once. A worker can die after the operation commits but before the result is persisted. See the runnable idempotent tool pattern in Connect Your Tools.
The durable queue serializes harness turns within a session before claiming an attempt. Waiting turns remain PENDING with attempt zero and run in queue acceptance order; waiting does not consume the retry budget. Concurrent HTTP arrival times are not an application-level ordering guarantee. If your product requires a particular conversation order, submit turns in that order and wait for acceptance. Foreground requests to a busy session return 409.