Skip to content

Python: Fix stateless replay of reasoning-paired tool calls#7233

Merged
moonbox3 merged 14 commits into
microsoft:mainfrom
moonbox3:issue-6074-mcp-reasoning-latest
Jul 22, 2026
Merged

Python: Fix stateless replay of reasoning-paired tool calls#7233
moonbox3 merged 14 commits into
microsoft:mainfrom
moonbox3:issue-6074-mcp-reasoning-latest

Conversation

@moonbox3

@moonbox3 moonbox3 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

Reasoning-model responses can bind a reasoning item to a tool-call item. During stateless replay, Agent Framework strips response-scoped reasoning items, but the client-side MCP path continued to serialize the paired function_call and function_call_output. Foundry rejects that orphaned call with HTTP 400 in multi-agent workflows. The earlier fix in #6907 covered hosted mcp_call items but not client-side MCP tools represented as function calls.

Description & Review Guide

  • What are the major changes? Extend stateless Responses replay sanitization to identify both hosted MCP and client-side function calls paired with reasoning. Completed historical function-call groups are removed atomically with their results, while active function loops remain intact. When middleware terminates a function loop before a follow-up assistant response, core records a durable marker on every result in the terminated batch and the OpenAI serializer uses it to remove the reasoning-paired calls and results atomically. Add deterministic end-to-end regression coverage through WorkflowBuilder, FoundryAgent, AgentExecutor, Responses SDK serialization, and Foundry hosting, including the middleware-terminated path.
  • What is the impact of these changes? Multi-turn and multi-agent reasoning workflows no longer send orphaned function_call or mcp_call items when service-side storage is unavailable. Existing non-reasoning tool history and active client-side function loops retain their current behavior.
  • What do you want reviewers to focus on? Please focus on the completed-function-loop detection in _tool_call_ids_paired_with_reasoning, the batch-wide middleware-termination marker, and the public workflow regression that distinguishes active tool execution from historical cross-agent replay.

Related Issue

Fixes #6074

No other open PR exists for this issue. This completes the client-side function-call path that remained after #6907.

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. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Copilot AI review requested due to automatic review settings July 21, 2026 03:28
@giles17 giles17 added the python Usage: [Issues, PRs], Target: Python label Jul 21, 2026
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _types.py12329792%61, 70–71, 125, 134, 153, 155, 159, 163, 165, 167, 169, 187, 191, 217, 239, 244, 249, 253, 283, 709–710, 891–892, 1350, 1425, 1460, 1480, 1490, 1548, 1682–1684, 1969–1974, 1999, 2054, 2059, 2069, 2077, 2084–2088, 2106, 2179, 2192, 2197, 2310, 2333, 2594, 2618, 2717, 2898–2899, 3001, 3230, 3283, 3302, 3341, 3352, 3354–3358, 3360, 3363–3371, 3381, 3470, 3607, 3612, 3617, 3622, 3626, 3712–3714, 3743, 3831–3835
packages/foundry/agent_framework_foundry
   _agent.py2614184%119, 122, 244–245, 249–251, 256–259, 358, 466–467, 484, 491, 494, 505–517, 521–522, 720–721, 724, 750, 760, 782, 870, 875, 879
packages/foundry_hosting/agent_framework_foundry_hosting
   _responses.py8569588%186–189, 276, 281, 305, 360–361, 375, 378–379, 427–428, 438, 483, 515–517, 520–521, 532, 575, 589, 664, 667, 673, 675, 747, 834–835, 953, 1183, 1196, 1687–1689, 1691, 1738–1739, 1741–1742, 1744–1745, 1747–1748, 1753, 1762, 1765–1767, 1769, 1779, 1783, 1796, 1887–1888, 1903–1904, 1906, 1911–1915, 1917, 1924–1925, 1927–1928, 1934, 1936–1940, 1947, 1953, 1975, 1981, 1987, 1989, 1991–1994, 2002, 2004, 2065–2067, 2081–2082, 2085–2086
packages/openai/agent_framework_openai
   _chat_client.py136911791%313, 326, 676–680, 688–691, 697–701, 751–758, 760–762, 769–771, 829, 837, 860, 1077, 1136, 1138, 1140, 1142, 1208, 1222, 1302, 1312, 1317, 1360, 1482–1483, 1498, 1763, 1870, 1875–1876, 1959, 1969, 1996, 2002, 2012, 2018, 2023, 2029, 2034–2035, 2115, 2159, 2162–2165, 2179, 2189–2190, 2202, 2244, 2309, 2326, 2329, 2356–2358, 2397, 2414, 2417, 2479, 2486, 2523–2524, 2559, 2597–2598, 2616–2617, 2789–2790, 2808, 2894–2902, 3080, 3095, 3145, 3155–3157, 3184–3186, 3196–3197, 3203, 3218, 3351–3352
TOTAL45262507088% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
9255 33 💤 0 ❌ 0 🔥 2m 10s ⏱️

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 5 | Confidence: 80% | Result: All clear

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


Automated review by moonbox3's agents

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

This PR fixes stateless replay for reasoning-model Responses by ensuring tool-call history that was originally paired with server-scoped reasoning items is not replayed “orphaned” (which Foundry rejects with HTTP 400), covering both hosted MCP calls and client-side function calls across workflow/multi-agent boundaries.

Changes:

  • Extend stateless replay sanitization to identify tool-call IDs paired with text_reasoning, including completed client-side function_call loops (call + result) and hosted MCP calls.
  • Update OpenAI chat client serialization behavior/tests so completed reasoning-paired function-call groups are removed when service-side storage is unavailable, while active tool loops remain unchanged.
  • Add deterministic end-to-end regression tests covering workflow cross-agent replay (FoundryAgent/WorkflowBuilder) and hosted server replay (ResponsesHostServer).

