Middleware - PraisonAI
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 #4334 (2026-08-25), model middleware (@before_model / @after_model / @wrap_model_call) now fires on every model call for both sync (agent.start, agent.chat) and async (agent.astart, agent.achat) paths. Prior to this fix only tool hooks were wired — model hooks registered but never ran. If you defined model hooks that “seemed to do nothing” before, they will now execute. Zero-overhead fast path preserved: agents with no model hooks bypass the middleware chain 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
Under the hood:
  • _chat_completion_with_retry / _achat_completion_with_retry build a ModelRequest (with InvocationContext), pass it through MiddlewareManager.execute_model_call, and unwrap the returned ModelResponse back to the agent’s expected return shape.
  • The retry/backoff logic is the final handler of the middleware chain — so if a hook wraps call_next, it wraps the retry loop.
  • before_model / wrap_model_call mutations to request.messages, request.temperature, and request.tools are threaded into the retry core.
  • after_model / wrap_model_call edits to response.content win over the raw provider payload; unchanged content passes the original object through untouched.
  • The async path runs the sync middleware chain in an executor and bridges back to the async retry core via asyncio.run_coroutine_threadsafe — hooks are written the same way for sync and async.
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

Short-circuit the model with a cached response:
Inject a system prompt on every call:
Block a specific tool:
Cache expensive tool results:
Gate a tool through an external policy engine:
See HOL Guard Tool Policy for a complete fail-closed integration with an external security engine.

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 model calls (_chat_completion_with_retry / _achat_completion_with_retry) and tool calls (sync execute_tool and async execute_tool_async). Agents with no model hooks call the retry core directly, and the async path only spins up the thread-pool middleware worker when hooks are actually registered.

Hooks

Event-based hooks for agent lifecycle events

Callbacks

UI-focused callbacks for display and streaming events