Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b043790
Python: Refresh dependencies and restore Python 3.15 resolution (#8505)
eavanvalkenburg Sep 18, 2026
2d9a822
.NET: Bind always approval responses to surfaced requests (#8432)
westey-m Sep 18, 2026
4f41eab
Python: Require Azure Monitor 1.8.10 for Foundry trace propagation (#…
jpalvarezl Sep 18, 2026
8b29709
Python: Synchronize workflow preemption test startup (#8525)
jpalvarezl Sep 18, 2026
57cdbf6
Python: [BREAKING] preserve expression results in SendActivity output…
jpalvarezl Sep 18, 2026
7147b25
Python: avoid repeated workflow reference traversal (#8510)
jpalvarezl Sep 18, 2026
f99fcc7
Python: evaluate autoSend expressions before emitting output (#8508)
jpalvarezl Sep 18, 2026
f2ba504
.NET: Preserve harness middleware for dynamic tools (#8402)
rogerbarreto Sep 18, 2026
e21140c
[BREAKING] Python: Add request-scoped Foundry agent factories (#8372)
rogerbarreto Sep 18, 2026
dd15a01
2026-09-18 Release version bump (#8529)
westey-m Sep 18, 2026
0c9944c
.NET: Pass ChatClientAgent tools per run only rather than setting on …
westey-m Sep 18, 2026
42c22c0
Bump GitHub.Copilot.SDK to 1.0.1 and forward session config propertie…
saikir1994 Sep 18, 2026
0799f6a
Python: Improve regex validation for shell and file access (#8507)
westey-m Sep 18, 2026
7232569
Python: Reject empty available modes in mode helpers (#8536)
ZedingZhang Sep 18, 2026
53e9813
Python: Include InvokeAzureAgent input.arguments in agent text (#7902…
Shivani767 Sep 21, 2026
2565daf
Python: Require expected keys in tool argument evaluation (#8560)
YaoxinHuang Sep 21, 2026
669c8b9
Python: fix(orchestrations): preserve user multimodal content during …
ZedingZhang Sep 21, 2026
79cf890
Python: Copy only the requested in-memory search page (#8544)
emme1t Sep 21, 2026
ec69020
Python: Surface switch-case condition errors instead of routing to th…
RachelWanggg Sep 21, 2026
2d85a90
Python: preserve AgentResponse metadata in WorkflowAgent streaming (#…
ryo-whaletech Sep 21, 2026
823e021
Python: preserve native keys in filtered vector delete tools (#8535)
Shy7777 Sep 21, 2026
08ba0a9
Python: [BREAKING] bound PowerFx state construction (#8511)
jpalvarezl Sep 21, 2026
04efdbf
Python: Fail closed on unresolved agent-tool approvals (#8527)
eavanvalkenburg Sep 21, 2026
09daae8
Python: match User-Agent header case-insensitively (#8521)
ManoharPaturi Sep 21, 2026
effd4d9
Python: preserve dict subclasses in workflow checkpoints (#8519)
ManoharPaturi Sep 21, 2026
61d796c
Python: accept integer timeouts in A2AAgent (#8516)
ManoharPaturi Sep 21, 2026
f80498b
Python: mark hosted /responses incomplete on content_filter and lengt…
anishmehta24 Sep 21, 2026
435012e
Python: add public TypedDict for AgentExecutor checkpoint state (#820…
FOWEPJF255 Sep 21, 2026
87d4d0a
Python: feat(core): add session-scoped file access isolation (#8542)
JHf0912 Sep 21, 2026
c0afb6c
Python: Preserve empty hosted MCP allowlists
eavanvalkenburg Sep 21, 2026
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
10 changes: 10 additions & 0 deletions docs/specs/004-python-function-calling-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,15 @@ that manually replay messages own the equivalent rule: do not resend an approval
approval `Message`, approval `Content`, or an earlier returned response.
- Approval-time `UserInputRequiredException` and `MiddlewareTermination` return immediately without another model
call.
- `Agent.as_tool()` keeps child function approvals inside the delegated invocation. A child
`ToolApprovalMiddleware` may resolve them through runtime `auto_approval_rules`; any unresolved child function
approval does not enter the caller's approval state or model transcript. An approval-only response fails the
agent-tool invocation; a mixed response preserves its non-approval user-input requests while discarding the child
approval continuation. Interactive, delayed, or durable approval belongs in a workflow. When
`propagate_session=True`, child application-state changes merge back into the parent while framework approval and
invocation-budget state remain isolated, including approval queues stored under custom child middleware
`source_id` values. Parent and child `ToolApprovalMiddleware` instances must use distinct `source_id` values; an
overlap fails before the child runs.

### Approval control content

Expand Down Expand Up @@ -578,6 +587,7 @@ that manually replay messages own the equivalent rule: do not resend an approval
| Mixed approved/rejected batch | Every call gets one correctly correlated terminal result. | `packages/core/tests/core/test_function_invocation_logic.py::test_rejected_approval` |
| Persisted approval replay | Resume executes with the prior call available. | `test_persisted_approval_messages_replay_correctly` |
| Hosted approval pass-through | Hosted requests/responses are bound to the recorded provider request and are not processed as local calls. | `test_hosted_tool_approval_response`, `test_hosted_mcp_approval_response_passthrough`, `test_session_approval_binding_reconstructs_hosted_response`, `test_mixed_local_and_hosted_approval_flow` |
| Agent-tool child approval | A child `ToolApprovalMiddleware` can auto-approve runtime tool requests inside one delegated invocation; unresolved child approvals execute nothing and do not enter the caller's approval state or dispatch a same-named parent tool, including with propagated application state. Mixed batches preserve non-approval user-input requests. Shared parent and child state rejects overlapping `ToolApprovalMiddleware.source_id` values before running the child; parent approval state is neither visible nor mutable inside the child, and custom child approval queues do not leak into later delegations. | `packages/core/tests/core/test_agents.py::test_chat_agent_as_tool_auto_approves_child_tool_with_middleware`, `test_chat_agent_as_tool_fails_closed_for_unresolved_child_approval`, `test_chat_agent_as_tool_child_approval_does_not_dispatch_same_named_parent_tool`, `test_chat_agent_as_tool_preserves_non_approval_requests_from_mixed_child_batch`, `test_chat_agent_as_tool_shared_session_requires_distinct_tool_approval_source_ids`, `test_chat_agent_as_tool_isolates_custom_parent_approval_state`, `test_chat_agent_as_tool_approved_delegation_does_not_confuse_framework_approval_state`, `test_chat_agent_as_tool_does_not_restore_custom_approval_queue_on_fresh_delegation` |
| Approval-time user input | Every user-input request from one approved execution returns in order with assistant role and no extra model call; the execution consumes one call-budget unit. | `packages/core/tests/core/test_harness_tool_approval.py::test_approval_resume_returns_all_user_input_requests_without_another_model_call`, `packages/core/tests/core/test_function_invocation_logic.py::test_approval_resume_user_input_counts_toward_function_call_budget` |
| Mixed terminal result and follow-up input | Completed siblings remain tool-role while only follow-up input requests use assistant-role messages/updates. | `packages/core/tests/core/test_function_invocation_logic.py::test_approval_resume_separates_terminal_results_from_follow_up_requests`, `packages/openai/tests/openai/test_openai_chat_completion_client.py::test_mixed_approval_resume_roles_serialize_function_result_as_tool`, `packages/core/tests/core/test_harness_tool_approval.py::test_dynamic_policy_approval_partitions_safe_sibling_result_roles` |
| Approval-time middleware termination | Terminal result returns with no extra model call in either response mode. | `packages/core/tests/core/test_function_invocation_logic.py::test_approval_resume_honors_middleware_termination` |
Expand Down
2 changes: 1 addition & 1 deletion dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
<PackageVersion Include="CommunityToolkit.VectorData.InMemory" Version="1.0.1" />
<PackageVersion Include="CommunityToolkit.VectorData.Qdrant" Version="1.0.0" />
<!-- Agent SDKs -->
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.11" />
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.14" />
<PackageVersion Include="ResponsibleAI.AgentHooks" Version="0.1.0-alpha.5" />
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.9.35-beta" />
<!-- M365 Agents SDK -->
Expand Down
6 changes: 3 additions & 3 deletions dotnet/nuget/nuget-package.props
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<Project>
<PropertyGroup>
<!-- Central version prefix - applies to all nuget packages. -->
<VersionPrefix>1.21.0</VersionPrefix>
<VersionPrefix>1.22.0</VersionPrefix>
<RCNumber>1</RCNumber>
<DateSuffix>260911</DateSuffix>
<DateSuffix>260918</DateSuffix>
<PackageVersion Condition="'$(IsReleaseCandidate)' == 'true'">$(VersionPrefix)-rc$(RCNumber)</PackageVersion>
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).$(DateSuffix).1</PackageVersion>
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.$(DateSuffix).1</PackageVersion>
<PackageVersion Condition="'$(IsReleased)' == 'true'">$(VersionPrefix)</PackageVersion>
<GitTag>1.21.0</GitTag>
<GitTag>$(VersionPrefix)</GitTag>

<Configurations>Debug;Release;Publish</Configurations>
<IsPackable>true</IsPackable>
Expand Down
103 changes: 80 additions & 23 deletions dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingA
{
copilotSession = await this._copilotClient.ResumeSessionAsync(
typedSession.SessionId,
this.CreateResumeConfig(),
ToResumeSessionConfig(this._sessionConfig),
cancellationToken).ConfigureAwait(false);
}
else
Expand Down Expand Up @@ -306,11 +306,6 @@ private async Task EnsureClientStartedAsync(CancellationToken cancellationToken)
await this._copilotClient.StartAsync(cancellationToken).ConfigureAwait(false);
}

private ResumeSessionConfig CreateResumeConfig()
{
return CopyResumeSessionConfig(this._sessionConfig);
}

/// <summary>
/// Copies all supported properties from a source <see cref="SessionConfig"/> into a new instance,
/// preserving <see cref="SessionConfigBase.Streaming"/> from the source (defaulting to <c>true</c> if unset).
Expand All @@ -323,34 +318,96 @@ internal static SessionConfig CopySessionConfig(SessionConfig source)
}

/// <summary>
/// Copies all supported properties from a source <see cref="SessionConfig"/> into a new
/// Converts a source <see cref="SessionConfig"/> into a new
/// <see cref="ResumeSessionConfig"/>, preserving <see cref="SessionConfigBase.Streaming"/>
/// from the source (defaulting to <c>true</c> if unset).
/// </summary>
internal static ResumeSessionConfig CopyResumeSessionConfig(SessionConfig? source)
internal static ResumeSessionConfig ToResumeSessionConfig(SessionConfig? source)
{
// ResumeSessionConfig is a separate SDK type, so resumed sessions need every shared setting projected explicitly.
return new ResumeSessionConfig
{
Model = source?.Model,
ReasoningEffort = source?.ReasoningEffort,
ReasoningSummary = source?.ReasoningSummary,
ContextTier = source?.ContextTier,
Tools = source?.Tools,
SystemMessage = source?.SystemMessage,
AdditionalDirectories = source?.AdditionalDirectories,
Agent = source?.Agent,
AskUserVariant = source?.AskUserVariant,
AuthClientIdMetadataUrl = source?.AuthClientIdMetadataUrl,
AvailableTools = source?.AvailableTools,
ExcludedTools = source?.ExcludedTools,
Provider = source?.Provider,
OnPermissionRequest = source?.OnPermissionRequest,
OnUserInputRequest = source?.OnUserInputRequest,
Hooks = source?.Hooks,
WorkingDirectory = source?.WorkingDirectory,
CanvasHandler = source?.CanvasHandler,
CanvasProvider = source?.CanvasProvider,
Canvases = source?.Canvases,
Capi = source?.Capi,
ClientName = source?.ClientName,
CoauthorEnabled = source?.CoauthorEnabled,
Commands = source?.Commands,
ConfigDirectory = source?.ConfigDirectory,
McpServers = source?.McpServers,
ContextTier = source?.ContextTier,
CreateSessionFsProvider = source?.CreateSessionFsProvider,
CustomAgents = source?.CustomAgents,
SkillDirectories = source?.SkillDirectories,
CustomAgentsLocalOnly = source?.CustomAgentsLocalOnly,
DefaultAgent = source?.DefaultAgent,
DisabledMcpServers = source?.DisabledMcpServers,
DisabledSkills = source?.DisabledSkills,
EmbeddingCacheStorage = source?.EmbeddingCacheStorage,
EnableCitations = source?.EnableCitations,
EnableConfigDiscovery = source?.EnableConfigDiscovery,
EnableExperimentalMode = source?.EnableExperimentalMode,
EnableFileChangeTracking = source?.EnableFileChangeTracking,
EnableFileHooks = source?.EnableFileHooks,
EnableHostGitOperations = source?.EnableHostGitOperations,
EnableManagedSettings = source?.EnableManagedSettings,
EnableMcpApps = source?.EnableMcpApps ?? default,
EnableOnDemandInstructionDiscovery = source?.EnableOnDemandInstructionDiscovery,
EnableSessionStore = source?.EnableSessionStore,
EnableSessionTelemetry = source?.EnableSessionTelemetry,
EnableSkills = source?.EnableSkills,
ExcludedBuiltInAgents = source?.ExcludedBuiltInAgents,
ExcludedTools = source?.ExcludedTools,
ExpAssignments = source?.ExpAssignments,
ExtensionInfo = source?.ExtensionInfo,
ExtensionSdkPath = source?.ExtensionSdkPath,
FeatureFlags = source?.FeatureFlags,
GitHubMcpToolConfig = source?.GitHubMcpToolConfig,
GitHubToken = source?.GitHubToken,
GitHubTokenProvider = source?.GitHubTokenProvider,
Hooks = source?.Hooks,
IncludeSubAgentStreamingEvents = source?.IncludeSubAgentStreamingEvents ?? default,
IncludedBuiltinSkills = source?.IncludedBuiltinSkills,
InfiniteSessions = source?.InfiniteSessions,
Streaming = source?.Streaming ?? true
InstructionDirectories = source?.InstructionDirectories,
LargeOutput = source?.LargeOutput,
ManagedSettings = source?.ManagedSettings,
ManageScheduleEnabled = source?.ManageScheduleEnabled,
McpOAuthTokenStorage = source?.McpOAuthTokenStorage,
McpServers = source?.McpServers,
Memory = source?.Memory,
Model = source?.Model,
ModelCapabilities = source?.ModelCapabilities,
Models = source?.Models,
OnAutoModeSwitchRequest = source?.OnAutoModeSwitchRequest,
OnElicitationRequest = source?.OnElicitationRequest,
OnEvent = source?.OnEvent,
OnExitPlanModeRequest = source?.OnExitPlanModeRequest,
OnMcpAuthRequest = source?.OnMcpAuthRequest,
OnPermissionRequest = source?.OnPermissionRequest,
OnUserInputRequest = source?.OnUserInputRequest,
OrganizationCustomInstructions = source?.OrganizationCustomInstructions,
PluginDirectories = source?.PluginDirectories,
Provider = source?.Provider,
Providers = source?.Providers,
ReasoningEffort = source?.ReasoningEffort,
ReasoningSummary = source?.ReasoningSummary,
RemoteSession = source?.RemoteSession,
RequestCanvasRenderer = source?.RequestCanvasRenderer,
RequestExtensions = source?.RequestExtensions,
SessionLimits = source?.SessionLimits,
SkillDirectories = source?.SkillDirectories,
SkipCustomInstructions = source?.SkipCustomInstructions,
SkipEmbeddingRetrieval = source?.SkipEmbeddingRetrieval,
Streaming = source?.Streaming ?? true,
SystemMessage = source?.SystemMessage,
ToolSearch = source?.ToolSearch,
Tools = source?.Tools,
WorkingDirectory = source?.WorkingDirectory,
};
}

Expand Down
39 changes: 34 additions & 5 deletions dotnet/src/Microsoft.Agents.AI.Tools.Shell/ShellPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ public bool Equals(ShellPolicyOutcome other) =>
/// </remarks>
public sealed class ShellPolicy
{
/// <summary>
/// Ceiling on any single pattern match. Policy patterns are operator-authored, but the
/// commands they filter are model-generated, so a pattern that backtracks catastrophically
/// would stall the authorization path itself. The timeout makes that failure bounded and
/// recoverable rather than a hang.
/// </summary>
private static readonly TimeSpan s_patternMatchTimeout = TimeSpan.FromSeconds(1);

private readonly IReadOnlyList<Regex> _denyList;
private readonly IReadOnlyList<Regex>? _allowList;
private readonly Func<ShellRequest, ShellPolicyOutcome?>? _custom;
Expand Down Expand Up @@ -170,11 +178,11 @@ public ShellPolicy(
Func<ShellRequest, ShellPolicyOutcome?>? custom = null)
{
this._denyList = denyList?
.Select(pattern => new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase))
.Select(pattern => new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase, s_patternMatchTimeout))
.ToArray() ?? Array.Empty<Regex>();

this._allowList = allowList?
.Select(pattern => new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase))
.Select(pattern => new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase, s_patternMatchTimeout))
.ToArray();

this._custom = custom;
Expand All @@ -200,15 +208,36 @@ public ShellPolicyOutcome Evaluate(ShellRequest request)

foreach (var deny in this._denyList)
{
if (deny.IsMatch(command))
// A deny pattern that cannot be evaluated in time fails closed: an
// unevaluated rule must never read as "this command is fine".
try
{
return ShellPolicyOutcome.Deny($"matched deny pattern: {deny}");
if (deny.IsMatch(command))
{
return ShellPolicyOutcome.Deny($"matched deny pattern: {deny}");
}
}
catch (RegexMatchTimeoutException)
{
return ShellPolicyOutcome.Deny($"deny pattern could not be evaluated in time: {deny}");
}
}

if (this._allowList is not null)
{
var matched = this._allowList.Any(allow => allow.IsMatch(command));
var matched = this._allowList.Any(allow =>
{
// A timed-out allow pattern is treated as a non-match, so it can
// never be the thing that grants permission.
try
{
return allow.IsMatch(command);
}
catch (RegexMatchTimeoutException)
{
return false;
}
});
if (!matched)
{
return ShellPolicyOutcome.Deny("command does not match allow list");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -134,16 +133,6 @@ internal static IChatClient WithDefaultAgentMiddleware(this IChatClient chatClie
// so FICC emits execute_tool spans on the agent source.
chatBuilder.Use(innerClient => new DeferredOpenTelemetryChatClient(innerClient));

var agentChatClient = chatBuilder.Build(services);

if (options?.ChatOptions?.Tools is { Count: > 0 })
{
// When tools are provided in the constructor, set the tools for the whole lifecycle of the chat client
var functionService = agentChatClient.GetService<FunctionInvokingChatClient>();
Debug.Assert(functionService is not null, "FunctionInvokingChatClient should be registered in the chat client.");
functionService!.AdditionalTools = options.ChatOptions.Tools;
}

return agentChatClient;
return chatBuilder.Build(services);
}
}
Loading
Loading