Files
Roger Barreto ad95f2f2fa .NET: Add Hosted-MemoryAgent sample with isolation key plumbing (#5692) (#5702)
* .NET: Add Hosted-MemoryAgent sample with isolation key plumbing (#5692)

Adds HostedSessionContext + HostedSessionIsolationKeyProvider in Microsoft.Agents.AI.Foundry.Hosting so AIContextProviders (notably FoundryMemoryProvider) can scope per user via the platform's x-agent-user-isolation-key / x-agent-chat-isolation-key headers.

- New types: HostedSessionContext (sealed), HostedSessionContextExtensions (public Get, internal Set), abstract HostedSessionIsolationKeyProvider (async), internal PlatformHostedSessionIsolationKeyProvider mapping ResponseContext.Isolation.

- AgentFrameworkResponseHandler now resolves the provider, tags fresh sessions, and validates resumed sessions against the live request (strict 403 'Hosted session identity context mismatch' on any mismatch; 500 on null keys).

- New shared sample project Hosted_Shared_Contributor_Setup hosts DevTemporaryTokenCredential and DevTemporaryLocalSessionIsolationKeyProvider plus AddDevTemporaryLocalContributorSetup. All 9 existing responses samples migrated to consume it so local runs keep working under the strict isolation contract.

- New Hosted-MemoryAgent sample: travel assistant wired through FoundryMemoryProvider with stateInitializer reading session.GetHostedContext().UserId. Includes Dockerfile, smoke.ps1, agent.yaml/manifest.

- New IT scenario 'memory' in Foundry.Hosting.IntegrationTests + MemoryHostedAgentFixture + MemoryHostedAgentTests. Verified end to end against the tao Foundry project.

- ADR 0026 captures the design tree.

* Address PR review feedback

- Dockerfile: add header noting it targets NuGet builds; contributors must use Dockerfile.contributor for ProjectReference source builds.

- PlatformHostedSessionIsolationKeyProvider: doc said 'returns context with empty values'; corrected to 'returns null' which the handler treats as 500.

- FakeHostedSessionIsolationKeyProvider: doc clarifies that null configurations are allowed for testing the handler error path.

- HostedSessionContextExtensions.SetHostedContext: enforce write-once with InvalidOperationException; doc + xml exception updated.

- AgentFrameworkResponseHandler: cache PlatformHostedSessionIsolationKeyProvider as static readonly to avoid per-request allocation.

- MemoryHostedAgentTests: tighten waits from 20s to 5s (FoundryMemoryProvider defaults UpdateDelay=0; ingestion ~3s).

- Sample Program.cs imports reordered to satisfy IDE0005.

* Add HostedFoundryMemoryProviderScopes built-in helpers (#5692)

Addresses review feedback from @lokitoth on Hosted-MemoryAgent/Program.cs:54.

- New HostedFoundryMemoryProviderScopes static class with PerUser, PerChat, PerUserAndChat factories returning Func<AgentSession?, FoundryMemoryProvider.State>.

- All helpers throw InvalidOperationException when GetHostedContext() is null, with a message pointing at writing a custom stateInitializer for non-hosted scenarios.

- New HostedFoundryMemoryScope enum and AddHostedFoundryMemoryProvider DI extension (two overloads: explicit AIProjectClient and DI-resolved). Singleton lifetime. Default scope = PerUser.

- Hosted-MemoryAgent sample and the memory IT scenario container both swap their inline lambdas for HostedFoundryMemoryProviderScopes.PerUser().

- 14 new unit tests (241/241 hosting unit tests pass).

* Replace HostedFoundryMemoryScope enum with Func<...> parameter (#5692)

Address PR review feedback from @westey-m: enums are a breaking-change hazard when extended, and the enum was redundant with the existing HostedFoundryMemoryProviderScopes static class.

- Delete HostedFoundryMemoryScope.cs.

- AddHostedFoundryMemoryProvider DI extensions now take Func<AgentSession?, FoundryMemoryProvider.State>? stateInitializer = null. When null, default to HostedFoundryMemoryProviderScopes.PerUser().

- Callers pick a built-in helper (PerUser/PerChat/PerUserAndChat) or pass a custom delegate. New built-ins are a single static method addition with zero impact on existing callers.

- Tests updated; 244/244 hosting unit tests pass.

* Fix isolation context resume for externally-created conversations (#5692)

Branch on the session's existing hosted-context (not on conversation_id presence) so a conversation provisioned externally (e.g. via conversations.CreateProjectConversationAsync) is treated as fresh on first hosted-agent request and stamped, rather than rejected with 403 hosted_session_identity_mismatch. Strict equality is preserved on real resume of an already-stamped session.

Also tighten dotnet/global.json to version 10.0.204 + rollForward latestPatch so local builds match the CI Docker image SDK and avoid 10.0.300 dotnet format stripping required usings.

* Revert global.json SDK pin to upstream (#5692)

The 10.0.204 + latestPatch pin from the previous commit broke the dotnet-format CI job (hostfxr_resolve_sdk2 could not find a compatible SDK in the mcr.microsoft.com/dotnet/sdk:10.0 image). Restore upstream 10.0.200 + minor; local Release builds with SDK 10.0.300 should set GITHUB_ACTIONS=true to bypass the auto-format-on-build target.
ad95f2f2fa · 2026-05-15 05:42:12 +00:00
History
..

Hosted-Workflow-Handoff

A hosted agent server demonstrating two patterns in a single app:

  • tool-agent — an agent with local tools (time, weather) plus remote Microsoft Learn MCP tools
  • triage-workflow — a handoff workflow that routes conversations to specialist agents (code expert or creative writer) using AgentWorkflowBuilder

Both agents are served over the Responses protocol. The server also exposes interactive web demos at /tool-demo and /workflow-demo.

Unlike the other samples in this folder, this one connects to an Azure OpenAI resource directly (not an Azure AI Foundry project endpoint).

Prerequisites

  • .NET 10 SDK
  • An Azure OpenAI resource with a deployed model (e.g., gpt-4o)
  • Azure CLI logged in (az login)

Configuration

Copy the template and fill in your values:

cp .env.example .env

Edit .env:

AZURE_OPENAI_ENDPOINT=https://<your-account>.openai.azure.com/
AZURE_OPENAI_DEPLOYMENT=gpt-4o
AZURE_BEARER_TOKEN=DefaultAzureCredential
ASPNETCORE_URLS=http://+:8088
ASPNETCORE_ENVIRONMENT=Development

AZURE_BEARER_TOKEN=DefaultAzureCredential is a sentinel value that tells the app to skip the bearer token and fall through to DefaultAzureCredential (requires az login). Set it to a real token only when running in Docker.

Note: .env is gitignored. The .env.example template is checked in as a reference.

Running directly (contributors)

cd dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff
dotnet run

The server starts on http://localhost:8088. Open http://localhost:8088 to see the demo index page.

Test it

Using the Azure Developer CLI (invokes triage-workflow — the primary/default agent):

azd ai agent invoke --local "Write me a short poem about coding"

To target a specific agent by name, use curl:

# Invoke triage-workflow explicitly
curl -X POST http://localhost:8088/responses \
  -H "Content-Type: application/json" \
  -d '{"input": "Write me a haiku about autumn", "model": "triage-workflow"}'
# Invoke tool-agent (local tools + MCP)
curl -X POST http://localhost:8088/responses \
  -H "Content-Type: application/json" \
  -d '{"input": "What time is it in Tokyo?", "model": "tool-agent"}'

Running with Docker

1. Publish for the container runtime

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-workflow-handoff .

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 AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \
  --env-file .env \
  hosted-workflow-handoff

4. Test it

azd ai agent invoke --local "Explain async/await in C#"

How the triage workflow works

User message
     │
     ▼
┌──────────────┐
│ Triage Agent │  ──routes──▶  ┌─────────────┐
│  (router)    │               │ Code Expert │
└──────────────┘               └─────────────┘
     ▲                                │
     │◀──────────────────────────────┘
     │
     └──routes──▶  ┌─────────────────┐
                   │ Creative Writer │
                   └─────────────────┘

The triage agent receives every message and hands off to the appropriate specialist. Specialists route back to the triage agent after responding, allowing for multi-turn conversations.

NuGet package users

Use the standard Dockerfile instead of Dockerfile.contributor. See the commented section in HostedWorkflowHandoff.csproj for the PackageReference alternative.