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.
- Draft — Agent composes candidate memory with
content, optionaltags, and optionalcontextsarray - Human verification — User reviews and edits (chat-driven baseline workflow)
- Persist —
POST /v1/memoryexecutes atomically:- Inserts
memoryrow - Inserts associated
memory_contextrows - Server overwrites
sourcefield - Forces
scope='user'
- Inserts
- 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 memoryGET /v1/memory/search— Lexical searchGET /v1/memory— List user's memoriesDELETE /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.
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 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →