How ai-memory Implements Cross-Project Isolation in Rust
The ai-memory system guarantees strict cross-project isolation by embedding a typed 3-tuple identity—(workspace_id, project_id, path)—into every SQLite row and routing all storage operations through a mandatory ScopeResolver that validates workspace and project boundaries before any data access.
Cross-project isolation ensures that data from one workspace cannot leak into another, even when file paths collide. In the akitaonrails/ai-memory repository, this isolation is enforced at the architectural level through construction-time validation rather than runtime filtering. The design embeds project identity directly into the database schema and requires explicit scope resolution for every operation, making bypasses physically impossible.
The Typed 3-Tuple Identity
Every domain record in ai-memory stores a composite identity of (workspace_id, project_id, path) as documented in docs/ARCHITECTURE.md. This tuple forms the logical primary key component for rows in the SQLite store.
Unlike simple path-based storage systems, this three-part identifier ensures that two projects using identical directory structures remain strictly separated. The database schema enforces this constraint at the storage layer, making cross-project data leakage impossible through standard query patterns. Even if Project A and Project B both contain a file at docs/README.md, the composite key ensures they occupy distinct database rows.
ScopeResolver: The Gatekeeping Pattern
The ScopeResolver acts as the mandatory gatekeeper for all storage operations. Defined in crates/ai-memory-store/src/scope.rs, this component validates the caller's workspace and project credentials before returning a scoped handle to the store.
Resolving Existing Scopes for Reads
Read-only operations utilize lookup-existing-scope helpers. When a component calls resolve_existing_scope("my-workspace", "my-project"), the resolver validates that the requesting context possesses access rights to that specific project boundary. If validation fails, the operation aborts before any database interaction occurs, preventing information leakage through error messages or timing attacks.
Creating Explicit Scopes for Writes
Write operations require explicit scope creation through dedicated methods. This architectural distinction between read and write scope resolution prevents accidental mutations across project boundaries. According to the source in crates/ai-memory-store/src/scope.rs, the resolver never creates a new scope during read operations, eliminating an entire class of cross-project write errors.
Implementing Scope Resolution
Here is how the cross-project isolation pattern appears in production code:
// Resolve a scope for the current workspace / project
let resolver = ScopeResolver::new(&config)?;
let scope = resolver.resolve_existing_scope("my-workspace", "my-project")?;
// Use the scoped store – all queries will be automatically filtered
let rows = store
.query()
.with_scope(&scope)
.filter_by_path("notes/todo.md")
.run()?;
// Attempting to access a different project requires a different scope
let other_scope = resolver.resolve_existing_scope("other-workspace", "other-project")?;
assert_ne!(scope.id(), other_scope.id());
MCP Layer Enforcement
The Model Context Protocol (MCP) layer extends these isolation guarantees to external agents. In crates/ai-memory-mcp/src/server.rs, all incoming requests route through ai_memory_mcp::admin and ai_memory_mcp::actor modules before touching the store.
This RPC-style routing ensures that even programmatic access from external tools respects the same workspace and project boundaries. The MCP server instantiates a fresh ScopeResolver for each request context, preventing session confusion between different projects. Because the resolver validates scope before executing any store method, external agents cannot exploit the MCP interface to bypass project isolation.
Wiki Mutation Safety
The wiki subsystem demonstrates practical isolation enforcement through crates/ai-memory-wiki/src/lib.rs. Page mutations flow through Wiki::write_page and Wiki::apply_batch methods, both of which require a validated scope parameter.
// Writing a wiki page – the scope is injected automatically
let page = WikiPage::new("notes/ideas.md", "New ideas …");
wiki.write_page(&scope, page)?; // Persists only for the resolved project
Because the scope is injected at the API boundary, the wiki layer cannot accidentally persist data to an incorrect project, even if the calling code contains logic errors. The write_page method binds the content to the specific (workspace_id, project_id) pair encapsulated in the scope object.
Database-Level Guarantees
The SQLite backend enforces isolation at the storage layer through composite unique constraints on the (workspace_id, project_id, path) tuple. This schema design ensures that index lookups are automatically scoped to the requesting project and that foreign key relationships maintain referential integrity within project boundaries. The public façade in crates/ai-memory-store/src/lib.rs requires a Scope object for every operation, making unscoped queries syntactically impossible in the type system.
Summary
- Typed 3-tuple identity: Every row embeds
(workspace_id, project_id, path)to physically separate project data at the database level, as defined indocs/ARCHITECTURE.md. - ScopeResolver pattern: All operations route through
crates/ai-memory-store/src/scope.rsto validate project boundaries before storage access. - Explicit scope creation: Write operations require deliberate scope instantiation, preventing accidental cross-project mutations.
- MCP routing: External agent requests in
crates/ai-memory-mcp/src/server.rsenforce the same isolation contracts as internal APIs throughai_memory_mcp::adminandai_memory_mcp::actor. - Wiki safety: Methods like
Wiki::write_pageincrates/ai-memory-wiki/src/lib.rsbind storage operations to specific project scopes.
Frequently Asked Questions
How does ai-memory prevent accidental cross-project reads?
The ScopeResolver in crates/ai-memory-store/src/scope.rs provides lookup-existing-scope helpers that validate project membership before returning data. Read operations must specify an exact workspace and project tuple, and the resolver refuses to return a scope handle for projects outside the requesting context's permissions. This construction-time validation ensures that queries are automatically filtered by project identity without relying on developer discipline.
What is the ScopeResolver and where is it defined?
The ScopeResolver is the core isolation component defined in crates/ai-memory-store/src/scope.rs. It acts as a factory for Scope objects that encapsulate workspace and project identities. According to the architecture documentation, this resolver ensures that no store operation can execute without a properly validated scope, making it the single point of enforcement for cross-project isolation.
Can two projects use the same file paths without conflict?
Yes. Because the SQLite schema uses the composite key (workspace_id, project_id, path), two distinct projects can store files at identical logical paths without collision. The workspace_id and project_id components of the typed 3-tuple ensure that even if both projects contain a file named notes/todo.md, the database stores them as distinct rows with separate identities.
How does the MCP layer maintain isolation for external agents?
The MCP implementation in crates/ai-memory-mcp/src/server.rs routes all external requests through the same ai_memory_store::ScopeResolver used by internal components. Before processing any admin or actor command, the server resolves the requesting agent's workspace and project scope. This ensures that RPC-style calls from external tools, including AI agents, are bound to the same strict project boundaries as native API calls.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →