Skip to content

feat(agent): add add_hook convenience method for hook callback registration#1706

Open
Unshure wants to merge 4 commits intomainfrom
agent-tasks/1686
Open

feat(agent): add add_hook convenience method for hook callback registration#1706
Unshure wants to merge 4 commits intomainfrom
agent-tasks/1686

Conversation

@Unshure
Copy link
Member

@Unshure Unshure commented Feb 16, 2026

Motivation

Currently, registering a hook requires accessing the internal hooks registry:

agent.hooks.add_callback(BeforeModelCallEvent, my_callback)

This exposes implementation details and is less discoverable for users. Plugin authors and users who want to add hooks directly need a cleaner, more intuitive API.

Resolves #1686

Public API Changes

The Agent class now has an add_hook method that provides a cleaner API for registering hook callbacks:

# Before - accessing internal registry
agent.hooks.add_callback(BeforeModelCallEvent, my_callback)

# After - cleaner public API with explicit event type
agent.add_hook(my_callback, BeforeModelCallEvent)

# After - event type inferred from callback type hint
def my_callback(event: BeforeModelCallEvent) -> None:
    print(f"Model call for: {event.agent.name}")

agent.add_hook(my_callback)  # event_type inferred automatically

The add_callback method on HookRegistry also supports type inference:

# With explicit event type
registry.add_callback(BeforeModelCallEvent, my_handler)

# With event type inferred from type hint
registry.add_callback(my_typed_handler)

When event_type is not provided, it's inferred from the callback's first parameter type hint. A ValueError is raised with a helpful message if inference fails.

Naming Justification

I went back and forth between add_hook and add_callback. I decided to go with add_hook, as I think its the most representative name (callback is generic, but hook specifically means running code at a specific lifecycle event).

As for helping users who may not be familiar with hooks, I added a link to our docs to help them understand this concept.

@codecov
Copy link

codecov bot commented Feb 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link

Assessment: Comment

This PR adds a useful convenience method for hook registration that improves the developer experience. The implementation is functional and well-documented.

Review Categories
  • API Design: The argument order inconsistency between Agent.add_hook(callback, event_type) and HookRegistry.add_callback(event_type, callback) may confuse users. Consider aligning these signatures.
  • Type Safety: The complex union type in add_callback could benefit from @overload decorators for better IDE support.
  • Testing: One test case should be strengthened to actually verify the callback was invoked.

The feature addresses a real need per issue #1686 and the type inference capability is a nice touch. Consider the API consistency feedback before merging.

@Unshure
Copy link
Member Author

Unshure commented Feb 16, 2026

/strands review the current pull request and add the @ overload changes proposed in the review

@github-actions
Copy link

Implementation of Review Feedback

I've implemented the @overload suggestion from the review. Here's a summary of changes:

Changes Made

1. src/strands/hooks/registry.py

  • Added overload to imports
  • Added two @overload signatures for add_callback():
    • add_callback(callback) - infers event type from type hint
    • add_callback(event_type, callback) - explicit event type
  • Updated docstring to document both call patterns

2. src/strands/agent/agent.py

  • Added overload to imports
  • Added two @overload signatures for add_hook():
    • add_hook(callback) - infers event type from type hint
    • add_hook(callback, event_type) - explicit event type
  • Updated docstring to document both call patterns

Verification

  • ✅ All 13 related tests pass
  • ✅ Ruff linting passes

Diff

# src/strands/hooks/registry.py
+from typing import (..., overload, ...)

+    @overload
+    def add_callback(self, callback: HookCallback[TEvent]) -> None: ...
+
+    @overload
+    def add_callback(self, event_type: type[TEvent], callback: HookCallback[TEvent]) -> None: ...

# src/strands/agent/agent.py
+from typing import (..., overload, ...)

+    @overload
+    def add_hook(self, callback: HookCallback[TEvent]) -> None: ...
+
+    @overload
+    def add_hook(self, callback: HookCallback[TEvent], event_type: type[TEvent]) -> None: ...

