Skip to content
Closed
Changes from 5 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
c73282b
ADR: Supporting user approvals
westey-m Jul 22, 2025
9e3b98b
Add more context.
westey-m Jul 22, 2025
a9a6a97
Add further justification
westey-m Jul 22, 2025
61063c0
Add decision drivers.
westey-m Jul 22, 2025
3a0dabf
Update with Copilot Suggestion
westey-m Jul 22, 2025
786374c
Merge branch 'main' into userapproval-adr
westey-m Jul 23, 2025
eace4e7
Update adr with feedback.
westey-m Jul 23, 2025
4076ed3
Adding another generalized option for all user input.
westey-m Jul 23, 2025
23b4fe5
Merge branch 'main' into userapproval-adr
westey-m Jul 24, 2025
12817f1
Add another base class based UserInput option
westey-m Jul 25, 2025
84e2855
Remove TextContent inheritance.
westey-m Jul 25, 2025
632635c
Change Schema and Strucutured input data types to JsonElement
westey-m Jul 25, 2025
461c8ef
Rename JsonSchema to Schema
westey-m Jul 25, 2025
7de1787
Merge branch 'main' into userapproval-adr
westey-m Jul 29, 2025
aa3c14e
Merge branch 'main' into userapproval-adr
crickman Jul 30, 2025
ab04590
Address some feedback.
westey-m Jul 30, 2025
aba7dc6
Merge branch 'main' into userapproval-adr
westey-m Jul 30, 2025
89ff266
Merge branch 'main' into userapproval-adr
crickman Jul 30, 2025
629204e
Add base user input request and response types with FunctionApproval …
westey-m Jul 31, 2025
3783074
Merge branch 'main' into userapproval-adr
crickman Jul 31, 2025
d3a9319
Merge branch 'main' into userapproval-adr
westey-m Aug 1, 2025
74d1f5d
Add POC ApprovalGeneratingChatClient
westey-m Aug 1, 2025
3c40615
Merge branch 'main' into userapproval-adr
crickman Aug 5, 2025
c068247
Add pre-FunctionInvokingChatClient ApprovalGeneratingChatClient POC
westey-m Aug 5, 2025
d8626a4
Merge branch 'userapproval-adr' of https://github.com/westey-m/agent-…
westey-m Aug 5, 2025
45254f3
Merge branch 'main' into userapproval-adr
westey-m Aug 5, 2025
3300dce
Merge branch 'main' into userapproval-adr
westey-m Aug 6, 2025
c8e42de
Fix namespaces
westey-m Aug 6, 2025
97352a0
Add combined FICC with approvals
westey-m Aug 7, 2025
2c39648
Bug fix.
westey-m Aug 7, 2025
5293568
Add MCP and ApprovalRequiredAIFunction POC
westey-m Aug 8, 2025
c5bedd0
Bug fixes
westey-m Aug 11, 2025
edcad34
Address PR Feedback.
westey-m Aug 11, 2025
0a61294
Add AIFunctionApprovalContext with some small efficiency improvements.
westey-m Aug 11, 2025
436c490
Remove unecessary helpers that are only used in one place.
westey-m Aug 11, 2025
0d5fcbf
Replace dictionary with array and lazy initialize.
westey-m Aug 11, 2025
8beb0f6
Improve sample code
westey-m Aug 11, 2025
9fbd0a7
Make user input id required and rename it to be more general.
westey-m Aug 12, 2025
302d43d
Merge branch 'main' into userapproval-adr
westey-m Aug 12, 2025
8ad466b
Upgrade packages to resolve conflicts.
westey-m Aug 12, 2025
049c9d3
Move some code into methods to simply main GetResponse.
westey-m Aug 12, 2025
39ef6bd
Adding approvals support to streaming
westey-m Aug 13, 2025
14d4773
Address PR comments.
westey-m Aug 13, 2025
b288fed
Change approval response type and naming
westey-m Aug 13, 2025
fc0bf74
Remove commented out validation checks that won't work for server sid…
westey-m Aug 13, 2025
d1939ad
Remove original metadata and read metadata from approval request mess…
westey-m Aug 14, 2025
6d8b20e
Small code refactoring and clarifying comments.
westey-m Aug 14, 2025
2bc90af
Remove failed POCs
westey-m Aug 14, 2025
d12cdf8
Update ADR with latest changes
westey-m Aug 14, 2025
3420337
Explain case when pre FICC AGCC fails
westey-m Aug 14, 2025
6a6f9dd
Add support for mcp servers over http.
westey-m Aug 14, 2025
f05bf90
Merge branch 'main' into userapproval-adr
westey-m Aug 14, 2025
3105941
Addressing PR comments.
westey-m Aug 15, 2025
26eb742
Address PR comments
westey-m Aug 15, 2025
0adf519
Merge branch 'main' into userapproval-adr
westey-m Aug 15, 2025
b2a4915
Merge latest updates from main
westey-m Aug 15, 2025
5e52c11
Add FICC unit tests and fix bugs.
westey-m Aug 19, 2025
b1289e3
Add additional unit test.
westey-m Aug 19, 2025
0b4405e
Refactor ConvertToFunctionCallContentMessages to avoid unecessary all…
westey-m Aug 20, 2025
545c231
Refactor preinvocation logic to share between streaming and non-strea…
westey-m Aug 20, 2025
e6aeebb
Move some approval generation code to a separate method to simplify m…
westey-m Aug 20, 2025
a1b2973
More refactoring, additional unit tests and bug fixes.
westey-m Aug 20, 2025
906ad2f
Remove comments delineating additions.
westey-m Aug 20, 2025
4c59b25
Merge latest changes from main
westey-m Aug 20, 2025
66b904e
Fixing some typos.
westey-m Aug 20, 2025
d3dd02e
Merge branch 'main' into userapproval-adr
westey-m Aug 21, 2025
21680be
Address PR comments.
westey-m Aug 22, 2025
a712408
Address PR comments.
westey-m Aug 22, 2025
c1da46b
Address PR feedback.
westey-m Aug 22, 2025
091f754
Fix bug for server side threads.
westey-m Aug 22, 2025
7518b70
Address PR comments
westey-m Aug 25, 2025
a349c79
Merge from main
westey-m Aug 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions docs/decisions/00NN-userapproval-content-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
---
# These are optional elements. Feel free to remove any of them.
status: proposed
contact: westey-m
date: 2025-07-16 {YYYY-MM-DD when the decision was last updated}
deciders: sergeymenshykh, markwallace, rbarreto, dmytrostruk, westey-m, eavanvalkenburg, stephentoub
consulted:
informed:
---

# Agent User Approvals Content Types Design

## Context and Problem Statement

When agents are operating on behalf of a user, there may be cases where the agent requires user approval to continue an operation.
This is complicated by the fact that an agent may be remote and the user may not immediately be available to provide the approval.

Inference services are also increasingly supporting built-in tools or service side MCP invocation, which may require user approval before the tool can be invoked.

This document aims to provide options and capture the decision on how to model this user approval interaction with the agent caller.

## Decision Drivers

- Agents should encapsulate their internal logic and not leak it to the caller.
- We need to support approvals for local actions as well as remote actions.
- We need to support approvals for existing protocols and services, such as OpenAI's MCP and function calling.

## Considered Options

### Return a FunctionCallContent to the agent caller, that it executes

This introduces a manual function calling element to agents, where the caller of the agent is expected to invoke the function if the user approves it.

This approach is problematic for a number of reasons:

- This may not work for remote agents (e.g. via A2A), where the function that the agent wants to call does not reside on the caller's machine.
- The main value prop of an agent is to encapsulate the internal logic of the agent, but this leaks that logic to the caller, requiring the caller to know how to invoke the agent's function calls.
- Inference services are introducing their own approval content types for server side tool or function invocation, and will not be addressed by this approach.

### Introduce new ApprovalRequestContent and ApprovalResponseContent types

The agent would return an `ApprovalRequestContent` to the caller, which would then be responsible for getting approval from the user in whatever way is appropriate for the application.
The caller would then invoke the agent again with an `ApprovalResponseContent` to the agent containing the user decision.

When an agent returns an `ApprovalRequestContent`, the run is finished for the time being, and to continue, the agent must be invoked again with an `ApprovalResponseContent` on the same thread as the original request.

The `ApprovalRequestContent` could contain an optional `FunctionCallContent` if the approval is for a function call, along with any additional information that the agent wants to provide to the user to help them make a decision.

It is up to the agent to decide when and if a user approval is required, and therefore when to return an `ApprovalRequestContent`.

`ApprovalRequestContent` and `ApprovalResponseContent` will not necessarily always map to a supported content type for the underlying service or agent thread storage.
Specifically, when we are deciding in the IChatClient stack to ask for approval from the user, for a function call, this does not mean that the underlying ai service or
service side thread type (where applicable) supports the concept of a function call approval request. We therefore need the ability to temporarily store the approval request in the
AgentThread, without it becoming part of the thread history. This will serve as a temporary record of the fact that there is an outstanding approval request that the agent is waiting for to continue.
There will be no long term record of an approval request in the chat history, but if the server side thread doesn't support this, there is nothing we can do to change that.

Suggested Types:

