Files
microsoft--agent-framework/docs/decisions/0030-hosted-platform-context-agentserver-2.0.md
wehub-resource-sync db620d33df
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:25 +08:00

6.4 KiB

status, contact, date, deciders, consulted, informed
status contact date deciders consulted informed
accepted rogerbarreto 2026-06-29 rogerbarreto

Hosted platform context (user id + call id) for Foundry Hosting on AgentServer 2.0

Supersedes ADR-0026.

Context and Problem Statement

ADR-0026 sourced the hosted-agent end-user identity from ResponseContext.Isolation (an IsolationContext typed UserIsolationKey / ChatIsolationKey), injected by the platform as the x-agent-user-isolation-key and x-agent-chat-isolation-key headers.

Azure.AI.AgentServer.* 2.0.0 (responses protocol 2.0.0) removes that surface. ResponseContext.Isolation is gone; the platform now exposes ResponseContext.PlatformContext (a PlatformContext typed UserIdKey and CallId), populated from the x-agent-user-id and x-agent-foundry-call-id headers. The chat isolation key no longer exists, and a new per-request call id is introduced that first-party Foundry services (the toolbox proxy in particular) require on outbound calls to resolve the server-side-stored caller context. The hosting layer in Microsoft.Agents.AI.Foundry.Hosting had to migrate to this contract without changing the public shape that samples and providers depend on.

Decision Drivers

  • Track the breaking Azure.AI.AgentServer.* 2.0.0 surface (PlatformContext replacing Isolation) while keeping the same per-user partitioning guarantees from ADR-0026.
  • Keep the change internal: existing hosted samples and AIContextProviders must not need code changes. session.GetHostedContext().UserId, HostedSessionIsolationKeyProvider, and AddFoundryResponses stay source-compatible.
  • Forward the new per-request call id verbatim on outbound calls to Foundry first-party services so per-user toolbox OAuth consent and other server-side caller-context lookups keep working.
  • Remain resilient on protocol 1.0.0: when only the legacy headers are present, UserIdKey still resolves and CallId is simply absent.
  • Preserve the strict-resume tamper defense from ADR-0026 with identity now reduced to user only.

Considered Options

For the identity source:

  1. Map ResponseContext.PlatformContext.UserIdKey into the existing HostedSessionContext (user only), keeping ADR-0026's storage shape and read accessor.
  2. Keep a ChatId slot on HostedSessionContext for backward source-compatibility, populated from CallId or left null.

For the call id propagation:

A. A request-scoped ambient (HostedCallContext, an AsyncLocal<string?>) set by the handler and re-applied before each egress point, read by the outbound delegating handler. B. Thread the call id through every method signature down to the toolbox bearer handler.

For session keying (previously implied by the conversation/chat pairing):

I. HostedConversationKey resolving a stable partition from conversation_id ?? partition(previous_response_id) ?? partition(responseId). II. Continue keying on the container session id (FOUNDRY_AGENT_SESSION_ID).

Decision Outcome

Chosen: Option 1 for identity, Option A for call id, Option I for session keying.

Rationale:

  • ChatId dropped (Option 2 rejected). The platform no longer supplies a chat key; carrying a synthetic one would invent identity the trust boundary does not provide. HostedSessionContext becomes user-only (HostedSessionContext(string userId) / UserId), and the strict-resume check validates UserId alone. The corresponding HostedFoundryMemoryProviderScopes values PerChat and PerUserAndChat are removed; PerUser is retained.
  • Ambient call id (Option B rejected). Writing HostedCallContext.CallId inside the streaming async IAsyncEnumerable iterator is reverted across each yield, so a single up-front assignment is lost before the toolbox/MCP egress runs. The handler therefore captures context.PlatformContext?.CallId once and re-applies it immediately before each egress point; FoundryToolboxBearerTokenHandler forwards it as x-agent-foundry-call-id. The ambient is request-scoped and never leaks into the caller's execution context (guarded by a unit test).
  • HostedConversationKey (Option II rejected). One container serves many conversations for its lifetime, so the container session id cannot key per-conversation state. The partition key is derived from the conversation/previous_response_id/minted response id instead.

Implementation summary in Microsoft.Agents.AI.Foundry.Hosting:

Type Visibility Change vs ADR-0026
HostedSessionContext public sealed Now user-only (UserId); ChatId removed.
PlatformHostedSessionIsolationKeyProvider internal sealed Maps context.PlatformContext.UserIdKey (was context.Isolation.UserIsolationKey / ChatIsolationKey).
HostedCallContext internal static New. Request-scoped AsyncLocal<string?> holding the x-agent-foundry-call-id value.
HostedConversationKey internal New. Resolves the per-conversation partition key.
FoundryToolboxBearerTokenHandler internal Now also forwards x-agent-foundry-call-id outbound.
HostedFoundryMemoryProviderScopes public PerChat / PerUserAndChat removed; PerUser kept.

Package manifests bump the responses container protocol to 2.0.0 (invocations stays 1.0.0).

Consequences

Positive:

  • Per-user memory partitioning and the strict-resume tamper defense from ADR-0026 are preserved with no public API churn for samples or providers.
  • Per-user toolbox OAuth consent and other server-side caller-context lookups keep working because the per-request call id is forwarded on egress.
  • Works unchanged on protocol 1.0.0 (no call id) and 2.0.0.

Negative:

  • HostedSessionContext.ChatId and the PerChat / PerUserAndChat memory scopes are removed; any out-of-tree consumer that referenced them must move to user-scoped partitioning.
  • The call id must be re-applied before every egress point because of the async-iterator AsyncLocal revert; a missed re-apply silently drops the header. This is covered by unit tests.

Out of scope

  • HMAC tamper signatures over the persisted context remain unimplemented; equality comparison against ResponseContext.PlatformContext on every request is sufficient because the platform sets the header at the trust boundary.
  • The per-request User field on CreateResponse is still intentionally not consumed.