diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AAgentHandler.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AAgentHandler.cs
index 72cbde5a6bd..8d3de412b6b 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AAgentHandler.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AAgentHandler.cs
@@ -33,7 +33,7 @@ internal sealed class A2AAgentHandler : IAgentHandler
/// Initializes a new instance of the class.
///
/// The hosted agent that provides the execution logic.
- /// Controls whether the agent runs in background mode.
+ /// Controls which A2A artifact the agent response is returned as.
public A2AAgentHandler(
AIHostAgent hostAgent,
AgentRunMode runMode)
@@ -80,20 +80,20 @@ public async Task CancelAsync(RequestContext context, AgentEventQueue eventQueue
/// The queue the response events are written to.
///
/// to run the agent to completion before emitting a single completed task;
- /// to emit task updates as they are produced. Ignored when the server disallows
- /// background responses, because a message response is always aggregated.
+ /// to emit task updates as they are produced. Ignored when the server is configured
+ /// through to return a message, because a message response is always aggregated.
///
/// A to cancel the operation.
///
/// The response shape is decided by two independent inputs:
///
/// -
- /// Whether the server allows background responses. This is configured per agent registration, for example:
+ /// Which A2A artifact the server returns. This is configured per agent registration, for example:
///
/// builder.AddA2AServer(agent, (A2AServerRegistrationOptions options) =>
- /// options.AgentRunMode = AgentRunMode.AllowBackgroundIfSupported);
+ /// options.AgentRunMode = AgentRunMode.ReturnTask);
///
- /// Use AgentRunMode.DisallowBackground to always respond with a message instead of a task.
+ /// Use AgentRunMode.ReturnMessage to always respond with a message instead of a task.
///
/// -
/// Whether the client asked for an immediate response. In the A2A protocol this is the
@@ -104,17 +104,20 @@ public async Task CancelAsync(RequestContext context, AgentEventQueue eventQueue
/// The resulting combinations are:
///
/// -
- /// Server allows background responses and ReturnImmediately = true: returns the initial task, then the
- /// rest of the updates piece by piece.
+ /// Server is configured through to return a task and ReturnImmediately = true:
+ /// returns the initial task, then the rest of the updates piece by piece.
///
/// -
- /// Server allows background responses and ReturnImmediately = false: returns a single completed task.
+ /// Server is configured through to return a task and ReturnImmediately = false:
+ /// returns a single completed task.
///
/// -
- /// Server disallows background responses and ReturnImmediately = true: returns a message.
+ /// Server is configured through to return a message and ReturnImmediately = true:
+ /// returns a message.
///
/// -
- /// Server disallows background responses and ReturnImmediately = false: returns a message.
+ /// Server is configured through to return a message and ReturnImmediately = false:
+ /// returns a message.
///
///
///
@@ -133,11 +136,11 @@ private async Task HandleNewMessageAsync(RequestContext context, AgentEventQueue
List chatMessages = context.Message is not null ? [context.Message.ToChatMessage()] : [];
- var options = CreateRunOptions(context);
-
- // Decide whether to run in background based on user preferences and agent capabilities
+ // Decide which A2A artifact to return based on the configured run mode.
var decisionContext = new A2ARunDecisionContext(context);
- var returnTask = await this._runMode.ShouldRunInBackgroundAsync(decisionContext, cancellationToken).ConfigureAwait(false);
+ var returnTask = await this._runMode.ShouldReturnTaskAsync(decisionContext, cancellationToken).ConfigureAwait(false);
+
+ var options = CreateRunOptions(context);
var updates = this._hostAgent.RunStreamingAsync(chatMessages, session, options, cancellationToken);
@@ -148,20 +151,20 @@ private async Task HandleNewMessageAsync(RequestContext context, AgentEventQueue
var taskUpdater = new TaskUpdater(eventQueue, context.TaskId, contextId);
if (aggregateTaskUpdates)
{
- // The server allows background responses, but the non-streaming client request has
+ // The server is configured through AgentRunMode to return a task, but the non-streaming client request has
// ReturnImmediately disabled, so collect all updates and return a completed task.
await AggregateTaskUpdatesAsync(updates, taskUpdater, eventQueue, cancellationToken).ConfigureAwait(false);
}
else
{
- // The server allows background responses and this is either a streaming request or a
+ // The server is configured through AgentRunMode to return a task and this is either a streaming request or a
// non-streaming request with ReturnImmediately enabled, so emit task updates as they arrive.
await StreamTaskUpdatesAsync(updates, taskUpdater, cancellationToken).ConfigureAwait(false);
}
}
else
{
- // The server disallows background responses, so return one aggregated message regardless
+ // The server is configured through AgentRunMode to return a message, so return one aggregated message regardless
// of the client request's ReturnImmediately value.
await StreamMessageUpdatesAsync(contextId, updates, eventQueue, cancellationToken).ConfigureAwait(false);
}
@@ -179,10 +182,7 @@ private async Task HandleTaskUpdateAsync(RequestContext context, AgentEventQueue
List chatMessages = ExtractChatMessagesFromTaskHistory(context.Task);
- var decisionContext = new A2ARunDecisionContext(context);
- var allowBackgroundResponses = await this._runMode.ShouldRunInBackgroundAsync(decisionContext, cancellationToken).ConfigureAwait(false);
-
- var options = CreateRunOptions(context, allowBackgroundResponses);
+ var options = CreateRunOptions(context);
AgentResponse response;
try
@@ -233,13 +233,8 @@ private async Task HandleTaskUpdateAsync(RequestContext context, AgentEventQueue
/// MessageSendParams.metadata and MessageSendParams.configuration to the hosted agent.
///
/// The A2A request context of the incoming request.
- ///
- /// The value to assign to . Defaults to , which leaves it unset.
- ///
- ///
- /// The run options to invoke the agent with, or when there is nothing to forward.
- ///
- private static AgentRunOptions? CreateRunOptions(RequestContext context, bool? allowBackgroundResponses = null)
+ /// The run options to invoke the agent with.
+ private static AgentRunOptions CreateRunOptions(RequestContext context)
{
AdditionalPropertiesDictionary? additionalProperties = context.Metadata is { Count: > 0 }
? context.Metadata.ToAdditionalProperties()
@@ -252,14 +247,8 @@ private async Task HandleTaskUpdateAsync(RequestContext context, AgentEventQueue
(additionalProperties ??= [])[ConfigurationPropertyKey] = configuration;
}
- if (allowBackgroundResponses is null && additionalProperties is null)
- {
- return null;
- }
-
return new AgentRunOptions
{
- AllowBackgroundResponses = allowBackgroundResponses,
AdditionalProperties = additionalProperties
};
}
@@ -294,7 +283,8 @@ private static List ExtractChatMessagesFromTaskHistory(AgentTask? a
/// Emits a task and streams the agent updates into it as artifacts as they are produced.
///
///
- /// Handles the case where the server allows background responses and the response is delivered incrementally:
+ /// Handles the case where the server is configured through to return a task and the
+ /// response is delivered incrementally:
/// either a streaming (message/stream) request, or a non-streaming request with
/// ReturnImmediately = true. In the latter case the caller receives the initial task immediately and
/// obtains the remaining updates by polling the task.
@@ -342,7 +332,8 @@ private static async Task StreamTaskUpdatesAsync(IAsyncEnumerable
///
- /// Handles the case where the server allows background responses and a non-streaming client sent
+ /// Handles the case where the server is configured through to return a task and a
+ /// non-streaming client sent
/// ReturnImmediately = false, meaning it wants the final result in the response rather than a task
/// it has to poll. No task event is emitted until the agent stream finishes, because the server returns on the
/// first task event; emitting early would hand the caller an in-progress task instead of a completed one.
@@ -384,7 +375,8 @@ await eventQueue.AddArtifactAsync(
/// Consumes the agent updates and emits the aggregated result as a single message.
///
///
- /// Handles the case where the server disallows background responses, which applies regardless of the client's
+ /// Handles the case where the server is configured through to return a message, which
+ /// applies regardless of the client's
/// ReturnImmediately value: a message is not a long-running entity, so there is nothing to return early
/// or poll for and the full agent run is always aggregated into one message. An empty message is emitted when
/// the agent produces no messages.
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerRegistrationOptions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerRegistrationOptions.cs
index 7bd30f9a7cd..2ef3f6be779 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerRegistrationOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerRegistrationOptions.cs
@@ -16,7 +16,7 @@ public sealed class A2AServerRegistrationOptions
/// Gets or sets the agent run mode that controls how the agent responds to A2A requests.
///
///
- /// When , defaults to .
+ /// When , defaults to .
///
public AgentRunMode? AgentRunMode { get; set; }
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs
index 556a0d931a3..8e8240493c2 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs
@@ -186,7 +186,7 @@ private static A2AServer CreateA2AServer(IServiceProvider serviceProvider, AIAge
if (agentHandler is null)
{
var agentSessionStore = serviceProvider.GetKeyedService(agent.Name);
- var runMode = options?.AgentRunMode ?? AgentRunMode.DisallowBackground;
+ var runMode = options?.AgentRunMode ?? AgentRunMode.ReturnMessage;
// Ensure that we have an IsolationKeyScopedAgentSessionStore registered.
if (agentSessionStore?.GetService() is null)
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AgentRunMode.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AgentRunMode.cs
index 3abb90afb6d..95519b3255d 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AgentRunMode.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AgentRunMode.cs
@@ -10,7 +10,8 @@
namespace Microsoft.Agents.AI.Hosting.A2A;
///
-/// Specifies how the A2A hosting layer determines whether to run in background or not.
+/// Specifies which A2A protocol artifact the hosting layer returns for a run of an :
+/// an AgentMessage or an AgentTask.
///
[Experimental(DiagnosticIds.Experiments.AIResponseContinuations)]
public sealed class AgentRunMode : IEquatable
@@ -20,48 +21,46 @@ public sealed class AgentRunMode : IEquatable
private const string DynamicValue = "dynamic";
private readonly string _value;
- private readonly Func>? _runInBackground;
+ private readonly Func>? _returnTask;
- private AgentRunMode(string value, Func>? runInBackground = null)
+ private AgentRunMode(string value, Func>? returnTask = null)
{
this._value = value;
- this._runInBackground = runInBackground;
+ this._returnTask = returnTask;
}
///
- /// Disallows the background responses from the agent. Is equivalent to configuring as false.
- /// In the A2A protocol terminology will make responses be returned as AgentMessage.
+ /// Returns the agent response as an AgentMessage. The updates produced by the agent are aggregated
+ /// into a single message.
///
- public static AgentRunMode DisallowBackground => new(MessageValue);
+ public static AgentRunMode ReturnMessage => new(MessageValue);
///
- /// Allows the background responses from the agent. Is equivalent to configuring as true.
- /// In the A2A protocol terminology will make responses be returned as AgentTask if the agent supports background responses, and as AgentMessage otherwise.
+ /// Returns the agent response as an AgentTask, allowing the caller to track its lifecycle and to
+ /// receive the result incrementally.
///
- public static AgentRunMode AllowBackgroundIfSupported => new(TaskValue);
+ public static AgentRunMode ReturnTask => new(TaskValue);
///
- /// The agent run mode is decided by the supplied delegate.
- /// The delegate receives an with the incoming
- /// message and returns a boolean specifying whether to run the agent in background mode.
- /// indicates that the agent should run in background mode and return an
- /// AgentTask if the agent supports background mode; otherwise, it returns an AgentMessage
- /// if the mode is not supported. indicates that the agent should run in
- /// non-background mode and return an AgentMessage.
+ /// Defers the choice between an AgentMessage and an AgentTask to the supplied
+ /// delegate, which is invoked for each new-message request. The delegate receives
+ /// an describing the incoming request and returns to
+ /// return an AgentTask, or to return an AgentMessage. Continuations of an
+ /// existing task remain task responses and do not invoke the delegate.
///
- ///
- /// An async delegate that decides whether the response should be wrapped in an AgentTask.
+ ///
+ /// An async delegate that decides whether a new-message response is returned as an AgentTask.
///
- public static AgentRunMode AllowBackgroundWhen(Func> runInBackground)
+ public static AgentRunMode ReturnTaskWhen(Func> returnTask)
{
- ArgumentNullException.ThrowIfNull(runInBackground);
- return new(DynamicValue, runInBackground);
+ ArgumentNullException.ThrowIfNull(returnTask);
+ return new(DynamicValue, returnTask);
}
///
/// Determines whether the agent response should be returned as an AgentTask.
///
- internal ValueTask ShouldRunInBackgroundAsync(A2ARunDecisionContext context, CancellationToken cancellationToken)
+ internal ValueTask ShouldReturnTaskAsync(A2ARunDecisionContext context, CancellationToken cancellationToken)
{
if (string.Equals(this._value, MessageValue, StringComparison.OrdinalIgnoreCase))
{
@@ -74,9 +73,9 @@ internal ValueTask ShouldRunInBackgroundAsync(A2ARunDecisionContext contex
}
// Dynamic: delegate to custom callback.
- if (this._runInBackground is not null)
+ if (this._returnTask is not null)
{
- return this._runInBackground(context, cancellationToken);
+ return this._returnTask(context, cancellationToken);
}
// No delegate provided — fall back to "message" behavior.
@@ -87,7 +86,7 @@ internal ValueTask ShouldRunInBackgroundAsync(A2ARunDecisionContext contex
public bool Equals(AgentRunMode? other) =>
other is not null
&& string.Equals(this._value, other._value, StringComparison.OrdinalIgnoreCase)
- && ReferenceEquals(this._runInBackground, other._runInBackground);
+ && ReferenceEquals(this._returnTask, other._returnTask);
///
public override bool Equals(object? obj) => this.Equals(obj as AgentRunMode);
@@ -95,7 +94,7 @@ other is not null
///
public override int GetHashCode() => HashCode.Combine(
StringComparer.OrdinalIgnoreCase.GetHashCode(this._value),
- RuntimeHelpers.GetHashCode(this._runInBackground));
+ RuntimeHelpers.GetHashCode(this._returnTask));
///
public override string ToString() => this._value;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AAgentHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AAgentHandlerTests.cs
index 8ab2c239a31..48999255b2e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AAgentHandlerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AAgentHandlerTests.cs
@@ -24,10 +24,10 @@ public sealed class A2AAgentHandlerTests
private const string ConfigurationPropertyKey = "a2a.configuration";
///
- /// Verifies that when there is no request data to forward, null options are passed to RunStreamingAsync.
+ /// Verifies that when there is no request data to forward, empty options are passed to RunStreamingAsync.
///
[Fact]
- public async Task ExecuteAsync_WhenMetadataIsNull_PassesNullOptionsToRunStreamingAsync()
+ public async Task ExecuteAsync_WhenMetadataIsNull_PassesEmptyOptionsToRunStreamingAsync()
{
// Arrange
AgentRunOptions? capturedOptions = null;
@@ -40,7 +40,9 @@ public async Task ExecuteAsync_WhenMetadataIsNull_PassesNullOptionsToRunStreamin
});
// Assert
- Assert.Null(capturedOptions);
+ Assert.NotNull(capturedOptions);
+ Assert.Null(capturedOptions.AllowBackgroundResponses);
+ Assert.Null(capturedOptions.AdditionalProperties);
}
///
@@ -146,7 +148,7 @@ public async Task ExecuteAsync_WhenConfigurationRequestsImmediateReturn_DoesNotS
AgentRunOptions? capturedOptions = null;
A2AAgentHandler handler = CreateHandler(
CreateAgentMock(options => capturedOptions = options),
- runMode: AgentRunMode.DisallowBackground);
+ runMode: AgentRunMode.ReturnMessage);
// Act
await InvokeExecuteAsync(handler, new RequestContext
@@ -250,7 +252,7 @@ public async Task ExecuteAsync_DynamicMode_WithFalseCallback_ReturnsMessageAsync
// Arrange
A2AAgentHandler handler = CreateHandler(
CreateAgentMock(_ => { }),
- runMode: AgentRunMode.AllowBackgroundWhen((_, _) => ValueTask.FromResult(false)));
+ runMode: AgentRunMode.ReturnTaskWhen((_, _) => ValueTask.FromResult(false)));
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -272,7 +274,7 @@ public async Task ExecuteAsync_DynamicMode_WithTrueCallback_ReturnsTaskAsync()
// Arrange
A2AAgentHandler handler = CreateHandler(
CreateAgentMock(_ => { }),
- runMode: AgentRunMode.AllowBackgroundWhen((_, _) => ValueTask.FromResult(true)));
+ runMode: AgentRunMode.ReturnTaskWhen((_, _) => ValueTask.FromResult(true)));
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -288,10 +290,10 @@ public async Task ExecuteAsync_DynamicMode_WithTrueCallback_ReturnsTaskAsync()
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
///
- /// Verifies that an immediate request emits the initial task and streams subsequent updates when background responses are allowed.
+ /// Verifies that an immediate request emits the initial task and streams subsequent updates in ReturnTask mode.
///
[Fact]
- public async Task ExecuteAsync_WhenBackgroundResponsesAllowedAndReturnImmediatelyTrue_StreamsTaskUpdatesAsync()
+ public async Task ExecuteAsync_WhenReturnTaskModeAndReturnImmediatelyTrue_StreamsTaskUpdatesAsync()
{
// Arrange
AgentResponseUpdate[] updates =
@@ -306,7 +308,7 @@ public async Task ExecuteAsync_WhenBackgroundResponsesAllowedAndReturnImmediatel
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -592,7 +594,7 @@ public async Task ExecuteAsync_WhenMessageIsNull_SucceedsWithEmptyMessagesAsync(
}
///
- /// Verifies that the dynamic AllowBackgroundWhen delegate receives the correct RequestContext.
+ /// Verifies that the dynamic ReturnTaskWhen delegate receives the correct RequestContext.
///
[Fact]
public async Task ExecuteAsync_DynamicMode_DelegateReceivesRequestContextAsync()
@@ -601,7 +603,7 @@ public async Task ExecuteAsync_DynamicMode_DelegateReceivesRequestContextAsync()
A2ARunDecisionContext? capturedContext = null;
A2AAgentHandler handler = CreateHandler(
CreateAgentMock(_ => { }),
- runMode: AgentRunMode.AllowBackgroundWhen((ctx, _) =>
+ runMode: AgentRunMode.ReturnTaskWhen((ctx, _) =>
{
capturedContext = ctx;
return ValueTask.FromResult(false);
@@ -698,10 +700,10 @@ public async Task ExecuteAsync_Streaming_EnqueuesSingleAggregatedMessageAsync()
}
///
- /// Verifies that allowing background responses emits a task lifecycle in streaming mode.
+ /// Verifies that ReturnTask mode emits a task lifecycle in streaming mode.
///
[Fact]
- public async Task ExecuteAsync_Streaming_WhenBackgroundResponsesAllowed_StreamsTaskUpdatesAsync()
+ public async Task ExecuteAsync_Streaming_WhenReturnTaskMode_StreamsTaskUpdatesAsync()
{
// Arrange
AgentResponseUpdate[] updates =
@@ -730,7 +732,7 @@ public async Task ExecuteAsync_Streaming_WhenBackgroundResponsesAllowed_StreamsT
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -782,7 +784,7 @@ public async Task ExecuteAsync_Streaming_WhenBackgroundResponsesAllowed_StreamsT
/// Verifies that a non-immediate request aggregates all updates into a completed task.
///
[Fact]
- public async Task ExecuteAsync_WhenBackgroundResponsesAllowedAndReturnImmediatelyFalse_ReturnsCompletedTaskAsync()
+ public async Task ExecuteAsync_WhenReturnTaskModeAndReturnImmediatelyFalse_ReturnsCompletedTaskAsync()
{
// Arrange
AgentResponseUpdate[] updates =
@@ -792,7 +794,7 @@ public async Task ExecuteAsync_WhenBackgroundResponsesAllowedAndReturnImmediatel
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -831,7 +833,7 @@ public async Task ExecuteAsync_WhenAggregatingTaskUpdates_PreservesArtifactMetad
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -869,7 +871,7 @@ public async Task ExecuteAsync_WhenAggregatedTaskEmissionIsCanceled_CancelsTaskA
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
var events = new EventCollector();
var eventQueue = new AgentEventQueue();
var readerTask = ReadEventsAsync(eventQueue, events);
@@ -917,7 +919,7 @@ public async Task ExecuteAsync_WhenAggregatedTaskEmissionFails_FailsTaskAsync()
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsForThrowingExecuteAsync(handler, new RequestContext
@@ -940,10 +942,10 @@ public async Task ExecuteAsync_WhenAggregatedTaskEmissionFails_FailsTaskAsync()
}
///
- /// Verifies that an immediate request returns one aggregated message when background responses are disabled.
+ /// Verifies that an immediate request returns one aggregated message in ReturnMessage mode.
///
[Fact]
- public async Task ExecuteAsync_WhenBackgroundResponsesDisallowedAndReturnImmediatelyTrue_ReturnsMessageAsync()
+ public async Task ExecuteAsync_WhenReturnMessageModeAndReturnImmediatelyTrue_ReturnsMessageAsync()
{
// Arrange
AgentResponseUpdate[] updates =
@@ -953,7 +955,7 @@ public async Task ExecuteAsync_WhenBackgroundResponsesDisallowedAndReturnImmedia
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.DisallowBackground);
+ runMode: AgentRunMode.ReturnMessage);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -973,10 +975,10 @@ public async Task ExecuteAsync_WhenBackgroundResponsesDisallowedAndReturnImmedia
}
///
- /// Verifies that a non-immediate request aggregates all updates into one message when background responses are disabled.
+ /// Verifies that a non-immediate request aggregates all updates into one message in ReturnMessage mode.
///
[Fact]
- public async Task ExecuteAsync_WhenBackgroundResponsesDisallowedAndReturnImmediatelyFalse_ReturnsMessageAsync()
+ public async Task ExecuteAsync_WhenReturnMessageModeAndReturnImmediatelyFalse_ReturnsMessageAsync()
{
// Arrange
AgentResponseUpdate[] updates =
@@ -986,7 +988,7 @@ public async Task ExecuteAsync_WhenBackgroundResponsesDisallowedAndReturnImmedia
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.DisallowBackground);
+ runMode: AgentRunMode.ReturnMessage);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -1021,7 +1023,7 @@ public async Task ExecuteAsync_Streaming_WithoutMessageId_ContinuesCurrentArtifa
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -1063,7 +1065,7 @@ public async Task ExecuteAsync_Streaming_WithoutMessageIds_StreamsSingleArtifact
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -1106,7 +1108,7 @@ public async Task ExecuteAsync_Streaming_WithEmptyMessageIds_StreamsSingleArtifa
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -1156,7 +1158,7 @@ public async Task ExecuteAsync_Streaming_WhenCancellationRequested_CancelsTaskAs
ItExpr.IsAny(),
ItExpr.IsAny())
.Returns(() => ToCancelingAsyncEnumerableAsync(cts));
- A2AAgentHandler handler = CreateHandler(agentMock, runMode: AgentRunMode.AllowBackgroundIfSupported);
+ A2AAgentHandler handler = CreateHandler(agentMock, runMode: AgentRunMode.ReturnTask);
var events = new EventCollector();
var eventQueue = new AgentEventQueue();
var readerTask = ReadEventsAsync(eventQueue, events);
@@ -1199,7 +1201,7 @@ public async Task ExecuteAsync_Streaming_WhenAgentThrows_FailsTaskAsync()
CreateThrowingStreamingAgentMock(
[new AgentResponseUpdate(ChatRole.Assistant, "chunk 1") { ResponseId = "r1", MessageId = "m1" }],
new InvalidOperationException("Stream failed")),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsForThrowingExecuteAsync(handler, new RequestContext
@@ -1245,7 +1247,7 @@ public async Task ExecuteAsync_Streaming_WhenMessageIdChanges_FinalizesPreviousA
];
A2AAgentHandler handler = CreateHandler(
CreateThrowingStreamingAgentMock(updates, new InvalidOperationException("Stream failed")),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsForThrowingExecuteAsync(handler, new RequestContext
@@ -1291,7 +1293,7 @@ public async Task ExecuteAsync_Streaming_WhenMessageIdReappears_UsesDistinctArti
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -1345,7 +1347,7 @@ public async Task ExecuteAsync_Streaming_WhenAgentThrowsOperationCanceledWithout
// Arrange
A2AAgentHandler handler = CreateHandler(
CreateThrowingStreamingAgentMock([], new OperationCanceledException("Agent gave up")),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsForThrowingExecuteAsync(handler, new RequestContext
@@ -1369,7 +1371,7 @@ public async Task ExecuteAsync_Streaming_WithNoUpdates_CompletesTaskAsync()
// Arrange
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock([]),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -1404,7 +1406,7 @@ public async Task ExecuteAsync_Streaming_WithOnlyContentlessUpdates_CompletesTas
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -1435,7 +1437,7 @@ public async Task ExecuteAsync_Streaming_WhenContentlessUpdateChangesMessageId_F
];
A2AAgentHandler handler = CreateHandler(
CreateStreamingAgentMock(updates),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ runMode: AgentRunMode.ReturnTask);
// Act
var events = await CollectEventsAsync(handler, new RequestContext
@@ -1500,10 +1502,10 @@ public async Task ExecuteAsync_Streaming_WithMetadata_PassesOptionsWithAdditiona
}
///
- /// Verifies that streaming mode passes null options when metadata is null.
+ /// Verifies that streaming mode passes empty options when metadata is null.
///
[Fact]
- public async Task ExecuteAsync_Streaming_WithNullMetadata_PassesNullOptionsAsync()
+ public async Task ExecuteAsync_Streaming_WithNullMetadata_PassesEmptyOptionsAsync()
{
// Arrange
AgentRunOptions? capturedOptions = null;
@@ -1522,7 +1524,9 @@ public async Task ExecuteAsync_Streaming_WithNullMetadata_PassesNullOptionsAsync
// Assert
Assert.True(optionsCaptured);
- Assert.Null(capturedOptions);
+ Assert.NotNull(capturedOptions);
+ Assert.Null(capturedOptions.AllowBackgroundResponses);
+ Assert.Null(capturedOptions.AdditionalProperties);
}
///
@@ -2072,7 +2076,7 @@ public async Task Handler_WithNullSessionStore_SessionIsPersistedAcrossCallsAsyn
}
///
- /// Verifies that when the AllowBackgroundWhen delegate throws, the exception propagates
+ /// Verifies that when the ReturnTaskWhen delegate throws, the exception propagates
/// and the agent is not invoked.
///
[Fact]
@@ -2082,7 +2086,7 @@ public async Task ExecuteAsync_DynamicMode_WhenCallbackThrows_PropagatesExceptio
bool agentInvoked = false;
A2AAgentHandler handler = CreateHandler(
CreateAgentMock(_ => agentInvoked = true),
- runMode: AgentRunMode.AllowBackgroundWhen((_, _) =>
+ runMode: AgentRunMode.ReturnTaskWhen((_, _) =>
throw new InvalidOperationException("Callback failed")));
// Act & Assert
@@ -2096,7 +2100,7 @@ await Assert.ThrowsAsync(() =>
}
///
- /// Verifies that the CancellationToken is propagated to the AllowBackgroundWhen delegate.
+ /// Verifies that the CancellationToken is propagated to the ReturnTaskWhen delegate.
///
[Fact]
public async Task ExecuteAsync_DynamicMode_CancellationTokenIsPropagatedToCallbackAsync()
@@ -2106,7 +2110,7 @@ public async Task ExecuteAsync_DynamicMode_CancellationTokenIsPropagatedToCallba
using var cts = new CancellationTokenSource();
A2AAgentHandler handler = CreateHandler(
CreateAgentMock(_ => { }),
- runMode: AgentRunMode.AllowBackgroundWhen((_, ct) =>
+ runMode: AgentRunMode.ReturnTaskWhen((_, ct) =>
{
capturedToken = ct;
return ValueTask.FromResult(false);
@@ -2128,17 +2132,20 @@ await handler.ExecuteAsync(
}
///
- /// Verifies that the agent run mode is applied on the continuation/task-update path,
- /// not just the new message path.
+ /// Verifies that the ReturnTaskWhen delegate is not invoked when updating an existing task.
///
[Fact]
- public async Task ExecuteAsync_OnContinuation_RunModeIsAppliedAsync()
+ public async Task ExecuteAsync_OnContinuation_DoesNotInvokeDynamicModeCallbackAsync()
{
// Arrange
- AgentRunOptions? capturedOptions = null;
+ bool callbackInvoked = false;
A2AAgentHandler handler = CreateHandler(
- CreateAgentMock(options => capturedOptions = options),
- runMode: AgentRunMode.AllowBackgroundIfSupported);
+ CreateAgentMock(_ => { }),
+ runMode: AgentRunMode.ReturnTaskWhen((_, _) =>
+ {
+ callbackInvoked = true;
+ return ValueTask.FromResult(false);
+ }));
// Act
await InvokeExecuteAsync(handler, new RequestContext
@@ -2147,13 +2154,11 @@ public async Task ExecuteAsync_OnContinuation_RunModeIsAppliedAsync()
TaskId = "task-1",
ContextId = "ctx-1",
Message = new Message { MessageId = "empty", Role = Role.User, Parts = [] },
-
Task = new AgentTask { Id = "task-1", ContextId = "ctx-1", History = [new Message { Role = Role.User, Parts = [new Part { Text = "Hello" }] }] }
});
// Assert
- Assert.NotNull(capturedOptions);
- Assert.True(capturedOptions.AllowBackgroundResponses);
+ Assert.False(callbackInvoked);
}
///
@@ -2463,7 +2468,7 @@ private static A2AAgentHandler CreateHandler(
AgentRunMode? runMode = null,
AgentSessionStore? agentSessionStore = null)
{
- runMode ??= AgentRunMode.DisallowBackground;
+ runMode ??= AgentRunMode.ReturnMessage;
var hostAgent = new AIHostAgent(
innerAgent: agentMock.Object,
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AEndpointRouteBuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AEndpointRouteBuilderExtensionsTests.cs
index df4669a014a..9d61932417e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AEndpointRouteBuilderExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AEndpointRouteBuilderExtensionsTests.cs
@@ -248,7 +248,7 @@ public void AddA2AServer_WithCustomOptions_Succeeds()
IChatClient mockChatClient = new DummyChatClient();
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
- agentBuilder.AddA2AServer(options => options.AgentRunMode = AgentRunMode.AllowBackgroundIfSupported);
+ agentBuilder.AddA2AServer(options => options.AgentRunMode = AgentRunMode.ReturnTask);
builder.Services.AddLogging();
using WebApplication app = builder.Build();
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AServerServiceCollectionExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AServerServiceCollectionExtensionsTests.cs
index 40905bd6ecb..ce310dbec84 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AServerServiceCollectionExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AServerServiceCollectionExtensionsTests.cs
@@ -249,7 +249,7 @@ public async Task AddA2AServer_WithConfigureOptions_InvokesCallbackAsync()
services.AddA2AServer(AgentName, options =>
{
callbackInvoked = true;
- options.AgentRunMode = AgentRunMode.AllowBackgroundIfSupported;
+ options.AgentRunMode = AgentRunMode.ReturnTask;
});
// Assert - callback is invoked during resolution
@@ -510,7 +510,7 @@ public async Task AddA2AServer_WithBackgroundResponsesAndNonImmediateRequest_Ret
services.AddKeyedSingleton(AgentName, (_, _) => agentMock.Object);
services.AddA2AServer(
AgentName,
- options => options.AgentRunMode = AgentRunMode.AllowBackgroundIfSupported);
+ options => options.AgentRunMode = AgentRunMode.ReturnTask);
await using var provider = services.BuildServiceProvider();
var server = provider.GetRequiredKeyedService(AgentName);
SendMessageRequest request = CreateTestSendMessageRequest();
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/AgentRunModeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/AgentRunModeTests.cs
index cbe1254b81c..b9c5e5920ac 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/AgentRunModeTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/AgentRunModeTests.cs
@@ -12,25 +12,25 @@ namespace Microsoft.Agents.AI.Hosting.A2A.UnitTests;
public sealed class AgentRunModeTests
{
///
- /// Verifies that AllowBackgroundWhen throws ArgumentNullException for null delegate.
+ /// Verifies that ReturnTaskWhen throws ArgumentNullException for null delegate.
///
[Fact]
- public void AllowBackgroundWhen_NullDelegate_ThrowsArgumentNullException()
+ public void ReturnTaskWhen_NullDelegate_ThrowsArgumentNullException()
{
// Arrange & Act & Assert
Assert.Throws(() =>
- AgentRunMode.AllowBackgroundWhen(null!));
+ AgentRunMode.ReturnTaskWhen(null!));
}
///
- /// Verifies that DisallowBackground equals another DisallowBackground instance.
+ /// Verifies that ReturnMessage equals another ReturnMessage instance.
///
[Fact]
- public void Equals_DisallowBackground_AreEqual()
+ public void Equals_ReturnMessage_AreEqual()
{
// Arrange
- var mode1 = AgentRunMode.DisallowBackground;
- var mode2 = AgentRunMode.DisallowBackground;
+ var mode1 = AgentRunMode.ReturnMessage;
+ var mode2 = AgentRunMode.ReturnMessage;
// Act & Assert
Assert.True(mode1.Equals(mode2));
@@ -40,14 +40,14 @@ public void Equals_DisallowBackground_AreEqual()
}
///
- /// Verifies that AllowBackgroundIfSupported equals another AllowBackgroundIfSupported instance.
+ /// Verifies that ReturnTask equals another ReturnTask instance.
///
[Fact]
- public void Equals_AllowBackgroundIfSupported_AreEqual()
+ public void Equals_ReturnTask_AreEqual()
{
// Arrange
- var mode1 = AgentRunMode.AllowBackgroundIfSupported;
- var mode2 = AgentRunMode.AllowBackgroundIfSupported;
+ var mode1 = AgentRunMode.ReturnTask;
+ var mode2 = AgentRunMode.ReturnTask;
// Act & Assert
Assert.True(mode1.Equals(mode2));
@@ -55,19 +55,18 @@ public void Equals_AllowBackgroundIfSupported_AreEqual()
}
///
- /// Verifies that DisallowBackground and AllowBackgroundIfSupported are not equal.
+ /// Verifies that ReturnMessage and ReturnTask are not equal.
///
[Fact]
public void Equals_DifferentModes_AreNotEqual()
{
// Arrange
- var disallow = AgentRunMode.DisallowBackground;
- var allow = AgentRunMode.AllowBackgroundIfSupported;
+ var message = AgentRunMode.ReturnMessage;
+ var task = AgentRunMode.ReturnTask;
// Act & Assert
- Assert.False(disallow.Equals(allow));
- Assert.False(disallow == allow);
- Assert.True(disallow != allow);
+ Assert.False(message.Equals(task));
+ Assert.False(message == task);
}
///
@@ -77,7 +76,7 @@ public void Equals_DifferentModes_AreNotEqual()
public void Equals_Null_ReturnsFalse()
{
// Arrange
- var mode = AgentRunMode.DisallowBackground;
+ var mode = AgentRunMode.ReturnMessage;
// Act & Assert
Assert.False(mode.Equals(null));
@@ -108,9 +107,9 @@ public void Equals_BothNull_AreEqual()
public void ToString_ReturnsExpectedValues()
{
// Act & Assert
- Assert.Equal("message", AgentRunMode.DisallowBackground.ToString());
- Assert.Equal("task", AgentRunMode.AllowBackgroundIfSupported.ToString());
- Assert.Equal("dynamic", AgentRunMode.AllowBackgroundWhen((_, _) => ValueTask.FromResult(true)).ToString());
+ Assert.Equal("message", AgentRunMode.ReturnMessage.ToString());
+ Assert.Equal("task", AgentRunMode.ReturnTask.ToString());
+ Assert.Equal("dynamic", AgentRunMode.ReturnTaskWhen((_, _) => ValueTask.FromResult(true)).ToString());
}
///
@@ -120,24 +119,24 @@ public void ToString_ReturnsExpectedValues()
public void Equals_WithObjectParameter_WorksCorrectly()
{
// Arrange
- var mode = AgentRunMode.DisallowBackground;
+ var mode = AgentRunMode.ReturnMessage;
// Act & Assert
- Assert.True(mode.Equals((object)AgentRunMode.DisallowBackground));
- Assert.False(mode.Equals((object)AgentRunMode.AllowBackgroundIfSupported));
+ Assert.True(mode.Equals((object)AgentRunMode.ReturnMessage));
+ Assert.False(mode.Equals((object)AgentRunMode.ReturnTask));
Assert.False(mode.Equals("not a run mode"));
}
///
- /// Verifies that two AllowBackgroundWhen instances with different delegates are not considered equal,
+ /// Verifies that two ReturnTaskWhen instances with different delegates are not considered equal,
/// because equality includes delegate identity for dynamic modes.
///
[Fact]
- public void Equals_AllowBackgroundWhen_DifferentDelegates_AreNotEqual()
+ public void Equals_ReturnTaskWhen_DifferentDelegates_AreNotEqual()
{
// Arrange
- var mode1 = AgentRunMode.AllowBackgroundWhen((_, _) => ValueTask.FromResult(true));
- var mode2 = AgentRunMode.AllowBackgroundWhen((_, _) => ValueTask.FromResult(false));
+ var mode1 = AgentRunMode.ReturnTaskWhen((_, _) => ValueTask.FromResult(true));
+ var mode2 = AgentRunMode.ReturnTaskWhen((_, _) => ValueTask.FromResult(false));
// Act & Assert
Assert.False(mode1.Equals(mode2));
@@ -145,15 +144,15 @@ public void Equals_AllowBackgroundWhen_DifferentDelegates_AreNotEqual()
}
///
- /// Verifies that two AllowBackgroundWhen instances with the same delegate are considered equal.
+ /// Verifies that two ReturnTaskWhen instances with the same delegate are considered equal.
///
[Fact]
- public void Equals_AllowBackgroundWhen_SameDelegate_AreEqual()
+ public void Equals_ReturnTaskWhen_SameDelegate_AreEqual()
{
// Arrange
static ValueTask CallbackAsync(A2ARunDecisionContext _, CancellationToken __) => ValueTask.FromResult(true);
- var mode1 = AgentRunMode.AllowBackgroundWhen(CallbackAsync);
- var mode2 = AgentRunMode.AllowBackgroundWhen(CallbackAsync);
+ var mode1 = AgentRunMode.ReturnTaskWhen(CallbackAsync);
+ var mode2 = AgentRunMode.ReturnTaskWhen(CallbackAsync);
// Act & Assert
Assert.True(mode1.Equals(mode2));