Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/strands/experimental/hooks/multiagent/__init__.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down
138 changes: 24 additions & 114 deletions src/strands/experimental/hooks/multiagent/events.py
Original file line number Diff line number Diff line change
@@ -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",
]
11 changes: 11 additions & 0 deletions src/strands/hooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -56,4 +62,9 @@ def log_end(self, event: AfterInvocationEvent) -> None:
"HookRegistry",
"HookEvent",
"BaseHookEvent",
"AfterMultiAgentInvocationEvent",
"AfterNodeCallEvent",
"BeforeMultiAgentInvocationEvent",
"BeforeNodeCallEvent",
"MultiAgentInitializedEvent",
]
106 changes: 105 additions & 1 deletion src/strands/hooks/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
4 changes: 2 additions & 2 deletions src/strands/multiagent/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/strands/multiagent/swarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/strands/session/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 2 additions & 4 deletions tests/fixtures/mock_multiagent_hook_provider.py
Original file line number Diff line number Diff line change
@@ -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,
)


Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from strands import Agent
from strands.experimental.hooks.multiagent.events import (
from strands.hooks import (
AfterMultiAgentInvocationEvent,
AfterNodeCallEvent,
BeforeMultiAgentInvocationEvent,
Expand Down
Loading
Loading