microsoft / microsoft/agent-framework

.NET: Intermittent HTTP 400 during GPT-5.6 approval resume through the documented stateless MAF Responses path

Open
#7,067 2 comments 0 reactions 1 assignee View on GitHub

@rogerbarreto is already working on this.

Since Jul 13, 2026.

.NET likely-fixed
Dominant language
Python
Stars
13.6k
Forks
2.3k
Avg merge
2d 45m
Merged PRs (30d)
358

Description

### Description

# Microsoft Agent Framework / GPT-5.6 stateless approval-resume failure

Status: **final submission report — sanitized HTTP 400 evidence captured on the exact 10.5.2 package set**

## Suggested title

Intermittent HTTP 400 during GPT-5.6 approval resume through the documented stateless Microsoft Agent Framework Responses path after the approved tool executes

## Product area

- Microsoft Agent Framework for .NET
- `Microsoft.Agents.AI.Foundry`
- `Microsoft.Extensions.AI.OpenAI`
- Azure AI Foundry / Azure OpenAI Responses API
- Human-in-the-loop `ApprovalRequiredAIFunction`

## Summary

We are evaluating GPT-5.6 for two chat tiers in a production Microsoft Agent Framework application. The existing production route remains unchanged and healthy on Azure OpenAI Chat Completions. GPT-5.6 requires a Responses-based route for our combination of explicit reasoning and function tools.

Using the documented MAF composition over the Foundry project Responses client, a protected tool correctly pauses and emits `ToolApprovalRequestContent` without executing. Resuming the session with the correlated approval is intermittent. On the exact 10.5.2 candidate, iteration 1 passed both model tiers; iteration 2 failed on Terra/low during `approval_resume` with `System.ClientModel.ClientResultException`, HTTP 400, and Azure `apim-request-id` `e03444a9-8d3c-41c2-ba9d-b3309d929790`. The approved synthetic tool had executed exactly once before the provider rejected the continuation. We stopped immediately without another provider call.

No customer data or side-effecting tool is involved. The reproduction tool only increments an in-memory counter and returns a constant string.

## Supported structure under test

The candidate migration uses Microsoft Agent Framework APIs only:

1. `AIProjectClient`
2. `GetProjectOpenAIClient().GetProjectResponsesClient()`
3. `AsIChatClientWithStoredOutputDisabled(modelId, includeReasoningEncryptedContent: true)`
4. `UseFunctionInvocation(...)`
5. `AsAIAgent(new ChatClientAgentOptions { ... })`
6. `ApprovalRequiredAIFunction`
7. `CreateSessionAsync()` / `RunStreamingAsync(...)`
8. `ToolApprovalRequestContent.CreateResponse(...)`
9. Resume with `RunStreamingAsync(new ChatMessage(ChatRole.User, [approval]), session)`

We are not asking Microsoft to validate a raw `ResponsesClient`, hand-written protocol adapter, mixed API route, or `previous_response_id` workaround.

## Package combinations validated

### Live confirmed failing candidate

- Target framework: `net9.0`
- `Microsoft.Agents.AI`: `1.6.2`
- `Microsoft.Agents.AI.Foundry`: `1.6.2-preview.260521.1`
- `Microsoft.Extensions.AI.OpenAI`: `10.5.2`
- `Azure.AI.Projects`: `2.1.0-beta.2`
- `OpenAI`: `2.10.0`

### Newer-package offline reconnaissance

- `Microsoft.Agents.AI`: `1.13.0`
- `Microsoft.Agents.AI.Foundry`: `1.13.0-preview.260703.1`
- `Microsoft.Extensions.AI.OpenAI`: `10.6.0`
- `Azure.AI.Projects`: `2.1.0-beta.4`
- `OpenAI`: `2.10.0`

Both combinations restore and build with zero warnings/errors and pass the same eight offline MAF contracts. The newer combination was not treated as a fix and was not deployed.

## Azure deployments exercised

- `gpt-5.6-terra`, reasoning effort `low`
- `gpt-5.6-sol`, reasoning effort `medium`

Resource endpoint, subscription, tenant, resource group, Container App configuration, tokens, and customer identifiers are intentionally omitted from this public-safe report. They can be supplied through an authenticated Microsoft support channel if required.

