diff --git a/docs/user-guide/concepts/multi-agent/graph.md b/docs/user-guide/concepts/multi-agent/graph.md index ca204e12..d374b97b 100644 --- a/docs/user-guide/concepts/multi-agent/graph.md +++ b/docs/user-guide/concepts/multi-agent/graph.md @@ -245,6 +245,148 @@ 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() +``` + +### 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.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(AfterNodeCallEvent, self.after_node_execution) + registry.add_callback(BeforeNodeCallEvent, self.before_node_execution) + + 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.node_id] + + # Defensive check + if hasattr(executed_node.executor, 'state'): + # Delete sensitive 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) + + # 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" + +``` + +### Registering [Individual Hook Callbacks](../agents/hooks.md#registering-individual-hook-callbacks) + +For simple cases, you can register callbacks for specific events using add_callback: + +```python +from strands.multiagent.hooks import AfterNodeCallEvent, BeforeNodeCallEvent + +def track_node_execution(event: AfterNodeCallEvent) -> None: + """Track and manage node execution state.""" + graph = event.source + executed_node = graph.nodes[event.node_id] + executed_node.reset_executor_state() + +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.add_callback(AfterNodeCallEvent, track_node_execution) +graph.hooks.add_callback(BeforeNodeCallEvent, prepare_node_execution) +``` + +### 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 3f333d59..d1e9382d 100644 --- a/docs/user-guide/concepts/multi-agent/swarm.md +++ b/docs/user-guide/concepts/multi-agent/swarm.md @@ -254,6 +254,52 @@ 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 + +### Registering [Individual Hook Callbacks](../agents/hooks.md#registering-individual-hook-callbacks) + +You can customize state reset behavior using hooks: + +```python +from strands.multiagent.hooks import AfterNodeCallEvent +from strands.hooks import HookRegistry + +def custom_state_reset(event: AfterNodeCallEvent) -> None: + """Custom state management after agent execution.""" + swarm = event.source + executed_node = swarm.nodes[event.node_id] + + # Defensive check + if hasattr(executed_node.executor, 'state'): + # Preserve certain state keys + important_data = executed_node.executor.state.get('important_data') + # Reset state only + executed_node.executor.state = AgentState() + # 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 = [] + + # Or reset message & state completely + # executed_node.reset_executor_state() + +# Create Swarm instance +swarm = Swarm([agent1, agent2]) + +# Register individual callback +swarm.hooks.add_callback(AfterNodeCallEvent, custom_state_reset) +``` + ## 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).