From eefacc363cbc1f52c8402e4dfca70f2c356805ea Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Sun, 1 Mar 2026 01:45:28 +0100 Subject: [PATCH 1/5] fix: skip AgentState reset for MultiAgentBase executors in GraphNode GraphNode.reset_executor_state() unconditionally overwrites executor.state with AgentState(...), corrupting the GraphState dataclass on nested Graph/Swarm executors. This breaks: - Cyclic graphs with reset_on_revisit enabled (nested graph revisit) - Session persistence deserialization of completed graph runs Add isinstance(self.executor, MultiAgentBase) guard to skip the state reset for multi-agent executors whose state is not an AgentState dict. Fixes #1775 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/strands/multiagent/graph.py | 6 ++++- tests/strands/multiagent/test_graph.py | 37 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/strands/multiagent/graph.py b/src/strands/multiagent/graph.py index 966d2a0b3..1873d171a 100644 --- a/src/strands/multiagent/graph.py +++ b/src/strands/multiagent/graph.py @@ -184,11 +184,15 @@ def reset_executor_state(self) -> None: This is useful when nodes are executed multiple times and need to start fresh on each execution, providing stateless behavior. + + For MultiAgentBase executors (e.g. nested Graph or Swarm), the state is a + GraphState/SwarmState dataclass — not an AgentState dict — so we skip the + state reset to avoid corrupting it with an incompatible type. """ if hasattr(self.executor, "messages"): self.executor.messages = copy.deepcopy(self._initial_messages) - if hasattr(self.executor, "state"): + if hasattr(self.executor, "state") and not isinstance(self.executor, MultiAgentBase): self.executor.state = AgentState(self._initial_state.get()) # Reset execution status diff --git a/tests/strands/multiagent/test_graph.py b/tests/strands/multiagent/test_graph.py index 8158bf4b1..1d122f47e 100644 --- a/tests/strands/multiagent/test_graph.py +++ b/tests/strands/multiagent/test_graph.py @@ -793,6 +793,43 @@ async def test_node_reset_executor_state(): assert multi_agent_node.result is None +def test_reset_executor_state_preserves_multiagent_state_type(): + """Test that reset_executor_state does not overwrite MultiAgentBase.state with AgentState. + + Regression test for https://github.com/strands-agents/sdk-python/issues/1775. + GraphNode.reset_executor_state() was unconditionally assigning AgentState(...) to + executor.state, corrupting the GraphState dataclass on nested Graph/Swarm executors. + """ + multi_agent = create_mock_multi_agent("nested_graph") + # Simulate a MultiAgentBase that has a GraphState (like a nested Graph) + original_state = GraphState(task="original task") + multi_agent.state = original_state + + node = GraphNode("nested_node", multi_agent) + + # Modify execution status as if the node had run + node.execution_status = Status.COMPLETED + node.result = NodeResult( + result="test", + execution_time=100, + status=Status.COMPLETED, + accumulated_usage={}, + accumulated_metrics={}, + execution_count=1, + ) + + # Reset should NOT corrupt the state type + node.reset_executor_state() + + # The state must still be a GraphState, not an AgentState dict + assert isinstance(multi_agent.state, GraphState), ( + f"Expected GraphState but got {type(multi_agent.state).__name__}; " + "reset_executor_state must not overwrite MultiAgentBase state with AgentState" + ) + assert node.execution_status == Status.PENDING + assert node.result is None + + def test_graph_dataclasses_and_enums(): """Test dataclass initialization, properties, and enum behavior.""" # Test Status enum From 52810a00605c0115c2c6e2019c297559eeca97a7 Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Sun, 1 Mar 2026 02:45:52 +0100 Subject: [PATCH 2/5] fix: use proper AgentResult in test_reset_executor_state_preserves_graph_state Replace plain string with AgentResult instance for NodeResult.result to match the type annotation (AgentResult | MultiAgentResult | Exception) and prevent future breakage from type-checking code paths. Refs: strands-agents/sdk-python#1791 --- tests/strands/multiagent/test_graph.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/strands/multiagent/test_graph.py b/tests/strands/multiagent/test_graph.py index 1d122f47e..0906abd90 100644 --- a/tests/strands/multiagent/test_graph.py +++ b/tests/strands/multiagent/test_graph.py @@ -810,7 +810,12 @@ def test_reset_executor_state_preserves_multiagent_state_type(): # Modify execution status as if the node had run node.execution_status = Status.COMPLETED node.result = NodeResult( - result="test", + result=AgentResult( + message={"role": "assistant", "content": [{"text": "test"}]}, + stop_reason="end_turn", + state={}, + metrics=Mock(accumulated_usage={}, accumulated_metrics={}), + ), execution_time=100, status=Status.COMPLETED, accumulated_usage={}, From 67141b9114169667269f029868b5fdbcf0ee8888 Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Mon, 2 Mar 2026 16:41:33 +0100 Subject: [PATCH 3/5] fix: apply code review suggestions --- tests/strands/multiagent/test_graph.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/strands/multiagent/test_graph.py b/tests/strands/multiagent/test_graph.py index 0906abd90..cfad4e9c0 100644 --- a/tests/strands/multiagent/test_graph.py +++ b/tests/strands/multiagent/test_graph.py @@ -809,8 +809,14 @@ def test_reset_executor_state_preserves_multiagent_state_type(): # Modify execution status as if the node had run node.execution_status = Status.COMPLETED + agent_result = AgentResult( + message={"role": "assistant", "content": [{"text": "test"}]}, + stop_reason="end_turn", + state={}, + metrics=None, + ) node.result = NodeResult( - result=AgentResult( + result=agent_result, message={"role": "assistant", "content": [{"text": "test"}]}, stop_reason="end_turn", state={}, From 00174f42b7f0a0241cbe14e7cab508a56f0657c6 Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Mon, 2 Mar 2026 18:30:35 +0100 Subject: [PATCH 4/5] fix: apply code review suggestion Refs: #1791 --- tests/strands/multiagent/test_graph.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/strands/multiagent/test_graph.py b/tests/strands/multiagent/test_graph.py index cfad4e9c0..94c3908b7 100644 --- a/tests/strands/multiagent/test_graph.py +++ b/tests/strands/multiagent/test_graph.py @@ -738,8 +738,14 @@ async def test_node_reset_executor_state(): # Also modify execution status and result node.execution_status = Status.COMPLETED + agent_result = AgentResult( + message={"role": "assistant", "content": [{"text": "test"}]}, + stop_reason="end_turn", + state={}, + metrics=None, + ) node.result = NodeResult( - result="test result", + result=agent_result, execution_time=100, status=Status.COMPLETED, accumulated_usage={"inputTokens": 10, "outputTokens": 20, "totalTokens": 30}, @@ -817,11 +823,6 @@ def test_reset_executor_state_preserves_multiagent_state_type(): ) node.result = NodeResult( result=agent_result, - message={"role": "assistant", "content": [{"text": "test"}]}, - stop_reason="end_turn", - state={}, - metrics=Mock(accumulated_usage={}, accumulated_metrics={}), - ), execution_time=100, status=Status.COMPLETED, accumulated_usage={}, From b192db0507487507a1f7f9c2542d810d706b24b5 Mon Sep 17 00:00:00 2001 From: giulio-leone Date: Sat, 7 Mar 2026 14:14:02 +0100 Subject: [PATCH 5/5] fix: use proper MultiAgentResult type in test instead of plain string Address review feedback: NodeResult.result should use MultiAgentResult (for multi-agent executors) instead of a plain string to match the actual type annotation and avoid brittleness. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/strands/multiagent/test_graph.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/strands/multiagent/test_graph.py b/tests/strands/multiagent/test_graph.py index 94c3908b7..f67fde959 100644 --- a/tests/strands/multiagent/test_graph.py +++ b/tests/strands/multiagent/test_graph.py @@ -782,8 +782,11 @@ async def test_node_reset_executor_state(): # Since MultiAgentBase doesn't have messages or state attributes, # reset_executor_state should not fail multi_agent_node.execution_status = Status.COMPLETED + multi_agent_result = MultiAgentResult( + status=Status.COMPLETED, + ) multi_agent_node.result = NodeResult( - result="test result", + result=multi_agent_result, execution_time=100, status=Status.COMPLETED, accumulated_usage={},