## Minimal reproduction behavior

1. Construct the stateless project Responses `IChatClient` with encrypted reasoning included.
2. Wrap a no-side-effect `AIFunction` in `ApprovalRequiredAIFunction`.
3. Create a MAF session.
4. Prompt the model to call the protected tool exactly once.
5. Consume the first stream.
6. Assert that exactly one `ToolApprovalRequestContent` is present and the tool execution count remains zero.
7. Create an approved response correlated to the emitted call.
8. Resume the same MAF session with the approval response.
9. Assert exactly one tool execution and one correlated `FunctionResultContent`.
10. Repeat the exact two-tier test in a bounded loop.

## Expected behavior

- Every first stream pauses before tool execution.
- Every approved resume executes the tool exactly once.
- The resume stream exposes the correlated function result and completes normally.
- Stateless requests use `store=false`, send neither `previous_response_id` nor `conversation`, and include `reasoning.encrypted_content`.

## Actual behavior

- Offline request and MAF contracts pass consistently.
- In the latest 10.5.2 bounded live run, iteration 1 passed both Terra/low and Sol/medium.
- Iteration 2 failed on Terra/low at `2026-07-11T12:39:18.3248089+00:00` during `approval_resume` with `System.ClientModel.ClientResultException` and HTTP 400.
- Azure correlation header: `apim-request-id: e03444a9-8d3c-41c2-ba9d-b3309d929790`.
- The response did not provide an allowlisted `x-ms-error-code` header, so the captured error code is `null`. No response body or exception message was retained.
- The approved synthetic tool execution count was exactly one when the provider rejected the continuation.
- An earlier 10.5.1 spike recorded in the frozen plan reported `invalid_payload` with protected-only reasoning. Because that earlier raw artifact was not retained, it remains historical context and a root-cause hypothesis; the new 10.5.2 evidence independently establishes HTTP 400 without claiming a body error code.
- Source inspection found the MEAI OpenAI conversion shape constructs `ReasoningResponseItem(reasoningContent.Text)` and assigns `EncryptedContent = ProtectedData`. That shape remained textually unchanged across the inspected 10.5.1, 10.5.2, 10.6.0, and 10.7.0 source revisions.

The protected-only reasoning conversion is our leading localized hypothesis, not a claim about Azure parser internals. The end-to-end intermittent failure is the blocker regardless of mechanism.

## Controls used to validate the report

- Eight exact offline contracts passed on the default package set, Matrix A, and Matrix B.
- Contracts cover stateless wire shape, denial, serialized cross-agent resume, dual approval correlation, replay idempotency, cancellation, skills/tool-loop caps, and streaming terminal order.
- Default and both matrix builds completed with zero warnings/errors.
- Fable independently reviewed the probe across multiple rounds, required corrections to package defaults, test discrimination, failure coverage, and redaction controls, and approved after every accepted finding was corrected and re-proven.
- Claude Code Opus 4.8 independently returned `PASS` in read/check mode.
- The repository diff-scoped Semgrep/gitleaks gate returned zero findings and zero vulnerable-package signals. Because the untracked probe sources were outside that diff scan, they also received dedicated line-level source and redaction reviews: Fable approved and Claude Code returned `PASS` before live capture and external packaging.
- Product `backend/` and `frontend/` trees have zero drift against the frozen production-evidence SHA.
- No product implementation, feature flag, QA deployment, upload, Avatar, SAP, database, or secret mutation occurred.

## Questions for Microsoft

1. Is this exact stateless MAF approval-resume structure supported for GPT-5.6 deployments when encrypted reasoning is included?
2. Is a protected-only reasoning item with empty text/summary and non-empty encrypted content valid input on approval resume?
3. Is there a released or planned version of `Microsoft.Extensions.AI.OpenAI`, `Microsoft.Agents.AI.Foundry`, or the Azure Responses service that changes this replay shape or fixes the rejection?
4. Is there a different officially documented MAF-only construction for stateless GPT-5.6 Responses with human tool approval, streaming, tools, and skills?
5. Can Microsoft provide a documented mitigation that preserves `store=false`, disables provider-side response/conversation chaining, preserves encrypted reasoning, and does not bypass approval semantics?

