previous_response_id, and context.get_history() returns only the history the current request’s user is authorized to see. You own two things: the user-to-session mapping in your middle tier, and the partitioning of any data your container stores itself (files, rows, or cache) beyond that platform-managed conversation state.
A complete, runnable session multiplexing sample demonstrates both sides - the middle-tier session pool and the container handler - and this article links its files as you go.
Prerequisites
- A hosted agent that uses container protocol version 2.0.0. To upgrade, see Migrate hosted agents.
- The
Microsoft.CognitiveServices/accounts/AIServices/agents/endpoints/UserIdentityImpersonation/actionpermission assigned to your middle-tier service’s identity. This permission isn’t included in built-in roles; grant it through a custom role — see Delegate the end-user identity. Without it, thex-ms-user-identityheader is rejected with a403. - The Azure AI Projects client library for the middle tier, and the Azure AI AgentServer SDK for the container (
azure-ai-agentserver-core2.0.0b7+ for Python, orAzure.AI.AgentServer.Core1.0.0-beta.26+ for .NET). - A deployed agent to test against. Isolation isn’t enforced for local runs.
Isolate two users in a shared session
Start with the core behavior: two users - call them Alice and Bob, the acted-for users in the sample - can share oneagent_session_id, and the platform still keeps each user’s conversation private. Your middle tier identifies the acted-for user on every call with the x-ms-user-identity header (delegation). To continue a user’s own conversation, it passes that user’s previous response as previous_response_id.
The minimal invoke_previous_response_isolation.py caller in the sample sends exactly that, using the SDK’s agent-bound Responses client:
previous_response_id while sitting in the same session, the call fails - Bob can’t continue Alice’s conversation. That guarantee holds without any extra isolation code in your container.
Scale to many users with a session pool
Isolating two users in one session is the building block. To serve many users, pool them across a bounded set of sessions instead of opening one session per user. Each session counts against the regional concurrent-session limits while it actively processes a turn, so one session per user doesn’t scale. Because users read, think, and type between turns, your peak concurrent requests are typically a small fraction of your total user count. Size a pool to that peak, then map each user to a session in it and pass that user’s identity on every call, exactly as in the previous section. Decide how to map users to sessions. Common strategies include:- Sticky, least-loaded. A returning user reuses their session; new users go to the least-loaded session. This strategy spreads load evenly and keeps a user’s turns together. Grow the pool when sessions reach a per-user cap.
- Hash-based. Assign a session with
hash(user_id) % pool_size. This strategy is simple and stateless, but load can be uneven and resizing the pool reshuffles users. - Round-robin. Distribute requests evenly across the pool. This strategy is simple, but a user’s turns can land on different sessions.
- Group-based. Route by tenant, team, or region so related users share sessions. This strategy is useful when users in a group share context.
invoke_session_pool.py caller in the sample implements caller-owned assignment with two strategies, sticky-fill and round-robin. A returning user always keeps their session; a new user is placed by the selected strategy. The sticky-fill path fills the least-loaded session and opens a new one only when every session is at capacity:
agent_session_id in extra_body, and x-ms-user-identity stays the per-user identifier.
Handle the request in your container
On protocol 2.0.0, the platform resolves the acted-for user and exposes it to your handler throughget_request_context(). Validate that context (fail closed when it’s missing, such as on local runs), then let the platform return the per-user history with context.get_history(). The main.py handler in the sample keeps no conversation state of its own:
context.get_history() per request, a user in a shared session never receives another user’s conversation history.
Partition per-user data your container stores
The platform isolates conversation history for you. If your container also stores its own data - files, database rows, or a cache - that data isn’t partitioned automatically. Key it by both the session ID and the user ID so two users in the same session can’t see each other’s data:- Python
- C#
x-agent-user-id request header. If your runtime doesn’t use the SDK context, read this header directly.The platform populates get_request_context().user_id on protocol 2.0.0. Never use the session ID alone for user-owned data when more than one user can enter the session.$HOME. For a shared session, extend that key with the user ID from the request context so each user gets their own partition.
Verify isolation
Confirm the guarantee with the sample’s A-A-B test,invoke_previous_response_isolation.py. Run it against your deployed agent with two distinct users (the sample defaults to Alice and Bob):
- As Alice, create a response in a shared session and capture its
id. - As Alice, create a second response in the same session with
previous_response_idset to the first response’sid, and capture itsid. - As Bob, in the same session, send a request with
previous_response_idset to Alice’s second response. The call fails - Bob can’t continue Alice’s chain.
Related content
- Isolate hosted agent sessions per user for the default, per-caller isolation model.
- Session multiplexing sample for the complete middle-tier session pool, container handler, and isolation test.
- Note-taking agent sample for a container that persists user-owned data per session (Python and C#).
- Quotas and limits for Foundry Agent Service for regional concurrent-session limits.
- Migrate hosted agents to move a container to protocol 2.0.0.
- Hosted agent runtime contract for the platform headers and environment variables a container receives.