Error handling strategies
Different errors need different handling strategies:
| Model refusals | System or developer | Fall back to another model, then catch the error | modelFallbackMiddleware, modelRefusalMiddleware |
| User-fixable errors (missing information, unclear instructions) | Human | Pause with
interrupt() | Human-in-the-loop |
| Provider outage | System (automatic) | Fall back to an alternative model | ModelFallbackMiddleware |
| Excessive calls (runaway loops) | System (automatic) | Cap model and tool calls per run | ModelCallLimitMiddleware, ToolCallLimitMiddleware |
| Unexpected errors | Developer | Let them bubble up | No middleware; let the exception propagate |
The sections below cover each strategy with code examples.
- Transient errors
- User-fixable
- Provider outage
- Excessive calls
- Unexpected
Add retry middleware to automatically retry network issues and rate limits. Model calls and tool calls each have their own retry middleware with exponential backoff:
Rate limiting
There are two complementary ways to limit resource usage: controlling the request rate to your model provider, and capping the total number of calls per run.Provider rate limiting
Chat model providers impose a limit on the number of invocations that can be made in a given time period. To control the rate at which requests are made, initialize your model with arate_limiter:
Call limits
Without limits, a confused agent can burn through your LLM API budget in minutes by looping on the same tool call or making hundreds of model calls. Set caps on both model calls and tool executions per run:run_limit to cap calls within a single invocation (resets each turn). Use thread_limit to cap calls across an entire conversation (requires a checkpointer). See ModelCallLimitMiddleware and ToolCallLimitMiddleware for the full configuration.
Retries
Transient failures (network timeouts, rate limits) should be retried automatically. Model calls and tool calls each have their own retry middleware with exponential backoff:read_file that fails will not benefit from a retry, but a web search that times out probably will. See ModelRetryMiddleware for the full configuration.
Fallbacks
If your primary model provider goes down entirely, the fallback middleware switches to an alternative model:Model refusals
A provider can refuse a request, for example under a safety policy, and still return a successful response. Deep Agents throwModelRefusalError when the model refuses, so the run does not look like a successful run even though it was a refusal. modelRefusalMiddleware runs after your middleware. A modelFallbackMiddleware you pass therefore retries the refused request on the next model first. The run throws only when no fallback is left. The fallback model can be from the same provider or a different one, and it can still refuse the same request.
Model-level withFallbacks does not catch refusals, because the middleware detects them after the model call returns. Use modelFallbackMiddleware instead.
When every model refuses, catch ModelRefusalError where you invoke the agent. The refused reply is not saved to the thread, so you can tell the user and continue the conversation. For this and other options, see Handle a refusal. For how subagent refusals reach the parent agent, see Model refusals.
Throwing on model refusals by default requires
deepagents>=1.16.4.Error handling
When a tool raises an exception during execution, the agent run halts by default.ToolErrorMiddleware is not yet available in the JavaScript SDK.
Connect these docs to your agent of choice via MCP for real-time answers.

