.NET: Persist hosted agent state in Foundry - #7649
.NET: Persist hosted agent state in Foundry#7649Roger Barreto (rogerbarreto) wants to merge 6 commits into
Conversation
The durable state-store API this branch is built on ships in Core beta.28, which is not on nuget.org yet. The local feed is a stopgap for developing against it and must be removed before this branch ships.
A hosted agent kept its sessions, and a hosted workflow its checkpoints, in files under the container's own directory. That state is lost when the container is replaced and cannot be read by another instance of the same agent, so a conversation could not survive a restart or be served by more than one instance. Both now go to the Foundry durable state store when the process runs in a Foundry container, and stay on disk everywhere else: - FoundryAgentSessionStore holds the agent sessions, partitioned by agent, conversation and end user. - FoundryJsonCheckpointStore holds the workflow checkpoints, one item per checkpoint plus a per-session index that keeps them in commit order. Retrieving a checkpoint deletes the rest of that session's checkpoints, which is the only point at which nothing can still reach them, and is what stops the index growing past the size the platform accepts for one item. A workflow agent is redirected to that checkpoint store when it is resolved for a request, so nothing changes in how a container registers one. An agent built with a checkpoint manager of its own is left alone and reported by the new foundry-workflow-checkpointing readiness check, because its state would go somewhere hosting does not manage. Workflow agents are recognised through a new WorkflowAgentMetadata returned by GetService, which still finds them behind middleware.
The stored-output probe ran the registered agent with its chat client replaced, which still set the agent's chat history provider and context providers running. Those are the parts most likely to reach outside the container and to write state, so every readiness probe could make external calls and add its own empty turn to real conversations. The probe now runs a stand-in built from the agent's own options with both kinds of provider dropped. It keeps what decides the setting, the chat options and the raw request factory, and cannot see a decorator wrapped around the agent, which is accepted for a readiness check.
Core beta.29 adds the shared local state-store fallback used by hosted sessions and workflow checkpoints. Align its Azure Core and System package dependencies to avoid assembly and downgrade conflicts.
Use FoundryStateStore for sessions and workflow checkpoints in every environment. Core beta.29 selects Foundry Storage when hosted and a file-backed local store otherwise, so local runs exercise the production storage shape without requiring Azure credentials. Give the hosted workflow sample stable inner-agent identities so its checkpoints remain compatible after container replacement.
There was a problem hiding this comment.
Pull request overview
Adds durable Foundry-backed persistence for hosted .NET agent sessions and workflow checkpoints.
Changes:
- Adds Foundry session and checkpoint stores with local fallback.
- Redirects hosted workflows to durable checkpointing and adds readiness validation.
- Updates tests, packages, and the hosted workflow sample.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
dotnet/Directory.Packages.props |
Updates AgentServer dependencies. |
dotnet/nuget.config |
Adds preview package source. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AgentFrameworkResponseHandler.cs |
Applies workflow checkpointing during resolution. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryAgentSessionStore.cs |
Implements durable session persistence. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryJsonCheckpointStore.cs |
Implements durable workflow checkpoints. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryStateStoreBinding.cs |
Caches state-store binding. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedStoredOutputHealthCheck.cs |
Avoids invoking configured providers. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedWorkflowCheckpointingHealthCheck.cs |
Detects unsupported checkpoint configuration. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj |
Adds storage dependencies. |
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs |
Registers stores, checks, and workflow decoration. |
dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowAgentMetadata.cs |
Exposes workflow metadata. |
dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs |
Supports checkpoint-manager substitution. |
dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostingExtensions.cs |
Adds checkpointing extension. |
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/Program.cs |
Stabilizes workflow executor identities. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAgentSessionStoreTests.cs |
Tests session persistence and partitioning. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryJsonCheckpointStoreTests.cs |
Tests checkpoint behavior and concurrency. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryStateStoreLocalFallbackTests.cs |
Tests local fallback storage. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs |
Retains in-memory test isolation. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedStoredOutputHealthCheckTests.cs |
Tests provider-free readiness probes. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedWorkflowCheckpointingHealthCheckTests.cs |
Tests workflow readiness validation. |
dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs |
Tests default store registration. |
dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj |
Adds async-interface dependency. |
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostingExtensionsTests.cs |
Tests workflow metadata and redirection. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| return agent is WorkflowHostAgent workflowAgent | ||
| ? workflowAgent.WithCheckpointing(checkpointManager) | ||
| : agent; |
| List<IndexEntry> obsolete = []; | ||
| IndexEntry? resumed = null; | ||
| foreach (IndexEntry entry in ReadEntries(indexItem)) | ||
| { | ||
| if (entry.CheckpointId == resumedCheckpointId) | ||
| { | ||
| resumed = entry; | ||
| } | ||
| else | ||
| { | ||
| obsolete.Add(entry); | ||
| } | ||
| } | ||
|
|
||
| if (resumed is null || obsolete.Count == 0) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // The index is shortened first. A checkpoint item that is still listed but already gone | ||
| // would be read as a missing checkpoint, whereas one that is listed nowhere is simply | ||
| // never asked for. | ||
| await WriteEntriesAsync(store, sessionIndexKey, sessionId, [resumed], indexItem?.Etag).ConfigureAwait(false); |
| catch (Exception ex) when (ex is not OperationCanceledException) | ||
| { | ||
| lock (this._gate) | ||
| { | ||
| if (ReferenceEquals(this._pending, binding)) | ||
| { | ||
| this._pending = null; | ||
| } | ||
| } | ||
|
|
||
| throw; | ||
| } |
| <packageSources> | ||
| <clear /> | ||
| <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> | ||
| <add key="agentserver-preview-local" value="C:\local_packages" /> |
|
|
||
| /// <summary> | ||
| /// Covers <see cref="WorkflowHostingExtensions.WithCheckpointing"/>, which lets a host redirect | ||
| /// where a already-built workflow agent writes its checkpoints. |
| builder.Append("u-").Append(userId).Append(':'); | ||
| } | ||
|
|
||
| return builder.Append("c-").Append(conversationId).ToString(); |
| if (!string.IsNullOrEmpty(agent.Name)) | ||
| { | ||
| builder.Append("a-").Append(agent.Name).Append(':'); | ||
| } |
| var probeAgent = new ChatClientAgent(probe, probeOptions); | ||
| await probeAgent.RunAsync([], cancellationToken: cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (5 commit(s)): 13d59beaf64d, f5f4eba3cbaf, f73cc27d5ace, 0f0fa8f1143c, 9da7d9d1e7ee
Model: gpt-5.6-sol
Overview
The review found 2 verified inline finding(s).
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (1 high, 1 medium) across 2 files. Details are attached to the affected lines below.
Affected areas: dotnet/nuget.config, dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs
| { | ||
| return s_workflowCheckpointingAgents.GetValue( | ||
| agent, | ||
| source => source.WithCheckpointing(GetFoundryWorkflowCheckpointManager(loggerFactory))); |
There was a problem hiding this comment.
For a workflow behind DelegatingAIAgent/middleware, WithCheckpointing returns the wrapper unchanged, so its checkpoints remain inside the serialized session and are lost or hit the item-size limit after container replacement. GetService<WorkflowAgentMetadata>() still sees through that same wrapper, but readiness reports it healthy because UsesOwnCheckpointStorage is false. If the wrapper cannot be safely rebuilt around the redirected inner agent, please make readiness reject this configuration instead of serving it without durable checkpointing.
Use published AgentServer packages so CI no longer depends on a local package source.
There was a problem hiding this comment.
MAF Automated Review — Iteration 2
Result: Findings reported
Scope: 1 net-new commit(s): 08b57eb9a00e
Model: gpt-5.6-sol
Overview
The review found 3 verified inline finding(s).
Reviewed the supplied incremental change set across correctness, security/reliability, architecture, and failure behavior.
3 verified findings remained after source verification (3 medium) across 3 files. Details are attached to the affected lines below.
Affected areas: dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AgentFrameworkResponseHandler.cs, dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryAgentSessionStore.cs, dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryJsonCheckpointStore.cs
| return; | ||
| } | ||
|
|
||
| List<IndexEntry> obsolete = entries.GetRange(0, resumedIndex); |
There was a problem hiding this comment.
At FoundryJsonCheckpointStore.cs:304, resuming a checkpoint deletes every earlier index entry regardless of recorded parent, so an earlier sibling checkpoint can be removed while another persisted session still references it, causing that session’s resume to fail; a safe fix must preserve ordered pruning for legacy or parentless checkpoint entries.
| { | ||
| FoundryHostingExtensions.TryApplyUserAgent(agent); | ||
| return FoundryHostingExtensions.ApplyOpenTelemetry(agent); | ||
| storageIdentity = $"key:{agentName}"; |
There was a problem hiding this comment.
AddFoundryResponses(agent) registers the same named instance as both keyed and default, but this path stores it as key:<name> while an unnamed request stores it as name:<name>. Alternating between these supported request forms therefore loads a fresh session for the same agent, user, and conversation. Please assign one canonical storage identity to both aliases of the same registration.
| return builder.ToString(); | ||
| } | ||
|
|
||
| private static string ResolveAgentIdentity(AIAgent agent) => |
There was a problem hiding this comment.
For direct calls through the public AgentSessionStore overrides, an unnamed agent is partitioned by its generated instance ID. After a container replacement, an equivalent unnamed agent receives a new ID and cannot retrieve the durable session saved by the prior instance. Please require a stable explicit identity for unnamed agents, or reject this case instead of persisting state under an ephemeral key.
Motivation & Context
Hosted agent sessions were stored on the container filesystem. That state could be lost when a container was replaced and could not be read by another instance.
Workflow checkpoints were serialized inside the agent session. Long-running workflows could therefore grow the session until it exceeded the platform item-size limit.
This change stores sessions and workflow checkpoints through the AgentServer
FoundryStateStore, allowing conversations and workflows to resume across container replacements.Description & Review Guide
What are the major changes?
FoundryAgentSessionStorefor serialized agent sessions.FoundryJsonCheckpointStore, with one item per checkpoint and an ordered per-session index.WorkflowAgentMetadatathroughAIAgent.GetServicefor workflow detection through wrappers.Hosted-Workflow-Simplestable inner-agent identities so its checkpoints remain compatible after container replacement.What is the impact of these changes?
AgentSessionStoreremain unchanged.What do you want reviewers to focus on?
Related Issue
None. This draft tracks private-preview integration before the StateStore packages are published.
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.