From 26d3418abe4b69ee429d24a62d48da7782caccad Mon Sep 17 00:00:00 2001 From: Copilot Date: Mon, 13 Apr 2026 03:46:19 +0000 Subject: [PATCH 1/4] Fix HandoffBuilder dropping function-level middleware when cloning agents (#5173) _clone_chat_agent() was using agent.agent_middleware (agent-level only) instead of agent.middleware (all types), which silently dropped any function middleware registered on the original agent. Changed to use agent.middleware to preserve all middleware types (agent, function, and chat) during cloning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../_handoff.py | 2 +- .../orchestrations/tests/test_handoff.py | 40 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py index e71fdd3882..c3e156096c 100644 --- a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py +++ b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py @@ -287,7 +287,7 @@ def _clone_chat_agent(self, agent: Agent[Any]) -> Agent[Any]: name=agent.name, description=agent.description, context_providers=agent.context_providers, - middleware=agent.agent_middleware, + middleware=agent.middleware, require_per_service_call_history_persistence=agent.require_per_service_call_history_persistence, default_options=cloned_options, # type: ignore[assignment] ) diff --git a/python/packages/orchestrations/tests/test_handoff.py b/python/packages/orchestrations/tests/test_handoff.py index 225b408422..307a85d7f9 100644 --- a/python/packages/orchestrations/tests/test_handoff.py +++ b/python/packages/orchestrations/tests/test_handoff.py @@ -18,11 +18,12 @@ ResponseStream, WorkflowEvent, WorkflowRunState, + function_middleware, resolve_agent_id, tool, ) from agent_framework._clients import BaseChatClient -from agent_framework._middleware import ChatMiddlewareLayer, FunctionInvocationContext, MiddlewareTermination +from agent_framework._middleware import ChatMiddlewareLayer, FunctionInvocationContext, FunctionMiddleware, MiddlewareTermination from agent_framework._tools import FunctionInvocationLayer, FunctionTool from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder, HandoffSentEvent from pytest import param @@ -745,6 +746,43 @@ async def test_handoff_clone_preserves_per_service_call_history_persistence() -> assert all(message.role != "tool" for message in stored_messages) +async def test_handoff_clone_preserves_all_middleware_types() -> None: + """Handoff clones should preserve function and agent middleware from the original agent.""" + + @function_middleware + async def tracking_middleware(context: FunctionInvocationContext, call_next): + await call_next() + + agent_a = Agent( + id="agent_a", + name="agent_a", + client=MockChatClient(name="agent_a", handoff_to="agent_b"), + middleware=[tracking_middleware], + require_per_service_call_history_persistence=True, + ) + agent_b = Agent( + id="agent_b", + name="agent_b", + client=MockChatClient(name="agent_b"), + default_options={"tool_choice": "none"}, + require_per_service_call_history_persistence=True, + ) + + workflow = ( + HandoffBuilder(participants=[agent_a, agent_b], termination_condition=lambda _: False) + .with_start_agent(agent_a) + .add_handoff(agent_a, [agent_b]) + .add_handoff(agent_b, [agent_a]) + .build() + ) + + executor = workflow.executors[resolve_agent_id(agent_a)] + assert isinstance(executor, HandoffAgentExecutor) + cloned_middleware = executor._agent.middleware or [] + function_mw = [m for m in cloned_middleware if isinstance(m, FunctionMiddleware)] + assert len(function_mw) >= 1, "Function middleware should be preserved on cloned agent" + + def test_clean_conversation_for_handoff_keeps_text_only_history() -> None: """Tool-control messages must be excluded from persisted handoff history.""" function_call = Content.from_function_call( From 7b0a5e1d2d6fa129ef882d7a2a08f669b3999a53 Mon Sep 17 00:00:00 2001 From: Copilot Date: Mon, 13 Apr 2026 03:54:12 +0000 Subject: [PATCH 2/4] Python: Fix HandoffBuilder dropping function-level middleware when cloning agents Fixes #5173 --- python/packages/orchestrations/tests/test_handoff.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/packages/orchestrations/tests/test_handoff.py b/python/packages/orchestrations/tests/test_handoff.py index 307a85d7f9..38f1824c7a 100644 --- a/python/packages/orchestrations/tests/test_handoff.py +++ b/python/packages/orchestrations/tests/test_handoff.py @@ -23,7 +23,12 @@ tool, ) from agent_framework._clients import BaseChatClient -from agent_framework._middleware import ChatMiddlewareLayer, FunctionInvocationContext, FunctionMiddleware, MiddlewareTermination +from agent_framework._middleware import ( + ChatMiddlewareLayer, + FunctionInvocationContext, + FunctionMiddleware, + MiddlewareTermination, +) from agent_framework._tools import FunctionInvocationLayer, FunctionTool from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder, HandoffSentEvent from pytest import param From 46d4095fa1e457a7089e11435de146cf0888f4d9 Mon Sep 17 00:00:00 2001 From: Copilot Date: Mon, 13 Apr 2026 03:58:59 +0000 Subject: [PATCH 3/4] Fix false-positive middleware regression test (#5173) The test used isinstance(m, FunctionMiddleware) which matched _AutoHandoffMiddleware (always appended during build) instead of the user's @function_middleware decorator. Assert directly that tracking_middleware is present in the cloned agent's middleware list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/packages/orchestrations/tests/test_handoff.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/packages/orchestrations/tests/test_handoff.py b/python/packages/orchestrations/tests/test_handoff.py index 38f1824c7a..729ae8aa00 100644 --- a/python/packages/orchestrations/tests/test_handoff.py +++ b/python/packages/orchestrations/tests/test_handoff.py @@ -26,7 +26,6 @@ from agent_framework._middleware import ( ChatMiddlewareLayer, FunctionInvocationContext, - FunctionMiddleware, MiddlewareTermination, ) from agent_framework._tools import FunctionInvocationLayer, FunctionTool @@ -784,8 +783,9 @@ async def tracking_middleware(context: FunctionInvocationContext, call_next): executor = workflow.executors[resolve_agent_id(agent_a)] assert isinstance(executor, HandoffAgentExecutor) cloned_middleware = executor._agent.middleware or [] - function_mw = [m for m in cloned_middleware if isinstance(m, FunctionMiddleware)] - assert len(function_mw) >= 1, "Function middleware should be preserved on cloned agent" + assert tracking_middleware in cloned_middleware, ( + "User function middleware should be preserved on cloned agent" + ) def test_clean_conversation_for_handoff_keeps_text_only_history() -> None: From acf8b3c1768dcd473f70688c35ca8be56522b9a1 Mon Sep 17 00:00:00 2001 From: Copilot Date: Mon, 13 Apr 2026 04:02:22 +0000 Subject: [PATCH 4/4] Address review feedback for #5173: Python: [Bug]: HandoffBuilder drops function-level middleware when cloning agents --- python/packages/orchestrations/tests/test_handoff.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/packages/orchestrations/tests/test_handoff.py b/python/packages/orchestrations/tests/test_handoff.py index 729ae8aa00..a512d9b9df 100644 --- a/python/packages/orchestrations/tests/test_handoff.py +++ b/python/packages/orchestrations/tests/test_handoff.py @@ -783,9 +783,7 @@ async def tracking_middleware(context: FunctionInvocationContext, call_next): executor = workflow.executors[resolve_agent_id(agent_a)] assert isinstance(executor, HandoffAgentExecutor) cloned_middleware = executor._agent.middleware or [] - assert tracking_middleware in cloned_middleware, ( - "User function middleware should be preserved on cloned agent" - ) + assert tracking_middleware in cloned_middleware, "User function middleware should be preserved on cloned agent" def test_clean_conversation_for_handoff_keeps_text_only_history() -> None: