Python: keep agent compaction configuration when HandoffBuilder clones participants - #8329
Conversation
…s participants `_clone_chat_agent` rebuilds each participant through `Agent(...)` to inject the handoff tools and middleware, listing constructor arguments by hand. `compaction_strategy` and `tokenizer` live outside `default_options` and were not in that list, so the clone silently got `None` for both and the participant ran with no compaction at all. Nothing raises: a long handoff conversation simply grows unbounded until it trips the model's context limit, after showing up as cost and latency first. Both are forwarded by reference rather than deep-copied, matching `context_providers` and `middleware`. They hold immutable configuration the clone never mutates, and a tokenizer can carry a vocabulary that is expensive or unsafe to copy. Rebuilding through the constructor is deliberate and stays: `Agent.__init__` re-separates MCP tools from regular ones, which is why the method recombines `agent.mcp_tools` into the tools list first. Copying the instance instead would skip that. The second test is the one that matters beyond this bug. Every parameter added to `Agent.__init__` has to be added to this call or it is silently dropped, and the `test_handoff_clone_preserves_*` tests above were each written after that already happened. The guard reads the keyword names off the `Agent(...)` call with `ast` and asserts every constructor parameter is either forwarded or named as deliberately handled elsewhere, so the next missing field fails here rather than in a user's workflow. Reading the AST rather than substring-matching the source means a commented-out argument cannot satisfy it.
There was a problem hiding this comment.
🟢 Approval recommended
The focused fix correctly preserves both omitted fields and includes appropriate regression protection.
Pull request overview
Preserves agent-level compaction settings when handoff participants are cloned.
Changes:
- Forwards
compaction_strategyandtokenizerto cloned agents. - Adds regression and constructor-field drift tests.
File summaries
| File | Description |
|---|---|
python/packages/orchestrations/agent_framework_orchestrations/_handoff.py |
Preserves compaction configuration during cloning. |
python/packages/orchestrations/tests/test_handoff.py |
Tests configuration retention and future constructor coverage. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
|
Thanks for the update. Before this is ready, could you please:
Once those are addressed, please re-request review. Thanks! |
…keeps-compaction-8320
|
Thanks for the contribution. The approval-gated workflows completed, but the current head is not green: |
…keeps-compaction-8320
|
Green now, in The run was 1 failed / 12546 passed, and the single failure was Evan Mattson (@moonbox3)'s #8380, "stabilize Hyperlight output cleanup test", fixes it; this branch simply sat Re-verified after the merge rather than assuming, since No change to this PR's own code was needed. On re-requesting review: you are still in the pending-reviewer list here -- your last message was a PR comment rather than a submitted review -- so there is no re-request for me to make. The one thing that would unblock it is releasing the approval-gated workflows on this head: they queue at |
…keeps-compaction-8320
Motivation & Context
HandoffAgentExecutor._clone_chat_agentrebuilds each participant throughAgent(...)to injecthandoff tools and middleware, listing constructor arguments by hand.
compaction_strategyandtokenizerlive outsidedefault_optionsand were missing from that list, so the clone gotNonefor both:
Nothing raises. The participant simply runs with no compaction, so a long handoff conversation grows
unbounded until it trips the model's context limit — surfacing as cost and latency well before it
surfaces as an error.
One note on the issue text: it says "the cloned executor agent gets
Nonefor both" and "forwardboth fields", but only ever names
compaction_strategy. The second field istokenizer. A fixwritten from the title alone would close half the bug.
Description & Review Guide
What are the major changes? Two arguments forwarded in
_clone_chat_agent, plus two tests.No API change, no change to compaction behaviour itself.
Why by reference and not
deepcopy. Both are forwarded as-is, matchingcontext_providersand
middlewareon the lines above. I checked that the strategies hold only immutableconfiguration —
ToolResultCompactionStrategystores one threshold,ContextWindowCompactionStrategyten — and
CharacterEstimatorTokenizeris stateless, so clones cannot interfere through them.additional_propertiesstays deep-copied because it is a mutable mapping the clone does own.Why not replace the hand-rolled constructor with a copy.
Agentinherits__copy__and__deepcopy__fromSerializationMixin, which copy every instance field and would have fixed thisclass of bug permanently — so I checked whether they could be used here. They cannot:
Agent.__init__re-separates MCP tools from regular tools, which is exactly why this methodrecombines
agent.mcp_toolsinto the tools list before calling it. Copying the instance would skipthat separation, and
copywould additionally sharedefault_optionswith the original, somutating
allow_multiple_tool_callson the clone would reach back into the caller's agent.Rebuilding through the constructor is deliberate and stays.
The second test is the point. Every parameter added to
Agent.__init__has to be added to thiscall or it is silently dropped, and the
test_handoff_clone_preserves_*tests directly above wereeach written after that already happened — this is at least the fourth field to go missing.
test_handoff_clone_forwards_every_agent_constructor_fieldreads the keyword names off theAgent(...)call withastand asserts every constructor parameter is either forwarded or named inhandled_elsewherewith a reason. The next new parameter fails there instead of in a user'sworkflow. Reading the AST rather than substring-matching the source is deliberate: I verified that a
commented-out argument still fails the guard, which a substring check would have passed.
What is the impact of these changes? Compaction configured on a handoff participant now takes
effect. That is a behaviour change in the sense that previously-ignored configuration starts
working, so a long-running handoff conversation will begin compacting where it did not before. I
have not marked it a breaking change because it makes documented configuration behave as
documented, but say if you would rather label it.
What do you want reviewers to focus on? Whether by-reference is the sharing you want for these
two, and whether the drift guard is welcome — it is a little unusual for this suite, and I would
rather drop it than have it be a maintenance burden.
_clone_chat_agentis the onlyclone-by-reconstruct site in the repo, so there is no sibling instance to fix.
Validation: orchestrations 211 passing with
poe check -P orchestrationsclean across all fivetype checkers, and core 5329 passing unchanged. Both new tests fail against the unfixed tree.
Related Issue
Fixes #8320
Contribution Checklist