# Architectural Design of the Memory Bank System in OpenWork: MCP Tools and MySQL Schema Explained

> Explore the OpenWork memory bank system architecture. Discover its per-user persistence layer, MCP tools, and flexible MySQL/Drizzle backend with FULLTEXT search.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: architecture
- Published: 2026-07-31

---

**The OpenWork memory bank system implements a per-user persistence layer that exposes stable MCP tools for saving and searching memories while using a flexible MySQL/Drizzle backend with FULLTEXT search capabilities.**

The `different-ai/openwork` repository implements a sophisticated memory persistence layer that allows both AI agents and human users to store and retrieve contextual information across sessions. This article examines the architectural design of the memory bank system in OpenWork, detailing how the platform balances a stable external API contract with a pluggable internal implementation.

## High-Level Architecture: Stable Contracts Over Flexible Storage

The system deliberately separates the public API surface from the storage implementation. Agents interact with memory functions through Model Context Protocol (MCP) tools, while the backend uses MySQL (PlanetScale) with Drizzle ORM for persistence.

### Public Contract Layer

The memory bank exposes four primary MCP tools: `postMemory`, `getMemorySearch`, `getMemory`, and `deleteMemoryById`. These tools are discovered dynamically through `search_capabilities` and invoked via `execute_capability`. According to the source in [`ee/apps/den-api/src/routes/memory/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/memory/core.ts), tool names are not hardcoded but generated from route definitions using `buildOperationId`, ensuring the contract remains stable even if underlying implementations change.

### Internal Implementation Layer

Behind the MCP interface, the system persists data in MySQL using Drizzle ORM. The architecture supports future migration to vector databases or organization-wide memory banks without altering the tool contracts exposed to agents.

## Database Schema and Persistence Model

The storage layer centers on two related tables defined in [`ee/packages/den-db/src/schema/memory.ts`](https://github.com/different-ai/openwork/blob/main/ee/packages/den-db/src/schema/memory.ts).

### Core Tables

The `memory` table stores the primary record with fields for `id`, `user_id`, `org_id`, `scope`, `content`, `source`, `tags`, and timestamps. The `memory_context` table handles optional citations through fields like `citation`, `snippet`, and `origin`, linked via `memory_id`.

### Search Indexing

To enable natural-language queries, the schema creates a `FULLTEXT` index on the `memory.content` column. The `ensureMemoryFulltextIndex` routine handles this idempotently during both bootstrap and migration operations, ensuring the lexical search capability is always available.

## API Routes and MCP Tool Implementation

The route handlers in [`ee/apps/den-api/src/routes/memory/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/memory/core.ts) implement the CRUD operations while enforcing strict user scoping.

### Route Definitions

The API provides four endpoints:

- `POST /v1/memory` → `postMemory` (creates memories, forces `scope='user'`)
- `GET /v1/memory/search` → `getMemorySearch` (FULLTEXT search using `MATCH … AGAINST` in natural language mode)
- `GET /v1/memory` → `getMemory` (lists user memories)
- `DELETE /v1/memory/:id` → `deleteMemoryById` (hard delete with cascade to `memory_context`)

### Relevance Scoring

