diff --git a/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/Program.cs b/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/Program.cs index 26e2460dd13..cb8c57f377f 100644 --- a/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/Program.cs +++ b/dotnet/samples/05-end-to-end/A2AClientServer/A2AServer/Program.cs @@ -101,10 +101,15 @@ You specialize in handling queries related to logistics. throw new ArgumentException("Either A2AServer:ApiKey or A2AServer:ConnectionString & agentName must be provided"); } -// When running in production, make sure to use an SessionIsolationKeyProvider, e.g. ClaimsIdentity-based -// if using Claims-based Identity for Authentication/Authorization +// IMPORTANT: In production, register a SessionIsolationKeyProvider to isolate sessions by authenticated caller. +// Without this, contextId alone is the session key — any caller who knows a contextId can access that session. +// Example using claims-based identity: // builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier }); +// By default, NoopAgentSessionStore is used — sessions are not persisted across requests. +// To enable multi-turn conversations, register a session store explicitly, e.g.: +// builder.Services.AddKeyedSingleton(hostA2AAgent.Name, new InMemoryAgentSessionStore()); + builder.AddA2AServer(hostA2AAgent); var app = builder.Build(); diff --git a/dotnet/samples/05-end-to-end/AgentWebChat/AgentWebChat.AgentHost/Program.cs b/dotnet/samples/05-end-to-end/AgentWebChat/AgentWebChat.AgentHost/Program.cs index ca399272b47..c54efda7a5e 100644 --- a/dotnet/samples/05-end-to-end/AgentWebChat/AgentWebChat.AgentHost/Program.cs +++ b/dotnet/samples/05-end-to-end/AgentWebChat/AgentWebChat.AgentHost/Program.cs @@ -28,10 +28,15 @@ builder.AddOpenAIChatCompletions(); builder.AddOpenAIResponses(); -// When running in production, make sure to use an SessionIsolationKeyProvider, e.g. ClaimsIdentity-based -// if using Claims-based Identity for Authentication/Authorization +// IMPORTANT: In production, register a SessionIsolationKeyProvider to isolate sessions by authenticated caller. +// Without this, contextId alone is the session key — any caller who knows a contextId can access that session. +// Example using claims-based identity: // builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier }); +// By default, NoopAgentSessionStore is used — sessions are not persisted across requests. +// To enable multi-turn conversations, register a session store explicitly, e.g.: +// agentBuilder.WithInMemorySessionStore(); + var pirateAgentBuilder = builder.AddAIAgent( "pirate", instructions: "You are a pirate. Speak like a pirate", @@ -152,8 +157,9 @@ Once the user has deduced what type (knight or knave) both Alice and Bob are, te pirateAgentBuilder.AddA2AServer(); knightsKnavesAgentBuilder.AddA2AServer(); -// When running in production, make sure to use an SessionIsolationKeyProvider, e.g. ClaimsIdentity-based -// if using Claims-based Identity for Authentication/Authorization +// IMPORTANT: In production, register a SessionIsolationKeyProvider to isolate sessions by authenticated caller. +// Without this, contextId alone is the session key — any caller who knows a contextId can access that session. +// Example using claims-based identity: // builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier }); var app = builder.Build(); diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs index cb0e15dac81..ad07155387e 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs @@ -189,7 +189,7 @@ private static A2AServer CreateA2AServer(IServiceProvider serviceProvider, AIAge var isolationKeyProvider = serviceProvider.GetService(); if (agentSessionStore?.GetService() is null) { - agentSessionStore ??= new InMemoryAgentSessionStore(); + agentSessionStore ??= new NoopAgentSessionStore(); agentSessionStore = new IsolationKeyScopedAgentSessionStore(agentSessionStore, isolationKeyProvider, new() { Strict = isolationKeyProvider != null }); } 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 aae07e8e6f8..11ad8574d32 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AServerServiceCollectionExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AServerServiceCollectionExtensionsTests.cs @@ -62,10 +62,10 @@ public async Task AddA2AServer_WithAgentInstance_ResolvesKeyedA2AServerAsync() /// /// Verifies that when no ITaskStore or AgentSessionStore are registered, - /// AddA2AServer falls back to in-memory defaults and resolves successfully. + /// AddA2AServer falls back to noop session store default and resolves successfully. /// [Fact] - public async Task AddA2AServer_WithNoCustomStores_FallsBackToInMemoryDefaultsAsync() + public async Task AddA2AServer_WithNoCustomStores_FallsBackToNoopSessionStoreDefaultAsync() { // Arrange const string AgentName = "default-stores-agent"; @@ -382,7 +382,7 @@ public async Task AddA2AServer_WithCustomSessionStore_NoHandler_SessionStoreIsUs /// /// Verifies that when no custom stores or handlers are registered, the server uses - /// the default in-memory stores and processes requests successfully end-to-end. + /// the default noop session store and processes requests successfully end-to-end. /// [Fact] public async Task AddA2AServer_WithNoCustomStores_DefaultStoresProcessRequestSuccessfullyAsync() @@ -400,7 +400,7 @@ public async Task AddA2AServer_WithNoCustomStores_DefaultStoresProcessRequestSuc using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var response = await server.SendMessageAsync(CreateTestSendMessageRequest(), cts.Token); - // Assert - request was processed successfully with default in-memory stores + // Assert - request was processed successfully with default noop session store Assert.NotNull(response); Assert.Equal(SendMessageResponseCase.Message, response.PayloadCase); Assert.NotNull(response.Message);