From e87859e82c3c45b7ece64d8ba215174f1b33089c Mon Sep 17 00:00:00 2001 From: Farukest Date: Sun, 1 Mar 2026 03:06:13 +0300 Subject: [PATCH] fix(agent): copy conversation_history to avoid mutating caller's list --- run_agent.py | 4 ++-- tests/test_run_agent.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/run_agent.py b/run_agent.py index 8e10dc676..9c2c8b3e8 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1842,8 +1842,8 @@ class AIAgent: self._turns_since_memory = 0 self._iters_since_skill = 0 - # Initialize conversation - messages = conversation_history or [] + # Initialize conversation (copy to avoid mutating the caller's list) + messages = list(conversation_history) if conversation_history else [] # Hydrate todo store from conversation history (gateway creates a fresh # AIAgent per message, so the in-memory store is empty -- we need to diff --git a/tests/test_run_agent.py b/tests/test_run_agent.py index 2d3703933..fd1d2a8af 100644 --- a/tests/test_run_agent.py +++ b/tests/test_run_agent.py @@ -758,3 +758,36 @@ class TestRunConversation: ) result = agent.run_conversation("search something") mock_compress.assert_called_once() + + +# --------------------------------------------------------------------------- +# Conversation history mutation +# --------------------------------------------------------------------------- + +class TestConversationHistoryNotMutated: + """run_conversation must not mutate the caller's conversation_history list.""" + + def test_caller_list_unchanged_after_run(self, agent): + """Passing conversation_history should not modify the original list.""" + history = [ + {"role": "user", "content": "previous question"}, + {"role": "assistant", "content": "previous answer"}, + ] + original_len = len(history) + + resp = _mock_response(content="new answer", finish_reason="stop") + agent.client.chat.completions.create.return_value = resp + + with ( + patch.object(agent, "_persist_session"), + patch.object(agent, "_save_trajectory"), + patch.object(agent, "_cleanup_task_resources"), + ): + result = agent.run_conversation("new question", conversation_history=history) + + # Caller's list must be untouched + assert len(history) == original_len, ( + f"conversation_history was mutated: expected {original_len} items, got {len(history)}" + ) + # Result should have more messages than the original history + assert len(result["messages"]) > original_len