Skip to main content
Middleware lets your agent intercept every LLM call and every tool call — log them, modify inputs, retry on failure, or block execution entirely.
As of PraisonAI #3086 (2026-07-16), tool middleware (before_tool / after_tool / wrap_tool_call) gates async tool calls as well — the same hooks fire around agent.astart(...) and execute_tool_async(...). No API change; existing hooks work unmodified.
The user adds hooks or wrap decorators; middleware runs on every model and tool call in the agent loop.

Quick Start

1

Add a logging middleware

2

Wrap calls for retry logic

3

Async parity

The same hooks gate async tool calls — write them once and they fire for both agent.start(...) and agent.astart(...).
Under the hood the synchronous wrap_tool_call chain runs in a thread-pool worker and the actual async tool execution is scheduled back on the event loop — so you write hooks the same way for sync and async.

How It Works

Execution order for a model call:
  1. All before_model hooks run in registration order
  2. wrap_model_call middleware chain wraps the actual LLM call
  3. The real LLM call executes
  4. All after_model hooks run in reverse registration order
The same order applies for tool calls with before_tool, wrap_tool_call, and after_tool — including async tool calls:

Configuration Options

Pass middleware as a flat list to Agent(hooks=[...]). Mix decorator types freely.
Data types available:

Middleware API Reference

TypeScript middleware configuration

Hooks Rust Reference

Rust hooks configuration

Common Patterns

Inject a system prompt on every call:
Block a specific tool:
Cache expensive tool results:

Best Practices

Every before_* and after_* hook must return the (possibly modified) object. Returning None breaks the chain and raises a runtime error.
wrap_model_call and wrap_tool_call give you full control over whether call_next is called — perfect for retries, timeouts, and feature flags. before_*/after_* hooks cannot short-circuit the call.
Agents can run concurrently. Avoid mutable global state in hooks. If you need per-request state, use request.context.metadata or Python’s contextvars.
The middleware manager checks for registered hooks before executing any code. When hooks=[] (the default), the fast path skips all hook processing entirely — for both sync (execute_tool) and async (execute_tool_async) tool calls. The async path only spins up the thread-pool middleware worker when tool-level hooks are actually registered.

Hooks

Event-based hooks for agent lifecycle events

Callbacks

UI-focused callbacks for display and streaming events