Repository navigation
.NET: Honor hosted agent lifetimes and add request-time agent selection for AG-UI hosting - #9382
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Detached OpenAI and A2A execution can outlive request-scoped dependencies, and Responses retrieval bypasses agent selection.
3 open findings
What changed in this PR
Updates .NET protocol hosting to honor agent DI lifetimes and support request-time agent selection.
Changes:
- Resolves scoped/transient agents and session services per request or operation.
- Adds selector overloads for AG-UI, OpenAI, and A2A endpoints.
- Adds stable session identities and extensive lifetime/selection tests.
| File | Description |
|---|---|
dotnet/src/Shared/HostedAgents/HostedAgentResolution.cs |
Adds shared registration and identity validation. |
dotnet/eng/MSBuild/Shared.props |
Enables shared hosted-agent source injection. |
dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/ResponsesHttpHandler.cs |
Supports request-selected executors. |
dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/IResponsesService.cs |
Adds executor-specific operations. |
dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs |
Stores and executes selected executors. |
dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AIAgentResponseExecutor.cs |
Adds stable session identities. |
dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.Responses.cs |
Adds Responses selector mapping. |
dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.ChatCompletions.cs |
Adds per-request Chat Completions selection. |
dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj |
Injects shared resolution code. |
dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs |
Resolves AG-UI agents and stores per request. |
dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj |
Injects shared resolution code. |
dotnet/src/Microsoft.Agents.AI.Hosting.A2A/ScopedA2AAgentHandler.cs |
Adds operation-owned A2A scopes. |
dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AServerServiceCollectionExtensions.cs |
Defers A2A service resolution. |
dotnet/src/Microsoft.Agents.AI.Hosting.A2A/A2AAgentHandler.cs |
Extracts task cancellation logic. |
dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj |
Adds shared code and internals visibility. |
dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/SelectedAgentA2ARequestHandler.cs |
Dispatches A2A requests by selected agent. |
dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/A2AEndpointRouteBuilderExtensions.cs |
Adds A2A selector overloads. |
dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj |
Avoids duplicate shared types. |
dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/ResponseSessionRegressionTests.cs |
Tests selector persistence and scoped resolution. |
dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIChatCompletionsAgentResolutionTests.cs |
Tests Chat Completions lifetimes and selection. |
dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj |
Adds TestHost dependency. |
dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIRequestScopedAgentTests.cs |
Tests AG-UI scoped behavior. |
dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs |
Updates AG-UI mapping tests. |
dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AServerServiceCollectionExtensionsTests.cs |
Updates deferred store-resolution expectations. |
dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2ARequestScopedAgentTests.cs |
Tests A2A lifetimes, selection, and persistence. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
The selected agent and its session store are bound to HttpContext.RequestServices, so a run returned immediately as a task would outlive the request scope. Reject non-streaming requests with ReturnImmediately when AgentRunMode returns a task.
The agent selector now runs for retrieval, cancellation, deletion, and input item listing as well as creation, returning 404 before any state is loaded when no agent matches. Stored responses are bound to the selected agent name, so a response is only visible through requests that select the agent that created it.
Stored response states outlive execution under a sliding expiration, so clear the executor, and the selected agent graph it references, once execution ends, including on failure or cancellation.
|
Marco Minerva (@marcominerva), thanks very much for the contribution and for working on this! We’re receiving a large number of contributions at the moment and are finding it challenging to keep up with all the reviews. Since this PR covers several hosting scenarios and changes a significant amount of code, would you mind splitting it into smaller, focused PRs - a separate PR for each concrete scenario you currently need to support or that is blocking your work? Keeping PRs smaller and focused helps us review and merge contributions much faster. For future PRs, please also take a look at our updated contribution model. Thanks again for your work and understanding! |
|
Thank you SergeyMenshykh for your reply. Some changes are shared across the different scenarios, but over the next few days I'll try to figure out how to split this PR. I'll keep you informed about the progress. |
…t-scoped-agents Adapt SelectedAgentA2ARequestHandler and the agent-selector MapA2AHttpJson overload to the A2A 1.0.0-preview3 API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the A2A, OpenAI Chat Completions and OpenAI Responses changes to separate branches so this PR only covers MapAGUIServer and the shared hosted agent resolution helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Hi SergeyMenshykh, I've split this PR as suggested. Now there is one PR per hosting scenario:
|
The explicit-instance MapAGUIServer overload partitioned persisted sessions by the agent instance, while the named, hosted, and selector mappings partition them by the agent name, so the same agent exposed through a named and an explicit-instance mapping no longer shared conversations. Named agents mapped by instance are now identified by their name as well; unnamed agents keep their instance identity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Marco Minerva (@marcominerva), thanks for splitting this up! This PR LGTM. Of the remaining PRs, which one covers the scenario you need most or that is currently blocking you? |
|
Great news, SergeyMenshykh! Regarding the other PRs, I have several customers who are currently blocked on the A2A scenario, so getting this support would definitely be very helpful. Also, while I have your attention, I'd like to ask if it would be possible to prioritize #8801 as well. It has already been approved, and getting it merged too would be another great help to several of my customers. Some of them have previously implemented custom stores based on the expectation that Thanks again for your help and for considering these requests! |


Motivation & Context
MapAGUIServerresolves the hosted agent and its session store from the root service provider when the endpoint is mapped, so Scoped and TransientAddAIAgentregistrations are captured as singletons. There is also no way to choose the agent at request time (for example from a route value, header or claim) without mapping one endpoint per agent.This PR is the AG-UI part of the original #9382, which was split per hosting protocol to make review easier. The A2A, OpenAI Chat Completions and OpenAI Responses parts are in separate PRs and can be merged independently.
Description & Review Guide
MapAGUIServer(agentName)/MapAGUIServer(IHostedAgentBuilder)now resolve the keyedAIAgent, its keyedAgentSessionStoreandAgentIsolationKeyProviderfromHttpContext.RequestServicesfor each request, so the configured DI lifetime is honored. Singleton registrations mapped throughIHostedAgentBuilderare still resolved once at mapping time. Missing registrations are still reported at mapping time, usingIServiceProviderIsKeyedServiceso that no scoped/transient instance is resolved from the root provider.MapAGUIServer(string pattern, Func<HttpContext, AIAgent?> agentSelector)overload. Anullselection returns404before any session state is loaded; the selected agent'sNameis used as the stable session storage identity, so sessions persist across new agent instances.MapAGUIServer(pattern, AIAgent)overload keeps its agent instance but now resolves the session store per request.src/Shared/HostedAgents/HostedAgentResolution.cs(opt-in viaInjectSharedHostedAgents), reused by the other hosting PRs of this split.Related Issue
Part of #7733 (split from #9382).
Contribution Checklist