-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Overview
Add BeforeToolsEvent and AfterToolsEvent hooks to the Python SDK for parity with the TypeScript SDK (docs).
These batch-level events are triggered before and after a group of tools are executed, complementing the existing per-tool BeforeToolCallEvent and AfterToolCallEvent.
Implementation Requirements
Based on clarification discussion and repository analysis:
Event Specifications
BeforeToolsEvent
Purpose: Triggered before tools are executed in a batch, after the model returns tool use blocks.
Implementation Details:
- Location:
/src/strands/hooks/events.py - Base Classes:
HookEventand_Interruptible - Fields:
agent: Agent- The agent executing the toolsmessage: Message- The message containing tool use blockstool_uses: list[ToolUse]- List of tools to be executed (extracted from message for convenience)
- Interruptible: Yes - supports
event.interrupt()for approval workflows - Writable Properties: None (no cancel_tools property)
- Callback Ordering: Normal (first registered, first executed)
Example Use Case:
class ApprovalHook(HookProvider):
def register_hooks(self, registry: HookRegistry) -> None:
registry.add_callback(BeforeToolsEvent, self.approve_batch)
def approve_batch(self, event: BeforeToolsEvent) -> None:
tool_names = [tool['name'] for tool in event.tool_uses]
approval = event.interrupt("batch-approval", reason={"tools": tool_names})
if approval.lower() != "y":
# Individual tools can be cancelled via BeforeToolCallEvent
passAfterToolsEvent
Purpose: Triggered after all tools in a batch complete execution, before results are added to conversation.
Implementation Details:
- Location:
/src/strands/hooks/events.py - Base Class:
HookEvent - Fields:
agent: Agent- The agent that executed the toolsmessage: Message- The original message containing tool use blockstool_uses: list[ToolUse]- List of tools that were executed
- Writable Properties: None
- Callback Ordering: Reverse (last registered, first executed) for proper cleanup semantics
Note: Tool results are available in the tool result message created after this event. This event receives the original assistant message with tool uses, not the result message.
Integration Points
File Modifications Required
-
/src/strands/hooks/events.py- Add
BeforeToolsEventclass with_Interruptiblemixin - Add
AfterToolsEventclass with reverse callback ordering - Add docstrings following existing event documentation patterns
- Add
-
/src/strands/tools/executors/_executor.py- Update
_execute()abstract method signature to handle tool batch events - Document when these events are triggered
- Update
-
/src/strands/tools/executors/sequential.py- Trigger
BeforeToolsEventat start of_execute()method - Handle interrupts from
BeforeToolsEvent - Trigger
AfterToolsEventat end of_execute()method
- Trigger
-
/src/strands/tools/executors/concurrent.py- Trigger
BeforeToolsEventat start of_execute()method - Handle interrupts from
BeforeToolsEvent - Trigger
AfterToolsEventat end after all tasks complete
- Trigger
-
/src/strands/hooks/__init__.py- Export new event classes
-
Documentation Updates:
AGENTS.md- Update hooks section to mention new eventsdocs/HOOKS.md- Add BeforeToolsEvent and AfterToolsEvent to documentation
Event Flow in Agent Loop
1. Model returns message with tool uses
2. → BeforeToolsEvent triggered (batch-level, interruptible)
3. For each tool in batch:
a. → BeforeToolCallEvent triggered (per-tool, interruptible)
b. Tool execution
c. → AfterToolCallEvent triggered (per-tool, reverse order)
4. → AfterToolsEvent triggered (batch-level, reverse order)
5. Tool results added to conversation
Testing Requirements
Unit Tests
Location: /tests/strands/agent/test_agent_hooks.py
Required test cases:
- BeforeToolsEvent is triggered before tool batch execution
- AfterToolsEvent is triggered after all tools complete
- AfterToolsEvent uses reverse callback ordering
- BeforeToolsEvent contains correct agent, message, and tool_uses
- AfterToolsEvent contains correct agent, message, and tool_uses
- BeforeToolsEvent interrupt functionality works correctly
- Events work with sequential executor
- Events work with concurrent executor
- Events are not triggered when no tools are present
Integration Tests
Location: /tests_integ/hooks/ or /tests_integ/interrupts/
Required test cases:
- End-to-end interrupt workflow with batch approval
- Multiple interrupts in same batch
- Batch events with actual model provider
- Sequential vs concurrent executor behavior
Acceptance Criteria
-
BeforeToolsEventandAfterToolsEventclasses implemented in/src/strands/hooks/events.py - Events exported from
/src/strands/hooks/__init__.py - Both executors (sequential and concurrent) trigger the events
-
BeforeToolsEventsupports interrupts for approval workflows -
AfterToolsEventuses reverse callback ordering - All unit tests pass
- All integration tests pass
- Documentation updated in
AGENTS.mdanddocs/HOOKS.md - Existing tests continue to pass
- Type hints are correct and mypy passes
Technical Constraints
- No Bidi Support: Do not add these events to the experimental bidirectional streaming system (
/src/strands/experimental/bidi/) at this time - No cancel_tools Property: Unlike per-tool
cancel_tool, there is no batch cancellation property - No invocation_state: Events do not include invocation_state field (use BeforeToolCallEvent for that)
- Consistency: Follow existing event patterns for docstrings, type hints, and structure
Reference Implementation
TypeScript SDK Reference: sdk-typescript/src/hooks/events.ts
The Python implementation should provide equivalent functionality while following Python SDK conventions and patterns.
Original Request
Problem Statement
I would like the Python SDK to support a BeforeToolsEvent and AfterToolsEvent for parity with the TypeScript SDK (docs).
- BeforeToolsEvent: Triggered before tools are executed in a batch.
- AfterToolsEvent: Triggered after tools are executed in a batch. Uses reverse callback ordering.
Use Case
An example use case would be for requesting approval on a batch of tool calls through the interrupt mechanism.