Callback Handlers
Intercept and process agent events as they happen, synchronously, with a callback function in Python. Callback handlers drive real-time monitoring, custom output formatting, and integration with external systems.
For every event the stream can emit, including text, tool usage, lifecycle, and reasoning events, see the stream event types reference.
Note: For asynchronous applications, consider async iterators instead.
Basic Usage
Section titled “Basic Usage”The simplest way to use a callback handler is to pass a callback function to your agent:
from strands import Agentfrom strands.vended_tools import notebook
def custom_callback_handler(**kwargs): # Process stream data if "data" in kwargs: print(f"MODEL OUTPUT: {kwargs['data']}") elif "current_tool_use" in kwargs and kwargs["current_tool_use"].get("name"): print(f"\nUSING TOOL: {kwargs['current_tool_use']['name']}")
# Create an agent with custom callback handleragent = Agent( tools=[notebook], callback_handler=custom_callback_handler)
agent('Create a notebook named "ideas" and add three project ideas.')Default Callback Handler
Section titled “Default Callback Handler”Strands Agents provides a default callback handler that formats output to the console:
from strands import Agentfrom strands.handlers.callback_handler import PrintingCallbackHandler
# The default callback handler prints text and shows tool usageagent = Agent(callback_handler=PrintingCallbackHandler())If you want to disable all output, specify None for the callback handler:
from strands import Agent
# No output will be displayedagent = Agent(callback_handler=None)Custom Callback Handlers
Section titled “Custom Callback Handlers”Custom callback handlers give you fine-grained control over what streams from your agents.
Example - Print all events in the stream sequence
Section titled “Example - Print all events in the stream sequence”Custom callback handlers can be useful to debug sequences of events in the agent loop:
from strands import Agentfrom strands.vended_tools import notebook
def debugger_callback_handler(**kwargs): # Print the values in kwargs so that we can see everything print(kwargs)
agent = Agent( tools=[notebook], callback_handler=debugger_callback_handler)
agent('Create a notebook named "ideas" and add three project ideas.')This handler prints all calls to the callback handler including full event details.
Example - Buffering Output Per Message
Section titled “Example - Buffering Output Per Message”This handler buffers text and shows it only when a complete message is generated, which suits chat interfaces that display polished, complete responses:
import jsonfrom strands import Agentfrom strands.vended_tools import notebook
def message_buffer_handler(**kwargs): # When a new message is created from the assistant, print its content if "message" in kwargs and kwargs["message"].get("role") == "assistant": print(json.dumps(kwargs["message"], indent=2))
# Usage with an agentagent = Agent( tools=[notebook], callback_handler=message_buffer_handler)
agent('Add two AWS Lambda project ideas to the "ideas" notebook.')The message event fires when a complete message is created, so this handler buffers the incrementally streamed text and displays whole messages rather than partial fragments. Use it for conversational interfaces, or wherever responses read better as complete units.
Example - Event Loop Lifecycle Tracking
Section titled “Example - Event Loop Lifecycle Tracking”This handler prints each lifecycle event as it arrives, so you can watch the order the agent moves through its loop:
from strands import Agentfrom strands.vended_tools import notebook
def event_loop_tracker(**kwargs): # Track event loop lifecycle if kwargs.get("init_event_loop", False): print("Event loop initialized") elif kwargs.get("start_event_loop", False): print("Event loop cycle starting") elif "message" in kwargs: print(f"New message created: {kwargs['message']['role']}") elif "result" in kwargs: print("Agent completed with result") elif kwargs.get("force_stop", False): print(f"Event loop force-stopped: {kwargs.get('force_stop_reason', 'unknown reason')}")
# Track tool usage if "current_tool_use" in kwargs and kwargs["current_tool_use"].get("name"): tool_name = kwargs["current_tool_use"]["name"] print(f"Using tool: {tool_name}")
# Show the first 20 characters of each text chunk to keep output readable if "data" in kwargs: data_snippet = kwargs["data"][:20] + ("..." if len(kwargs["data"]) > 20 else "") print(f"Text: {data_snippet}")
# Create agent with event loop trackeragent = Agent( tools=[notebook], callback_handler=event_loop_tracker)
# This will show the full event lifecycle in the consoleagent('Read the "ideas" notebook and summarize it.')The output will show the sequence of events:
- First the event loop initializes (
init_event_loop) - Then the cycle begins (
start_event_loop) - New cycles may start multiple times during execution (
start_event_loop) - Text generation and tool usage events occur during the cycle
- Finally, the agent completes with a
resultevent or may be force-stopped
Best Practices
Section titled “Best Practices”When implementing callback handlers:
- Keep Them Fast: Callback handlers run in the critical path of agent execution
- Handle All Event Types: Be prepared for different event types
- Graceful Errors: Handle exceptions within your handler
- State Management: Store accumulated state in the
request_state