Introduction

AI agents often handle information that belongs to different users, conversations, applications, or business processes. A customer-support agent may process one customer's account while another request is being handled at the same time. An internal agent may work with sensitive company documents while a separate session handles public information.

If these sessions are not properly isolated, one session can accidentally receive context belonging to another session.

Session isolation is therefore an important part of AI agent architecture. It is not enough to create separate chat windows in the user interface. The underlying conversation state, tool access, identity, retrieved data, and application state must also be scoped correctly.

Microsoft Foundry provides capabilities for building agent-based applications, but session isolation ultimately depends on how the application defines and enforces boundaries around each agent execution.

What Is an AI Agent Session?

A session represents the state associated with a particular interaction or workflow.

A simplified session can contain:

Session
 |
 +-- Conversation history
 +-- User/application context
 +-- Agent configuration
 +-- Tool interactions
 +-- Retrieved information
 +-- Temporary state

For example:

User A
  |
  v
Session A
  |
  +-- Conversation A
  +-- Tools
  +-- Retrieved data

User B
  |
  v
Session B
  |
  +-- Conversation B
  +-- Tools
  +-- Retrieved data

The critical requirement is that information created for Session A should not unintentionally become available to Session B.

Why Session Isolation Matters

Consider a customer-support application.

User A asks:

What is the status of my order?

The agent retrieves order information and stores it in the conversation context.

A few seconds later, User B starts a new conversation.

If the application accidentally reuses User A's conversation state, User B might receive information from User A.

The problem is not necessarily the model itself. It can originate from application state management.

Common sources of session leakage include:

Session isolation therefore needs to be designed across the entire application.

Session Isolation Is More Than Conversation History

A common mistake is to isolate only the chat history.

For example:

Session A
  |
  +-- Conversation A

while using shared state for everything else:

Global
  |
  +-- User profile
  +-- Retrieved documents
  +-- Tool results
  +-- Temporary files

This can still create cross-session data exposure.

A stronger design treats session-related information as scoped state:

Session A
 |
 +-- Conversation
 +-- User context
 +-- Retrieval scope
 +-- Tool state
 +-- Temporary resources

Session B
 |
 +-- Conversation
 +-- User context
 +-- Retrieval scope
 +-- Tool state
 +-- Temporary resources

Use a Unique Session Identifier

Every conversation or workflow should have a unique identifier.

For example:

var sessionId = Guid.NewGuid().ToString();

The application can associate the identifier with the authenticated principal and relevant application context.

Conceptually:

User
 |
 +-- User ID
 |
 +-- Session ID
 |
 +-- Agent ID

A database record might look like:

{
  "sessionId": "sess-7f31",
  "userId": "user-184",
  "agentId": "support-agent"
}

The important point is that a session ID should not be treated as authorization by itself.

A client should not be able to change the session identifier and gain access to another user's conversation.

Validate Session Ownership

Every request should verify that the session belongs to the authenticated user or permitted application context.

For example:

public async Task<Session> GetSessionAsync(
    string sessionId,
    string userId)
{
    var session = await _sessionStore.GetAsync(sessionId);

    if (session == null)
        throw new KeyNotFoundException();

    if (session.UserId != userId)
        throw new UnauthorizedAccessException();

    return session;
}

This is a simple but important boundary.

Do not rely on:

sessionId == authorization

Instead:

Authenticated Identity
        |
        v
Session Ownership Check
        |
        v
Session State

Keep Session State Scoped

Application code should avoid global mutable state for user-specific agent sessions.

This pattern is risky:

public static class AgentState
{
    public static List<string> Messages { get; } = new();
}

All users can potentially interact with the same collection.

Instead, scope state by session:

public class SessionState
{
    public string SessionId { get; init; }
    public List<string> Messages { get; } = new();
}

Persistent applications should generally store important session state in a durable store rather than relying on process memory.

That also makes the application more resilient when instances restart or scale horizontally.

Be Careful With Distributed Caches

Caching can introduce subtle session-isolation problems.

Suppose an application uses:

cache key:
agent-response

Every user could potentially receive the same cached value.

Instead, include the relevant scope:

cache key:
agent:{agentId}:user:{userId}:session:{sessionId}:response:{requestId}

The exact key structure depends on the application, but the principle is consistent: a cache key must reflect the security boundary of the data being cached.

Do not include more identifying information than necessary, and avoid putting sensitive data directly into cache keys when the infrastructure exposes them broadly.

Isolate Retrieval Context

Retrieval-augmented generation introduces another important boundary.

Suppose two customers have separate documents:

Customer A
  |
  +-- invoice-a.pdf
  +-- contract-a.pdf

Customer B
  |
  +-- invoice-b.pdf
  +-- contract-b.pdf

A query from Customer A should not retrieve Customer B's documents.

A conceptual retrieval query might include a tenant or user scope:

var documents = await _search.SearchAsync(
    query,
    tenantId: currentTenantId);

The retrieval layer should enforce this scope.

Do not rely on the model to ignore documents belonging to another tenant.

The safer flow is:

Authenticated Identity
        |
        v
Tenant/User Scope
        |
        v
Retrieval Filter
        |
        v
Allowed Documents
        |
        v
Agent

Tool Calls Must Respect Session Boundaries

An agent may call tools that access application data.

For example:

get_customer_profile
get_order
search_documents
create_ticket

The tool itself should verify the caller's scope.

A dangerous pattern is:

Agent
  |
  v
get_order(orderId)
  |
  v
Return order

The API should not assume that knowing an order ID proves authorization.

A stronger design is:

Agent
  |
  v
get_order(orderId, userContext)
  |
  v
Authorization Check
  |
  v
Order

For example:

var order = await _orders.GetForCustomerAsync(
    orderId,
    authenticatedUserId);

This prevents the model from bypassing application authorization by supplying another identifier.

Separate Sessions From Agent Configuration

Two sessions can use the same agent configuration without sharing their state.

For example:

Support Agent
 |
 +-- Session A
 |
 +-- Session B
 |
 +-- Session C

The agent's general instructions can remain shared, while conversation state remains session-specific.

This is an important distinction:

Shared:
- Agent definition
- General instructions
- Tool definitions

Isolated:
- Conversation history
- User data
- Retrieved private documents
- Temporary state
- Tool results

Shared configuration should not accidentally contain user-specific information.

Handle Concurrent Requests

A single session can also receive multiple requests at the same time.

For example:

Session A
   |
   +---- Request 1
   |
   +---- Request 2

If both requests modify the same conversation state simultaneously, updates can arrive in the wrong order.

Use appropriate concurrency controls such as:

A simple version field might look like:

{
  "sessionId": "sess-7f31",
  "version": 12
}

A request can verify that the session is still at version 12 before writing version 13.

This helps prevent one request from silently overwriting another.

Session Isolation in Multi-Tenant Applications

Multi-tenant applications need at least two boundaries:

Tenant
  |
  +-- User
       |
       +-- Session
            |
            +-- Agent

For example:

Tenant A
 |
 +-- User 1
 |    +-- Session 1
 |    +-- Session 2
 |
 +-- User 2
      +-- Session 3

Tenant B
 |
 +-- User 3
      +-- Session 4

A query for Session 3 should never return Session 1 or Session 4 data.

The database schema, authorization layer, cache, retrieval system, and tool APIs should all respect the tenant boundary.

Do Not Trust Client-Supplied Tenant IDs

A request might contain:

{
  "tenantId": "tenant-b",
  "sessionId": "session-123"
}

The application should not automatically trust this value.

The tenant should normally be derived from the authenticated identity or a trusted authorization context.

Conceptually:

Authentication
      |
      v
Trusted Tenant Context
      |
      v
Session Lookup
      |
      v
Tenant Validation

This prevents a client from simply changing a tenant identifier in the request.

Logging Without Exposing Sensitive Data

Session-level observability is important, but logs can themselves become a source of data leakage.