The [`shared.ts`](https://github.com/different-ai/openwork/blob/main/shared.ts) utility file defines `memoryRelevance`, which constructs the SQL expression for calculating match scores. This separation of concerns allows the search algorithm to evolve without modifying route handlers.

### User Isolation and Security

All routes automatically filter by `user_id` extracted from the authentication token. If an agent attempts to access another user's memory ID, the system returns a **404** error, preventing information leakage through ID enumeration.

## Saving and Retrieving Memories: Code Examples

### Creating a Memory

Agents or UI clients POST to the memory endpoint:

```javascript
await fetch("/v1/memory", {
  method: "POST",
  headers: { 
    "Content-Type": "application/json", 
    Authorization: "Bearer <token>" 
  },
  body: JSON.stringify({
    content: "Deployed version 1.2 to production on 2026-07-20",
    tags: ["deployment", "prod"],
    contexts: [
      {
        snippet: "Deploy script output ...",
        conversation_id: "conv_123",
        message_id: "msg_456",
        origin: "active_conversation"
      }
    ]
  })
});

```

The server overrides any client-provided `scope` value to `'user'`, ensuring strict ownership boundaries.

### Natural Language Search

Retrieving memories uses the FULLTEXT index via the `getMemorySearch` tool:

```javascript
const resp = await fetch(
  "/v1/memory/search?q=deployment+prod&limit=5", 
  { headers: { Authorization: "Bearer <token>" } }
);
const { results } = await resp.json();
console.log(results.map(r => ({ 
  id: r.id, 
  content: r.content, 
  score: r.score 
})));

```

Results include a relevance `score` calculated by the `memoryRelevance` utility and are automatically scoped to the authenticated user.

### Memory Management

Listing and deletion follow standard REST patterns:

```javascript
// List all memories
const { memories } = await (
  await fetch("/v1/memory", { 
    headers: { Authorization: "Bearer <token>" } 
  })
).json();

// Delete with cascade to memory_context
await fetch(`/v1/memory/${memoryId}`, {
  method: "DELETE",
  headers: { Authorization: "Bearer <token>" }
});

```

Deletion cascades to associated `memory_context` rows, supporting right-to-be-forgotten requirements.

## Client Interface and Feature Flagging

The desktop application provides a thin React interface for memory management, implemented in [`apps/app/src/react-app/domains/settings/pages/memory-view.tsx`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/domains/settings/pages/memory-view.tsx).

### UI Components

The interface displays the user's memory list with optimistic deletion (undo toast support) and a copy-prompt button. All components use Base UI primitives from `shadcn/ui` for consistency.

### Feature Gating

Access to the memory bank UI is controlled by the `featureFlags.memory` flag, allowing gradual rollouts or organization-specific enablement.

## Future-Proofing and Extensibility

The architectural design of the memory bank system in OpenWork anticipates several evolution paths without breaking the MCP tool contract.

### Scope Expansion

The `scope` column in the memory table already supports an `'org'` value for future organization-wide memory banks, though the current server implementation enforces `'user'` for privacy.

### Search Backend Migration

Because the MCP tool names (`getMemorySearch`, etc.) are abstracted from the underlying query implementation, the system can swap MySQL FULLTEXT for vector embeddings or dedicated search engines without requiring agent code changes.

## Summary

- **OpenWork's memory bank** uses a per-user storage model with strict server-side scoping enforced in [`ee/apps/den-api/src/routes/memory/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/memory/core.ts).
- **MCP tools** provide a stable contract for agents, with operation IDs generated dynamically from route definitions rather than hardcoded mappings.
- **MySQL and Drizzle ORM** handle persistence, utilizing a `FULLTEXT` index on `memory.content` for natural language search via `MATCH … AGAINST` queries.
- **Security** relies on user ID filtering on all routes, returning 404 for unauthorized access attempts to prevent data leakage.
- **Future enhancements** like vector search or organization-wide scopes can be added without changing the public API surface.

## Frequently Asked Questions

### How does OpenWork ensure memories remain private to individual users?

The system enforces user isolation at the API layer. In [`ee/apps/den-api/src/routes/memory/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/memory/core.ts), every route handler automatically filters queries by the authenticated `user_id` extracted from the JWT token. The server explicitly overwrites any client-provided `scope` parameter to `'user'`, and cross-user access attempts return a 404 status to prevent ID enumeration attacks.

### What database technology powers the memory bank search functionality?

The current implementation uses MySQL (specifically PlanetScale) with a `FULLTEXT` index on the `content` column of the `memory` table. The `memoryRelevance` utility in [`shared.ts`](https://github.com/different-ai/openwork/blob/main/shared.ts) constructs SQL `MATCH … AGAINST` expressions in natural language mode. However, the MCP tool contract is designed to allow swapping this for vector-based search in future versions without breaking existing agent integrations.

### Can agents directly invoke memory operations, or do they require human approval?

Agents discover memory capabilities through `search_capabilities` and invoke them via `execute_capability` using the MCP protocol. While the system architecture supports automated agent writes, the current implementation treats these as standard API calls. The architecture documentation suggests that agents are instructed not to persist secrets, with content stored as plaintext, implying human verification is recommended for sensitive operations.

### How does the memory bank handle deletion and data retention?

The `deleteMemoryById` endpoint performs hard deletes on the `memory` table with cascading deletes to associated `memory_context` rows. This is implemented in [`ee/apps/den-api/src/routes/memory/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/memory/core.ts) to support right-to-be-forgotten requirements. The desktop UI in [`memory-view.tsx`](https://github.com/different-ai/openwork/blob/main/memory-view.tsx) provides optimistic deletion with undo functionality, though the server operation is permanent once confirmed.