Skip to content

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

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

feat(agent): add add_hook convenience method for hook registration#1706
Unshure wants to merge 3 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.

Use Cases

  • Direct hook registration: Users can register callbacks without accessing internal hooks registry
  • Plugin development: Plugin init_plugin implementations can use this cleaner API
  • Quick prototyping: Lambda callbacks for debugging or logging are easier to add
  • Type-safe registration: Typed callbacks automatically register for the correct event type

@codecov
Copy link

codecov bot commented Feb 16, 2026

Codecov Report

❌ Patch coverage is 70.73171% with 12 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/strands/hooks/registry.py 66.66% 8 Missing and 4 partials ⚠️

📢 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.

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
@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

- 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
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

agent.add_hook(typed_callback)

# Verify callback was registered by checking it gets invoked
agent("test prompt")

Choose a reason for hiding this comment

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

Issue: Test doesn't verify the callback was actually invoked.

This test registers a typed callback and calls the agent, but doesn't assert that the callback was actually called. Without this assertion, the test could pass even if the callback registration failed silently.

Suggestion: Add assertion to verify the callback was invoked:

def test_agent_add_hook_infers_event_type_from_callback():
    from strands.hooks import BeforeModelCallEvent

    agent = Agent(model=MockedModelProvider([{"role": "assistant", "content": [{"text": "response"}]}]))
    callback_invoked = []

    def typed_callback(event: BeforeModelCallEvent) -> None:
        callback_invoked.append(event)

    agent.add_hook(typed_callback)
    agent("test prompt")
    
    assert len(callback_invoked) == 1
    assert isinstance(callback_invoked[0], BeforeModelCallEvent)

@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
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?

"""
self.tool_registry.cleanup()

def add_hook(
Copy link
Member

Choose a reason for hiding this comment

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

This repurposes hook to mean "hook callback"; I think that makes sense, but call it out in the PR description as I want the release notes to reflect this new direction/terminology

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

def add_callback(
Copy link
Member

Choose a reason for hiding this comment

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

Can we just leave these apis alone and sort of deprecate the usage of it directly? That sets us up better I think long term to reduce confusion - e.g. don't use agent.hooks directly is the guidance

Copy link
Member Author

Choose a reason for hiding this comment

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

Are you saying move the typehint checking to the agent class, or remove the method overloads I added above?

Copy link
Member

Choose a reason for hiding this comment

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

Remove the method overloads you added here.

Not a blocker, but a possible way to simplify

@Unshure
Copy link
Member Author

Unshure commented Feb 17, 2026

/strands continue

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

3 participants