OpenWork Memory Bank Architecture: How Persistent Context Works Across Sessions

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 Agents discover capabilities via search_capabilities and invoke execute_capability
API (den-api) 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 MySQL/Drizzle schema with memory and memory_context tables
Search backend docs/memory-bank-architecture.md MySQL FULLTEXT index for lexical search
Client UI 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.

// 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
// 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:

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.

// 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 file registers these operations for capability discovery, while 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 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, 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.

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 under the ## Memory Bank prompt section to never store secrets. Human verification in the draft stage provides additional filtering.

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 →