From dd107c197251d53700ea95557acd2f4cf1e06b3b Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Wed, 15 Oct 2025 11:56:01 -0400 Subject: [PATCH 1/2] feat: add multiagent state & message reset sections --- docs/user-guide/concepts/multi-agent/graph.md | 125 ++++++++++++++++++ docs/user-guide/concepts/multi-agent/swarm.md | 43 ++++++ 2 files changed, 168 insertions(+) diff --git a/docs/user-guide/concepts/multi-agent/graph.md b/docs/user-guide/concepts/multi-agent/graph.md index db043b0e..5f20f3af 100644 --- a/docs/user-guide/concepts/multi-agent/graph.md +++ b/docs/user-guide/concepts/multi-agent/graph.md @@ -223,6 +223,131 @@ Custom nodes enable: - **Hybrid workflows**: Combine AI creativity with deterministic control - **Business rules**: Implement complex business logic as graph nodes +## Agent State and Message Reset Mechanism + +The Graph pattern provides several mechanisms for managing agent state and messages during execution: + +### Automatic Reset on Revisit + +When `reset_on_revisit=True` is configured, nodes automatically reset their state when revisited in cyclic graphs: + +```python +builder = GraphBuilder() +builder.reset_on_revisit(True) # Reset state when nodes are revisited +graph = builder.build() +``` + +### Custom Management with Hooks + +For more control over when and how agents reset their state, use the hook system to implement custom reset logic: + +```python +from strands.hooks import HookProvider, HookRegistry +from strands.experimental.multiagent_hooks import AfterNodeInvocationEvent +from strands.agent.state import AgentState + +class StateResetHook(HookProvider): + """Hook provider for custom agent state reset logic.""" + + def register_hooks(self, registry: HookRegistry) -> None: + registry.add_callback(AfterNodeInvocationEvent, self.after_node_execution) + + def after_node_execution(self, event: AfterNodeInvocationEvent) -> None: + """Handle state management after node execution.""" + graph = event.source + executed_node = graph.nodes[event.executed_node] + + # Just defensive check + if hasattr(executed_node.executor, 'state'): + # If you just want to deletge some state data + executed_node.executor.state.delete('sensitive_data') + # Reset state only + executed_node.executor.state = AgentState() + + # Modify execution count + count = executed_node.executor.state.get('execution_count', 0) + executed_node.executor.state.set('execution_count', count+1) + # Just defensive check + if hasattr(executed_node.executor, 'messages'): + # Reset message only + executed_node.executor.messages = [] + + # Or, you just want to reset all message and state + executed_node.reset_executor_state() + +# Use the hook with your graph +hooks = HookRegistry() +hooks.add_hook(StateResetHook()) + +agent1 = Agent(name="agent1", system_prompt="You are agent 1. Keep responses short.") +agent2 = Agent(name="agent2", system_prompt="You are agent 2. Keep responses short. You should finish") + +# Set initial state +agent1.state.set('initial_data', 'agent1_data') +agent2.state.set('initial_data', 'agent2_data') +agent1.state.set('temporary_data', 'temp1') +agent2.state.set('temporary_data', 'temp2') + +builder = GraphBuilder() +builder.add_node(agent1, "agent1") +builder.add_node(agent2, "agent2") +builder.add_edge("agent1", "agent2") +graph = builder.build() + +# Set hooks on the built graph +graph.hooks = hooks +result = graph("Say hello briefly and continue the conversation.") +print(f"Agent1 messages: {agent1.messages}") +print(f"Agent2 messages: {agent2.messages}") +print(f"Agent1 temporary_data: {agent1.state.get('temporary_data')}") +print(f"Agent2 temporary_data: {agent2.state.get('temporary_data')}") + +# Example output +"Graph output : Hello! Nice to meet you. What brings you here today? Hello! Good to meet you too. I'm here to chat and see where our conversation takes us. What about you - what's on your mind today?" +"Agent1 messages: []" +"Agent2 messages: []" +"Agent1 temporary_data: None" +"Agent2 temporary_data: None" + +``` + +### Default Hook Approach + +For simple cases, you can use functions directly with multiagent hooks, for all available hooks, see MultiagentHooks(TBD): + +```python +from strands.experimental.multiagent_hooks import AfterNodeInvocationEvent + +def track_node_execution(event: AfterNodeInvocationEvent) -> None: + """Track and manage node execution state.""" + graph = event.source + executed_node = graph.nodes[event.executed_node] + executed_node.reset_executor_state() + +# Register function directly +hooks = HookRegistry() +hooks.add_callback(AfterNodeInvocationEvent, track_node_execution) + +builder = GraphBuilder() +# ... add nodes and edges +graph = builder.build() +graph.hooks = hooks +``` + +### Agent-Level Hooks + +Since Graph calls `agent.invoke_async()` directly, any hooks registered on individual agents will automatically trigger during graph execution if you registered: + +```python +# Agent hooks work automatically in Graph context +researcher = Agent( + name="researcher", + system_prompt="You are a research specialist...", + hooks=agent_hooks +) +``` +These hooks provide comprehensive control over agent lifecycle and state management during graph execution.For all support hooks, please see [`Hooks`](../agents/hooks.md#hook-event-lifecycle) + ## Multi-Modal Input Support Graphs support multi-modal inputs like text and images using [`ContentBlocks`](../../../api-reference/types.md#strands.types.content.ContentBlock): diff --git a/docs/user-guide/concepts/multi-agent/swarm.md b/docs/user-guide/concepts/multi-agent/swarm.md index b62c261b..ce349c0c 100644 --- a/docs/user-guide/concepts/multi-agent/swarm.md +++ b/docs/user-guide/concepts/multi-agent/swarm.md @@ -213,6 +213,49 @@ print(f"Execution time: {result.execution_time}ms") print(f"Token usage: {result.accumulated_usage}") ``` +## Agent State and Message Reset Mechanism + +Swarms automatically reset agent state between executions to ensure clean handoffs and prevent state pollution. Each agent in a swarm maintains: + +- **Initial state capture**: When the swarm is created, each agent's messages and state are captured +- **Automatic reset**: Before each agent execution, the agent is reset to its initial state +- **Clean handoffs**: Agents receive only the shared context and handoff message, not previous execution artifacts + +### Default Hook Approach + +You can customize state reset behavior using hooks, for all available hooks, see MultiagentHooks(TBD): + +```python +from strands.experimental.multiagent_hooks import AfterNodeInvocationEvent +from strands.hooks import HookRegistry + +def custom_state_reset(event: AfterNodeInvocationEvent) -> None: + """Custom state management after agent execution.""" + graph = event.source + executed_node = graph.nodes[event.executed_node] + + # Just defensive check + if hasattr(executed_node.executor, 'state'): + # Preserve certain state keys + important_data = executed_node.executor.state.delete('important_data') + # Reset state only + executed_node.executor.state = AgentState() + # Just defensive check + if hasattr(executed_node.executor, 'messages'): + # Reset message only + executed_node.executor.messages = [] + + # Reset message & state + executed_node.reset_executor_state() + +# Register hook +hooks = HookRegistry() +hooks.add_callback(AfterNodeInvocationEvent, custom_state_reset) + +# Create Swarm instance +swarm = Swarm([agent1, agent2], hooks=hooks) +``` + ## Swarm as a Tool Agents can dynamically create and orchestrate swarms by using the `swarm` tool available in the [Strands tools package](../tools/community-tools-package.md). From 9810c5c91a021f0159d78105e7bebf7f4ce2a916 Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Wed, 5 Nov 2025 12:44:00 -0500 Subject: [PATCH 2/2] fix: update hooks and events' name, add doc reference --- docs/user-guide/concepts/multi-agent/graph.md | 55 ++++++++++++------- docs/user-guide/concepts/multi-agent/swarm.md | 35 ++++++------ 2 files changed, 55 insertions(+), 35 deletions(-) diff --git a/docs/user-guide/concepts/multi-agent/graph.md b/docs/user-guide/concepts/multi-agent/graph.md index 893f6f77..d374b97b 100644 --- a/docs/user-guide/concepts/multi-agent/graph.md +++ b/docs/user-guide/concepts/multi-agent/graph.md @@ -259,29 +259,40 @@ builder.reset_on_revisit(True) # Reset state when nodes are revisited graph = builder.build() ``` -### Custom Management with Hooks +### Creating a [Hook Provider](../agents/hooks.md#creating-a-hook-provider) For more control over when and how agents reset their state, use the hook system to implement custom reset logic: ```python from strands.hooks import HookProvider, HookRegistry -from strands.experimental.multiagent_hooks import AfterNodeInvocationEvent +from strands.multiagent.hooks import AfterNodeCallEvent, BeforeNodeCallEvent from strands.agent.state import AgentState class StateResetHook(HookProvider): """Hook provider for custom agent state reset logic.""" def register_hooks(self, registry: HookRegistry) -> None: - registry.add_callback(AfterNodeInvocationEvent, self.after_node_execution) + registry.add_callback(AfterNodeCallEvent, self.after_node_execution) + registry.add_callback(BeforeNodeCallEvent, self.before_node_execution) - def after_node_execution(self, event: AfterNodeInvocationEvent) -> None: + def before_node_execution(self, event: BeforeNodeCallEvent) -> None: + """Handle state preparation before node execution.""" + graph = event.source + node = graph.nodes[event.node_id] + + # Prepare node state before execution + if hasattr(node.executor, 'state'): + # Set execution context + node.executor.state.set('current_node_id', event.node_id) + + def after_node_execution(self, event: AfterNodeCallEvent) -> None: """Handle state management after node execution.""" graph = event.source - executed_node = graph.nodes[event.executed_node] + executed_node = graph.nodes[event.node_id] - # Just defensive check + # Defensive check if hasattr(executed_node.executor, 'state'): - # If you just want to deletge some state data + # Delete sensitive data executed_node.executor.state.delete('sensitive_data') # Reset state only executed_node.executor.state = AgentState() @@ -289,8 +300,9 @@ class StateResetHook(HookProvider): # Modify execution count count = executed_node.executor.state.get('execution_count', 0) executed_node.executor.state.set('execution_count', count+1) - # Just defensive check - if hasattr(executed_node.executor, 'messages'): + + # Defensive check + if hasattr(executed_node.executor, 'messages'): # Reset message only executed_node.executor.messages = [] @@ -333,27 +345,32 @@ print(f"Agent2 temporary_data: {agent2.state.get('temporary_data')}") ``` -### Default Hook Approach +### Registering [Individual Hook Callbacks](../agents/hooks.md#registering-individual-hook-callbacks) -For simple cases, you can use functions directly with multiagent hooks, for all available hooks, see MultiagentHooks(TBD): +For simple cases, you can register callbacks for specific events using add_callback: ```python -from strands.experimental.multiagent_hooks import AfterNodeInvocationEvent +from strands.multiagent.hooks import AfterNodeCallEvent, BeforeNodeCallEvent -def track_node_execution(event: AfterNodeInvocationEvent) -> None: +def track_node_execution(event: AfterNodeCallEvent) -> None: """Track and manage node execution state.""" graph = event.source - executed_node = graph.nodes[event.executed_node] + executed_node = graph.nodes[event.node_id] executed_node.reset_executor_state() - -# Register function directly -hooks = HookRegistry() -hooks.add_callback(AfterNodeInvocationEvent, track_node_execution) +def prepare_node_execution(event: BeforeNodeCallEvent) -> None: + """Prepare node before execution.""" + graph = event.source + node = graph.nodes[event.node_id] + # Custom preparation logic here + +# Register individual callbacks builder = GraphBuilder() # ... add nodes and edges graph = builder.build() -graph.hooks = hooks + +graph.hooks.add_callback(AfterNodeCallEvent, track_node_execution) +graph.hooks.add_callback(BeforeNodeCallEvent, prepare_node_execution) ``` ### Agent-Level Hooks diff --git a/docs/user-guide/concepts/multi-agent/swarm.md b/docs/user-guide/concepts/multi-agent/swarm.md index b6d7330f..d1e9382d 100644 --- a/docs/user-guide/concepts/multi-agent/swarm.md +++ b/docs/user-guide/concepts/multi-agent/swarm.md @@ -262,39 +262,42 @@ Swarms automatically reset agent state between executions to ensure clean handof - **Automatic reset**: Before each agent execution, the agent is reset to its initial state - **Clean handoffs**: Agents receive only the shared context and handoff message, not previous execution artifacts -### Default Hook Approach +### Registering [Individual Hook Callbacks](../agents/hooks.md#registering-individual-hook-callbacks) -You can customize state reset behavior using hooks, for all available hooks, see MultiagentHooks(TBD): +You can customize state reset behavior using hooks: ```python -from strands.experimental.multiagent_hooks import AfterNodeInvocationEvent +from strands.multiagent.hooks import AfterNodeCallEvent from strands.hooks import HookRegistry -def custom_state_reset(event: AfterNodeInvocationEvent) -> None: +def custom_state_reset(event: AfterNodeCallEvent) -> None: """Custom state management after agent execution.""" - graph = event.source - executed_node = graph.nodes[event.executed_node] + swarm = event.source + executed_node = swarm.nodes[event.node_id] - # Just defensive check + # Defensive check if hasattr(executed_node.executor, 'state'): # Preserve certain state keys - important_data = executed_node.executor.state.delete('important_data') + important_data = executed_node.executor.state.get('important_data') # Reset state only executed_node.executor.state = AgentState() - # Just defensive check + # Restore important data + if important_data: + executed_node.executor.state.set('important_data', important_data) + + # Defensive check if hasattr(executed_node.executor, 'messages'): # Reset message only executed_node.executor.messages = [] - # Reset message & state - executed_node.reset_executor_state() - -# Register hook -hooks = HookRegistry() -hooks.add_callback(AfterNodeInvocationEvent, custom_state_reset) + # Or reset message & state completely + # executed_node.reset_executor_state() # Create Swarm instance -swarm = Swarm([agent1, agent2], hooks=hooks) +swarm = Swarm([agent1, agent2]) + +# Register individual callback +swarm.hooks.add_callback(AfterNodeCallEvent, custom_state_reset) ``` ## Swarm as a Tool