Repository navigation
.NET: [BREAKING] Support scoped and transient agent lifetimes in Foundry Hosting - #10238
Roger Barreto (rogerbarreto) wants to merge 4 commits into
Conversation
Resolve agents and session stores from a DI scope owned by each response run, so scoped and transient registrations are created per run and disposed when the event stream ends, including background and recovered runs. Health checks resolve agents from a short lived scope. Add AddFoundryResponses overloads that take an agent factory, a ServiceLifetime, and an optional session store factory sharing that lifetime. Factory registrations keep the name based session storage identity for every lifetime.
There was a problem hiding this comment.
🟡 Changes recommended
Default aliases can resolve scoped agents from the root provider and dispose instances twice, while factory migration changes non-default storage identities, making existing saved sessions inaccessible.
3 open issues
What changed in this PR
Adds configurable agent lifetimes to .NET Foundry Hosting, extending existing hosting support so scoped and transient services can remain alive for an entire response run, including background execution and recovery.
Changes:
- Resolve agents and session stores within response-owned scopes, and use short-lived scopes for readiness checks.
- Add factory-based registration overloads with configurable lifetimes and storage-identity tracking.
- Document lifetime behavior and add tests for disposal, background execution, and recovery.
| File | Description |
|---|---|
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ResilientTwoLifetimeIntegrationTests.cs |
Extends recovery tests to scoped and transient factories. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedWorkflowCheckpointingHealthCheckTests.cs |
Tests scoped workflow resolution during readiness checks. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedStoredOutputHealthCheckTests.cs |
Tests scoped agent resolution during stored-output checks. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAgentLifetimeTests.cs |
Adds registration, disposal, identity, and background-lifetime tests. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs |
Adds factory registration with configurable service lifetimes. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedWorkflowCheckpointingHealthCheck.cs |
Resolves workflow agents within an asynchronous scope. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedStoredOutputHealthCheck.cs |
Scopes agent resolution and stored-output probing. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostApplicationBuilderExtensions.cs |
Exposes factory registration through the host builder. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryAgentFactoryRegistration.cs |
Tracks factory registrations and session-storage identities. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AgentFrameworkResponseHandler.cs |
Owns response-run scopes and applies factory storage identities. |
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/README.md |
Documents agent lifetimes and factory-based registration. |
Review effort: Balanced
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (1 commit(s)): 3f6d66df7af0
Model: gpt-5.6-sol
Overview
The PR correctly moves response and readiness resolution into async scopes, and its tests establish scope survival through background execution, cancellation, recovery, and normal disposal. The keyed/default forwarding design also preserves one scoped instance and stable session identity when both registrations are created together. Residual risk remains when the overload composes with pre-existing keyed or default registrations, and the forwarding descriptor gives DI two disposal owners for one instance.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
4 verified findings remained after source verification (4 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs
Register factory agents and stores only under their name so DI disposes each object once and no forwarding registration can mismatch lifetimes. Route requests without an agent name to the factory default in the response handler. Use the instance overload storage identities: name based for the effective default, key based otherwise. Decide the effective default from the built container so a non keyed agent registered later never shares sessions with a factory agent. Validate the hosted user identity before creating any agent or store, and document that the response scope is not the HTTP request scope.
Add AddFoundryAgentSessionStore on IServiceCollection and IHostApplicationBuilder. It registers a session store object, or a factory with its own DI lifetime (Singleton by default), for one agent name or for every agent without a store of its own. A later call for the same target replaces the earlier one, in any order relative to AddFoundryResponses. The agent factory overload no longer takes a store factory, so a scoped agent no longer forces its store to be scoped too. A store passed to AddFoundryResponses(agent, store) is now used only by that agent, and agents without a store fall back to the store registered for all agents. The default agent looks up the store registered under its own name. Replace the factory registration markers with a single FoundryDefaultAgentName. The handler now checks at run time whether a non-keyed agent took the default role, so the IServiceProviderIsService lookup is no longer needed.
Explain in the XML docs and the sample README that a store passed to AddFoundryResponses(agent, store) serves only that agent, that agents without a store use the store registered for all agents, and that moving an agent to a different store leaves its earlier sessions in the old store.
|
/review |
There was a problem hiding this comment.
MAF Automated Review — Iteration 2
Result: Findings reported
Scope: 3 net-new commit(s): 2587519e3f2c, 94f66ee5e13a, 8e316de27039
Model: gpt-5.6-sol
Overview
The change gives each response run an asynchronously disposed DI scope and avoids duplicate ownership of factory-created agents, with focused tests covering scope lifetime, exact-once disposal, session identity, and health-check resolution. Hosted identity validation also occurs before caller factories run. One routing-order defect remains: a request that explicitly selects a keyed agent can still be blocked by construction of an unrelated non-keyed default.
Reviewed the supplied incremental change set across correctness, security/reliability, architecture, and failure behavior.
1 verified finding remained after source verification (1 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AgentFrameworkResponseHandler.cs
| private (AIAgent Agent, string? SessionStoreKey) ResolveAgent(CreateResponse request, IServiceProvider services) | ||
| { | ||
| var agentName = GetAgentName(request); | ||
| var defaultAgent = services.GetService<AIAgent>(); |
There was a problem hiding this comment.
Resolving the non-keyed default before checking the requested key means every named request constructs that unrelated agent too. If its transient/scoped factory throws or performs expensive side effects, a healthy keyed agent can no longer serve its own requests. Resolve the keyed target first and avoid instantiating the non-keyed default unless it is actually selected, while preserving storage identity through registration metadata.


Motivation & Context
During review of #10132, westey (@westey-m) noted that Foundry hosting needs more options for configuring the agent DI scope (comment). This PR follows up on that.
Microsoft.Agents.AI.Hosting already lets callers choose a
ServiceLifetimefor agents and for session stores (#4476, issue #4457), and the generic OpenAI Responses host resolves agents from a scope that stays open for the whole streamed response (#8423). Foundry hosting had neither:AgentFrameworkResponseHandleris a singleton and resolved every agent and session store from the rootIServiceProvider. It never created a scope.IDisposablewas kept alive by the root container until shutdown.The HTTP request scope cannot be reused for this. Background responses keep running after the HTTP response is sent, and a resilient recovery calls the handler again in a new process with no HTTP request at all.
Description & Review Guide
AgentFrameworkResponseHandler.CreateAsynccreates oneAsyncServiceScopeper call and resolves the agent and the session store from it.await usingdisposes the scope when the event stream ends, whether the run completes, fails, is cancelled, the client disconnects, or the run is handed off for recovery. This is the same pattern asHostedAgentResponseExecutorin the OpenAI host.HostedStoredOutputHealthCheckandHostedWorkflowCheckpointingHealthCheckresolve agents inside a short-lived scope.AddFoundryResponsesoverloads on bothIHostApplicationBuilderandIServiceCollectiontake an agent factory and aServiceLifetime(Singleton by default, likeAddAIAgentin Microsoft.Agents.AI.Hosting):FoundryDefaultAgentNamerecords this agent's name, and the response handler routes requests without a matching agent name to it. The instance overload no longer takes the default slot when a factory agent already holds it.TryAddbehavior. The factory call then adds nothing for the agent.AddFoundryAgentSessionStoreoverloads on both builders register a session store separately from agents, following the precedent ofWithSessionStorein Microsoft.Agents.AI.Hosting. The store's lifetime is independent of the agent's, which is safe because the handler uses the store directly and passes the agent on every call:AddFoundryResponses(agent, store)or directly in DI, in whatever order the methods are called.AddFoundryResponses(agent, store)now registersstoreonly under the agent's name. Onmainit also became the store for all agents. Without a store, it no longer registers a separate default store under the agent's name, so the agent falls back to the store registered for all agents.name:{agentName}for the agent that receives requests without a matching name, andkey:{agentName}for every other agent. That holds for every lifetime. The handler checks at run time whether a non-keyedAIAgenttook the default role, so such an agent never shares sessions with a factory agent of the same name.Singleton registrations behave exactly as before, because a scope returns the same root instance.
Scoped and Transient registrations now work, including keyed registrations made by hand and
AddAIAgent(..., lifetime)from Microsoft.Agents.AI.Hosting.Lifetime behavior, for agents and for stores registered with a factory:
Conversation state still comes from the saved session, not from the agent object. A new agent per turn therefore loses nothing.
Breaking behavior, only for apps that mix registration styles:
AddFoundryResponses(agentA, storeA)plus another agent registered by hand: the other agent moves fromstoreAto the store registered for all agents, or the default store.AgentSessionStoreregistered by hand plusAddFoundryResponses(agentA)without a store: agent A moves from its own default store to the store registered by hand.In both cases the affected agent's earlier sessions stay in the old store and are no longer found. No error is raised; those conversations start without their history. Apps with a single agent, apps that add several agents with
AddFoundryResponses(agent, ...), and apps that only useAddFoundryResponses()with agents registered by hand are not affected. No public API frommainis removed or changed.AddFoundryAgentSessionStorewith its own lifetime, replace semantics where the last call wins, and a store factory that does not receive the agent name.FoundryDefaultAgentNameinstead of a forwarding DI registration, and the storage identity rule inResolveAgent.CreateAsyncenumeration) is the right owner for steering and recovery.Related Issue
Follow-up to deferred review feedback on #10132: #10132 (comment). Related to #4457 and #4476. No open PR covers this.
AI Assistance
AI assistance details: investigation of the existing DI flow, implementation, tests, documentation, a security review of the new registration and resolution paths, and this PR description.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.