From 114cfeedf6918eff375e7b683a2192dd2682731b Mon Sep 17 00:00:00 2001 From: Abhijeet Sharma Date: Mon, 17 Aug 2026 13:08:37 +0530 Subject: [PATCH 1/2] docs: correct Agent.clone list attribute semantics --- src/agents/agent.py | 17 ++++++--- tests/test_agent_clone_shallow_copy.py | 49 ++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/agents/agent.py b/src/agents/agent.py index d0df7267a8..72d3e03265 100644 --- a/src/agents/agent.py +++ b/src/agents/agent.py @@ -548,11 +548,18 @@ def __post_init__(self): def clone(self, **kwargs: Any) -> Agent[TContext]: """Make a copy of the agent, with the given arguments changed. Notes: - - Uses `dataclasses.replace`, which performs a **shallow copy**. - - Mutable attributes like `tools` and `handoffs` are shallow-copied: - new list objects are created only if overridden, but their contents - (tool functions and handoff objects) are shared with the original. - - To modify these independently, pass new lists when calling `clone()`. + - Uses `dataclasses.replace`, which performs a **shallow copy** and never copies a + list attribute such as `tools`, `handoffs`, `mcp_servers`, `input_guardrails`, or + `output_guardrails`. Each of those attributes is whatever the merged arguments hold. + - An attribute you do not pass arrives as the original agent's own list, so both + agents hold that one list and its entries. Appending through either agent, for + example `cloned.tools.append(extra_tool)`, therefore also changes the other. + - An attribute you do pass is used exactly as given, so it shares a list or an entry + with the original agent only where you reused one. `agent.clone(tools=agent.tools)` + still shares that list, while `agent.clone(tools=[other_tool])` shares nothing. + - To give the clone a list that no other agent holds, pass a new one, for example + `agent.clone(tools=[*agent.tools, extra_tool])`. The entries copied into it remain + the same objects the original agent holds. Example: ```python new_agent = agent.clone(instructions="New instructions") diff --git a/tests/test_agent_clone_shallow_copy.py b/tests/test_agent_clone_shallow_copy.py index 44b41bd3d0..79559898b2 100644 --- a/tests/test_agent_clone_shallow_copy.py +++ b/tests/test_agent_clone_shallow_copy.py @@ -30,3 +30,52 @@ def test_agent_clone_shallow_copy(): assert cloned.tools[0] is original.tools[0], "Tool objects should be same instance" assert cloned.handoffs is not original.handoffs, "Handoffs should be different list" assert cloned.handoffs[0] is original.handoffs[0], "Handoff objects should be same instance" + + +def test_agent_clone_keeps_list_attributes_it_is_not_given(): + """An attribute that clone() is not given arrives as the original agent's own list.""" + target_agent = Agent(name="Target") + original = Agent(name="Original", tools=[greet], handoffs=[handoff(target_agent)]) + + cloned = original.clone(name="Cloned") + + assert cloned.tools is original.tools + assert cloned.handoffs is original.handoffs + + +def test_agent_clone_uses_a_given_list_as_is(): + """An attribute passed to clone() is used exactly as given, entries included.""" + + @function_tool + def farewell(name: str) -> str: + return f"Goodbye, {name}!" + + original = Agent(name="Original", tools=[greet]) + supplied = [farewell] + + cloned = original.clone(name="Cloned", tools=supplied) + + assert cloned.tools is supplied + assert original.tools == [greet] + # Passing a list does not by itself share entries with the original agent. + assert all(tool is not greet for tool in cloned.tools) + + +def test_agent_clone_still_shares_when_given_the_original_list(): + """Passing the original agent's own list keeps both agents on that one list.""" + original = Agent(name="Original", tools=[greet]) + + cloned = original.clone(name="Cloned", tools=original.tools) + + assert cloned.tools is original.tools + + +def test_agent_clone_shared_list_mutation_affects_both_agents(): + """Appending through either agent changes the other while they hold one list.""" + original = Agent(name="Original", tools=[greet]) + cloned = original.clone(name="Cloned") + + cloned.tools.append(greet) + + assert original.tools == cloned.tools + assert len(original.tools) == 2 From 2943fe137183769fc209915c64abafb5560a224d Mon Sep 17 00:00:00 2001 From: Abhijeet Sharma Date: Mon, 17 Aug 2026 14:03:30 +0530 Subject: [PATCH 2/2] docs: correct RealtimeAgent.clone list attribute semantics --- src/agents/realtime/agent.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/agents/realtime/agent.py b/src/agents/realtime/agent.py index 0fcead874b..aa325eff02 100644 --- a/src/agents/realtime/agent.py +++ b/src/agents/realtime/agent.py @@ -103,11 +103,18 @@ def clone(self, **kwargs: Any) -> RealtimeAgent[TContext]: """Make a copy of the agent, with the given arguments changed. Notes: - - Uses `dataclasses.replace`, which performs a **shallow copy**. - - Mutable attributes like `tools` and `handoffs` are shallow-copied: - new list objects are created only if overridden, but their contents - (tool functions and handoff objects) are shared with the original. - - To modify these independently, pass new lists when calling `clone()`. + - Uses `dataclasses.replace`, which performs a **shallow copy** and never copies a + list attribute such as `tools`, `handoffs`, `mcp_servers`, or `output_guardrails`. + Each of those attributes is whatever the merged arguments hold. + - An attribute you do not pass arrives as the original agent's own list, so both + agents hold that one list and its entries. Appending through either agent, for + example `cloned.tools.append(extra_tool)`, therefore also changes the other. + - An attribute you do pass is used exactly as given, so it shares a list or an entry + with the original agent only where you reused one. `agent.clone(tools=agent.tools)` + still shares that list, while `agent.clone(tools=[other_tool])` shares nothing. + - To give the clone a list that no other agent holds, pass a new one, for example + `agent.clone(tools=[*agent.tools, extra_tool])`. The entries copied into it remain + the same objects the original agent holds. Example: ```python