Skip to main content
Fault tolerance middleware keeps your deep agent running when things go wrong. Not all errors should be handled the same way: transient failures (network timeouts, rate limits) should be retried automatically, errors the LLM can recover from (bad tool output, parsing failures) should be fed back to the model, and errors that need human input should pause the agent.

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.
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 a rate_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:
Use 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:
Scope ToolRetryMiddleware to specific tools rather than retrying everything. A filesystem 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:
See ModelFallbackMiddleware for the full configuration.

Model refusals

A provider can refuse a request, for example under a safety policy, and still return a successful response. Deep Agents throw ModelRefusalError 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.