Skip to content

Python: Refactor workflow as agent pending request handling#6259

Open
TaoChenOSU wants to merge 6 commits into
mainfrom
feature/python-refactor-workflow-as-agent-pending-request-handling
Open

Python: Refactor workflow as agent pending request handling#6259
TaoChenOSU wants to merge 6 commits into
mainfrom
feature/python-refactor-workflow-as-agent-pending-request-handling

Conversation

@TaoChenOSU
Copy link
Copy Markdown
Contributor

@TaoChenOSU TaoChenOSU commented Jun 2, 2026

Motivation and Context

Function approval was not handled correctly when the approval comes from an agent within a workflow that is used as an agent.

Addresses #5654

Description

  1. Add support for handling function approval requests coming from an agent within a workflow that is used as agent and hosted.
  2. Simplify the function approval flow for workflow as agent.
  3. Add tests.

Old data flow:

1. An approval request emitted from an agent in a workflow
2. AgentExecutor captures the request and emits it as a request info event
3. WorkflowAgent captures the request info event, serializes it in a function call content argument and wraps it in an approval request
4. Client captures the request, approval or denies, and sends the response to the workflow as an agent
5. WorkflowAgent gets the response, unwraps it, and deserializes the argument string to get the response
6. WorkflowAgent then sends the response to the underlying workflow

New data flow:

1. An approval request emitted from an agent in a workflow
2. AgentExecutor captures the request and emits it as a request info event
3. WorkflowAgent captures the request info event and unwraps the approval request
4. Client captures the request, approval or denies, and sends the response to the workflow as an agent
5. WorkflowAgent then sends the response to the underlying workflow

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

@TaoChenOSU TaoChenOSU self-assigned this Jun 2, 2026
Copilot AI review requested due to automatic review settings June 2, 2026 05:11
@TaoChenOSU TaoChenOSU added python workflows Related to Workflows in agent-framework Foundry Hosted Agent Issues related to foundry hosted agents labels Jun 2, 2026
@github-actions github-actions Bot changed the title Refactor workflow as agent pending request handling Python: Refactor workflow as agent pending request handling Jun 2, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors Python workflow-as-agent “pending request” handling so tool-approval flows (FunctionApprovalRequest/Response) round-trip correctly across WorkflowAgent, AgentExecutor, and the Foundry ResponsesHostServer HTTP pipeline. It also introduces a Workflow.status property to expose run-level state transitions.

Changes:

  • Add Workflow.status (mirrors emitted status events) and new tests covering status transitions, including pending-request scenarios.
  • Update WorkflowAgent/AgentExecutor to route tool-approval requests via request_info using the underlying approval request id (so pending request IDs match what the caller echoes back).
  • Update Foundry hosting response conversion to persist approval requests and rehydrate them when converting mcp_approval_response items; add end-to-end hosting tests for workflow agents with approvals.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
python/samples/02-agents/tools/function_tool_with_approval.py Disables the streaming demo invocation in main().
python/packages/foundry_hosting/tests/test_responses.py Adds end-to-end HTTP tests for hosting WorkflowAgent with tool approvals (streaming and non-streaming).
python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py Plumbs approval storage through input/output conversion so approval responses can be reconstructed.
python/packages/core/tests/workflow/test_workflow_status.py New tests for the new Workflow.status property across success/failure/pending-request flows.
python/packages/core/tests/workflow/test_workflow_agent.py Updates request-info/tool-approval expectations and adds tests ensuring raw tool-approval content forwards unchanged.
python/packages/core/tests/workflow/test_agent_executor.py Adds regression tests for “double-emission” of approval payload and request-id matching.
python/packages/core/tests/core/test_skills.py Minor parentheses cleanup in async awaits.
python/packages/core/agent_framework/_workflows/_workflow.py Implements run-level status tracking (Workflow.status) and updates during execution.
python/packages/core/agent_framework/_workflows/_agent.py Refactors WorkflowAgent continuation logic to depend on Workflow.status and adjusts request-info conversion.
python/packages/core/agent_framework/_workflows/_agent_executor.py Adjusts approval request handling to avoid double-emitting approval payloads and passes explicit request_id.
python/packages/core/agent_framework/_skills.py Minor formatting of tool descriptions.

