Skip to content

[Feature] @hook decorator for simplified hook definitions #1483

@cagataycali

Description

@cagataycali

Summary

Add a @hook decorator to enable simple, function-based hook definitions - mirroring the ergonomics of the existing @tool decorator. This would make hooks as easy to define and share via PyPI packages as tools are today.

Motivation

Current State (Class-based):

from strands.hooks import HookProvider, HookRegistry, BeforeToolCallEvent

class MyHooks(HookProvider):
    def register_hooks(self, registry: HookRegistry) -> None:
        registry.add_callback(BeforeToolCallEvent, self.on_tool_call)
    
    def on_tool_call(self, event: BeforeToolCallEvent) -> None:
        print(f"Tool: {event.tool_use}")

agent = Agent(hooks=[MyHooks()])

Proposed (Decorator-based):

from strands import Agent, hook
from strands.hooks import BeforeToolCallEvent

@hook
def log_tool_calls(event: BeforeToolCallEvent) -> None:
    """Log all tool calls before execution."""
    print(f"Tool: {event.tool_use}")

agent = Agent(hooks=[log_tool_calls])

Design

Core Decorator

@hook
def my_hook(event: BeforeToolCallEvent) -> None:
    """Hook description extracted from docstring."""
    pass

# Or with explicit event type
@hook(event=BeforeToolCallEvent)
def my_hook(event) -> None:
    pass

Event Type Detection

  1. Type hints (preferred): Extract from function signature
  2. Explicit parameter: @hook(event=BeforeToolCallEvent)
  3. Validation: Ensure event type inherits from HookEvent

DecoratedFunctionHook Class

class DecoratedFunctionHook(HookProvider):
    """Wraps a decorated function as a HookProvider."""
    
    def __init__(self, func, event_type, metadata):
        self._func = func
        self._event_type = event_type
        self._metadata = metadata
    
    def register_hooks(self, registry: HookRegistry) -> None:
        registry.add_callback(self._event_type, self._func)
    
    def __call__(self, event):
        """Allow direct invocation for testing."""
        return self._func(event)

Multi-Event Hooks

@hook(events=[BeforeToolCallEvent, AfterToolCallEvent])
def audit_tools(event: BeforeToolCallEvent | AfterToolCallEvent) -> None:
    """Audit both before and after tool execution."""
    pass

Package Distribution

Enable sharing hooks via PyPI (like tools):

# strands_observability/hooks.py
from strands import hook
from strands.hooks import BeforeInvocationEvent, AfterInvocationEvent

@hook
def trace_requests(event: BeforeInvocationEvent) -> None:
    """Start OpenTelemetry span for agent invocation."""
    pass

@hook  
def end_trace(event: AfterInvocationEvent) -> None:
    """End OpenTelemetry span."""
    pass

Usage:

from strands_observability.hooks import trace_requests, end_trace

agent = Agent(hooks=[trace_requests, end_trace])

API Surface

# New exports from strands
from strands import hook

# New exports from strands.hooks
from strands.hooks import DecoratedFunctionHook, HookMetadata

Compatibility

  • Backward compatible: Existing HookProvider classes continue to work
  • Mixed usage: Support both class-based and decorator-based hooks
  • Async support: Handle both sync and async hook functions
agent = Agent(hooks=[
    MyClassBasedHook(),  # Existing
    log_tool_calls,       # New decorator-based
])

Implementation Checklist

  • FunctionHookMetadata class for extracting function metadata
  • DecoratedFunctionHook class implementing HookProvider
  • @hook decorator with event type detection
  • Support for async hook functions
  • Multi-event registration support
  • Documentation and examples
  • Unit tests

Related

  • Tool decorator: strands/tools/decorator.py
  • Hook system: strands/hooks/
  • Similar pattern: @tool enables package distribution of tools

Questions

  1. Should @hook support context injection like @tool(context=True)?
  2. Should we support hook ordering/priority via decorator params?
  3. Should hooks be loadable from directories like tools (load_hooks_from_directory)?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions