Hook into agent lifecycle
React to events in your agent's execution flow. Add logging, monitoring, reflection, and custom behavior at every step.
9 Event Types
Quick Start
Add event handlers to your agent in 3 simple steps:
Tip: Event handlers receive the agent instance, giving you full access to current_session, messages, trace, and more.
Group multiple handlers
You can pass multiple handlers to the same event type:
Note: Decorator Syntax
You can also use @before_each_tool decorator instead of before_each_tool(fn). We recommend wrapper style because it's easier for LLMs to understand when reading your code. But if you prefer decorators, they work too.
All Event Types
Here's when each event fires and what you can do with it:
after_user_input
Fires once per turn, after user input is added
after_llm
Fires after each LLM response (multiple times per turn)
before_each_tool
Fires before EACH individual tool execution
before_tools
Fires ONCE before ALL tools in a batch
after_each_tool
Fires after EACH tool (logging only, NOT for messages)
WARNING: Do NOT add messages here! This breaks Anthropic Claude's API message ordering.
after_tools
Fires ONCE after ALL tools complete (safe for messages)
SAFE: This is the correct place to add reflection messages after tools.
on_error
Fires when tool execution fails or tool not found
on_complete
Fires once after agent finishes task
Combining Multiple Events
Use multiple event handlers together for comprehensive monitoring and control:
Key Concepts
Event Handler Signature
All event handlers receive the agent instance:
Message Injection Timing
Important: Use after_tools to inject messages after tool execution:
❌ Don't use after_each_tool: Injecting messages during tool execution breaks Anthropic Claude's message sequence (all tool_results must follow tool_use)
✅ Use after_tools: Fires once after ALL tool results are added to messages, safe for reflection injection
Error Handling
Event handlers follow fail-fast principle:
Real-World Use Cases
1. Performance Monitoring Dashboard
2. Automatic Context Injection
3. Smart Retry Logic
API Reference
Event Wrapper Functions
after_user_input(func: Callable[[Agent], None]) → EventHandlerFires once per turn after user input is added to session.
before_llm(func: Callable[[Agent], None]) → EventHandlerFires before each LLM call.
after_llm(func: Callable[[Agent], None]) → EventHandlerFires after each LLM response.
before_each_tool(func: Callable[[Agent], None]) → EventHandlerFires before EACH individual tool execution. Access pending tool via agent.current_session['pending_tool'].
before_tools(func: Callable[[Agent], None]) → EventHandlerFires ONCE before ALL tools in a batch execute.
after_each_tool(func: Callable[[Agent], None]) → EventHandlerFires after EACH individual tool. WARNING: Do NOT add messages here!
after_tools(func: Callable[[Agent], None]) → EventHandlerFires ONCE after ALL tools complete. SAFE for adding messages.
on_error(func: Callable[[Agent], None]) → EventHandlerFires when tool execution fails or tool is not found.
on_complete(func: Callable[[Agent], None]) → EventHandlerFires once after agent completes the task.
Agent Constructor
Agent(name, tools, on_events: Optional[List[EventHandler]] = None, ...)on_events: List of event handlers wrapped with event type functions
Best Practices
✅ Keep handlers simple: Each event handler should do one thing well. Compose multiple handlers for complex behavior.
✅ Use after_tools for message injection: This is the safe time to inject reflection/context after ALL tools in a batch complete.
✅ Handle exceptions internally: If your event handler can fail, catch exceptions to prevent stopping the agent.
❌ Don't inject during tool execution: Using after_each_tool to inject messages breaks Anthropic Claude's tool_result message ordering.
❌ Don't do heavy computation: Event handlers run synchronously and block agent execution. Keep them fast.
