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
@moonbox3
moonbox3 added this pull request to the merge queue Jul 22, 2026
Merged via the queue into microsoft:main with commit a2927c1 Jul 22, 2026
37 checks passed
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