diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ToolApprovalAgentSessionExtensions.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ToolApprovalAgentSessionExtensions.cs
new file mode 100644
index 0000000000..d850ef6d62
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ToolApprovalAgentSessionExtensions.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides extension methods for reading human-in-the-loop tool approval state from an .
+///
+public static class ToolApprovalAgentSessionExtensions
+{
+ ///
+ /// Attempts to retrieve the tool approval requests that the framework has surfaced for the specified session and
+ /// that have not yet been answered with a matching .
+ ///
+ ///
+ ///
+ /// A host with durable sessions can use this after deserializing a session to discover that the conversation is
+ /// paused on a pending approval, restore its approval UI, and submit the approval later using
+ /// the request id.
+ ///
+ ///
+ /// The returned requests are snapshots of the model-originated requests. Mutating them does not change the
+ /// recorded state used to bind an incoming approval response.
+ ///
+ ///
+ /// The agent session to read pending approval requests from.
+ /// When this method returns, contains the pending approval requests if any were found; otherwise, .
+ /// if at least one pending approval request was found; otherwise.
+ public static bool TryGetPendingToolApprovalRequests(
+ this AgentSession session,
+ [NotNullWhen(true)] out IReadOnlyList? requests)
+ {
+ _ = Throw.IfNull(session);
+
+ if (session.StateBag.TryGetValue>(
+ ApprovalResponseBindingChatClient.StateBagKey,
+ out var pending,
+ AgentJsonUtilities.DefaultOptions)
+ && pending is { Count: > 0 })
+ {
+ requests = pending;
+ return true;
+ }
+
+ requests = null;
+ return false;
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ApprovalResponseBindingChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ApprovalResponseBindingChatClientTests.cs
index e89e2eae97..d279db3fce 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ApprovalResponseBindingChatClientTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ApprovalResponseBindingChatClientTests.cs
@@ -270,6 +270,55 @@ public async Task GetResponseAsync_NoSession_PassesThroughUnvalidatedAsync()
Assert.Contains(capture.Messages!.SelectMany(m => m.Contents), c => c is ToolApprovalResponseContent);
}
+ [Fact]
+ public async Task TryGetPendingToolApprovalRequests_SurvivesSessionStateRoundTripAsync()
+ {
+ // Arrange — a run stops on an approval request, then the session is persisted and reloaded.
+ var session = new ChatClientAgentSession();
+ var call = new FunctionCallContent("call1", "get_weather", new Dictionary { ["location"] = "Beijing" });
+ await RecordRequestAsync(session, new ToolApprovalRequestContent(RequestId, call));
+
+ var restored = new ChatClientAgentSession(
+ stateBag: AgentSessionStateBag.Deserialize(session.StateBag.Serialize()));
+
+ // Act
+ var found = restored.TryGetPendingToolApprovalRequests(out var pending);
+
+ // Assert — the host can discover the pending approval without reading private state bag keys.
+ Assert.True(found);
+ var request = Assert.Single(pending!);
+ Assert.Equal(RequestId, request.RequestId);
+ var pendingCall = Assert.IsType(request.ToolCall);
+ Assert.Equal("get_weather", pendingCall.Name);
+ Assert.Equal("call1", pendingCall.CallId);
+ }
+
+ [Fact]
+ public async Task TryGetPendingToolApprovalRequests_AfterResponseIsConsumed_ReturnsFalseAsync()
+ {
+ // Arrange — record a request, then answer it.
+ var session = new ChatClientAgentSession();
+ await RecordRequestAsync(session, new ToolApprovalRequestContent(RequestId, new FunctionCallContent("call1", "toolA")));
+ Assert.True(session.TryGetPendingToolApprovalRequests(out _));
+
+ var decorator = new ApprovalResponseBindingChatClient(CreateCapturingChatClient(new Capture()));
+ var approval = new ToolApprovalResponseContent(RequestId, approved: true, new FunctionCallContent("call1", "toolA"));
+
+ // Act
+ await RunAsync(decorator, session, [new ChatMessage(ChatRole.User, [approval])]);
+
+ // Assert — the answered request is no longer pending.
+ Assert.False(session.TryGetPendingToolApprovalRequests(out var pending));
+ Assert.Null(pending);
+ }
+
+ [Fact]
+ public void TryGetPendingToolApprovalRequests_NoApprovalState_ReturnsFalse()
+ {
+ Assert.False(new ChatClientAgentSession().TryGetPendingToolApprovalRequests(out var pending));
+ Assert.Null(pending);
+ }
+
private static async Task RecordRequestAsync(ChatClientAgentSession session, ToolApprovalRequestContent request)
{
var inner = CreateMockChatClient((_, _, _) =>