SqliteMemoryStore over the same SQLite file, so a fact saved here is searchable from a runtime’s Memory tool and vice versa.
Memory is always on; these routes are not flag-gated. The store is FTS5 (full-text) plus an optional vector index. Vector and hybrid search require a reachable embedding provider; when none resolves, they degrade to FTS automatically. See
GET /api/memory/provider to inspect the active provider.POST /api/memory) reads a JSON body parsed by express.json({ limit: '2mb' }). The two GET routes read their inputs from the query string, then validate them against the same zod schemas the save body uses, so an out-of-range limit or empty query is a 400.
Routes
Save scrubs secrets at the write boundary: a fact’s
title/content and a procedure’s content are passed through a secret scrubber before they are embedded and inserted. A credential can never land in a durable, searchable, or auto-injectable fact regardless of who wrote it.GET /api/memory
Searches stored facts. The handler reads query, mode, limit, teamId, and agentId from the query string, assembles a { query, mode, limit, scope: { teamId, agentId } } object, and validates it with the same schema the MCP memory_search tool uses. Each result is a fact annotated with a 0..1 score and a matchedVia field recording how it matched.
- Query params
- Request body: none.
Scope is inclusive. A scoped query (a
teamId and/or agentId) also sees globally-scoped facts (rows with a null scope), so global memory is always visible to a scoped search. A tenantId scope, if supplied, is strict; but tenantId is a dormant multi-tenant seam (a single implicit tenant today).Responses
200 OK: the (possibly empty) result list:
matchedVia reflects the mode that actually ran, not the mode requested: a vector/hybrid request with no embedding provider runs as fts and reports matchedVia: 'fts'. In hybrid mode the score blends the cosine similarity (60%) and an FTS-hit signal (40%).
400 Bad Request: the assembled query object failed validation (e.g. blank query, limit out of 1..100, or an unknown mode):
500 Internal Server Error: any failure constructing the store or running the search:
Example
POST /api/memory
Saves a memory entry. The body is a discriminated union: a fact (the default, when kind is absent or "fact") or a procedure (kind: "procedure"). A fact is a durable declarative statement (“User prefers concise responses”); a procedure is a versioned, SKILL-style “how” kept out of the fact store. Saving a procedure under a name+scope that already exists creates a new version (the prior max version +1) rather than overwriting.
- Path/query params: none.
- Request body: one of:
Responses
200 OK: a fact was saved (it is embedded if a provider was available, but the embedding is best-effort and never blocks the write):
200 OK: a procedure was saved (kind: 'procedure'):
400 Bad Request: the body failed the discriminated-union validation (e.g. an over-length title/content, too many tags, or a procedure missing name):
500 Internal Server Error: any failure constructing the store or writing the row:
Example
GET /api/memory/browse
Lists the most recent facts and procedures (facts newest-first by updatedAt), scoped the same inclusive way as search. The handler reads limit, teamId, and agentId from the query string, validates them, then fetches facts and procedures in parallel.
- Query params
- Request body: none.
Responses
200 OK: facts and procedures side by side:
400 Bad Request: limit out of the 1..200 range:
500 Internal Server Error: any failure constructing the store or reading:
Example
GET /api/memory/provider
Reports the embedding provider that backs vector/hybrid search, resolved once at boot (a one-time network probe) and reused. The resolution order is: a reachable Ollama instance (the offline-first default, probed at http://localhost:11434), then an OpenAI key (OPENAI_API_KEY), then null. A null provider means the store is FTS-only; vector and hybrid search silently fall back to FTS. The response shape is provider-independent ({ id, dimensions }) so the UI can warn when vector search is degraded.
- Path/query params: none.
- Request body: none.
Responses
200 OK: a provider resolved:
200 OK: no provider reachable (FTS-only):
500 Internal Server Error: an unexpected failure resolving the provider:
Example
Error envelope
Errors on these routes use the standard envelope{ error: string }. The two validating GET routes (/api/memory, /api/memory/browse) and the save route add a details field carrying the zod flatten() output on a 400, e.g. { "error": "invalid query", "details": { … } }.
See also
- Memory (concept), the shared tier, FTS5 + vector, scope inclusivity, scrub-on-write
- Memory browser (UI), search/save/browse from the dashboard
- Tools & MCP API, the Memory MCP server (the model-facing half), attach config, transports
- @clawboo/db,
SqliteMemoryStore, theMemoryStore/EmbeddingProviderseams, the memory schemas - REST API overview