Memory
Store and retrieve memories in the Memory Library. Memories are scoped (user-global, agent, app, or workflow), kind-typed (fact / episodic / semantic / instruction / preference), versioned, and bitemporal (validFrom / validUntil). Search blends BM25 + dense vectors via RRF with temporal decay; ingest runs a Mem0-style LLM judge that decides ADD / UPDATE / DELETE / NOOP against retrieved candidates.
All endpoints require authentication via X-API-Key header and the appropriate scope.
Memory Object
{
"id": "mem_abc123",
"kind": "fact",
"content": "User prefers TypeScript over JavaScript for new projects.",
"summary": "Prefers TypeScript",
"tags": ["preference", "languages"],
"category": "engineering",
"source": "chat",
"scope": {
"agentId": null,
"appId": null,
"workflowId": null
},
"importance": 0.7,
"confidence": 0.9,
"decayHalfLifeDays": 90,
"usageCount": 4,
"lastAccessedAt": "2026-05-10T12:00:00Z",
"eventTime": "2026-05-01T09:00:00Z",
"validFrom": "2026-05-01T09:00:00Z",
"validUntil": null,
"sourceThreadId": "thr_789",
"sourceMessageId": "msg_456",
"sourceToolName": null,
"links": [
{ "targetId": "mem_def456", "relation": "supports", "weight": 0.8 }
],
"contradictionOf": null,
"qualityScore": 0.82,
"isPublic": false,
"ownerId": "user_456",
"createdAt": "2026-05-01T09:00:00Z",
"updatedAt": "2026-05-10T12:00:00Z"
}
GET /api/v1/memory
List memories with pagination, kind/tag/search filters, scope filters, and bitemporal asOf selection.
Scope: memory:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | string | No | Filter by kind: fact, episodic, semantic, instruction, preference |
tags | string | No | Comma-separated tag list (matches any) |
search | string | No | Full-text search on content, summary, and tags |
limit | integer | No | Max results (default 50, max 200) |
offset | integer | No | Pagination offset |
agentId | string | No | Narrow to memories scoped to this agent |
appId | string | No | Narrow to memories scoped to this app instance |
workflowId | string | No | Narrow to memories scoped to this workflow |
asOf | string | No | ISO timestamp — return only memories valid at this instant (bitemporal) |
includeInvalidated | boolean | No | Include rows whose validUntil is in the past |
Response 200 OK
{
"count": 25,
"limit": 50,
"offset": 0,
"memories": [ /* Memory objects */ ]
}
POST /api/v1/memory
Create a new memory.
Scope: memory:write
Request Body
{
"kind": "fact",
"content": "User prefers TypeScript over JavaScript for new projects.",
"summary": "Prefers TypeScript",
"tags": ["preference", "languages"],
"category": "engineering",
"source": "chat",
"scope": { "agentId": null, "appId": null, "workflowId": null },
"importance": 0.7,
"confidence": 0.9,
"decayHalfLifeDays": 90,
"eventTime": "2026-05-01T09:00:00Z",
"validFrom": "2026-05-01T09:00:00Z",
"validUntil": null,
"sourceThreadId": "thr_789",
"sourceMessageId": "msg_456",
"sourceToolName": null,
"links": []
}
| Field | Type | Required | Description |
|---|---|---|---|
kind | string | Yes | fact / episodic / semantic / instruction / preference |
content | string | Yes | Memory body |
summary | string | No | One-line summary |
tags | string[] | No | Discoverability tags |
category | string | No | Free-form category |
source | string | No | Provenance label (e.g. chat, import, agent) |
scope | object | No | {agentId?, appId?, workflowId?}; absence = user-global. Top-level agentId / appId / workflowId are also accepted. |
importance | number | No | 0..1 retention weight |
confidence | number | No | 0..1 truth confidence |
decayHalfLifeDays | number | No | Per-row temporal decay half-life used at search time |
eventTime | string | No | ISO timestamp the memory describes |
validFrom | string | No | ISO timestamp the assertion starts being true |
validUntil | string | null | No | ISO timestamp the assertion stops being true (null = open-ended) |
sourceThreadId | string | No | Originating thread |
sourceMessageId | string | No | Originating message |
sourceToolName | string | No | Originating tool call |
links | object[] | No | Initial graph edges to other memories |
Response 201 Created
{ "memoryId": "mem_abc123" }
GET /api/v1/memory/:id
Get a single memory by ID.
Scope: memory:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Response 200 OK
Returns the full Memory object.
PUT /api/v1/memory/:id
Update a memory. Content changes append a new version row — history is preserved.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
kind | string | No | Memory kind |
content | string | No | New content (triggers a new version snapshot) |
summary | string | No | One-line summary |
tags | string[] | No | Tags |
category | string | null | No | Category (pass null to clear) |
eventTime | string | No | ISO timestamp |
validFrom | string | No | ISO timestamp |
validUntil | string | null | No | ISO timestamp or null |
sourceThreadId | string | No | Thread provenance |
sourceMessageId | string | No | Message provenance |
sourceToolName | string | No | Tool provenance |
importance | number | No | 0..1 retention weight |
decayHalfLifeDays | number | No | Per-row decay half-life |
confidence | number | No | 0..1 truth confidence |
links | object[] | No | Graph edges |
changeNote | string | No | Free-form note attached to the new version |
Response 200 OK
Returns the updated Memory object.
DELETE /api/v1/memory/:id
Delete a memory and its version history. Irreversible.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Response 204 No Content
POST /api/v1/memory/:id/access
Record an access — increments usageCount and updates lastAccessedAt.
Scope: memory:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Response 200 OK
{ "recorded": true }
POST /api/v1/memory/:id/share
Share a memory with another user.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User to share with |
Response 200 OK
{ "success": true }
POST /api/v1/memory/:id/unshare
Remove sharing with a user.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User to unshare from |
Response 200 OK
{ "success": true }
POST /api/v1/memory/import
Bulk re-ingest of the JSON shape /memory/export emits.
Scope: memory:write
Request Body
{
"memories": [ /* Memory rows, or pass the full export wrapper */ ],
"scope": { "agentId": null, "appId": null, "workflowId": null }
}
| Field | Type | Required | Description |
|---|---|---|---|
memories | array | No | Array of memory rows. If absent, the entire body is treated as the export wrapper. |
scope | object | No | Override scope for all imported rows |
Response 200 OK
{ "imported": 42, "skipped": 1, "errors": [] }
GET /api/v1/memory/export
Bulk-export every memory the caller can read as JSON.
Scope: memory:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
includeInvalidated | boolean | No | Include rows with validUntil in the past |
agentId | string | No | Narrow to scope |
appId | string | No | Narrow to scope |
workflowId | string | No | Narrow to scope |
Response 200 OK
{
"memories": [ /* Memory objects */ ],
"exportedAt": "2026-05-16T10:00:00Z"
}
POST /api/v1/memory/consolidate
Run deterministic sleep-time consolidation: detects duplicates, evicts stale low-value rows, promotes heavily-used ones. Pass dryRun: true to preview without writing.
Scope: memory:write
Request Body
{
"scope": { "agentId": null, "appId": null, "workflowId": null },
"config": {
"evictionScoreThreshold": 0.2,
"evictionIdleDays": 90,
"duplicateOverlapThreshold": 0.85,
"promotionUsageThreshold": 10,
"promotionImportanceCeiling": 0.5,
"promotionImportanceTarget": 0.8
},
"dryRun": false
}
| Field | Type | Required | Description |
|---|---|---|---|
scope | object | No | Narrow consolidation to a scope |
config | object | No | Override default thresholds |
dryRun | boolean | No | Return the plan without applying changes |
Response 200 OK
{
"evicted": 3,
"promoted": 1,
"deduplicated": 2,
"dryRun": false
}
POST /api/v1/memory/:id/assess
Run the Memory quality ruleset against a row and cache the score on it. Returns the full per-check report.
Scope: memory:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Response 200 OK
{
"score": 0.82,
"checks": [
{ "id": "has-summary", "passed": true, "weight": 0.2 },
{ "id": "has-tags", "passed": true, "weight": 0.1 }
]
}
POST /api/v1/memory/:id/toggle-public
Toggle whether a memory is readable across the organization.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Response 200 OK
{ "isPublic": true }
PATCH /api/v1/memory/:id/scope
Re-scope an existing memory. Owner-only — the row owner can move a memory between user-global / agent / app / workflow scope without rewriting any content or losing version history.
Per the per-agent isolation model: a memory created without an explicit
scope by a marketplace-app instance defaults to that app's scope (see
POST /api/v1/memory); this endpoint lets the owner change that
later. The request body shape mirrors create: either nested scope: {...}
or flat top-level {agentId, appId, workflowId} — empty / whitespace
values collapse to null. Sending {"scope": {}} is the explicit
opt-out to user-global.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Request Body (either form accepted)
{
"scope": {
"agentId": "agent-123",
"appId": null,
"workflowId": null
}
}
or flat:
{
"agentId": "agent-123"
}
Response 200 OK
{
"success": true,
"scope": {
"agentId": "agent-123",
"appId": null,
"workflowId": null
}
}
Status Codes
| Code | Meaning |
|---|---|
| 200 | Scope updated |
| 400 | Invalid memory ID |
| 401 | Missing / invalid auth |
| 403 | Caller is not the row owner |
| 404 | Memory not found (or hidden from caller via per-agent isolation) |
POST /api/v1/memory/ingest
Mem0-style ingest. Embeds the new content, retrieves similar memories, asks an LLM to decide ADD / UPDATE / DELETE / NOOP, and executes the action.
Scope: memory:write
Request Body
{
"content": "User prefers TypeScript over JavaScript for new projects.",
"kind": "preference",
"tags": ["preference", "languages"],
"scope": { "agentId": null, "appId": null, "workflowId": null },
"model": "gpt-4o",
"candidateK": 5
}
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The new piece of information |
kind | string | Yes | fact / episodic / semantic / instruction / preference |
tags | string[] | No | Tags applied if the judge chooses ADD |
scope | object | No | {agentId?, appId?, workflowId?} |
model | string | No | Override the judge LLM model |
candidateK | number | No | How many similar memories to show the LLM (default 5) |
Response 200 OK
{
"action": "ADD",
"memoryId": "mem_abc123",
"candidates": [ /* candidates the judge saw */ ]
}
Returns 400 validation-error for empty content/kind or judge parse errors; 503 configuration-error if no judge model or org is configured.
POST /api/v1/memory/:id/links
Create or update a graph edge from this memory to another. Symmetric relations (supports / contradicts / related) are mirrored on the target.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Source memory ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
targetId | string | Yes | Target memory ID |
relation | string | Yes | supports / contradicts / refines / related |
weight | number | No | Edge weight in [0, 1] |
Response 200 OK
{ "success": true }
DELETE /api/v1/memory/:id/links/:targetId
Remove the link from this memory to a target. Symmetric relations are also stripped from the target side.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Source memory ID |
targetId | string | Yes | Target memory ID |
Response 200 OK
{ "success": true }
GET /api/v1/memory/:id/linked-to
List memories that link to this one (reverse-edge lookup).
Scope: memory:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Response 200 OK
{
"memories": [ /* Memory objects with edges pointing to :id */ ],
"count": 3
}
POST /api/v1/memory/:id/invalidate
Mark a memory as contradicted or superseded. Sets validUntil = now and contradictionOf (if supplied). History is preserved.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
supersededBy | string | No | Memory ID that supersedes this one |
Response 200 OK
{
"memoryId": "mem_abc123",
"validUntil": "2026-05-16T10:00:00Z",
"supersededBy": "mem_def456"
}
POST /api/v1/memory/search
Hybrid (BM25 + dense vector) memory search with RRF fusion and temporal-decay re-ranking. Scope, kind, and tag filters apply uniformly to both signals.
Scope: memory:read
Request Body
{
"query": "What does the user prefer for new TypeScript projects?",
"k": 10,
"kind": "preference",
"tags": ["languages"],
"scope": { "agentId": null, "appId": null, "workflowId": null },
"rrfK": 60,
"weights": { "vector": 1.0, "text": 1.0 },
"decayHalfLifeDaysOverride": 90,
"asOf": "2026-05-16T10:00:00Z",
"includeInvalidated": false,
"rerank": true,
"rerankModel": "bge-reranker"
}
| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Natural-language query |
k | number | No | Max results (default 10, max 200) |
kind | string | No | Filter by memory kind |
tags | string[] | No | Filter by tags (OR) |
scope | object | No | {agentId?, appId?, workflowId?} |
rrfK | number | No | RRF k constant (default 60) |
weights | object | No | {vector?, text?} per-list weights for RRF |
decayHalfLifeDaysOverride | number | null | No | Override per-row decay; pass null to disable decay |
asOf | string | No | ISO timestamp for bitemporal filter |
includeInvalidated | boolean | No | Include invalidated rows |
rerank | boolean | No | Run a reranker over the fused list |
rerankModel | string | No | Override the reranker model |
Response 200 OK
{
"results": [
{ "memory": { /* Memory object */ }, "score": 0.91, "signals": { "vector": 0.93, "text": 0.78, "decay": 0.95 } }
],
"count": 1
}
GET /api/v1/memory/:id/versions
List every version of a memory with reconstructed content.
Scope: memory:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
Response 200 OK
{
"versions": [
{
"versionNumber": 1,
"content": "Original content",
"changeNote": null,
"createdAt": "2026-04-01T09:00:00Z"
},
{
"versionNumber": 2,
"content": "Updated content",
"changeNote": "Clarified phrasing",
"createdAt": "2026-05-10T12:00:00Z"
}
],
"count": 2
}
GET /api/v1/memory/:id/versions/:n
Fetch one version of a memory with reconstructed content.
Scope: memory:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
n | integer | Yes | Version number (positive integer) |
Response 200 OK
{
"versionNumber": 2,
"content": "Updated content",
"changeNote": "Clarified phrasing",
"createdAt": "2026-05-10T12:00:00Z"
}
POST /api/v1/memory/:id/versions/:n/restore
Restore the memory to a prior version. Appends a new version row — does not mutate history.
Scope: memory:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Memory ID |
n | integer | Yes | Version number to restore to |
Response 200 OK
{
"memoryId": "mem_abc123",
"restoredFrom": 1,
"newVersionNumber": 3
}