Advanced12 min
Custom Middleware
Build custom middleware with node-style hooks (before/after) for state updates and wrap-style hooks (wrap_model_call, wrap_tool_call) for retry, caching, and request mutation. Use request.override() to change the model or tools per call.
Quick Reference
- →@before_model(can_jump_to=['end']) — early exit hook with jump support
- →return {'jump_to': 'end'} — exit the agent loop from any hook
- →request.override(model=model, tools=tools) — mutate model call in wrap_model_call
- →ExtendedModelResponse(response, Command(update={...})) — write state from wrap_model_call
- →abefore_model / aafter_model — async versions of all node hooks
Two Hook Styles
Middleware has two fundamentally different hook styles. Node-style hooks run at discrete points (before/after) and return state diffs. Wrap-style hooks surround the actual call and give you control over whether and how many times the underlying operation runs.
| Hook | Style | When it runs |
|---|---|---|
| @before_agent | node | Once before the agent loop starts |
| @before_model | node | Before each model call |
| @after_model | node | After each model response |
| @after_agent | node | Once after the agent loop ends |
| @wrap_model_call | wrap | Around each model call — you call handler() |
| @wrap_tool_call | wrap | Around each tool call — you call handler() |
Node-style vs wrap-style side by side