* .NET: Bump Azure.AI.Projects to 2.1.0-beta.2 and add agent-endpoint AsAIAgent path
Bumps Azure.AI.Projects to 2.1.0-beta.2 with the matching transitive pins (Azure.Core 1.55.0, System.ClientModel 1.11.0).
Foundry agent endpoint plumbing:
* FoundryAgent now routes the agent-endpoint constructor through the new GetProjectResponsesClientForAgentEndpoint helper.
* Adds an internal FoundryAgent ctor that takes an existing AIProjectClient plus a parsed agent endpoint so the public extension does not need to construct a second project client.
* Adds public AIProjectClient.AsAIAgent(Uri agentEndpoint, ...) extension. This is the path consumer samples are expected to use for hosted agents because version selection happens server-side.
* Trims the dangling "If you want to construct a FoundryAgent against a project endpoint..." sentence from ParseAgentEndpoint.
Unit tests:
* Four new tests in AzureAIProjectChatClientExtensionsTests cover the AIProjectClient.AsAIAgent(Uri agentEndpoint, ...) overload. 263/263 pass.
Consumer samples (Using-Samples):
* SimpleAgent and SessionFilesClient now read AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_AGENT_NAME (both required, throw on missing), derive the agent endpoint with new Uri($"{projectEndpoint}/agents/{agentName}/endpoint/protocols/openai"), then call aiProjectClient.AsAIAgent(agentEndpoint, ...).
* SessionFilesClient README updated.
Contributor samples (responses/*):
* New HostedContributorRouteExtensions.MapDevTemporaryLocalAgentEndpoint() wildcard route extension so localhost contributor servers accept the per-agent OpenAI endpoint shape the production Hosted runtime exposes.
* All 11 contributor Program.cs files call MapDevTemporaryLocalAgentEndpoint() with a contributor-only warning comment.
* Hosted-Files and Hosted-AzureSearchRag were importing Hosted_Shared_Contributor_Setup but never calling AddDevTemporaryLocalContributorSetup(). Both now call it so HostedSessionIsolationKeyProvider resolves correctly in dev.
* Hosted-AzureSearchRag, Hosted-Files, Hosted-MemoryAgent csprojs drop stale VersionOverride="2.1.0-beta.1" pins.
* Hosted-AzureSearchRag and Hosted-Files csprojs add ProjectReference to Hosted_Shared_Contributor_Setup.
* Hosted-Observability/.dockerignore removed the out/ exclusion that was blocking COPY out/ . in Dockerfile.contributor.
Verified:
* Full solution-scoped build of changed projects: green.
* Scoped CI-parity dotnet format via WSL2 + Docker (mcr.microsoft.com/dotnet/sdk:10.0) over every changed csproj: clean.
* Foundry unit tests: 263/263.
* Contributor docker smoke for 8 hosted samples (publish + docker build + docker run + curl POST to the wildcard route): HTTP 200 / 500 with route matched.
* End-to-end smoke against the real Azure Foundry project with a fresh bearer token: Hosted-Files contributor container served HTTP 200, the agent invoked ListBundledFiles, and returned the expected file name.
* Address PR review: forward pipeline settings; add UTs
- CreateProjectClientOptions also carries RetryPolicy, NetworkTimeout, ClientLoggingOptions, MessageLoggingPolicy (was Transport+UserAgentApplicationId only).
- Make CreateProjectClientOptions internal so tests can verify the copy directly.
- Add AsAIAgent(Uri) UTs covering tools forwarding to inner ChatOptions and null tools handling.
- Add CreateProjectClientOptions UTs covering null caller and full pipeline-settings copy.
Hosted-MemoryAgent
A hosted Foundry agent that uses FoundryMemoryProvider to remember user-private details across requests and across sessions, scoped per end user via the Foundry platform's isolation keys. The agent plays a friendly travel assistant: tell it about your trip, ask follow-up questions in a new session, and it recalls what it learned about you.
This sample exists to demonstrate two things together:
- How to host an agent that consumes a
Microsoft.Extensions.AI.AIContextProvider(specificallyFoundryMemoryProvider) under the Foundry Responses hosting layer. - How the new
HostedSessionContextflows from theFoundryplatform isolation headers (x-agent-user-isolation-key,x-agent-chat-isolation-key) through theHostedSessionIsolationKeyProviderinto the provider'sstateInitializer, so memories are partitioned per user automatically.
Prerequisites
- .NET 10 SDK
- An Azure AI Foundry project with at least one chat model deployment and one embedding model deployment
- Azure CLI logged in (
az login)
Configuration
Copy the template and fill in your values:
cp .env.example .env
Required:
AZURE_AI_PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
AZURE_AI_EMBEDDING_DEPLOYMENT_NAME=text-embedding-ada-002
AZURE_AI_MEMORY_STORE_ID=hosted-memory-sample
AGENT_NAME=hosted-memory-agent
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development
For local container runs only (the platform supplies these in production):
HOSTED_USER_ISOLATION_KEY=alice
HOSTED_CHAT_ISOLATION_KEY=alice-chat-1
.envis gitignored. The.env.exampletemplate is checked in as a reference.
How memory scoping works
| Layer | Source of the user identity |
|---|---|
| Inbound request | The Foundry platform sets x-agent-user-isolation-key and x-agent-chat-isolation-key headers on every request. |
| Hosting layer | AgentFrameworkResponseHandler resolves a HostedSessionIsolationKeyProvider from DI and calls GetKeysAsync(context, request, ct). The default implementation reads context.Isolation.UserIsolationKey and context.Isolation.ChatIsolationKey. |
| Session | The handler stores the resolved values on the session as a HostedSessionContext on the first request, and validates the values on every subsequent request that resumes the same conversation (mismatch returns 403). |
| Memory provider | The sample's stateInitializer reads session.GetHostedContext().UserId and uses it as the FoundryMemoryProviderScope. Memories are partitioned per user. |
When running outside the Foundry platform the headers are absent. The sample registers
DevTemporaryLocalSessionIsolationKeyProvider (via AddDevTemporaryLocalContributorSetup) which
falls back to the HOSTED_USER_ISOLATION_KEY and HOSTED_CHAT_ISOLATION_KEY environment variables,
defaulting to a single local-dev-* bucket when neither is set.
Production warning. Never register
DevTemporaryLocalSessionIsolationKeyProviderin production. The Foundry platform sets the isolation keys for every inbound request, and client-supplied environment variables can be forged.
Running directly (contributors)
This project uses ProjectReference to build against the local Agent Framework source.
cd dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent
dotnet run
The agent starts on http://localhost:8088.
Test it
curl -X POST http://localhost:8088/responses \
-H "Content-Type: application/json" \
-d '{"input": "Hi! My name is Taylor and I am planning a hiking trip to Patagonia in November.", "model": "hosted-memory-agent"}'
Wait a few seconds for memory extraction, then ask a follow-up using the response id from the
previous call as previous_response_id:
curl -X POST http://localhost:8088/responses \
-H "Content-Type: application/json" \
-d '{"input": "What do you already know about my upcoming trip?", "previous_response_id": "<id>", "model": "hosted-memory-agent"}'
Running with Docker
Since this project uses ProjectReference, the standard Dockerfile cannot resolve dependencies
outside this folder. Use Dockerfile.contributor which takes a pre-published output.
1. Publish for the container runtime (Linux Alpine)
dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
2. Build the Docker image
docker build -f Dockerfile.contributor -t hosted-memory-agent .
3. Run the container
export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
docker run --rm -p 8088:8088 \
-e AGENT_NAME=hosted-memory-agent \
-e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \
-e HOSTED_USER_ISOLATION_KEY=alice \
-e HOSTED_CHAT_ISOLATION_KEY=alice-chat-1 \
--env-file .env \
hosted-memory-agent
4. Smoke test the running container
A scripted smoke test that exercises memory recall and per-user isolation across two simulated
users is provided at scripts/smoke.ps1. From the sample folder:
pwsh ./scripts/smoke.ps1
The script publishes the project, builds the image, runs the container with two distinct
HOSTED_USER_ISOLATION_KEY values, drives a multi-turn conversation per user, asserts that each
user only sees their own memories, and exits non-zero on failure.
NuGet package users
If you are consuming the Agent Framework as a NuGet package (not building from source), use the
standard Dockerfile instead of Dockerfile.contributor. See the commented section in
HostedMemoryAgent.csproj for the PackageReference alternative.
How it differs from sibling samples
| Hosted-ChatClientAgent | Hosted-MemoryAgent | |
|---|---|---|
| Agent definition | Inline (AsAIAgent(model, instructions)) |
Inline, plus AIContextProviders = [memoryProvider] |
| State | None beyond the conversation history | Per-user memories persisted in Foundry Memory |
| Identity | Not used | Required: HostedSessionContext.UserId flows into the memory scope |
| Local dev | AddDevTemporaryLocalContributorSetup() keeps requests succeeding when isolation headers are absent |
Same; additionally honours HOSTED_USER_ISOLATION_KEY to simulate distinct users |