diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index e1b7c9a71b..d494768139 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -122,7 +122,7 @@
-
+
diff --git a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/SubAgentToolFormatter.cs b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/BackgroundAgentToolFormatter.cs
similarity index 78%
rename from dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/SubAgentToolFormatter.cs
rename to dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/BackgroundAgentToolFormatter.cs
index 915491d354..4907abbd89 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/SubAgentToolFormatter.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/BackgroundAgentToolFormatter.cs
@@ -6,26 +6,26 @@
namespace Harness.Shared.Console.ToolFormatters;
///
-/// Formats SubAgents_* tool calls with human-readable details
+/// Formats BackgroundAgents_* tool calls with human-readable details
/// for task start, continue, wait, and result retrieval operations.
///
-public sealed class SubAgentToolFormatter : ToolCallFormatter
+public sealed class BackgroundAgentToolFormatter : ToolCallFormatter
{
///
- public override bool CanFormat(FunctionCallContent call) => call.Name.StartsWith("SubAgents_", StringComparison.Ordinal);
+ public override bool CanFormat(FunctionCallContent call) => call.Name.StartsWith("BackgroundAgents_", StringComparison.Ordinal);
///
public override string? FormatDetail(FunctionCallContent call) => call.Name switch
{
- "SubAgents_StartTask" => FormatStartSubTask(call),
- "SubAgents_WaitForFirstCompletion" => FormatIdList(call, "taskIds", "Wait for"),
- "SubAgents_GetTaskResults" => FormatSingleId(call, "taskId"),
- "SubAgents_ContinueTask" => FormatContinueTask(call),
- "SubAgents_ClearCompletedTask" => FormatSingleId(call, "taskId"),
+ "BackgroundAgents_StartTask" => FormatStartBackgroundTask(call),
+ "BackgroundAgents_WaitForFirstCompletion" => FormatIdList(call, "taskIds", "Wait for"),
+ "BackgroundAgents_GetTaskResults" => FormatSingleId(call, "taskId"),
+ "BackgroundAgents_ContinueTask" => FormatContinueTask(call),
+ "BackgroundAgents_ClearCompletedTask" => FormatSingleId(call, "taskId"),
_ => null,
};
- private static string? FormatStartSubTask(FunctionCallContent call)
+ private static string? FormatStartBackgroundTask(FunctionCallContent call)
{
string? agentName = GetStringArgumentValue(call, "agentName");
string? description = GetStringArgumentValue(call, "description");
diff --git a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/TodoToolFormatter.cs b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/TodoToolFormatter.cs
index 98e041ede7..b907c4afb1 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/TodoToolFormatter.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/TodoToolFormatter.cs
@@ -19,7 +19,7 @@ public sealed class TodoToolFormatter : ToolCallFormatter
public override string? FormatDetail(FunctionCallContent call) => call.Name switch
{
"TodoList_Add" => FormatAddTodos(call),
- "TodoList_Complete" => FormatIdList(call, "ids", "Complete"),
+ "TodoList_Complete" => FormatCompleteTodos(call),
"TodoList_Remove" => FormatIdList(call, "ids", "Remove"),
_ => null,
};
@@ -64,6 +64,50 @@ public sealed class TodoToolFormatter : ToolCallFormatter
return sb.ToString();
}
+ private static string? FormatCompleteTodos(FunctionCallContent call)
+ {
+ if (call.Arguments?.TryGetValue("items", out object? itemsObj) != true || itemsObj is null)
+ {
+ return null;
+ }
+
+ var entries = new List<(int Id, string? Reason)>();
+
+ if (itemsObj is JsonElement jsonArray && jsonArray.ValueKind == JsonValueKind.Array)
+ {
+ foreach (JsonElement item in jsonArray.EnumerateArray())
+ {
+ if (!item.TryGetProperty("id", out JsonElement idElement) || !idElement.TryGetInt32(out int id))
+ {
+ continue;
+ }
+
+ string? reason = item.TryGetProperty("reason", out JsonElement reasonElement)
+ ? reasonElement.GetString()
+ : null;
+ entries.Add((id, reason));
+ }
+ }
+
+ if (entries.Count == 0)
+ {
+ return null;
+ }
+
+ var sb = new StringBuilder();
+ for (int i = 0; i < entries.Count; i++)
+ {
+ string connector = i < entries.Count - 1 ? "├─" : "└─";
+ sb.Append($"\n {connector} Complete #{entries[i].Id}");
+ if (!string.IsNullOrEmpty(entries[i].Reason))
+ {
+ sb.Append($" — {Truncate(entries[i].Reason!, 80)}");
+ }
+ }
+
+ return sb.ToString();
+ }
+
private static string? FormatIdList(FunctionCallContent call, string paramName, string verb)
{
List? ids = GetIntListArgumentValue(call, paramName);
diff --git a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ToolCallFormatter.cs b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ToolCallFormatter.cs
index f8a131dd74..e8edfa5177 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ToolCallFormatter.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Shared_Console/ToolFormatters/ToolCallFormatter.cs
@@ -56,7 +56,7 @@ public static List BuildDefaultToolFormatters()
[
new TodoToolFormatter(),
new ModeToolFormatter(),
- new SubAgentToolFormatter(),
+ new BackgroundAgentToolFormatter(),
new FileMemoryToolFormatter(),
new WebSearchToolFormatter(),
new FallbackToolFormatter(),
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents/Harness_Step02_Research_WithSubAgents.csproj b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Harness_Step02_Research_WithBackgroundAgents.csproj
similarity index 100%
rename from dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents/Harness_Step02_Research_WithSubAgents.csproj
rename to dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Harness_Step02_Research_WithBackgroundAgents.csproj
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents/Program.cs b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Program.cs
similarity index 81%
rename from dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents/Program.cs
rename to dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Program.cs
index bb4c50e0d7..c88c958247 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents/Program.cs
+++ b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/Program.cs
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
-// This sample demonstrates how to use the SubAgentsProvider to delegate work to sub-agents.
+// This sample demonstrates how to use the BackgroundAgentsProvider to delegate work to background agents.
// A parent agent is given a list of stock tickers and instructed to find the closing price
-// for each ticker on December 31, 2025. It delegates the web searches to a sub-agent.
+// for each ticker on December 31, 2025. It delegates the web searches to a background agent.
// The HarnessAgent provides built-in WebSearch (HostedWebSearchTool) so no manual web search
-// tool configuration is needed on the sub-agent.
+// tool configuration is needed on the background agent.
//
// Special commands:
// /exit — End the session.
@@ -26,7 +26,7 @@
const int MaxContextWindowTokens = 1_050_000;
const int MaxOutputTokens = 128_000;
-// --- Sub-agent: Web Search Agent ---
+// --- Background agent: Web Search Agent ---
// This agent uses the HarnessAgent's built-in HostedWebSearchTool to search the web.
// Features not needed by this sub-agent are disabled.
AIAgent webSearchAgent =
@@ -55,26 +55,26 @@
});
// --- Parent agent: Stock Price Researcher ---
-// This agent orchestrates the sub-agent to look up stock prices in parallel.
+// This agent orchestrates the background agent to look up stock prices in parallel.
var parentInstructions =
"""
- You are a stock price research assistant. You have access to a web search sub-agent that can look up information on the web.
+ You are a stock price research assistant. You have access to a web search background agent that can look up information on the web.
When given a list of stock tickers, your job is to find the closing price for each ticker on December 31, 2025.
## Workflow
- 1. For each ticker, start a sub-task on the WebSearchAgent asking it to find the closing price on December 31, 2025.
- - Start all sub-tasks before waiting for any of them to complete, so they run concurrently.
- 2. Wait for all sub-tasks to complete.
- 3. Retrieve the results from each sub-task.
+ 1. For each ticker, start a background task on the WebSearchAgent asking it to find the closing price on December 31, 2025.
+ - Start all background tasks before waiting for any of them to complete, so they run concurrently.
+ 2. Wait for all background tasks to complete.
+ 3. Retrieve the results from each background task.
4. Present a summary table with the ticker symbol and closing price for each stock.
5. Clear all completed tasks to free memory.
## Important
- - Always delegate web searches to the WebSearchAgent sub-agent. Do not try to answer from memory.
- - If a sub-task fails or returns unclear results, continue the task with a more specific query.
+ - Always delegate web searches to the WebSearchAgent background agent. Do not try to answer from memory.
+ - If a background task fails or returns unclear results, continue the task with a more specific query.
- Present results in a clean markdown table format.
""";
@@ -94,7 +94,7 @@ 5. Clear all completed tasks to free memory.
.AsHarnessAgent(MaxContextWindowTokens, MaxOutputTokens, new HarnessAgentOptions
{
Name = "StockPriceResearcher",
- Description = "An agent that researches stock prices using sub-agents.",
+ Description = "An agent that researches stock prices using background agents.",
DisableTodoProvider = true,
DisableAgentModeProvider = true,
DisableFileMemory = true, // If enabled, this would allow the agent to store memories as files in a directory associated with the current session
@@ -103,7 +103,7 @@ 5. Clear all completed tasks to free memory.
DisableWebSearch = true,
AIContextProviders =
[
- new SubAgentsProvider([webSearchAgent]),
+ new BackgroundAgentsProvider([webSearchAgent]),
],
ChatOptions = new ChatOptions
{
diff --git a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents/README.md b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/README.md
similarity index 53%
rename from dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents/README.md
rename to dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/README.md
index a52ebe0372..c04f68d13c 100644
--- a/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents/README.md
+++ b/dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents/README.md
@@ -1,24 +1,24 @@
-# Harness Step 02 — SubAgents (Stock Price Research)
+# Harness Step 02 — BackgroundAgents (Stock Price Research)
-This sample demonstrates how to use the **SubAgentsProvider** to delegate work from a parent agent to sub-agents. Both agents use `HarnessAgent` for pre-configured function invocation, per-service-call persistence, and context-window compaction.
+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.
## What It Does
-A parent agent receives a list of stock tickers and uses a web-search sub-agent to find the closing price for each ticker on December 31, 2025. The sub-tasks run concurrently, and results are presented in a summary table.
+A parent agent receives a list of stock tickers and uses a web-search background agent to find the closing price for each ticker on December 31, 2025. The background tasks run concurrently, and results are presented in a summary table.
### Architecture
```
-┌─────────────────────────────────┐
-│ StockPriceResearcher │
-│ (Parent Agent) │
-│ │
-│ SubAgentsProvider │
-│ ├─ SubAgents_StartTask │
-│ ├─ SubAgents_WaitFor... │
-│ ├─ SubAgents_GetTaskResults │
-│ └─ ... │
-└────────────┬────────────────────┘
+┌────────────────────────────────────────┐
+│ StockPriceResearcher │
+│ (Parent Agent) │
+│ │
+│ BackgroundAgentsProvider │
+│ ├─ BackgroundAgents_StartTask │
+│ ├─ BackgroundAgents_WaitFor... │
+│ ├─ BackgroundAgents_GetTaskResults │
+│ └─ ... │
+└────────────┬───────────────────────────┘
│ delegates to
▼
┌─────────────────────────────────┐
@@ -40,7 +40,7 @@ A parent agent receives a list of stock tickers and uses a web-search sub-agent
## Running the Sample
```bash
-cd dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithSubAgents
+cd dotnet/samples/02-agents/Harness/Harness_Step02_Research_WithBackgroundAgents
dotnet run
```
@@ -50,4 +50,4 @@ When prompted, enter a list of stock tickers such as:
BAC, MSFT, BA
```
-The parent agent will delegate each ticker lookup to the web search sub-agent concurrently and present the results in a table.
+The parent agent will delegate each ticker lookup to the web search background agent concurrently and present the results in a table.
diff --git a/dotnet/samples/02-agents/Harness/README.md b/dotnet/samples/02-agents/Harness/README.md
index d868323648..16fad9ac62 100644
--- a/dotnet/samples/02-agents/Harness/README.md
+++ b/dotnet/samples/02-agents/Harness/README.md
@@ -7,5 +7,5 @@ Samples demonstrating the [Harness AIContextProviders](../../../src/Microsoft.Ag
| Sample | Description |
| --- | --- |
| [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_WithSubAgents](./Harness_Step02_Research_WithSubAgents/README.md) | Using SubAgentsProvider to delegate stock price lookups to a web-search sub-agent concurrently |
+| [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 |
diff --git a/dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs
index f3bc543f03..e28144f45c 100644
--- a/dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs
+++ b/dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs
@@ -74,9 +74,11 @@ private static JsonSerializerOptions CreateDefaultOptions()
[JsonSerializable(typeof(TodoState))]
[JsonSerializable(typeof(TodoItem))]
[JsonSerializable(typeof(TodoItemInput))]
+ [JsonSerializable(typeof(TodoCompleteInput))]
[JsonSerializable(typeof(List), TypeInfoPropertyName = "IntList")]
[JsonSerializable(typeof(List), TypeInfoPropertyName = "TodoItemList")]
[JsonSerializable(typeof(List), TypeInfoPropertyName = "TodoItemInputList")]
+ [JsonSerializable(typeof(List), TypeInfoPropertyName = "TodoCompleteInputList")]
// AgentModeProvider types
[JsonSerializable(typeof(AgentModeState))]
@@ -95,12 +97,12 @@ private static JsonSerializerOptions CreateDefaultOptions()
[JsonSerializable(typeof(FileListEntry))]
[JsonSerializable(typeof(List), TypeInfoPropertyName = "FileListEntryList")]
- // SubAgentsProvider types
- [JsonSerializable(typeof(SubAgentState))]
- [JsonSerializable(typeof(SubAgentRuntimeState))]
- [JsonSerializable(typeof(SubTaskInfo))]
- [JsonSerializable(typeof(SubTaskStatus))]
- [JsonSerializable(typeof(List), TypeInfoPropertyName = "SubTaskInfoList")]
+ // BackgroundAgentsProvider types
+ [JsonSerializable(typeof(BackgroundAgentState))]
+ [JsonSerializable(typeof(BackgroundAgentRuntimeState))]
+ [JsonSerializable(typeof(BackgroundTaskInfo))]
+ [JsonSerializable(typeof(BackgroundTaskStatus))]
+ [JsonSerializable(typeof(List), TypeInfoPropertyName = "BackgroundTaskInfoList")]
[ExcludeFromCodeCoverage]
internal sealed partial class JsonContext : JsonSerializerContext;
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentRuntimeState.cs b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentRuntimeState.cs
similarity index 67%
rename from dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentRuntimeState.cs
rename to dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentRuntimeState.cs
index 7b3096dba8..f8e2f3accc 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentRuntimeState.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentRuntimeState.cs
@@ -7,15 +7,15 @@
namespace Microsoft.Agents.AI;
///
-/// Holds non-serializable runtime references for in-flight sub-tasks within a single parent session.
+/// Holds non-serializable runtime references for in-flight background tasks within a single parent session.
///
///
/// Properties are marked with because
/// and are not JSON-serializable. After deserialization (e.g., after a restart),
/// a fresh empty instance is created and any previously-running tasks are marked as
-/// by .
+/// by .
///
-internal sealed class SubAgentRuntimeState
+internal sealed class BackgroundAgentRuntimeState
{
///
/// Gets the mapping of task IDs to their in-flight instances.
@@ -24,9 +24,9 @@ internal sealed class SubAgentRuntimeState
public Dictionary> InFlightTasks { get; } = [];
///
- /// Gets the mapping of task IDs to their sub-agent instances,
+ /// Gets the mapping of task IDs to their background agent instances,
/// needed for ContinueTask.
///
[JsonIgnore]
- public Dictionary SubTaskSessions { get; } = [];
+ public Dictionary BackgroundTaskSessions { get; } = [];
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentState.cs b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentState.cs
similarity index 62%
rename from dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentState.cs
rename to dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentState.cs
index 4e086fb910..223ebd98c5 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentState.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentState.cs
@@ -8,21 +8,21 @@
namespace Microsoft.Agents.AI;
///
-/// Represents the serializable state of sub-tasks managed by the ,
+/// Represents the serializable state of background tasks managed by the ,
/// stored in the session's .
///
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
-internal sealed class SubAgentState
+internal sealed class BackgroundAgentState
{
///
- /// Gets or sets the next ID to assign to a new sub-task.
+ /// Gets or sets the next ID to assign to a new background task.
///
[JsonPropertyName("nextTaskId")]
public int NextTaskId { get; set; } = 1;
///
- /// Gets the list of sub-task metadata entries.
+ /// Gets the list of background task metadata entries.
///
[JsonPropertyName("tasks")]
- public List Tasks { get; set; } = [];
+ public List Tasks { get; set; } = [];
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentsProvider.cs
similarity index 63%
rename from dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentsProvider.cs
rename to dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentsProvider.cs
index 254e082523..3e347f9983 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentsProvider.cs
@@ -15,56 +15,56 @@
namespace Microsoft.Agents.AI;
///
-/// An that enables an agent to delegate work to sub-agents asynchronously.
+/// An that enables an agent to delegate work to background agents asynchronously.
///
///
///
-/// The allows a parent agent to start sub-tasks on child agents,
-/// wait for their completion, and retrieve results. Each sub-task runs in its own session and
+/// The allows a parent agent to start background tasks on child agents,
+/// wait for their completion, and retrieve results. Each background task runs in its own session and
/// executes concurrently.
///
///
/// This provider exposes the following tools to the agent:
///
-/// - SubAgents_StartTask — Start a sub-task on a named agent with text input. Returns the task ID.
-/// - SubAgents_WaitForFirstCompletion — Block until the first of the specified tasks completes. Returns the completed task's ID.
-/// - SubAgents_GetTaskResults — Retrieve the text output of a completed sub-task.
-/// - SubAgents_GetAllTasks — List all sub-tasks with their IDs, statuses, descriptions, and agent names.
-/// - SubAgents_ContinueTask — Send follow-up input to a completed sub-task's session to resume work.
-/// - SubAgents_ClearCompletedTask — Remove a completed sub-task and release its session to free memory.
+/// - BackgroundAgents_StartTask — Start a background task on a named agent with text input. Returns the task ID.
+/// - BackgroundAgents_WaitForFirstCompletion — Block until the first of the specified tasks completes. Returns the completed task's ID.
+/// - BackgroundAgents_GetTaskResults — Retrieve the text output of a completed background task.
+/// - BackgroundAgents_GetAllTasks — List all background tasks with their IDs, statuses, descriptions, and agent names.
+/// - BackgroundAgents_ContinueTask — Send follow-up input to a completed background task's session to resume work.
+/// - BackgroundAgents_ClearCompletedTask — Remove a completed background task and release its session to free memory.
///
///
///
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
-public sealed class SubAgentsProvider : AIContextProvider
+public sealed class BackgroundAgentsProvider : AIContextProvider
{
private const string DefaultInstructions =
"""
- ## SubAgents
- You have access to sub-agents that can perform work on your behalf.
+ ## BackgroundAgents
+ You have access to background agents that can perform work on your behalf.
- - Use the `SubAgents_*` list of tools to start tasks on sub agents and check their results.
- - Creating a sub task does not block, and sub-tasks run concurrently.
+ - Use the `BackgroundAgents_*` list of tools to start tasks on background agents and check their results.
+ - Creating a background task does not block, and background tasks run concurrently.
- Important: Always wait for outstanding tasks to finish before you finish processing.
- - Important: After retrieving results from a completed task, clear it with SubAgents_ClearCompletedTask to free memory, unless you plan to continue it with SubAgents_ContinueTask.
+ - Important: After retrieving results from a completed task, clear it with BackgroundAgents_ClearCompletedTask to free memory, unless you plan to continue it with BackgroundAgents_ContinueTask.
- {sub_agents}
+ {background_agents}
""";
private readonly Dictionary _agents;
- private readonly ProviderSessionState _sessionState;
- private readonly ProviderSessionState _runtimeSessionState;
+ private readonly ProviderSessionState _sessionState;
+ private readonly ProviderSessionState _runtimeSessionState;
private readonly string _instructions;
private IReadOnlyList? _stateKeys;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// The collection of sub-agents available for delegation.
+ /// The collection of background agents available for delegation.
/// Optional settings controlling the provider behavior.
/// is .
/// An agent has a null or empty name, or agent names are not unique.
- public SubAgentsProvider(IEnumerable agents, SubAgentsProviderOptions? options = null)
+ public BackgroundAgentsProvider(IEnumerable agents, BackgroundAgentsProviderOptions? options = null)
{
_ = Throw.IfNull(agents);
@@ -74,15 +74,15 @@ public SubAgentsProvider(IEnumerable agents, SubAgentsProviderOptions?
string agentListText = options?.AgentListBuilder is not null
? options.AgentListBuilder(this._agents)
: BuildDefaultAgentListText(this._agents);
- this._instructions = baseInstructions.Replace("{sub_agents}", agentListText);
+ this._instructions = baseInstructions.Replace("{background_agents}", agentListText);
- this._sessionState = new ProviderSessionState(
- _ => new SubAgentState(),
+ this._sessionState = new ProviderSessionState(
+ _ => new BackgroundAgentState(),
this.GetType().Name,
AgentJsonUtilities.DefaultOptions);
- this._runtimeSessionState = new ProviderSessionState(
- _ => new SubAgentRuntimeState(),
+ this._runtimeSessionState = new ProviderSessionState(
+ _ => new BackgroundAgentRuntimeState(),
this.GetType().Name + "_Runtime",
AgentJsonUtilities.DefaultOptions);
}
@@ -93,8 +93,8 @@ public SubAgentsProvider(IEnumerable agents, SubAgentsProviderOptions?
///
protected override ValueTask ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
- SubAgentState state = this._sessionState.GetOrInitializeState(context.Session);
- SubAgentRuntimeState runtimeState = this._runtimeSessionState.GetOrInitializeState(context.Session);
+ BackgroundAgentState state = this._sessionState.GetOrInitializeState(context.Session);
+ BackgroundAgentRuntimeState runtimeState = this._runtimeSessionState.GetOrInitializeState(context.Session);
return new ValueTask(new AIContext
{
@@ -113,12 +113,12 @@ private static Dictionary ValidateAndBuildAgentDictionary(IEnum
{
if (string.IsNullOrWhiteSpace(agent.Name))
{
- throw new ArgumentException("All sub-agents must have a non-empty Name.", nameof(agents));
+ throw new ArgumentException("All background agents must have a non-empty Name.", nameof(agents));
}
if (dict.ContainsKey(agent.Name))
{
- throw new ArgumentException($"Duplicate sub-agent name: '{agent.Name}'. Agent names must be unique (case-insensitive).", nameof(agents));
+ throw new ArgumentException($"Duplicate background agent name: '{agent.Name}'. Agent names must be unique (case-insensitive).", nameof(agents));
}
dict[agent.Name] = agent;
@@ -126,19 +126,19 @@ private static Dictionary ValidateAndBuildAgentDictionary(IEnum
if (dict.Count == 0)
{
- throw new ArgumentException("At least one sub-agent must be provided.", nameof(agents));
+ throw new ArgumentException("At least one background agent must be provided.", nameof(agents));
}
return dict;
}
///
- /// Builds the default text listing available sub-agents and their descriptions.
+ /// Builds the default text listing available background agents and their descriptions.
///
private static string BuildDefaultAgentListText(IReadOnlyDictionary agents)
{
var sb = new StringBuilder();
- sb.AppendLine("Available sub-agents:");
+ sb.AppendLine("Available background agents:");
foreach (var kvp in agents)
{
sb.Append("- ").Append(kvp.Key);
@@ -156,12 +156,12 @@ private static string BuildDefaultAgentListText(IReadOnlyDictionary
/// Refreshes the status of in-flight tasks in the given state for the specified session.
///
- private void TryRefreshTaskState(SubAgentState state, SubAgentRuntimeState runtimeState, AgentSession? session)
+ private void TryRefreshTaskState(BackgroundAgentState state, BackgroundAgentRuntimeState runtimeState, AgentSession? session)
{
bool changed = false;
- foreach (SubTaskInfo task in state.Tasks)
+ foreach (BackgroundTaskInfo task in state.Tasks)
{
- if (task.Status != SubTaskStatus.Running)
+ if (task.Status != BackgroundTaskStatus.Running)
{
continue;
}
@@ -169,7 +169,7 @@ private void TryRefreshTaskState(SubAgentState state, SubAgentRuntimeState runti
if (!runtimeState.InFlightTasks.TryGetValue(task.Id, out Task? inFlight))
{
// In-flight reference lost (e.g., after restart/deserialization).
- task.Status = SubTaskStatus.Lost;
+ task.Status = BackgroundTaskStatus.Lost;
changed = true;
continue;
}
@@ -188,32 +188,32 @@ private void TryRefreshTaskState(SubAgentState state, SubAgentRuntimeState runti
}
///
- /// Finalizes a task by extracting results from the completed Task and updating the SubTaskInfo.
+ /// Finalizes a task by extracting results from the completed Task and updating the BackgroundTaskInfo.
///
- private static void FinalizeTask(SubTaskInfo taskInfo, Task completedTask, SubAgentRuntimeState runtimeState)
+ private static void FinalizeTask(BackgroundTaskInfo taskInfo, Task completedTask, BackgroundAgentRuntimeState runtimeState)
{
if (completedTask.Status == TaskStatus.RanToCompletion)
{
- taskInfo.Status = SubTaskStatus.Completed;
+ taskInfo.Status = BackgroundTaskStatus.Completed;
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits — task is already completed
taskInfo.ResultText = completedTask.Result.Text;
#pragma warning restore VSTHRD002
}
else if (completedTask.IsFaulted)
{
- taskInfo.Status = SubTaskStatus.Failed;
+ taskInfo.Status = BackgroundTaskStatus.Failed;
taskInfo.ErrorText = completedTask.Exception?.InnerException?.Message ?? completedTask.Exception?.Message ?? "Unknown error";
}
else if (completedTask.IsCanceled)
{
- taskInfo.Status = SubTaskStatus.Failed;
+ taskInfo.Status = BackgroundTaskStatus.Failed;
taskInfo.ErrorText = "Task was canceled.";
}
runtimeState.InFlightTasks.Remove(taskInfo.Id);
}
- private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeState, AgentSession? session)
+ private AITool[] CreateTools(BackgroundAgentState state, BackgroundAgentRuntimeState runtimeState, AgentSession? session)
{
var serializerOptions = AgentJsonUtilities.DefaultOptions;
@@ -221,43 +221,43 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
[
AIFunctionFactory.Create(
async (
- [Description("The name of the sub agent to delegate the task to.")] string agentName,
- [Description("The request to pass to the sub agent.")] string input,
+ [Description("The name of the background agent to delegate the task to.")] string agentName,
+ [Description("The request to pass to the background agent.")] string input,
[Description("A description of the task used to identify the task later.")] string description) =>
{
if (!this._agents.TryGetValue(agentName, out AIAgent? agent))
{
- return $"Error: No sub-agent found with name '{agentName}'. Available agents: {string.Join(", ", this._agents.Keys)}";
+ return $"Error: No background agent found with name '{agentName}'. Available agents: {string.Join(", ", this._agents.Keys)}";
}
int taskId = state.NextTaskId++;
- var taskInfo = new SubTaskInfo
+ var taskInfo = new BackgroundTaskInfo
{
Id = taskId,
AgentName = agentName,
Description = description,
- Status = SubTaskStatus.Running,
+ Status = BackgroundTaskStatus.Running,
};
state.Tasks.Add(taskInfo);
- // Create a dedicated session for this sub-task so it can be continued later.
+ // Create a dedicated session for this background task so it can be continued later.
AgentSession subSession = await agent.CreateSessionAsync().ConfigureAwait(false);
// Wrap in Task.Run to fork the ExecutionContext. AIAgent.RunAsync is a non-async
// method that synchronously sets the static AsyncLocal CurrentRunContext. Without
- // this isolation, the sub-agent's RunAsync would overwrite the outer (calling)
+ // this isolation, the background agent's RunAsync would overwrite the outer (calling)
// agent's CurrentRunContext, corrupting all subsequent tool invocations in the
// same FICC batch.
runtimeState.InFlightTasks[taskId] = Task.Run(() => agent.RunAsync(input, subSession));
- runtimeState.SubTaskSessions[taskId] = subSession;
+ runtimeState.BackgroundTaskSessions[taskId] = subSession;
this._sessionState.SaveState(session, state);
- return $"Sub-task {taskId} started on agent '{agentName}'.";
+ return $"Background task {taskId} started on agent '{agentName}'.";
},
new AIFunctionFactoryOptions
{
- Name = "SubAgents_StartTask",
- Description = "Start a sub-task on a named sub-agent. Returns a confirmation message containing the task ID.",
+ Name = "BackgroundAgents_StartTask",
+ Description = "Start a background task on a named background agent. Returns a confirmation message containing the task ID.",
SerializerOptions = serializerOptions,
}),
@@ -287,7 +287,7 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
this._sessionState.SaveState(session, state);
// Check if any of the requested IDs are already complete.
- SubTaskInfo? alreadyComplete = state.Tasks.FirstOrDefault(t => taskIds.Contains(t.Id) && t.Status != SubTaskStatus.Running);
+ BackgroundTaskInfo? alreadyComplete = state.Tasks.FirstOrDefault(t => taskIds.Contains(t.Id) && t.Status != BackgroundTaskStatus.Running);
if (alreadyComplete is not null)
{
return $"Task {alreadyComplete.Id} is not running; current status: {alreadyComplete.Status}.";
@@ -303,7 +303,7 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
var completedEntry = waitableTasks.First(t => t.Task == completedTask);
// Finalize the completed task.
- SubTaskInfo? taskInfo = state.Tasks.FirstOrDefault(t => t.Id == completedEntry.Id);
+ BackgroundTaskInfo? taskInfo = state.Tasks.FirstOrDefault(t => t.Id == completedEntry.Id);
if (taskInfo is not null)
{
FinalizeTask(taskInfo, completedEntry.Task, runtimeState);
@@ -314,8 +314,8 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
},
new AIFunctionFactoryOptions
{
- Name = "SubAgents_WaitForFirstCompletion",
- Description = "Block until the first of the specified sub-tasks completes. Provide one or more task IDs. Returns a status message containing the ID of the task that completed first.",
+ Name = "BackgroundAgents_WaitForFirstCompletion",
+ Description = "Block until the first of the specified background tasks completes. Provide one or more task IDs. Returns a status message containing the ID of the task that completed first.",
SerializerOptions = serializerOptions,
}),
@@ -324,7 +324,7 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
{
this.TryRefreshTaskState(state, runtimeState, session);
- SubTaskInfo? taskInfo = state.Tasks.FirstOrDefault(t => t.Id == taskId);
+ BackgroundTaskInfo? taskInfo = state.Tasks.FirstOrDefault(t => t.Id == taskId);
if (taskInfo is null)
{
return $"Error: No task found with ID {taskId}.";
@@ -332,17 +332,17 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
return taskInfo.Status switch
{
- SubTaskStatus.Completed => taskInfo.ResultText ?? "(no output)",
- SubTaskStatus.Failed => $"Task failed: {taskInfo.ErrorText ?? "Unknown error"}",
- SubTaskStatus.Lost => "Task state was lost (reference unavailable).",
- SubTaskStatus.Running => $"Task {taskId} is still running.",
+ BackgroundTaskStatus.Completed => taskInfo.ResultText ?? "(no output)",
+ BackgroundTaskStatus.Failed => $"Task failed: {taskInfo.ErrorText ?? "Unknown error"}",
+ BackgroundTaskStatus.Lost => "Task state was lost (reference unavailable).",
+ BackgroundTaskStatus.Running => $"Task {taskId} is still running.",
_ => $"Task {taskId} has status: {taskInfo.Status}.",
};
},
new AIFunctionFactoryOptions
{
- Name = "SubAgents_GetTaskResults",
- Description = "Get the text output of a sub-task by its ID. Returns the result text if complete, or status information if still running or failed.",
+ Name = "BackgroundAgents_GetTaskResults",
+ Description = "Get the text output of a background task by its ID. Returns the result text if complete, or status information if still running or failed.",
SerializerOptions = serializerOptions,
}),
@@ -358,7 +358,7 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
var sb = new StringBuilder();
sb.AppendLine("Tasks:");
- foreach (SubTaskInfo task in state.Tasks)
+ foreach (BackgroundTaskInfo task in state.Tasks)
{
sb.Append("- Task ").Append(task.Id).Append(" [").Append(task.Status).Append("] (").Append(task.AgentName).Append("): ").AppendLine(task.Description);
}
@@ -367,8 +367,8 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
},
new AIFunctionFactoryOptions
{
- Name = "SubAgents_GetAllTasks",
- Description = "List all sub-tasks with their IDs, statuses, agent names, and descriptions.",
+ Name = "BackgroundAgents_GetAllTasks",
+ Description = "List all background tasks with their IDs, statuses, agent names, and descriptions.",
SerializerOptions = serializerOptions,
}),
@@ -377,18 +377,18 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
{
this.TryRefreshTaskState(state, runtimeState, session);
- SubTaskInfo? taskInfo = state.Tasks.FirstOrDefault(t => t.Id == taskId);
+ BackgroundTaskInfo? taskInfo = state.Tasks.FirstOrDefault(t => t.Id == taskId);
if (taskInfo is null)
{
return $"Error: No task found with ID {taskId}.";
}
- if (taskInfo.Status == SubTaskStatus.Lost)
+ if (taskInfo.Status == BackgroundTaskStatus.Lost)
{
return $"Error: Task {taskId} cannot be continued because its session was lost (e.g., after a session restore). Start a new task instead.";
}
- if (taskInfo.Status == SubTaskStatus.Running)
+ if (taskInfo.Status == BackgroundTaskStatus.Running)
{
return $"Error: Task {taskId} is still running. Wait for it to complete before continuing.";
}
@@ -398,17 +398,17 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
return $"Error: Agent '{taskInfo.AgentName}' is no longer available.";
}
- if (!runtimeState.SubTaskSessions.TryGetValue(taskId, out AgentSession? subSession))
+ if (!runtimeState.BackgroundTaskSessions.TryGetValue(taskId, out AgentSession? subSession))
{
return $"Error: Session for task {taskId} is no longer available.";
}
// Reset task state and start a new run on the existing session.
- taskInfo.Status = SubTaskStatus.Running;
+ taskInfo.Status = BackgroundTaskStatus.Running;
taskInfo.ResultText = null;
taskInfo.ErrorText = null;
- // Wrap in Task.Run to isolate the ExecutionContext (see StartSubTask comment).
+ // Wrap in Task.Run to isolate the ExecutionContext (see StartBackgroundTask comment).
runtimeState.InFlightTasks[taskId] = Task.Run(() => agent.RunAsync(text, subSession));
this._sessionState.SaveState(session, state);
@@ -416,8 +416,8 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
},
new AIFunctionFactoryOptions
{
- Name = "SubAgents_ContinueTask",
- Description = "Send follow-up input to a completed or failed sub-task to resume its work. The sub-task's session is preserved, so the agent retains conversational context.",
+ Name = "BackgroundAgents_ContinueTask",
+ Description = "Send follow-up input to a completed or failed background task to resume its work. The background task's session is preserved, so the agent retains conversational context.",
SerializerOptions = serializerOptions,
}),
@@ -426,13 +426,13 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
{
this.TryRefreshTaskState(state, runtimeState, session);
- SubTaskInfo? taskInfo = state.Tasks.FirstOrDefault(t => t.Id == taskId);
+ BackgroundTaskInfo? taskInfo = state.Tasks.FirstOrDefault(t => t.Id == taskId);
if (taskInfo is null)
{
return $"Error: No task found with ID {taskId}.";
}
- if (taskInfo.Status == SubTaskStatus.Running)
+ if (taskInfo.Status == BackgroundTaskStatus.Running)
{
return $"Error: Task {taskId} is still running. Wait for it to complete before clearing.";
}
@@ -442,15 +442,15 @@ private AITool[] CreateTools(SubAgentState state, SubAgentRuntimeState runtimeSt
// Clean up runtime references.
runtimeState.InFlightTasks.Remove(taskId);
- runtimeState.SubTaskSessions.Remove(taskId);
+ runtimeState.BackgroundTaskSessions.Remove(taskId);
this._sessionState.SaveState(session, state);
return $"Task {taskId} cleared.";
},
new AIFunctionFactoryOptions
{
- Name = "SubAgents_ClearCompletedTask",
- Description = "Remove a completed or failed sub-task and release its session to free memory. Use this after retrieving results when you no longer need to continue the task.",
+ Name = "BackgroundAgents_ClearCompletedTask",
+ Description = "Remove a completed or failed background task and release its session to free memory. Use this after retrieving results when you no longer need to continue the task.",
SerializerOptions = serializerOptions,
}),
];
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentsProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentsProviderOptions.cs
similarity index 72%
rename from dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentsProviderOptions.cs
rename to dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentsProviderOptions.cs
index 27a4c1530d..83d8cd959f 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubAgentsProviderOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundAgentsProviderOptions.cs
@@ -8,21 +8,21 @@
namespace Microsoft.Agents.AI;
///
-/// Options controlling the behavior of .
+/// Options controlling the behavior of .
///
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
-public sealed class SubAgentsProviderOptions
+public sealed class BackgroundAgentsProviderOptions
{
///
- /// Gets or sets custom instructions provided to the agent for using the sub-agent tools.
+ /// Gets or sets custom instructions provided to the agent for using the background agent tools.
///
///
- /// Use the {sub_agents} placeholder to allow the provider to inject
- /// the formatted list of available sub agents.
+ /// Use the {background_agents} placeholder to allow the provider to inject
+ /// the formatted list of available background agents.
///
///
/// When (the default), the provider uses built-in instructions
- /// that guide the agent on how to use the sub-agent tools.
+ /// that guide the agent on how to use the background agent tools.
/// The agent list is always appended after the instructions regardless of this setting.
///
public string? Instructions { get; set; }
@@ -33,7 +33,7 @@ public sealed class SubAgentsProviderOptions
///
/// When (the default), the provider generates a standard list of agent names and descriptions.
/// When set, this function receives the dictionary of available agents (keyed by name) and should return
- /// a formatted string describing the available sub-agents.
+ /// a formatted string describing the available background agents.
///
public Func, string>? AgentListBuilder { get; set; }
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubTaskInfo.cs b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundTaskInfo.cs
similarity index 62%
rename from dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubTaskInfo.cs
rename to dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundTaskInfo.cs
index 91b6084ece..98f36c7e9d 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubTaskInfo.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundTaskInfo.cs
@@ -7,43 +7,43 @@
namespace Microsoft.Agents.AI;
///
-/// Represents the metadata and result of a sub-task managed by the .
+/// Represents the metadata and result of a background task managed by the .
///
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
-public sealed class SubTaskInfo
+public sealed class BackgroundTaskInfo
{
///
- /// Gets or sets the unique identifier for this sub-task.
+ /// Gets or sets the unique identifier for this background task.
///
[JsonPropertyName("id")]
public int Id { get; set; }
///
- /// Gets or sets the name of the agent that is executing this sub-task.
+ /// Gets or sets the name of the agent that is executing this background task.
///
[JsonPropertyName("agentName")]
public string AgentName { get; set; } = string.Empty;
///
- /// Gets or sets a description of what this sub-task is doing.
+ /// Gets or sets a description of what this background task is doing.
///
[JsonPropertyName("description")]
public string Description { get; set; } = string.Empty;
///
- /// Gets or sets the current status of this sub-task.
+ /// Gets or sets the current status of this background task.
///
[JsonPropertyName("status")]
- public SubTaskStatus Status { get; set; }
+ public BackgroundTaskStatus Status { get; set; }
///
- /// Gets or sets the text result of the sub-task, populated when the task completes successfully.
+ /// Gets or sets the text result of the background task, populated when the task completes successfully.
///
[JsonPropertyName("resultText")]
public string? ResultText { get; set; }
///
- /// Gets or sets the error message if the sub-task failed.
+ /// Gets or sets the error message if the background task failed.
///
[JsonPropertyName("errorText")]
public string? ErrorText { get; set; }
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubTaskStatus.cs b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundTaskStatus.cs
similarity index 57%
rename from dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubTaskStatus.cs
rename to dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundTaskStatus.cs
index f5e66f6f72..b3dfeee671 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/SubAgents/SubTaskStatus.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/BackgroundAgents/BackgroundTaskStatus.cs
@@ -6,28 +6,28 @@
namespace Microsoft.Agents.AI;
///
-/// Represents the status of a sub-task managed by the .
+/// Represents the status of a background task managed by the .
///
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
-public enum SubTaskStatus
+public enum BackgroundTaskStatus
{
///
- /// The sub-task is currently running.
+ /// The background task is currently running.
///
Running,
///
- /// The sub-task completed successfully.
+ /// The background task completed successfully.
///
Completed,
///
- /// The sub-task failed with an error.
+ /// The background task failed with an error.
///
Failed,
///
- /// The sub-task's in-flight reference was lost (e.g., after a restart),
+ /// The background task's in-flight reference was lost (e.g., after a restart),
/// and its final state cannot be determined.
///
Lost,
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Todo/TodoCompleteInput.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Todo/TodoCompleteInput.cs
new file mode 100644
index 0000000000..355c494337
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Todo/TodoCompleteInput.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using Microsoft.Shared.DiagnosticIds;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Represents the input for completing a single todo item via the .
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+internal sealed class TodoCompleteInput
+{
+ ///
+ /// Gets or sets the ID of the todo item to mark as complete.
+ ///
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Gets or sets the reason describing how or why the item was completed.
+ ///
+ [JsonPropertyName("reason")]
+ public string Reason { get; set; } = string.Empty;
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Harness/Todo/TodoProvider.cs b/dotnet/src/Microsoft.Agents.AI/Harness/Todo/TodoProvider.cs
index bb39e71c20..429c6b5646 100644
--- a/dotnet/src/Microsoft.Agents.AI/Harness/Todo/TodoProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Harness/Todo/TodoProvider.cs
@@ -54,7 +54,7 @@ Ask questions from the user where clarification is needed to create effective to
Use these tools to manage your tasks:
- Use TodoList_Add to break down complex work into trackable items (supports adding one or many at once).
- - Use TodoList_Complete to mark items as done when finished (supports one or many at once).
+ - Use TodoList_Complete to mark items as done when finished (supports one or many at once). Include a reason describing how the items were completed.
- Use TodoList_GetRemaining to check what work is still pending.
- Use TodoList_GetAll to review the full list including completed items.
- Use TodoList_Remove to remove items that are no longer needed (supports one or many at once).
@@ -235,14 +235,14 @@ private AITool[] CreateTools(AgentSession? session)
}),
AIFunctionFactory.Create(
- async (List ids) =>
+ async (List items) =>
{
SemaphoreSlim sessionLock = this.GetSessionLock(session);
await sessionLock.WaitAsync().ConfigureAwait(false);
try
{
TodoState state = this._sessionState.GetOrInitializeState(session);
- var idSet = new HashSet(ids);
+ var idSet = new HashSet(items.Select(i => i.Id));
int completed = 0;
foreach (TodoItem item in state.Items)
{
@@ -268,7 +268,7 @@ private AITool[] CreateTools(AgentSession? session)
new AIFunctionFactoryOptions
{
Name = "TodoList_Complete",
- Description = "Mark one or more todo items as complete by their IDs. Returns the number of items that were found and marked complete.",
+ Description = "Mark one or more todo items as complete. Each entry has an ID and a reason describing how/why the item was completed. Returns the number of items that were found and marked complete.",
SerializerOptions = serializerOptions,
}),
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/SubAgents/SubAgentsProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/BackgroundAgents/BackgroundAgentsProviderTests.cs
similarity index 77%
rename from dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/SubAgents/SubAgentsProviderTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/BackgroundAgents/BackgroundAgentsProviderTests.cs
index 4f76d610b3..b8a6051a4c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/SubAgents/SubAgentsProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/BackgroundAgents/BackgroundAgentsProviderTests.cs
@@ -13,9 +13,9 @@
namespace Microsoft.Agents.AI.UnitTests;
///
-/// Unit tests for the class.
+/// Unit tests for the class.
///
-public class SubAgentsProviderTests
+public class BackgroundAgentsProviderTests
{
#region Constructor Tests
@@ -26,7 +26,7 @@ public class SubAgentsProviderTests
public void Constructor_NullAgents_Throws()
{
// Act & Assert
- Assert.Throws(() => new SubAgentsProvider(null!));
+ Assert.Throws(() => new BackgroundAgentsProvider(null!));
}
///
@@ -36,7 +36,7 @@ public void Constructor_NullAgents_Throws()
public void Constructor_EmptyAgents_Throws()
{
// Act & Assert
- Assert.Throws(() => new SubAgentsProvider(Array.Empty()));
+ Assert.Throws(() => new BackgroundAgentsProvider(Array.Empty()));
}
///
@@ -49,7 +49,7 @@ public void Constructor_AgentWithNullName_Throws()
var agent = CreateMockAgent(null!, "desc");
// Act & Assert
- Assert.Throws(() => new SubAgentsProvider(new[] { agent }));
+ Assert.Throws(() => new BackgroundAgentsProvider(new[] { agent }));
}
///
@@ -62,7 +62,7 @@ public void Constructor_AgentWithEmptyName_Throws()
var agent = CreateMockAgent("", "desc");
// Act & Assert
- Assert.Throws(() => new SubAgentsProvider(new[] { agent }));
+ Assert.Throws(() => new BackgroundAgentsProvider(new[] { agent }));
}
///
@@ -76,7 +76,7 @@ public void Constructor_DuplicateNames_Throws()
var agent2 = CreateMockAgent("research", "Agent 2");
// Act & Assert
- Assert.Throws(() => new SubAgentsProvider(new[] { agent1, agent2 }));
+ Assert.Throws(() => new BackgroundAgentsProvider(new[] { agent1, agent2 }));
}
///
@@ -90,7 +90,7 @@ public void Constructor_ValidAgents_Succeeds()
var agent2 = CreateMockAgent("Writer", "Writer agent");
// Act
- var provider = new SubAgentsProvider(new[] { agent1, agent2 });
+ var provider = new BackgroundAgentsProvider(new[] { agent1, agent2 });
// Assert
Assert.NotNull(provider);
@@ -108,7 +108,7 @@ public async Task ProvideAIContextAsync_ReturnsToolsAndInstructionsAsync()
{
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
- var provider = new SubAgentsProvider(new[] { agent });
+ var provider = new BackgroundAgentsProvider(new[] { agent });
var context = CreateInvokingContext();
// Act
@@ -129,7 +129,7 @@ public async Task ProvideAIContextAsync_InstructionsIncludeAgentInfoAsync()
// Arrange
var agent1 = CreateMockAgent("Research", "Performs research");
var agent2 = CreateMockAgent("Writer", "Writes content");
- var provider = new SubAgentsProvider(new[] { agent1, agent2 });
+ var provider = new BackgroundAgentsProvider(new[] { agent1, agent2 });
var context = CreateInvokingContext();
// Act
@@ -144,22 +144,22 @@ public async Task ProvideAIContextAsync_InstructionsIncludeAgentInfoAsync()
#endregion
- #region StartSubTask Tests
+ #region StartBackgroundTask Tests
///
- /// Verify that StartSubTask returns a task ID.
+ /// Verify that StartBackgroundTask returns a task ID.
///
[Fact]
- public async Task StartSubTask_ReturnsTaskIdAsync()
+ public async Task StartBackgroundTask_ReturnsTaskIdAsync()
{
// Arrange
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
// Act
- object? result = await startSubTask.InvokeAsync(new AIFunctionArguments
+ object? result = await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Find information about AI",
@@ -175,18 +175,18 @@ public async Task StartSubTask_ReturnsTaskIdAsync()
}
///
- /// Verify that StartSubTask with invalid agent name returns an error.
+ /// Verify that StartBackgroundTask with invalid agent name returns an error.
///
[Fact]
- public async Task StartSubTask_InvalidAgentName_ReturnsErrorAsync()
+ public async Task StartBackgroundTask_InvalidAgentName_ReturnsErrorAsync()
{
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
// Act
- object? result = await startSubTask.InvokeAsync(new AIFunctionArguments
+ object? result = await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "NonExistent",
["input"] = "Some input",
@@ -200,10 +200,10 @@ public async Task StartSubTask_InvalidAgentName_ReturnsErrorAsync()
}
///
- /// Verify that StartSubTask assigns sequential IDs.
+ /// Verify that StartBackgroundTask assigns sequential IDs.
///
[Fact]
- public async Task StartSubTask_AssignsSequentialIdsAsync()
+ public async Task StartBackgroundTask_AssignsSequentialIdsAsync()
{
// Arrange
var tcs1 = new TaskCompletionSource();
@@ -215,16 +215,16 @@ public async Task StartSubTask_AssignsSequentialIdsAsync()
return callCount == 1 ? tcs1.Task : tcs2.Task;
});
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
// Act
- object? result1 = await startSubTask.InvokeAsync(new AIFunctionArguments
+ object? result1 = await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Task 1",
["description"] = "First task",
});
- object? result2 = await startSubTask.InvokeAsync(new AIFunctionArguments
+ object? result2 = await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Task 2",
@@ -253,11 +253,11 @@ public async Task WaitForFirstCompletion_ReturnsCompletedTaskIdAsync()
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction waitForFirst = GetTool(tools, "SubAgents_WaitForFirstCompletion");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction waitForFirst = GetTool(tools, "BackgroundAgents_WaitForFirstCompletion");
// Start one task
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Task 1",
@@ -288,7 +288,7 @@ public async Task WaitForFirstCompletion_EmptyList_ReturnsErrorAsync()
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction waitForFirst = GetTool(tools, "SubAgents_WaitForFirstCompletion");
+ AIFunction waitForFirst = GetTool(tools, "BackgroundAgents_WaitForFirstCompletion");
// Act
object? result = await waitForFirst.InvokeAsync(new AIFunctionArguments
@@ -302,24 +302,24 @@ public async Task WaitForFirstCompletion_EmptyList_ReturnsErrorAsync()
#endregion
- #region GetSubTaskResults Tests
+ #region GetBackgroundTaskResults Tests
///
- /// Verify that GetSubTaskResults returns the result text of a completed task.
+ /// Verify that GetBackgroundTaskResults returns the result text of a completed task.
///
[Fact]
- public async Task GetSubTaskResults_CompletedTask_ReturnsResultTextAsync()
+ public async Task GetBackgroundTaskResults_CompletedTask_ReturnsResultTextAsync()
{
// Arrange
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction waitForFirst = GetTool(tools, "SubAgents_WaitForFirstCompletion");
- AIFunction getResults = GetTool(tools, "SubAgents_GetTaskResults");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction waitForFirst = GetTool(tools, "BackgroundAgents_WaitForFirstCompletion");
+ AIFunction getResults = GetTool(tools, "BackgroundAgents_GetTaskResults");
// Start a task
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -346,20 +346,20 @@ await waitForFirst.InvokeAsync(new AIFunctionArguments
}
///
- /// Verify that GetSubTaskResults for a still-running task returns status info.
+ /// Verify that GetBackgroundTaskResults for a still-running task returns status info.
///
[Fact]
- public async Task GetSubTaskResults_RunningTask_ReturnsStatusAsync()
+ public async Task GetBackgroundTaskResults_RunningTask_ReturnsStatusAsync()
{
// Arrange
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction getResults = GetTool(tools, "SubAgents_GetTaskResults");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction getResults = GetTool(tools, "BackgroundAgents_GetTaskResults");
// Start a task (don't complete it)
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -379,15 +379,15 @@ await startSubTask.InvokeAsync(new AIFunctionArguments
}
///
- /// Verify that GetSubTaskResults for a nonexistent task returns an error.
+ /// Verify that GetBackgroundTaskResults for a nonexistent task returns an error.
///
[Fact]
- public async Task GetSubTaskResults_NonexistentTask_ReturnsErrorAsync()
+ public async Task GetBackgroundTaskResults_NonexistentTask_ReturnsErrorAsync()
{
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction getResults = GetTool(tools, "SubAgents_GetTaskResults");
+ AIFunction getResults = GetTool(tools, "BackgroundAgents_GetTaskResults");
// Act
object? result = await getResults.InvokeAsync(new AIFunctionArguments
@@ -400,21 +400,21 @@ public async Task GetSubTaskResults_NonexistentTask_ReturnsErrorAsync()
}
///
- /// Verify that GetSubTaskResults for a failed task returns the error.
+ /// Verify that GetBackgroundTaskResults for a failed task returns the error.
///
[Fact]
- public async Task GetSubTaskResults_FailedTask_ReturnsErrorTextAsync()
+ public async Task GetBackgroundTaskResults_FailedTask_ReturnsErrorTextAsync()
{
// Arrange
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction waitForFirst = GetTool(tools, "SubAgents_WaitForFirstCompletion");
- AIFunction getResults = GetTool(tools, "SubAgents_GetTaskResults");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction waitForFirst = GetTool(tools, "BackgroundAgents_WaitForFirstCompletion");
+ AIFunction getResults = GetTool(tools, "BackgroundAgents_GetTaskResults");
// Start a task
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -456,11 +456,11 @@ public async Task GetAllTasks_ReturnsRunningTasksAsync()
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction getAllTasks = GetTool(tools, "SubAgents_GetAllTasks");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction getAllTasks = GetTool(tools, "BackgroundAgents_GetAllTasks");
// Start a task
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -490,12 +490,12 @@ public async Task GetAllTasks_ShowsCompletedTasksAsync()
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction waitForFirst = GetTool(tools, "SubAgents_WaitForFirstCompletion");
- AIFunction getAllTasks = GetTool(tools, "SubAgents_GetAllTasks");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction waitForFirst = GetTool(tools, "BackgroundAgents_WaitForFirstCompletion");
+ AIFunction getAllTasks = GetTool(tools, "BackgroundAgents_GetAllTasks");
// Start and complete a task
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -525,7 +525,7 @@ public async Task GetAllTasks_NoTasks_ReturnsNoneAsync()
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction getAllTasks = GetTool(tools, "SubAgents_GetAllTasks");
+ AIFunction getAllTasks = GetTool(tools, "BackgroundAgents_GetAllTasks");
// Act
object? result = await getAllTasks.InvokeAsync(new AIFunctionArguments());
@@ -554,13 +554,13 @@ public async Task ContinueTask_CompletedTask_ResumesAsync()
return callCount == 1 ? tcs1.Task : tcs2.Task;
});
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction waitForFirst = GetTool(tools, "SubAgents_WaitForFirstCompletion");
- AIFunction continueTask = GetTool(tools, "SubAgents_ContinueTask");
- AIFunction getResults = GetTool(tools, "SubAgents_GetTaskResults");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction waitForFirst = GetTool(tools, "BackgroundAgents_WaitForFirstCompletion");
+ AIFunction continueTask = GetTool(tools, "BackgroundAgents_ContinueTask");
+ AIFunction getResults = GetTool(tools, "BackgroundAgents_GetTaskResults");
// Start and complete a task
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -606,11 +606,11 @@ public async Task ContinueTask_RunningTask_ReturnsErrorAsync()
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction continueTask = GetTool(tools, "SubAgents_ContinueTask");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction continueTask = GetTool(tools, "BackgroundAgents_ContinueTask");
// Start a task (don't complete it)
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -639,7 +639,7 @@ public async Task ContinueTask_NonexistentTask_ReturnsErrorAsync()
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction continueTask = GetTool(tools, "SubAgents_ContinueTask");
+ AIFunction continueTask = GetTool(tools, "BackgroundAgents_ContinueTask");
// Act
object? result = await continueTask.InvokeAsync(new AIFunctionArguments
@@ -666,13 +666,13 @@ public async Task ClearCompletedTask_RemovesTerminalTaskAsync()
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction waitForFirst = GetTool(tools, "SubAgents_WaitForFirstCompletion");
- AIFunction clearTask = GetTool(tools, "SubAgents_ClearCompletedTask");
- AIFunction getResults = GetTool(tools, "SubAgents_GetTaskResults");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction waitForFirst = GetTool(tools, "BackgroundAgents_WaitForFirstCompletion");
+ AIFunction clearTask = GetTool(tools, "BackgroundAgents_ClearCompletedTask");
+ AIFunction getResults = GetTool(tools, "BackgroundAgents_GetTaskResults");
// Start and complete a task
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -711,11 +711,11 @@ public async Task ClearCompletedTask_RunningTask_ReturnsErrorAsync()
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction startSubTask = GetTool(tools, "SubAgents_StartTask");
- AIFunction clearTask = GetTool(tools, "SubAgents_ClearCompletedTask");
+ AIFunction startBackgroundTask = GetTool(tools, "BackgroundAgents_StartTask");
+ AIFunction clearTask = GetTool(tools, "BackgroundAgents_ClearCompletedTask");
// Start a task (don't complete it)
- await startSubTask.InvokeAsync(new AIFunctionArguments
+ await startBackgroundTask.InvokeAsync(new AIFunctionArguments
{
["agentName"] = "Research",
["input"] = "Research AI",
@@ -743,7 +743,7 @@ public async Task ClearCompletedTask_NonexistentTask_ReturnsErrorAsync()
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- AIFunction clearTask = GetTool(tools, "SubAgents_ClearCompletedTask");
+ AIFunction clearTask = GetTool(tools, "BackgroundAgents_ClearCompletedTask");
// Act
object? result = await clearTask.InvokeAsync(new AIFunctionArguments
@@ -767,7 +767,7 @@ public void StateKeys_ReturnsExpectedKeys()
{
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
- var provider = new SubAgentsProvider(new[] { agent });
+ var provider = new BackgroundAgentsProvider(new[] { agent });
// Act
var keys = provider.StateKeys;
@@ -782,23 +782,23 @@ public void StateKeys_ReturnsExpectedKeys()
#region CurrentRunContext Isolation Tests
///
- /// Verify that StartSubTask does not corrupt CurrentRunContext of the calling agent.
+ /// Verify that StartBackgroundTask does not corrupt CurrentRunContext of the calling agent.
/// Because RunAsync is a non-async method that synchronously sets the static AsyncLocal
- /// CurrentRunContext, the provider must isolate the sub-agent call to prevent overwriting
+ /// CurrentRunContext, the provider must isolate the background agent call to prevent overwriting
/// the outer agent's context.
///
[Fact]
- public async Task StartSubTask_DoesNotCorruptCurrentRunContextAsync()
+ public async Task StartBackgroundTask_DoesNotCorruptCurrentRunContextAsync()
{
// Arrange
var tcs = new TaskCompletionSource();
var agent = CreateMockAgentWithRunResult("Research", tcs.Task);
var (tools, _) = await CreateToolsWithProviderAsync(agent);
- var startTool = GetTool(tools, "SubAgents_StartTask");
+ var startTool = GetTool(tools, "BackgroundAgents_StartTask");
AgentRunContext? contextBefore = AIAgent.CurrentRunContext;
- // Act — invoke StartSubTask; this calls agent.RunAsync internally.
+ // Act — invoke StartBackgroundTask; this calls agent.RunAsync internally.
var args = new AIFunctionArguments(new Dictionary
{
["agentName"] = "Research",
@@ -826,16 +826,16 @@ public async Task CustomInstructions_OverridesDefaultInstructionsAsync()
{
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
- const string CustomInstructions = "These are custom sub-agent instructions.\n{sub_agents}";
- var options = new SubAgentsProviderOptions { Instructions = CustomInstructions };
- var provider = new SubAgentsProvider(new[] { agent }, options);
+ const string CustomInstructions = "These are custom background agent instructions.\n{background_agents}";
+ var options = new BackgroundAgentsProviderOptions { Instructions = CustomInstructions };
+ var provider = new BackgroundAgentsProvider(new[] { agent }, options);
var context = CreateInvokingContext();
// Act
AIContext result = await provider.InvokingAsync(context);
// Assert — custom instructions replace default, agent list is injected via {sub_agents} placeholder
- Assert.Contains("These are custom sub-agent instructions.", result.Instructions);
+ Assert.Contains("These are custom background agent instructions.", result.Instructions);
Assert.Contains("Research", result.Instructions);
}
@@ -847,15 +847,15 @@ public async Task DefaultInstructions_ContainsToolReferenceAndAgentListAsync()
{
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
- var provider = new SubAgentsProvider(new[] { agent });
+ var provider = new BackgroundAgentsProvider(new[] { agent });
var context = CreateInvokingContext();
// Act
AIContext result = await provider.InvokingAsync(context);
// Assert — instructions contain tool usage guidance and agent list
- Assert.Contains("SubAgents_*", result.Instructions);
- Assert.Contains("SubAgents_ClearCompletedTask", result.Instructions);
+ Assert.Contains("BackgroundAgents_*", result.Instructions);
+ Assert.Contains("BackgroundAgents_ClearCompletedTask", result.Instructions);
Assert.Contains("Research", result.Instructions);
Assert.Contains("Research agent", result.Instructions);
}
@@ -868,11 +868,11 @@ public async Task CustomAgentListBuilder_UsedForAgentListAsync()
{
// Arrange
var agent = CreateMockAgent("Research", "Research agent");
- var options = new SubAgentsProviderOptions
+ var options = new BackgroundAgentsProviderOptions
{
AgentListBuilder = agents => $"Custom list: {string.Join(", ", agents.Keys)}",
};
- var provider = new SubAgentsProvider(new[] { agent }, options);
+ var provider = new BackgroundAgentsProvider(new[] { agent }, options);
var context = CreateInvokingContext();
// Act
@@ -880,7 +880,7 @@ public async Task CustomAgentListBuilder_UsedForAgentListAsync()
// Assert — custom agent list builder output is in instructions
Assert.Contains("Custom list: Research", result.Instructions);
- Assert.DoesNotContain("Available sub-agents:", result.Instructions);
+ Assert.DoesNotContain("Available background agents:", result.Instructions);
}
#endregion
@@ -935,9 +935,9 @@ private static AIAgent CreateMockAgentWithCallback(string name, Func Tools, SubAgentsProvider Provider)> CreateToolsWithProviderAsync(AIAgent agent)
+ private static async Task<(IEnumerable Tools, BackgroundAgentsProvider Provider)> CreateToolsWithProviderAsync(AIAgent agent)
{
- var provider = new SubAgentsProvider(new[] { agent });
+ var provider = new BackgroundAgentsProvider(new[] { agent });
var context = CreateInvokingContext();
AIContext result = await provider.InvokingAsync(context);
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Todo/TodoProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Todo/TodoProviderTests.cs
index d2724478d3..c733d8efe7 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Todo/TodoProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/Todo/TodoProviderTests.cs
@@ -116,7 +116,7 @@ public async Task CompleteTodos_MarksItemCompleteAsync()
await addTodos.InvokeAsync(new AIFunctionArguments() { ["todos"] = new List { new() { Title = "Test", Description = null } } });
// Act
- object? result = await completeTodos.InvokeAsync(new AIFunctionArguments() { ["ids"] = new List { 1 } });
+ object? result = await completeTodos.InvokeAsync(new AIFunctionArguments() { ["items"] = new List { new() { Id = 1, Reason = "Done" } } });
// Assert
Assert.True(state.Items[0].IsComplete);
@@ -139,7 +139,7 @@ await addTodos.InvokeAsync(new AIFunctionArguments()
});
// Act
- object? result = await completeTodos.InvokeAsync(new AIFunctionArguments() { ["ids"] = new List { 1, 3 } });
+ object? result = await completeTodos.InvokeAsync(new AIFunctionArguments() { ["items"] = new List { new() { Id = 1, Reason = "Done" }, new() { Id = 3, Reason = "Done" } } });
// Assert
Assert.True(state.Items[0].IsComplete);
@@ -159,12 +159,35 @@ public async Task CompleteTodos_ReturnsZeroForMissingIdsAsync()
AIFunction completeTodos = GetTool(tools, "TodoList_Complete");
// Act
- object? result = await completeTodos.InvokeAsync(new AIFunctionArguments() { ["ids"] = new List { 999 } });
+ object? result = await completeTodos.InvokeAsync(new AIFunctionArguments() { ["items"] = new List { new() { Id = 999, Reason = "Done" } } });
// Assert
Assert.Equal(0, GetIntResult(result));
}
+ ///
+ /// Verify that CompleteTodos accepts an optional reason parameter.
+ ///
+ [Fact]
+ public async Task CompleteTodos_AcceptsReasonParameterAsync()
+ {
+ // Arrange
+ var (tools, state) = await CreateToolsWithStateAsync();
+ AIFunction addTodos = GetTool(tools, "TodoList_Add");
+ AIFunction completeTodos = GetTool(tools, "TodoList_Complete");
+ await addTodos.InvokeAsync(new AIFunctionArguments() { ["todos"] = new List { new() { Title = "Research topic" } } });
+
+ // Act
+ object? result = await completeTodos.InvokeAsync(new AIFunctionArguments()
+ {
+ ["items"] = new List { new() { Id = 1, Reason = "Found the answer in the documentation." } },
+ });
+
+ // Assert
+ Assert.True(state.Items[0].IsComplete);
+ Assert.Equal(1, GetIntResult(result));
+ }
+
#endregion
#region RemoveTodos Tests
@@ -249,7 +272,7 @@ await addTodos.InvokeAsync(new AIFunctionArguments()
{
["todos"] = new List { new() { Title = "Done", Description = null }, new() { Title = "Pending", Description = null } },
});
- await completeTodos.InvokeAsync(new AIFunctionArguments() { ["ids"] = new List { 1 } });
+ await completeTodos.InvokeAsync(new AIFunctionArguments() { ["items"] = new List { new() { Id = 1, Reason = "Done" } } });
// Act
object? result = await getRemainingTodos.InvokeAsync(new AIFunctionArguments());
@@ -279,7 +302,7 @@ await addTodos.InvokeAsync(new AIFunctionArguments()
{
["todos"] = new List { new() { Title = "Done", Description = null }, new() { Title = "Pending", Description = null } },
});
- await completeTodos.InvokeAsync(new AIFunctionArguments() { ["ids"] = new List { 1 } });
+ await completeTodos.InvokeAsync(new AIFunctionArguments() { ["items"] = new List { new() { Id = 1, Reason = "Done" } } });
// Act
object? result = await getAllTodos.InvokeAsync(new AIFunctionArguments());
@@ -376,7 +399,7 @@ await addTodos.InvokeAsync(new AIFunctionArguments()
{
["todos"] = new List { new() { Title = "Done", Description = null }, new() { Title = "Pending", Description = null } },
});
- await completeTodos.InvokeAsync(new AIFunctionArguments() { ["ids"] = new List { 1 } });
+ await completeTodos.InvokeAsync(new AIFunctionArguments() { ["items"] = new List { new() { Id = 1, Reason = "Done" } } });
// Act
var remaining = await provider.GetRemainingTodosAsync(session);
@@ -543,7 +566,7 @@ await addTodos.InvokeAsync(new AIFunctionArguments()
new() { Title = "Second", Description = "Has details" },
},
});
- await completeTodos.InvokeAsync(new AIFunctionArguments() { ["ids"] = new List { 1 } });
+ await completeTodos.InvokeAsync(new AIFunctionArguments() { ["items"] = new List { new() { Id = 1, Reason = "Done" } } });
// Act — second invocation should see the updated list in messages
AIContext result2 = await provider.InvokingAsync(context);
@@ -762,7 +785,7 @@ await Task.WhenAll(
{
["todos"] = new List { new() { Title = "New C" } },
}).AsTask(),
- completeTodos.InvokeAsync(new AIFunctionArguments() { ["ids"] = new List { 1, 2, 3 } }).AsTask());
+ completeTodos.InvokeAsync(new AIFunctionArguments() { ["items"] = new List { new() { Id = 1, Reason = "Done" }, new() { Id = 2, Reason = "Done" }, new() { Id = 3, Reason = "Done" } } }).AsTask());
// Assert
object? allResult = await getAllTodos.InvokeAsync(new AIFunctionArguments());