Reviewed changes

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

File Description
python/packages/openai/agent_framework_openai/_chat_client.py Drops reasoning-paired tool-call items atomically during stateless replay, including completed function-call loops.
python/packages/openai/tests/openai/test_openai_chat_client.py Updates unit coverage to assert completed reasoning-paired function-call history is stripped without storage, while active loops remain.
python/packages/foundry/tests/foundry/test_foundry_agent.py Adds deterministic workflow regression to ensure cross-agent replay doesn’t send orphaned function_call items.
python/packages/foundry_hosting/tests/test_responses_int.py Adds deterministic hosted-server regression ensuring second turn doesn’t replay mcp_call without its paired reasoning item.

@moonbox3
moonbox3 marked this pull request as ready for review July 21, 2026 03:44

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 5 | Confidence: 87% | Result: All clear

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


Automated review by moonbox3's agents

@moonbox3
moonbox3 enabled auto-merge July 21, 2026 06:57
Comment thread python/packages/openai/agent_framework_openai/_chat_client.py Outdated
@TaoChenOSU

Copy link
Copy Markdown
Contributor

Why did we choose to drop the function call and result pair instead of replaying the reasonings?

moonbox3 and others added 5 commits July 22, 2026 08:07
Key decisions:
- Request encrypted reasoning on client-managed Responses calls while preserving caller include values.
- Store encrypted payloads in Content.protected_data and reconstruct one provider reasoning item per reasoning id.
- Replay active and completed function call/result groups; retain continuation-owned history behavior and the existing orphan-safe MCP path.

Files changed:
- python/packages/openai/agent_framework_openai/_chat_client.py
- python/packages/openai/tests/openai/test_openai_chat_client.py

Next iteration:
- Extend encrypted reasoning preservation to streaming and framework serialization boundaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Key decisions:
- Capture encrypted reasoning from terminal streamed output items in Content.protected_data.
- Preserve summary and private reasoning as distinct framework contents while reconstructing one provider reasoning item per id.
- Prove replay after Message JSON and workflow checkpoint round trips, including encrypted-only and completed function groups.

Files changed:
- python/packages/core/agent_framework/_types.py
- python/packages/openai/agent_framework_openai/_chat_client.py
- python/packages/openai/tests/openai/test_openai_chat_client.py

Next iteration:
- Extend lossless stateless reasoning replay to hosted MCP call/output groups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Key decisions:
- Preserve hosted MCP call/output groups in client-managed history instead of deleting them when reasoning cannot be reconstructed.
- Keep call/result coalescing and orphan-result exclusion intact, while retaining continuation-owned duplicate avoidance.
- Cover completed, active, and multi-call reasoning groups plus the public outgoing request boundary.

Files changed:
- python/packages/openai/agent_framework_openai/_chat_client.py
- python/packages/openai/tests/openai/test_openai_chat_client.py

Next iteration:
- Preserve middleware-terminated and parallel function groups atomically.
- Add preflight rejection for non-replayable reasoning groups in the dedicated validation slice.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Key decisions:
- Return ordinary function results when middleware terminates a loop, removing the provider-specific durable marker.
- Preserve every parallel call and available sibling result as one encrypted reasoning group in stateless replay.
- Prove successful and policy-blocked batches through the public two-agent Foundry workflow and outgoing HTTP boundary.

Files changed:
- python/packages/core/agent_framework/_tools.py
- python/packages/core/tests/core/test_function_invocation_logic.py
- python/packages/openai/tests/openai/test_openai_chat_client.py
- python/packages/foundry/tests/foundry/test_foundry_agent.py

Next iteration:
- Add preflight rejection for non-replayable and partially compacted reasoning groups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Key decisions:
- Validate client-managed reasoning groups after compaction and report every affected reasoning and call identifier before transport.
- Permit service-owned continuation and fully excluded atomic groups while rejecting partial compaction projections.
- Surface encrypted-reasoning capability failures without lossy retries.

Files changed:
- python/packages/openai/agent_framework_openai/_chat_client.py
- python/packages/openai/tests/openai/test_openai_chat_client.py

Next iteration:
- Run the resource-specific Foundry proof and finish PR microsoft#7233; that live proof remains intentionally local and requires the configured developer resource.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@moonbox3

Copy link
Copy Markdown
Contributor Author

Why did we choose to drop the function call and result pair instead of replaying the reasonings?

I fixed this in latest commits.

Comment thread python/packages/openai/agent_framework_openai/_chat_client.py Outdated
Comment thread python/packages/openai/agent_framework_openai/_chat_client.py Outdated
@moonbox3
moonbox3 temporarily deployed to github-app-auth July 22, 2026 12:11 — with GitHub Actions Inactive
Comment thread python/packages/core/agent_framework/_types.py
@moonbox3
moonbox3 added this pull request to the merge queue Jul 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 22, 2026
@moonbox3
moonbox3 temporarily deployed to github-app-auth July 22, 2026 23:00 — with GitHub Actions Inactive
@moonbox3
moonbox3 temporarily deployed to github-app-auth July 22, 2026 23:19 — with GitHub Actions Inactive
@moonbox3
moonbox3 temporarily deployed to github-app-auth July 22, 2026 23:28 — with GitHub Actions Inactive
@moonbox3
moonbox3 enabled auto-merge July 22, 2026 23:44
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]: ] Orphaned mcp_call items cause HTTP 400 in multi-turn reasoning model flows

6 participants