How ai-memory Keys Data Entries Using the (workspace_id, project_id, path) Tuple

ai-memory treats every stored object as uniquely identifiable by the composite triple (WorkspaceId, ProjectId, PagePath), enforcing strict data isolation and consistent addressing across its SQLite store and filesystem-backed wiki.

The akitaonrails/ai-memory repository implements a robust identity system where the (workspace_id, project_id, path) tuple serves as the universal primary key for all persistent data. This three-part identifier guarantees that pages, observations, and handoffs remain strictly scoped to their respective workspaces and projects while enabling seamless cross-component lookups.

Deconstructing the Composite Key Components

In crates/ai-memory-core/src/ids.rs, the system defines three distinct newtypes that combine to form the universal addressing scheme.

WorkspaceId (UUID v7)

The WorkspaceId is a UUID version 7 that uniquely identifies a workspace. Generated via WorkspaceId::new(), this identifier serves as the top-level namespace for all data, ensuring complete isolation between different workspaces.

ProjectId (UUID v7)

Nested within a workspace, the ProjectId—also a UUID v7 defined in ids.rs—scopes data to a specific project. When combined with WorkspaceId, it creates a two-level hierarchy that prevents collisions between identically named paths in different projects.

PagePath (Normalized POSIX Path)

The PagePath newtype represents a normalized, POSIX-style relative path. It enforces strict validation: no leading slashes, no backslashes, and no . or .. components. This ensures portable, deterministic addressing across operating systems.

Database Schema and Primary Key Constraints

All SQLite tables in the store layer—such as pages, observations, and handovers—use the composite (workspace_id, project_id, path) as their primary key. As implemented in the store crate, each row contains explicit fields:

  • workspace_id: WorkspaceId
  • project_id: ProjectId
  • path: PagePath

This schema guarantees that data from different workspaces or projects never collides, supporting multi-user concurrency by ensuring every row is uniquely addressable. The domain model in crates/ai-memory-core/src/page.rs embeds this triple directly, maintaining alignment between business logic and storage layers.

Filesystem Layout and Path Resolution

The repository mirrors the database addressing scheme on disk. In crates/ai-memory-wiki/src/wiki.rs, the abs_path helper constructs absolute filesystem locations using the exact same triple:


<wiki_root>/<workspace_id>/<project_id>/<page_path>

Every write and read operation routes through this helper, ensuring consistency between the SQLite store and the on-disk representation. The PagePath normalization rules guarantee that the filesystem structure remains portable and unambiguous across platforms.

Practical Implementation Examples

The following Rust code demonstrates how to construct and use the composite key:

use ai_memory_core::ids::{WorkspaceId, ProjectId, PagePath};

// Create a fresh workspace and project.
let ws_id = WorkspaceId::new();          // e.g. "018d0c5b-8c9f-7e8a-..."
let proj_id = ProjectId::new();          // e.g. "018d0c5c-1a2b-7e8b-..."

// Build a page path (must be portable).
let path = PagePath::new("notes/meeting.md")?;

// The complete key used by the store and wiki.
let key = (ws_id, proj_id, path);

// Example: writing a page via the wiki API.
wiki.write_page(ws_id, proj_id, &path, "Page contents".into()).await?;

To retrieve data from the store using this identifier:

// Querying a page from the store (pseudo-code).
let page = store
    .latest_page_id_by_ids(ws_id, proj_id, path.as_str().to_string())
    .await?;

Benefits of the Triple Key Design

Centralizing identity in the (workspace_id, project_id, path) tuple provides three critical advantages:

  • Strict Data Isolation: Workspaces and projects are completely segregated at the database and filesystem levels, preventing accidental data leakage between tenants.
  • Multi-user Concurrency: The composite primary key eliminates race conditions by ensuring every write targets a unique, unambiguous row.
  • Cross-Component Consistency: Whether addressing entities via the store, wiki, or MCP routes, all subsystems use the identical triple, simplifying scope resolution logic throughout the codebase.

Summary

  • The (workspace_id, project_id, path) tuple functions as the universal primary key across all ai-memory data stores.
  • WorkspaceId and ProjectId are UUID v7 types defined in crates/ai-memory-core/src/ids.rs, while PagePath enforces normalized POSIX conventions.
  • SQLite tables use this composite key to prevent collisions, and the abs_path helper in crates/ai-memory-wiki/src/wiki.rs mirrors the structure on disk.
  • This design enables strict tenant isolation, safe concurrent access, and consistent addressing between the database and filesystem layers.

Frequently Asked Questions

How does ai-memory prevent path traversal attacks?

The PagePath newtype in crates/ai-memory-core/src/ids.rs validates all inputs to reject paths containing .. components, leading slashes, or backslashes. This ensures that malicious paths like ../../../etc/passwd are rejected before reaching the filesystem, keeping data confined to the intended <wiki_root>/<workspace_id>/<project_id>/ directory.

Can two projects in different workspaces have identical page paths?

Yes. Because the primary key includes workspace_id, project_id, and path, the database treats (workspace_a, project_1, "readme.md") and (workspace_b, project_2, "readme.md") as distinct rows. This allows complete namespace independence between workspaces.

What happens if I try to create a PagePath with a leading slash?

The PagePath::new() constructor returns an error. The normalization rules require relative paths without leading slashes to ensure portability and to maintain the invariant that the on-disk layout matches the database key structure exactly.

Is the composite key used for entity types beyond wiki pages?

Yes. According to the source code, tables for observations, handovers, and other entities in the store layer all include the workspace_id, project_id, and path fields. This uniform addressing scheme allows any subsystem to locate related metadata using the same triple that identifies the primary page.

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 →