Skip to content

strands.plugins.decorator

Hook decorator for Plugin methods.

Marks methods as hook callbacks for automatic registration when the plugin is attached to an agent. Infers event types from type hints and supports union types for multiple events.

Example
class MyPlugin(Plugin):
    @hook
    def on_model_call(self, event: BeforeModelCallEvent):
        print(event)

TEvent = TypeVar('TEvent', bound=BaseHookEvent, contravariant=True) module-attribute

Generic for adding callback handlers - contravariant to allow adding handlers which take in base classes.

HookCallback

Bases: Protocol, Generic[TEvent]

Protocol for callback functions that handle hook events.

Hook callbacks are functions that receive a single strongly-typed event argument and perform some action in response. They should not return values and any exceptions they raise will propagate to the caller.

Example
def my_callback(event: StartRequestEvent) -> None:
    print(f"Request started for agent: {event.agent.name}")

# Or

async def my_callback(event: StartRequestEvent) -> None:
    # await an async operation
Source code in strands/hooks/registry.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
class HookCallback(Protocol, Generic[TEvent]):
    """Protocol for callback functions that handle hook events.

    Hook callbacks are functions that receive a single strongly-typed event
    argument and perform some action in response. They should not return
    values and any exceptions they raise will propagate to the caller.

    Example:
        ```python
        def my_callback(event: StartRequestEvent) -> None:
            print(f"Request started for agent: {event.agent.name}")

        # Or

        async def my_callback(event: StartRequestEvent) -> None:
            # await an async operation
        ```
    """

    def __call__(self, event: TEvent) -> None | Awaitable[None]:
        """Handle a hook event.

        Args:
            event: The strongly-typed event to handle.
        """
        ...

__call__(event)

Handle a hook event.

Parameters:

Name Type Description Default
event TEvent

The strongly-typed event to handle.

required
Source code in strands/hooks/registry.py
137
138
139
140
141
142
143
def __call__(self, event: TEvent) -> None | Awaitable[None]:
    """Handle a hook event.

    Args:
        event: The strongly-typed event to handle.
    """
    ...

_WrappedHookCallable

Bases: HookCallback, Generic[TEvent]

Wrapped version of HookCallback that includes a _hook_event_types attribute.

Source code in strands/plugins/decorator.py
23
24
25
26
class _WrappedHookCallable(HookCallback, Generic[TEvent]):
    """Wrapped version of HookCallback that includes a `_hook_event_types` attribute."""

    _hook_event_types: list[type[TEvent]]

hook(func=None)

hook(__func: HookCallback) -> _WrappedHookCallable
hook() -> Callable[[HookCallback], _WrappedHookCallable]

Mark a method as a hook callback for automatic registration.

Infers event type from the callback's type hint. Supports union types for multiple events. Can be used as @hook or @hook().

Parameters:

Name Type Description Default
func HookCallback | None

The function to decorate.

None

Returns:

Type Description
_WrappedHookCallable | Callable[[HookCallback], _WrappedHookCallable]

The decorated function with hook metadata.

Raises:

Type Description
ValueError

If event type cannot be inferred from type hints.

Source code in strands/plugins/decorator.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def hook(
    func: HookCallback | None = None,
) -> _WrappedHookCallable | Callable[[HookCallback], _WrappedHookCallable]:
    """Mark a method as a hook callback for automatic registration.

    Infers event type from the callback's type hint. Supports union types
    for multiple events. Can be used as @hook or @hook().

    Args:
        func: The function to decorate.

    Returns:
        The decorated function with hook metadata.

    Raises:
        ValueError: If event type cannot be inferred from type hints.
    """

    def decorator(f: HookCallback[TEvent]) -> _WrappedHookCallable[TEvent]:
        # Infer event types from type hints
        event_types: list[type[TEvent]] = infer_event_types(f)

        # Store hook metadata on the function
        f_wrapped = cast(_WrappedHookCallable, f)
        f_wrapped._hook_event_types = event_types

        return f_wrapped

    if func is None:
        return decorator
    return decorator(func)

infer_event_types(callback)

Infer the event type(s) from a callback's type hints.

Supports both single types and union types (A | B or Union[A, B]).

Parameters:

Name Type Description Default
callback HookCallback[TEvent]

The callback function to inspect.

required

Returns:

Type Description
list[type[TEvent]]

A list of event types inferred from the callback's first parameter type hint.

Raises:

Type Description
ValueError

If the event type cannot be inferred from the callback's type hints, or if a union contains None or non-BaseHookEvent types.

Source code in strands/hooks/_type_inference.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def infer_event_types(callback: "HookCallback[TEvent]") -> "list[type[TEvent]]":
    """Infer the event type(s) from a callback's type hints.

    Supports both single types and union types (A | B or Union[A, B]).

    Args:
        callback: The callback function to inspect.

    Returns:
        A list of event types inferred from the callback's first parameter type hint.

    Raises:
        ValueError: If the event type cannot be inferred from the callback's type hints,
            or if a union contains None or non-BaseHookEvent types.
    """
    # Import here to avoid circular dependency
    from .registry import BaseHookEvent

    try:
        hints = get_type_hints(callback)
    except Exception as e:
        logger.debug("callback=<%s>, error=<%s> | failed to get type hints", callback, e)
        raise ValueError(
            "failed to get type hints for callback | cannot infer event type, please provide event_type explicitly"
        ) from e

    # Get the first parameter's type hint
    sig = inspect.signature(callback)
    params = list(sig.parameters.values())

    if not params:
        raise ValueError("callback has no parameters | cannot infer event type, please provide event_type explicitly")

    # Skip 'self' and 'cls' parameters for methods
    first_param = params[0]
    if first_param.name in ("self", "cls") and len(params) > 1:
        first_param = params[1]

    type_hint = hints.get(first_param.name)

    if type_hint is None:
        raise ValueError(
            f"parameter=<{first_param.name}> has no type hint | "
            "cannot infer event type, please provide event_type explicitly"
        )

    # Check if it's a Union type (Union[A, B] or A | B)
    origin = get_origin(type_hint)
    if origin is Union or origin is types.UnionType:
        event_types: list[type[TEvent]] = []
        for arg in get_args(type_hint):
            if arg is type(None):
                raise ValueError("None is not a valid event type in union")
            if not (isinstance(arg, type) and issubclass(arg, BaseHookEvent)):
                raise ValueError(f"Invalid type in union: {arg} | must be a subclass of BaseHookEvent")
            event_types.append(cast("type[TEvent]", arg))
        return event_types

    # Handle single type
    if isinstance(type_hint, type) and issubclass(type_hint, BaseHookEvent):
        return [cast("type[TEvent]", type_hint)]

    raise ValueError(
        f"parameter=<{first_param.name}>, type=<{type_hint}> | type hint must be a subclass of BaseHookEvent"
    )