# How ai-memory Handles Multi-Session and Multi-User Access: Isolation Modes Explained

> Discover how ai-memory isolation modes manage multi-session and multi-user access safely. Learn about Single, PerSession, and PerActor modes for secure data handling.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: deep-dive
- Published: 2026-09-05

---

**ai-memory uses a process-wide active-project pointer with three isolation modes—Single, PerSession, and PerActor—to safely handle concurrent sessions and multiple users without data corruption.**

The `ai-memory` project (akitaonrails/ai-memory) provides a memory layer for AI agents that must operate reliably across parallel tool executions and distinct operator identities. When an MCP tool receives a request, the system needs to know which workspace and project context applies—without requiring every call to explicitly pass this state. The solution is an **active-project pointer** that resolves context dynamically based on who is calling and from which session.

## The ActiveProject Pointer and Isolation Modes

At the core is `ActiveProject`, defined in [`crates/ai-memory-core/src/active_project.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/active_project.rs). This structure maintains a globally accessible pointer that hook routers set and tool handlers read. The pointer supports three isolation behaviors controlled by the **`ActiveProjectMode`** enum:

| Mode | Isolation Level | Use Case |
|------|-----------------|----------|
| **Single** | No isolation; single global slot | Legacy/single-operator setups only |
| **PerSession** | Isolated by `session_id` | Same user, multiple parallel agent runs |
| **PerActor** (default) | Isolated by composite `ActorKey { user, session_id }` | Full multi-user, multi-session safety |

### Single Mode: Last-Write-Wins

In `Single` mode (lines 94-100), there is one shared slot for the entire process. The last hook event overwrites it unconditionally. This works for a single operator running one agent at a time, but concurrent hooks from different repositories or sessions corrupt each other's state.

### PerSession Mode: Session-Scoped Isolation

`PerSession` keys entries by `session_id` alone (lines 30-33). Each parallel run of the same user gets its own workspace/project entry, preventing cross-repository interference even when the same bearer token is used across multiple concurrent agents.

### PerActor Mode: Full User + Session Isolation

`PerActor` (the default) uses an **`ActorKey`** combining `user` and `session_id` (lines 82-101, 166-176). This provides complete separation between:

- Different users (distinct bearer tokens)
- Concurrent sessions from the same user
- Legacy anonymous calls (fallback to single slot when no actor identity is present)

## Storage, TTL, and Capacity Management

The pointer entries live in a **`PerActorMap`**—a `HashMap<ActorKey, Entry>` protected by an `RwLock`. Each entry stores:

- `workspace_id` and `project_id`
- `default_global` flag indicating whether this is a fallback default

To prevent unbounded memory growth, the implementation enforces:

- **Time-to-live**: Entries expire after a configurable duration (default 1 hour)
- **Capacity cap**: Maximum 4096 entries (default)
- **Lazy eviction**: Expired entries are purged on every read and write operation (lines 44-58)

## Actor Identification and Authentication

The `ActorKey` originates in the authentication layer. In [`crates/ai-memory-mcp/src/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/auth.rs), the system extracts a stable user identifier from bearer tokens. For interactive sessions, [`crates/ai-memory-mcp/src/human_auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/human_auth.rs) provides human-authentication specifics. The `ActorContext` struct in [`crates/ai-memory-mcp/src/actor.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/actor.rs) carries this identity through the MCP layer.

The authentication flow combines user and session identifiers:

```rust
use ai_memory_core::active_project::{
    ActiveProject, ActiveProjectMode, ActorKey,
};

// Create the pointer with default PerActor mode
let active_proj = ActiveProject::new();

// Construct actor identity from authenticated request
let actor = ActorKey {
    user: Some("alice".into()),
    session_id: Some("sess-42".into()),
};

```

## Resolving Projects: The Lookup Outcome

Tool handlers call `ActiveProject::get_for(actor_key)` to resolve context. The method returns an **`ActiveProjectLookup`** with three possible outcomes (lines 82-94):

- **`Resolved(ws, proj)`** — Concrete workspace and project found for this actor
- **`Mismatch`** — Caller supplied specific coordinates but no matching entry exists; prevents accidental fallback to another user's data
- **`Unset`** — No actor information available; system falls back to legacy default project

## Setting and Updating the Active Project

When repository hooks fire (e.g., directory change, file operation), the hook router publishes the new active project:

```rust
use ai_memory_core::active_project::ActiveProject;

// After resolving workspace and project from cwd or config
let new_ws = /* WorkspaceId */;
let new_proj = /* ProjectId */;
active_proj.set_for(actor, new_ws, new_proj, true);

```

The `set_for` method handles actor-keyed storage, TTL refresh, and capacity enforcement atomically.

## Integration in MCP Server Handlers

In [`crates/ai-memory-mcp/src/server.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs), every tool handler follows this pattern:

1. Extract `ActorContext` from the request (authentication layer)
2. Call `ActiveProject::get_for(&actor_key)` to resolve workspace/project
3. Match on `ActiveProjectLookup` to determine execution context or reject the call

This design guarantees that a tool operating on user Alice's repository cannot accidentally access Bob's data, even when both users connect through the same running `ai-memory` server process.

## Summary

- **Three isolation modes** (`Single`, `PerSession`, `PerActor`) trade off simplicity against concurrent safety
- **`PerActor` default** provides complete separation using composite `ActorKey { user, session_id }`
- **`PerActorMap`** with `RwLock`, TTL, and capacity bounds keeps memory safe and bounded
- **Authentication layer** in [`auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/auth.rs) extracts stable identities for the pointer system
- **Three-way lookup result** (`Resolved`, `Mismatch`, `Unset`) enables explicit failure modes instead of silent cross-user data leaks

## Frequently Asked Questions

### What happens if two users with different bearer tokens connect simultaneously?

With the default `PerActor` mode, each user receives a distinct `ActorKey`. Their active-project entries are stored separately in the `PerActorMap`, so neither can read or overwrite the other's workspace/project context. The `Mismatch` result explicitly prevents fallback to another user's slot.

### Can the same user run multiple agent sessions in parallel without conflict?

Yes. `PerSession` or `PerActor` mode isolates by `session_id`. Each parallel agent run receives a unique session identifier, resulting in separate `ActorKey` entries and independent active-project pointers. This prevents repository A's hooks from corrupting repository B's context.

### How does ai-memory prevent memory exhaustion from stale sessions?

Entries expire after a configurable TTL (default 1 hour) and the map enforces a capacity cap (default 4096 entries). Expired or overflowed entries are lazily evicted during read and write operations, keeping the `ActiveProject` structure bounded regardless of connection churn.

### What is the fallback behavior for unauthenticated or legacy requests?

When no user or session identity is present, `get_for` returns `ActiveProjectLookup::Unset`. The MCP server then falls back to a legacy default project, preserving backward compatibility with clients that predate the authentication layer.