Skip to content

Python: Fix handoff target invoked with no messages when response has no text - #7576

Open
Sadok Barbouche (cr-sbarbouche) wants to merge 2 commits into
microsoft:mainfrom
cr-sbarbouche:fix-handoff-empty-target-invocation
Open

Python: Fix handoff target invoked with no messages when response has no text#7576
Sadok Barbouche (cr-sbarbouche) wants to merge 2 commits into
microsoft:mainfrom
cr-sbarbouche:fix-handoff-empty-target-invocation

Conversation

@cr-sbarbouche

Copy link
Copy Markdown

Motivation & Context

HandoffAgentExecutor broadcasts only the cleaned agent response to the other agents, then sends the handoff target an AgentExecutorRequest. clean_conversation_for_handoff drops every message with no text content, so a response consisting solely of the handoff tool call (no text) cleans to an empty list. The handoff target's request then carries messages=[].

AgentExecutor tolerates an empty cache and only logs a warning, so most chat agents survive. Agents that require input do not — for example an A2AAgent target raises ValueError: At least one message is required when starting a new task (no continuation_token)., aborting the whole workflow. This is especially reachable in the handoff pattern because the handoff itself is a function call: an agent that hands off without also emitting text produces exactly the tool-only response that cleans away to nothing.

This is the same root cause as #7456, in a different orchestrator. PR #7549 fixed the group-chat orchestrators by supplying a continuation instruction when the selected speaker would otherwise receive no messages; the handoff path was deliberately left out of that PR to keep it scoped to the linked issue.

Description & Review Guide

  • What are the major changes?
    HandoffAgentExecutor._run_agent_and_emit now sends the handoff target a continuation instruction message instead of messages=[] when the handing-off agent's cleaned response is empty. The instruction text lives in a private module constant, _HANDOFF_CONTINUATION_DEFAULT_INSTRUCTION, following the existing _AUTONOMOUS_MODE_DEFAULT_PROMPT pattern already in _handoff.py.

  • What is the impact of these changes?
    The fix is scoped to the case where the handoff response cleans to no messages at all. When the response has any text content, nothing changes and no instruction is injected (covered by a dedicated test). It applies to every agent type rather than special-casing A2A, so any participant that rejects empty input benefits.

  • What do you want reviewers to focus on?
    Whether the injected instruction is the right shape versus replaying other conversation context into the request, and whether cleaned_response being empty is the correct (and only) signal to check here in the handoff path.

Tests cover a handoff whose response has only the handoff tool call and no text (streaming and non-streaming), asserting the target's first invocation carries the continuation instruction, plus a companion test asserting the instruction is not injected when the handoff response already has text.

Related Issue

Fixes #7573

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change.

… no text

HandoffAgentExecutor sent the handoff target an AgentExecutorRequest with
messages=[] whenever the handing-off agent's response cleaned to an empty
conversation (e.g. a response consisting solely of the handoff tool call,
with no text content). Send a continuation instruction instead so agents
that reject empty input are never invoked with nothing at all.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents handoff targets from receiving an empty request after a textless tool-only handoff response.

Changes:

  • Adds a fallback continuation instruction for empty cleaned responses.
  • Adds streaming, non-streaming, and text-preservation tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
_handoff.py Injects the fallback handoff message.
test_handoff.py Adds regression coverage for textless handoffs.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread python/packages/orchestrations/tests/test_handoff.py Outdated
Comment thread python/packages/orchestrations/agent_framework_orchestrations/_handoff.py Outdated
The prior test went through HandoffBuilder's full multi-agent graph, but
that graph broadcasts the initial user message to every participant before
anyone runs, so the target's cache is never actually empty there - the test
only checked for the injected text, not the reported failure mode. Drive
HandoffAgentExecutor directly instead, so the assertion is exactly about
what the fix changes: the direct request sent to the handoff target. Also
correct the source comment, which claimed the target's cache is guaranteed
empty when it's actually conditional on broadcast history, and drop the
A2AAgent example (it can't be a HandoffBuilder participant).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: Handoff target can be invoked with no messages when the response cleans to empty

2 participants