Comment thread python/packages/core/agent_framework/_workflows/_agent_executor.py
Comment thread python/packages/core/agent_framework/_workflows/_agent_executor.py
Comment thread python/packages/core/agent_framework/_workflows/_agent_executor.py
Comment thread python/packages/core/agent_framework/_workflows/_agent_executor.py
Comment thread python/packages/core/agent_framework/_workflows/_agent.py
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 62% | Result: All clear

Reviewed: Correctness, Security Reliability, Test Coverage, Design Approach


Automated review by TaoChenOSU's agents

@TaoChenOSU TaoChenOSU marked this pull request as ready for review June 2, 2026 06:02
@moonbox3
Copy link
Copy Markdown
Contributor

moonbox3 commented Jun 2, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework/_workflows
   _agent.py3464586%69, 77–83, 119–120, 269, 282, 349, 360, 362, 418, 484, 586, 664, 694, 722, 741–744, 750, 754–756, 758, 767, 828, 835, 841–842, 853, 885, 892, 913, 922, 926, 928–930, 937
   _agent_executor.py2081791%166, 190, 231, 255, 275–276, 356–358, 360, 370–371, 494, 521–522, 594, 600
   _workflow.py3472493%59, 61, 66, 90, 95, 156, 192, 416–418, 420–421, 445, 479, 648, 859, 880, 928, 940, 946, 951, 971–973
packages/foundry_hosting/agent_framework_foundry_hosting
   _responses.py6856790%186–189, 254, 336–337, 347, 384, 439, 453, 505, 508–512, 531, 534, 540, 542, 601, 951, 964, 1430–1432, 1434, 1481–1482, 1484–1485, 1487–1488, 1490–1491, 1496, 1505, 1508–1510, 1512, 1526, 1539, 1584–1585, 1587, 1591–1595, 1597, 1604–1605, 1607–1608, 1614, 1616–1620, 1627, 1633, 1655, 1661
TOTAL37713435888% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
7519 34 💤 0 ❌ 0 🔥 2m 7s ⏱️

) from exc
elif isinstance(arguments_payload, dict):
parsed_args = self.RequestInfoFunctionArgs.from_dict(arguments_payload)
if function_call.name != self.REQUEST_INFO_FUNCTION_NAME:
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.

Old code raised AgentInvalidResponseException on not content.approved for every approval response. New code never reads approved. Raw-approval branch (line 736) forwards the full content, so the workflow still sees approved. But the request_info-derived branch (line 753) forwards parsed_args.data, the echoed request payload, so a rejected response (approved=False) is indistinguishable from approval and the workflow proceeds as fulfilled. Is the asymmetry intended? If a declined request_info should still short-circuit, should we read approved before line 753?

for user_input_request in response.user_input_requests:
self._pending_agent_requests[user_input_request.id] = user_input_request # type: ignore[index]
await ctx.request_info(user_input_request, Content)
await ctx.request_info(user_input_request, Content, request_id=user_input_request.id)
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.

What happens when a response carries a user_input_request plus other contents (model emits explanatory text alongside the approval request)? We return None below, so yield_output never runs and the non-request contents never reach workflow output. The warning above documents the drop but does not prevent it. Common case for chatty models. Could we yield the non-request contents before issuing the requests instead of log-and-drop?

# Only yield output events for updates that do not contain user input requests.
# This is to avoid emitting two events of different types ('output' and 'request_info')
# that carry the same payload.
await ctx.yield_output(update)
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.

Same drop on the streaming path. An update carrying both a user_input_request and content deltas takes the if branch, so the else yield_output(update) is skipped and the deltas are never emitted. The reconstructed response only feeds request extraction and returns None at line 544 when requests exist, so the text is lost here too. Worth splitting the request contents from the rest and yielding the remainder? Are we not supporting this scenario?

# original tool name ('delete_file'), NOT 'request_info'. This exercises the
# branch in WorkflowAgent._extract_function_responses that routes raw
# tool-approval responses straight through using content.id.
approval_response = approval.to_function_approval_response(approved=True) # type: ignore[attr-defined]
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.

Are we covering the rejection path? This is the only resume test and it asserts approved=True through the raw-approval branch. Couldn't find a test that sends approved=False, which is the contract that changed (old code raised, new code forwards). Also no test drives the request_info-derived branch at _agent.py:753 or the AgentException invalid-state raise in _run_core. A rejected-approval test pinning the new intended behavior would lock down the finding that I commented on above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Foundry Hosted Agent Issues related to foundry hosted agents python workflows Related to Workflows in agent-framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants