# OpenWork Memory Bank Architecture: How Persistent Context Works Across Sessions

> Discover OpenWork's memory bank architecture for persistent AI context. Learn how it stores and retrieves information across sessions using a durable REST/MCP contract and swappable storage.

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

---

**OpenWork implements a user-scoped "memory bank" that lets AI agents store and retrieve contextual information across sessions through a durable REST/MCP contract with swappable storage backends.**

The `different-ai/openwork` repository provides agents with long-term memory capabilities through a carefully layered architecture. This system balances immediate utility with future extensibility, allowing agents to recall prior work, decisions, and relevant snippets without requiring users to repeat context. The design separates interface from implementation, ensuring agents depend only on stable tool contracts while storage technologies can evolve.

## Core Architecture Layers

OpenWork's memory bank follows a five-layer design that isolates concerns from agent-facing tools down to database storage.

| Layer | Primary Files | Responsibility |
|-------|---------------|----------------|
| **Surface (MCP tools)** | [`ee/apps/den-api/src/openapi.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/openapi.ts) | Agents discover capabilities via `search_capabilities` and invoke `execute_capability` |
| **API (den-api)** | [`ee/apps/den-api/src/mcp/policy.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/mcp/policy.ts) | Exposes REST routes for CRUD and search operations |
| **Data model** | [`ee/packages/den-db/src/schema/memory.ts`](https://github.com/different-ai/openwork/blob/main/ee/packages/den-db/src/schema/memory.ts) | MySQL/Drizzle schema with `memory` and `memory_context` tables |
| **Search backend** | [`docs/memory-bank-architecture.md`](https://github.com/different-ai/openwork/blob/main/docs/memory-bank-architecture.md) | MySQL FULLTEXT index for lexical search |
| **Client UI** | [`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) | Desktop panel for memory management |

The operation IDs `postMemory` and `getMemorySearch` in the OpenAPI generator define the agent-facing interface. These map to REST endpoints that remain stable even as underlying implementations change.

## Data Model and Schema Design

The persistence layer uses two related tables with custom type IDs for type safety.

```typescript
// From ee/packages/den-db/src/schema/memory.ts
// Primary memory record
memory: {
  id: typeid('memory'),      // Custom type-safe identifier
  user_id: string,            // Owner scoping
  scope: 'user' | 'org',      // Future org-wide support
  content: text,              // Main memory content
  tags: json,                 // Structured categorization
  created_at: timestamp,
  updated_at: timestamp,
}

// Citations and source context
memory_context: {
  id: typeid('memctx'),
  memory_id: reference,       // FK to parent memory
  snippet: text,              // Excerpt from source
  conversation_id: string,    // Provenance tracking
  message_id: string,
  origin: string,             // e.g., 'active_conversation'
}

```

The `typeid` columns (`memory`, `memctx`) provide collision-resistant, type-safe identifiers. The `scope` column currently forces `'user'` in v0 but anticipates organization-wide memories in future releases.

## Persistence Flow: How Memories Are Stored

The write path enforces human verification and transactional integrity.

1. **Draft** — Agent composes candidate memory with `content`, optional `tags`, and optional `contexts` array
2. **Human verification** — User reviews and edits (chat-driven baseline workflow)
3. **Persist** — `POST /v1/memory` executes atomically:
   - Inserts `memory` row
   - Inserts associated `memory_context` rows
   - Server overwrites `source` field
   - Forces `scope='user'`
4. **Feedback** — API returns created memory ID; client displays success/failure toast

```typescript
// Client-side save operation
await fetch('https://api.openworklabs.com/v1/memory', {
  method: 'POST',
  headers: { 
    'Content-Type': 'application/json', 
    Authorization: `Bearer ${token}` 
  },
  body: JSON.stringify({
    content: 'Deployed microservice via den-worker-proxy in a Daytona sandbox',
    tags: ['deploy', 'infra'],
    contexts: [
      {
        snippet: '...excerpt from chat...',
        conversation_id: 'c123',
        message_id: 'm456',
        origin: 'active_conversation',
      },
    ],
  }),
});

```

## Retrieval Flow: Lexical Search Implementation

OpenWork v0 implements **explicit lexical search only** — no automatic memory injection.

The agent calls `GET /v1/memory/search?q=<query>&limit=<n>` (default limit: 20). The backend executes:

```sql
MATCH(content) AGAINST(${q} IN NATURAL LANGUAGE MODE)

```

Combined with `user_id` filtering, this returns relevance-ranked results. Empty result sets return HTTP 200 with an empty array, allowing graceful "nothing found" handling.

```typescript
// Agent-side search operation
const resp = await fetch(
  `https://api.openworklabs.com/v1/memory/search?q=daytona+sandbox&limit=5`,
  { headers: { Authorization: `Bearer ${token}` } }
);
const { results } = await resp.json(); 
// results: [{id, content, score, created_at, …}]

```

## API Routes and MCP Integration

The `den-api` application exposes four memory routes, all tagged `"Memory"` in MCP policy:

- `POST /v1/memory` — Create new memory
- `GET /v1/memory/search` — Lexical search
- `GET /v1/memory` — List user's memories
- `DELETE /v1/memory/:id` — Remove specific memory

The [`ee/apps/den-api/src/mcp/policy.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/mcp/policy.ts) file registers these operations for capability discovery, while [`ee/apps/den-api/src/openapi.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/openapi.ts) generates the operation IDs that agents reference.

## Security and Privacy Controls

Multiple safeguards protect memory data:

- **Owner-scoped access** — All queries filter by `user_id = principal.userId`; non-owned IDs return 404 on deletion attempts
- **Plaintext storage** — v0 stores content unencrypted with explicit agent instruction: **never store secrets**
- **Input validation** — Bounds on content length and context count prevent abuse
- **Rate limiting** — Deferred to post-GA implementation

The architecture document at [`docs/memory-bank-architecture.md`](https://github.com/different-ai/openwork/blob/main/docs/memory-bank-architecture.md) specifies encryption as a pre-GA requirement.

## Client Interface and Feature Gating

The desktop UI lives 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), providing:

- Memory list with content preview
- Delete action with optimistic update
- Undo toast for deletion recovery
- Safe content rendering (HTML-escaped)

Access is controlled by `featureFlags.memory` per-device toggle.

## Extensibility and Future Directions

The contract-first design enables several evolution paths:

| Future Capability | Implementation Path |
|-------------------|---------------------|
| **Semantic search** | Replace MySQL FULLTEXT with vector database; MCP tools unchanged |
| **Organization scope** | Activate `'org'` value in `scope` column |
| **Encryption at rest** | Transparent layer below Drizzle ORM |
| **Automatic memory injection** | New MCP tool for relevance-ranked retrieval without explicit query |

Agents depend only on `postMemory` and `getMemorySearch` contracts, insulating them from backend changes.

## Summary

- OpenWork's memory bank architecture provides **user-scoped persistent context** through stable MCP tool contracts
- **Two-table schema** (`memory`, `memory_context`) with custom type IDs supports rich provenance tracking
- **MySQL FULLTEXT** powers v0 lexical search with natural relevance ranking
- **Transactional writes** ensure consistency between memory records and their contexts
- **Owner-scoped security** with explicit no-secrets policy protects against data exposure
- **Contract-first design** enables backend replacement (vector search, new storage) without agent changes

## Frequently Asked Questions

### How does OpenWork's memory bank differ from other AI context systems?

Most AI assistants rely on conversation window limits or expensive context stuffing. OpenWork's memory bank uses an explicit opt-in model where agents suggest memories, humans verify them, and retrieval requires intentional search queries. This prevents context pollution and gives users control over what persists.

### Can memories be shared across a team or organization?

The schema supports this via the `scope` column, which accepts `'user'` or `'org'` values. However, v0 forces `'user'` scope. Organization-wide memories are a documented future capability that will activate without changing agent-facing APIs.

### Why does v0 use lexical search instead of semantic/vector search?

Lexical search with MySQL FULLTEXT provides immediate functionality without additional infrastructure. The architecture explicitly separates the search backend as replaceable — future versions can implement vector similarity without breaking existing agent integrations that depend on `getMemorySearch`.

### What prevents agents from storing sensitive information in memories?

Two mechanisms: technical and procedural. Technically, v0 stores plaintext with planned pre-GA encryption. Procedurally, agents receive explicit instructions in [`apps/server/src/openwork-runtime-config.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-runtime-config.ts) under the `## Memory Bank` prompt section to never store secrets. Human verification in the draft stage provides additional filtering.