```csharp
class ApprovalRequestContent : TextContent // TextContent.Text may contain text to explain to the user what they are approving. This is important if the approval is not for a function call.
{
// An ID to uniquely identify the approval request/response pair.
public string ApprovalId { get; set; }

// Optional: If the approval is for a function call, this will contain the function call content.
public FunctionCallContent? FunctionCall { get; set; }

public ChatMessage Approve()
{
return new ChatMessage(ChatRole.User,
[
new ApprovalResponseContent
{
ApprovalId = this.ApprovalId,
Approved = true,
FunctionCall = this.FunctionCall
}
]);
}

public ChatMessage Reject()
{
return new ChatMessage(ChatRole.User,
[
new ApprovalResponseContent
{
ApprovalId = this.ApprovalId,
Approved = false,
FunctionCall = this.FunctionCall
}
]);
}
}

class ApprovalResponseContent : AIContent
{
// An ID to uniquely identify the approval request/response pair.
public string ApprovalId { get; set; }

// Indicates whether the user approved the request.
public bool Approved { get; set; }

// Optional: If the approval is for a function call, this will contain the function call content.
public FunctionCallContent? FunctionCall { get; set; }
}

var response = await agent.RunAsync("Please book me a flight for Friday to Paris.", thread);
while (response is not null && response.ApprovalRequests.Count > 0)
{
List<ChatMessage> messages = new List<ChatMessage>();
foreach (var approvalRequest in response.ApprovalRequests)
{
// Show the approval request to the user in the appropriate format.
// The user can then approve or reject the request.
// The optional FunctionCallContent can be used to show the user what function the agent wants to call with the parameter set:
// approvalRequest.FunctionCall?.Arguments.
// The Text property of the ApprovalRequestContent can also be used to show the user any additional textual context about the request.

// If the user approves:
var approvalMessage = approvalRequest.Approve();
messages.Add(approvalMessage);
}

// Get the next response from the agent.
response = await agent.RunAsync(messages, thread);
}

class AgentThread
{
...

// The thread state may need to store the approval requests and responses.
// TODO: Consider whether we should have a more generic ActiveUserRequests list, which could include other types of user requests in the future.
// This may mean a base class for all user requests.
public List<ApprovalRequestContent> ActiveApprovalRequests { get; set; }

...
}
```

- Also see [dotnet issue 6492](https://github.com/dotnet/extensions/issues/6492), which discusses the need for a similar pattern in the context of MCP approvals.
- Also see [the openai RunToolApprovalItem](https://openai.github.io/openai-agents-js/openai/agents/classes/runtoolapprovalitem/).
- Also see [the openai human-in-the-loop guide](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#approval-requests).
- Also see [MCP Approval Requests from OpenAI](https://platform.openai.com/docs/guides/tools-remote-mcp#approvals).

### ChatClientAgent Approval Process Flow

1. User asks agent to perform a task and request is added to the thread.
1. Agent calls model with registered functions.
1. Model responds with function calls to make.
1. ConfirmingFunctionInvokingChatClient decorator (new feature / enhancement to FunctionInvokingChatClient) identifies any function calls that require user approval and returns an ApprovalRequestContent.
ChatClient implementations should also convert any approval requests from the service into ApprovalRequestContent.
1. Agent updates the thread with the FunctionCallContent (or this may have already been done by a service threaded agent) if the approval request is for a function call.
1. Agent stores the ApprovalRequestContent in its AgentThread under ActiveApprovalRequests, so that it knows that there is an outstanding user request.
1. Agent returns the ApprovalRequestContent to the caller which shows it to the user in the appropriate format.
1. User (via caller) invokes the agent again with ApprovalResponseContent.
1. Agent removes the ApprovalRequestContent from its AgentThread ActiveApprovalRequests.
1. Agent invokes IChatClient with ApprovalResponseContent and the ConfirmingFunctionInvokingChatClient decorator identifies the response as an approval for the function call.
If it isn't an approval for a manual function call, it can be passed through to the underlying ChatClient to be converted to the appropriate Approval content type for the service.
1. ConfirmingFunctionInvokingChatClient decorator invokes the function call and invokes the underlying IChatClient with a FunctionResultContent.
1. Model responds with the result.
1. Agent responds to caller with result message and thread is updated with the result message.

At construction time the set of functions that require user approval will need to be registered with the `ConfirmingFunctionInvokingChatClient` decorator
so that it can identify which function calls should be returned as an `ApprovalRequestContent`.

### CustomAgent Approval Process Flow

1. User asks agent to perform a task and request is added to the thread.
1. Agent executes various steps.
1. Agent encounters a step for which it requires user approval to continue.
1. Agent responds with an ApprovalRequestContent.
1. Agent updates its own state with the progress that it has made up to that point and adds the ApprovalRequestContent to its AgentThread ActiveApprovalRequests.
1. User (via caller) invokes the agent again with ApprovalResponseContent.
1. Agent loads its progress from state and continues processing.
1. Agent removes its ApprovalRequestContent from its AgentThread ActiveApprovalRequests.
1. Agent responds to caller with result message and thread is updated with the result message.

## Decision Outcome

Chosen approach: Introduce new ApprovalRequestContent and ApprovalResponseContent types.