## Evidence included in the support package

- `02-diagnostic-evidence.public.json` — sanitized HTTP 400 metadata, request ID, exact package versions, final probe hashes, and pass/failure sequence.
- `03-offline-matrix-proof.json` — final-source Matrix A build and 9/9 offline contract proof.
- `04-independent-review-summary.json` — public-safe Fable and Claude Code review summary.
- `repro/` — the complete four-file instrumented approval-resume reproduction.
- `SHA256SUMS.txt` — integrity manifest for every other package file.

## Captured diagnostic safety boundary

The submission evidence includes the exact UTC timestamp, Azure `apim-request-id`, exception type, HTTP status, model/tier, phase, package versions, method-only stack frames, execution count, and probe hashes. It contains no bearer token, endpoint, request or response body, exception message, encrypted reasoning, customer/tenant data, file path, or unrelated Container App configuration. The capture projection and all assertion/exception paths were reviewed by Fable and independently passed Claude Code read/check review before the live call.

## Current disposition

Migration and QA deployment remain blocked. The existing production model route is unchanged. We will reconsider only after Microsoft supplies a documented MAF-compatible fix or mitigation and it passes the complete parity suite, including uploads and Avatar.

[microsoft-maf-gpt56-20260711.zip](https://github.com/user-attachments/files/29925046/microsoft-maf-gpt56-20260711.zip)

### Code Sample

```markdown
var responsesClient = projectClient
.GetProjectOpenAIClient()
.GetProjectResponsesClient();

var statelessClient =
responsesClient.AsIChatClientWithStoredOutputDisabled(
modelId,
includeReasoningEncryptedContent: true);

var tool = new ApprovalRequiredAIFunction(
AIFunctionFactory.Create(
() => "APPROVAL_EXECUTED",
"approval_probe"));

var agent = (ChatClientAgent)statelessClient
.AsBuilder()
.UseFunctionInvocation()
.Build()
.AsAIAgent(new ChatClientAgentOptions
{
ChatOptions = new ChatOptions
{
ModelId = modelId,
Tools = [tool],
},
UseProvidedChatClientAsIs = true,
});

var session = await agent.CreateSessionAsync();

var first = await agent.RunAsync(
"Call approval_probe now.",
session);

var request = first.Messages
.SelectMany(m => m.Contents)
.OfType()
.Single();

var approval = request.CreateResponse(
approved: true,
reason: request.ToolCall.CallId);

// Intermittently fails with HTTP 400:
var resumed = await agent.RunAsync(
new ChatMessage(ChatRole.User, [approval]),
session);
```

### Error Messages / Stack Traces

```markdown
Exception: System.ClientModel.ClientResultException
HTTP status: 400
Phase: approval_resume
Model: gpt-5.6-terra
Reasoning effort: low
UTC: 2026-07-11T12:39:18.3248089Z
Azure apim-request-id: e03444a9-8d3c-41c2-ba9d-b3309d929790
Synthetic tool execution count: 1

Sanitized stack:
OpenAI.ClientPipelineExtensions__ProcessMessageAsync_d__0.MoveNext
OpenAI.Responses.ResponsesClient__CreateResponseAsync_d__48.MoveNext
OpenAI.Responses.ResponsesClient___c__DisplayClass18_0___CreateResponseStreamingAsync_b__0_d.MoveNext

No response body or exception message was retained.
```

### Package Versions

Microsoft.Agents.AI: 1.6.2 Microsoft.Agents.AI.Foundry: 1.6.2-preview.260521.1 Microsoft.Extensions.AI.OpenAI: 10.5.2 Azure.AI.Projects: 2.1.0-beta.2 OpenAI: 2.10.0 Target framework: .NET 9.0

### .NET Version

Microsoft.Agents.AI: 1.6.2 Microsoft.Agents.AI.Foundry: 1.6.2-preview.260521.1 Microsoft.Extensions.AI.OpenAI: 10.5.2 Azure.AI.Projects: 2.1.0-beta.2 OpenAI: 2.10.0 Target framework: .NET 9.0

### Additional Context

[microsoft-maf-gpt56-20260711.zip](https://github.com/user-attachments/files/29925061/microsoft-maf-gpt56-20260711.zip)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.