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, RetryThe 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" signalCatch 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 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 ErrorAgentclass 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 ErrorMaxTurnsclass ErrorMaxTurns(ErrorAgent):
def __init__(self, *, turn: int, limit: int) -> None: ...| Parameter | Type | Default | Description |
|---|---|---|---|
turnrequired | int | — | The turn number the loop had reached when it stopped. |
limitrequired | int | — | The configured max_turns value that was exceeded. |
Attributes
| Parameter | Type | Default | Description |
|---|---|---|---|
turn | int | — | The turn the loop reached. Same value passed to the constructor. |
limit | int | — | The 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 ErrorBudgetclass ErrorBudget(ErrorAgent):
def __init__(self, *, spent: float, limit: float) -> None: ...| Parameter | Type | Default | Description |
|---|---|---|---|
spentrequired | float | — | Total cost in USD accumulated when the loop stopped. |
limitrequired | float | — | The configured max_cost value, in USD, that was exceeded. |
Attributes
| Parameter | Type | Default | Description |
|---|---|---|---|
spent | float | — | Accumulated cost in USD. Same value passed to the constructor. |
limit | float | — | The 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 Retryclass Retry(Exception):
def __init__(self, message: str) -> None: ...| Parameter | Type | Default | Description |
|---|---|---|---|
messagerequired | str | — | The hint shown to the model. Make it actionable — say what was wrong and what to do differently. |
Attributes
| Parameter | Type | Default | Description |
|---|---|---|---|
message | str | — | The hint message. Same value passed to the constructor. |
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.
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.0Example: 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.
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
Handle any run failure in one place — the base class covers every current and future subclass.
Catch ErrorMaxTurns or ErrorBudget when you want to react differently to a turn cap versus a budget cap.
Signal a correctable problem from a validator or tool. Never catch it yourself — the loop owns it.
See how limits, retries, and the loop fit together.