Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ def _clone_chat_agent(self, agent: Agent[Any]) -> Agent[Any]:
middleware=agent.middleware,
require_per_service_call_history_persistence=agent.require_per_service_call_history_persistence,
default_options=cloned_options, # type: ignore[assignment]
additional_properties=deepcopy(agent.additional_properties),
)

def _apply_auto_tools(self, agent: Agent, targets: Sequence[HandoffConfiguration]) -> None:
Expand Down
48 changes: 48 additions & 0 deletions python/packages/orchestrations/tests/test_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pytest
from agent_framework import (
Agent,
AgentContext,
AgentResponse,
AgentResponseUpdate,
ChatOptions,
Expand All @@ -21,6 +22,7 @@
ResponseStream,
WorkflowEvent,
WorkflowRunState,
agent_middleware,
function_middleware,
resolve_agent_id,
tool,
Expand Down Expand Up @@ -805,6 +807,52 @@ async def tracking_middleware(context: FunctionInvocationContext, call_next):
assert tracking_middleware in cloned_middleware, "User function middleware should be preserved on cloned agent"


async def test_handoff_clone_preserves_additional_properties() -> None:
"""Handoff clones should preserve additional_properties from the original agent."""
tracked_properties: list[dict[str, Any]] = []

@agent_middleware
async def observe_properties(context: AgentContext, call_next):
agent = cast(Agent, context.agent)
tracked_properties.append(dict(agent.additional_properties))
await call_next()

coordinator = Agent(
id="coordinator",
name="coordinator",
client=MockChatClient(name="coordinator"),
additional_properties={"tenant": "contoso", "trace_tag": "triage"},
middleware=[observe_properties],
require_per_service_call_history_persistence=True,
)
specialist = Agent(
id="specialist",
name="specialist",
client=MockChatClient(name="specialist"),
require_per_service_call_history_persistence=True,
)

workflow = (
HandoffBuilder(
participants=_as_handoff_agents(coordinator, specialist),
termination_condition=lambda conversation: any(msg.role == "assistant" for msg in conversation),
)
.with_start_agent(_as_handoff_agent(coordinator))
.build()
)

await _drain(workflow.run("hello", stream=True))

assert tracked_properties == [{"tenant": "contoso", "trace_tag": "triage"}]

# The clone owns its own copy; the original agent's properties stay untouched.
executor = workflow.executors[resolve_agent_id(coordinator)]
assert isinstance(executor, HandoffAgentExecutor)
cloned_additional_properties = cast(Agent, executor.agent).additional_properties
assert cloned_additional_properties == {"tenant": "contoso", "trace_tag": "triage"}
assert cloned_additional_properties is not coordinator.additional_properties


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(
Expand Down
Loading