Platform
Memory API
Store facts and preferences about each user, or about an agent or an org, and recall the relevant ones by meaning, with deduplication, expiry, forget and GDPR erasure.
The Memory API gives your app a long-term memory per user. Store short facts ("Prefers vegetarian recipes"), then search them by meaning when the user asks something. Near-duplicates update the existing memory instead of piling up, memories can expire, and one call forgets everything about a user. Threads can add a user's memories to a model's context automatically.
This is memory for your app's users, stored in your project database. It is separate from Project Memory, which is where AI coding agents keep notes about your project.
Subjects
Every memory belongs to a subject, written type:id:
- A signed-in user is always
user:<their id>. Their calls never name a subject, and naming someone else's is refused with403. - The service-role key must name the subject, and can use any type:
user:<id>,agent:support,org:42. The type is a lowercase word of up to 32 characters; the id is up to 200 characters.
Quick start
import { createClient } from '@nestcore/js';
const client = createClient({ apiKey: import.meta.env.VITE_NESTCORE_ANON_KEY });
await client.auth.signIn({ email, password });
await client.memory.add('Prefers vegetarian recipes', { kind: 'preference', importance: 4 });
await client.memory.add('Has a dog called Pixel');
const { data } = await client.memory.search('what should I cook tonight?', { k: 5 });
// data.results: memories sorted by relevance, each with similarity and score
// data.degraded: true if the provider was down and keyword search answeredOn a server, with the service-role key, name the subject:
await admin.memory.add('Escalate refunds over $500 to a human', { subject: 'agent:support' });
await admin.memory.search('large refund', { subject: 'agent:support' });client.memory is in the Node.js SDK from version 0.14.0 and the web SDK (@nestcore/js) from version 0.8.0.
Flutter:
await nestcore.memory.add('Prefers vegetarian recipes', kind: 'preference');
final found = await nestcore.memory.search('what should I cook tonight?');Add
memory.add(content, options) returns { memory, deduped }.
| Option | Default | Description |
|---|---|---|
subject | the signed-in user | Service key only, and then required |
kind | fact | A lowercase word: fact, preference, episode, summary or your own |
importance | 3 | 1 to 5. Used to break near-ties in search. |
expiresAt | none | ISO date. The memory stops appearing in search after it and is deleted within the hour. |
metadata | {} | Any JSON |
sourceThreadId | none | The thread the memory came from, for your records |
dedupe | true | Update a near-identical memory instead of adding one |
Content is 1 to 8,000 characters. With dedupe on, NestCore compares the new memory with the subject's closest existing memory. If their cosine similarity is 0.92 or more, it updates that memory instead: the content and kind are replaced, importance becomes the higher of the two, metadata is merged, and a new expiresAt replaces the old one. The result then has deduped: true and the existing id.
Search
memory.search(query, options) returns { results, degraded, model }.
| Option | Default | Description |
|---|---|---|
subject | the signed-in user | Service key only, and then required |
k | 5 | Results, 1 to 50 |
kind | all | Only memories of this kind |
minSimilarity | 0 | Drop results below this cosine similarity |
Results are ranked by score: the cosine similarity plus small nudges for importance (0.02 × (importance − 3)) and recency (up to 0.04, halving every 30 days since the memory was last updated). Similarity dominates; the nudges only reorder memories that are about equally relevant. Expired memories are never returned.
If the AI provider is down (a server error or an unavailable model), search falls back to keyword matching instead of failing. The response then has degraded: true, model: null, and each result has similarity: null. Configuration errors, such as a missing key, still fail.
List, update and delete
| Method | What it does |
|---|---|
memory.list({ subject, kind, limit, offset }) | The subject's memories, most recently updated first. limit 1 to 200 (default 50). Returns { memories, total }. |
memory.get(id) | One memory |
memory.update(id, { content, kind, importance, expiresAt, metadata }) | Change a memory. New content is embedded again. |
memory.remove(id) | Delete one memory |
memory.forget(subject?) | Delete every memory of a subject. Users always forget themselves. Returns { deleted }. |
memory.subjects({ type, limit }) | Service key: the subjects that have memories, with counts. limit up to 500 (default 100). |
memory.reembed({ subject, limit }) | Service key: re-embed up to 64 memories made with another model. Returns { reembedded, remaining, model }. |
Signed-in users only ever reach their own memories: get and update of another id return 404, and remove returns deleted: false.
Forget and GDPR
forget hard-deletes the subject's memories, vectors included. It leaves a tombstone (subject, number of rows deleted, time) for 7 days as a record that the data was erased.
When you delete one of your app's users (in Studio or with the auth admin API), NestCore also forgets user:<their id> and deletes the threads they own.
Changing the embedding model
Memories are embedded with the project's default embedding model (see Add a provider key). Each memory remembers the model that embedded it, and search only compares memories made with the current model. If you switch provider or default embedding model, call memory.reembed() from a server with the service key until remaining is 0.
Access rules
| Caller | Can |
|---|---|
| Signed-in user (anon key + access token) | Add, search, list, update, delete and forget their own memories |
| Service-role key | Everything, for any subject, plus subjects and reembed |
| Anon key alone | Nothing: 401 |
Memories live in the ai_memories table of your project database, created on first use. Its row-level security policy also limits users to their own rows if they query it with SQL.
Limits and quotas
Every Memory API call that embeds text counts as a memory operation: an add, a search, an update with new content, and a re-embed. Thread runs that use memory count too. The workspace allowance per month:
| Plan | Memory operations per month |
|---|---|
| Free | 1,000 |
| Starter | 25,000 |
| Pro | 250,000 |
Past it, adds, searches and content updates return 402 with code: "QUOTA_EXCEEDED" and quota: "ai_memory_ops" until the next month. Listing, deleting and forgetting always work. Memory calls are also AI gateway calls on your own key: they count toward your per-minute AI limit and spend caps. See Plans & billing.
In Studio
Project Settings → AI → Memory lists subjects with their memory counts. Pick a subject to see its memories, search them, delete one, or forget the subject. Deleting and forgetting need the workspace admin role.
The MCP tool nestcore_memory_subjects lists subjects, and nestcore ai overview (CLI) or nestcore_ai_overview (MCP) count memories and subjects.
HTTP API
Under /api/project/{slug}/ai/v1. Send the anon key in apikey and the user's access token in Authorization: Bearer, or the service-role key.
| Method | Path | Body or query |
|---|---|---|
POST | /memory | { content, subject?, kind?, importance?, expiresAt?, metadata?, sourceThreadId?, dedupe? } |
POST | /memory/search | { query, subject?, k?, kind?, minSimilarity? } |
GET | /memory | ?subject&kind&limit&offset |
GET | /memory/subjects | ?type&limit (service key) |
POST | /memory/forget | { subject? } |
POST | /memory/reembed | { subject?, limit? } (service key) |
GET | /memory/{id} | — |
PATCH | /memory/{id} | { content?, kind?, importance?, expiresAt?, metadata? } |
DELETE | /memory/{id} | — |
Memories come back as stored: id, subject_type, subject_id, kind, content, embed_model, importance, metadata, source_thread_id, expires_at, created_at, updated_at.
Errors
| Status | When |
|---|---|
400 | Empty or too long content, a bad kind, importance or expiresAt, or a service-key call without subject |
401 | The anon key was sent without a user token |
403 | A user named another subject, or called subjects or reembed |
404 | The memory doesn't exist or isn't the user's |
402 | QUOTA_EXCEEDED (ai_memory_ops) or AI_BUDGET_EXCEEDED |
Embedding can also fail with the AI gateway errors.