diff --git a/src/strands/experimental/hooks/multiagent/__init__.py b/src/strands/experimental/hooks/multiagent/__init__.py index d059d0da5..6755db7e4 100644 --- a/src/strands/experimental/hooks/multiagent/__init__.py +++ b/src/strands/experimental/hooks/multiagent/__init__.py @@ -1,6 +1,6 @@ -"""Multi-agent hook events and utilities. +"""Multi-agent hook events. -Provides event classes for hooking into multi-agent orchestrator lifecycle. +Deprecated: Use strands.hooks.multiagent instead. """ from .events import ( diff --git a/src/strands/experimental/hooks/multiagent/events.py b/src/strands/experimental/hooks/multiagent/events.py index fa881bf32..2c65c53e3 100644 --- a/src/strands/experimental/hooks/multiagent/events.py +++ b/src/strands/experimental/hooks/multiagent/events.py @@ -1,118 +1,28 @@ """Multi-agent execution lifecycle events for hook system integration. -These events are fired by orchestrators (Graph/Swarm) at key points so -hooks can persist, monitor, or debug execution. No intermediate state model -is used—hooks read from the orchestrator directly. +Deprecated: Use strands.hooks.multiagent instead. """ -import uuid -from dataclasses import dataclass -from typing import TYPE_CHECKING, Any - -from typing_extensions import override - -from ....hooks import BaseHookEvent -from ....types.interrupt import _Interruptible - -if TYPE_CHECKING: - from ....multiagent.base import MultiAgentBase - - -@dataclass -class MultiAgentInitializedEvent(BaseHookEvent): - """Event triggered when multi-agent orchestrator initialized. - - Attributes: - source: The multi-agent orchestrator instance - invocation_state: Configuration that user passes in - """ - - source: "MultiAgentBase" - invocation_state: dict[str, Any] | None = None - - -@dataclass -class BeforeNodeCallEvent(BaseHookEvent, _Interruptible): - """Event triggered before individual node execution starts. - - Attributes: - source: The multi-agent orchestrator instance - node_id: ID of the node about to execute - invocation_state: Configuration that user passes in - cancel_node: A user defined message that when set, will cancel the node execution with status FAILED. - The message will be emitted under a MultiAgentNodeCancel event. If set to `True`, Strands will cancel the - node using a default cancel message. - """ - - source: "MultiAgentBase" - node_id: str - invocation_state: dict[str, Any] | None = None - cancel_node: bool | str = False - - def _can_write(self, name: str) -> bool: - return name in ["cancel_node"] - - @override - def _interrupt_id(self, name: str) -> str: - """Unique id for the interrupt. - - Args: - name: User defined name for the interrupt. - - Returns: - Interrupt id. - """ - node_id = uuid.uuid5(uuid.NAMESPACE_OID, self.node_id) - call_id = uuid.uuid5(uuid.NAMESPACE_OID, name) - return f"v1:before_node_call:{node_id}:{call_id}" - - -@dataclass -class AfterNodeCallEvent(BaseHookEvent): - """Event triggered after individual node execution completes. - - Attributes: - source: The multi-agent orchestrator instance - node_id: ID of the node that just completed execution - invocation_state: Configuration that user passes in - """ - - source: "MultiAgentBase" - node_id: str - invocation_state: dict[str, Any] | None = None - - @property - def should_reverse_callbacks(self) -> bool: - """True to invoke callbacks in reverse order.""" - return True - - -@dataclass -class BeforeMultiAgentInvocationEvent(BaseHookEvent): - """Event triggered before orchestrator execution starts. - - Attributes: - source: The multi-agent orchestrator instance - invocation_state: Configuration that user passes in - """ - - source: "MultiAgentBase" - invocation_state: dict[str, Any] | None = None - - -@dataclass -class AfterMultiAgentInvocationEvent(BaseHookEvent): - """Event triggered after orchestrator execution completes. - - Attributes: - source: The multi-agent orchestrator instance - invocation_state: Configuration that user passes in - """ - - source: "MultiAgentBase" - invocation_state: dict[str, Any] | None = None - - @property - def should_reverse_callbacks(self) -> bool: - """True to invoke callbacks in reverse order.""" - return True +import warnings + +from ....hooks import ( + AfterMultiAgentInvocationEvent, + AfterNodeCallEvent, + BeforeMultiAgentInvocationEvent, + BeforeNodeCallEvent, + MultiAgentInitializedEvent, +) + +warnings.warn( + "strands.experimental.hooks.multiagent is deprecated. Use strands.hooks instead.", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "AfterMultiAgentInvocationEvent", + "AfterNodeCallEvent", + "BeforeMultiAgentInvocationEvent", + "BeforeNodeCallEvent", + "MultiAgentInitializedEvent", +] diff --git a/src/strands/hooks/__init__.py b/src/strands/hooks/__init__.py index 30163f207..96c7f577b 100644 --- a/src/strands/hooks/__init__.py +++ b/src/strands/hooks/__init__.py @@ -32,12 +32,18 @@ def log_end(self, event: AfterInvocationEvent) -> None: from .events import ( AfterInvocationEvent, AfterModelCallEvent, + # Multiagent hook events + AfterMultiAgentInvocationEvent, + AfterNodeCallEvent, AfterToolCallEvent, AgentInitializedEvent, BeforeInvocationEvent, BeforeModelCallEvent, + BeforeMultiAgentInvocationEvent, + BeforeNodeCallEvent, BeforeToolCallEvent, MessageAddedEvent, + MultiAgentInitializedEvent, ) from .registry import BaseHookEvent, HookCallback, HookEvent, HookProvider, HookRegistry @@ -56,4 +62,9 @@ def log_end(self, event: AfterInvocationEvent) -> None: "HookRegistry", "HookEvent", "BaseHookEvent", + "AfterMultiAgentInvocationEvent", + "AfterNodeCallEvent", + "BeforeMultiAgentInvocationEvent", + "BeforeNodeCallEvent", + "MultiAgentInitializedEvent", ] diff --git a/src/strands/hooks/events.py b/src/strands/hooks/events.py index 8aa8a68d6..1faa8a917 100644 --- a/src/strands/hooks/events.py +++ b/src/strands/hooks/events.py @@ -16,7 +16,10 @@ from ..types.interrupt import _Interruptible from ..types.streaming import StopReason from ..types.tools import AgentTool, ToolResult, ToolUse -from .registry import HookEvent +from .registry import BaseHookEvent, HookEvent + +if TYPE_CHECKING: + from ..multiagent.base import MultiAgentBase @dataclass @@ -250,3 +253,104 @@ def _can_write(self, name: str) -> bool: def should_reverse_callbacks(self) -> bool: """True to invoke callbacks in reverse order.""" return True + + +# Multiagent hook events start here +@dataclass +class MultiAgentInitializedEvent(BaseHookEvent): + """Event triggered when multi-agent orchestrator initialized. + + Attributes: + source: The multi-agent orchestrator instance + invocation_state: Configuration that user passes in + """ + + source: "MultiAgentBase" + invocation_state: dict[str, Any] | None = None + + +@dataclass +class BeforeNodeCallEvent(BaseHookEvent, _Interruptible): + """Event triggered before individual node execution starts. + + Attributes: + source: The multi-agent orchestrator instance + node_id: ID of the node about to execute + invocation_state: Configuration that user passes in + cancel_node: A user defined message that when set, will cancel the node execution with status FAILED. + The message will be emitted under a MultiAgentNodeCancel event. If set to `True`, Strands will cancel the + node using a default cancel message. + """ + + source: "MultiAgentBase" + node_id: str + invocation_state: dict[str, Any] | None = None + cancel_node: bool | str = False + + def _can_write(self, name: str) -> bool: + return name in ["cancel_node"] + + @override + def _interrupt_id(self, name: str) -> str: + """Unique id for the interrupt. + + Args: + name: User defined name for the interrupt. + + Returns: + Interrupt id. + """ + node_id = uuid.uuid5(uuid.NAMESPACE_OID, self.node_id) + call_id = uuid.uuid5(uuid.NAMESPACE_OID, name) + return f"v1:before_node_call:{node_id}:{call_id}" + + +@dataclass +class AfterNodeCallEvent(BaseHookEvent): + """Event triggered after individual node execution completes. + + Attributes: + source: The multi-agent orchestrator instance + node_id: ID of the node that just completed execution + invocation_state: Configuration that user passes in + """ + + source: "MultiAgentBase" + node_id: str + invocation_state: dict[str, Any] | None = None + + @property + def should_reverse_callbacks(self) -> bool: + """True to invoke callbacks in reverse order.""" + return True + + +@dataclass +class BeforeMultiAgentInvocationEvent(BaseHookEvent): + """Event triggered before orchestrator execution starts. + + Attributes: + source: The multi-agent orchestrator instance + invocation_state: Configuration that user passes in + """ + + source: "MultiAgentBase" + invocation_state: dict[str, Any] | None = None + + +@dataclass +class AfterMultiAgentInvocationEvent(BaseHookEvent): + """Event triggered after orchestrator execution completes. + + Attributes: + source: The multi-agent orchestrator instance + invocation_state: Configuration that user passes in + """ + + source: "MultiAgentBase" + invocation_state: dict[str, Any] | None = None + + @property + def should_reverse_callbacks(self) -> bool: + """True to invoke callbacks in reverse order.""" + return True diff --git a/src/strands/multiagent/graph.py b/src/strands/multiagent/graph.py index 97435ad4a..32eca00ff 100644 --- a/src/strands/multiagent/graph.py +++ b/src/strands/multiagent/graph.py @@ -27,14 +27,14 @@ from .._async import run_async from ..agent import Agent from ..agent.state import AgentState -from ..experimental.hooks.multiagent import ( +from ..hooks.events import ( AfterMultiAgentInvocationEvent, AfterNodeCallEvent, BeforeMultiAgentInvocationEvent, BeforeNodeCallEvent, MultiAgentInitializedEvent, ) -from ..hooks import HookProvider, HookRegistry +from ..hooks.registry import HookProvider, HookRegistry from ..interrupt import Interrupt, _InterruptState from ..session import SessionManager from ..telemetry import get_tracer diff --git a/src/strands/multiagent/swarm.py b/src/strands/multiagent/swarm.py index 6c1149624..1b883246c 100644 --- a/src/strands/multiagent/swarm.py +++ b/src/strands/multiagent/swarm.py @@ -27,14 +27,14 @@ from .._async import run_async from ..agent import Agent from ..agent.state import AgentState -from ..experimental.hooks.multiagent import ( +from ..hooks.events import ( AfterMultiAgentInvocationEvent, AfterNodeCallEvent, BeforeMultiAgentInvocationEvent, BeforeNodeCallEvent, MultiAgentInitializedEvent, ) -from ..hooks import HookProvider, HookRegistry +from ..hooks.registry import HookProvider, HookRegistry from ..interrupt import Interrupt, _InterruptState from ..session import SessionManager from ..telemetry import get_tracer diff --git a/src/strands/session/session_manager.py b/src/strands/session/session_manager.py index ba4356089..cc954e17d 100644 --- a/src/strands/session/session_manager.py +++ b/src/strands/session/session_manager.py @@ -9,12 +9,14 @@ BidiAgentInitializedEvent, BidiMessageAddedEvent, ) -from ..experimental.hooks.multiagent.events import ( +from ..hooks.events import ( + AfterInvocationEvent, AfterMultiAgentInvocationEvent, AfterNodeCallEvent, + AgentInitializedEvent, + MessageAddedEvent, MultiAgentInitializedEvent, ) -from ..hooks.events import AfterInvocationEvent, AgentInitializedEvent, MessageAddedEvent from ..hooks.registry import HookProvider, HookRegistry from ..types.content import Message diff --git a/tests/fixtures/mock_multiagent_hook_provider.py b/tests/fixtures/mock_multiagent_hook_provider.py index 4d18297a2..a89d5aca8 100644 --- a/tests/fixtures/mock_multiagent_hook_provider.py +++ b/tests/fixtures/mock_multiagent_hook_provider.py @@ -1,16 +1,14 @@ from collections.abc import Iterator from typing import Literal -from strands.experimental.hooks.multiagent.events import ( +from strands.hooks import ( AfterMultiAgentInvocationEvent, AfterNodeCallEvent, BeforeNodeCallEvent, - MultiAgentInitializedEvent, -) -from strands.hooks import ( HookEvent, HookProvider, HookRegistry, + MultiAgentInitializedEvent, ) diff --git a/tests/strands/experimental/hooks/multiagent/__init__.py b/tests/strands/experimental/hooks/multiagent/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/strands/experimental/hooks/multiagent/test_events.py b/tests/strands/hooks/test_events.py similarity index 97% rename from tests/strands/experimental/hooks/multiagent/test_events.py rename to tests/strands/hooks/test_events.py index 6c4d7c4e7..90ab205a9 100644 --- a/tests/strands/experimental/hooks/multiagent/test_events.py +++ b/tests/strands/hooks/test_events.py @@ -4,14 +4,14 @@ import pytest -from strands.experimental.hooks.multiagent.events import ( +from strands.hooks import ( AfterMultiAgentInvocationEvent, AfterNodeCallEvent, + BaseHookEvent, BeforeMultiAgentInvocationEvent, BeforeNodeCallEvent, MultiAgentInitializedEvent, ) -from strands.hooks import BaseHookEvent @pytest.fixture diff --git a/tests/strands/experimental/hooks/multiagent/test_multi_agent_hooks.py b/tests/strands/hooks/test_multi_agent_hooks.py similarity index 98% rename from tests/strands/experimental/hooks/multiagent/test_multi_agent_hooks.py rename to tests/strands/hooks/test_multi_agent_hooks.py index 4e97a9217..3f6e0c940 100644 --- a/tests/strands/experimental/hooks/multiagent/test_multi_agent_hooks.py +++ b/tests/strands/hooks/test_multi_agent_hooks.py @@ -1,7 +1,7 @@ import pytest from strands import Agent -from strands.experimental.hooks.multiagent.events import ( +from strands.hooks import ( AfterMultiAgentInvocationEvent, AfterNodeCallEvent, BeforeMultiAgentInvocationEvent, diff --git a/tests/strands/multiagent/conftest.py b/tests/strands/multiagent/conftest.py index 85e0ef7fc..e5dd1b4f9 100644 --- a/tests/strands/multiagent/conftest.py +++ b/tests/strands/multiagent/conftest.py @@ -1,7 +1,6 @@ import pytest -from strands.experimental.hooks.multiagent import BeforeNodeCallEvent -from strands.hooks import HookProvider +from strands.hooks import BeforeNodeCallEvent, HookProvider @pytest.fixture diff --git a/tests/strands/multiagent/test_graph.py b/tests/strands/multiagent/test_graph.py index ab2d86e70..cd750865e 100644 --- a/tests/strands/multiagent/test_graph.py +++ b/tests/strands/multiagent/test_graph.py @@ -6,8 +6,7 @@ from strands.agent import Agent, AgentResult from strands.agent.state import AgentState -from strands.experimental.hooks.multiagent import BeforeNodeCallEvent -from strands.hooks import AgentInitializedEvent +from strands.hooks import AgentInitializedEvent, BeforeNodeCallEvent from strands.hooks.registry import HookProvider, HookRegistry from strands.interrupt import Interrupt, _InterruptState from strands.multiagent.base import MultiAgentBase, MultiAgentResult, NodeResult diff --git a/tests/strands/multiagent/test_swarm.py b/tests/strands/multiagent/test_swarm.py index f2abed9f7..97d07e0b4 100644 --- a/tests/strands/multiagent/test_swarm.py +++ b/tests/strands/multiagent/test_swarm.py @@ -6,7 +6,7 @@ from strands.agent import Agent, AgentResult from strands.agent.state import AgentState -from strands.experimental.hooks.multiagent import BeforeNodeCallEvent +from strands.hooks import BeforeNodeCallEvent from strands.hooks.registry import HookRegistry from strands.interrupt import Interrupt, _InterruptState from strands.multiagent.base import Status diff --git a/tests_integ/hooks/multiagent/test_cancel.py b/tests_integ/hooks/multiagent/test_cancel.py index 9267330b7..ae3008861 100644 --- a/tests_integ/hooks/multiagent/test_cancel.py +++ b/tests_integ/hooks/multiagent/test_cancel.py @@ -1,8 +1,7 @@ import pytest from strands import Agent -from strands.experimental.hooks.multiagent import BeforeNodeCallEvent -from strands.hooks import HookProvider +from strands.hooks import BeforeNodeCallEvent, HookProvider from strands.multiagent import GraphBuilder, Swarm from strands.multiagent.base import Status from strands.types._events import MultiAgentNodeCancelEvent diff --git a/tests_integ/hooks/multiagent/test_events.py b/tests_integ/hooks/multiagent/test_events.py index e8039444f..3a10b74c1 100644 --- a/tests_integ/hooks/multiagent/test_events.py +++ b/tests_integ/hooks/multiagent/test_events.py @@ -1,14 +1,14 @@ import pytest from strands import Agent -from strands.experimental.hooks.multiagent import ( +from strands.hooks import ( AfterMultiAgentInvocationEvent, AfterNodeCallEvent, BeforeMultiAgentInvocationEvent, BeforeNodeCallEvent, + HookProvider, MultiAgentInitializedEvent, ) -from strands.hooks import HookProvider from strands.multiagent import GraphBuilder, Swarm diff --git a/tests_integ/interrupts/multiagent/test_hook.py b/tests_integ/interrupts/multiagent/test_hook.py index 9350b3535..53305b4e8 100644 --- a/tests_integ/interrupts/multiagent/test_hook.py +++ b/tests_integ/interrupts/multiagent/test_hook.py @@ -4,8 +4,7 @@ import pytest from strands import Agent, tool -from strands.experimental.hooks.multiagent import BeforeNodeCallEvent -from strands.hooks import HookProvider +from strands.hooks import BeforeNodeCallEvent, HookProvider from strands.interrupt import Interrupt from strands.multiagent import GraphBuilder, Swarm from strands.multiagent.base import Status diff --git a/tests_integ/interrupts/multiagent/test_session.py b/tests_integ/interrupts/multiagent/test_session.py index bab4b428f..2ccff2c12 100644 --- a/tests_integ/interrupts/multiagent/test_session.py +++ b/tests_integ/interrupts/multiagent/test_session.py @@ -4,8 +4,7 @@ import pytest from strands import Agent, tool -from strands.experimental.hooks.multiagent import BeforeNodeCallEvent -from strands.hooks import HookProvider +from strands.hooks import BeforeNodeCallEvent, HookProvider from strands.interrupt import Interrupt from strands.multiagent import GraphBuilder, Swarm from strands.multiagent.base import Status diff --git a/tests_integ/test_multiagent_swarm.py b/tests_integ/test_multiagent_swarm.py index e8e969af1..e9738d3d9 100644 --- a/tests_integ/test_multiagent_swarm.py +++ b/tests_integ/test_multiagent_swarm.py @@ -3,13 +3,13 @@ import pytest from strands import Agent, tool -from strands.experimental.hooks.multiagent import BeforeNodeCallEvent from strands.hooks import ( AfterInvocationEvent, AfterModelCallEvent, AfterToolCallEvent, BeforeInvocationEvent, BeforeModelCallEvent, + BeforeNodeCallEvent, BeforeToolCallEvent, MessageAddedEvent, )