From 3813820d32a50f799aef304b97a9c7ac9164ac94 Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Mon, 13 Jul 2026 14:56:53 +0000
Subject: [PATCH 1/2] Switch FileAcessProvider on Harness to opt-in
---
.../Harness_Step01_Research/Program.cs | 1 -
.../Program.cs | 2 -
.../Harness_Step03_DataProcessing/Program.cs | 6 +--
.../Harness_Step03_DataProcessing/README.md | 4 +-
.../Harness_Step04_CodeExecution/Program.cs | 7 +++-
.../Harness/Harness_Step05_Loop/Program.cs | 1 -
.../HarnessAgent.cs | 10 ++---
.../HarnessAgentOptions.cs | 20 +++++-----
.../HarnessAgentOptionsTests.cs | 7 ++--
.../HarnessAgentTests.cs | 37 +++++++++++++------
10 files changed, 53 insertions(+), 42 deletions(-)
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step01_Research/Program.cs b/dotnet/samples/02-agents/Harness/Harness_Step01_Research/Program.cs
index f2fcfa52daf..fda733d635d 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step01_Research/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step01_Research/Program.cs
@@ -85,7 +85,6 @@ Track your sources — you will need them when presenting results.
MaxOutputTokens = MaxOutputTokens,
Name = "ResearchAgent",
Description = "A research assistant that plans and executes research tasks.",
- DisableFileAccess = true, // If enabled, this would allow the agent to read/write files in a working directory
OpenTelemetrySourceName = TracingSourceName, // Use our custom source name so spans are captured by the TracerProvider above.
FileMemoryStore = new FileSystemAgentFileStore( // Configure the file memory provider to store files in a local folder called "agent-files".
Path.Combine(AppContext.BaseDirectory, "agent-files")),
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Program.cs b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Program.cs
index aa2ad5c7d12..daaa61a9a19 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Program.cs
@@ -57,7 +57,6 @@
DisableTodoProvider = true,
DisableAgentModeProvider = true,
DisableFileMemory = true, // If enabled, this would allow the agent to store memories as files in a directory associated with the current session
- DisableFileAccess = true, // If enabled, this would allow the agent to read/write files in a working directory
DisableToolAutoApproval = true, // If true, this disables the don't-ask-again approval functionality.
ChatOptions = new ChatOptions
{
@@ -107,7 +106,6 @@ 5. Clear all completed tasks to free memory.
DisableTodoProvider = true,
DisableAgentModeProvider = true,
DisableFileMemory = true, // If enabled, this would allow the agent to store memories as files in a directory associated with the current session
- DisableFileAccess = true, // If enabled, this would allow the agent to read/write files in a working directory
DisableToolAutoApproval = true, // If true, this disables the don't-ask-again approval functionality.
DisableWebSearch = true,
BackgroundAgents = [webSearchAgent],
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/Program.cs b/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/Program.cs
index 00f50c17b37..78a41fd3ed4 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/Program.cs
@@ -1,12 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
-// This sample demonstrates how to use a HarnessAgent with the default FileAccessProvider
+// This sample demonstrates how to use a HarnessAgent with the FileAccessProvider
// to give an agent access to a folder of CSV data files. The agent can read, analyze,
// and extract information from the data, then write results back as new files.
//
// The sample includes a pre-populated `working/` folder with sales transaction data.
-// The HarnessAgent's default FileAccessProvider uses `{cwd}/working` as its working directory,
-// which matches this sample's folder layout.
+// File access is opt-in: setting HarnessAgentOptions.FileAccessStore enables the
+// FileAccessProvider, and this sample points it at the `working/` folder below the location of the executable.
// Ask the agent to analyze the data, produce summaries, or create new output files.
//
// Special commands:
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/README.md b/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/README.md
index d06348fa728..b8ca80b188c 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/README.md
+++ b/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/README.md
@@ -1,11 +1,11 @@
# What this sample demonstrates
-This sample demonstrates how to use a `HarnessAgent` with the default `FileAccessProvider` to give an agent access to a folder of data files for reading, analyzing, and writing results. The `HarnessAgent` pre-configures function invocation, per-service-call chat history persistence, in-loop compaction, tool approval, and OpenTelemetry — so the sample only needs to supply the chat client, token limits, custom instructions, and opt out of unused features.
+This sample demonstrates how to use a `HarnessAgent` with the `FileAccessProvider` to give an agent access to a folder of data files for reading, analyzing, and writing results. The `HarnessAgent` pre-configures function invocation, per-service-call chat history persistence, in-loop compaction, tool approval, and OpenTelemetry — so the sample only needs to supply the chat client, token limits, custom instructions, a `FileAccessStore`, and opt out of unused features.
Key features showcased:
- **HarnessAgent** — a pre-configured agent that wraps a `ChatClientAgent` with function invocation, per-service-call persistence, and context-window compaction
-- **FileAccessProvider** — the HarnessAgent's default file access provider uses `{cwd}/working` as its working directory, matching this sample's `working/` folder
+- **FileAccessProvider** — file access is opt-in; setting `HarnessAgentOptions.FileAccessStore` to the sample's `working/` folder enables the provider's read/write tools
- **CSV data processing** — the agent reads sales transaction data and performs analysis on demand
- **Output file creation** — the agent can write summaries, filtered data, or reports back to the data folder
- **Streaming output** — responses are streamed token-by-token for a natural experience
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/Program.cs b/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/Program.cs
index 46f4c32c81e..b074089d198 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/Program.cs
@@ -82,8 +82,9 @@ to solve problems precisely rather than guessing. You also have access to skills
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Create the agent with ALL HarnessAgent features enabled plus Hyperlight CodeAct.
-// No Disable* flags are set — TodoProvider, AgentModeProvider, FileMemory, FileAccess,
-// ToolApproval, WebSearch, and AgentSkillsProvider are all active.
+// TodoProvider, AgentModeProvider, FileMemory, ToolApproval, WebSearch, and
+// AgentSkillsProvider are on by default. File access is opt-in, so it is enabled here by
+// supplying a FileAccessStore.
AIAgent agent =
new AIProjectClient(
new Uri(endpoint),
@@ -101,6 +102,8 @@ to solve problems precisely rather than guessing. You also have access to skills
OpenTelemetrySourceName = TracingSourceName,
// Point the file memory at a local folder for persistent memory across sessions.
FileMemoryStore = new FileSystemAgentFileStore(Path.Combine(AppContext.BaseDirectory, "agent-files")),
+ // Enable file access (opt-in) by rooting the file access tools at a local working folder.
+ FileAccessStore = new FileSystemAgentFileStore(Path.Combine(AppContext.BaseDirectory, "working")),
// Add the HyperlightCodeActProvider so the agent can execute Python code in a sandbox.
AIContextProviders = [codeAct],
ChatOptions = new ChatOptions
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Program.cs b/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Program.cs
index 5236302a451..98b18172fad 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Program.cs
@@ -252,7 +252,6 @@ AIAgent CreateLeanHarnessAgent(
DisableAgentModeProvider = true,
DisableTodoProvider = disableTodoProvider,
DisableFileMemory = true,
- DisableFileAccess = true,
DisableWebSearch = true,
ToolApprovalAgentOptions = toolApprovalAgentOptions,
ChatOptions = new ChatOptions
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
index 9bb00ce88ca..87fa7d47b59 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgent.cs
@@ -43,13 +43,13 @@ namespace Microsoft.Agents.AI;
/// - — persistent todo list that the agent uses to track multi-step plans. Disable with .
/// - — mode tracking (e.g., "plan" vs "execute") that the agent uses to structure its work. Disable with .
/// - — file-based session memory allowing the agent to persist notes and artifacts across turns. Disable with .
-/// - — shared file access providing read/write tools for a working directory. Disable with .
/// - — discovers and loads skill definitions from the file system, enabling dynamic tool sets. Disable with .
///
///
///
/// Optional context providers (enabled via ):
///
+/// - — 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).
///
@@ -320,13 +320,9 @@ private static List BuildContextProviders(HarnessAgentOptions
}));
}
- if (options?.DisableFileAccess is not true)
+ if (options?.FileAccessStore is AgentFileStore fileAccessStore)
{
- AgentFileStore fileAccessStore = options?.FileAccessStore
- ?? new FileSystemAgentFileStore(
- Path.Combine(Directory.GetCurrentDirectory(), "working"));
-
- providers.Add(new FileAccessProvider(fileAccessStore));
+ providers.Add(new FileAccessProvider(fileAccessStore, options.FileAccessProviderOptions));
}
if (options?.DisableAgentSkillsProvider is not true)
diff --git a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
index 07bc054f733..a639188926a 100644
--- a/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
@@ -237,24 +237,24 @@ public sealed class HarnessAgentOptions
public AgentFileStore? FileMemoryStore { get; set; }
///
- /// Gets or sets a value indicating whether the is disabled.
+ /// Gets or sets the that enables the .
///
///
- /// When (the default), a is included in the
- /// agent's context providers, using either or a default
- /// rooted at {cwd}/working.
+ /// File access is opt-in. When (the default), no
+ /// is added and the agent has no file access tools. When set, a is
+ /// included in the agent's context providers, backed by the supplied store and configured with
+ /// when provided.
///
- public bool DisableFileAccess { get; set; }
+ public AgentFileStore? FileAccessStore { get; set; }
///
- /// Gets or sets a custom for the .
+ /// Gets or sets the used to configure the .
///
///
- /// When and is ,
- /// a default is created.
- /// This property is ignored when is .
+ /// This property is only used when is set (file access is opt-in).
+ /// When , the provider uses its default options.
///
- public AgentFileStore? FileAccessStore { get; set; }
+ public FileAccessProviderOptions? FileAccessProviderOptions { get; set; }
///
/// Gets or sets a value indicating whether the is disabled.
diff --git a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentOptionsTests.cs
index 21e158138af..dc54a92f0c3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentOptionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentOptionsTests.cs
@@ -32,7 +32,6 @@ public void DefaultPropertyValues()
Assert.False(options.DisableToolAutoApproval);
Assert.False(options.DisableApprovalNotRequiredFunctionBypassing);
Assert.False(options.DisableFileMemory);
- Assert.False(options.DisableFileAccess);
Assert.False(options.DisableWebSearch);
Assert.False(options.DisableTodoProvider);
Assert.False(options.DisableAgentModeProvider);
@@ -42,6 +41,7 @@ public void DefaultPropertyValues()
Assert.Null(options.MaximumIterationsPerRequest);
Assert.Null(options.FileMemoryStore);
Assert.Null(options.FileAccessStore);
+ Assert.Null(options.FileAccessProviderOptions);
Assert.Null(options.AgentModeProviderOptions);
Assert.Null(options.AgentSkillsSource);
Assert.Null(options.BackgroundAgents);
@@ -63,6 +63,7 @@ public void PropertiesCanBeSetAndRetrieved()
var contextProviders = new AIContextProvider[] { new TodoProvider() };
var fileMemoryStore = new Mock().Object;
var fileAccessStore = new Mock().Object;
+ var fileAccessOptions = new FileAccessProviderOptions();
var agentModeOptions = new AgentModeProviderOptions();
var skillsSource = new Mock().Object;
var backgroundAgents = new AIAgent[] { new Mock().Object };
@@ -89,8 +90,8 @@ public void PropertiesCanBeSetAndRetrieved()
DisableApprovalNotRequiredFunctionBypassing = true,
DisableFileMemory = true,
FileMemoryStore = fileMemoryStore,
- DisableFileAccess = true,
FileAccessStore = fileAccessStore,
+ FileAccessProviderOptions = fileAccessOptions,
DisableWebSearch = true,
DisableTodoProvider = true,
DisableAgentModeProvider = true,
@@ -124,8 +125,8 @@ public void PropertiesCanBeSetAndRetrieved()
Assert.True(options.DisableApprovalNotRequiredFunctionBypassing);
Assert.True(options.DisableFileMemory);
Assert.Same(fileMemoryStore, options.FileMemoryStore);
- Assert.True(options.DisableFileAccess);
Assert.Same(fileAccessStore, options.FileAccessStore);
+ Assert.Same(fileAccessOptions, options.FileAccessProviderOptions);
Assert.True(options.DisableWebSearch);
Assert.True(options.DisableTodoProvider);
Assert.True(options.DisableAgentModeProvider);
diff --git a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
index cf64bfb711c..8c13024db08 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
@@ -32,7 +32,6 @@ public class HarnessAgentTests
DisableToolAutoApproval = true,
DisableOpenTelemetry = true,
DisableFileMemory = true,
- DisableFileAccess = true,
DisableWebSearch = true,
DisableTodoProvider = true,
DisableAgentModeProvider = true,
@@ -1212,15 +1211,15 @@ public void FileMemoryProvider_UsesCustomStore()
#region Feature: FileAccessProvider
///
- /// Verify that FileAccessProvider is included in AIContextProviders by default.
+ /// Verify that FileAccessProvider is included in AIContextProviders when a FileAccessStore is provided.
///
[Fact]
- public void FileAccessProvider_IncludedByDefault()
+ public void FileAccessProvider_IncludedWhenStoreProvided()
{
// Arrange
var chatClient = new Mock().Object;
var options = CreateAllDisabledOptions();
- options.DisableFileAccess = false;
+ options.FileAccessStore = new Mock().Object;
// Act
var agent = new HarnessAgent(chatClient, options);
@@ -1232,10 +1231,10 @@ public void FileAccessProvider_IncludedByDefault()
}
///
- /// Verify that FileAccessProvider is excluded when disabled.
+ /// Verify that FileAccessProvider is excluded by default (opt-in: no store provided).
///
[Fact]
- public void FileAccessProvider_ExcludedWhenDisabled()
+ public void FileAccessProvider_ExcludedByDefault()
{
// Arrange
var chatClient = new Mock().Object;
@@ -1262,7 +1261,6 @@ public void FileAccessProvider_UsesCustomStore()
var chatClient = new Mock().Object;
var customStore = new Mock().Object;
var options = CreateAllDisabledOptions();
- options.DisableFileAccess = false;
options.FileAccessStore = customStore;
// Act
@@ -1274,6 +1272,27 @@ public void FileAccessProvider_UsesCustomStore()
Assert.Contains(innerAgent!.AIContextProviders!, p => p is FileAccessProvider);
}
+ ///
+ /// Verify that FileAccessProviderOptions are honored when a FileAccessStore is provided.
+ ///
+ [Fact]
+ public void FileAccessProvider_UsesProvidedOptions()
+ {
+ // Arrange
+ var chatClient = new Mock().Object;
+ var options = CreateAllDisabledOptions();
+ options.FileAccessStore = new Mock().Object;
+ options.FileAccessProviderOptions = new FileAccessProviderOptions { DisableWriteTools = true };
+
+ // Act
+ var agent = new HarnessAgent(chatClient, options);
+ var innerAgent = agent.GetService();
+
+ // Assert — FileAccessProvider should be present when options are supplied alongside a store.
+ Assert.NotNull(innerAgent?.AIContextProviders);
+ Assert.Contains(innerAgent!.AIContextProviders!, p => p is FileAccessProvider);
+ }
+
#endregion
#region Feature: AgentSkillsProvider
@@ -1419,7 +1438,6 @@ public async Task AllDefaults_AllFeaturesEnabledAsync()
Assert.Contains(providers, p => p is TodoProvider);
Assert.Contains(providers, p => p is AgentModeProvider);
Assert.Contains(providers, p => p is FileMemoryProvider);
- Assert.Contains(providers, p => p is FileAccessProvider);
Assert.Contains(providers, p => p is AgentSkillsProvider);
// Assert — HostedWebSearchTool is present in the tools sent to the model
@@ -1903,7 +1921,6 @@ public void Constructor_LoggerFactoryIsUsedByDownstreamComponents()
DisableToolAutoApproval = true,
DisableOpenTelemetry = true,
DisableFileMemory = true,
- DisableFileAccess = true,
DisableWebSearch = true,
DisableTodoProvider = true,
DisableAgentModeProvider = true,
@@ -1954,7 +1971,6 @@ public void Constructor_SucceedsWithoutTokenValues()
DisableToolAutoApproval = true,
DisableOpenTelemetry = true,
DisableFileMemory = true,
- DisableFileAccess = true,
DisableWebSearch = true,
DisableTodoProvider = true,
DisableAgentModeProvider = true,
@@ -1986,7 +2002,6 @@ public void Constructor_SucceedsWithOnlyMaxContextWindowTokens()
DisableToolAutoApproval = true,
DisableOpenTelemetry = true,
DisableFileMemory = true,
- DisableFileAccess = true,
DisableWebSearch = true,
DisableTodoProvider = true,
DisableAgentModeProvider = true,
From 5f010d52799f7bbd6ec66ae39927eb830c95f6ef Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Mon, 13 Jul 2026 15:35:02 +0000
Subject: [PATCH 2/2] Address PR comment
---
.../HarnessAgentTests.cs | 29 +++++++++++++++----
1 file changed, 24 insertions(+), 5 deletions(-)
diff --git a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
index 8c13024db08..2b9613d0610 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Harness.UnitTests/HarnessAgentTests.cs
@@ -1273,24 +1273,43 @@ public void FileAccessProvider_UsesCustomStore()
}
///
- /// Verify that FileAccessProviderOptions are honored when a FileAccessStore is provided.
+ /// Verify that FileAccessProviderOptions are honored: setting DisableWriteTools must remove the
+ /// write tools from the provider that HarnessAgent wires up, while the read-only tools remain.
///
[Fact]
- public void FileAccessProvider_UsesProvidedOptions()
+ public async Task FileAccessProvider_UsesProvidedOptionsAsync()
{
// Arrange
var chatClient = new Mock().Object;
var options = CreateAllDisabledOptions();
- options.FileAccessStore = new Mock().Object;
+ options.FileAccessStore = new InMemoryAgentFileStore();
options.FileAccessProviderOptions = new FileAccessProviderOptions { DisableWriteTools = true };
// Act
var agent = new HarnessAgent(chatClient, options);
var innerAgent = agent.GetService();
- // Assert — FileAccessProvider should be present when options are supplied alongside a store.
+ // Assert — the FileAccessProvider is present and honors the supplied options.
Assert.NotNull(innerAgent?.AIContextProviders);
- Assert.Contains(innerAgent!.AIContextProviders!, p => p is FileAccessProvider);
+ var fileAccessProvider = Assert.IsType(
+ Assert.Single(innerAgent!.AIContextProviders!, p => p is FileAccessProvider));
+
+ var mockAgent = new Mock().Object;
+ var session = await agent.CreateSessionAsync();
+#pragma warning disable MAAI001
+ var context = new AIContextProvider.InvokingContext(mockAgent, session, new AIContext());
+#pragma warning restore MAAI001
+ AIContext result = await fileAccessProvider.InvokingAsync(context);
+ var toolNames = result.Tools!.OfType().Select(t => t.Name).ToList();
+
+ // DisableWriteTools = true => only the read-only tools are exposed.
+ Assert.Contains(FileAccessProvider.ReadFileToolName, toolNames);
+ Assert.Contains(FileAccessProvider.LsToolName, toolNames);
+ Assert.Contains(FileAccessProvider.GrepToolName, toolNames);
+ Assert.DoesNotContain(FileAccessProvider.WriteToolName, toolNames);
+ Assert.DoesNotContain(FileAccessProvider.DeleteFileToolName, toolNames);
+ Assert.DoesNotContain(FileAccessProvider.ReplaceToolName, toolNames);
+ Assert.DoesNotContain(FileAccessProvider.ReplaceLinesToolName, toolNames);
}
#endregion