Skip to content

Python: [Bug]: HandoffBuilder drops function-level middleware when cloning agents #5173

@nadeemis

Description

@nadeemis

Description

Summary

HandoffAgentExecutor._clone_chat_agent() passes agent.agent_middleware (agent-level only) instead of agent.middleware (all types) when constructing the cloned agent. This silently drops any @function_middleware registered on the original agent, so function-level middleware never executes during handoff workflows.

Environment

  • agent-framework version: 1.0.0
  • Python version: 3.13
  • OS: macOS (arm64)

Steps to Reproduce

from agent_framework import Agent, function_middleware, FunctionInvocationContext
from agent_framework.orchestrations import HandoffBuilder

# 1. Define a function middleware
@function_middleware
async def my_logging_middleware(context: FunctionInvocationContext, call_next):
    print(f"TOOL CALLED: {context.function.name}")
    await call_next()
    print(f"TOOL RESULT: {context.result}")

# 2. Create agents with function middleware
agent_a = Agent(
    client=client,
    name="AgentA",
    instructions="You are agent A.",
    tools=[some_tool],
    middleware=[my_logging_middleware],
)

agent_b = Agent(
    client=client,
    name="AgentB",
    instructions="You are agent B.",
    tools=[some_tool],
    middleware=[my_logging_middleware],
)

# 3. Verify middleware is present on the original agent
print(agent_a.middleware)        # [my_logging_middleware] ✓
print(agent_a.agent_middleware)  # []                      ← only agent-level

# 4. Build a handoff workflow
workflow = (
    HandoffBuilder(participants=[agent_a, agent_b])
    .with_start_agent(agent_a)
    .add_handoff(agent_a, [agent_b])
    .add_handoff(agent_b, [agent_a])
    .build()
)

# 5. Run the workflow — my_logging_middleware never fires
async for event in workflow.run("Do something", stream=True):
    pass

Expected Behavior

Function-level middleware registered on agents should be preserved when agents are cloned for handoff workflows. Tool call logging (or any custom function middleware) should execute normally during handoff execution.

Actual Behavior

Function-level middleware is silently dropped. No error is raised. Tool calls execute but the middleware is never invoked.

Root Cause

In agent_framework_orchestrations/_handoff.py, the _clone_chat_agent method uses agent.agent_middleware which only returns agent-level middleware:

# _handoff.py, HandoffAgentExecutor._clone_chat_agent()

def _clone_chat_agent(self, agent: Agent[Any]) -> Agent[Any]:
    options = agent.default_options
    # ...
    return Agent(
        client=agent.client,
        id=agent.id,
        name=agent.name,
        description=agent.description,
        context_providers=agent.context_providers,
        middleware=agent.agent_middleware,  # ← BUG: only agent-level middleware
        require_per_service_call_history_persistence=agent.require_per_service_call_history_persistence,
        default_options=cloned_options,
    )

The Agent class separates middleware into buckets during __init__ via categorize_middleware() in agent_framework/_middleware.py:

  • agent.middleware → full original list (all types)
  • agent.agent_middleware → only @agent_middleware items
  • Function middleware → stored separately for runtime invocation

Since _clone_chat_agent reads from agent.agent_middleware, any @function_middleware is lost.

Notably, _prepare_agent_with_handoffs() does later append _AutoHandoffMiddleware (itself a FunctionMiddleware) to cloned_agent.middleware, so the framework's own function middleware works — but user-provided function middleware is already gone by that point.

Code Sample

Error Messages / Stack Traces

Package Versions

agent-framework-core:1.0.0

Python Version

Python 3.13

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions