How ai-memory Manages Multi-Session and Multi-User Access to Projects

ai-memory isolates concurrent sessions through per-actor active-project tracking, session-scoped identity resolution, and a single-writer SQLite actor that enforces atomic updates and versioned supersession chains.

The ai-memory project (github.com/akitaonrails/ai-memory) implements a robust concurrency model that allows multiple users and independent sessions to work simultaneously on shared projects without data corruption or cross-contamination. By combining strict session identity management with per-actor state tracking and atomic write batches, the system ensures that every read and write operation respects the caller's specific context and project scope.

Session and User Identity Isolation

Every request to the ai-memory server carries a session ID provided by the MCP client. In crates/ai-memory-core/src/actor.rs, the server creates a Session record that serves as the primary key for all subsequent operations, ensuring that state never leaks between connections.

User authentication is handled in crates/ai-memory-mcp/src/auth.rs, which maps bearer tokens or database user tokens to a User struct containing UserId, name, and authorization level. This separation of session identity (transient connection) from user identity (persistent credentials) allows the system to distinguish between "who is connected" and "who is authorized" for every operation.

Per-Actor Active Project Tracking

Each session maintains its own project context through the ActiveProjectMode::PerActor enumeration defined in crates/ai-memory-core/src/active_project.rs. The ActiveProject struct stores the per-actor pointer and resolves it on every incoming request.

This design guarantees that two concurrent sessions can operate on different "current projects" within the same workspace without interference. When Session A sets project-x as active and Session B sets project-y as active, the ActiveProject resolver ensures that subsequent commands from each session target their respective projects exclusively.

Scope Resolution and Access Control

All read and write routes utilize the ScopeResolver helper functions—lookup_existing_scope, create_explicit_scope, and related utilities—located in crates/ai-memory-mcp/src/actor.rs. These functions validate the (workspace_id, project_id, path) triple exactly once per operation.

By enforcing scope resolution at the actor boundary, the system prevents accidental cross-project leakage. The resolver checks that the requesting session's user has appropriate permissions for the specific workspace-project combination before allowing data access, effectively creating a security sandbox around each request.

Atomic Writes and Conflict Prevention

Mutations flow through a single-writer SQLite actor pattern implemented in crates/ai-memory-store/src/writer.rs. The WriterHandle receives commands via an mpsc channel, batches them into a single transaction, and commits both the data rows and accompanying index updates atomically.

This architecture eliminates "lost-update" race conditions that typically occur when several users edit the same page concurrently. Because all writes serialize through the single writer thread, SQLite's transactional guarantees ensure that even under heavy load, commits are processed sequentially without interleaving partial updates.

Version Safety Through Supersession

When multiple sessions modify the same markdown page simultaneously, ai-memory does not overwrite existing data. Instead, the storage layer creates a supersession chain as implemented in crates/ai-memory-store/src/page.rs.

Each new write generates a supersession entry that references the previous version, establishing a linked history where the newest version is the head of the chain while older versions remain reachable. This satisfies the "last write wins" requirement without destroying historical data, allowing users to trace the evolution of content across concurrent editing sessions.

Exclusive Resource Handoffs

Cross-session work handoffs utilize a double-guard mechanism defined in crates/ai-memory-core/src/handoff.rs. Handoff objects maintain a database row with state = 'open' and require an UPDATE … WHERE state='open' clause to succeed.

Both checks—the initial state query and the atomic update—must succeed for a handoff to be claimed. This guarantees that a handoff transfers work exactly once, even when multiple sessions attempt to claim the resource simultaneously under heavy concurrency.

Practical Implementation Examples

The following examples demonstrate the multi-session API in practice.

Creating a new session with explicit session headers:

// The client library automatically includes a session header.
// Here we manually craft a request to illustrate the flow.
let client = reqwest::Client::new();
let resp = client
    .post("http://127.0.0.1:49374/api/v1/pages/write")
    .header("X-Session-Id", "sess-1234abcd")
    .header("Authorization", "Bearer <user-token>")
    .json(&serde_json::json!({
        "workspace": "demo-ws",
        "project": "demo-proj",
        "path": "notes/todo.md",
        "content": "# TODO\n- [ ] Refactor"

    }))
    .send()
    .await?;

Reading the current active project for a specific session:

let resp = client
    .get("http://127.0.0.1:49374/api/v1/active_project")
    .header("X-Session-Id", "sess-1234abcd")
    .send()
    .await?
    .json::<serde_json::Value>()
    .await?;
println!("Active project: {}", resp["project"]);

Handling concurrent edits that generate supersession chains:

// Session A writes version 1
write_page(sess_a, "shared.md", "Version 1");

// Session B writes version 2 at the same time
write_page(sess_b, "shared.md", "Version 2");

// After both commits, the store contains a supersession chain:
//   v1 -> v2 (v2 is the head, v1 still reachable)
let history = read_page_history(sess_a, "shared.md");
assert_eq!(history.len(), 2);

Summary

  • Session isolation is enforced through unique session IDs in actor.rs and authenticated User structs in auth.rs, preventing cross-session data leakage.
  • Per-actor project tracking via ActiveProjectMode::PerActor ensures each session maintains its own working context independently of other connections.
  • Scope resolution in the MCP actor layer validates workspace-project-path triples before any data access occurs, establishing strict boundaries.
  • Single-writer architecture serializes all mutations through WriterHandle in writer.rs, providing atomic transactions that prevent lost updates.
  • Supersession chains preserve complete edit history when concurrent writes occur, allowing non-destructive collaboration on shared pages.
  • Double-guard handoffs guarantee exclusive claiming of transferred work items, ensuring state consistency during inter-session workflows.

Frequently Asked Questions

How does ai-memory prevent two users from overwriting each other's changes?

The system routes all write operations through a single-writer SQLite actor (WriterHandle in crates/ai-memory-store/src/writer.rs) that processes mutations sequentially via an mpsc channel. Additionally, instead of overwriting existing content, the storage layer creates supersession chains in crates/ai-memory-store/src/page.rs, linking new versions to previous ones rather than destroying them. This combination ensures that concurrent edits result in versioned history rather than data loss.

Can different users work on different projects within the same workspace simultaneously?

Yes. The ActiveProjectMode::PerActor implementation in crates/ai-memory-core/src/active_project.rs maintains separate active-project pointers for each session. When User A sets Project X as active and User B sets Project Y as active, the ActiveProject resolver ensures that each subsequent command targets the correct project context without requiring explicit project identifiers in every request.

What happens to session state if the server restarts?

Session state is transient and tied to the active connection, but persistent user identity and project data remain in the SQLite database. Upon reconnection, clients provide their authentication tokens via crates/ai-memory-mcp/src/auth.rs, which re-establishes the User context. The client must then re-select their active project, as the per-actor pointer is maintained in memory during the session lifetime.

How are permissions enforced across multi-user access?

The ScopeResolver utilities in crates/ai-memory-mcp/src/actor.rs validate the (workspace_id, project_id, path) triple against the authenticated user's permissions before executing any read or write operation. This check occurs at the actor boundary, ensuring that users cannot access projects or workspaces outside their authorized scope regardless of the request parameters they provide.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →