Skip to content

.NET: [Bug]: MAF approval-required function call is persisted as a dangling FunctionCallContent and pending approval state has no public read API #7862

Description

@soul-soft

[Bug] MAF approval-required function call is persisted as a dangling FunctionCallContent and pending approval state has no public read API

Reconstructed draft based on the original issue context. This is not guaranteed to be a byte-for-byte copy of the previous text.

Problem

When an approval-required function tool is called, MAF/FICC can persist the public conversation history with a raw FunctionCallContent, while the corresponding pending approval state is stored separately in session metadata.

This creates a practical restore problem for durable host applications:

  • The visible message history says the assistant emitted a FunctionCallContent.
  • The session state says there is a pending approval request for that tool call.
  • The public message history does not contain a host-facing ToolApprovalRequestContent.
  • There is no public API to enumerate the pending approval requests from the restored session.

As a result, the host cannot reliably restore the approval UI from public state alone.

Reproduction

  1. Configure an approval-required function tool, for example get_weather.
  2. Ask the agent a question that causes the model to call that tool.
  3. The run stops and waits for user approval.
  4. Before the user approves or rejects the tool call, close or refresh the conversation.
  5. Later, restore the same historical conversation/session.

At this point, the host must restore the approval UI and should not allow normal user input before the approval is resolved.

Persisted history example

The stored user message is ordinary text:

{
  "role": "user",
  "contents": [
    {
      "$type": "text",
      "text": "北京的天气"
    }
  ]
}

The stored assistant message contains reasoning/text and a raw FunctionCallContent:

{
  "role": "assistant",
  "contents": [
    {
      "$type": "reasoning",
      "text": "The user asks about the weather in Beijing. I should call the get_weather tool. It requires user approval before execution, but let me try."
    },
    {
      "$type": "text",
      "text": ""
    },
    {
      "$type": "functionCall",
      "name": "get_weather",
      "arguments": {
        "location": "北京",
        "unit": "celsius"
      },
      "informationalOnly": false,
      "callId": "call_00_vigxDpknXBrXBTnynu6P8614"
    }
  ]
}

The problem is that durable message history does not contain a ToolApprovalRequestContent that the host can render directly.

Why this is a bug

For a host application with durable sessions, pending approval is not an internal implementation detail. It is user-visible conversation state.

The user is waiting on a decision: approve or reject the tool call. If the host cannot restore that state, the conversation is left in an invalid state:

  • If the host allows a new normal user message, the previous function call remains unresolved.
  • If the host wants to restore the approval UI, it has no public way to enumerate the pending request.
  • If the host reads private serialized state, the implementation becomes fragile.

This means the framework exposes a durable conversation state that cannot be interpreted reliably through public contracts.

Current workaround

The workaround is to inspect private session state and reconstruct the approval UI manually.

In practice, the relevant pending approval state may appear in internal state such as:

  • stateBag._pendingApprovalRequests
  • stateBag._sessionThreadState.queuedApprovalRequests

This works as a defensive workaround, but it is not a stable contract. Field names and serialized shapes are private implementation details.

Expected behavior

MAF should provide one of the following:

  • Persist approval-required tool calls as stable ToolApprovalRequestContent in chat history.
  • Expose a public API to enumerate pending approvals from a restored session.
  • Document a stable contract that maps persisted FunctionCallContent to the pending approval request id.

With that, the host can restore the approval UI, block normal user input while pending approvals exist, and submit an approval/rejection response safely.

Desired restored flow

After restoring a historical session:

  1. The host asks MAF whether the session has pending approvals.
  2. If pending approvals exist, the host renders approval UI instead of accepting normal user input.
  3. The user approves or rejects.
  4. The host sends the corresponding ToolApprovalResponseContent.
  5. MAF/FICC resumes the original tool lifecycle, executes or rejects the function, and produces the corresponding result.

Scope

This issue is specifically about durable restore of pending approval state.

It is not about changing approval policy, and it is not asking for automatic approval.

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETUsage: [Issues, PRs], Target: .NettriageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions