diff --git a/.gitignore b/.gitignore
index e6b9efb40e..258a8c0704 100644
--- a/.gitignore
+++ b/.gitignore
@@ -214,6 +214,7 @@ WARP.md
**/memory-bank/
**/projectBrief.md
**/tmpclaude*
+.kiro/
# Dependency-bound validation reports
python/scripts/dependency-*-results.json
python/scripts/dependencies/dependency-*-results.json
diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index fc57fdea08..76a4444bc1 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -138,10 +138,15 @@
+
+
+
+
+
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 98c364cd53..3160eb6de7 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -24,7 +24,6 @@
-
@@ -129,6 +128,7 @@
+
@@ -194,6 +194,8 @@
+
+
@@ -625,6 +627,7 @@
+
@@ -678,5 +681,6 @@
+
diff --git a/dotnet/eng/verify-samples/AgentsSamples.cs b/dotnet/eng/verify-samples/AgentsSamples.cs
index 589b3a6fb8..66d68248f6 100644
--- a/dotnet/eng/verify-samples/AgentsSamples.cs
+++ b/dotnet/eng/verify-samples/AgentsSamples.cs
@@ -50,19 +50,6 @@ internal static class AgentsSamples
],
},
- new SampleDefinition
- {
- Name = "Agent_With_AzureAIAgentsPersistent",
- ProjectPath = "samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent",
- RequiredEnvironmentVariables = ["AZURE_AI_PROJECT_ENDPOINT"],
- OptionalEnvironmentVariables = ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
- ExpectedOutputDescription =
- [
- "The output should contain a joke about a pirate.",
- "The output should not contain error messages or stack traces.",
- ],
- },
-
new SampleDefinition
{
Name = "Agent_With_AzureAIProject",
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs
deleted file mode 100644
index af41e69c77..0000000000
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-#pragma warning disable CS0618 // Type or member is obsolete - sample uses deprecated PersistentAgentsClientExtensions
-
-// This sample shows how to create and use a simple AI agent with Microsoft Foundry Agents as the backend.
-
-using Azure.AI.Agents.Persistent;
-using Azure.Identity;
-using Microsoft.Agents.AI;
-
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
-
-const string JokerName = "Joker";
-const string JokerInstructions = "You are good at telling jokes.";
-
-// Get a client to create/retrieve server side agents with.
-// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
-// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
-// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
-var persistentAgentsClient = new PersistentAgentsClient(endpoint, new DefaultAzureCredential());
-
-// You can create a server side persistent agent with the Azure.AI.Agents.Persistent SDK.
-var agentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync(
- model: deploymentName,
- name: JokerName,
- instructions: JokerInstructions);
-
-// You can retrieve an already created server side persistent agent as an AIAgent.
-AIAgent agent1 = await persistentAgentsClient.GetAIAgentAsync(agentMetadata.Value.Id);
-
-// You can also create a server side persistent agent and return it as an AIAgent directly.
-AIAgent agent2 = await persistentAgentsClient.CreateAIAgentAsync(
- model: deploymentName,
- name: JokerName,
- instructions: JokerInstructions);
-
-// You can then invoke the agent like any other AIAgent.
-AgentSession session = await agent1.CreateSessionAsync();
-Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", session));
-
-// Cleanup for sample purposes.
-await persistentAgentsClient.Administration.DeleteAgentAsync(agent1.Id);
-await persistentAgentsClient.Administration.DeleteAgentAsync(agent2.Id);
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md
deleted file mode 100644
index dbe7c2c12f..0000000000
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Classic Foundry Agents
-
-This sample demonstrates how to create an agent using the classic Foundry Agents experience.
-
-# Classic vs New Foundry Agents
-
-Below is a comparison between the classic and new Foundry Agents approaches:
-
-[Migration Guide](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/migrate?view=foundry)
-
-# Prerequisites
-
-Before you begin, ensure you have the following prerequisites:
-
-- .NET 10 SDK or later
-- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (for Azure credential authentication)
-
-**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
-
-Set the following environment variables:
-
-```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
-```
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Program.cs b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Program.cs
index 233705d4af..d523e3c8bd 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Program.cs
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/Program.cs
@@ -8,8 +8,8 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
const string JokerName = "JokerAgent";
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/README.md b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/README.md
index 0e225751fb..4a1412838d 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/README.md
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIProject/README.md
@@ -1,4 +1,4 @@
-# New Foundry Agents
+# New Foundry Agents
This sample demonstrates how to create an agent using the new Foundry Agents experience.
@@ -21,6 +21,6 @@ Before you begin, ensure you have the following prerequisites:
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
+$env:FOUNDRY_MODEL="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
```
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Program.cs b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Program.cs
index 556b52bf17..bc55293037 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Program.cs
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/Program.cs
@@ -13,7 +13,7 @@ using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
-var model = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "Phi-4-mini-instruct";
+var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "Phi-4-mini-instruct";
// Since we are using the OpenAI Client SDK, we need to override the default endpoint to point to Microsoft Foundry.
var clientOptions = new OpenAIClientOptions() { Endpoint = new Uri(endpoint) };
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/README.md b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/README.md
index 9bc4d60881..9e518e9456 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/README.md
+++ b/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureFoundryModel/README.md
@@ -1,4 +1,4 @@
-## Overview
+## Overview
This sample shows how to use the OpenAI SDK to create and use a simple AI agent with any model hosted in Microsoft Foundry.
@@ -13,7 +13,7 @@ Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
- Microsoft Foundry resource
- A model deployment in your Microsoft Foundry resource. This example defaults to using the `Phi-4-mini-instruct` model,
-so if you want to use a different model, ensure that you set your `AZURE_AI_MODEL_DEPLOYMENT_NAME` environment
+so if you want to use a different model, ensure that you set your `FOUNDRY_MODEL` environment
variable to the name of your deployed model.
- An API key or role based authentication to access the Microsoft Foundry resource
@@ -30,5 +30,5 @@ $env:AZURE_OPENAI_ENDPOINT="https://ai-foundry-.services.ai.azur
$env:AZURE_OPENAI_API_KEY="************"
# Optional, defaults to Phi-4-mini-instruct
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="Phi-4-mini-instruct"
+$env:FOUNDRY_MODEL="Phi-4-mini-instruct"
```
diff --git a/dotnet/samples/02-agents/AgentProviders/README.md b/dotnet/samples/02-agents/AgentProviders/README.md
index 5584fdc810..7c0e003713 100644
--- a/dotnet/samples/02-agents/AgentProviders/README.md
+++ b/dotnet/samples/02-agents/AgentProviders/README.md
@@ -16,7 +16,6 @@ See the README.md for each sample for the prerequisites for that sample.
|---|---|
|[Creating an AIAgent with A2A](./Agent_With_A2A/)|This sample demonstrates how to create AIAgent for an existing A2A agent.|
|[Creating an AIAgent with Anthropic](./Agent_With_Anthropic/)|This sample demonstrates how to create an AIAgent using Anthropic Claude models as the underlying inference service|
-|[Creating an AIAgent with Foundry Agents using Azure.AI.Agents.Persistent](./Agent_With_AzureAIAgentsPersistent/)|This sample demonstrates how to create a Foundry Persistent agent and expose it as an AIAgent using the Azure.AI.Agents.Persistent SDK|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Project](./Agent_With_AzureAIProject/)|This sample demonstrates how to create an Foundry Project agent and expose it as an AIAgent using the Azure.AI.Project SDK|
|[Creating an AIAgent with Foundry Model](./Agent_With_AzureFoundryModel/)|This sample demonstrates how to use any model deployed to Microsoft Foundry to create an AIAgent|
|[Creating an AIAgent with Azure OpenAI ChatCompletion](./Agent_With_AzureOpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using Azure OpenAI ChatCompletion as the underlying inference service|
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Program.cs
index c9dc86e3b4..83c6823089 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Program.cs
@@ -26,6 +26,9 @@ var skillsProvider = new AgentSkillsProvider(
SubprocessScriptRunner.RunAsync);
// --- Agent Setup ---
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_CodeDefinedSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step02_CodeDefinedSkills/Program.cs
index 8c1cfa33bb..059ec7e4a4 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_CodeDefinedSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step02_CodeDefinedSkills/Program.cs
@@ -67,6 +67,9 @@ var unitConverterSkill = new AgentInlineSkill(
var skillsProvider = new AgentSkillsProvider(unitConverterSkill);
// --- Agent Setup ---
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
index aa70c4461e..d255f1be6e 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
@@ -22,6 +22,9 @@ var unitConverter = new UnitConverterSkill();
var skillsProvider = new AgentSkillsProvider(unitConverter);
// --- Agent Setup ---
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Program.cs
index 28d5cb9ee9..a67658b444 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Program.cs
@@ -64,6 +64,9 @@ var skillsProvider = new AgentSkillsProviderBuilder()
.Build();
// --- Agent Setup ---
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Program.cs
index 251503a918..7c5e594fd2 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Program.cs
@@ -80,6 +80,9 @@ var weightSkill = new WeightConverterSkill();
var skillsProvider = new AgentSkillsProvider(distanceSkill, weightSkill);
// --- Agent Setup ---
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/Program.cs b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/Program.cs
index ed3b1315cf..7737eed3c2 100644
--- a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/Program.cs
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/Program.cs
@@ -16,6 +16,9 @@ var guestPath = Environment.GetEnvironmentVariable("HYPERLIGHT_PYTHON_GUEST_PATH
using var codeAct = new HyperlightCodeActProvider(HyperlightCodeActProviderOptions.CreateForWasm(guestPath));
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/Program.cs b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/Program.cs
index 3ae1faccf2..563fee388b 100644
--- a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/Program.cs
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/Program.cs
@@ -39,6 +39,9 @@ options.Tools = [fetchDocs, queryData, sendEmail];
using var codeAct = new HyperlightCodeActProvider(options);
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/Program.cs b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/Program.cs
index fae83b14fd..cae0613429 100644
--- a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/Program.cs
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/Program.cs
@@ -31,6 +31,9 @@ var instructions =
+ "and calling `execute_code` instead of computing values yourself.\n\n"
+ executeCode.BuildInstructions(toolsVisibleToModel: false);
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/AgentWithMemory_Step03_MemoryUsingValkey.csproj b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/AgentWithMemory_Step03_MemoryUsingValkey.csproj
new file mode 100644
index 0000000000..1217591bc1
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/AgentWithMemory_Step03_MemoryUsingValkey.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/Program.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/Program.cs
new file mode 100644
index 0000000000..6faa02a0f3
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/Program.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates using Valkey for persistent chat history with the Agent Framework.
+// ValkeyChatHistoryProvider persists conversation history across sessions using Valkey lists.
+//
+// Prerequisites:
+// - A running Valkey server (any version):
+// docker run -d --name valkey -p 6379:6379 valkey/valkey:latest
+// - Azure OpenAI endpoint and deployment configured via environment variables
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Valkey;
+using Microsoft.Extensions.AI;
+using OpenAI.Chat;
+using Valkey.Glide;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var valkeyConnection = Environment.GetEnvironmentVariable("VALKEY_CONNECTION") ?? "localhost:6379";
+
+var connection = await ConnectionMultiplexer.ConnectAsync(valkeyConnection);
+
+Console.WriteLine("=== ValkeyChatHistoryProvider — Persistent Chat History ===\n");
+
+var historyProvider = new ValkeyChatHistoryProvider(
+ connection,
+ _ => new ValkeyChatHistoryProvider.State($"sample-{Guid.NewGuid():N}"),
+ new ValkeyChatHistoryProviderOptions
+ {
+ KeyPrefix = "sample_chat",
+ MaxMessages = 20
+ });
+
+AIAgent historyAgent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
+ .GetChatClient(deploymentName)
+ .AsAIAgent(new ChatClientAgentOptions()
+ {
+ ChatOptions = new() { Instructions = "You are a helpful assistant that remembers our conversation." },
+ ChatHistoryProvider = historyProvider
+ });
+
+AgentSession session1 = await historyAgent.CreateSessionAsync();
+Console.WriteLine(await historyAgent.RunAsync("Hello! My name is Alex and I'm a software engineer.", session1));
+Console.WriteLine(await historyAgent.RunAsync("I'm working on a project using Valkey for caching.", session1));
+Console.WriteLine(await historyAgent.RunAsync("What do you remember about me?", session1));
+
+var messageCount = await historyProvider.GetMessageCountAsync(session1);
+Console.WriteLine($"\n Stored {messageCount} messages in Valkey.\n");
+
+// Clean up
+connection.Dispose();
+
+Console.WriteLine("Done!");
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/README.md b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/README.md
new file mode 100644
index 0000000000..08f65ecffa
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey/README.md
@@ -0,0 +1,30 @@
+# Agent with Memory Using Valkey
+
+This sample demonstrates using Valkey for persistent chat history with the Agent Framework.
+
+## Components
+
+- **ValkeyChatHistoryProvider** — Persists conversation history across sessions using Valkey lists. Works with any Valkey or Redis OSS server (no search module required).
+
+## Prerequisites
+
+- Azure OpenAI endpoint and deployment
+- A running Valkey server (any version):
+
+```bash
+docker run -d --name valkey -p 6379:6379 valkey/valkey:latest
+```
+
+## Environment Variables
+
+| Variable | Description | Default |
+|---|---|---|
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint URL | (required) |
+| `AZURE_OPENAI_DEPLOYMENT_NAME` | Model deployment name | `gpt-5.4-mini` |
+| `VALKEY_CONNECTION` | Valkey connection string | `localhost:6379` |
+
+## Running
+
+```bash
+dotnet run
+```
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock.csproj b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock.csproj
new file mode 100644
index 0000000000..274ac15c97
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/Program.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/Program.cs
new file mode 100644
index 0000000000..6f3027681f
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/Program.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates using Valkey for persistent chat history with the Agent Framework,
+// powered by Amazon Bedrock.
+//
+// Prerequisites:
+// - A running Valkey server (any version):
+// docker run -d --name valkey -p 6379:6379 valkey/valkey:latest
+// - AWS credentials configured (environment variables, AWS profile, or IAM role)
+// - Access to an Amazon Bedrock model (e.g., Anthropic Claude)
+
+using Amazon;
+using Amazon.BedrockRuntime;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Valkey;
+using Microsoft.Extensions.AI;
+using Valkey.Glide;
+
+var awsRegion = Environment.GetEnvironmentVariable("AWS_REGION") ?? "us-east-1";
+var modelId = Environment.GetEnvironmentVariable("BEDROCK_MODEL_ID") ?? "anthropic.claude-3-5-sonnet-20241022-v2:0";
+var valkeyConnection = Environment.GetEnvironmentVariable("VALKEY_CONNECTION") ?? "localhost:6379";
+
+// Create the Bedrock runtime client.
+var bedrockRuntime = new AmazonBedrockRuntimeClient(RegionEndpoint.GetBySystemName(awsRegion));
+IChatClient chatClient = bedrockRuntime.AsIChatClient(modelId);
+
+var connection = await ConnectionMultiplexer.ConnectAsync(valkeyConnection);
+
+Console.WriteLine("=== ValkeyChatHistoryProvider — Persistent Chat History (Bedrock) ===\n");
+
+var historyProvider = new ValkeyChatHistoryProvider(
+ connection,
+ _ => new ValkeyChatHistoryProvider.State($"bedrock-sample-{Guid.NewGuid():N}"),
+ new ValkeyChatHistoryProviderOptions
+ {
+ KeyPrefix = "bedrock_chat",
+ MaxMessages = 20
+ });
+
+AIAgent historyAgent = chatClient.AsAIAgent(new ChatClientAgentOptions()
+{
+ ChatOptions = new() { Instructions = "You are a helpful assistant that remembers our conversation." },
+ ChatHistoryProvider = historyProvider
+});
+
+AgentSession session1 = await historyAgent.CreateSessionAsync();
+Console.WriteLine(await historyAgent.RunAsync("Hello! My name is Alex and I'm a software engineer.", session1));
+Console.WriteLine(await historyAgent.RunAsync("I'm working on a project using Valkey for caching.", session1));
+Console.WriteLine(await historyAgent.RunAsync("What do you remember about me?", session1));
+
+var messageCount = await historyProvider.GetMessageCountAsync(session1);
+Console.WriteLine($"\n Stored {messageCount} messages in Valkey.\n");
+
+// Clean up
+connection.Dispose();
+
+Console.WriteLine("Done!");
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/README.md b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/README.md
new file mode 100644
index 0000000000..06d4012bd9
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step03_MemoryUsingValkey_Bedrock/README.md
@@ -0,0 +1,41 @@
+# Agent with Memory Using Valkey + Amazon Bedrock
+
+This sample demonstrates using Valkey for persistent chat history with the Agent Framework, powered by Amazon Bedrock via the `AWSSDK.Extensions.Bedrock.MEAI` adapter.
+
+## Components
+
+- **ValkeyChatHistoryProvider** — Persists conversation history across sessions using Valkey lists. Works with any Valkey or Redis OSS server (no search module required).
+- **Amazon Bedrock** — Provides the LLM via `AWSSDK.Extensions.Bedrock.MEAI`, which implements `IChatClient` from `Microsoft.Extensions.AI`.
+
+## Prerequisites
+
+- AWS credentials configured (environment variables, AWS CLI profile, or IAM role)
+- Access to an Amazon Bedrock model (e.g., Anthropic Claude 3.5 Sonnet)
+- A running Valkey server (any version):
+
+```bash
+docker run -d --name valkey -p 6379:6379 valkey/valkey:latest
+```
+
+## Environment Variables
+
+| Variable | Description | Default |
+|---|---|---|
+| `AWS_REGION` | AWS region for Bedrock | `us-east-1` |
+| `BEDROCK_MODEL_ID` | Bedrock model identifier | `anthropic.claude-3-5-sonnet-20241022-v2:0` |
+| `VALKEY_CONNECTION` | Valkey connection string | `localhost:6379` |
+| `AWS_ACCESS_KEY_ID` | AWS access key (if not using profile/role) | — |
+| `AWS_SECRET_ACCESS_KEY` | AWS secret key (if not using profile/role) | — |
+
+## Running
+
+```bash
+# Using default AWS credential chain (profile, env vars, or IAM role)
+dotnet run
+
+# Or with explicit credentials
+export AWS_ACCESS_KEY_ID="your-access-key"
+export AWS_SECRET_ACCESS_KEY="your-secret-key"
+export AWS_REGION="us-east-1"
+dotnet run
+```
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/Program.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/Program.cs
index 402ae47a2d..6129a097c6 100644
--- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/Program.cs
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/Program.cs
@@ -13,9 +13,9 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry;
-string foundryEndpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
+string foundryEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
string memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? "memory-store-sample";
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
string embeddingModelName = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002";
// Create an AIProjectClient for Foundry with Azure Identity authentication.
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/README.md b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/README.md
index e863b2eada..f0ed4e43cd 100644
--- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/README.md
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/README.md
@@ -1,4 +1,4 @@
-# Agent with Memory Using Microsoft Foundry
+# Agent with Memory Using Microsoft Foundry
This sample demonstrates how to create and run an agent that uses Microsoft Foundry's managed memory service to extract and retrieve individual memories across sessions.
@@ -22,11 +22,11 @@ This sample demonstrates how to create and run an agent that uses Microsoft Foun
```bash
# Microsoft Foundry project endpoint and memory store name
-export AZURE_AI_PROJECT_ENDPOINT="https://your-account.services.ai.azure.com/api/projects/your-project"
+export FOUNDRY_PROJECT_ENDPOINT="https://your-account.services.ai.azure.com/api/projects/your-project"
export AZURE_AI_MEMORY_STORE_ID="my_memory_store"
# Model deployment names (models deployed in your Foundry project)
-export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+export FOUNDRY_MODEL="gpt-5.4-mini"
export AZURE_AI_EMBEDDING_DEPLOYMENT_NAME="text-embedding-ada-002"
```
diff --git a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs
index 8fc21174a1..0d3cb06fd4 100644
--- a/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs
+++ b/dotnet/samples/02-agents/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs
@@ -13,8 +13,8 @@ using OpenAI.Files;
using OpenAI.Responses;
using OpenAI.VectorStores;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// Create an AI Project client and get an OpenAI client that works with the foundry service.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/Program.cs b/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/Program.cs
index 82b9e16fdd..37c0d68d6a 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/Program.cs
+++ b/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/Program.cs
@@ -10,8 +10,8 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/README.md b/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/README.md
index 14b0835151..18dfabd9c7 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/README.md
+++ b/dotnet/samples/02-agents/Agents/Agent_Step07_AsMcpTool/README.md
@@ -1,4 +1,4 @@
-This sample demonstrates how to expose an existing AI agent as an MCP tool.
+This sample demonstrates how to expose an existing AI agent as an MCP tool.
## Run the sample
@@ -21,9 +21,9 @@ To use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector)
```
1. Open a web browser and navigate to the URL displayed in the terminal. If not opened automatically, this will open the MCP Inspector interface.
1. In the MCP Inspector interface, add the following environment variables to allow your MCP server to access Microsoft Foundry Project to create and run the agent:
- - AZURE_AI_PROJECT_ENDPOINT = https://your-resource.openai.azure.com/ # Replace with your Microsoft Foundry Project endpoint
- - AZURE_AI_MODEL_DEPLOYMENT_NAME = gpt-5.4-mini # Replace with your model deployment name
+ - FOUNDRY_PROJECT_ENDPOINT = https://your-resource.openai.azure.com/ # Replace with your Microsoft Foundry Project endpoint
+ - FOUNDRY_MODEL = gpt-5.4-mini # Replace with your model deployment name
1. Find and click the `Connect` button in the MCP Inspector interface to connect to the MCP server.
1. As soon as the connection is established, open the `Tools` tab in the MCP Inspector interface and select the `Joker` tool from the list.
1. Specify your prompt as a value for the `query` argument, for example: `Tell me a joke about a pirate` and click the `Run Tool` button to run the tool.
-1. The agent will process the request and return a response in accordance with the provided instructions that instruct it to always start each joke with 'Aye aye, captain!'.
\ No newline at end of file
+1. The agent will process the request and return a response in accordance with the provided instructions that instruct it to always start each joke with 'Aye aye, captain!'.
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/Program.cs b/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/Program.cs
index 92222f64e7..e4ed51ddac 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/Program.cs
+++ b/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/Program.cs
@@ -8,9 +8,9 @@ using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Agents.AI;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var deepResearchDeploymentName = Environment.GetEnvironmentVariable("AZURE_AI_REASONING_DEPLOYMENT_NAME") ?? "o3-deep-research";
-var modelDeploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var modelDeploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
var bingConnectionId = Environment.GetEnvironmentVariable("AZURE_AI_BING_CONNECTION_ID") ?? throw new InvalidOperationException("AZURE_AI_BING_CONNECTION_ID is not set.");
// Configure extended network timeout for long-running Deep Research tasks.
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/README.md b/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/README.md
index ee3c0935a2..ca2ffac65b 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/README.md
+++ b/dotnet/samples/02-agents/Agents/Agent_Step15_DeepResearch/README.md
@@ -1,4 +1,4 @@
-# What this sample demonstrates
+# What this sample demonstrates
This sample demonstrates how to create an Azure AI Agent with the Deep Research Tool, which leverages the o3-deep-research reasoning model to perform comprehensive research on complex topics.
@@ -37,7 +37,7 @@ Set the following environment variables:
```powershell
# Replace with your Microsoft Foundry project endpoint
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/"
# Replace with your Bing Grounding connection ID (full ARM resource URI)
$env:AZURE_AI_BING_CONNECTION_ID="/subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts//projects//connections/"
@@ -46,4 +46,4 @@ $env:AZURE_AI_BING_CONNECTION_ID="/subscriptions//resourceGroups//pr
$env:AZURE_AI_REASONING_DEPLOYMENT_NAME="o3-deep-research"
# Optional, defaults to gpt-5.4-mini
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
diff --git a/dotnet/samples/02-agents/Agents/Agent_Step21_ShellWithEnvironment/Program.cs b/dotnet/samples/02-agents/Agents/Agent_Step21_ShellWithEnvironment/Program.cs
index 447dfe92ee..9731f464db 100644
--- a/dotnet/samples/02-agents/Agents/Agent_Step21_ShellWithEnvironment/Program.cs
+++ b/dotnet/samples/02-agents/Agents/Agent_Step21_ShellWithEnvironment/Program.cs
@@ -40,6 +40,9 @@ using OpenAI.Chat;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetChatClient(deploymentName);
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Program.cs
index 0803418ca4..7328366433 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/Program.cs
@@ -9,13 +9,16 @@ using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI.Foundry;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
const string JokerName = "JokerAgent";
// Create the AIProjectClient to manage server-side agents.
-AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential());
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
// Create a server-side agent version using the native SDK.
ProjectsAgentVersion agentVersion = await aiProjectClient.AgentAdministrationClient.CreateAgentVersionAsync(
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/README.md
index 8179c3b299..50b00dc4b7 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step00_FoundryAgentLifecycle/README.md
@@ -1,4 +1,4 @@
-# Agent Step 00 - FoundryAgent Lifecycle
+# Agent Step 00 - FoundryAgent Lifecycle
This sample demonstrates the full lifecycle of a `FoundryAgent` backed by a server-side versioned agent in Microsoft Foundry: create → run → delete.
@@ -6,14 +6,14 @@ This sample demonstrates the full lifecycle of a `FoundryAgent` backed by a serv
- A Microsoft Foundry project endpoint
- A model deployment name (defaults to `gpt-5.4-mini`)
-- Azure CLI installed and authenticated
+- An authenticated Azure identity (for example, sign in with `az login`)
## Environment Variables
| Variable | Description | Required |
| --- | --- | --- |
-| `AZURE_AI_PROJECT_ENDPOINT` | Microsoft Foundry project endpoint | Yes |
-| `AZURE_AI_MODEL_DEPLOYMENT_NAME` | Model deployment name | No (defaults to `gpt-5.4-mini`) |
+| `FOUNDRY_PROJECT_ENDPOINT` | Microsoft Foundry project endpoint | Yes |
+| `FOUNDRY_MODEL` | Model deployment name | No (defaults to `gpt-5.4-mini`) |
## Running the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Program.cs
index 403bae05c2..200a9bf530 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/Program.cs
@@ -6,8 +6,8 @@ using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/README.md
index 612bd21891..09bd0f7c02 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step01_Basics/README.md
@@ -14,15 +14,15 @@ Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (for Azure credential authentication)
+- An authenticated Azure identity (for example, sign in with `az login`)
-**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
+**Note**: This sample uses `DefaultAzureCredential`. `az login` is the easiest local development path, but Visual Studio, VS Code, and managed identity credentials also work when available.
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Program.cs
index e00982199f..5bdda025c7 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/Program.cs
@@ -7,8 +7,8 @@ using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/README.md
index f34c486b53..7394ff432a 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.1_MultiturnConversation/README.md
@@ -15,15 +15,15 @@ Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (for Azure credential authentication)
+- An authenticated Azure identity (for example, sign in with `az login`)
-**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
+**Note**: This sample uses `DefaultAzureCredential`. `az login` is the easiest local development path, but Visual Studio, VS Code, and managed identity credentials also work when available.
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Program.cs
index 317474aa6e..c41d9713f3 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/Program.cs
@@ -9,8 +9,8 @@ using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/README.md
index ee91d935ef..4cc1f635ec 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step02.2_MultiturnWithServerConversations/README.md
@@ -15,15 +15,15 @@ Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (for Azure credential authentication)
+- An authenticated Azure identity (for example, sign in with `az login`)
-**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
+**Note**: This sample uses `DefaultAzureCredential`. `az login` is the easiest local development path, but Visual Studio, VS Code, and managed identity credentials also work when available.
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Program.cs
index e1b4548a04..94492f6cbc 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/Program.cs
@@ -15,8 +15,8 @@ static string GetWeather([Description("The location to get the weather for.")] s
// Define the function tool.
AITool tool = AIFunctionFactory.Create(GetWeather);
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/README.md
index dfad8d0b5c..4692b4838f 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step03_UsingFunctionTools/README.md
@@ -16,15 +16,15 @@ Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (for Azure credential authentication)
+- An authenticated Azure identity (for example, sign in with `az login`)
-**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Microsoft Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
+**Note**: This sample uses `DefaultAzureCredential`. `az login` is the easiest local development path, but Visual Studio, VS Code, and managed identity credentials also work when available.
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs
index 3943f32295..4e470845b8 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs
@@ -12,8 +12,8 @@ using Microsoft.Extensions.AI;
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/README.md
index a832d308e9..27db13b748 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step04_UsingFunctionToolsWithApprovals/README.md
@@ -13,13 +13,13 @@ This sample demonstrates how to use function tools that require human-in-the-loo
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Program.cs
index 07636f12dd..e887b00a6d 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/Program.cs
@@ -12,8 +12,8 @@ using SampleApp;
#pragma warning disable CA5399
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/README.md
index f2770d6055..48e5697f60 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step05_StructuredOutput/README.md
@@ -12,13 +12,13 @@ This sample demonstrates how to configure an agent to produce structured output
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Program.cs
index 18ce97ef88..d99007c972 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/Program.cs
@@ -7,8 +7,8 @@ using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/README.md
index 42074f2972..3b572503e8 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step06_PersistedConversations/README.md
@@ -13,13 +13,13 @@ This sample demonstrates how to persist and resume agent conversations using ses
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Program.cs
index e4b451fea3..c1066fb0a0 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/Program.cs
@@ -10,8 +10,8 @@ using OpenTelemetry;
using OpenTelemetry.Trace;
string? applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING");
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// Create TracerProvider with console exporter.
string sourceName = Guid.NewGuid().ToString("N");
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/README.md
index 70e10d805b..760f2d27dc 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step07_Observability/README.md
@@ -13,13 +13,13 @@ This sample demonstrates how to add OpenTelemetry observability to an agent usin
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
$env:APPLICATIONINSIGHTS_CONNECTION_STRING="..." # Optional
```
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Program.cs
index 019323e56f..7ce6d1eab6 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/Program.cs
@@ -9,8 +9,8 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SampleApp;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/README.md
index 52bb3f591e..cb20f43bbf 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step08_DependencyInjection/README.md
@@ -13,13 +13,13 @@ This sample demonstrates how to register a `ChatClientAgent` in a dependency inj
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Program.cs
index b07917ee01..62682d5cc0 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/Program.cs
@@ -9,8 +9,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// Connect to the Microsoft Learn MCP server via HTTP (Streamable HTTP transport).
Console.WriteLine("Connecting to MCP server at https://learn.microsoft.com/api/mcp ...");
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/README.md
index ae7fffcb2a..378d72c408 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step09_UsingMcpClientAsTools/README.md
@@ -12,14 +12,14 @@ This sample shows how to use MCP (Model Context Protocol) client tools with a `C
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
- Node.js installed (for npx/MCP server)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Program.cs
index 076237c072..8a2126dbdb 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/Program.cs
@@ -7,8 +7,8 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/README.md
index 12d5fb6284..8bd095e99a 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step10_UsingImages/README.md
@@ -13,13 +13,13 @@ This sample demonstrates how to use image multi-modality with an agent.
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and a vision-capable model deployment (e.g., `gpt-5.4-mini`)
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Program.cs
index 06d2a4dc18..892c27c427 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/Program.cs
@@ -12,8 +12,8 @@ using Microsoft.Extensions.AI;
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/README.md
index 4fe155d76d..793235b5ac 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step11_AsFunctionTool/README.md
@@ -13,13 +13,13 @@ This sample demonstrates how to use one agent as a function tool for another age
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Program.cs
index 27240b8372..eaa7cbd07e 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/Program.cs
@@ -20,8 +20,8 @@ static string GetWeather([Description("The location to get the weather for.")] s
static string GetDateTime()
=> DateTimeOffset.Now.ToString();
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/README.md
index 45543329bc..58d59086a7 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step12_Middleware/README.md
@@ -14,13 +14,13 @@ This sample demonstrates multiple middleware layers working together: PII filter
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Program.cs
index 966a7bba12..5d67220fae 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/Program.cs
@@ -16,8 +16,8 @@ using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using SampleApp;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
const string AssistantInstructions = "You are a helpful assistant that helps people find information.";
const string AssistantName = "PluginAssistant";
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/README.md
index e10025b7f3..1c8d078204 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step13_Plugins/README.md
@@ -13,13 +13,13 @@ This sample shows how to use plugins with a `ChatClientAgent` using the Response
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Program.cs
index c4661ae49e..9dec5a9eca 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/Program.cs
@@ -12,8 +12,8 @@ using OpenAI.Assistants;
const string AgentInstructions = "You are a personal math tutor. When asked a math question, write and run code using the python tool to answer the question.";
const string AgentName = "CoderAgent-RAPI";
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/README.md
index db63f82e9c..e339acb934 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/README.md
@@ -12,13 +12,13 @@ This sample shows how to use the Code Interpreter tool with a `ChatClientAgent`
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Program.cs
index 00e4e02843..865dfac806 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/Program.cs
@@ -10,9 +10,12 @@ using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_COMPUTER_USE_DEPLOYMENT_NAME") ?? "computer-use-preview";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIProjectClient projectClient = new(new Uri(endpoint), new DefaultAzureCredential());
using IHostedFileClient fileClient = projectClient.GetProjectOpenAIClient().AsIHostedFileClient();
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/README.md
index eee05e2a69..85ceb4c415 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step15_ComputerUse/README.md
@@ -1,4 +1,4 @@
-# Computer Use with the Responses API
+# Computer Use with the Responses API
This sample shows how to use the Computer Use tool with `AIProjectClient.AsAIAgent(...)`.
@@ -39,12 +39,12 @@ The model receives a screenshot as input, analyzes it, and responds with a compu
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
$env:AZURE_AI_COMPUTER_USE_DEPLOYMENT_NAME="computer-use-preview"
```
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Program.cs
index 1a2d870342..adb3376993 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/Program.cs
@@ -9,8 +9,8 @@ using Microsoft.Extensions.AI;
using OpenAI.Assistants;
using OpenAI.Files;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
const string AgentInstructions = "You are a helpful assistant that can search through uploaded files to answer questions.";
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/README.md
index 45818ca354..6009492113 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step16_FileSearch/README.md
@@ -13,13 +13,13 @@ This sample shows how to use the File Search tool with a `ChatClientAgent` using
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Program.cs
index d47f4b0078..6c6ba4b111 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/Program.cs
@@ -9,8 +9,8 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
const string AgentInstructions = "You are a helpful assistant that can use the countries API to retrieve information about countries by their currency code.";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/README.md
index 05227fdd98..0bc08c21f2 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step17_OpenAPITools/README.md
@@ -13,13 +13,13 @@ This sample shows how to use OpenAPI tools with a `ChatClientAgent` using the Re
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Program.cs
index 11048c8154..e3ad81c95c 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/Program.cs
@@ -21,8 +21,8 @@ BingCustomSearchToolOptions bingCustomSearchToolParameters = new([
new BingCustomSearchConfiguration(connectionId, instanceName)
]);
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/README.md
index 18bffeacd6..8174ca32f8 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step18_BingCustomSearch/README.md
@@ -12,14 +12,14 @@ This sample shows how to use the Bing Custom Search tool with a `ChatClientAgent
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
- Bing Custom Search resource configured with a connection ID
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
$env:AZURE_AI_CUSTOM_SEARCH_CONNECTION_ID="your-connection-id" # The full ARM resource URI, e.g., "/subscriptions/.../connections/your-bing-connection"
$env:AZURE_AI_CUSTOM_SEARCH_INSTANCE_NAME="your-instance-name" # The Bing Custom Search configuration name (from Azure portal)
```
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Program.cs
index 186acb9da5..9abb8d5e78 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/Program.cs
@@ -19,8 +19,8 @@ const string AgentInstructions = """
var sharepointOptions = new SharePointGroundingToolOptions();
sharepointOptions.ProjectConnections.Add(new ToolProjectConnection(sharepointConnectionId));
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/README.md
index 1049eb4ebc..12d58cf6c1 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step19_SharePoint/README.md
@@ -12,14 +12,14 @@ This sample shows how to use the SharePoint Grounding tool with a `ChatClientAge
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
- SharePoint connection configured in your Microsoft Foundry project
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
$env:SHAREPOINT_PROJECT_CONNECTION_ID="your-sharepoint-connection-id" # The full ARM resource URI, e.g., "/subscriptions/.../connections/SharepointTestTool"
```
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Program.cs
index ccc3c0dcf0..1d1e27fa5b 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/Program.cs
@@ -16,8 +16,8 @@ const string AgentInstructions = "You are a helpful assistant with access to Mic
var fabricToolOptions = new FabricDataAgentToolOptions();
fabricToolOptions.ProjectConnections.Add(new ToolProjectConnection(fabricConnectionId));
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/README.md
index 03536262d2..0d848ca1e5 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step20_MicrosoftFabric/README.md
@@ -12,14 +12,14 @@ This sample shows how to use the Microsoft Fabric tool with a `ChatClientAgent`
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
- Microsoft Fabric connection configured in your Microsoft Foundry project
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
$env:FABRIC_PROJECT_CONNECTION_ID="your-fabric-connection-id" # The full ARM resource URI, e.g., "/subscriptions/.../connections/FabricTestTool"
```
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Program.cs
index da1652536b..509d2dfdea 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/Program.cs
@@ -11,8 +11,8 @@ using OpenAI.Responses;
const string AgentInstructions = "You are a helpful assistant that can search the web to find current information and answer questions accurately.";
const string AgentName = "WebSearchAgent-RAPI";
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/README.md
index 81d37e6ff5..3b4ea1b429 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step21_WebSearch/README.md
@@ -12,13 +12,13 @@ This sample shows how to use the Web Search tool with a `ChatClientAgent` using
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Program.cs
index 5ba9ccedb1..908d03f7bd 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/Program.cs
@@ -14,8 +14,8 @@ using Microsoft.Agents.AI.Foundry;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
string embeddingModelName = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002";
string memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? $"foundry-memory-sample-{Guid.NewGuid():N}";
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/README.md
index 63af9cd9c8..b6293eb674 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step22_MemorySearch/README.md
@@ -13,14 +13,14 @@ This sample demonstrates how to use the Memory Search tool with a `ChatClientAge
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
- A memory store created beforehand via Azure Portal or Python SDK
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
$env:AZURE_AI_MEMORY_STORE_ID="your-memory-store-name"
```
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Program.cs
index 772d1a17f9..89d5b0029b 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/Program.cs
@@ -30,8 +30,8 @@ Console.WriteLine($"MCP tools available: {string.Join(", ", mcpTools.Select(t =>
// Wrap each MCP tool with a DelegatingAIFunction to log local invocations.
List wrappedTools = mcpTools.Select(tool => (AITool)new LoggingMcpTool(tool)).ToList();
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/README.md
index c3464efe5d..3c37a9e50a 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step23_LocalMCP/README.md
@@ -13,13 +13,13 @@ This sample demonstrates how to use a local MCP (Model Context Protocol) client
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/Program.cs
index 79fac0d5d4..3d33c91c9d 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/Program.cs
@@ -12,8 +12,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/README.md
index 4d50b98ca8..a74123d711 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/README.md
@@ -35,13 +35,13 @@ The container ID and file ID are available from the `ContainerFileCitationMessag
- .NET 10 SDK or later
- Microsoft Foundry service endpoint and deployment configured
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
```
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/Program.cs
index 01a1f36d1b..e611c34502 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/Program.cs
@@ -24,9 +24,9 @@ using OpenAI.Responses;
const string ToolboxName = "research_toolbox";
const string Query = "What tools do you have access to?";
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
TokenCredential credential = new DefaultAzureCredential();
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/README.md
index 8a9d22e28a..3120647778 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_FoundryToolboxMcp/README.md
@@ -12,18 +12,18 @@ This sample shows how to use a Foundry Toolbox by pointing an `McpClient` at the
## Prerequisites
- A Microsoft Foundry project with a toolbox configured (or let the sample create one for you)
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
The sample creates a toolbox named `research_toolbox` in your Foundry project on
startup, then connects to its MCP endpoint at
-`{AZURE_AI_PROJECT_ENDPOINT}/toolboxes/research_toolbox/mcp?api-version=v{version}`.
+`{FOUNDRY_PROJECT_ENDPOINT}/toolboxes/research_toolbox/mcp?api-version=v{version}`.
## Run the sample
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs
index 6efb8ce40e..d8fff025c5 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs
@@ -14,9 +14,9 @@ using Microsoft.Agents.AI;
using ModelContextProtocol.Client;
// --- Configuration ---
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
string toolboxMcpServerUrl = Environment.GetEnvironmentVariable("FOUNDRY_TOOLBOX_MCP_SERVER_URL")
?? throw new InvalidOperationException("FOUNDRY_TOOLBOX_MCP_SERVER_URL is not set.");
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/README.md
index 2e8efef8cc..fc7ff655cc 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step26_FoundryToolboxMcpSkills/README.md
@@ -15,13 +15,13 @@ and inject them as `AIContextProviders` so the agent can discover and use them a
- A Microsoft Foundry project with a toolbox already configured
- The toolbox MCP endpoint must expose `skill://index.json` with `skill-md` entries (SEP-2640). If the resource is absent, the sample runs but the skills provider will be empty.
-- Azure CLI installed and authenticated (`az login`)
+- An authenticated Azure identity (for example, sign in with `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
$env:FOUNDRY_TOOLBOX_MCP_SERVER_URL="https://your-foundry-service.services.ai.azure.com/api/projects/your-project/toolboxes/your-toolbox/mcp?api-version=v1"
```
diff --git a/dotnet/samples/02-agents/AgentsWithFoundry/README.md b/dotnet/samples/02-agents/AgentsWithFoundry/README.md
index dda1e476f0..e90f92c2a2 100644
--- a/dotnet/samples/02-agents/AgentsWithFoundry/README.md
+++ b/dotnet/samples/02-agents/AgentsWithFoundry/README.md
@@ -4,12 +4,12 @@ These samples demonstrate how to use Microsoft Foundry with Agent Framework.
## Quick start
-The simplest way to create a Foundry agent is using the `FoundryAgent` type directly:
+You can create a Foundry agent directly with the `FoundryAgent` type:
```csharp
FoundryAgent agent = new(
new Uri(endpoint),
- new AzureCliCredential(),
+ new DefaultAzureCredential(),
model: "gpt-5.4-mini",
instructions: "You are good at telling jokes.",
name: "JokerAgent");
@@ -32,13 +32,13 @@ FoundryAgent agent = aiProjectClient.AsAIAgent(
- .NET 10 SDK or later
- Foundry project endpoint
-- Azure CLI installed and authenticated
+- An authenticated Azure identity (for example, sign in with `az login`)
Set:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-5.4-mini"
```
Some samples require extra tool-specific environment variables. See each sample for details.
@@ -78,7 +78,11 @@ Some samples require extra tool-specific environment variables. See each sample
## Running the samples
+Use the basics sample for a quick smoke test:
+
```powershell
cd dotnet/samples/02-agents/AgentsWithFoundry
-dotnet run --project .\FoundryAgent_Step01
-```
\ No newline at end of file
+dotnet run --project .\Agent_Step01_Basics
+```
+
+If you want to exercise the full create-run-delete lifecycle, run `Agent_Step00_FoundryAgentLifecycle`.
diff --git a/dotnet/samples/02-agents/Evaluation/Evaluation_CustomEvals/Program.cs b/dotnet/samples/02-agents/Evaluation/Evaluation_CustomEvals/Program.cs
index a5fa9cc945..0c263d1620 100644
--- a/dotnet/samples/02-agents/Evaluation/Evaluation_CustomEvals/Program.cs
+++ b/dotnet/samples/02-agents/Evaluation/Evaluation_CustomEvals/Program.cs
@@ -8,8 +8,8 @@ using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/Evaluation/Evaluation_CustomEvals/README.md b/dotnet/samples/02-agents/Evaluation/Evaluation_CustomEvals/README.md
index da4c9c652f..691537efeb 100644
--- a/dotnet/samples/02-agents/Evaluation/Evaluation_CustomEvals/README.md
+++ b/dotnet/samples/02-agents/Evaluation/Evaluation_CustomEvals/README.md
@@ -1,4 +1,4 @@
-# Evaluation - Custom Evals
+# Evaluation - Custom Evals
This sample demonstrates writing custom domain-specific evaluation functions using `FunctionEvaluator.Create`. Custom evaluators run locally with no cloud evaluator service needed — useful for enforcing business rules, format requirements, or safety guardrails.
@@ -13,13 +13,13 @@ This sample demonstrates writing custom domain-specific evaluation functions usi
## Prerequisites
- .NET 10 SDK or later
-- Azure CLI installed and authenticated (`az login`)
+- Azure authentication available to `DefaultAzureCredential` (for local development, run `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/Program.cs b/dotnet/samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/Program.cs
index 96f41bd835..8a9de240be 100644
--- a/dotnet/samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/Program.cs
+++ b/dotnet/samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/Program.cs
@@ -6,10 +6,13 @@ using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
// Create a math tutor agent.
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
.AsAIAgent(
model: deploymentName,
diff --git a/dotnet/samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/README.md b/dotnet/samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/README.md
index 34f16865d2..1e82cda2e5 100644
--- a/dotnet/samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/README.md
+++ b/dotnet/samples/02-agents/Evaluation/Evaluation_ExpectedOutputs/README.md
@@ -1,4 +1,4 @@
-# Evaluation - Expected Outputs
+# Evaluation - Expected Outputs
This sample demonstrates evaluating agent responses against expected outputs using built-in checks.
@@ -16,8 +16,8 @@ This sample demonstrates evaluating agent responses against expected outputs usi
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini"
```
## Run the sample
diff --git a/dotnet/samples/02-agents/Evaluation/Evaluation_SimpleEval/Program.cs b/dotnet/samples/02-agents/Evaluation/Evaluation_SimpleEval/Program.cs
index f43a1253e7..8a11f5c016 100644
--- a/dotnet/samples/02-agents/Evaluation/Evaluation_SimpleEval/Program.cs
+++ b/dotnet/samples/02-agents/Evaluation/Evaluation_SimpleEval/Program.cs
@@ -10,8 +10,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI.Evaluation;
using FoundryEvals = Microsoft.Agents.AI.Foundry.FoundryEvals;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/02-agents/Evaluation/Evaluation_SimpleEval/README.md b/dotnet/samples/02-agents/Evaluation/Evaluation_SimpleEval/README.md
index 35bb11c3bd..d130661fae 100644
--- a/dotnet/samples/02-agents/Evaluation/Evaluation_SimpleEval/README.md
+++ b/dotnet/samples/02-agents/Evaluation/Evaluation_SimpleEval/README.md
@@ -1,4 +1,4 @@
-# Evaluation - Simple Eval
+# Evaluation - Simple Eval
The simplest agent evaluation: create a Foundry agent, run it against test questions, and use Foundry quality evaluators (Relevance, Coherence) to score the responses.
@@ -11,14 +11,14 @@ The simplest agent evaluation: create a Foundry agent, run it against test quest
## Prerequisites
- .NET 10 SDK or later
-- Azure CLI installed and authenticated (`az login`)
+- Azure authentication available to `DefaultAzureCredential` (for local development, run `az login`)
- A deployed model in your Azure AI Foundry project
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini"
```
## Run the sample
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 a1d8aca1b8..904a4ab7e4 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step01_Research/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step01_Research/Program.cs
@@ -25,8 +25,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using SampleApp;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4";
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4";
const int MaxContextWindowTokens = 1_050_000;
const int MaxOutputTokens = 128_000;
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step01_Research/README.md b/dotnet/samples/02-agents/Harness/Harness_Step01_Research/README.md
index 7adb0a311f..9511178cec 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step01_Research/README.md
+++ b/dotnet/samples/02-agents/Harness/Harness_Step01_Research/README.md
@@ -1,4 +1,4 @@
-# What this sample demonstrates
+# What this sample demonstrates
This sample demonstrates how to use a `HarnessAgent` with the Harness `AIContextProviders` (`TodoProvider` and `AgentModeProvider`) for interactive research tasks with web search capabilities powered by Azure AI Foundry. The `HarnessAgent` pre-configures function invocation, per-service-call chat history persistence, and context-window compaction.
@@ -30,7 +30,7 @@ Set the following environment variables:
export AZURE_FOUNDRY_OPENAI_ENDPOINT="https://your-project.services.ai.azure.com/openai/v1/"
# Optional: Model deployment name (defaults to gpt-5.4)
-export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4"
+export FOUNDRY_MODEL="gpt-5.4"
```
## Running the Sample
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 654c169850..6c372f8ed1 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
@@ -20,8 +20,8 @@ using Harness.Shared.Console.OpenAI;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4";
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4";
const int MaxContextWindowTokens = 1_050_000;
const int MaxOutputTokens = 128_000;
@@ -30,6 +30,9 @@ const string TracingSourceName = "Harness.SubAgents";
// Set up OpenTelemetry tracing that writes spans to a text file.
using var tracerProvider = HarnessTracing.CreateFileTracerProvider(TracingSourceName);
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// 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 AIProjectClient for communicating with the Foundry responses service.
var projectClient = new AIProjectClient(
new Uri(endpoint),
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/README.md b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/README.md
index c04f68d13c..4b61a77b76 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/README.md
+++ b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/README.md
@@ -1,4 +1,4 @@
-# Harness Step 02 — BackgroundAgents (Stock Price Research)
+# Harness Step 02 — BackgroundAgents (Stock Price Research)
This sample demonstrates how to use the **BackgroundAgentsProvider** to delegate work from a parent agent to background agents. Both agents use `HarnessAgent` for pre-configured function invocation, per-service-call persistence, and context-window compaction.
@@ -35,7 +35,7 @@ A parent agent receives a list of stock tickers and uses a web-search background
- An Azure AI Foundry endpoint with an OpenAI model deployment
- Set the following environment variables:
- `AZURE_FOUNDRY_OPENAI_ENDPOINT` — Your Foundry OpenAI endpoint URL
- - `AZURE_AI_MODEL_DEPLOYMENT_NAME` — Model deployment name (defaults to `gpt-5.4`)
+ - `FOUNDRY_MODEL` — Model deployment name (defaults to `gpt-5.4`)
## Running the Sample
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 6b88d708f2..e2f9b46307 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/Program.cs
@@ -22,8 +22,8 @@ using Harness.Shared.Console;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4";
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4";
const int MaxContextWindowTokens = 1_050_000;
const int MaxOutputTokens = 128_000;
@@ -57,6 +57,9 @@ var instructions =
- Always explain what you learned and what you are going to do next between tool calls, so the user can follow along with your thought process.
""";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// 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 using AsHarnessAgent. The FileAccessStore is explicitly set to the
// sample's working/ folder (copied to the output directory) so it works regardless of cwd.
// Unused features are disabled.
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 a9d6cba384..d06348fa72 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/README.md
+++ b/dotnet/samples/02-agents/Harness/Harness_Step03_DataProcessing/README.md
@@ -1,4 +1,4 @@
-# What this sample demonstrates
+# 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.
@@ -27,7 +27,7 @@ Set the following environment variables:
export AZURE_FOUNDRY_OPENAI_ENDPOINT="https://your-project.services.ai.azure.com/openai/v1/"
# Optional: Model deployment name (defaults to gpt-5.4)
-export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4"
+export FOUNDRY_MODEL="gpt-5.4"
```
## Running the Sample
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 908e43abd7..46f4c32c81 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/Program.cs
@@ -27,8 +27,8 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hyperlight;
using Microsoft.Extensions.AI;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4";
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4";
const int MaxContextWindowTokens = 1_050_000;
const int MaxOutputTokens = 128_000;
@@ -78,6 +78,9 @@ var instructions =
- If applicable, save final results to file memory.
""";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// 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.
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/README.md b/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/README.md
index 0d1b109bee..daebc0a19b 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/README.md
+++ b/dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution/README.md
@@ -1,4 +1,4 @@
-# Harness Step 04 — Code Execution (Hyperlight + Skills)
+# Harness Step 04 — Code Execution (Hyperlight + Skills)
This sample demonstrates a HarnessAgent with **all features enabled**, plus:
@@ -17,8 +17,8 @@ The agent can plan tasks, manage modes, store memories, read/write files, search
| Variable | Description |
|----------|-------------|
-| `AZURE_AI_PROJECT_ENDPOINT` | Your Azure AI Foundry project endpoint |
-| `AZURE_AI_MODEL_DEPLOYMENT_NAME` | Model deployment name (default: `gpt-5.4`) |
+| `FOUNDRY_PROJECT_ENDPOINT` | Your Azure AI Foundry project endpoint |
+| `FOUNDRY_MODEL` | Model deployment name (default: `gpt-5.4`) |
## Running
diff --git a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj b/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Harness_Step05_Loop.csproj
similarity index 65%
rename from dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj
rename to dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Harness_Step05_Loop.csproj
index d40e93232b..f5d6f368b6 100644
--- a/dotnet/samples/02-agents/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj
+++ b/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Harness_Step05_Loop.csproj
@@ -9,12 +9,13 @@
-
+
-
+
+
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Program.cs b/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Program.cs
new file mode 100644
index 0000000000..1e19dabc22
--- /dev/null
+++ b/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/Program.cs
@@ -0,0 +1,272 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates how to wrap a HarnessAgent with the LoopAgent decorator to re-invoke
+// the agent until a configured LoopEvaluator decides to stop. It covers the common looping patterns
+// through one decorator, each driven by a different evaluator:
+//
+// 1. Completion-marker (Ralph-style) loop — keep refining until the agent emits a completion
+// marker, restarting each pass from a fresh context (CompletionMarkerLoopEvaluator +
+// FreshContextPerIteration).
+// 2. Delegate predicate (todos remaining) — loop while the built-in TodoProvider still has open
+// items (DelegateLoopEvaluator).
+// 3. AI judge — a second chat client decides whether the original request was answered, and the
+// loop continues while the answer is "no" (AIJudgeLoopEvaluator).
+// 4. Approval heuristics + loop — combine the LoopAgent with the ToolApprovalAgent auto-approval
+// heuristics so a looped agent auto-approves tool calls instead of stalling on approval.
+//
+// The demos run sequentially and print each loop's final response.
+
+#pragma warning disable OPENAI001 // Suppress experimental API warnings for Responses API usage.
+#pragma warning disable MAAI001 // Suppress experimental API warnings for Agents AI experiments.
+
+using System.ClientModel.Primitives;
+using System.ComponentModel;
+using Azure.AI.Projects;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4";
+
+// The HarnessAgent pre-configures function invocation, per-service-call chat history persistence, and
+// context-window compaction. These bounds size the in-loop compaction window.
+const int MaxContextWindowTokens = 1_050_000;
+const int MaxOutputTokens = 32_000;
+
+// Build a single Foundry-backed IChatClient factory shared by every demo. Each call returns a fresh
+// IChatClient over the same Responses endpoint.
+var projectClient = new AIProjectClient(
+ new Uri(endpoint),
+ // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+ // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+ // latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+ new DefaultAzureCredential(),
+ new AIProjectClientOptions { RetryPolicy = new ClientRetryPolicy(3) });
+
+IChatClient CreateChatClient() =>
+ projectClient.GetProjectOpenAIClient().GetResponsesClient().AsIChatClient(deploymentName);
+
+await RalphLoopAsync();
+await TodoLoopAsync();
+await JudgeLoopAsync();
+await ApprovalLoopAsync();
+
+// Pattern 1: a "Ralph"-style loop that refines until the agent signals completion.
+async Task RalphLoopAsync()
+{
+ Console.WriteLine("\n=== 1. Completion-marker (Ralph-style) loop — refine until COMPLETE (max 5) ===");
+
+ // Build a lean HarnessAgent: no todo or mode providers for this iterative-refinement task.
+ AIAgent harnessAgent = CreateLeanHarnessAgent(
+ name: "ralph",
+ instructions:
+ """
+ You are iteratively refining a product name for a note-taking app. Each turn, build on the
+ feedback so far: propose an improved candidate with a short reason. When you are confident the
+ name is final, end your message with the exact marker COMPLETE.
+ """);
+
+ // CompletionMarkerLoopEvaluator stops once the marker appears in the response; until then it
+ // re-invokes the agent. FreshContextPerIteration restarts each pass from the original task plus the
+ // aggregated feedback log on a brand-new session. Because each pass starts fresh, the agent has no
+ // memory of its prior suggestion — so the feedback template includes the {last_response} placeholder
+ // to echo the previous candidate back to it.
+ AIAgent loopAgent = new LoopAgent(
+ harnessAgent,
+ new CompletionMarkerLoopEvaluator("COMPLETE", options: new()
+ {
+ FeedbackMessageTemplate =
+ "Your previous suggestion was:\n" + CompletionMarkerLoopEvaluator.LastResponsePlaceholder +
+ "\n\nContinue to refine the name and remember to reply with " +
+ CompletionMarkerLoopEvaluator.CompletionMarkerPlaceholder + " when happy.",
+ }),
+ new LoopAgentOptions { MaxIterations = 5, FreshContextPerIteration = true });
+
+ AgentResponse response = await StreamLoopAsync(loopAgent, "Suggest a name for a note-taking app.");
+ Console.WriteLine($"\nFinal response:\n{response.Text}");
+}
+
+// Pattern 2: loop while the built-in TodoProvider still has open items.
+async Task TodoLoopAsync()
+{
+ Console.WriteLine("\n=== 2. Delegate predicate — loop while todos remain (max 6) ===");
+
+ // Keep the built-in TodoProvider enabled (only the mode provider is disabled) so the agent has
+ // todo tools to plan and track work.
+ AIAgent harnessAgent = CreateLeanHarnessAgent(
+ name: "planner",
+ instructions:
+ """
+ You are a planning assistant. First break the task into todo items using your todo tools.
+ Then, on each turn, make progress and mark completed items as done. When all items are
+ complete, summarize the result.
+ """,
+ disableTodoProvider: false);
+
+ // The predicate re-invokes the agent while any todo item is still open. The evaluator fetches the
+ // built-in TodoProvider from context.Agent (via GetService, which forwards through the harness
+ // decorators to the underlying ChatClientAgent's context providers), keeping the delegate
+ // self-contained, then queries it against the loop's current session. When items remain, it returns
+ // feedback telling the agent to finish them. MaxIterations guarantees the loop stops even if the
+ // agent stalls.
+ AIAgent loopAgent = new LoopAgent(
+ harnessAgent,
+ new DelegateLoopEvaluator(async (context, cancellationToken) =>
+ {
+ var todoProvider = context.Agent.GetService()
+ ?? throw new InvalidOperationException("The agent did not expose a TodoProvider.");
+ var remaining = await todoProvider.GetRemainingTodosAsync(context.Session).ConfigureAwait(false);
+ return remaining.Count > 0
+ ? LoopEvaluation.Continue($"Not all todos are complete yet ({remaining.Count} remaining). Please complete the remaining todo items.")
+ : LoopEvaluation.Stop();
+ }),
+ new LoopAgentOptions { MaxIterations = 6 });
+
+ // The LoopAgent creates a single session up front and reuses it across iterations (non-fresh
+ // mode), so the todo state persists; the predicate reads it via context.Session.
+ AgentResponse response = await StreamLoopAsync(
+ loopAgent,
+ "Plan and outline a 3-section blog post about Rayleigh scattering.");
+ Console.WriteLine($"\nFinal response:\n{response.Text}");
+}
+
+// Pattern 3: a second chat client judges whether the original request was answered.
+async Task JudgeLoopAsync()
+{
+ Console.WriteLine("\n=== 3. AI judge — loop until the request is answered (max 4) ===");
+
+ AIAgent harnessAgent = CreateLeanHarnessAgent(
+ name: "answerer",
+ instructions: "You are a helpful assistant. Answer the user's question thoroughly.");
+
+ // The judge uses its own IChatClient. AIJudgeLoopEvaluator asks it (via a JudgeVerdict structured
+ // output) whether the original request has been fully addressed and continues while the answer is
+ // "no", injecting the judge's gap analysis as the next iteration's input. Judge loops use a small
+ // MaxIterations cap because each pass costs an extra model call.
+ AIAgent loopAgent = new LoopAgent(
+ harnessAgent,
+ new AIJudgeLoopEvaluator(CreateChatClient()),
+ new LoopAgentOptions { MaxIterations = 4 });
+
+ AgentResponse response = await StreamLoopAsync(
+ loopAgent,
+ "Explain why the sky is blue, then also explain why sunsets are red.");
+ Console.WriteLine($"\nFinal response:\n{response.Text}");
+}
+
+// Pattern 4: combine the loop with the ToolApprovalAgent auto-approval heuristics.
+async Task ApprovalLoopAsync()
+{
+ Console.WriteLine("\n=== 4. Approval heuristics + loop — auto-approve tool calls in the loop (max 2) ===");
+
+ var deployTool = new ApprovalRequiredAIFunction(
+ AIFunctionFactory.Create(DeploymentTools.DeployService));
+
+ // Configure the HarnessAgent's built-in ToolApprovalAgent with an auto-approval rule. The rule
+ // approves the deploy_service call without prompting, so the inner agent resolves the approval
+ // internally and never surfaces a pending approval to the LoopAgent — letting the loop proceed.
+ AIAgent harnessAgent = CreateLeanHarnessAgent(
+ name: "operator",
+ instructions: "You are a deployment operator. Use the DeployService tool to fulfil requests.",
+ tools: [deployTool],
+ toolApprovalAgentOptions: new ToolApprovalAgentOptions
+ {
+ AutoApprovalRules =
+ [
+ functionCall =>
+ {
+ Console.WriteLine($" Auto-approving: {functionCall.Name}");
+ return ValueTask.FromResult(true);
+ },
+ ],
+ });
+
+ // Drive a short loop that continues until the response confirms the deployment.
+ AIAgent loopAgent = new LoopAgent(
+ harnessAgent,
+ new DelegateLoopEvaluator((context, _) =>
+ new ValueTask(
+ context.LastResponse.Text.Contains("deployed", StringComparison.OrdinalIgnoreCase)
+ ? LoopEvaluation.Stop()
+ : LoopEvaluation.Continue())),
+ new LoopAgentOptions { MaxIterations = 2 });
+
+ // The LoopAgent reuses a single session across iterations, so the approval response flows back in.
+ AgentResponse response = await StreamLoopAsync(loopAgent, "Deploy the billing service.");
+ Console.WriteLine($"\nFinal response:\n{response.Text}");
+}
+
+// Streams a loop run to the console, printing updates live and marking each new inner run (detected
+// via a change in ResponseId) with an "--- run N ---" header so you can see when the LoopAgent
+// re-invokes the inner agent. Each message is prefixed with "User:" or "Agent:" based on its role, so
+// the loop's on-behalf-of feedback (User) is visually distinct from the agent's responses (Agent).
+// Returns the aggregated final response.
+static async Task StreamLoopAsync(AIAgent loopAgent, string input, AgentSession? session = null)
+{
+ string? currentResponseId = null;
+ ChatRole? currentRole = null;
+ var runCount = 0;
+ var updates = new List();
+
+ await foreach (var update in loopAgent.RunStreamingAsync(input, session))
+ {
+ // A new ResponseId signals the start of another inner run (loop iteration).
+ if (update.ResponseId is { } responseId && responseId != currentResponseId)
+ {
+ currentResponseId = responseId;
+ currentRole = null;
+ Console.WriteLine($"\n--- run {++runCount} ---");
+ }
+
+ // Print a role-based prefix whenever the speaker changes — for example the loop's on-behalf-of
+ // user feedback versus the agent's response.
+ if (update.Role is { } role && role != currentRole)
+ {
+ currentRole = role;
+ var prefix = role == ChatRole.User ? "User" : role == ChatRole.Assistant ? "Agent" : role.Value;
+ Console.Write($"\n{prefix}: ");
+ }
+
+ Console.Write(update.Text);
+ updates.Add(update);
+ }
+
+ Console.WriteLine();
+ return updates.ToAgentResponse();
+}
+
+// Creates a HarnessAgent with the agent-mode provider always disabled (and the todo provider disabled
+// by default), plus all other heavyweight providers turned off so each loop demo stays focused.
+AIAgent CreateLeanHarnessAgent(
+ string name,
+ string instructions,
+ bool disableTodoProvider = true,
+ IList? tools = null,
+ ToolApprovalAgentOptions? toolApprovalAgentOptions = null) =>
+ CreateChatClient().AsHarnessAgent(new HarnessAgentOptions
+ {
+ Name = name,
+ MaxContextWindowTokens = MaxContextWindowTokens,
+ MaxOutputTokens = MaxOutputTokens,
+ DisableAgentModeProvider = true,
+ DisableTodoProvider = disableTodoProvider,
+ DisableFileMemory = true,
+ DisableFileAccess = true,
+ DisableWebSearch = true,
+ ToolApprovalAgentOptions = toolApprovalAgentOptions,
+ ChatOptions = new ChatOptions
+ {
+ Instructions = instructions,
+ Tools = tools,
+ MaxOutputTokens = MaxOutputTokens,
+ },
+ });
+
+/// Tool used by the approval-handling demo.
+internal static class DeploymentTools
+{
+ [Description("Deploy a service to production (requires approval).")]
+ public static string DeployService([Description("The name of the service to deploy.")] string service) =>
+ $"Deployed {service} to production.";
+}
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/README.md b/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/README.md
new file mode 100644
index 0000000000..4a19f66e57
--- /dev/null
+++ b/dotnet/samples/02-agents/Harness/Harness_Step05_Loop/README.md
@@ -0,0 +1,59 @@
+# What this sample demonstrates
+
+This sample demonstrates how to wrap a `HarnessAgent` with the **`LoopAgent`** decorator to re-invoke the agent until a configured **`LoopEvaluator`** decides to stop. A single decorator covers the common looping patterns — you just plug in a different evaluator (and optionally switch on fresh-context mode).
+
+The `HarnessAgent` pre-configures function invocation, per-service-call chat history persistence, and in-loop compaction, so each demo only supplies the chat client, token limits, and instructions, then wraps the result with a `LoopAgent`.
+
+## Looping patterns showcased
+
+The program runs four demos sequentially, each driven by a different evaluator:
+
+| # | Pattern | Evaluator | Notes |
+| --- | --- | --- | --- |
+| 1 | Completion-marker ("Ralph"-style) loop | `CompletionMarkerLoopEvaluator` | Re-invokes until the agent emits `COMPLETE`. Uses `FreshContextPerIteration = true` to restart each pass from the original task plus the aggregated feedback log on a new session, and includes the `{last_response}` placeholder in the feedback template so the agent sees its previous suggestion even though each pass starts fresh. |
+| 2 | Delegate predicate (todos remaining) | `DelegateLoopEvaluator` | Loops while the built-in `TodoProvider` still has open items. The provider is fetched from the agent via `GetService()` and queried against the loop's current session. |
+| 3 | AI judge | `AIJudgeLoopEvaluator` | A second `IChatClient` judges whether the original request was fully answered and continues while the answer is "no", injecting its gap analysis as the next input. |
+| 4 | Approval heuristics + loop | `DelegateLoopEvaluator` + `ToolApprovalAgent` | Combines the `ToolApprovalAgent` auto-approval heuristics (`AutoApprovalRules`) with the loop, so a looped agent auto-approves tool calls instead of stalling on a pending approval. |
+
+`MaxIterations` caps every loop so it always terminates even if the evaluator never stops.
+
+### Evaluator mapping (Python → .NET)
+
+The Python sample in [microsoft/agent-framework#6174](https://github.com/microsoft/agent-framework/pull/6174) exposes several distinct loop classes. In .NET these collapse into one `LoopAgent` that consumes evaluators:
+
+| Python | .NET |
+| --- | --- |
+| Ralph loop (completion marker) | `LoopAgent` + `CompletionMarkerLoopEvaluator` |
+| Ralph loop (fresh context each pass) | `LoopAgent` + `CompletionMarkerLoopEvaluator` + `FreshContextPerIteration = true` |
+| Callable / predicate loop | `LoopAgent` + `DelegateLoopEvaluator` |
+| AI judge loop | `LoopAgent` + `AIJudgeLoopEvaluator` |
+
+## Prerequisites
+
+Before running this sample, ensure you have:
+
+1. An Azure AI Foundry project with a deployed model (e.g., `gpt-5.4`)
+2. Azure CLI installed and authenticated (`az login`)
+
+## Environment Variables
+
+Set the following environment variables:
+
+```bash
+# Required: Your Azure AI Foundry project endpoint
+export AZURE_AI_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/api/projects/your-project"
+
+# Optional: Model deployment name (defaults to gpt-5.4)
+export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4"
+```
+
+## Running the Sample
+
+```bash
+cd dotnet
+dotnet run --project samples/02-agents/Harness/Harness_Step05_Loop
+```
+
+## What to Expect
+
+The program runs the four demos in order. Each loop is executed with `RunStreamingAsync`, so output is printed live and every re-invocation of the inner agent is marked with a `--- run N ---` header (detected via a change in the streamed `ResponseId`) — this lets you see exactly when the `LoopAgent` loops. Each streamed message is prefixed with `User:` or `Agent:` based on its role, so the loop's on-behalf-of feedback messages (surfaced as `User` turns) are visually distinct from the agent's responses (`Agent`). Each demo finishes by printing its aggregated final response. Demo 4 also prints an `Auto-approving: ...` line each time the `ToolApprovalAgent` heuristic approves the `DeployService` tool call, showing how approval-aware agents integrate with the loop.
diff --git a/dotnet/samples/02-agents/Harness/README.md b/dotnet/samples/02-agents/Harness/README.md
index 16fad9ac62..61981827c4 100644
--- a/dotnet/samples/02-agents/Harness/README.md
+++ b/dotnet/samples/02-agents/Harness/README.md
@@ -9,3 +9,4 @@ Samples demonstrating the [Harness AIContextProviders](../../../src/Microsoft.Ag
| [Harness_Step01_Research](./Harness_Step01_Research/README.md) | Using a ChatClientAgent with TodoProvider and AgentModeProvider for research, showcasing planning mode and todo management |
| [Harness_Step02_Research_WithBackgroundAgents](./Harness_Step02_Research_WithBackgroundAgents/README.md) | Using BackgroundAgentsProvider to delegate stock price lookups to a web-search background agent concurrently |
| [Harness_Step03_DataProcessing](./Harness_Step03_DataProcessing/README.md) | Using FileAccessProvider to give an agent access to CSV data files for reading, analysis, and output generation |
+| [Harness_Step05_Loop](./Harness_Step05_Loop/README.md) | Wrapping a HarnessAgent with the LoopAgent decorator to re-invoke it until a configured LoopEvaluator (completion marker, predicate, AI judge, or approval-aware loop) decides to stop |
diff --git a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
index ce27d036f9..7494cc9c0c 100644
--- a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
+++ b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
@@ -11,8 +11,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
-var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var model = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// Get a client to create/retrieve server side agents with.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
diff --git a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
index c1a62a9080..1ef0ca0f17 100644
--- a/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
+++ b/dotnet/samples/02-agents/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
@@ -1,4 +1,4 @@
-# Prerequisites
+# Prerequisites
Before you begin, ensure you have the following prerequisites:
@@ -11,6 +11,6 @@ Before you begin, ensure you have the following prerequisites:
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project" # Replace with your Microsoft Foundry resource endpoint
+$env:FOUNDRY_MODEL="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
```
diff --git a/dotnet/samples/03-workflows/Agents/FoundryAgent/Program.cs b/dotnet/samples/03-workflows/Agents/FoundryAgent/Program.cs
index 91d52398f9..5c2f02f733 100644
--- a/dotnet/samples/03-workflows/Agents/FoundryAgent/Program.cs
+++ b/dotnet/samples/03-workflows/Agents/FoundryAgent/Program.cs
@@ -23,10 +23,13 @@ public static class Program
private static async Task Main()
{
// Set up the Azure AI Project client
- var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
- var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
- var aiProjectClient = new AIProjectClient(new Uri(endpoint), new AzureCliCredential());
+ var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+ var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
+ // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+ // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+ // latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+ var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
// Create agents
AIAgent frenchAgent = await CreateTranslationAgentAsync("French", aiProjectClient, deploymentName);
diff --git a/dotnet/samples/03-workflows/Concurrent/Concurrent/Program.cs b/dotnet/samples/03-workflows/Concurrent/Concurrent/Program.cs
index 38e89653d6..43a04f5da1 100644
--- a/dotnet/samples/03-workflows/Concurrent/Concurrent/Program.cs
+++ b/dotnet/samples/03-workflows/Concurrent/Concurrent/Program.cs
@@ -33,10 +33,13 @@ public static class Program
private static async Task Main()
{
// Set up the Azure AI Project client
- var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
- var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
- var chatClient = new AIProjectClient(new Uri(endpoint), new AzureCliCredential())
+ var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+ var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
+ // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+ // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+ // latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+ var chatClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
.ProjectOpenAIClient.GetChatClient(deploymentName).AsIChatClient();
// Create the executors
diff --git a/dotnet/samples/03-workflows/Declarative/ExecuteCode/Program.cs b/dotnet/samples/03-workflows/Declarative/ExecuteCode/Program.cs
index 67d467266b..0566a5ff55 100644
--- a/dotnet/samples/03-workflows/Declarative/ExecuteCode/Program.cs
+++ b/dotnet/samples/03-workflows/Declarative/ExecuteCode/Program.cs
@@ -17,7 +17,7 @@ namespace Demo.DeclarativeCode;
///
///
/// Configuration
-/// Define AZURE_AI_PROJECT_ENDPOINT as a user-secret or environment variable that
+/// Define FOUNDRY_PROJECT_ENDPOINT as a user-secret or environment variable that
/// points to your Foundry project endpoint.
///
internal sealed class Program
diff --git a/dotnet/samples/03-workflows/Declarative/ExecuteWorkflow/Program.cs b/dotnet/samples/03-workflows/Declarative/ExecuteWorkflow/Program.cs
index f7e4dea673..041b6b10bf 100644
--- a/dotnet/samples/03-workflows/Declarative/ExecuteWorkflow/Program.cs
+++ b/dotnet/samples/03-workflows/Declarative/ExecuteWorkflow/Program.cs
@@ -19,7 +19,7 @@ namespace Demo.DeclarativeWorkflow;
///
///
/// Configuration
-/// Define AZURE_AI_PROJECT_ENDPOINT as a user-secret or environment variable that
+/// Define FOUNDRY_PROJECT_ENDPOINT as a user-secret or environment variable that
/// points to your Foundry project endpoint.
/// Usage
/// Provide the path to the workflow definition file as the first argument.
diff --git a/dotnet/samples/03-workflows/Declarative/HostedWorkflow/Program.cs b/dotnet/samples/03-workflows/Declarative/HostedWorkflow/Program.cs
index a871d233ca..29fc74a62d 100644
--- a/dotnet/samples/03-workflows/Declarative/HostedWorkflow/Program.cs
+++ b/dotnet/samples/03-workflows/Declarative/HostedWorkflow/Program.cs
@@ -21,7 +21,7 @@ namespace Demo.DeclarativeWorkflow;
///
///
/// Configuration
-/// Define AZURE_AI_PROJECT_ENDPOINT as a user-secret or environment variable that
+/// Define FOUNDRY_PROJECT_ENDPOINT as a user-secret or environment variable that
/// points to your Foundry project endpoint.
/// Usage
/// Provide the path to the workflow definition file as the first argument.
diff --git a/dotnet/samples/03-workflows/Declarative/README.md b/dotnet/samples/03-workflows/Declarative/README.md
index 6bd2c85824..1fe87a6a78 100644
--- a/dotnet/samples/03-workflows/Declarative/README.md
+++ b/dotnet/samples/03-workflows/Declarative/README.md
@@ -18,8 +18,8 @@ The configuraton required by the samples is:
|Setting Name| Description|
|:--|:--|
-|AZURE_AI_PROJECT_ENDPOINT| The endpoint URL of your Microsoft Foundry Project.|
-|AZURE_AI_MODEL_DEPLOYMENT_NAME| The name of the model deployment to use
+|FOUNDRY_PROJECT_ENDPOINT| The endpoint URL of your Microsoft Foundry Project.|
+|FOUNDRY_MODEL| The name of the model deployment to use
|AZURE_AI_BING_CONNECTION_ID| The name of the Bing Grounding connection configured in your Microsoft Foundry Project.|
To set your secrets with .NET Secret Manager:
@@ -45,13 +45,13 @@ To set your secrets with .NET Secret Manager:
4. Define setting that identifies your Microsoft Foundry Project (endpoint):
```
- dotnet user-secrets set "AZURE_AI_PROJECT_ENDPOINT" "https://..."
+ dotnet user-secrets set "FOUNDRY_PROJECT_ENDPOINT" "https://..."
```
5. Define setting that identifies your Microsoft Foundry Model Deployment (endpoint):
```
- dotnet user-secrets set "AZURE_AI_MODEL_DEPLOYMENT_NAME" "gpt-5"
+ dotnet user-secrets set "FOUNDRY_MODEL" "gpt-5"
```
6. Define setting that identifies your Bing Grounding connection:
@@ -63,8 +63,8 @@ To set your secrets with .NET Secret Manager:
You may alternatively set your secrets as an environment variable (PowerShell):
```pwsh
-$env:AZURE_AI_PROJECT_ENDPOINT="https://..."
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://..."
+$env:FOUNDRY_MODEL="gpt-5"
$env:AZURE_AI_BING_CONNECTION_ID="mybinggrounding"
```
@@ -96,4 +96,4 @@ To run the sampes from the command line:
dotnet run "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours."
dotnet run c:/myworkflows/Marketing.yaml
```
- > The sample will allow for interactive input in the absence of an input argument.
\ No newline at end of file
+ > The sample will allow for interactive input in the absence of an input argument.
diff --git a/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowEval/Program.cs b/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowEval/Program.cs
index ce37dd89f6..6755074d7f 100644
--- a/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowEval/Program.cs
+++ b/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowEval/Program.cs
@@ -8,10 +8,13 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
// Create two agents: a planner and an executor.
diff --git a/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowEval/README.md b/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowEval/README.md
index 7a550f8833..64dfd72e7b 100644
--- a/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowEval/README.md
+++ b/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowEval/README.md
@@ -1,4 +1,4 @@
-# Evaluation - Workflow Eval
+# Evaluation - Workflow Eval
This sample demonstrates evaluating a multi-agent workflow with per-agent breakdown.
@@ -13,13 +13,13 @@ This sample demonstrates evaluating a multi-agent workflow with per-agent breakd
## Prerequisites
- .NET 10 SDK or later
-- Azure CLI installed and authenticated (`az login`)
+- Azure authentication available to `DefaultAzureCredential` (for local development, run `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini"
```
## Run the sample
diff --git a/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/Program.cs b/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/Program.cs
index 30fa79faa8..b008f5043f 100644
--- a/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/Program.cs
+++ b/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/Program.cs
@@ -10,9 +10,9 @@ using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using FoundryEvals = Microsoft.Agents.AI.Foundry.FoundryEvals;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/README.md b/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/README.md
index 9390e91e4c..0462f618dc 100644
--- a/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/README.md
+++ b/dotnet/samples/03-workflows/Evaluation/Evaluation_WorkflowExpectedOutputs/README.md
@@ -1,4 +1,4 @@
-# Evaluation - Workflow Expected Outputs
+# Evaluation - Workflow Expected Outputs
This sample demonstrates evaluating a multi-agent workflow's final answer
against a golden expected output using Foundry's reference-based **Similarity**
@@ -20,13 +20,13 @@ Evals API.
## Prerequisites
- .NET 10 SDK or later
-- Azure CLI installed and authenticated (`az login`)
+- Azure authentication available to `DefaultAzureCredential` (for local development, run `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini"
```
## Run the sample
diff --git a/dotnet/samples/03-workflows/Orchestration/Handoff/Program.cs b/dotnet/samples/03-workflows/Orchestration/Handoff/Program.cs
index 69cf8c168b..c5fefe191f 100644
--- a/dotnet/samples/03-workflows/Orchestration/Handoff/Program.cs
+++ b/dotnet/samples/03-workflows/Orchestration/Handoff/Program.cs
@@ -6,9 +6,9 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/03-workflows/Orchestration/Magentic/Program.cs b/dotnet/samples/03-workflows/Orchestration/Magentic/Program.cs
index 4cb148b2cd..0382745d7d 100644
--- a/dotnet/samples/03-workflows/Orchestration/Magentic/Program.cs
+++ b/dotnet/samples/03-workflows/Orchestration/Magentic/Program.cs
@@ -33,9 +33,9 @@ public static class Program
private static async Task Main()
{
- string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
- string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+ string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+ string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/03-workflows/Orchestration/Magentic/README.md b/dotnet/samples/03-workflows/Orchestration/Magentic/README.md
index e8314759f1..739229df26 100644
--- a/dotnet/samples/03-workflows/Orchestration/Magentic/README.md
+++ b/dotnet/samples/03-workflows/Orchestration/Magentic/README.md
@@ -15,8 +15,8 @@ This sample showcases the Magentic Orchestration Pattern in .NET, setting up a t
## Prerequisites
-- `AZURE_AI_PROJECT_ENDPOINT` set to your Azure AI Foundry project endpoint
-- `AZURE_AI_MODEL_DEPLOYMENT_NAME` set to your model deployment name (defaults to `gpt-5.4-mini`)
+- `FOUNDRY_PROJECT_ENDPOINT` set to your Azure AI Foundry project endpoint
+- `FOUNDRY_MODEL` set to your model deployment name (defaults to `gpt-5.4-mini`)
- `az login` completed before running the sample
## Running the Sample
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Program.cs b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Program.cs
index 51b9fb4d7f..b9108b4b29 100644
--- a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Program.cs
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Program.cs
@@ -27,6 +27,9 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYM
string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
+ // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+ // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+ // latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
: new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential());
ChatClient chatClient = client.GetChatClient(deploymentName);
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/.env.example
index 79fac42841..c40c94eb4a 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/.env.example
@@ -1,7 +1,7 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AGENT_NAME=hosted-agent-skills
SKILL_NAMES=support-style,escalation-policy
# Set to true to provision sample skills to Foundry on startup (first-run convenience).
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/Program.cs
index 90dc325120..b7a3925841 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/Program.cs
@@ -34,9 +34,9 @@ using Microsoft.Extensions.AI;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
string skillNames = Environment.GetEnvironmentVariable("SKILL_NAMES")
?? throw new InvalidOperationException("SKILL_NAMES is not set. Provide a comma-separated list of skill names (e.g., support-style,escalation-policy).");
@@ -56,6 +56,9 @@ foreach (string name in requestedSkills)
}
}
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/README.md
index 01239efef5..747e1dec4f 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/README.md
@@ -1,4 +1,4 @@
-# What this sample demonstrates
+# What this sample demonstrates
An [Agent Framework](https://github.com/microsoft/agent-framework) agent that loads its behavioral guidelines from [**Foundry Skills**](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/skills) at startup, hosted using the **Responses protocol**. Skills are authored once as `SKILL.md` files, uploaded to your Foundry project through the Skills REST API, and downloaded by the agent on boot so updates ship without code changes.
@@ -54,8 +54,8 @@ Your identity (or the Managed Identity running the container in production) need
Set the required environment variables and run the sample with `dotnet run`:
```bash
-export AZURE_AI_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/"
-export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o"
+export FOUNDRY_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/"
+export FOUNDRY_MODEL="gpt-4o"
export SKILL_NAMES="support-style,escalation-policy"
export PROVISION_SAMPLE_SKILLS="true" # First run only — provisions skills to Foundry
```
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.manifest.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.manifest.yaml
index 6be5e63017..83f442a637 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.manifest.yaml
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.manifest.yaml
@@ -26,8 +26,8 @@ template:
cpu: "0.25"
memory: 0.5Gi
environment_variables:
- - name: AZURE_AI_MODEL_DEPLOYMENT_NAME
- value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
+ - name: FOUNDRY_MODEL
+ value: "{{FOUNDRY_MODEL}}"
- name: SKILL_NAMES
value: "{{SKILL_NAMES}}"
parameters:
@@ -38,4 +38,4 @@ parameters:
resources:
- kind: model
id: gpt-4.1-mini
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
+ name: FOUNDRY_MODEL
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.yaml
index 363107d0ea..a63017f828 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.yaml
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.yaml
@@ -8,7 +8,7 @@ resources:
cpu: "0.25"
memory: 0.5Gi
environment_variables:
- - name: AZURE_AI_MODEL_DEPLOYMENT_NAME
- value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
+ - name: FOUNDRY_MODEL
+ value: ${FOUNDRY_MODEL}
- name: SKILL_NAMES
value: ${SKILL_NAMES}
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/scripts/smoke.ps1 b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/scripts/smoke.ps1
index 09094706a1..e13c94cfd6 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/scripts/smoke.ps1
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/scripts/smoke.ps1
@@ -11,7 +11,7 @@
Prerequisites:
- Docker
- az login (token is fetched from the host)
- - .env populated with AZURE_AI_PROJECT_ENDPOINT and model deployment
+ - .env populated with FOUNDRY_PROJECT_ENDPOINT and model deployment
- Skills provisioned to Foundry (set PROVISION_SAMPLE_SKILLS=true on first run)
.NOTES
This script is for local Docker debugging only. The Foundry platform supplies the
@@ -30,7 +30,7 @@ $ErrorActionPreference = 'Stop'
Set-Location -Path $PSScriptRoot/..
if (-not (Test-Path .env)) {
- throw '.env not found. Copy .env.example to .env and fill in AZURE_AI_PROJECT_ENDPOINT.'
+ throw '.env not found. Copy .env.example to .env and fill in FOUNDRY_PROJECT_ENDPOINT.'
}
Write-Host '==> Publishing sample for linux-musl-x64 ...'
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/.env.example
index 3b63f9d218..fe9adaaad8 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/.env.example
@@ -1,5 +1,5 @@
-AZURE_AI_PROJECT_ENDPOINT=
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_PROJECT_ENDPOINT=
+FOUNDRY_MODEL=gpt-4o
AZURE_SEARCH_ENDPOINT=
AZURE_SEARCH_INDEX_NAME=contoso-outdoors
AZURE_BEARER_TOKEN_FOUNDRY=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/Program.cs
index 4b97324134..51b827467b 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/Program.cs
@@ -23,15 +23,18 @@ using OpenAI.Chat;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-string projectEndpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+string projectEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
string searchEndpoint = Environment.GetEnvironmentVariable("AZURE_SEARCH_ENDPOINT")
?? throw new InvalidOperationException("AZURE_SEARCH_ENDPOINT is not set.");
string searchIndexName = Environment.GetEnvironmentVariable("AZURE_SEARCH_INDEX_NAME")
?? throw new InvalidOperationException("AZURE_SEARCH_INDEX_NAME is not set.");
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential. Try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in
// production). The dev credential is scope aware so a single instance serves both Foundry and
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/README.md
index f82ee30e5a..638ca559df 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/README.md
@@ -1,4 +1,4 @@
-# Hosted-AzureSearchRag
+# Hosted-AzureSearchRag
A hosted agent with **Retrieval Augmented Generation (RAG)** capabilities backed by **Azure AI Search**. The agent grounds its answers in product documentation by running a keyword search against an Azure AI Search index before each model invocation, then citing the source in its response.
@@ -77,8 +77,8 @@ cp .env.example .env
Edit `.env`:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_MODEL=gpt-4o
AZURE_SEARCH_ENDPOINT=https://.search.windows.net
AZURE_SEARCH_INDEX_NAME=contoso-outdoors
AZURE_BEARER_TOKEN_FOUNDRY=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/.env.example
index 984e8625cf..99a2f75c03 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/.env.example
@@ -1,6 +1,6 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AGENT_NAME=hosted-chat-client-agent
AZURE_BEARER_TOKEN=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/Program.cs
index b4b08ba5a8..b25889b93a 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/Program.cs
@@ -11,14 +11,17 @@ using Microsoft.Agents.AI.Foundry.Hosting;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."));
+var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."));
var agentName = Environment.GetEnvironmentVariable("AGENT_NAME")
?? throw new InvalidOperationException("AGENT_NAME is not set.");
-var deployment = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+var deployment = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity running in foundry).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/README.md
index 4e11ed8023..89c9bb8592 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/README.md
@@ -1,4 +1,4 @@
-# Hosted-ChatClientAgent
+# Hosted-ChatClientAgent
A simple general-purpose AI assistant hosted as a Foundry Hosted Agent using the Agent Framework instance hosting pattern. The agent is created inline via `AIProjectClient.AsAIAgent(model, instructions)` and served using the Responses protocol.
@@ -19,10 +19,10 @@ cp .env.example .env
Edit `.env` and set your Azure AI Foundry project endpoint:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
```
> **Note:** `.env` is gitignored. The `.env.example` template is checked in as a reference.
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/.env.example
index b8fe9e8e7a..04335e65b8 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/.env.example
@@ -1,5 +1,5 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/Program.cs
index 3f79a0eb7d..4472370e9a 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/Program.cs
@@ -20,8 +20,8 @@
// indirect prompt injection in an uploaded file.
//
// Required environment variables:
-// AZURE_AI_PROJECT_ENDPOINT - Azure AI Foundry project endpoint
-// AZURE_AI_MODEL_DEPLOYMENT_NAME - Model deployment name (default: gpt-4o)
+// FOUNDRY_PROJECT_ENDPOINT - Azure AI Foundry project endpoint
+// FOUNDRY_MODEL - Model deployment name (default: gpt-4o)
//
// Optional:
// AGENT_NAME - Agent name (default: hosted-files)
@@ -46,10 +46,13 @@ Env.TraversePath().Load();
// Bypass SampleEnvironment alias (which prompts on missing env vars) for optional values.
string? GetOptionalEnv(string key) => System.Environment.GetEnvironmentVariable(key);
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = GetOptionalEnv("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = GetOptionalEnv("FOUNDRY_MODEL") ?? "gpt-4o";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/README.md
index 80b68abe2e..d9c77c073d 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/README.md
@@ -1,4 +1,4 @@
-# Hosted-Files
+# Hosted-Files
A hosted agent that demonstrates **two distinct file knowledge sources** through scoped, security-hardened tools:
@@ -57,10 +57,10 @@ cp .env.example .env
Edit `.env`:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
```
> `.env` is gitignored. The `.env.example` template is checked in as a reference.
@@ -153,4 +153,4 @@ Drop additional text files into [`resources/`](./resources/). The csproj `/resources` (`/app/resources/` in container) |
-| `HOME` | The per-session sandbox volume root the session-files tools read from. Set by the Foundry platform; can be overridden for local testing. | `/home/session` |
\ No newline at end of file
+| `HOME` | The per-session sandbox volume root the session-files tools read from. Set by the Foundry platform; can be overridden for local testing. | `/home/session` |
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/.env.example
index c72380d125..aaeb71a9e4 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/.env.example
@@ -1,4 +1,4 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
AGENT_NAME=
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/Program.cs
index f83a67f66d..1c0f1768ee 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/Program.cs
@@ -12,11 +12,14 @@ using Microsoft.Agents.AI.Foundry.Hosting;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."));
+var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."));
var agentName = Environment.GetEnvironmentVariable("AGENT_NAME")
?? throw new InvalidOperationException("AGENT_NAME is not set.");
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity running in foundry).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/README.md
index 3aa80756ee..a4894e0d24 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/README.md
@@ -1,4 +1,4 @@
-# Hosted-FoundryAgent
+# Hosted-FoundryAgent
A hosted agent that delegates to a **Foundry-managed agent definition**. Instead of defining the model, instructions, and tools inline in code, this sample retrieves an existing agent registered in the Foundry platform via `AIProjectClient.AsAIAgent(agentRecord)` and hosts it using the Responses protocol.
@@ -21,7 +21,7 @@ cp .env.example .env
Edit `.env` and set your Azure AI Foundry project endpoint:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
```
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/.env.example
index b8fe9e8e7a..04335e65b8 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/.env.example
@@ -1,5 +1,5 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/Program.cs
index 8a665d38a3..19922f19e5 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/Program.cs
@@ -19,10 +19,13 @@ using Microsoft.Extensions.AI;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/README.md
index 8ad876e21e..246c2a495a 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/README.md
@@ -1,4 +1,4 @@
-# Hosted-LocalTools
+# Hosted-LocalTools
A hosted agent with **local C# function tools** for hotel search. Demonstrates how to define and wire local tools that the LLM can invoke — a key advantage of code-based hosted agents over prompt agents.
@@ -21,10 +21,10 @@ cp .env.example .env
Edit `.env` and set your Azure AI Foundry project endpoint:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
```
> **Note:** `.env` is gitignored. The `.env.example` template is checked in as a reference.
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/.env.example
index b8fe9e8e7a..04335e65b8 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/.env.example
@@ -1,5 +1,5 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/Program.cs
index 1eed2126f7..ae11482133 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/Program.cs
@@ -28,10 +28,13 @@ using ModelContextProtocol.Client;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."));
-var deployment = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."));
+var deployment = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/README.md
index db0a232412..d635de65bd 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/README.md
@@ -1,4 +1,4 @@
-# Hosted-McpTools
+# Hosted-McpTools
A hosted agent demonstrating **two layers of MCP (Model Context Protocol) tool integration**:
@@ -33,8 +33,8 @@ cp .env.example .env
Edit `.env`:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_MODEL=gpt-4o
```
## Running directly (contributors)
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/.env.example
index 29d86f5ef1..9eb77d2a5b 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/.env.example
@@ -1,7 +1,7 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AZURE_AI_EMBEDDING_DEPLOYMENT_NAME=text-embedding-ada-002
AZURE_AI_MEMORY_STORE_ID=hosted-memory-sample
AGENT_NAME=hosted-memory-agent
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/Program.cs
index 22bfd316b3..1221dcbc9d 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/Program.cs
@@ -26,14 +26,17 @@ using Microsoft.Extensions.AI;
// Load .env file if present (for local development).
Env.TraversePath().Load();
-var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."));
+var projectEndpoint = new Uri(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."));
var agentName = Environment.GetEnvironmentVariable("AGENT_NAME")
?? throw new InvalidOperationException("AGENT_NAME is not set.");
-var deployment = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+var deployment = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
var embeddingDeployment = Environment.GetEnvironmentVariable("AZURE_AI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-ada-002";
var memoryStoreName = Environment.GetEnvironmentVariable("AZURE_AI_MEMORY_STORE_ID") ?? "hosted-memory-sample";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in foundry).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/README.md
index d8820096a0..69c8e34ec5 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/README.md
@@ -1,4 +1,4 @@
-# Hosted-MemoryAgent
+# Hosted-MemoryAgent
A hosted Foundry agent that uses **FoundryMemoryProvider** to remember user-private details across
requests and across sessions, scoped per end user via the Foundry platform's isolation keys. The
@@ -31,8 +31,8 @@ cp .env.example .env
Required:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_MODEL=gpt-4o
AZURE_AI_EMBEDDING_DEPLOYMENT_NAME=text-embedding-ada-002
AZURE_AI_MEMORY_STORE_ID=hosted-memory-sample
AGENT_NAME=hosted-memory-agent
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/scripts/smoke.ps1 b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/scripts/smoke.ps1
index 4f85fb3873..fd35751839 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/scripts/smoke.ps1
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/scripts/smoke.ps1
@@ -11,7 +11,7 @@
Prerequisites:
- Docker
- az login (token is fetched from the host)
- - .env populated with AZURE_AI_PROJECT_ENDPOINT and model deployments
+ - .env populated with FOUNDRY_PROJECT_ENDPOINT and model deployments
.NOTES
This script is for local Docker debugging only. The Foundry platform supplies the isolation
keys for every inbound request in production and the dev fallback used here must not be
@@ -29,7 +29,7 @@ $ErrorActionPreference = 'Stop'
Set-Location -Path $PSScriptRoot/..
if (-not (Test-Path .env)) {
- throw '.env not found. Copy .env.example to .env and fill in AZURE_AI_PROJECT_ENDPOINT.'
+ throw '.env not found. Copy .env.example to .env and fill in FOUNDRY_PROJECT_ENDPOINT.'
}
Write-Host '==> Publishing sample for linux-musl-x64 ...'
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/.env.example
index 4a6101948c..46900211af 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/.env.example
@@ -1,7 +1,7 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
# Capture prompt / completion / tool argument content on GenAI spans.
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/Program.cs
index fa57fc03a2..135fe92aac 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/Program.cs
@@ -18,10 +18,13 @@ using Microsoft.Extensions.AI;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/README.md
index e51959c6f5..20e6a7f2b1 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/README.md
@@ -1,4 +1,4 @@
-# Hosted-Observability
+# Hosted-Observability
A hosted [Agent Framework](https://github.com/microsoft/agent-framework) agent that demonstrates how the Foundry hosting pipeline emits OpenTelemetry traces, metrics and logs with no extra wiring.
@@ -39,10 +39,10 @@ cp .env.example .env
Edit `.env` and set your Azure AI Foundry project endpoint:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
```
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/.env.example
index b8fe9e8e7a..04335e65b8 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/.env.example
@@ -1,5 +1,5 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/Program.cs
index a374f81fd7..0194971576 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/Program.cs
@@ -17,10 +17,13 @@ using OpenAI.Chat;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/README.md
index c45b3f9101..2e7511fb6b 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/README.md
@@ -1,4 +1,4 @@
-# Hosted-TextRag
+# Hosted-TextRag
A hosted agent with **Retrieval Augmented Generation (RAG)** capabilities using `TextSearchProvider`. The agent grounds its answers in product documentation by running a search before each model invocation, then citing the source in its response.
@@ -21,10 +21,10 @@ cp .env.example .env
Edit `.env` and set your Azure AI Foundry project endpoint:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AZURE_BEARER_TOKEN=
```
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/Program.cs
index b06b8f5688..3adae07004 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/Program.cs
@@ -6,14 +6,16 @@
// call tools provided by the Foundry platform's managed MCP proxy.
//
// Required environment variables:
-// AZURE_AI_PROJECT_ENDPOINT (local-dev) OR FOUNDRY_PROJECT_ENDPOINT (hosted runtime)
+// FOUNDRY_PROJECT_ENDPOINT (hosted runtime) OR AZURE_AI_PROJECT_ENDPOINT (local-dev)
// - Azure AI Foundry project endpoint. The Foundry hosted
// runtime auto-injects FOUNDRY_PROJECT_ENDPOINT; locally
// set AZURE_AI_PROJECT_ENDPOINT.
-// AZURE_AI_MODEL_DEPLOYMENT_NAME - Model deployment name (default: gpt-4o)
+// FOUNDRY_MODEL - Model deployment name (default: gpt-4o)
//
// Optional:
-// TOOLBOX_NAME - Name of the toolbox to load (default: my-toolbox)
+// FOUNDRY_TOOLBOX_NAME - Name of the toolbox to load (default: my-toolset)
+// FOUNDRY_AGENT_TOOLSET_ENDPOINT - Foundry Toolsets proxy base URL
+// (injected automatically by Foundry platform at runtime)
// FOUNDRY_AGENT_NAME - Client name reported to MCP server (auto-injected in hosted runtime)
// FOUNDRY_AGENT_VERSION - Client version reported to MCP server (auto-injected in hosted runtime)
// FOUNDRY_AGENT_TOOLSET_FEATURES - Additional Foundry-Features header flags (the mandatory
@@ -39,9 +41,14 @@ string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
?? throw new InvalidOperationException(
"Neither FOUNDRY_PROJECT_ENDPOINT (platform-injected in hosted runtime) " +
"nor AZURE_AI_PROJECT_ENDPOINT (local-dev convention) is set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
-string toolboxName = Environment.GetEnvironmentVariable("TOOLBOX_NAME") ?? "my-toolbox";
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL")
+ ?? Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+string toolboxName = Environment.GetEnvironmentVariable("FOUNDRY_TOOLBOX_NAME")
+ ?? Environment.GetEnvironmentVariable("TOOLBOX_NAME") ?? "my-toolset";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/.env.example
index 5c312b3f8e..0f603ea20c 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/.env.example
@@ -1,6 +1,6 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-5
+FOUNDRY_MODEL=gpt-5
FOUNDRY_TOOLBOX_NAME=
AZURE_BEARER_TOKEN=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs
index f8ca3f4991..99dbdc6573 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs
@@ -7,9 +7,9 @@
// AgentSkillsProviderBuilder.UseMcpSkills().
//
// Required environment variables:
-// AZURE_AI_PROJECT_ENDPOINT - Azure AI Foundry project endpoint
+// FOUNDRY_PROJECT_ENDPOINT - Azure AI Foundry project endpoint
// FOUNDRY_TOOLBOX_NAME - Name of the Foundry Toolbox to connect to
-// AZURE_AI_MODEL_DEPLOYMENT_NAME - Model deployment name (default: gpt-5)
+// FOUNDRY_MODEL - Model deployment name (default: gpt-5)
using System.Net.Http.Headers;
using Azure.AI.Projects;
@@ -24,15 +24,18 @@ using ModelContextProtocol.Client;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-var projectEndpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-var deployment = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5";
+var projectEndpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+var deployment = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5";
var toolboxName = Environment.GetEnvironmentVariable("FOUNDRY_TOOLBOX_NAME")
?? throw new InvalidOperationException("FOUNDRY_TOOLBOX_NAME is not set.");
// Build the Toolbox MCP URL from the project endpoint and toolbox name.
var toolboxMcpServerUrl = $"{projectEndpoint.TrimEnd('/')}/toolboxes/{toolboxName}/mcp?api-version=v1";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/README.md
index 707f37fb58..7b09109520 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/README.md
@@ -1,4 +1,4 @@
-# Hosted-ToolboxMcpSkills
+# Hosted-ToolboxMcpSkills
A hosted agent that discovers **MCP-based skills from a Foundry Toolbox** and makes them available to the agent using `AgentSkillsProviderBuilder.UseMcpSkills(mcpClient)`.
@@ -28,10 +28,10 @@ cp .env.example .env
Edit `.env` and set your Azure AI Foundry project endpoint and toolbox name:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-5
+FOUNDRY_MODEL=gpt-5
FOUNDRY_TOOLBOX_NAME=my-toolbox
```
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.manifest.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.manifest.yaml
index 2887336252..ad85fb1bca 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.manifest.yaml
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.manifest.yaml
@@ -28,8 +28,8 @@ template:
cpu: "0.25"
memory: 0.5Gi
environment_variables:
- - name: AZURE_AI_MODEL_DEPLOYMENT_NAME
- value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
+ - name: FOUNDRY_MODEL
+ value: "{{FOUNDRY_MODEL}}"
- name: FOUNDRY_TOOLBOX_NAME
value: "{{FOUNDRY_TOOLBOX_NAME}}"
parameters:
@@ -40,4 +40,4 @@ parameters:
resources:
- kind: model
id: gpt-5
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
+ name: FOUNDRY_MODEL
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.yaml
index 5f53abb2e2..140a5fc4c6 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.yaml
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.yaml
@@ -8,7 +8,7 @@ resources:
cpu: "0.25"
memory: 0.5Gi
environment_variables:
- - name: AZURE_AI_MODEL_DEPLOYMENT_NAME
- value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
+ - name: FOUNDRY_MODEL
+ value: ${FOUNDRY_MODEL}
- name: FOUNDRY_TOOLBOX_NAME
value: ${FOUNDRY_TOOLBOX_NAME}
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff/Program.cs
index 30d9d43616..e89ad78210 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff/Program.cs
@@ -38,6 +38,9 @@ var builder = WebApplication.CreateBuilder(args);
var endpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."));
var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
var azureClient = new AzureOpenAIClient(endpoint, new ChainedTokenCredential(
new DevTemporaryTokenCredential(),
new DefaultAzureCredential()));
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/.env.example
index b8fe9e8e7a..04335e65b8 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/.env.example
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/.env.example
@@ -1,5 +1,5 @@
-AZURE_AI_PROJECT_ENDPOINT=
+FOUNDRY_PROJECT_ENDPOINT=
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+FOUNDRY_MODEL=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/Program.cs
index d0fb8ee129..b4d54bb977 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/Program.cs
@@ -18,10 +18,13 @@ using Microsoft.Extensions.AI;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o";
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
TokenCredential credential = new ChainedTokenCredential(
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/README.md
index 12aa97a8d7..cdbe36911d 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/README.md
@@ -1,4 +1,4 @@
-# Hosted-Workflow-Simple
+# Hosted-Workflow-Simple
A hosted agent that demonstrates **multi-agent workflow orchestration**. Three translation agents are composed into a sequential pipeline: English → French → Spanish → English, showing how agents can be chained as workflow executors using `WorkflowBuilder`.
@@ -19,10 +19,10 @@ cp .env.example .env
Edit `.env` and set your Azure AI Foundry project endpoint:
```env
-AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
-AZURE_AI_MODEL_DEPLOYMENT_NAME=hosted-workflow-simple
+FOUNDRY_MODEL=hosted-workflow-simple
```
> **Note:** `.env` is gitignored. The `.env.example` template is checked in as a reference.
@@ -125,7 +125,7 @@ If you need to override defaults, set deployment-time environment variables in t
```bash
azd env set AGENT_NAME hosted-workflow-simple
-azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME hosted-workflow-simple
+azd env set FOUNDRY_MODEL hosted-workflow-simple
```
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/Program.cs
index 0c2ba2d038..716a0b37c4 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/Program.cs
@@ -10,10 +10,10 @@ using Microsoft.Agents.AI.Foundry;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-// AZURE_AI_PROJECT_ENDPOINT is the Foundry project endpoint. Shape:
+// FOUNDRY_PROJECT_ENDPOINT is the Foundry project endpoint. Shape:
// https:///api/projects/
-Uri projectEndpoint = new(Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."));
+Uri projectEndpoint = new(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."));
// AZURE_AI_AGENT_NAME is the registered server-side agent name.
string agentName = Environment.GetEnvironmentVariable("AZURE_AI_AGENT_NAME")
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/README.md
index 356416596a..043cc8fa7d 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/README.md
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/README.md
@@ -1,4 +1,4 @@
-# SessionFilesClient
+# SessionFilesClient
A thin chat REPL that connects to a deployed [`Hosted-Files`](../../Hosted-Files/) agent via `FoundryAgent` and lets you ask questions whose answers come from the files bundled with that agent. Same shape as [`SimpleAgent`](../SimpleAgent/) — point it at an `AGENT_ENDPOINT`, build a `FoundryAgent`, run.
@@ -13,17 +13,17 @@ The agent's container-side `ListFiles` and `ReadFile` tools surface the bundled
## Configuration
```env
-AZURE_AI_PROJECT_ENDPOINT=https:///api/projects/
+FOUNDRY_PROJECT_ENDPOINT=https:///api/projects/
AZURE_AI_AGENT_NAME=hosted-files
```
-Both are required. `AZURE_AI_PROJECT_ENDPOINT` is the Foundry project endpoint URL and `AZURE_AI_AGENT_NAME` is the registered server-side agent name. The sample builds the per-agent OpenAI endpoint URL from these.
+Both are required. `FOUNDRY_PROJECT_ENDPOINT` is the Foundry project endpoint URL and `AZURE_AI_AGENT_NAME` is the registered server-side agent name. The sample builds the per-agent OpenAI endpoint URL from these.
## Run
```bash
cd dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient
-$env:AZURE_AI_PROJECT_ENDPOINT = "http://localhost:8088/api/projects/local"
+$env:FOUNDRY_PROJECT_ENDPOINT = "http://localhost:8088/api/projects/local"
$env:AZURE_AI_AGENT_NAME = "hosted-files"
dotnet run
```
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/Program.cs
index eedf136abb..b74e85e19d 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/Program.cs
@@ -10,10 +10,10 @@ using Microsoft.Agents.AI.Foundry;
// Load .env file if present (for local development)
Env.TraversePath().Load();
-// AZURE_AI_PROJECT_ENDPOINT is the Foundry project endpoint. Shape:
+// FOUNDRY_PROJECT_ENDPOINT is the Foundry project endpoint. Shape:
// https:///api/projects/
-Uri projectEndpoint = new(Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."));
+Uri projectEndpoint = new(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
+ ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set."));
// AZURE_AI_AGENT_NAME is the registered server-side agent name.
string agentName = Environment.GetEnvironmentVariable("AZURE_AI_AGENT_NAME")
diff --git a/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/Program.cs b/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/Program.cs
index c694351599..26e2460dd1 100644
--- a/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/Program.cs
+++ b/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/Program.cs
@@ -33,7 +33,7 @@ IConfigurationRoot configuration = new ConfigurationBuilder()
string? apiKey = configuration["OPENAI_API_KEY"];
string model = configuration["OPENAI_CHAT_MODEL_NAME"] ?? "gpt-5.4-mini";
-string? endpoint = configuration["AZURE_AI_PROJECT_ENDPOINT"];
+string? endpoint = configuration["FOUNDRY_PROJECT_ENDPOINT"];
string[] agentUrls = (builder.Configuration["urls"] ?? "http://localhost:5000").Split(';');
var invoiceQueryPlugin = new InvoiceQuery();
diff --git a/dotnet/samples/05-end-to-end/A2AClientServer/README.md b/dotnet/samples/05-end-to-end/A2AClientServer/README.md
index 0efb17e748..d0771ff4e1 100644
--- a/dotnet/samples/05-end-to-end/A2AClientServer/README.md
+++ b/dotnet/samples/05-end-to-end/A2AClientServer/README.md
@@ -1,4 +1,4 @@
-# A2A Client and Server samples
+# A2A Client and Server samples
> **Warning**
> The [A2A protocol](https://google.github.io/A2A/) is still under development and changing fast.
@@ -84,7 +84,7 @@ You must create the agents in a Microsoft Foundry project and then provide the p
```
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://ai-foundry-your-project.services.ai.azure.com/api/projects/ai-proj-ga-your-project" # Replace with your Foundry Project endpoint
+$env:FOUNDRY_PROJECT_ENDPOINT="https://ai-foundry-your-project.services.ai.azure.com/api/projects/ai-proj-ga-your-project" # Replace with your Foundry Project endpoint
```
Use the following commands to run each A2A server
diff --git a/dotnet/samples/05-end-to-end/DevUIAspireIntegration/EditorAgent/Program.cs b/dotnet/samples/05-end-to-end/DevUIAspireIntegration/EditorAgent/Program.cs
index d50213a9f7..ecef13f275 100644
--- a/dotnet/samples/05-end-to-end/DevUIAspireIntegration/EditorAgent/Program.cs
+++ b/dotnet/samples/05-end-to-end/DevUIAspireIntegration/EditorAgent/Program.cs
@@ -13,6 +13,9 @@ builder.AddServiceDefaults();
builder.AddAzureChatCompletionsClient(connectionName: "foundry",
configureSettings: settings =>
{
+ // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+ // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+ // latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
settings.TokenCredential = new DefaultAzureCredential();
settings.EnableSensitiveTelemetryData = builder.Environment.IsDevelopment();
})
diff --git a/dotnet/samples/05-end-to-end/DevUIAspireIntegration/WriterAgent/Program.cs b/dotnet/samples/05-end-to-end/DevUIAspireIntegration/WriterAgent/Program.cs
index 72f3215453..f8061d73da 100644
--- a/dotnet/samples/05-end-to-end/DevUIAspireIntegration/WriterAgent/Program.cs
+++ b/dotnet/samples/05-end-to-end/DevUIAspireIntegration/WriterAgent/Program.cs
@@ -10,6 +10,9 @@ builder.AddServiceDefaults();
builder.AddAzureChatCompletionsClient(connectionName: "foundry",
configureSettings: settings =>
{
+ // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+ // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+ // latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
settings.TokenCredential = new DefaultAzureCredential();
settings.EnableSensitiveTelemetryData = builder.Environment.IsDevelopment();
})
diff --git a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/Program.cs b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/Program.cs
index a4cd3c5257..76f930bc42 100644
--- a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/Program.cs
+++ b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/Program.cs
@@ -9,8 +9,8 @@ using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Evaluation;
using FoundryEvals = Microsoft.Agents.AI.Foundry.FoundryEvals;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/README.md b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/README.md
index b2c220a9ba..829ff982ca 100644
--- a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/README.md
+++ b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_ConversationSplits/README.md
@@ -1,4 +1,4 @@
-# Evaluation - Conversation Splits
+# Evaluation - Conversation Splits
This sample demonstrates multi-turn conversation evaluation with different split strategies.
@@ -14,13 +14,13 @@ This sample demonstrates multi-turn conversation evaluation with different split
## Prerequisites
- .NET 10 SDK or later
-- Azure CLI installed and authenticated (`az login`)
+- Azure authentication available to `DefaultAzureCredential` (for local development, run `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini"
```
## Run the sample
@@ -28,4 +28,4 @@ $env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
```powershell
cd dotnet/samples/05-end-to-end/Evaluation
dotnet run --project .\Evaluation_ConversationSplits
-```
\ No newline at end of file
+```
diff --git a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/Program.cs b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/Program.cs
index 8d1a150f47..2aaec605f1 100644
--- a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/Program.cs
+++ b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/Program.cs
@@ -9,8 +9,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI.Evaluation;
using FoundryEvals = Microsoft.Agents.AI.Foundry.FoundryEvals;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/README.md b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/README.md
index 53b67cec0c..0f86359943 100644
--- a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/README.md
+++ b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_FoundryQuality/README.md
@@ -1,4 +1,4 @@
-# Evaluation - Foundry Quality
+# Evaluation - Foundry Quality
This sample demonstrates agent evaluation using MEAI quality evaluators (Relevance, Coherence) via `FoundryEvals`.
@@ -13,13 +13,13 @@ This sample demonstrates agent evaluation using MEAI quality evaluators (Relevan
## Prerequisites
- .NET 10 SDK or later
-- Azure CLI installed and authenticated (`az login`)
+- Azure authentication available to `DefaultAzureCredential` (for local development, run `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini"
```
## Run the sample
diff --git a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/Program.cs b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/Program.cs
index 6c1c163317..7c15887421 100644
--- a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/Program.cs
+++ b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/Program.cs
@@ -8,8 +8,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI.Evaluation;
using FoundryEvals = Microsoft.Agents.AI.Foundry.FoundryEvals;
-string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
diff --git a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/README.md b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/README.md
index 1346635868..712cf0d783 100644
--- a/dotnet/samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/README.md
+++ b/dotnet/samples/05-end-to-end/Evaluation/Evaluation_MixedProviders/README.md
@@ -1,4 +1,4 @@
-# Evaluation - Mixed Providers
+# Evaluation - Mixed Providers
This sample demonstrates mixing local and cloud evaluators in a single evaluation run.
@@ -14,13 +14,13 @@ This sample demonstrates mixing local and cloud evaluators in a single evaluatio
## Prerequisites
- .NET 10 SDK or later
-- Azure CLI installed and authenticated (`az login`)
+- Azure authentication available to `DefaultAzureCredential` (for local development, run `az login`)
Set the following environment variables:
```powershell
-$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
-$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
+$env:FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"
+$env:FOUNDRY_MODEL="gpt-4o-mini"
```
## Run the sample
@@ -28,4 +28,4 @@ $env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"
```powershell
cd dotnet/samples/05-end-to-end/Evaluation
dotnet run --project .\Evaluation_MixedProviders
-```
\ No newline at end of file
+```
diff --git a/dotnet/src/Microsoft.Agents.AI.Valkey/Microsoft.Agents.AI.Valkey.csproj b/dotnet/src/Microsoft.Agents.AI.Valkey/Microsoft.Agents.AI.Valkey.csproj
new file mode 100644
index 0000000000..e819c3f51c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Valkey/Microsoft.Agents.AI.Valkey.csproj
@@ -0,0 +1,40 @@
+
+
+
+ $(TargetFrameworksCore)
+ Microsoft.Agents.AI.Valkey
+ alpha
+ $(NoWarn);CA1873
+
+
+
+ true
+ true
+
+
+
+
+
+ false
+
+
+
+
+ Microsoft Agent Framework - Valkey integration
+ Provides Valkey integration for Microsoft Agent Framework, including chat history persistence and context provider with full-text search.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Valkey/ValkeyChatHistoryProvider.cs b/dotnet/src/Microsoft.Agents.AI.Valkey/ValkeyChatHistoryProvider.cs
new file mode 100644
index 0000000000..088d66ed47
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Valkey/ValkeyChatHistoryProvider.cs
@@ -0,0 +1,225 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.Diagnostics;
+using Valkey.Glide;
+
+namespace Microsoft.Agents.AI.Valkey;
+
+///
+/// Provides a Valkey-backed implementation of for persistent chat history storage.
+///
+///
+///
+/// Uses basic Valkey list operations via Valkey.Glide.
+/// No search module is required — this provider works with any Valkey server.
+///
+///
+/// Data retention: Stored messages have no TTL and persist indefinitely.
+/// Use to limit per-conversation storage, and
+/// for explicit cleanup. Callers are responsible for implementing data retention policies.
+///
+///
+/// Security considerations:
+///
+/// - PII and sensitive data: Chat history stored in Valkey may contain PII and sensitive
+/// conversation content. Ensure the Valkey server is configured with appropriate access controls and encryption in transit
+/// (TLS). The property can limit stored messages per conversation.
+/// - Compromised store risks: Agent Framework does not validate or filter messages loaded
+/// from the store — they are accepted as-is. If the Valkey store is compromised, adversarial content could be injected
+/// into the conversation context.
+///
+///
+///
+public sealed class ValkeyChatHistoryProvider : ChatHistoryProvider
+{
+ private readonly ProviderSessionState _sessionState;
+ private IReadOnlyList? _stateKeys;
+ private readonly IConnectionMultiplexer _connection;
+ private readonly string _keyPrefix;
+ private readonly int? _maxMessages;
+ private readonly int? _maxMessagesToRetrieve;
+ private readonly JsonSerializerOptions _jsonSerializerOptions;
+ private readonly ILogger? _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// An existing instance.
+ /// A delegate that initializes the provider state on the first invocation.
+ /// Optional configuration options.
+ /// Optional logger factory.
+ public ValkeyChatHistoryProvider(
+ IConnectionMultiplexer connection,
+ Func stateInitializer,
+ ValkeyChatHistoryProviderOptions? options = null,
+ ILoggerFactory? loggerFactory = null)
+ : base(options?.ProvideOutputMessageFilter, options?.StoreInputRequestMessageFilter, options?.StoreInputResponseMessageFilter)
+ {
+ this._sessionState = new ProviderSessionState(
+ Throw.IfNull(stateInitializer),
+ options?.StateKey ?? this.GetType().Name,
+ options?.JsonSerializerOptions);
+ this._connection = Throw.IfNull(connection);
+ this._keyPrefix = options?.KeyPrefix ?? "chat_history";
+ this._maxMessages = options?.MaxMessages;
+ this._maxMessagesToRetrieve = options?.MaxMessagesToRetrieve;
+ this._jsonSerializerOptions = options?.JsonSerializerOptions ?? AgentAbstractionsJsonUtilities.DefaultOptions;
+ this._logger = loggerFactory?.CreateLogger();
+ }
+
+ ///
+ public override IReadOnlyList StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
+
+ ///
+ protected override async ValueTask> ProvideChatHistoryAsync(InvokingContext context, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(context);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var state = this._sessionState.GetOrInitializeState(context.Session);
+ var db = this._connection.GetDatabase();
+ var key = this.BuildKey(state);
+
+ // Fetch only the tail when MaxMessagesToRetrieve is set [Low: avoid fetching all then trimming]
+ ValkeyValue[] values;
+ if (this._maxMessagesToRetrieve.HasValue)
+ {
+ values = await db.ListRangeAsync(key, -this._maxMessagesToRetrieve.Value, -1).ConfigureAwait(false);
+ }
+ else
+ {
+ values = await db.ListRangeAsync(key).ConfigureAwait(false);
+ }
+
+ var messages = new List(values.Length);
+
+ foreach (var value in values)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (value.IsNullOrEmpty)
+ {
+ continue;
+ }
+
+ try
+ {
+ var message = JsonSerializer.Deserialize(value.ToString(), this._jsonSerializerOptions.GetTypeInfo(typeof(ChatMessage))) as ChatMessage;
+ if (message is not null)
+ {
+ messages.Add(message);
+ }
+ }
+ catch (JsonException ex)
+ {
+ // Skip malformed entries rather than crashing the session [VERIFY-002]
+ this._logger?.LogWarning(ex, "ValkeyChatHistoryProvider: Skipping malformed message in conversation '{ConversationId}'.", state.ConversationId);
+ }
+ }
+
+ this._logger?.LogInformation(
+ "ValkeyChatHistoryProvider: Retrieved {Count} messages for conversation.",
+ messages.Count);
+
+ return messages;
+ }
+
+ ///
+ protected override async ValueTask StoreChatHistoryAsync(InvokedContext context, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(context);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var state = this._sessionState.GetOrInitializeState(context.Session);
+ var messageList = context.RequestMessages.Concat(context.ResponseMessages ?? []).ToList();
+ if (messageList.Count == 0)
+ {
+ return;
+ }
+
+ var db = this._connection.GetDatabase();
+ var key = this.BuildKey(state);
+
+ // Batch push — single round-trip [Medium-8]
+ var serialized = new ValkeyValue[messageList.Count];
+ for (int i = 0; i < messageList.Count; i++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ serialized[i] = JsonSerializer.Serialize(messageList[i], this._jsonSerializerOptions.GetTypeInfo(typeof(ChatMessage)));
+ }
+
+ await db.ListRightPushAsync(key, serialized).ConfigureAwait(false);
+
+ // Trim to max messages if configured
+ if (this._maxMessages.HasValue)
+ {
+ await db.ListTrimAsync(key, -this._maxMessages.Value, -1).ConfigureAwait(false);
+ }
+
+ this._logger?.LogInformation(
+ "ValkeyChatHistoryProvider: Stored {Count} messages for conversation.",
+ messageList.Count);
+ }
+
+ ///
+ /// Clears all messages for the specified session's conversation.
+ ///
+ /// The session containing the conversation state.
+ /// Cancellation token.
+ /// A task representing the asynchronous operation.
+ public async Task ClearMessagesAsync(AgentSession? session, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var state = this._sessionState.GetOrInitializeState(session);
+ var db = this._connection.GetDatabase();
+ var key = this.BuildKey(state);
+ await db.KeyDeleteAsync(key).ConfigureAwait(false);
+ }
+
+ ///
+ /// Gets the count of stored messages for the specified session's conversation.
+ ///
+ /// The session containing the conversation state.
+ /// Cancellation token.
+ /// The number of stored messages.
+ public async Task GetMessageCountAsync(AgentSession? session, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var state = this._sessionState.GetOrInitializeState(session);
+ var db = this._connection.GetDatabase();
+ var key = this.BuildKey(state);
+ return await db.ListLengthAsync(key).ConfigureAwait(false);
+ }
+
+ private string BuildKey(State state) => $"{this._keyPrefix}:{state.ConversationId}";
+
+ ///
+ /// Represents the per-session state of a .
+ ///
+ public sealed class State
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier for this conversation thread.
+ [JsonConstructor]
+ public State(string conversationId)
+ {
+ this.ConversationId = Throw.IfNullOrWhitespace(conversationId);
+ }
+
+ ///
+ /// Gets the conversation ID associated with this state.
+ ///
+ public string ConversationId { get; }
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Valkey/ValkeyChatHistoryProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI.Valkey/ValkeyChatHistoryProviderOptions.cs
new file mode 100644
index 0000000000..eabe4680cf
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Valkey/ValkeyChatHistoryProviderOptions.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Valkey;
+
+///
+/// Options for configuring .
+///
+public sealed class ValkeyChatHistoryProviderOptions
+{
+ ///
+ /// Gets or sets the prefix for Valkey keys. Defaults to "chat_history".
+ ///
+ public string KeyPrefix { get; set; } = "chat_history";
+
+ ///
+ /// Gets or sets the maximum number of messages to retain per conversation.
+ /// When exceeded, oldest messages are automatically trimmed. Null means unlimited.
+ ///
+ public int? MaxMessages { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of messages to retrieve from the provider.
+ /// Null means no limit.
+ ///
+ public int? MaxMessagesToRetrieve { get; set; }
+
+ ///
+ /// Gets or sets an optional key for storing state in the session's StateBag.
+ ///
+ public string? StateKey { get; set; }
+
+ ///
+ /// Gets or sets optional JSON serializer options for serializing the state of this provider.
+ ///
+ public JsonSerializerOptions? JsonSerializerOptions { get; set; }
+
+ ///
+ /// Gets or sets an optional filter for messages when retrieving from history.
+ ///
+ public Func, IEnumerable>? ProvideOutputMessageFilter { get; set; }
+
+ ///
+ /// Gets or sets an optional filter for request messages before storing.
+ ///
+ public Func, IEnumerable>? StoreInputRequestMessageFilter { get; set; }
+
+ ///
+ /// Gets or sets an optional filter for response messages before storing.
+ ///
+ public Func, IEnumerable>? StoreInputResponseMessageFilter { get; set; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/AIJudgeLoopEvaluator.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/AIJudgeLoopEvaluator.cs
new file mode 100644
index 0000000000..b482d6c93e
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/AIJudgeLoopEvaluator.cs
@@ -0,0 +1,201 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// A that uses a separate judge chat client to decide whether the user's original request
+/// has been fully addressed, continuing the loop (with the judge's gap analysis as feedback) while the answer is "no".
+///
+///
+///
+/// After each iteration the judge is queried directly (without any agent tools, session, or middleware) with the
+/// original request and the agent's latest response, and asked for a structured . If the
+/// judge client does not honor structured output, the verdict falls back to parsing the raw text for the
+/// non-overlapping / markers (with
+/// winning, so the loop keeps running, when the verdict is ambiguous or absent).
+///
+///
+/// When the request is not yet answered, the evaluator returns feedback built from
+/// with the judge's gap analysis substituted for
+/// . How that feedback is delivered to the agent (and whether the session is
+/// reset) is decided by the that consumes this evaluator.
+///
+///
+/// The judge instructions act as a template: any occurrence of is replaced with the
+/// rendered (or removed when no criteria are supplied), letting
+/// callers add bespoke standards the response must satisfy.
+///
+///
+/// LLM-judged loops are costly and probabilistic, so consider setting a stricter
+/// on the owning .
+///
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class AIJudgeLoopEvaluator : LoopEvaluator
+{
+ /// The default system instructions used to prompt the judge.
+ ///
+ /// Acts as a template: the trailing is replaced with the rendered
+ /// (or removed when none are supplied).
+ ///
+ public const string DefaultInstructions =
+ "You are an evaluator. You are given a user's original request and an agent's latest response. " +
+ "Decide whether the agent has fully addressed the original request. " +
+ "Set 'answered' to true if the request has been fully addressed, or false if more work is still required. " +
+ "When 'answered' is false, use 'gapAnalysis' to explain what is still missing or what work remains. " +
+ "If you cannot return structured output, reply with " + DoneVerdictMarker + " when the request has been fully " +
+ "addressed, or " + MoreVerdictMarker + " when more work is still required." +
+ CriteriaPlaceholder;
+
+ ///
+ /// The verdict marker the judge is asked to emit (for clients that do not honor structured output) when the
+ /// original request has been fully addressed.
+ ///
+ ///
+ /// and are deliberately non-overlapping (neither is
+ /// a substring of the other), so the text fallback cannot misclassify one verdict as the other. When the marker is
+ /// ambiguous or absent, wins so the loop keeps running rather than stopping on an
+ /// incomplete answer.
+ ///
+ public const string DoneVerdictMarker = "VERDICT: DONE";
+
+ ///
+ /// The verdict marker the judge is asked to emit (for clients that do not honor structured output) when more work
+ /// is still required. Takes precedence over when both (or neither) are present.
+ ///
+ public const string MoreVerdictMarker = "VERDICT: MORE";
+
+ ///
+ /// The placeholder token within (or a custom
+ /// ) that is replaced with the rendered
+ /// . When no criteria are supplied, the placeholder is removed.
+ ///
+ public const string CriteriaPlaceholder = "{criteria}";
+
+ ///
+ /// The placeholder token within (or a custom
+ /// ) that is replaced with the judge's gap analysis.
+ ///
+ public const string GapAnalysisPlaceholder = "{gap_analysis}";
+
+ /// The default template used to build the feedback produced when the request is not yet answered.
+ public const string DefaultFeedbackMessageTemplate =
+ "Your previous response did not fully address the original request. " +
+ "The following is still missing or incomplete: " + GapAnalysisPlaceholder + " " +
+ "Please continue and fully address the original request.";
+
+ /// The value substituted for the gap analysis when the judge did not provide one.
+ private const string UnknownGapAnalysis = "";
+
+ private readonly IChatClient _judgeClient;
+ private readonly string _instructions;
+ private readonly string _feedbackMessageTemplate;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The chat client used to judge whether the original request was answered.
+ /// Optional configuration for the judge. When , defaults are used.
+ /// is .
+ public AIJudgeLoopEvaluator(IChatClient judgeClient, AIJudgeLoopEvaluatorOptions? options = null)
+ {
+ this._judgeClient = Throw.IfNull(judgeClient);
+ this._instructions = (options?.Instructions ?? DefaultInstructions)
+ .Replace(CriteriaPlaceholder, RenderCriteria(options?.Criteria));
+ this._feedbackMessageTemplate = options?.FeedbackMessageTemplate ?? DefaultFeedbackMessageTemplate;
+ }
+
+ ///
+ public override async ValueTask EvaluateAsync(LoopContext context, CancellationToken cancellationToken = default)
+ {
+ _ = Throw.IfNull(context);
+
+ // Build the judge's user message from AIContent so non-text request content (images, data, etc.) is
+ // preserved rather than flattened to text. The original request's contents are framed between header
+ // text segments, followed by the agent's latest response text.
+ var userContents = new List
+ {
+ new TextContent("# Has the original request been fully addressed?\n\n## Original request:\n"),
+ };
+ foreach (ChatMessage message in context.InitialMessages)
+ {
+ userContents.AddRange(message.Contents);
+ }
+
+ userContents.Add(new TextContent($"\n\n## Agent's latest response:\n{context.LastResponse.Text}"));
+
+ List judgeMessages =
+ [
+ new ChatMessage(ChatRole.System, this._instructions),
+ new ChatMessage(ChatRole.User, userContents),
+ ];
+
+ bool answered;
+ string gapAnalysis = UnknownGapAnalysis;
+ ChatResponse response = await this._judgeClient
+ .GetResponseAsync(judgeMessages, LoopJsonContext.Default.Options, cancellationToken: cancellationToken)
+ .ConfigureAwait(false);
+
+ if (response.TryGetResult(out JudgeVerdict? verdict) && verdict is not null)
+ {
+ answered = verdict.Answered;
+ if (!string.IsNullOrWhiteSpace(verdict.GapAnalysis))
+ {
+ gapAnalysis = verdict.GapAnalysis;
+ }
+ }
+ else
+ {
+ // Fallback for clients that do not honor structured output: look for the explicit, non-overlapping verdict
+ // markers. MoreVerdictMarker wins so an ambiguous or marker-less reply keeps looping rather than stopping
+ // on an incomplete answer.
+ string text = response.Text.ToUpperInvariant();
+ answered = !text.Contains(MoreVerdictMarker) && text.Contains(DoneVerdictMarker);
+ }
+
+ // The request is answered: stop looping.
+ if (answered)
+ {
+ return LoopEvaluation.Stop();
+ }
+
+ // Not yet answered: continue, providing feedback describing what is still missing.
+ string feedback = this._feedbackMessageTemplate.Replace(GapAnalysisPlaceholder, gapAnalysis);
+ return LoopEvaluation.Continue(feedback);
+ }
+
+ ///
+ /// Renders the supplied into a bullet block appended at ,
+ /// or an empty string when no non-blank criteria are supplied.
+ ///
+ private static string RenderCriteria(IEnumerable? criteria)
+ {
+ if (criteria is null)
+ {
+ return string.Empty;
+ }
+
+ var builder = new StringBuilder();
+ foreach (string criterion in criteria)
+ {
+ if (!string.IsNullOrWhiteSpace(criterion))
+ {
+ builder.Append("\n- ").Append(criterion);
+ }
+ }
+
+ return builder.Length == 0
+ ? string.Empty
+ : "\n\nThe response must satisfy all of the following criteria:" + builder;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/AIJudgeLoopEvaluatorOptions.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/AIJudgeLoopEvaluatorOptions.cs
new file mode 100644
index 0000000000..73285a924c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/AIJudgeLoopEvaluatorOptions.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Shared.DiagnosticIds;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides configuration options for .
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class AIJudgeLoopEvaluatorOptions
+{
+ ///
+ /// Gets or sets the system instructions used to prompt the judge, or to use
+ /// .
+ ///
+ ///
+ /// Any occurrence of in the instructions is replaced with
+ /// the rendered (or removed when no criteria are supplied). Instructions that omit the
+ /// placeholder do not receive the criteria.
+ ///
+ public string? Instructions { get; set; }
+
+ ///
+ /// Gets or sets an optional list of additional criteria the agent's response must satisfy, evaluated by the judge
+ /// alongside the original request.
+ ///
+ ///
+ /// When supplied, the criteria are rendered into the judge instructions wherever
+ /// appears (including in
+ /// ). When or empty, the placeholder is
+ /// removed and no criteria are added.
+ ///
+ public IEnumerable? Criteria { get; set; }
+
+ ///
+ /// Gets or sets the template used to build the feedback produced when the judge decides the original request was
+ /// not fully addressed, or to use
+ /// .
+ ///
+ ///
+ /// Any occurrence of in the template is replaced with the
+ /// judge's gap analysis (or a placeholder when none is available).
+ ///
+ public string? FeedbackMessageTemplate { get; set; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/CompletionMarkerLoopEvaluator.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/CompletionMarkerLoopEvaluator.cs
new file mode 100644
index 0000000000..cd2d7c8aa6
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/CompletionMarkerLoopEvaluator.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// A that stops the loop once a configured marker string appears in the agent's latest
+/// response, and otherwise continues with feedback asking the agent to keep working and to emit the marker when done.
+///
+///
+/// The feedback produced while the marker is absent is built from a template (see
+/// ) with the configured marker substituted
+/// for , and the agent's latest response text substituted for
+/// . How that feedback is delivered to the agent (and whether the session
+/// is reset) is decided by the that consumes this evaluator.
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class CompletionMarkerLoopEvaluator : LoopEvaluator
+{
+ ///
+ /// The placeholder token within (or a custom
+ /// ) that is replaced with the
+ /// configured completion marker.
+ ///
+ public const string CompletionMarkerPlaceholder = "{completion_marker}";
+
+ ///
+ /// The placeholder token within a custom
+ /// that is replaced with the text of the agent's latest response. This is substituted on each evaluation, so it lets
+ /// the feedback echo back what the agent previously produced — useful when the consuming
+ /// uses , where the agent would
+ /// otherwise have no record of its prior output.
+ ///
+ public const string LastResponsePlaceholder = "{last_response}";
+
+ /// The default template used to build the feedback produced while the completion marker is absent.
+ public const string DefaultFeedbackMessageTemplate =
+ "Continue working on the request. When you have fully completed the task, end your response with the marker '" +
+ CompletionMarkerPlaceholder + "' to indicate completion.";
+
+ private readonly string _completionMarker;
+ private readonly string _feedbackMessageTemplate;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The marker string that stops the loop once it appears in the agent's latest response text.
+ /// Optional configuration for the feedback message. When , defaults are used.
+ /// is , empty, or whitespace.
+ public CompletionMarkerLoopEvaluator(string completionMarker, CompletionMarkerLoopEvaluatorOptions? options = null)
+ {
+ this._completionMarker = Throw.IfNullOrWhitespace(completionMarker);
+
+ // The completion marker is fixed, so substitute it once here. The optional {last_response} placeholder depends
+ // on the per-iteration response text, so it is substituted later in EvaluateAsync.
+ this._feedbackMessageTemplate = (options?.FeedbackMessageTemplate ?? DefaultFeedbackMessageTemplate)
+ .Replace(CompletionMarkerPlaceholder, this._completionMarker);
+ }
+
+ ///
+ public override ValueTask EvaluateAsync(LoopContext context, CancellationToken cancellationToken = default)
+ {
+ _ = Throw.IfNull(context);
+
+ if (context.LastResponse.Text.Contains(this._completionMarker))
+ {
+ return new ValueTask(LoopEvaluation.Stop());
+ }
+
+ string feedback = this._feedbackMessageTemplate.Replace(LastResponsePlaceholder, context.LastResponse.Text);
+ return new ValueTask(LoopEvaluation.Continue(feedback));
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/CompletionMarkerLoopEvaluatorOptions.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/CompletionMarkerLoopEvaluatorOptions.cs
new file mode 100644
index 0000000000..de3c394c48
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/CompletionMarkerLoopEvaluatorOptions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Shared.DiagnosticIds;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides configuration options for .
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class CompletionMarkerLoopEvaluatorOptions
+{
+ ///
+ /// Gets or sets the template used to build the feedback produced when the completion marker has not yet appeared,
+ /// or to use .
+ ///
+ ///
+ /// Any occurrence of in the template is
+ /// replaced with the configured completion marker. Any occurrence of
+ /// is replaced, on each evaluation, with the
+ /// text of the agent's latest response — useful for echoing the agent's prior output back to it when the consuming
+ /// is used with a fresh context per iteration.
+ ///
+ public string? FeedbackMessageTemplate { get; set; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/DelegateLoopEvaluator.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/DelegateLoopEvaluator.cs
new file mode 100644
index 0000000000..9c41b1a11c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/DelegateLoopEvaluator.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// A that delegates the re-invocation decision and feedback to a user-supplied callback.
+///
+///
+/// This is the most flexible evaluator: the supplied delegate receives the full and returns
+/// a , so it can decide both whether to continue and what feedback (if any) to provide.
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class DelegateLoopEvaluator : LoopEvaluator
+{
+ private readonly Func> _evaluate;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A callback that decides whether to re-invoke the agent and what feedback to provide.
+ /// is .
+ public DelegateLoopEvaluator(Func> evaluate)
+ {
+ this._evaluate = Throw.IfNull(evaluate);
+ }
+
+ ///
+ public override ValueTask EvaluateAsync(LoopContext context, CancellationToken cancellationToken = default)
+ {
+ _ = Throw.IfNull(context);
+ return this._evaluate(context, cancellationToken);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/JudgeVerdict.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/JudgeVerdict.cs
new file mode 100644
index 0000000000..19d802e2fc
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/JudgeVerdict.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Shared.DiagnosticIds;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Represents the structured verdict returned by the judge chat client used by .
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+internal sealed class JudgeVerdict
+{
+ ///
+ /// Gets or sets a value indicating whether the agent has fully addressed the user's original request.
+ ///
+ [Description("True if the agent has fully addressed the original request, otherwise false.")]
+ public bool Answered { get; set; }
+
+ ///
+ /// Gets or sets an explanation of what is still missing when the request has not been fully addressed.
+ ///
+ [Description("When 'answered' is false, explain what is still missing or what work remains to fully address the original request.")]
+ public string GapAnalysis { get; set; } = string.Empty;
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgent.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgent.cs
new file mode 100644
index 0000000000..c92de6a331
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgent.cs
@@ -0,0 +1,548 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// A that re-invokes the wrapped agent in a loop until the configured
+/// set decides to stop.
+///
+///
+///
+/// After each run of the wrapped agent, the configured evaluators are asked whether to re-invoke the agent and what
+/// feedback to carry forward. This enables patterns such as iterative refinement, working through a task list, or
+/// judging whether the original request was answered. Out-of-the-box evaluators include
+/// , , and
+/// .
+///
+///
+/// When multiple evaluators are supplied they are evaluated in order after each iteration. The first evaluator that
+/// asks to re-invoke wins: its feedback drives the next iteration and the remaining evaluators are not evaluated. The
+/// loop stops only when every evaluator asks to stop. Consequently, evaluator order is priority order and
+/// means "this evaluator does not request continuation" rather than a veto that
+/// terminates the loop; place stop-only guards accordingly.
+///
+///
+/// The caller's initial messages are sent to the wrapped agent exactly once. By default (when
+/// is ) the loop reuses a single session
+/// and sends only the winning evaluator's feedback as the next input, letting the agent continue from session history.
+/// When is , each re-invocation restarts
+/// from the original input messages plus an aggregated feedback log, and the session is reset for each iteration: a
+/// loop-owned session is created anew, while a caller-supplied session is restored from a snapshot taken at the start
+/// of the run (so the wrapped agent must support session serialization). An evaluator may instead supply the exact next
+/// messages via , bypassing this construction.
+///
+///
+/// The loop is bounded by a global safety cap () regardless of the
+/// evaluators. If an iteration produces a pending tool-approval request, the loop stops and returns that response to
+/// the caller rather than attempting to resolve the approval automatically.
+///
+///
+/// A non-streaming run returns, by default, a single that aggregates the full transcript
+/// in order: the on-behalf-of messages the loop injected for each re-invocation followed by that iteration's response
+/// messages. The caller's original input messages are not echoed. Set
+/// to instead return only the final iteration's
+/// response. A streaming run always yields every iteration's updates, emitting the injected on-behalf-of messages as
+/// updates before each re-invocation. The injected messages can be attributed with
+/// , or omitted from the surfaced output entirely with
+/// .
+///
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class LoopAgent : DelegatingAIAgent
+{
+ /// The default value used for when none is specified.
+ public const int DefaultMaxIterations = 10;
+
+ private readonly IReadOnlyList _evaluators;
+ private readonly int _maxIterations;
+ private readonly bool _freshContextPerIteration;
+ private readonly string? _onBehalfOfAuthorName;
+ private readonly bool _excludeOnBehalfOfMessages;
+ private readonly bool _nonStreamingReturnsLastResponseOnly;
+ private readonly System.Func? _sessionCreatedCallback;
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class with a single evaluator.
+ ///
+ /// The underlying agent to invoke in a loop.
+ /// The that decides whether to re-invoke the agent.
+ /// Optional configuration for the loop. When , defaults are used.
+ /// Optional factory used to create the loop's logger.
+ /// or is .
+ /// is less than 1.
+ public LoopAgent(AIAgent innerAgent, LoopEvaluator evaluator, LoopAgentOptions? options = null, ILoggerFactory? loggerFactory = null)
+ : this(innerAgent, [Throw.IfNull(evaluator)], options, loggerFactory)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with one or more evaluators.
+ ///
+ /// The underlying agent to invoke in a loop.
+ ///
+ /// The ordered set of that decide whether to re-invoke the agent. They are evaluated in
+ /// order after each iteration and the first that asks to re-invoke wins.
+ ///
+ /// Optional configuration for the loop. When , defaults are used.
+ /// Optional factory used to create the loop's logger.
+ /// or is , or contains a element.
+ /// is empty.
+ /// is less than 1.
+ public LoopAgent(AIAgent innerAgent, IEnumerable evaluators, LoopAgentOptions? options = null, ILoggerFactory? loggerFactory = null)
+ : base(innerAgent)
+ {
+ _ = Throw.IfNull(evaluators);
+ LoopEvaluator[] evaluatorArray = evaluators.ToArray();
+ if (evaluatorArray.Length == 0)
+ {
+ throw new System.ArgumentException("At least one evaluator must be supplied.", nameof(evaluators));
+ }
+
+ foreach (LoopEvaluator item in evaluatorArray)
+ {
+ _ = Throw.IfNull(item, nameof(evaluators));
+ }
+
+ this._evaluators = evaluatorArray;
+
+ this._maxIterations = Throw.IfLessThan(options?.MaxIterations ?? DefaultMaxIterations, 1);
+ this._freshContextPerIteration = options?.FreshContextPerIteration ?? false;
+ this._onBehalfOfAuthorName = options?.OnBehalfOfAuthorName;
+ this._excludeOnBehalfOfMessages = options?.ExcludeOnBehalfOfMessages ?? false;
+ this._nonStreamingReturnsLastResponseOnly = options?.NonStreamingReturnsLastResponseOnly ?? false;
+ this._sessionCreatedCallback = options?.SessionCreatedCallback;
+ this._logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger();
+ }
+
+ ///
+ protected override async Task RunCoreAsync(
+ IEnumerable messages,
+ AgentSession? session = null,
+ AgentRunOptions? options = null,
+ CancellationToken cancellationToken = default)
+ {
+ _ = Throw.IfNull(messages);
+
+ // Capture the caller's initial messages (sent once) and ensure the loop always runs against a session.
+ IReadOnlyList initialMessages = messages as IReadOnlyList ?? messages.ToList();
+ bool sessionProvidedByCaller = session is not null;
+ if (session is null)
+ {
+ session = await this.InnerAgent.CreateSessionAsync(cancellationToken).ConfigureAwait(false);
+ await this.NotifyNewSessionAsync(session, cancellationToken).ConfigureAwait(false);
+ }
+
+ // When a fresh context is requested over a caller-supplied session, snapshot the pristine session up front so
+ // each re-invocation can restart from a fresh clone (see CreateFreshIterationSessionAsync). Taken before the
+ // first iteration mutates the session.
+ JsonElement? initialSessionSnapshot = this._freshContextPerIteration && sessionProvidedByCaller
+ ? await this.InnerAgent.SerializeSessionAsync(session, cancellationToken: cancellationToken).ConfigureAwait(false)
+ : null;
+
+ LoopContext? context = null;
+ List feedbackLog = [];
+ IEnumerable currentMessages = initialMessages;
+ int iteration = 0;
+
+ // Aggregates the full transcript across iterations: each iteration's surfaced on-behalf-of input messages
+ // followed by that iteration's response messages. Unused when only the final response is returned.
+ List transcript = [];
+
+ // The loop-synthesized on-behalf-of messages that drive the current iteration (none for the first iteration).
+ IReadOnlyList currentSurfaced = [];
+
+ while (true)
+ {
+ // Run the wrapped agent using the context's session once it exists (it may have been replaced for a fresh
+ // context), otherwise the resolved session for the first run.
+ AgentSession activeSession = context?.Session ?? session;
+ AgentResponse response = await this.InnerAgent.RunAsync(currentMessages, activeSession, options, cancellationToken).ConfigureAwait(false);
+ iteration++;
+
+ // Record this iteration's on-behalf-of input (before the response it elicited) and the response itself.
+ transcript.AddRange(currentSurfaced);
+ transcript.AddRange(response.Messages);
+
+ // Create the context after the first run (so LastResponse is never null) and reuse it thereafter.
+ // Expose the feedback log as a read-only wrapper so evaluators cannot downcast and mutate it; the
+ // wrapper still reflects entries appended by the loop.
+ context ??= new LoopContext(this.InnerAgent, session, initialMessages, response, options) { Feedback = feedbackLog.AsReadOnly() };
+
+ context.Iteration = iteration;
+ context.LastResponse = response;
+
+ // Stop and surface the response when the agent is waiting for a tool approval.
+ if (HasPendingApprovalRequests(response))
+ {
+ return this.BuildResult(response, transcript);
+ }
+
+ // Enforce the global safety cap regardless of what the evaluators want.
+ if (iteration >= this._maxIterations)
+ {
+ this.LogMaxIterationsReached(iteration);
+ return this.BuildResult(response, transcript);
+ }
+
+ // Ask the evaluators whether to continue; stop when none of them request a re-invocation.
+ LoopNextStep step = await this.EvaluateAndBuildNextAsync(context, feedbackLog, initialSessionSnapshot, cancellationToken).ConfigureAwait(false);
+ if (!step.ShouldContinue)
+ {
+ return this.BuildResult(response, transcript);
+ }
+
+ currentMessages = step.Messages;
+ currentSurfaced = step.SurfacedMessages;
+ }
+ }
+
+ ///
+ protected override async IAsyncEnumerable RunCoreStreamingAsync(
+ IEnumerable messages,
+ AgentSession? session = null,
+ AgentRunOptions? options = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ _ = Throw.IfNull(messages);
+
+ // Capture the caller's initial messages (sent once) and ensure the loop always runs against a session.
+ IReadOnlyList initialMessages = messages as IReadOnlyList ?? messages.ToList();
+ bool sessionProvidedByCaller = session is not null;
+ if (session is null)
+ {
+ session = await this.InnerAgent.CreateSessionAsync(cancellationToken).ConfigureAwait(false);
+ await this.NotifyNewSessionAsync(session, cancellationToken).ConfigureAwait(false);
+ }
+
+ // When a fresh context is requested over a caller-supplied session, snapshot the pristine session up front so
+ // each re-invocation can restart from a fresh clone (see CreateFreshIterationSessionAsync). Taken before the
+ // first iteration mutates the session.
+ JsonElement? initialSessionSnapshot = this._freshContextPerIteration && sessionProvidedByCaller
+ ? await this.InnerAgent.SerializeSessionAsync(session, cancellationToken: cancellationToken).ConfigureAwait(false)
+ : null;
+
+ LoopContext? context = null;
+ List feedbackLog = [];
+ IEnumerable currentMessages = initialMessages;
+ int iteration = 0;
+
+ // The loop-synthesized on-behalf-of messages that drive the current iteration (none for the first iteration).
+ IReadOnlyList currentSurfaced = [];
+
+ while (true)
+ {
+ // Stream this iteration's updates to the caller while collecting them so the iteration's full
+ // response can be aggregated for evaluation (true per-iteration streaming). Uses the context's
+ // session once it exists (it may have been replaced for a fresh context), otherwise the resolved session.
+ AgentSession activeSession = context?.Session ?? session;
+ List updates = [];
+
+ // The on-behalf-of messages that drive this iteration are surfaced before the response they elicit (none
+ // for the first iteration). They are flushed lazily on the first inner update so they can be stamped with
+ // that update's ResponseId/AgentId, keeping them grouped with the iteration for downstream mergers.
+ bool surfacedPending = currentSurfaced.Count > 0;
+ await foreach (var update in this.InnerAgent.RunStreamingAsync(currentMessages, activeSession, options, cancellationToken).ConfigureAwait(false))
+ {
+ if (surfacedPending)
+ {
+ foreach (ChatMessage surfaced in currentSurfaced)
+ {
+ yield return CreateOnBehalfOfUpdate(surfaced, update.ResponseId);
+ }
+
+ surfacedPending = false;
+ }
+
+ updates.Add(update);
+ yield return update;
+ }
+
+ // The inner agent produced no updates this iteration; surface the on-behalf-of messages anyway. Since there
+ // is no iteration response to inherit from, generate a ResponseId so they still group together downstream.
+ if (surfacedPending)
+ {
+ string fallbackResponseId = System.Guid.NewGuid().ToString("N");
+ foreach (ChatMessage surfaced in currentSurfaced)
+ {
+ yield return CreateOnBehalfOfUpdate(surfaced, fallbackResponseId);
+ }
+ }
+
+ // Aggregate this iteration's updates and record the result on the context.
+ iteration++;
+ AgentResponse response = updates.ToAgentResponse();
+
+ // Create the context after the first run (so LastResponse is never null) and reuse it thereafter.
+ // Expose the feedback log as a read-only wrapper so evaluators cannot downcast and mutate it; the
+ // wrapper still reflects entries appended by the loop.
+ context ??= new LoopContext(this.InnerAgent, session, initialMessages, response, options) { Feedback = feedbackLog.AsReadOnly() };
+
+ context.Iteration = iteration;
+ context.LastResponse = response;
+
+ // Stop when the agent is waiting for a tool approval.
+ if (HasPendingApprovalRequests(response))
+ {
+ yield break;
+ }
+
+ // Enforce the global safety cap regardless of what the evaluators want.
+ if (iteration >= this._maxIterations)
+ {
+ this.LogMaxIterationsReached(iteration);
+ yield break;
+ }
+
+ // Ask the evaluators whether to continue; stop when none of them request a re-invocation.
+ LoopNextStep step = await this.EvaluateAndBuildNextAsync(context, feedbackLog, initialSessionSnapshot, cancellationToken).ConfigureAwait(false);
+ if (!step.ShouldContinue)
+ {
+ yield break;
+ }
+
+ currentMessages = step.Messages;
+ currentSurfaced = step.SurfacedMessages;
+ }
+ }
+
+ ///
+ /// Evaluates the evaluators in order and, for the first one that requests a re-invocation, builds the next input
+ /// according to the loop's feedback and fresh-context policy.
+ ///
+ private async ValueTask EvaluateAndBuildNextAsync(LoopContext context, List feedbackLog, JsonElement? initialSessionSnapshot, CancellationToken cancellationToken)
+ {
+ // Evaluate in order; the first evaluator that requests a re-invocation wins.
+ LoopEvaluation? winner = null;
+ foreach (LoopEvaluator evaluator in this._evaluators)
+ {
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context, cancellationToken).ConfigureAwait(false);
+ if (evaluation.ShouldReinvoke)
+ {
+ winner = evaluation;
+ break;
+ }
+ }
+
+ // Every evaluator asked to stop.
+ if (winner is null)
+ {
+ return LoopNextStep.Stop();
+ }
+
+ // Start the next iteration from a fresh session when a fresh context is requested, so no prior conversation
+ // history leaks across iterations. This applies regardless of how the next input is built (feedback or explicit
+ // ContinueWithMessages): a caller-supplied session is cloned from the pristine start-of-run snapshot; a
+ // loop-owned session is created anew.
+ if (this._freshContextPerIteration)
+ {
+ context.Session = await this.CreateFreshIterationSessionAsync(context, initialSessionSnapshot, cancellationToken).ConfigureAwait(false);
+ }
+
+ // Record one feedback entry for this re-invoked iteration (null when none, including ContinueWithMessages
+ // iterations which carry no feedback string) so the log stays aligned: one entry per re-invoked iteration, with
+ // the last element always corresponding to the latest re-invoked iteration. Continue() normalizes whitespace to null.
+ feedbackLog.Add(winner.Feedback);
+
+ // An evaluator supplied explicit messages: send them verbatim, bypassing feedback/message construction (the
+ // session is still reset above when a fresh context is requested). These are surfaced to the caller as-is (the
+ // evaluator owns them, including any author name).
+ if (winner.Messages is not null)
+ {
+ return LoopNextStep.Continue(winner.Messages, this.Surfaced(winner.Messages));
+ }
+
+ (List messages, List surfaced) = this.BuildNextMessages(context, feedbackLog);
+ return LoopNextStep.Continue(messages, this.Surfaced(surfaced));
+ }
+
+ ///
+ /// Returns the messages to surface to the caller, honoring .
+ ///
+ private IReadOnlyList Surfaced(IReadOnlyList surfaced)
+ => this._excludeOnBehalfOfMessages ? [] : surfaced;
+
+ ///
+ /// Creates a streaming update for a surfaced on-behalf-of message, inheriting the driven iteration's
+ /// so downstream mergers group it with that iteration, and ensuring a unique
+ /// non-null . The is left
+ /// unset because the message is synthesized by the loop, not produced by the wrapped agent.
+ ///
+ private static AgentResponseUpdate CreateOnBehalfOfUpdate(ChatMessage message, string? responseId)
+ => new(message.Role, message.Contents)
+ {
+ AuthorName = message.AuthorName,
+ MessageId = message.MessageId is { Length: > 0 } messageId ? messageId : System.Guid.NewGuid().ToString("N"),
+ ResponseId = responseId,
+ };
+
+ ///
+ /// Builds the messages sent to the wrapped agent for the next iteration along with the subset that should be
+ /// surfaced to the caller (the loop-synthesized on-behalf-of feedback). Replayed caller input is excluded from the
+ /// surfaced subset.
+ ///
+ private (List Messages, List Surfaced) BuildNextMessages(LoopContext context, List feedback)
+ {
+ var messages = new List();
+ var surfaced = new List();
+
+ if (this._freshContextPerIteration)
+ {
+ // Fresh context: re-send the original task plus an aggregated log of all feedback recorded so far. Only the
+ // synthesized feedback message is surfaced; the replayed caller input messages are not.
+ messages.AddRange(context.InitialMessages);
+
+ ChatMessage? feedbackMessage = this.BuildAggregatedFeedbackMessage(feedback);
+ if (feedbackMessage is not null)
+ {
+ messages.Add(feedbackMessage);
+ surfaced.Add(feedbackMessage);
+ }
+ }
+ else
+ {
+ // Reused session: send only the latest feedback verbatim (the session already retains earlier turns). When
+ // the latest iteration produced no feedback, send no messages and let the agent continue from history.
+ string? latest = feedback.Count > 0 ? feedback[feedback.Count - 1] : null;
+ if (!string.IsNullOrWhiteSpace(latest))
+ {
+ var feedbackMessage = new ChatMessage(ChatRole.User, latest) { AuthorName = this._onBehalfOfAuthorName, MessageId = System.Guid.NewGuid().ToString("N") };
+ messages.Add(feedbackMessage);
+ surfaced.Add(feedbackMessage);
+ }
+ }
+
+ return (messages, surfaced);
+ }
+
+ private ChatMessage? BuildAggregatedFeedbackMessage(IReadOnlyList feedback)
+ {
+ var body = new StringBuilder("## Feedback\n");
+ bool any = false;
+ foreach (string? entry in feedback)
+ {
+ if (!string.IsNullOrWhiteSpace(entry))
+ {
+ body.Append("\n- ").Append(entry);
+ any = true;
+ }
+ }
+
+ return any ? new ChatMessage(ChatRole.User, body.ToString()) { AuthorName = this._onBehalfOfAuthorName, MessageId = System.Guid.NewGuid().ToString("N") } : null;
+ }
+
+ ///
+ /// Produces the non-streaming run result: either the final iteration's response (when configured) or an
+ /// aggregated response carrying the full transcript with the final response's metadata.
+ ///
+ private AgentResponse BuildResult(AgentResponse lastResponse, List transcript)
+ {
+ if (this._nonStreamingReturnsLastResponseOnly)
+ {
+ return lastResponse;
+ }
+
+ return new AgentResponse(transcript)
+ {
+ AgentId = lastResponse.AgentId,
+ ResponseId = lastResponse.ResponseId,
+ CreatedAt = lastResponse.CreatedAt,
+ FinishReason = lastResponse.FinishReason,
+ Usage = lastResponse.Usage,
+ AdditionalProperties = lastResponse.AdditionalProperties,
+ ContinuationToken = lastResponse.ContinuationToken,
+ };
+ }
+
+ private static bool HasPendingApprovalRequests(AgentResponse response)
+ {
+ foreach (ChatMessage message in response.Messages)
+ {
+ foreach (AIContent content in message.Contents)
+ {
+ if (content is ToolApprovalRequestContent)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void LogMaxIterationsReached(int iteration)
+ {
+ if (this._logger.IsEnabled(LogLevel.Information))
+ {
+ this._logger.LogInformation("LoopAgent reached the maximum of {MaxIterations} iterations and stopped.", iteration);
+ }
+ }
+
+ ///
+ /// Creates the session used for the next iteration when a fresh context is requested. A caller-supplied session is
+ /// restored from the pristine start-of-run snapshot by deserializing a fresh clone; a loop-owned session (no
+ /// snapshot) is created anew. The configured session-created callback is notified of the new session.
+ ///
+ private async ValueTask CreateFreshIterationSessionAsync(LoopContext context, JsonElement? initialSessionSnapshot, CancellationToken cancellationToken)
+ {
+ AgentSession session = initialSessionSnapshot is { } snapshot
+ ? await this.InnerAgent.DeserializeSessionAsync(snapshot, cancellationToken: cancellationToken).ConfigureAwait(false)
+ : await context.Agent.CreateSessionAsync(cancellationToken).ConfigureAwait(false);
+
+ await this.NotifyNewSessionAsync(session, cancellationToken).ConfigureAwait(false);
+ return session;
+ }
+
+ ///
+ /// Invokes the configured (if any) with a session the loop
+ /// has just created, so the caller can observe the latest session.
+ ///
+ private async ValueTask NotifyNewSessionAsync(AgentSession session, CancellationToken cancellationToken)
+ {
+ if (this._sessionCreatedCallback is not null)
+ {
+ await this._sessionCreatedCallback(session, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ /// Represents the loop's decision for the next iteration: stop, or continue with a set of messages.
+ private readonly struct LoopNextStep
+ {
+ private LoopNextStep(bool shouldContinue, IReadOnlyList messages, IReadOnlyList surfacedMessages)
+ {
+ this.ShouldContinue = shouldContinue;
+ this.Messages = messages;
+ this.SurfacedMessages = surfacedMessages;
+ }
+
+ public bool ShouldContinue { get; }
+
+ /// Gets the full set of messages sent to the wrapped agent for the next iteration.
+ public IReadOnlyList Messages { get; }
+
+ ///
+ /// Gets the subset of the loop synthesized on the caller's behalf (feedback or
+ /// evaluator-supplied messages) that should be surfaced to the caller. Replayed caller input is excluded.
+ ///
+ public IReadOnlyList SurfacedMessages { get; }
+
+ public static LoopNextStep Stop() => new(shouldContinue: false, [], []);
+
+ public static LoopNextStep Continue(IReadOnlyList messages, IReadOnlyList surfacedMessages)
+ => new(shouldContinue: true, messages, surfacedMessages);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgentOptions.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgentOptions.cs
new file mode 100644
index 0000000000..ec009b4594
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgentOptions.cs
@@ -0,0 +1,117 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Shared.DiagnosticIds;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides configuration options for .
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class LoopAgentOptions
+{
+ ///
+ /// Gets or sets the global safety cap on the number of times the wrapped agent is invoked in a single loop run,
+ /// or to use .
+ ///
+ ///
+ /// This is an absolute upper bound that applies regardless of the configured set. An
+ /// evaluator may stop the loop earlier, but no evaluator can cause the loop to exceed this cap, so raise this value
+ /// if you intend to allow longer loops.
+ ///
+ public int? MaxIterations { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether each re-invocation restarts from a clean context: the original input
+ /// messages plus an aggregated feedback log, rather than the latest feedback appended to the prior conversation.
+ /// Defaults to .
+ ///
+ ///
+ ///
+ /// This rebuilds the input messages each iteration and resets the session before each re-invocation so no
+ /// prior conversation history leaks across iterations. When the loop owns the session it creates a new one each
+ /// iteration. When the caller supplies a session, serializes it once at the start of the run
+ /// and restores a fresh clone (by deserializing that snapshot) before each re-invocation; this requires the wrapped
+ /// agent to support session serialization. The first iteration still runs against the caller's supplied session.
+ ///
+ ///
+ /// Note that cloning will only result in a fresh context, if the chat history storage mechanism supports cloning.
+ /// For example the default in-memory storage supports cloning, since the messages are serialized as part of the snapshot.
+ ///
+ ///
+ /// However, if the Conversations service is used, which stores messages in a single threaded list of messages,
+ /// then the cloned session will still contain the full message history, since the snapshot only captures an id reference
+ /// to the conversation and not the individual messages.
+ ///
+ ///
+ /// On the other hand, if responses are used with response ids, cloning will work well, since response ids are
+ /// forkable. Each new response has its own id, and is based on the id of the previous response.
+ ///
+ ///
+ /// On iterations where an evaluator returns explicit messages via
+ /// , the session is still reset (a fresh or cloned session is
+ /// used); only the rebuild of the input messages from the feedback log is skipped, because the evaluator's explicit
+ /// messages are sent verbatim.
+ ///
+ ///
+ public bool FreshContextPerIteration { get; set; }
+
+ ///
+ /// Gets or sets the author name stamped on the loop-synthesized "on-behalf-of" messages that the loop injects
+ /// into the wrapped agent for re-invocations, or to leave them unattributed. Defaults to
+ /// .
+ ///
+ ///
+ /// When the loop re-invokes the wrapped agent it sends feedback messages on the caller's behalf. Setting this name
+ /// marks those autonomous messages (for example with a value such as "loop") so that callers and the wrapped
+ /// agent can distinguish them from the caller's own turns. It is applied only to messages the loop synthesizes
+ /// itself; messages supplied explicitly by an evaluator via are
+ /// left untouched, and the caller's original input messages are never modified.
+ ///
+ public string? OnBehalfOfAuthorName { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the on-behalf-of messages the loop injects for re-invocations are
+ /// omitted from the output surfaced back to the caller. Defaults to .
+ ///
+ ///
+ /// When (the default) a streaming run emits the injected feedback / evaluator-supplied
+ /// messages as updates before each re-invocation, and a non-streaming run includes them in the aggregated
+ /// transcript, so callers can see the loop acting autonomously on their behalf. Set this to
+ /// to omit those messages from the returned output and surface only the wrapped agent's responses; the messages are
+ /// still sent to the wrapped agent. This setting has no effect when
+ /// causes a non-streaming run to return only the final response.
+ ///
+ public bool ExcludeOnBehalfOfMessages { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a non-streaming run returns only the final iteration's response instead
+ /// of the aggregated transcript of every iteration. Defaults to .
+ ///
+ ///
+ /// By default a non-streaming run returns a single that
+ /// aggregates, in order, the on-behalf-of messages the loop injected and the responses produced by every
+ /// iteration — mirroring the full sequence of updates yielded by a streaming run. Set this to
+ /// to instead return only the last iteration's . This setting affects non-streaming runs
+ /// only; streaming runs always yield every iteration's updates.
+ ///
+ public bool NonStreamingReturnsLastResponseOnly { get; set; }
+
+ ///
+ /// Gets or sets an optional callback invoked whenever creates a new session, so the caller
+ /// can capture the latest session (for example to continue the conversation after the loop completes). Defaults to
+ /// .
+ ///
+ ///
+ /// The callback is invoked with each session the loop itself creates: the initial loop-owned session (when the
+ /// caller does not supply one) and, when is enabled, every session created
+ /// for a re-invocation — whether a brand-new loop-owned session or a fresh clone deserialized from the caller's
+ /// original session. It is not invoked for a caller-supplied session, since the caller already holds that one. When
+ /// it fires multiple times, the most recent invocation carries the session the loop is currently using.
+ ///
+ public Func? SessionCreatedCallback { get; set; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopContext.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopContext.cs
new file mode 100644
index 0000000000..d0bdf03e7b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopContext.cs
@@ -0,0 +1,97 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides the per-run state that a uses to decide whether a
+/// should re-invoke the wrapped agent and what feedback to provide.
+///
+///
+/// A single instance is created for each run and is
+/// reused across iterations, with and updated before
+/// each call to . Because evaluator instances are expected to be
+/// stateless and may be shared across concurrent runs, any per-run mutable state must be stored on this
+/// context — for example via — rather than in fields on the evaluator itself.
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class LoopContext
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The wrapped that is being looped.
+ /// The used for the loop.
+ /// The messages passed in for the first iteration of the loop.
+ /// The produced by the iteration that just completed.
+ /// The that were passed to the loop run, if any.
+ ///
+ /// , , , or
+ /// is .
+ ///
+ public LoopContext(
+ AIAgent agent,
+ AgentSession session,
+ IReadOnlyList initialMessages,
+ AgentResponse lastResponse,
+ AgentRunOptions? runOptions = null)
+ {
+ this.Agent = Throw.IfNull(agent);
+ this.Session = Throw.IfNull(session);
+ this.InitialMessages = Throw.IfNull(initialMessages);
+ this.LastResponse = Throw.IfNull(lastResponse);
+ this.RunOptions = runOptions;
+ }
+
+ /// Gets the wrapped that is being looped.
+ public AIAgent Agent { get; }
+
+ /// Gets the used for the loop.
+ ///
+ /// When the caller does not provide a session, creates one up front. By default the same
+ /// session is reused across every iteration so that conversation continuity is preserved and the original request
+ /// is not replayed. When is enabled,
+ /// resets the session before each re-invocation: a loop-owned session is created anew, while a caller-supplied
+ /// session is restored from a snapshot taken at the start of the run by deserializing a fresh clone.
+ ///
+ public AgentSession Session { get; internal set; }
+
+ /// Gets the messages that were passed in for the first iteration of the loop.
+ public IReadOnlyList InitialMessages { get; }
+
+ /// Gets the that were passed to the loop run, if any.
+ public AgentRunOptions? RunOptions { get; }
+
+ /// Gets the number of completed agent runs so far (1-based after the first run).
+ public int Iteration { get; internal set; }
+
+ /// Gets the produced by the iteration that just completed.
+ public AgentResponse LastResponse { get; internal set; }
+
+ ///
+ /// Gets the feedback accumulated across iterations so far, one entry per re-invoked iteration in order.
+ ///
+ ///
+ /// Each entry is the feedback supplied by the evaluator that requested the corresponding re-invocation, or
+ /// when that iteration produced no feedback string (for example a plain
+ /// with no text, or a
+ /// that supplied explicit messages instead). The log records one entry per re-invoked iteration regardless of mode,
+ /// so the last entry always corresponds to the most recent re-invoked iteration. This log is owned and populated by
+ /// ; evaluators may read it to reason over prior feedback.
+ ///
+ public IReadOnlyList Feedback { get; internal set; } = [];
+
+ ///
+ /// Gets a mutable bag of per-run state shared across iterations and available to every evaluator.
+ ///
+ ///
+ /// This dictionary is owned by the loop run (not by any evaluator instance) so that evaluators can remain
+ /// stateless. Evaluators can stash arbitrary per-run state here keyed by a collision-resistant key.
+ ///
+ public AdditionalPropertiesDictionary AdditionalProperties { get; } = new();
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopEvaluation.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopEvaluation.cs
new file mode 100644
index 0000000000..2d8de152e8
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopEvaluation.cs
@@ -0,0 +1,86 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Represents the result produced by a after an agent iteration: whether the
+/// should re-invoke the wrapped agent and, optionally, the feedback or explicit messages that
+/// should inform the next iteration.
+///
+///
+/// An evaluator is concerned only with the judgment (continue or stop) and what to carry forward. In the common case
+/// it returns a feedback string and lets the decide how that feedback is turned into the next
+/// input (and whether the session is reset). For full control, supplies the exact
+/// messages to send next, bypassing the loop's feedback and message construction.
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class LoopEvaluation
+{
+ private static readonly LoopEvaluation s_stop = new(shouldReinvoke: false, feedback: null, messages: null);
+
+ private LoopEvaluation(bool shouldReinvoke, string? feedback, IReadOnlyList? messages)
+ {
+ this.ShouldReinvoke = shouldReinvoke;
+ this.Feedback = feedback;
+ this.Messages = messages;
+ }
+
+ /// Gets a value indicating whether the loop should run the wrapped agent again.
+ public bool ShouldReinvoke { get; }
+
+ ///
+ /// Gets the feedback describing what is missing or what the agent should do next, or when
+ /// no feedback was produced.
+ ///
+ /// This value is only meaningful when is .
+ public string? Feedback { get; }
+
+ ///
+ /// Gets the explicit messages to send on the next iteration, or when the loop should build
+ /// the next input from feedback instead.
+ ///
+ ///
+ /// When non-, the sends these messages verbatim and does not apply
+ /// its feedback or message construction. The session is still reset when
+ /// is enabled. Only meaningful when
+ /// is .
+ ///
+ internal IReadOnlyList? Messages { get; }
+
+ /// Creates an evaluation that stops the loop and returns the latest response to the caller.
+ /// An evaluation with set to .
+ public static LoopEvaluation Stop() => s_stop;
+
+ /// Creates an evaluation that re-invokes the wrapped agent, optionally carrying feedback forward.
+ ///
+ /// Optional feedback to inform the next iteration. , empty, or whitespace is treated as no
+ /// feedback.
+ ///
+ /// An evaluation with set to .
+ public static LoopEvaluation Continue(string? feedback = null) => new(shouldReinvoke: true, string.IsNullOrWhiteSpace(feedback) ? null : feedback, messages: null);
+
+ ///
+ /// Creates an evaluation that re-invokes the wrapped agent with the specified messages, bypassing the loop's
+ /// feedback and message construction.
+ ///
+ /// The messages to send to the wrapped agent on the next iteration.
+ /// An evaluation with set to .
+ /// is .
+ ///
+ /// Use this for full control over the next input (for example to send non-user roles, multiple messages, or
+ /// non-text content). The supplied messages are sent verbatim and the loop does not accumulate or inject feedback
+ /// for this iteration.
+ ///
+ public static LoopEvaluation ContinueWithMessages(IEnumerable messages)
+ {
+ _ = Throw.IfNull(messages);
+ return new LoopEvaluation(shouldReinvoke: true, feedback: null, messages: messages as IReadOnlyList ?? messages.ToList());
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopEvaluator.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopEvaluator.cs
new file mode 100644
index 0000000000..328c99e80c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopEvaluator.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Shared.DiagnosticIds;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides the abstract base class for the component that decides, after each agent iteration, whether a
+/// should re-invoke the wrapped agent and what feedback to provide.
+///
+///
+///
+/// A is pure judgment: it inspects the and returns a
+/// describing whether to continue and any feedback for the next iteration. It does not
+/// manage the session or construct the next input messages — that is the responsibility of the
+/// that consumes it.
+///
+///
+/// Out-of-the-box implementations include , ,
+/// and . Implementations should be stateless and safe to share across
+/// concurrent loop runs; any per-run state must be stored on the supplied .
+///
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public abstract class LoopEvaluator
+{
+ ///
+ /// Evaluates the loop state after an iteration and decides whether to re-invoke the wrapped agent and what
+ /// feedback to provide.
+ ///
+ /// The per-run describing the current loop state.
+ /// The to monitor for cancellation requests.
+ ///
+ /// A value task whose result is a indicating whether to continue and, if so, the
+ /// feedback to carry forward to the next iteration.
+ ///
+ public abstract ValueTask EvaluateAsync(LoopContext context, CancellationToken cancellationToken = default);
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopJsonContext.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopJsonContext.cs
new file mode 100644
index 0000000000..8d69383e3f
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopJsonContext.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Source-generated for loop types that require JSON serialization, such as the
+/// structured used by .
+///
+[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
+[JsonSerializable(typeof(JudgeVerdict))]
+[ExcludeFromCodeCoverage]
+internal sealed partial class LoopJsonContext : JsonSerializerContext;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs
index effad5fc53..9e6539fa88 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs
@@ -27,7 +27,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
#else
private const string BuildConfiguration = "Release";
#endif
- private static readonly HttpClient s_sharedHttpClient = new();
+ private static readonly HttpClient s_sharedHttpClient = new() { Timeout = TimeSpan.FromMinutes(3) };
private static readonly IConfiguration s_configuration =
new ConfigurationBuilder()
.AddEnvironmentVariables()
@@ -60,7 +60,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
await Task.CompletedTask;
}
- [RetryFact(2, 5000, Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [RetryFact(2, 5000)]
public async Task SingleAgentSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "01_SingleAgent");
@@ -148,7 +148,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
});
}
- [RetryFact(2, 5000, Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [RetryFact(2, 5000)]
public async Task MultiAgentOrchestrationConcurrentSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "03_AgentOrchestration_Concurrency");
@@ -198,7 +198,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
});
}
- [RetryFact(2, 5000, Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [RetryFact(2, 5000)]
public async Task MultiAgentOrchestrationConditionalsSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "04_AgentOrchestration_Conditionals");
@@ -216,7 +216,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
});
}
- [RetryFact(2, 5000, Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [RetryFact(2, 5000)]
public async Task SingleAgentOrchestrationHITLSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "05_AgentOrchestration_HITL");
@@ -272,7 +272,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
});
}
- [RetryFact(2, 5000, Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [RetryFact(2, 5000)]
public async Task LongRunningToolsSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "06_LongRunningTools");
@@ -362,7 +362,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
});
}
- [RetryFact(2, 5000, Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [RetryFact(2, 5000)]
public async Task AgentAsMcpToolAsync()
{
string samplePath = Path.Combine(s_samplesPath, "07_AgentAsMcpTool");
@@ -402,7 +402,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
});
}
- [RetryFact(2, 5000, Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [RetryFact(2, 5000)]
public async Task ReliableStreamingSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "08_ReliableStreaming");
@@ -844,6 +844,7 @@ public sealed class SamplesValidation(ITestOutputHelper outputHelper) : IAsyncLi
throw new InvalidOperationException("The required AZURE_OPENAI_DEPLOYMENT_NAME env variable is not set.");
// Set required environment variables for the function app (see local.settings.json for required settings)
+ startInfo.EnvironmentVariables["FUNCTIONS_WORKER_RUNTIME"] = "dotnet-isolated";
startInfo.EnvironmentVariables["AZURE_OPENAI_ENDPOINT"] = openAiEndpoint;
startInfo.EnvironmentVariables["AZURE_OPENAI_DEPLOYMENT_NAME"] = openAiDeployment;
startInfo.EnvironmentVariables["DURABLE_TASK_SCHEDULER_CONNECTION_STRING"] =
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
index 2a51cb467e..554e7f6beb 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
@@ -62,7 +62,7 @@ public sealed class WorkflowSamplesValidation(ITestOutputHelper outputHelper) :
return default;
}
- [Fact(Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [Fact]
public async Task SequentialWorkflowSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "01_SequentialWorkflow");
@@ -168,7 +168,7 @@ public sealed class WorkflowSamplesValidation(ITestOutputHelper outputHelper) :
});
}
- [Fact(Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [Fact]
public async Task HITLWorkflowSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "03_WorkflowHITL");
@@ -277,7 +277,7 @@ public sealed class WorkflowSamplesValidation(ITestOutputHelper outputHelper) :
});
}
- [Fact(Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [Fact]
public async Task WorkflowMcpToolSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "04_WorkflowMcpTool");
@@ -333,7 +333,7 @@ public sealed class WorkflowSamplesValidation(ITestOutputHelper outputHelper) :
});
}
- [Fact(Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [Fact]
public async Task WorkflowAndAgentsSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "05_WorkflowAndAgents");
@@ -385,7 +385,7 @@ public sealed class WorkflowSamplesValidation(ITestOutputHelper outputHelper) :
});
}
- [Fact(Skip = "Azure Functions Core Tools v4 cannot auto-detect worker runtime in CI. See https://github.com/microsoft/agent-framework/issues/6402")]
+ [Fact]
public async Task ConcurrentWorkflowSampleValidationAsync()
{
string samplePath = Path.Combine(s_samplesPath, "02_ConcurrentWorkflow");
@@ -619,6 +619,7 @@ public sealed class WorkflowSamplesValidation(ITestOutputHelper outputHelper) :
startInfo.EnvironmentVariables["AZURE_OPENAI_DEPLOYMENT"] = openAiDeployment;
}
+ startInfo.EnvironmentVariables["FUNCTIONS_WORKER_RUNTIME"] = "dotnet-isolated";
startInfo.EnvironmentVariables["DURABLE_TASK_SCHEDULER_CONNECTION_STRING"] =
$"Endpoint=http://localhost:{DtsPort};TaskHub=default;Authentication=None";
startInfo.EnvironmentVariables["AzureWebJobsStorage"] = "UseDevelopmentStorage=true";
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/AIJudgeLoopEvaluatorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/AIJudgeLoopEvaluatorTests.cs
new file mode 100644
index 0000000000..d91494ba03
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/AIJudgeLoopEvaluatorTests.cs
@@ -0,0 +1,314 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Moq;
+
+using static Microsoft.Agents.AI.UnitTests.LoopTestHelpers;
+
+namespace Microsoft.Agents.AI.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public class AIJudgeLoopEvaluatorTests
+{
+ ///
+ /// Verify that the evaluator stops when the judge reports the request was answered.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_Answered_StopsAsync()
+ {
+ // Arrange
+ var judgeClient = CreateJudgeClient("{\"answered\":true}");
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient);
+ LoopContext context = CreateContext();
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.False(evaluation.ShouldReinvoke);
+ }
+
+ ///
+ /// Verify that when not answered the evaluator continues with feedback carrying the judge's gap analysis.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_NotAnswered_ContinuesWithGapAnalysisAsync()
+ {
+ // Arrange
+ var judgeClient = CreateJudgeClient("{\"answered\":false,\"gapAnalysis\":\"the cost estimate is missing\"}");
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient);
+ LoopContext context = CreateContext();
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.True(evaluation.ShouldReinvoke);
+ Assert.NotNull(evaluation.Feedback);
+ Assert.Contains("the cost estimate is missing", evaluation.Feedback!);
+ Assert.DoesNotContain(AIJudgeLoopEvaluator.GapAnalysisPlaceholder, evaluation.Feedback!);
+ }
+
+ ///
+ /// Verify that the evaluator falls back to text parsing and stops when the DONE verdict marker is present.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_TextFallback_StopsWhenAnsweredAsync()
+ {
+ // Arrange
+ var judgeClient = CreateJudgeClient(AIJudgeLoopEvaluator.DoneVerdictMarker);
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient);
+ LoopContext context = CreateContext();
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.False(evaluation.ShouldReinvoke);
+ }
+
+ ///
+ /// Verify that the gap-analysis placeholder is filled with a fallback token when no structured output is produced.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_NotAnswered_TextFallback_InjectsUnknownGapAnalysisAsync()
+ {
+ // Arrange
+ var judgeClient = CreateJudgeClient(AIJudgeLoopEvaluator.MoreVerdictMarker);
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient);
+ LoopContext context = CreateContext();
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.True(evaluation.ShouldReinvoke);
+ Assert.Contains("", evaluation.Feedback!);
+ }
+
+ ///
+ /// Verify that the text fallback keeps looping for replies that merely contain the substring "ANSWERED" (for
+ /// example "UNANSWERED" or "NOT ANSWERED") rather than the explicit DONE verdict marker.
+ ///
+ [Theory]
+ [InlineData("UNANSWERED")]
+ [InlineData("NOT ANSWERED")]
+ [InlineData("The request is not yet answered.")]
+ public async Task EvaluateAsync_TextFallback_AmbiguousReply_ContinuesAsync(string reply)
+ {
+ // Arrange
+ var judgeClient = CreateJudgeClient(reply);
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient);
+ LoopContext context = CreateContext();
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.True(evaluation.ShouldReinvoke);
+ }
+
+ ///
+ /// Verify that custom judge instructions from options are sent to the judge client.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_CustomInstructions_AreSentToJudgeAsync()
+ {
+ // Arrange
+ List? judgeMessages = null;
+ var judgeMock = new Mock();
+ judgeMock.Setup(c => c.GetResponseAsync(
+ It.IsAny>(),
+ It.IsAny(),
+ It.IsAny()))
+ .Callback, ChatOptions?, CancellationToken>((msgs, _, _) => judgeMessages = msgs.ToList())
+ .ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "{\"answered\":true}")));
+ var evaluator = new AIJudgeLoopEvaluator(judgeMock.Object, new AIJudgeLoopEvaluatorOptions { Instructions = "CUSTOM JUDGE PROMPT" });
+ LoopContext context = CreateContext();
+
+ // Act
+ await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.NotNull(judgeMessages);
+ Assert.Contains(judgeMessages!, m => m.Role == ChatRole.System && m.Text == "CUSTOM JUDGE PROMPT");
+ }
+
+ ///
+ /// Verify that a custom feedback message template from options is honored.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_CustomFeedbackMessageTemplate_IsHonoredAsync()
+ {
+ // Arrange
+ var judgeClient = CreateJudgeClient("{\"answered\":false,\"gapAnalysis\":\"add unit tests\"}");
+ const string Template = "Please address: " + AIJudgeLoopEvaluator.GapAnalysisPlaceholder;
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient, new AIJudgeLoopEvaluatorOptions { FeedbackMessageTemplate = Template });
+ LoopContext context = CreateContext();
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.Equal("Please address: add unit tests", evaluation.Feedback);
+ }
+
+ ///
+ /// Verify that non-text content in the original request (for example an image) is forwarded to the judge
+ /// rather than being silently dropped when flattening the request to text.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_NonTextRequestContent_IsForwardedToJudgeAsync()
+ {
+ // Arrange
+ List? judgeMessages = null;
+ var judgeMock = new Mock();
+ judgeMock.Setup(c => c.GetResponseAsync(
+ It.IsAny>(),
+ It.IsAny(),
+ It.IsAny()))
+ .Callback, ChatOptions?, CancellationToken>((msgs, _, _) => judgeMessages = msgs.ToList())
+ .ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "{\"answered\":true}")));
+ var evaluator = new AIJudgeLoopEvaluator(judgeMock.Object);
+ var imageContent = new DataContent(new byte[] { 1, 2, 3, 4 }, "image/png");
+ var context = new LoopContext(
+ new Mock().Object,
+ new ChatClientAgentSession(),
+ [new ChatMessage(ChatRole.User, [imageContent])],
+ new AgentResponse([new ChatMessage(ChatRole.Assistant, "partial answer")]));
+
+ // Act
+ await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.NotNull(judgeMessages);
+ ChatMessage userMessage = Assert.Single(judgeMessages!, m => m.Role == ChatRole.User);
+ Assert.Contains(userMessage.Contents.OfType(), c => c.MediaType == "image/png");
+ }
+
+ ///
+ /// Verify that the constructor throws when the judge client is null.
+ ///
+ [Fact]
+ public void AIJudgeLoopEvaluator_NullClient_Throws()
+ {
+ // Act & Assert
+ Assert.Throws("judgeClient", () => new AIJudgeLoopEvaluator(null!));
+ }
+
+ ///
+ /// Verify that EvaluateAsync throws when the context is null.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_NullContext_ThrowsAsync()
+ {
+ // Arrange
+ var evaluator = new AIJudgeLoopEvaluator(CreateJudgeClient("{\"answered\":true}"));
+
+ // Act & Assert
+ await Assert.ThrowsAsync("context", async () => await evaluator.EvaluateAsync(null!));
+ }
+
+ ///
+ /// Verify that supplied criteria are rendered into the default judge instructions as a bullet list and the
+ /// placeholder is consumed.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_Criteria_AreRenderedIntoDefaultInstructionsAsync()
+ {
+ // Arrange
+ var judgeClient = CreateCapturingJudgeClient("{\"answered\":true}", out List judgeMessages);
+ var options = new AIJudgeLoopEvaluatorOptions { Criteria = ["Must cite sources", "Must be under 200 words"] };
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient, options);
+ LoopContext context = CreateContext();
+
+ // Act
+ await evaluator.EvaluateAsync(context);
+
+ // Assert
+ string system = judgeMessages.Single(static m => m.Role == ChatRole.System).Text;
+ Assert.Contains("The response must satisfy all of the following criteria:", system);
+ Assert.Contains("- Must cite sources", system);
+ Assert.Contains("- Must be under 200 words", system);
+ Assert.DoesNotContain(AIJudgeLoopEvaluator.CriteriaPlaceholder, system);
+ }
+
+ ///
+ /// Verify that when no criteria are supplied the placeholder is removed and no criteria block is added to the
+ /// default instructions.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_NoCriteria_LeavesDefaultInstructionsWithoutCriteriaBlockAsync()
+ {
+ // Arrange
+ var judgeClient = CreateCapturingJudgeClient("{\"answered\":true}", out List judgeMessages);
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient);
+ LoopContext context = CreateContext();
+
+ // Act
+ await evaluator.EvaluateAsync(context);
+
+ // Assert
+ string system = judgeMessages.Single(static m => m.Role == ChatRole.System).Text;
+ Assert.DoesNotContain(AIJudgeLoopEvaluator.CriteriaPlaceholder, system);
+ Assert.DoesNotContain("The response must satisfy all of the following criteria:", system);
+ }
+
+ ///
+ /// Verify that criteria are injected at the placeholder location in custom instructions.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_CustomInstructionsWithPlaceholder_InjectsCriteriaAsync()
+ {
+ // Arrange
+ var judgeClient = CreateCapturingJudgeClient("{\"answered\":true}", out List judgeMessages);
+ const string Instructions = "Judge the answer." + AIJudgeLoopEvaluator.CriteriaPlaceholder + " Be strict.";
+ var options = new AIJudgeLoopEvaluatorOptions { Instructions = Instructions, Criteria = ["Must include code"] };
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient, options);
+ LoopContext context = CreateContext();
+
+ // Act
+ await evaluator.EvaluateAsync(context);
+
+ // Assert
+ string system = judgeMessages.Single(static m => m.Role == ChatRole.System).Text;
+ Assert.StartsWith("Judge the answer.", system);
+ Assert.EndsWith("Be strict.", system);
+ Assert.Contains("- Must include code", system);
+ Assert.DoesNotContain(AIJudgeLoopEvaluator.CriteriaPlaceholder, system);
+ }
+
+ ///
+ /// Verify that custom instructions without the placeholder do not receive the criteria.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_CustomInstructionsWithoutPlaceholder_OmitsCriteriaAsync()
+ {
+ // Arrange
+ var judgeClient = CreateCapturingJudgeClient("{\"answered\":true}", out List judgeMessages);
+ const string Instructions = "Judge the answer and be strict.";
+ var options = new AIJudgeLoopEvaluatorOptions { Instructions = Instructions, Criteria = ["Must include code"] };
+ var evaluator = new AIJudgeLoopEvaluator(judgeClient, options);
+ LoopContext context = CreateContext();
+
+ // Act
+ await evaluator.EvaluateAsync(context);
+
+ // Assert
+ string system = judgeMessages.Single(static m => m.Role == ChatRole.System).Text;
+ Assert.Equal(Instructions, system);
+ }
+
+ private static LoopContext CreateContext() => new(
+ new Mock().Object,
+ new ChatClientAgentSession(),
+ [new ChatMessage(ChatRole.User, "original question")],
+ new AgentResponse([new ChatMessage(ChatRole.Assistant, "partial answer")]));
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/CompletionMarkerLoopEvaluatorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/CompletionMarkerLoopEvaluatorTests.cs
new file mode 100644
index 0000000000..81f6cc532f
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/CompletionMarkerLoopEvaluatorTests.cs
@@ -0,0 +1,145 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Moq;
+
+namespace Microsoft.Agents.AI.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public class CompletionMarkerLoopEvaluatorTests
+{
+ ///
+ /// Verify that the constructor throws when the marker is null, empty, or whitespace.
+ ///
+ /// The invalid marker value.
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void CompletionMarkerLoopEvaluator_InvalidMarker_Throws(string? marker)
+ {
+ // Act & Assert
+ Assert.ThrowsAny(() => new CompletionMarkerLoopEvaluator(marker!));
+ }
+
+ ///
+ /// Verify that the evaluator stops the loop when the marker appears in the latest response.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_MarkerPresent_StopsAsync()
+ {
+ // Arrange
+ var evaluator = new CompletionMarkerLoopEvaluator("DONE");
+ LoopContext context = CreateContext("all DONE here");
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.False(evaluation.ShouldReinvoke);
+ }
+
+ ///
+ /// Verify that the evaluator continues with default feedback (containing the marker) when the marker is absent.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_MarkerAbsent_ContinuesWithDefaultFeedbackAsync()
+ {
+ // Arrange
+ var evaluator = new CompletionMarkerLoopEvaluator("DONE");
+ LoopContext context = CreateContext("still working");
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.True(evaluation.ShouldReinvoke);
+ Assert.NotNull(evaluation.Feedback);
+ Assert.Contains("DONE", evaluation.Feedback!);
+ Assert.DoesNotContain(CompletionMarkerLoopEvaluator.CompletionMarkerPlaceholder, evaluation.Feedback!);
+ }
+
+ ///
+ /// Verify that a custom feedback template is honored, with the completion marker substituted for the placeholder.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_MarkerAbsent_CustomTemplate_IsHonoredAsync()
+ {
+ // Arrange
+ const string Template = "Keep going and finish with " + CompletionMarkerLoopEvaluator.CompletionMarkerPlaceholder + " when done.";
+ var evaluator = new CompletionMarkerLoopEvaluator("FINISHED", new CompletionMarkerLoopEvaluatorOptions { FeedbackMessageTemplate = Template });
+ LoopContext context = CreateContext("still working");
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.True(evaluation.ShouldReinvoke);
+ Assert.Equal("Keep going and finish with FINISHED when done.", evaluation.Feedback);
+ }
+
+ ///
+ /// Verify that a custom feedback template containing the last-response placeholder echoes the agent's latest
+ /// response text, with no leftover placeholder.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_MarkerAbsent_CustomTemplate_SubstitutesLastResponseAsync()
+ {
+ // Arrange
+ const string Template = "Your previous attempt was: '" + CompletionMarkerLoopEvaluator.LastResponsePlaceholder +
+ "'. Improve it and finish with " + CompletionMarkerLoopEvaluator.CompletionMarkerPlaceholder + " when done.";
+ var evaluator = new CompletionMarkerLoopEvaluator("FINISHED", new CompletionMarkerLoopEvaluatorOptions { FeedbackMessageTemplate = Template });
+ LoopContext context = CreateContext("candidate name: NoteNest");
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.True(evaluation.ShouldReinvoke);
+ Assert.Equal("Your previous attempt was: 'candidate name: NoteNest'. Improve it and finish with FINISHED when done.", evaluation.Feedback);
+ Assert.DoesNotContain(CompletionMarkerLoopEvaluator.LastResponsePlaceholder, evaluation.Feedback!);
+ }
+
+ ///
+ /// Verify that the default feedback template does not include the agent's latest response text (the last-response
+ /// placeholder is opt-in via a custom template).
+ ///
+ [Fact]
+ public async Task EvaluateAsync_MarkerAbsent_DefaultTemplate_DoesNotIncludeLastResponseAsync()
+ {
+ // Arrange
+ var evaluator = new CompletionMarkerLoopEvaluator("DONE");
+ LoopContext context = CreateContext("candidate name: NoteNest");
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.True(evaluation.ShouldReinvoke);
+ Assert.Equal(CompletionMarkerLoopEvaluator.DefaultFeedbackMessageTemplate.Replace(CompletionMarkerLoopEvaluator.CompletionMarkerPlaceholder, "DONE"), evaluation.Feedback);
+ Assert.DoesNotContain("NoteNest", evaluation.Feedback!);
+ }
+
+ ///
+ /// Verify that EvaluateAsync throws when the context is null.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_NullContext_ThrowsAsync()
+ {
+ // Arrange
+ var evaluator = new CompletionMarkerLoopEvaluator("DONE");
+
+ // Act & Assert
+ await Assert.ThrowsAsync("context", async () => await evaluator.EvaluateAsync(null!));
+ }
+
+ private static LoopContext CreateContext(string responseText) => new(
+ new Mock().Object,
+ new ChatClientAgentSession(),
+ [new ChatMessage(ChatRole.User, "go")],
+ new AgentResponse([new ChatMessage(ChatRole.Assistant, responseText)]));
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/DelegateLoopEvaluatorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/DelegateLoopEvaluatorTests.cs
new file mode 100644
index 0000000000..8718fe9250
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/DelegateLoopEvaluatorTests.cs
@@ -0,0 +1,113 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Moq;
+
+namespace Microsoft.Agents.AI.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public class DelegateLoopEvaluatorTests
+{
+ ///
+ /// Verify that the constructor throws when the evaluate delegate is null.
+ ///
+ [Fact]
+ public void DelegateLoopEvaluator_NullDelegate_Throws()
+ {
+ // Act & Assert
+ Assert.Throws("evaluate", () => new DelegateLoopEvaluator(null!));
+ }
+
+ ///
+ /// Verify that EvaluateAsync throws when the context is null.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_NullContext_ThrowsAsync()
+ {
+ // Arrange
+ var evaluator = new DelegateLoopEvaluator((_, _) => new ValueTask(LoopEvaluation.Stop()));
+
+ // Act & Assert
+ await Assert.ThrowsAsync("context", async () => await evaluator.EvaluateAsync(null!));
+ }
+
+ ///
+ /// Verify that EvaluateAsync invokes the supplied delegate and returns the evaluation it produces.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_InvokesDelegate_AndReturnsItsEvaluationAsync()
+ {
+ // Arrange
+ bool invoked = false;
+ var expected = LoopEvaluation.Continue("feedback");
+ var evaluator = new DelegateLoopEvaluator((_, _) =>
+ {
+ invoked = true;
+ return new ValueTask(expected);
+ });
+ LoopContext context = CreateContext();
+
+ // Act
+ LoopEvaluation evaluation = await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.True(invoked);
+ Assert.Same(expected, evaluation);
+ }
+
+ ///
+ /// Verify that EvaluateAsync passes the same context instance to the delegate.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_PassesContextToDelegateAsync()
+ {
+ // Arrange
+ LoopContext? received = null;
+ var evaluator = new DelegateLoopEvaluator((ctx, _) =>
+ {
+ received = ctx;
+ return new ValueTask(LoopEvaluation.Stop());
+ });
+ LoopContext context = CreateContext();
+
+ // Act
+ await evaluator.EvaluateAsync(context);
+
+ // Assert
+ Assert.Same(context, received);
+ }
+
+ ///
+ /// Verify that EvaluateAsync forwards the cancellation token to the delegate.
+ ///
+ [Fact]
+ public async Task EvaluateAsync_ForwardsCancellationTokenToDelegateAsync()
+ {
+ // Arrange
+ using var cts = new CancellationTokenSource();
+ CancellationToken received = default;
+ var evaluator = new DelegateLoopEvaluator((_, ct) =>
+ {
+ received = ct;
+ return new ValueTask(LoopEvaluation.Stop());
+ });
+ LoopContext context = CreateContext();
+
+ // Act
+ await evaluator.EvaluateAsync(context, cts.Token);
+
+ // Assert
+ Assert.Equal(cts.Token, received);
+ }
+
+ private static LoopContext CreateContext() => new(
+ new Mock().Object,
+ new ChatClientAgentSession(),
+ [new ChatMessage(ChatRole.User, "go")],
+ new AgentResponse([new ChatMessage(ChatRole.Assistant, "response")]));
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/LoopAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/LoopAgentTests.cs
new file mode 100644
index 0000000000..428298f1d6
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Loop/LoopAgentTests.cs
@@ -0,0 +1,1231 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Moq;
+using Moq.Protected;
+
+using static Microsoft.Agents.AI.UnitTests.LoopTestHelpers;
+
+namespace Microsoft.Agents.AI.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public class LoopAgentTests
+{
+ #region Constructor
+
+ ///
+ /// Verify that the constructor throws when innerAgent is null.
+ ///
+ [Fact]
+ public void Constructor_NullInnerAgent_Throws()
+ {
+ // Arrange
+ var evaluator = While(static _ => false);
+
+ // Act & Assert
+ Assert.Throws("innerAgent", () => new LoopAgent(null!, evaluator));
+ }
+
+ ///
+ /// Verify that the constructor throws when the evaluator is null.
+ ///
+ [Fact]
+ public void Constructor_NullEvaluator_Throws()
+ {
+ // Arrange
+ var innerAgent = new Mock().Object;
+
+ // Act & Assert
+ Assert.Throws("evaluator", () => new LoopAgent(innerAgent, (LoopEvaluator)null!));
+ }
+
+ ///
+ /// Verify that the constructor throws when the evaluators collection is null.
+ ///
+ [Fact]
+ public void Constructor_NullEvaluators_Throws()
+ {
+ // Arrange
+ var innerAgent = new Mock().Object;
+
+ // Act & Assert
+ Assert.Throws("evaluators", () => new LoopAgent(innerAgent, (IEnumerable)null!));
+ }
+
+ ///
+ /// Verify that the constructor throws when the evaluators collection is empty.
+ ///
+ [Fact]
+ public void Constructor_EmptyEvaluators_Throws()
+ {
+ // Arrange
+ var innerAgent = new Mock().Object;
+
+ // Act & Assert
+ Assert.Throws("evaluators", () => new LoopAgent(innerAgent, Array.Empty()));
+ }
+
+ ///
+ /// Verify that the constructor throws when the evaluators collection contains a null element.
+ ///
+ [Fact]
+ public void Constructor_NullEvaluatorElement_Throws()
+ {
+ // Arrange
+ var innerAgent = new Mock().Object;
+
+ // Act & Assert
+ Assert.Throws("evaluators", () => new LoopAgent(innerAgent, new LoopEvaluator[] { null! }));
+ }
+
+ ///
+ /// Verify that the constructor throws when MaxIterations is less than 1.
+ ///
+ [Fact]
+ public void Constructor_InvalidMaxIterations_Throws()
+ {
+ // Arrange
+ var innerAgent = new Mock().Object;
+ var evaluator = While(static _ => false);
+ var options = new LoopAgentOptions { MaxIterations = 0 };
+
+ // Act & Assert
+ Assert.Throws(() => new LoopAgent(innerAgent, evaluator, options));
+ }
+
+ ///
+ /// Verify that the constructor creates a valid instance with default options.
+ ///
+ [Fact]
+ public void Constructor_ValidArguments_CreatesInstance()
+ {
+ // Arrange
+ var innerAgent = new Mock().Object;
+ var evaluator = While(static _ => false);
+
+ // Act
+ var agent = new LoopAgent(innerAgent, evaluator);
+
+ // Assert
+ Assert.NotNull(agent);
+ }
+
+ #endregion
+
+ #region RunAsync - core loop behavior
+
+ ///
+ /// Verify that when the evaluator stops immediately the inner agent is invoked exactly once.
+ ///
+ [Fact]
+ public async Task RunAsync_EvaluatorStopsImmediately_InvokesOnceAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "done")]));
+ var evaluator = While(static _ => false);
+ var agent = new LoopAgent(capture.Agent, evaluator);
+
+ // Act
+ var response = await agent.RunAsync([new ChatMessage(ChatRole.User, "go")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal("done", response.Text);
+ Assert.Equal(1, capture.CallCount);
+ }
+
+ ///
+ /// Verify that the loop re-invokes while the predicate returns true and the aggregated response contains every
+ /// iteration's messages in order.
+ ///
+ [Fact]
+ public async Task RunAsync_PredicateLoopsUntilFalse_AggregatesAllIterationsAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(call =>
+ new AgentResponse([new ChatMessage(ChatRole.Assistant, $"iteration {call}")]));
+
+ // Continue while the latest response is not "iteration 3".
+ var evaluator = While(ctx => ctx.LastResponse.Text != "iteration 3");
+ var agent = new LoopAgent(capture.Agent, evaluator);
+
+ // Act
+ var response = await agent.RunAsync([new ChatMessage(ChatRole.User, "go")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(3, capture.CallCount);
+ Assert.Equal(["iteration 1", "iteration 2", "iteration 3"], response.Messages.Select(static m => m.Text));
+ }
+
+ ///
+ /// Verify that returns only the final
+ /// iteration's response instead of the aggregated transcript.
+ ///
+ [Fact]
+ public async Task RunAsync_LastResponseOnly_ReturnsFinalResponseAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(call =>
+ new AgentResponse([new ChatMessage(ChatRole.Assistant, $"iteration {call}")]));
+ var evaluator = While(ctx => ctx.LastResponse.Text != "iteration 3");
+ var options = new LoopAgentOptions { NonStreamingReturnsLastResponseOnly = true };
+ var agent = new LoopAgent(capture.Agent, evaluator, options);
+
+ // Act
+ var response = await agent.RunAsync([new ChatMessage(ChatRole.User, "go")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(3, capture.CallCount);
+ Assert.Equal("iteration 3", response.Text);
+ Assert.Single(response.Messages);
+ }
+
+ ///
+ /// Verify that the caller's initial messages are sent once and a re-invocation without feedback sends none.
+ ///
+ [Fact]
+ public async Task RunAsync_ContinueWithoutFeedback_SendsInitialOnceThenNoneAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "ack")]));
+ var evaluator = new DelegateLoopEvaluator((ctx, _) =>
+ new ValueTask(
+ ctx.Iteration < 2 ? LoopEvaluation.Continue() : LoopEvaluation.Stop()));
+ var agent = new LoopAgent(capture.Agent, evaluator);
+
+ // Act
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "original")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(2, capture.CallCount);
+ Assert.Equal("original", capture.MessagesPerCall[0].Single().Text);
+ Assert.Empty(capture.MessagesPerCall[1]);
+ }
+
+ ///
+ /// Verify that feedback supplied by the evaluator is injected verbatim on re-invocation (non-fresh mode).
+ ///
+ [Fact]
+ public async Task RunAsync_EvaluatorSuppliesFeedback_InjectsItVerbatimAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "ack")]));
+ var evaluator = new DelegateLoopEvaluator((ctx, _) =>
+ new ValueTask(
+ ctx.Iteration < 2 ? LoopEvaluation.Continue("custom follow-up") : LoopEvaluation.Stop()));
+ var agent = new LoopAgent(capture.Agent, evaluator);
+
+ // Act
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "original")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(2, capture.CallCount);
+ Assert.Equal("custom follow-up", capture.MessagesPerCall[1].Single().Text);
+ }
+
+ ///
+ /// Verify that an evaluator using sends the messages verbatim and
+ /// records an aligned feedback entry (it carries no feedback string).
+ ///
+ [Fact]
+ public async Task RunAsync_ContinueWithMessages_SendsMessagesVerbatimAndRecordsNullFeedbackAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "ack")]));
+ IReadOnlyList? feedbackSnapshot = null;
+ var evaluator = new DelegateLoopEvaluator((ctx, _) =>
+ {
+ if (ctx.Iteration < 2)
+ {
+ return new ValueTask(LoopEvaluation.ContinueWithMessages(
+ [new ChatMessage(ChatRole.System, "sys"), new ChatMessage(ChatRole.User, "explicit")]));
+ }
+
+ feedbackSnapshot = ctx.Feedback.ToList();
+ return new ValueTask(LoopEvaluation.Stop());
+ });
+ var agent = new LoopAgent(capture.Agent, evaluator);
+
+ // Act
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "original")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(2, capture.CallCount);
+ Assert.Equal(["sys", "explicit"], capture.MessagesPerCall[1].Select(static m => m.Text));
+ Assert.NotNull(feedbackSnapshot);
+ // One aligned entry for the single re-invoked iteration; null because ContinueWithMessages carries no feedback string.
+ Assert.Equal([null], feedbackSnapshot!);
+ }
+
+ ///
+ /// Verify that the global safety cap stops the loop even when the evaluator always continues.
+ ///
+ [Fact]
+ public async Task RunAsync_AlwaysContinue_StopsAtGlobalCapAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "working")]));
+ var evaluator = While(static _ => true);
+ var options = new LoopAgentOptions { MaxIterations = 3 };
+ var agent = new LoopAgent(capture.Agent, evaluator, options);
+
+ // Act
+ var response = await agent.RunAsync([new ChatMessage(ChatRole.User, "go")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(3, capture.CallCount);
+ Assert.Equal(["working", "working", "working"], response.Messages.Select(static m => m.Text));
+ }
+
+ ///
+ /// Verify that a pending tool-approval request terminates the loop and returns that response.
+ ///
+ [Fact]
+ public async Task RunAsync_PendingApprovalRequest_StopsLoopAsync()
+ {
+ // Arrange
+ var approvalRequest = new ToolApprovalRequestContent("req1", new FunctionCallContent("call1", "MyTool"));
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, [approvalRequest])]));
+ var evaluator = While(static _ => true);
+ var agent = new LoopAgent(capture.Agent, evaluator);
+
+ // Act
+ var response = await agent.RunAsync([new ChatMessage(ChatRole.User, "go")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(1, capture.CallCount);
+ Assert.Contains(response.Messages.SelectMany(static m => m.Contents), static c => c is ToolApprovalRequestContent);
+ }
+
+ ///
+ /// Verify that when no session is supplied the loop creates one and invokes the agent.
+ ///
+ [Fact]
+ public async Task RunAsync_NoSessionSupplied_CreatesSessionAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "done")]));
+ capture.Mock
+ .Protected()
+ .Setup>("CreateSessionCoreAsync", ItExpr.IsAny())
+ .Returns(new ValueTask(new ChatClientAgentSession()));
+ var evaluator = While(static _ => false);
+ var agent = new LoopAgent(capture.Agent, evaluator);
+
+ // Act
+ var response = await agent.RunAsync([new ChatMessage(ChatRole.User, "go")]);
+
+ // Assert
+ Assert.Equal("done", response.Text);
+ capture.Mock.Protected().Verify("CreateSessionCoreAsync", Times.Once(), ItExpr.IsAny());
+ }
+
+ #endregion
+
+ #region RunAsync - feedback log
+
+ ///
+ /// Verify that in the default (non-fresh) mode the latest feedback is injected verbatim as the next input.
+ ///
+ [Fact]
+ public async Task RunAsync_NonFresh_InjectsLatestFeedbackVerbatimAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "partial")]));
+ var evaluator = new DelegateLoopEvaluator((_, _) => new ValueTask(LoopEvaluation.Continue("fix it")));
+ var options = new LoopAgentOptions { MaxIterations = 2 };
+ var agent = new LoopAgent(capture.Agent, evaluator, options);
+
+ // Act
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "original")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(2, capture.CallCount);
+ Assert.Equal("fix it", capture.MessagesPerCall[1].Single().Text);
+ }
+
+ ///
+ /// Verify that when the latest iteration produces no feedback, no stale earlier feedback is re-injected (non-fresh).
+ ///
+ [Fact]
+ public async Task RunAsync_NonFresh_LatestEmpty_DoesNotReinjectStaleFeedbackAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "partial")]));
+
+ // Provide feedback only on the first iteration; the second records nothing.
+ var evaluator = new DelegateLoopEvaluator((ctx, _) =>
+ new ValueTask(LoopEvaluation.Continue(ctx.Iteration == 1 ? "feedback 1" : null)));
+ var options = new LoopAgentOptions { MaxIterations = 3 };
+ var agent = new LoopAgent(capture.Agent, evaluator, options);
+
+ // Act
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "original")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(3, capture.CallCount);
+ Assert.Equal("feedback 1", capture.MessagesPerCall[1].Single().Text);
+ Assert.Empty(capture.MessagesPerCall[2]);
+ }
+
+ ///
+ /// Verify that the accumulated feedback log is exposed read-only and shared across all evaluators in a run.
+ ///
+ [Fact]
+ public async Task RunAsync_FeedbackLog_IsSharedAcrossEvaluatorsAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "partial")]));
+ var observed = new List();
+ var producer = new DelegateLoopEvaluator((ctx, _) =>
+ new ValueTask(
+ ctx.Iteration < 3 ? LoopEvaluation.Continue($"fb {ctx.Iteration}") : LoopEvaluation.Stop()));
+ var observer = new DelegateLoopEvaluator((ctx, _) =>
+ {
+ // The observer runs only when the producer stops; it sees the full feedback log.
+ observed.Add(ctx.Feedback.Count);
+ return new ValueTask(LoopEvaluation.Stop());
+ });
+ var options = new LoopAgentOptions { MaxIterations = 5 };
+ var agent = new LoopAgent(capture.Agent, new LoopEvaluator[] { producer, observer }, options);
+
+ // Act
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "go")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.Equal(3, capture.CallCount);
+ // On the third iteration the producer stops, the observer runs and sees two recorded feedback entries.
+ Assert.Equal([2], observed);
+ }
+
+ ///
+ /// Verify that iterations driven by still record an (aligned)
+ /// entry in the feedback log, so the log stays one-entry-per-re-invoked-iteration. The explicit-messages iteration
+ /// contributes a entry since it carries no feedback string.
+ ///
+ [Fact]
+ public async Task RunAsync_ContinueWithMessages_RecordsNullFeedbackEntryAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "partial")]));
+ List? finalLog = null;
+ var evaluator = new DelegateLoopEvaluator((ctx, _) =>
+ {
+ // Capture the log on the final evaluation, after both re-invocations have been recorded.
+ if (ctx.Iteration >= 3)
+ {
+ finalLog = ctx.Feedback.ToList();
+ return new ValueTask(LoopEvaluation.Stop());
+ }
+
+ // Iteration 1 drives a feedback-string re-invocation; iteration 2 drives an explicit-messages one.
+ return new ValueTask(ctx.Iteration == 1
+ ? LoopEvaluation.Continue("needs work")
+ : LoopEvaluation.ContinueWithMessages([new ChatMessage(ChatRole.User, "explicit")]));
+ });
+ var options = new LoopAgentOptions { MaxIterations = 5 };
+ var agent = new LoopAgent(capture.Agent, evaluator, options);
+
+ // Act
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "go")], new ChatClientAgentSession());
+
+ // Assert
+ Assert.NotNull(finalLog);
+ // One entry per re-invoked iteration: the feedback string, then null for the ContinueWithMessages iteration.
+ Assert.Equal(["needs work", null], finalLog!);
+ }
+
+ #endregion
+
+ #region RunAsync - fresh context
+
+ ///
+ /// Verify that without fresh context the loop reuses a single session across all iterations.
+ ///
+ [Fact]
+ public async Task RunAsync_NonFresh_ReusesSameSessionAcrossIterationsAsync()
+ {
+ // Arrange
+ var loopSession = new ChatClientAgentSession();
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "x")]));
+ capture.Mock
+ .Protected()
+ .Setup>("CreateSessionCoreAsync", ItExpr.IsAny())
+ .Returns(new ValueTask(loopSession));
+ var evaluator = new DelegateLoopEvaluator((_, _) => new ValueTask(LoopEvaluation.Continue("more")));
+ var options = new LoopAgentOptions { MaxIterations = 3 };
+ var agent = new LoopAgent(capture.Agent, evaluator, options);
+
+ // Act (no session supplied by caller)
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "go")]);
+
+ // Assert
+ Assert.Equal(3, capture.CallCount);
+ Assert.Same(loopSession, capture.SessionsPerCall[0]);
+ Assert.Same(loopSession, capture.SessionsPerCall[1]);
+ Assert.Same(loopSession, capture.SessionsPerCall[2]);
+ }
+
+ ///
+ /// Verify that with fresh context each iteration is rebuilt from the original messages plus the aggregated feedback log.
+ ///
+ [Fact]
+ public async Task RunAsync_Fresh_RebuildsFromInitialMessagesAndAggregatedFeedbackAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "partial")]));
+ capture.Mock
+ .Protected()
+ .Setup>("CreateSessionCoreAsync", ItExpr.IsAny())
+ .Returns(() => new ValueTask(new ChatClientAgentSession()));
+ var evaluator = new DelegateLoopEvaluator((ctx, _) => new ValueTask(LoopEvaluation.Continue($"fb {ctx.Iteration}")));
+ var options = new LoopAgentOptions { MaxIterations = 3, FreshContextPerIteration = true };
+ var agent = new LoopAgent(capture.Agent, evaluator, options);
+
+ // Act (no session supplied by caller)
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "original task")]);
+
+ // Assert
+ Assert.Equal(3, capture.CallCount);
+ var secondCall = capture.MessagesPerCall[1];
+ Assert.Contains(secondCall, static m => m.Text == "original task");
+ Assert.Contains(secondCall, static m => m.Text.Contains("## Feedback") && m.Text.Contains("fb 1"));
+ var thirdCall = capture.MessagesPerCall[2];
+ Assert.Contains(thirdCall, static m => m.Text == "original task");
+ Assert.Contains(thirdCall, static m => m.Text.Contains("fb 1") && m.Text.Contains("fb 2"));
+ }
+
+ ///
+ /// Verify that with fresh context and a loop-owned session, a new session is created for each iteration.
+ ///
+ [Fact]
+ public async Task RunAsync_Fresh_RecreatesSessionEachIterationAsync()
+ {
+ // Arrange
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "x")]));
+ capture.Mock
+ .Protected()
+ .Setup>("CreateSessionCoreAsync", ItExpr.IsAny())
+ .Returns(() => new ValueTask(new ChatClientAgentSession()));
+ var evaluator = new DelegateLoopEvaluator((_, _) => new ValueTask(LoopEvaluation.Continue("more")));
+ var options = new LoopAgentOptions { MaxIterations = 3, FreshContextPerIteration = true };
+ var agent = new LoopAgent(capture.Agent, evaluator, options);
+
+ // Act (no session supplied by caller)
+ await agent.RunAsync([new ChatMessage(ChatRole.User, "go")]);
+
+ // Assert
+ Assert.Equal(3, capture.CallCount);
+ Assert.NotSame(capture.SessionsPerCall[0], capture.SessionsPerCall[1]);
+ Assert.NotSame(capture.SessionsPerCall[1], capture.SessionsPerCall[2]);
+ }
+
+ ///
+ /// Verify that with fresh context and a caller-supplied session, the caller's session is used for the first
+ /// iteration, then each re-invocation runs against a fresh clone restored from a snapshot taken at the start of
+ /// the run. The session is serialized once and deserialized once per re-invocation.
+ ///
+ [Fact]
+ public async Task RunAsync_Fresh_WithCallerSession_ClonesFromSerializedSnapshotAsync()
+ {
+ // Arrange
+ var callerSession = new ChatClientAgentSession();
+ var capture = new InnerAgentCapture(_ => new AgentResponse([new ChatMessage(ChatRole.Assistant, "x")]));
+ using var snapshotDoc = JsonDocument.Parse("{}");
+ JsonElement snapshot = snapshotDoc.RootElement;
+
+ int serializeCount = 0;
+ capture.Mock
+ .Protected()
+ .Setup>("SerializeSessionCoreAsync", ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny