Exceptions

The AgentRoute exception hierarchy — ErrorAgent and its subclasses for runtime failures, plus Retry, the control-flow signal that asks the model to try again.


AgentRoute raises a small, predictable set of exceptions. They split into two groups: real failures that subclass ErrorAgent, and one control-flow signal, Retry, that is deliberately not an error.

from agentroute import ErrorAgent, ErrorMaxTurns, ErrorBudget, Retry

The hierarchy:

Exception
├── ErrorAgent          # base for user-visible runtime failures
│   ├── ErrorMaxTurns   # loop exceeded max_turns
│   └── ErrorBudget     # accumulated cost exceeded max_cost
└── Retry               # NOT an ErrorAgent — a "try again" signal

Catch ErrorAgent to handle any agent run failure in one place. Reach for the specific subclasses only when you want to branch on why the run stopped. See Errors and retries for the conceptual model.

Retry is not an error

Retry subclasses Exception directly, not ErrorAgent. It is a signal the loop catches and acts on, not a failure you handle. Catching ErrorAgent will never catch a Retry.

ErrorAgent

ErrorAgent — the base class for every user-visible AgentRoute runtime error. Catch it to handle any failure from agent.run() or agent.arun() uniformly.

from agentroute import ErrorAgent
class ErrorAgent(Exception):
    """Base class for all user-visible AgentRoute runtime errors."""

It carries no extra attributes of its own beyond the standard Exception message. The concrete subclasses below add structured fields.

Example

from agentroute import Agent, ErrorAgent
 
agent = Agent(name="assistant", model="claude-sonnet-4")
 
try:
    result = agent.run("Plan a three-course dinner.")
    print(result)
except ErrorAgent as exc:
    # Catches ErrorMaxTurns, ErrorBudget, and any future subclass.
    print(f"Agent run failed: {exc}")

ErrorMaxTurns

ErrorMaxTurns — raised when the agentic loop runs more turns than Agent(max_turns=...) allows. Each turn is one model call plus any tool executions it requests; the limit guards against runaway loops.

from agentroute import ErrorMaxTurns
class ErrorMaxTurns(ErrorAgent):
    def __init__(self, *, turn: int, limit: int) -> None: ...
ParameterTypeDefaultDescription
turnrequiredintThe turn number the loop had reached when it stopped.
limitrequiredintThe configured max_turns value that was exceeded.

Attributes

ParameterTypeDefaultDescription
turnintThe turn the loop reached. Same value passed to the constructor.
limitintThe max_turns limit that was hit.

The string message reads Agent exceeded max_turns: reached turn {turn} (limit={limit}).

Example

from agentroute import Agent, ErrorMaxTurns
 
# A low cap makes the limit easy to hit on a multi-step task.
agent = Agent(name="researcher", model="claude-sonnet-4", max_turns=2)
 
try:
    agent.run("Research and summarize five recent papers, one tool call each.")
except ErrorMaxTurns as exc:
    print(f"Stopped at turn {exc.turn} of {exc.limit}.")
    # Stopped at turn 2 of 2.

ErrorBudget

ErrorBudget — raised when the run's accumulated cost exceeds Agent(max_cost=...). Cost is summed across every model call in the loop (see Context and usage); when it crosses the limit, the loop stops with this error.

from agentroute import ErrorBudget
class ErrorBudget(ErrorAgent):
    def __init__(self, *, spent: float, limit: float) -> None: ...
ParameterTypeDefaultDescription
spentrequiredfloatTotal cost in USD accumulated when the loop stopped.
limitrequiredfloatThe configured max_cost value, in USD, that was exceeded.

Attributes

ParameterTypeDefaultDescription
spentfloatAccumulated cost in USD. Same value passed to the constructor.
limitfloatThe max_cost limit, in USD, that was hit.

The string message reads Agent exceeded max_cost: spent=$X.XXXX (limit=$Y.YYYY).

Example

from agentroute import Agent, ErrorBudget
 
agent = Agent(name="assistant", model="claude-sonnet-4", max_cost=0.01)
 
try:
    agent.run("Write a detailed 2000-word market analysis.")
except ErrorBudget as exc:
    print(f"Spent ${exc.spent:.4f}, budget was ${exc.limit:.4f}.")

Retry

Retry — a control-flow signal, not a failure. Raise it from an output validator or from a tool body to tell the model to try again with a hint. The loop catches it, formats message as a tool-role message, and continues until agent.retries is exhausted.

from agentroute import Retry
class Retry(Exception):
    def __init__(self, message: str) -> None: ...
ParameterTypeDefaultDescription
messagerequiredstrThe hint shown to the model. Make it actionable — say what was wrong and what to do differently.

Attributes

ParameterTypeDefaultDescription
messagestrThe hint message. Same value passed to the constructor.
Retries are bounded

The loop retries until Agent(retries=...) is exhausted. Once retries run out, the underlying failure surfaces normally. Set retries high enough to give the model room to correct, but not so high that it loops on an impossible constraint.

Example: validate structured output

Raise Retry from an @agent.output_validator to reject an output and ask the model to fix it. The validator receives the run Context and the parsed output model.

retry_validator.py
from pydantic import BaseModel
from agentroute import Agent, Context, Retry
 
class Invoice(BaseModel):
    customer: str
    total: float
 
agent = Agent(name="biller", model="claude-sonnet-4", output=Invoice, retries=3)
 
@agent.output_validator
def positive_total(ctx: Context, output: Invoice) -> Invoice:
    if output.total <= 0:
        raise Retry("total must be a positive amount in USD; re-read the line items.")
    return output
 
result = agent.run("Bill Acme Corp for two seats at $40 each.")
print(result.output.total)  # 80.0

Example: validate tool input

You can also raise Retry from inside a tool when the model passes arguments you cannot act on. The hint goes back to the model, which can call the tool again with corrected arguments.

retry_tool.py
from agentroute import Agent, tool, Retry
 
@tool
def transfer(amount: float, to_account: str) -> str:
    """Transfer USD to an account number."""
    if amount <= 0:
        raise Retry("amount must be greater than zero.")
    if not to_account.isdigit():
        raise Retry("to_account must be digits only, no spaces or dashes.")
    return f"Transferred ${amount:.2f} to {to_account}."
 
agent = Agent(name="teller", model="claude-sonnet-4", tools=[transfer], retries=3)
result = agent.run("Send 50 dollars to account 1234-5678.")
print(result)

Choosing what to catch

Catch ErrorAgent

Handle any run failure in one place — the base class covers every current and future subclass.

Branch on subclasses

Catch ErrorMaxTurns or ErrorBudget when you want to react differently to a turn cap versus a budget cap.

Raise Retry

Signal a correctable problem from a validator or tool. Never catch it yourself — the loop owns it.

Read the concept guide

See how limits, retries, and the loop fit together.

Source

exceptions.py