From c2427d8ea495c397c1b3f8aa97cfc600cbe9078f Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Tue, 14 Jul 2026 17:54:36 +0000
Subject: [PATCH 1/2] Graduate HarnessAgent
---
.../ChatClientHarnessExtensions.cs | 3 ---
.../Microsoft.Agents.AI.Harness/HarnessAgent.cs | 3 ---
.../HarnessAgentOptions.cs | 17 ++++++++++++++++-
.../Microsoft.Agents.AI.Harness.csproj | 1 +
4 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/ChatClientHarnessExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Harness/ChatClientHarnessExtensions.cs
index 3d5f037b2f8..38315f5e54c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/ChatClientHarnessExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/ChatClientHarnessExtensions.cs
@@ -1,17 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
-using System.Diagnostics.CodeAnalysis;
using Microsoft.Agents.AI;
using Microsoft.Extensions.Logging;
-using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Extensions.AI;
///
/// Provides extension methods for creating a from an .
///
-[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public static class ChatClientHarnessExtensions
{
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
index 87fa7d47b59..c1764063e5e 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.Agents.AI.Compaction;
@@ -11,7 +10,6 @@
#endif
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
-using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
@@ -80,7 +78,6 @@ namespace Microsoft.Agents.AI;
/// and combined with agent-specific instructions via .
///
///
-[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class HarnessAgent : DelegatingAIAgent
{
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
index be61687088a..98fe976dd87 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
@@ -14,7 +14,6 @@ namespace Microsoft.Agents.AI;
///
/// Represents configuration options for a .
///
-[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class HarnessAgentOptions
{
///
@@ -46,6 +45,7 @@ public sealed class HarnessAgentOptions
/// .
///
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public int? MaxContextWindowTokens { get; set; }
///
@@ -62,6 +62,7 @@ public sealed class HarnessAgentOptions
/// is provided and is .
///
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public int? MaxOutputTokens { get; set; }
///
@@ -81,6 +82,7 @@ public sealed class HarnessAgentOptions
/// This property is ignored when is .
///
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public CompactionStrategy? CompactionStrategy { get; set; }
///
@@ -92,6 +94,7 @@ public sealed class HarnessAgentOptions
/// is added to the chat client pipeline, and the default
/// is configured without a chat reducer.
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public bool DisableCompaction { get; set; }
///
@@ -162,6 +165,7 @@ public sealed class HarnessAgentOptions
/// as a single-shot agent.
///
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public IEnumerable? LoopEvaluators { get; set; }
///
@@ -171,6 +175,7 @@ public sealed class HarnessAgentOptions
/// When , the uses its default settings. This property is ignored
/// when is or empty.
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public LoopAgentOptions? LoopAgentOptions { get; set; }
///
@@ -234,6 +239,7 @@ public sealed class HarnessAgentOptions
/// a default is created.
/// This property is ignored when is .
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public AgentFileStore? FileMemoryStore { get; set; }
///
@@ -245,6 +251,7 @@ public sealed class HarnessAgentOptions
/// included in the agent's context providers, backed by the supplied store and configured with
/// when provided.
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public AgentFileStore? FileAccessStore { get; set; }
///
@@ -254,6 +261,7 @@ public sealed class HarnessAgentOptions
/// This property is only used when is set (file access is opt-in).
/// When , the provider uses its default options.
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public FileAccessProviderOptions? FileAccessProviderOptions { get; set; }
///
@@ -348,6 +356,7 @@ public sealed class HarnessAgentOptions
/// (case-insensitive). If these requirements are not met, will throw
/// an during construction.
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public IEnumerable? BackgroundAgents { get; set; }
///
@@ -357,6 +366,7 @@ public sealed class HarnessAgentOptions
/// Use this to customize instructions or agent list formatting for the background agents feature.
/// This property is ignored when is or empty.
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public BackgroundAgentsProviderOptions? BackgroundAgentsProviderOptions { get; set; }
#if NET
@@ -369,6 +379,7 @@ public sealed class HarnessAgentOptions
/// is registered as a callable tool.
/// When (the default), no shell features are enabled.
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public ShellExecutor? ShellExecutor { get; set; }
///
@@ -387,6 +398,7 @@ public sealed class HarnessAgentOptions
/// name that no other registered tool uses.
///
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public string? ShellToolName { get; set; }
///
@@ -396,6 +408,7 @@ public sealed class HarnessAgentOptions
/// When (the default), the shell executor's built-in description is used.
/// This property is ignored when is .
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public string? ShellToolDescription { get; set; }
///
@@ -417,6 +430,7 @@ public sealed class HarnessAgentOptions
/// impose no such requirement.
///
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public bool DisableShellToolApproval { get; set; }
///
@@ -427,6 +441,7 @@ public sealed class HarnessAgentOptions
/// or the instructions formatter.
/// This property is ignored when is .
///
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public ShellEnvironmentProviderOptions? ShellEnvironmentProviderOptions { get; set; }
#endif
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj b/dotnet/src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj
index 31e24d43245..af0f2b8df9c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj
@@ -7,6 +7,7 @@
true
true
true
+ $(NoWarn);MAAI001
From 3acf53b63c6e71b275019034fd8cd147909e3544 Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Fri, 17 Jul 2026 15:49:02 +0000
Subject: [PATCH 2/2] Switch harness project to released and remove unreleased
shell dependency
---
.../Program.cs | 10 +-
.../HarnessAgent.cs | 21 --
.../HarnessAgentOptions.cs | 79 ------
.../Microsoft.Agents.AI.Harness.csproj | 12 +-
.../Harness/FileAccess/FileAccessProvider.cs | 8 +-
.../Skills/AgentSkillsProvider.cs | 8 +-
.../HarnessAgentOptionsTests.cs | 19 --
.../HarnessAgentTests.cs | 232 ------------------
8 files changed, 21 insertions(+), 368 deletions(-)
diff --git a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/Program.cs b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/Program.cs
index 54c05c49b0b..9b971b11a30 100644
--- a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/Program.cs
+++ b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/Program.cs
@@ -160,7 +160,9 @@ research before making decisions.
// Turn the chat client into a HarnessAgent. On top of Post 2's file access and approvals we add the
// four "scaling" capabilities: skills (our own provider), background agents, a confined shell, and
// CodeAct.
-List contextProviders = [skillsProvider, codeAct];
+// The shell is wired up in two parts: the ShellEnvironmentProvider injects OS/shell/CWD info into the
+// system prompt, and the shell tool is registered below in ChatOptions.
+List contextProviders = [skillsProvider, codeAct, new ShellEnvironmentProvider(shell)];
AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
@@ -170,8 +172,6 @@ research before making decisions.
DisableAgentSkillsProvider = true,
// Fan-out research is delegated to this background agent.
BackgroundAgents = [researchAgent],
- // The confined shell, exposed as the approval-gated run_shell tool.
- ShellExecutor = shell,
// Keep reading the portfolio frictionless while writes, trades, and shell commands still prompt.
ToolApprovalAgentOptions = new ToolApprovalAgentOptions
{
@@ -179,7 +179,7 @@ research before making decisions.
},
// Start in "execute" mode for quick lookups and actions; switch any time with /mode plan.
AgentModeProviderOptions = new AgentModeProviderOptions { DefaultMode = "execute" },
- // Our skills provider plus CodeAct.
+ // Our skills provider, CodeAct, and the shell environment provider.
AIContextProviders = contextProviders,
ChatOptions = new ChatOptions
{
@@ -188,6 +188,8 @@ research before making decisions.
[
StockTools.CreateGetStockPriceTool(),
TradingTools.CreatePlaceTradeTool(),
+ // The confined shell, exposed as the approval-gated run_shell tool.
+ shell.AsAIFunction(requireApproval: true),
],
Reasoning = new() { Effort = ReasoningEffort.Medium },
},
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
index c1764063e5e..c7a64a9e3d7 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
@@ -5,9 +5,6 @@
using System.IO;
using System.Linq;
using Microsoft.Agents.AI.Compaction;
-#if NET
-using Microsoft.Agents.AI.Tools.Shell;
-#endif
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Diagnostics;
@@ -49,7 +46,6 @@ namespace Microsoft.Agents.AI;
///
/// - — shared file access providing read/write tools for a working directory. Enable by setting ; configure via .
/// - — enables delegation to background agents for parallel work. Enable by setting .
-/// - ShellEnvironmentProvider — injects OS/shell/CWD information and a shell execution tool. Enable by setting HarnessAgentOptions.ShellExecutor (.NET only).
///
///
///
@@ -276,16 +272,6 @@ private static ChatOptions BuildChatOptions(HarnessAgentOptions? options, string
result.Tools.Add(new HostedWebSearchTool());
}
-#if NET
- if (options?.ShellExecutor is ShellExecutor shellExecutor)
- {
- result.Tools ??= [];
- result.Tools.Add(options.ShellToolName is { } shellToolName
- ? shellExecutor.AsAIFunction(shellToolName, options.ShellToolDescription, !options.DisableShellToolApproval)
- : shellExecutor.AsAIFunction(description: options.ShellToolDescription, requireApproval: !options.DisableShellToolApproval));
- }
-#endif
-
return result;
}
@@ -340,13 +326,6 @@ private static List BuildContextProviders(HarnessAgentOptions
}
}
-#if NET
- if (options?.ShellExecutor is ShellExecutor shellExecutor)
- {
- providers.Add(new ShellEnvironmentProvider(shellExecutor, options.ShellEnvironmentProviderOptions));
- }
-#endif
-
if (options?.AIContextProviders is IEnumerable userProviders)
{
providers.AddRange(userProviders);
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
index 98fe976dd87..b0c94130b9d 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
@@ -3,9 +3,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Agents.AI.Compaction;
-#if NET
-using Microsoft.Agents.AI.Tools.Shell;
-#endif
using Microsoft.Extensions.AI;
using Microsoft.Shared.DiagnosticIds;
@@ -368,80 +365,4 @@ public sealed class HarnessAgentOptions
///
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public BackgroundAgentsProviderOptions? BackgroundAgentsProviderOptions { get; set; }
-
-#if NET
- ///
- /// Gets or sets the shell executor used to enable shell tool and environment probing via .
- ///
- ///
- /// When non-null, a is automatically included in the agent's context
- /// providers (injecting OS/shell/CWD information into the system prompt), and the executor's
- /// is registered as a callable tool.
- /// When (the default), no shell features are enabled.
- ///
- [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
- public ShellExecutor? ShellExecutor { get; set; }
-
- ///
- /// Gets or sets the name of the shell execution tool exposed to the model.
- ///
- ///
- ///
- /// When (the default), the shell executor's default tool name (run_shell) is used.
- /// This property is ignored when is .
- ///
- ///
- /// Security warning: auto-approval rules may match tool calls solely by name. Pay attention to
- /// the tool names approved by auto-approval rules for other features. Setting this property to a
- /// value that collides with a tool name that is approved by an auto-approval rule for another feature will cause
- /// the shell tool to also be auto-approved, bypassing the human approval boundary. Choose a unique
- /// name that no other registered tool uses.
- ///
- ///
- [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
- public string? ShellToolName { get; set; }
-
- ///
- /// Gets or sets the description of the shell execution tool shown to the model.
- ///
- ///
- /// When (the default), the shell executor's built-in description is used.
- /// This property is ignored when is .
- ///
- [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
- public string? ShellToolDescription { get; set; }
-
- ///
- /// Gets or sets a value indicating whether approval is disabled for the shell execution tool.
- ///
- ///
- ///
- /// When (the default), the shell tool is wrapped in an
- /// so every command requires explicit approval before executing. When , the tool can be invoked
- /// without approval. This property is ignored when is .
- ///
- ///
- /// Setting this to also requires the underlying to permit
- /// unapproved use. The inverse of this value is forwarded as the requireApproval argument to
- /// , and some executors enforce their own security boundary:
- /// throws an unless it was
- /// constructed with set to ,
- /// because running unapproved commands directly on the host is inherently unsafe. Sandboxed executors such as
- /// impose no such requirement.
- ///
- ///
- [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
- public bool DisableShellToolApproval { get; set; }
-
- ///
- /// Gets or sets optional configuration for the .
- ///
- ///
- /// Use this to customize which tools are probed, the probe timeout, shell family override,
- /// or the instructions formatter.
- /// This property is ignored when is .
- ///
- [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
- public ShellEnvironmentProviderOptions? ShellEnvironmentProviderOptions { get; set; }
-#endif
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj b/dotnet/src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj
index af0f2b8df9c..e429447e894 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/Microsoft.Agents.AI.Harness.csproj
@@ -1,7 +1,7 @@
- false
+ true
true
true
true
@@ -12,14 +12,16 @@
+
+
+
+ false
+
+
-
-
-
-
Microsoft Agent Framework Harness
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.cs b/dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.cs
index 95594333aee..d7262d4bc40 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.cs
@@ -184,8 +184,8 @@ public FileAccessProvider(AgentFileStore fileStore, FileAccessProviderOptions? o
///
///
/// Security note: because matching is by tool name only, any other registered tool that
- /// shares one of these names — for example a configurable-name tool such as the Harness shell
- /// tool (HarnessAgentOptions.ShellToolName) that was assigned the same name — will also be auto-approved, bypassing the
+ /// shares one of these names — for example a configurable-name tool that was assigned the same
+ /// name — will also be auto-approved, bypassing the
/// human approval boundary. Ensure no other tool collides with these reserved names.
///
///
@@ -221,8 +221,8 @@ public FileAccessProvider(AgentFileStore fileStore, FileAccessProviderOptions? o
///
///
/// Security note: because matching is by tool name only, any other registered tool that
- /// shares one of these names — for example a configurable-name tool such as the Harness shell
- /// tool (HarnessAgentOptions.ShellToolName) that was assigned the same name — will also be auto-approved, bypassing the
+ /// shares one of these names — for example a configurable-name tool that was assigned the same
+ /// name — will also be auto-approved, bypassing the
/// human approval boundary. Ensure no other tool collides with these reserved names.
///
///
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
index 09922b52f3a..1d33227cc8a 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
@@ -100,8 +100,8 @@ public sealed partial class AgentSkillsProvider : AIContextProvider, IDisposable
///
///
/// Security note: because matching is by tool name only, any other registered tool that
- /// shares one of these names — for example a configurable-name tool such as the Harness shell
- /// tool (HarnessAgentOptions.ShellToolName) that was assigned the same name — will also be auto-approved, bypassing the
+ /// shares one of these names — for example a configurable-name tool that was assigned the same
+ /// name — will also be auto-approved, bypassing the
/// human approval boundary. Ensure no other tool collides with these reserved names.
///
///
@@ -133,8 +133,8 @@ public sealed partial class AgentSkillsProvider : AIContextProvider, IDisposable
///
///
/// Security note: because matching is by tool name only, any other registered tool that
- /// shares one of these names — for example a configurable-name tool such as the Harness shell
- /// tool (HarnessAgentOptions.ShellToolName) that was assigned the same name — will also be auto-approved, bypassing the
+ /// shares one of these names — for example a configurable-name tool that was assigned the same
+ /// name — will also be auto-approved, bypassing the
/// human approval boundary. Ensure no other tool collides with these reserved names.
///
///
diff --git a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentOptionsTests.cs
index dc54a92f0c3..fdb047a88c5 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentOptionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentOptionsTests.cs
@@ -2,9 +2,6 @@
using System.Threading.Tasks;
using Moq;
-#if NET
-using Microsoft.Agents.AI.Tools.Shell;
-#endif
namespace Microsoft.Agents.AI.UnitTests;
@@ -46,10 +43,6 @@ public void DefaultPropertyValues()
Assert.Null(options.AgentSkillsSource);
Assert.Null(options.BackgroundAgents);
Assert.Null(options.BackgroundAgentsProviderOptions);
-#if NET
- Assert.Null(options.ShellExecutor);
- Assert.Null(options.ShellEnvironmentProviderOptions);
-#endif
}
///
@@ -70,10 +63,6 @@ public void PropertiesCanBeSetAndRetrieved()
var backgroundAgentsOptions = new BackgroundAgentsProviderOptions();
var loopEvaluators = new LoopEvaluator[] { new DelegateLoopEvaluator((_, _) => new ValueTask(LoopEvaluation.Stop())) };
var loopAgentOptions = new LoopAgentOptions();
-#if NET
- var shellExecutor = new Mock().Object;
- var shellEnvOptions = new ShellEnvironmentProviderOptions();
-#endif
// Act
var options = new HarnessAgentOptions
@@ -104,10 +93,6 @@ public void PropertiesCanBeSetAndRetrieved()
BackgroundAgentsProviderOptions = backgroundAgentsOptions,
LoopEvaluators = loopEvaluators,
LoopAgentOptions = loopAgentOptions,
-#if NET
- ShellExecutor = shellExecutor,
- ShellEnvironmentProviderOptions = shellEnvOptions,
-#endif
};
// Assert
@@ -139,9 +124,5 @@ public void PropertiesCanBeSetAndRetrieved()
Assert.Same(backgroundAgentsOptions, options.BackgroundAgentsProviderOptions);
Assert.Same(loopEvaluators, options.LoopEvaluators);
Assert.Same(loopAgentOptions, options.LoopAgentOptions);
-#if NET
- Assert.Same(shellExecutor, options.ShellExecutor);
- Assert.Same(shellEnvOptions, options.ShellEnvironmentProviderOptions);
-#endif
}
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
index 2b9613d0610..2952510a2ce 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
@@ -7,9 +7,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-#if NET
-using Microsoft.Agents.AI.Tools.Shell;
-#endif
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Moq;
@@ -1618,235 +1615,6 @@ public async Task BackgroundAgentsProvider_IncludesMultipleAgentsAsync()
#endregion
-#if NET
- #region Feature: ShellEnvironmentProvider
-
- ///
- /// Verify that ShellEnvironmentProvider is included when ShellExecutor is provided.
- ///
- [Fact]
- public void ShellEnvironmentProvider_IncludedWhenExecutorProvided()
- {
- // Arrange
- var chatClient = new Mock().Object;
- var executorMock = new Mock();
- executorMock.Setup(e => e.AsAIFunction(It.IsAny(), It.IsAny(), It.IsAny()))
- .Returns(AIFunctionFactory.Create(() => "test", "run_shell"));
- var options = CreateAllDisabledOptions();
- options.ShellExecutor = executorMock.Object;
-
- // Act
- var agent = new HarnessAgent(chatClient, options);
- var innerAgent = agent.GetService();
-
- // Assert
- Assert.NotNull(innerAgent?.AIContextProviders);
- Assert.Contains(innerAgent!.AIContextProviders!, p => p is ShellEnvironmentProvider);
- }
-
- ///
- /// Verify that ShellEnvironmentProvider is not included when ShellExecutor is null.
- ///
- [Fact]
- public void ShellEnvironmentProvider_ExcludedWhenExecutorNull()
- {
- // Arrange
- var chatClient = new Mock().Object;
- var options = CreateAllDisabledOptions();
- options.ShellExecutor = null;
-
- // Act
- var agent = new HarnessAgent(chatClient, options);
- var innerAgent = agent.GetService();
-
- // Assert
- Assert.NotNull(innerAgent);
- Assert.NotNull(innerAgent!.AIContextProviders);
- Assert.DoesNotContain(innerAgent.AIContextProviders!, p => p is ShellEnvironmentProvider);
- }
-
- ///
- /// Verify that the shell tool AIFunction is added to ChatOptions.Tools when ShellExecutor is provided.
- ///
- [Fact]
- public async Task ShellExecutor_ToolAddedToChatOptionsAsync()
- {
- // Arrange
- ChatOptions? capturedOptions = null;
- var chatClientMock = new Mock();
- chatClientMock
- .Setup(c => c.GetResponseAsync(It.IsAny>(), It.IsAny(), It.IsAny()))
- .Callback, ChatOptions?, CancellationToken>((_, opts, _) => capturedOptions = opts)
- .ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "done")));
-
- var executorMock = new Mock();
- executorMock.Setup(e => e.AsAIFunction(It.IsAny(), It.IsAny(), It.IsAny()))
- .Returns(AIFunctionFactory.Create(() => "shell output", "run_shell"));
-
- var options = CreateAllDisabledOptions();
- options.DisableWebSearch = true;
- options.ShellExecutor = executorMock.Object;
-
- // Act
- var agent = new HarnessAgent(chatClientMock.Object, options);
- var session = await agent.CreateSessionAsync();
- await agent.RunAsync([new ChatMessage(ChatRole.User, "Hi")], session);
-
- // Assert — the shell tool should be present
- Assert.NotNull(capturedOptions?.Tools);
- Assert.Contains(capturedOptions!.Tools!, t => t is AIFunction f && f.Name == "run_shell");
- }
-
- ///
- /// Verify that a custom shell tool name, description, and approval flag are forwarded to the executor.
- ///
- [Fact]
- public async Task ShellExecutor_CustomToolNameDescriptionAndApprovalForwardedAsync()
- {
- // Arrange
- ChatOptions? capturedOptions = null;
- var chatClientMock = new Mock();
- chatClientMock
- .Setup(c => c.GetResponseAsync(It.IsAny>(), It.IsAny(), It.IsAny()))
- .Callback, ChatOptions?, CancellationToken>((_, opts, _) => capturedOptions = opts)
- .ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "done")));
-
- string? capturedName = null;
- string? capturedDescription = null;
- bool? capturedRequireApproval = null;
- var executorMock = new Mock();
- executorMock.Setup(e => e.AsAIFunction(It.IsAny(), It.IsAny(), It.IsAny()))
- .Callback((name, description, requireApproval) =>
- {
- capturedName = name;
- capturedDescription = description;
- capturedRequireApproval = requireApproval;
- })
- .Returns(AIFunctionFactory.Create(() => "shell output", "custom_shell"));
-
- var options = CreateAllDisabledOptions();
- options.DisableWebSearch = true;
- options.ShellExecutor = executorMock.Object;
- options.ShellToolName = "custom_shell";
- options.ShellToolDescription = "Run a custom command.";
- options.DisableShellToolApproval = true;
-
- // Act
- var agent = new HarnessAgent(chatClientMock.Object, options);
- var session = await agent.CreateSessionAsync();
- await agent.RunAsync([new ChatMessage(ChatRole.User, "Hi")], session);
-
- // Assert — the configured values are passed through to the executor and the tool is registered.
- Assert.Equal("custom_shell", capturedName);
- Assert.Equal("Run a custom command.", capturedDescription);
- Assert.False(capturedRequireApproval);
- Assert.NotNull(capturedOptions?.Tools);
- Assert.Contains(capturedOptions!.Tools!, t => t is AIFunction f && f.Name == "custom_shell");
- }
-
- ///
- /// Verify that the shell tool defaults to requiring approval and the executor's default name when not configured.
- ///
- [Fact]
- public async Task ShellExecutor_DefaultsToApprovalAndDefaultNameAsync()
- {
- // Arrange
- var chatClientMock = new Mock();
- chatClientMock
- .Setup(c => c.GetResponseAsync(It.IsAny>(), It.IsAny(), It.IsAny()))
- .ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "done")));
-
- bool? capturedRequireApproval = null;
- string? capturedName = null;
- var executorMock = new Mock();
- executorMock.Setup(e => e.AsAIFunction(It.IsAny(), It.IsAny(), It.IsAny()))
- .Callback((name, _, requireApproval) =>
- {
- capturedName = name;
- capturedRequireApproval = requireApproval;
- })
- .Returns(AIFunctionFactory.Create(() => "shell output", "run_shell"));
-
- var options = CreateAllDisabledOptions();
- options.DisableWebSearch = true;
- options.ShellExecutor = executorMock.Object;
-
- // Act
- var agent = new HarnessAgent(chatClientMock.Object, options);
- var session = await agent.CreateSessionAsync();
- await agent.RunAsync([new ChatMessage(ChatRole.User, "Hi")], session);
-
- // Assert — approval is required by default and the executor's default name is used.
- Assert.True(capturedRequireApproval);
- Assert.Equal("run_shell", capturedName);
- }
-
- ///
- /// Verify that disabling shell approval is honored end-to-end when the underlying executor permits unapproved use:
- /// a real constructed with
- /// set to plus set to
- /// yields a shell tool that is not wrapped in an .
- ///
- [Fact]
- public async Task ShellExecutor_ApprovalDisabledWithAcknowledgedExecutorProducesNonApprovalToolAsync()
- {
- // Arrange
- ChatOptions? capturedOptions = null;
- var chatClientMock = new Mock();
- chatClientMock
- .Setup(c => c.GetResponseAsync(It.IsAny>(), It.IsAny(), It.IsAny()))
- .Callback, ChatOptions?, CancellationToken>((_, opts, _) => capturedOptions = opts)
- .ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "done")));
-
- await using var executor = new LocalShellExecutor(new LocalShellExecutorOptions { AcknowledgeUnsafe = true });
-
- var options = CreateAllDisabledOptions();
- options.DisableWebSearch = true;
- options.ShellExecutor = executor;
- options.DisableShellToolApproval = true;
-
- // Act
- var agent = new HarnessAgent(chatClientMock.Object, options);
- var session = await agent.CreateSessionAsync();
- await agent.RunAsync([new ChatMessage(ChatRole.User, "Hi")], session);
-
- // Assert — the shell tool is registered but not gated by approval.
- Assert.NotNull(capturedOptions?.Tools);
- var shellTool = Assert.Single(capturedOptions!.Tools!, t => t is AIFunction f && f.Name == "run_shell");
- Assert.IsNotType(shellTool);
- }
-
- ///
- /// Verify that ShellEnvironmentProvider is present when ShellEnvironmentProviderOptions is also specified.
- ///
- [Fact]
- public void ShellEnvironmentProvider_PresentWhenOptionsProvided()
- {
- // Arrange
- var chatClient = new Mock().Object;
- var executorMock = new Mock();
- executorMock.Setup(e => e.AsAIFunction(It.IsAny(), It.IsAny(), It.IsAny()))
- .Returns(AIFunctionFactory.Create(() => "test", "run_shell"));
- var envOptions = new ShellEnvironmentProviderOptions
- {
- ProbeTools = ["git", "python"],
- };
- var options = CreateAllDisabledOptions();
- options.ShellExecutor = executorMock.Object;
- options.ShellEnvironmentProviderOptions = envOptions;
-
- // Act
- var agent = new HarnessAgent(chatClient, options);
- var innerAgent = agent.GetService();
-
- // Assert — provider should exist (options wiring is validated by the provider's behavior)
- Assert.NotNull(innerAgent?.AIContextProviders);
- Assert.Contains(innerAgent!.AIContextProviders!, p => p is ShellEnvironmentProvider);
- }
-
- #endregion
-#endif
-
#region LoggerFactory and ServiceProvider
///