Log identifiers that help investigation:

sessionId
userId hash or internal identifier
agentId
requestId
timestamp
tool name
execution status

Avoid logging sensitive conversation content unless there is a documented reason and appropriate protection.

A useful correlation structure is:

requestId
    |
    +-- sessionId
          |
          +-- agentId
          |
          +-- tool calls

This makes it possible to trace an execution without unnecessarily copying sensitive data into logs.

Common Mistakes

Reusing Session IDs

A session identifier should have a clearly defined lifecycle and ownership.

Using Global Variables for Conversation State

Global mutable state can mix data between users and sessions.

Trusting Session IDs From the Client

Always verify ownership against authenticated identity.

Filtering Data Only in the Prompt

Do not retrieve unauthorized data and expect the model to ignore it.

Ignoring Tool-Level Authorization

Every sensitive tool should enforce its own access rules.

Using Unscoped Cache Keys

Cached responses must respect user and tenant boundaries.

Ignoring Concurrent Requests

Multiple requests can update the same session at the same time.

Logging Complete Conversations by Default

Detailed logs can create another sensitive data store.

Troubleshooting Session Leakage

When you suspect that one session is receiving another session's data, investigate the entire request path.

Step 1: Trace the Request

Start with the request ID and identify its session.

Step 2: Verify Session Ownership

Confirm the authenticated identity and session mapping.

Step 3: Inspect State Storage

Check whether the conversation state is stored under the correct key.

Step 4: Inspect Cache Keys

Look for keys that omit user, tenant, or session scope.

Step 5: Check Retrieval Filters

Verify that search queries apply the correct tenant or user filter.

Step 6: Check Tool Authorization

Confirm that tools independently validate access to requested resources.

Step 7: Check Concurrency

Look for simultaneous writes or stale session versions.

This process is usually more effective than focusing only on the model's response.

Best Practices

  1. Give every conversation a unique session identifier.

  2. Associate each session with a trusted identity.

  3. Validate session ownership on every request.

  4. Scope state by tenant, user, and session where appropriate.

  5. Keep shared agent configuration separate from user state.

  6. Use authorization checks inside sensitive tools.

  7. Apply tenant filters before retrieval results reach the model.

  8. Use security-aware cache keys.

  9. Protect concurrent session updates.

  10. Keep sensitive information out of unnecessary logs.

  11. Test cross-user and cross-tenant access explicitly.

  12. Treat session isolation as an application-wide security boundary.

Advantages and Disadvantages

Advantages

Disadvantages

Prevents cross-session data exposure

Requires careful state management

Supports multi-user agent applications

Adds authorization checks

Makes debugging and auditing easier

Distributed systems increase complexity

Works with multi-tenant architectures

Cache and retrieval layers need additional controls

Supports safer concurrent workloads

Poor session design can still introduce subtle leaks

Production Checklist

[ ] Every session has a unique identifier
[ ] Session ownership is validated
[ ] Tenant scope is enforced
[ ] Conversation state is isolated
[ ] Cache keys include required security scope
[ ] Retrieval queries enforce access boundaries
[ ] Tools perform authorization checks
[ ] Shared agent configuration contains no user-specific state
[ ] Concurrent session updates are controlled
[ ] Sensitive data is protected in logs
[ ] Cross-user access tests are automated
[ ] Cross-tenant access tests are automated
[ ] Session lifecycle and retention are documented

Summary

AI agent session isolation is a security and architecture concern that extends beyond conversation history.

A reliable design separates shared agent configuration from private session state and ensures that every session is associated with a trusted identity and appropriate tenant scope.

The same boundary must be maintained across databases, caches, retrieval systems, tools, APIs, and logs. A secure conversation ID alone is not sufficient.

For Microsoft Foundry-based agent applications, the surrounding application architecture remains responsible for enforcing these boundaries. The model should operate within the data and tools that the application has already authorized.

The practical goal is simple: one session should only be able to access the state, data, and actions that its authenticated context is allowed to use.