Note: I don't have push access to apply these changes directly. The PR author will need to apply them.

@Unshure
Copy link
Member Author

Unshure commented Feb 16, 2026

/strands review

@github-actions
Copy link

Assessment: Approve

The @overload changes have been applied and the PR is looking good.

Review Status
  • @overload decorators - Applied to both add_callback() and add_hook()
  • All tests pass - 13/13 tests passing
  • Linting clean - No ruff errors
  • ⚠️ Minor: One test (test_agent_add_hook_infers_event_type_from_callback) could be strengthened to verify callback invocation

The API design with type inference is a nice convenience feature. The @overload decorators provide clear IDE support for the two call patterns.

@Unshure Unshure self-assigned this Feb 16, 2026
@github-actions github-actions bot added size/m and removed size/m labels Feb 16, 2026
@github-actions
Copy link

Assessment: Approve

The latest update (f552d46) makes a reasonable decision to keep @overload only on HookRegistry.add_callback() where the signature complexity warrants it. The simpler Agent.add_hook() signature doesn't need overloads.

Final Review Status
Item Status
@overload on HookRegistry.add_callback() ✅ Applied
Agent.add_hook() implementation ✅ Clean, simple signature
All tests ✅ 13/13 passing
Documentation ✅ Good examples in docstrings
Minor: Test assertion ⚠️ One test could verify callback invocation

The PR is ready to merge. The remaining test feedback is minor and can be addressed in a follow-up if desired.

@Unshure
Copy link
Member Author

Unshure commented Feb 16, 2026

/strands can you update the unit tests to address the review feedback, and increase the unit test coverage for the src/strands/hook/registry.py file?

zastrowm
zastrowm previously approved these changes Feb 16, 2026
@Unshure
Copy link
Member Author

Unshure commented Feb 17, 2026

/strands continue

strands-agent and others added 4 commits February 17, 2026 15:21
Add a public add_hook method to the Agent class that provides a cleaner
API for registering hook callbacks. This is a convenience method that
delegates to self.hooks.add_callback but provides a more discoverable
interface for users.

Changes:
- Add Agent.add_hook(callback, event_type=None) method
- Support inferring event_type from callback's type hint
- Update HookRegistry.add_callback to support type inference
- Raise clear ValueError when type cannot be inferred

The method:
- Accepts both sync and async callbacks
- Uses proper type hints with TEvent and HookCallback
- Includes comprehensive Google-style docstring with examples

Resolves #1686
- Add @overload decorators to HookRegistry.add_callback() for clearer type hints
- Add @overload decorators to Agent.add_hook() for clearer type hints
- Improves IDE support and documentation of supported call patterns
- Addresses review feedback from PR review
@github-actions github-actions bot added size/m and removed size/m labels Feb 17, 2026
@Unshure Unshure changed the title feat(agent): add add_hook convenience method for hook registration feat(agent): add add_hook convenience method for hook callback registration Feb 17, 2026
@github-actions
Copy link

Assessment: Approve ✅

All review feedback has been addressed in the latest commit. The PR is ready to merge.

Final Review Summary
Item Status
Test callback verification ✅ Fixed - now asserts callback invocation
Test coverage ✅ 100% (Codecov confirmed)
New edge case tests ✅ 6 additional tests for registry.py
All tests passing ✅ 16/16 tests pass
Documentation ✅ Good docstrings with examples
Naming justification ✅ Added to PR description

The implementation is clean, well-tested, and provides a nice convenience API for hook registration with type inference support.

"cannot infer event type, please provide event_type explicitly"
)

# Handle single type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No union support?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address this as a follow-up once this is merged

Docs:
https://strandsagents.com/latest/documentation/docs/user-guide/concepts/agents/hooks/
"""
if event_type is not None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need an if/else condition here?


Args:
event_type: The class type of events this callback should handle.
When using the single-argument form, pass the callback here instead.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we solve this another way? This seems like a bad practice to overload methods 😅

I would rather have an explicit enum for event type to auto-discovery, instead of doing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add add_hook Convenience Method to Agent

4 participants