Overview
Add a @hook decorator for Plugins to simplify hook registration, and convert the Plugin from a Protocol to a base class that auto-discovers and registers decorated hooks and tools.
Problem Statement
Currently, plugin authors must manually register hooks in their init_plugin() method:
class Steering:
name = "steering"
def init_plugin(self, agent: Agent) -> None:
agent.add_hook(self.log_call, BeforeModelCallEvent)
Proposed Solution
Enable declarative hook and tool registration using decorators:
class Steering(Plugin):
name = "steering"
@hook
def log_call(self, event: BeforeModelCallEvent):
print(event)
@tool
def printer(self, log: str):
print(log)
return "Printed log"
Implementation Requirements
1. Create @hook Decorator
Location: src/strands/plugins/decorator.py (new file)
Behavior:
- Mark methods as hook callbacks for automatic registration
- Infer event type from the callback's type hint (consistent with
HookRegistry.add_callback)
- Support both
@hook and @hook() syntax
- Support union types for multiple event types (e.g.,
BeforeModelCallEvent | AfterModelCallEvent)
- Store hook metadata on the decorated method for later discovery
Example Usage:
@hook
def on_model_call(self, event: BeforeModelCallEvent):
print(event)
@hook
def on_any_model_event(self, event: BeforeModelCallEvent | AfterModelCallEvent):
print(event)
2. Convert Plugin from Protocol to Base Class
Location: src/strands/plugins/plugin.py
Breaking Change: This is an intentional breaking change from the current Protocol-based approach.
Behavior:
__init__(): Scan the class for @hook and @tool decorated methods and store references
init_plugin(agent): Default implementation that:
- Registers all discovered
@hook methods with the agent's hook registry
- Adds all discovered
@tool methods to the agent's tools list
- Subclasses can override
init_plugin() and call super().init_plugin(agent) for custom behavior
Requirements:
name: str attribute still required (can be class attribute or property)
- Support both sync and async
init_plugin() implementations
- Each agent gets its own registration (plugin can be attached to multiple agents)
3. Auto-Registration of Tools
Behavior:
- Methods decorated with existing
@tool decorator should be auto-discovered
- Tools are added to the agent's
tools list during init_plugin()
- The bound method (with
self) should be registered, not the unbound function
4. Public API Exports
Update src/strands/plugins/__init__.py:
from .plugin import Plugin
from .decorator import hook
__all__ = ["Plugin", "hook"]
Update src/strands/__init__.py (if appropriate):
- Consider exporting
hook from the top-level strands namespace
Acceptance Criteria
Files to Create/Modify
New Files
src/strands/plugins/decorator.py - @hook decorator implementation
Modified Files
src/strands/plugins/plugin.py - Convert Protocol to base class
src/strands/plugins/__init__.py - Export hook decorator
src/strands/__init__.py - Optionally export hook
tests/strands/plugins/test_plugins.py - Update existing tests for new class-based approach
tests/strands/plugins/test_hook_decorator.py - New tests for @hook decorator
Technical Notes
Decorator Implementation Pattern
Follow the existing @tool decorator pattern in src/strands/tools/decorator.py:
- Support both
@hook and @hook() call patterns
- Use
functools.wraps to preserve function metadata
- Store metadata as attributes on the decorated function (e.g.,
_hook_event_types)
Method Discovery in __init__
def __init__(self):
self._hooks = []
self._tools = []
for name in dir(self):
attr = getattr(self, name)
if hasattr(attr, '_hook_event_types'):
self._hooks.append(attr)
if isinstance(attr, DecoratedFunctionTool):
self._tools.append(attr)
Backward Compatibility Consideration
This is a breaking change from the Protocol-based approach. Users with existing plugins implementing the Protocol will need to:
- Inherit from
Plugin class instead of implementing the protocol
- Their existing
init_plugin() implementations will continue to work
Additional Context
- Related documentation:
docs/HOOKS.md
- Existing hook events:
src/strands/hooks/events.py
- Existing tool decorator:
src/strands/tools/decorator.py
Overview
Add a
@hookdecorator for Plugins to simplify hook registration, and convert thePluginfrom a Protocol to a base class that auto-discovers and registers decorated hooks and tools.Problem Statement
Currently, plugin authors must manually register hooks in their
init_plugin()method:Proposed Solution
Enable declarative hook and tool registration using decorators:
Implementation Requirements
1. Create
@hookDecoratorLocation:
src/strands/plugins/decorator.py(new file)Behavior:
HookRegistry.add_callback)@hookand@hook()syntaxBeforeModelCallEvent | AfterModelCallEvent)Example Usage:
2. Convert
Pluginfrom Protocol to Base ClassLocation:
src/strands/plugins/plugin.pyBreaking Change: This is an intentional breaking change from the current Protocol-based approach.
Behavior:
__init__(): Scan the class for@hookand@tooldecorated methods and store referencesinit_plugin(agent): Default implementation that:@hookmethods with the agent's hook registry@toolmethods to the agent's tools listinit_plugin()and callsuper().init_plugin(agent)for custom behaviorRequirements:
name: strattribute still required (can be class attribute or property)init_plugin()implementations3. Auto-Registration of Tools
Behavior:
@tooldecorator should be auto-discoveredtoolslist duringinit_plugin()self) should be registered, not the unbound function4. Public API Exports
Update
src/strands/plugins/__init__.py:Update
src/strands/__init__.py(if appropriate):hookfrom the top-levelstrandsnamespaceAcceptance Criteria
@hookdecorator created and functional@hookinfers event types from type hints@hooksupports union types for multiple eventsPluginconverted from Protocol to base classPlugin.__init__()discovers decorated methodsPlugin.init_plugin()auto-registers hooks and tools@tooldecorated methods in plugins are auto-added to agent's tools@tooldecorator continues to work standalone (no breaking changes to tool system)Files to Create/Modify
New Files
src/strands/plugins/decorator.py-@hookdecorator implementationModified Files
src/strands/plugins/plugin.py- Convert Protocol to base classsrc/strands/plugins/__init__.py- Exporthookdecoratorsrc/strands/__init__.py- Optionally exporthooktests/strands/plugins/test_plugins.py- Update existing tests for new class-based approachtests/strands/plugins/test_hook_decorator.py- New tests for@hookdecoratorTechnical Notes
Decorator Implementation Pattern
Follow the existing
@tooldecorator pattern insrc/strands/tools/decorator.py:@hookand@hook()call patternsfunctools.wrapsto preserve function metadata_hook_event_types)Method Discovery in
__init__Backward Compatibility Consideration
This is a breaking change from the Protocol-based approach. Users with existing plugins implementing the Protocol will need to:
Pluginclass instead of implementing the protocolinit_plugin()implementations will continue to workAdditional Context
docs/HOOKS.mdsrc/strands/hooks/events.pysrc/strands/tools/decorator.py