Skip to content

[FEATURE] Hooks - Events - BeforeToolsEvent and AfterToolsEvent #25

@zastrowm

Description

@zastrowm

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: HookEvent and _Interruptible
  • Fields:
    • agent: Agent - The agent executing the tools
    • message: Message - The message containing tool use blocks
    • tool_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
            pass

AfterToolsEvent

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 tools
    • message: Message - The original message containing tool use blocks
    • tool_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

  1. /src/strands/hooks/events.py

    • Add BeforeToolsEvent class with _Interruptible mixin
    • Add AfterToolsEvent class with reverse callback ordering
    • Add docstrings following existing event documentation patterns
  2. /src/strands/tools/executors/_executor.py

    • Update _execute() abstract method signature to handle tool batch events
    • Document when these events are triggered
  3. /src/strands/tools/executors/sequential.py

    • Trigger BeforeToolsEvent at start of _execute() method
    • Handle interrupts from BeforeToolsEvent
    • Trigger AfterToolsEvent at end of _execute() method
  4. /src/strands/tools/executors/concurrent.py

    • Trigger BeforeToolsEvent at start of _execute() method
    • Handle interrupts from BeforeToolsEvent
    • Trigger AfterToolsEvent at end after all tasks complete
  5. /src/strands/hooks/__init__.py

    • Export new event classes
  6. Documentation Updates:

    • AGENTS.md - Update hooks section to mention new events
    • docs/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

  • BeforeToolsEvent and AfterToolsEvent classes implemented in /src/strands/hooks/events.py
  • Events exported from /src/strands/hooks/__init__.py
  • Both executors (sequential and concurrent) trigger the events
  • BeforeToolsEvent supports interrupts for approval workflows
  • AfterToolsEvent uses reverse callback ordering
  • All unit tests pass
  • All integration tests pass
  • Documentation updated in AGENTS.md and docs/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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions