# Data Model for Workspaces, Projects, and Page Hierarchies in ai-memory Wiki

> Discover the ai-memory wiki data model for workspaces projects and page hierarchies. Learn how SQLite and foreign-key constraints ensure referential integrity in this three-tier structure.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: data-model
- Published: 2026-08-20

---

**The ai-memory wiki implements a strictly-typed three-tier hierarchy in SQLite where Workspaces contain Projects, Projects contain Pages, and foreign-key constraints enforce referential integrity across all entities.**

The ai-memory repository organizes wiki content through a hierarchical data model designed to prevent identifier confusion and maintain data consistency. This Rust-based system uses new-type wrappers for database keys and strict relational constraints to ensure every Page belongs to a valid Project, and every Project belongs to exactly one Workspace.

## Core Entities in the ai-memory Wiki Data Model

The hierarchy consists of three distinct entities, each represented by strongly-typed structs and corresponding database tables.

### Workspace (Top-Level Container)

A **Workspace** serves as the root container that groups related projects across the entire installation. Defined in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) within the `WorkspaceScopeRow` and `WorkspaceSummary` structs, each workspace carries a unique `workspace_id: i64` (wrapped as `WorkspaceId`), along with `name`, `created_at`, and `updated_at` fields. The workspace name must be unique across the system, enforced by a unique index in the database migration [`V18__enforce_project_workspace_pairing.sql`](https://github.com/akitaonrails/ai-memory/blob/main/V18__enforce_project_workspace_pairing.sql).

### Project (Logical Grouping)

A **Project** belongs to exactly one workspace and acts as the logical grouping for documentation collections or codebases. The `ProjectSummary` struct in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) (lines 897-915) defines the fields: `project_id: i64` (wrapped as `ProjectId`), `workspace_id`, `name`, `slug`, and timestamps. The migration enforces a `UNIQUE (project_id, workspace_id)` constraint to prevent a project from being associated with multiple workspaces simultaneously.

### Page (Hierarchical Content)

**Pages** represent the leaf nodes of the hierarchy, storing markdown content and metadata under a specific project. Key structs include `PageSummary` (lines 955-970), `PageMeta` (lines 992-1009), and `ObservationPage` (lines 616-632), all defined in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs). Each page contains:

- `page_id: i64`
- `project_id` (foreign key to the parent project)
- `path: PagePath` (new-type wrapping a slash-separated string like [`docs/architecture/overview.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/architecture/overview.md))
- `body_hash`, `author`, and timestamps

The `PagePath` type encodes the hierarchical location within the project, allowing nested directory structures without separate folder entities.

## Typed Identifiers and Database Schema

All identifiers utilize strongly-typed new-type structs defined in [`crates/ai-memory-core/src/ids.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/ids.rs) to prevent accidental mix-ups at compile time:

- **`WorkspaceId`** (lines 31-38): Wraps an `i64`
- **`ProjectId`** (lines 44-51): Wraps an `i64`
- **`PagePath`** (lines 101-108): Wraps a `String` storing slash-separated paths

The database schema in [`crates/ai-memory-store/migrations/V18__enforce_project_workspace_pairing.sql`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/migrations/V18__enforce_project_workspace_pairing.sql) establishes foreign-key constraints ensuring every `project_id` references a valid workspace, and every page references a valid project. This migration also creates the unique indexes necessary for the workspace-project pairing integrity.

## Implementing the Hierarchy in Code

The following Rust code demonstrates creating a workspace, adding a project, writing a page, and reading it back using the ai-memory store and wiki APIs:

```rust
// 1️⃣ Create a new workspace
let ws = ai_memory_store::ops::CreateWorkspace {
    name: "my-workspace".into(),
    description: None,
}.run(&mut store)?;

// 2️⃣ Create a project inside that workspace
let proj = ai_memory_store::ops::CreateProject {
    workspace_id: ws.id,
    name: "my-project".into(),
    slug: "my-project".into(),
    description: None,
}.run(&mut store)?;

// 3️⃣ Write a page into the project
let write_req = ai_memory_wiki::WritePageRequest {
    project_id: proj.id,
    path: PagePath::new("docs/introduction.md".into()),
    body: "## Introduction\nWelcome to the project".into(),

    author: "alice".into(),
    metadata: Default::default(),
};
let resp = ai_memory_wiki::Wiki::write_page(&wiki, write_req)?;

// 4️⃣ Read the page back
let read = ai_memory_store::reader::ReadPageArgs {
    project_id: proj.id,
    path: PagePath::new("docs/introduction.md".into()),
    include_body: true,
};
let page = ai_memory_store::reader::read_page(&store, read)?;
println!("Page body:\n{}", page.body);

```

Administrative operations such as renaming, deleting, or merging workspaces and projects are handled through the MCP protocol implementations in [`crates/ai-memory-mcp/src/admin.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/admin.rs).

## Summary

- The ai-memory wiki uses a three-tier hierarchy: **Workspace → Project → Page**, stored in a single SQLite database.
- **Strongly-typed identifiers** (`WorkspaceId`, `ProjectId`, `PagePath`) in [`crates/ai-memory-core/src/ids.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/ids.rs) prevent compile-time errors when traversing relationships.
- **Foreign-key constraints** in migration [`V18__enforce_project_workspace_pairing.sql`](https://github.com/akitaonrails/ai-memory/blob/main/V18__enforce_project_workspace_pairing.sql) enforce that every project belongs to exactly one workspace and every page belongs to exactly one project.
- **Read operations** use structs like `WorkspaceSummary`, `ProjectSummary`, and `PageSummary` defined in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs).
- **Write operations** utilize `WritePageRequest` from [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs) to store markdown content with hierarchical paths.

## Frequently Asked Questions

### How are workspaces and projects related in the ai-memory wiki?

Each project must belong to exactly one workspace, enforced by the `workspace_id` foreign key in the projects table and a unique constraint preventing cross-workspace duplication. This relationship is defined in the [`V18__enforce_project_workspace_pairing.sql`](https://github.com/akitaonrails/ai-memory/blob/main/V18__enforce_project_workspace_pairing.sql) migration and mirrored in the `ProjectSummary` struct.

### What is the PagePath type used for in ai-memory?

**PagePath** is a new-type struct wrapping a `String` that stores slash-separated paths (e.g., [`docs/architecture/overview.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/architecture/overview.md)) to represent the hierarchical location of a page within its parent project. This eliminates the need for separate folder entities while maintaining logical organization.

### How does ai-memory enforce referential integrity between entities?

The system uses SQLite foreign-key constraints defined in the database migrations to ensure every `project_id` references a valid workspace and every page references a valid project. Additionally, the Rust API uses strongly-typed identifiers (`WorkspaceId`, `ProjectId`) to prevent accidental ID swaps at compile time.

### Where are the data structures for reading pages defined?

Page-related structs such as `PageSummary`, `PageMeta`, and `ObservationPage` are defined in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) at lines 955-970, 992-1009, and 616-632 respectively. These structs mirror the database schema and provide type-safe interfaces for querying page content and metadata.