Give your Convex app long-term, semantically searchable memory with Supermemory. Reactive local memory and document state, direct REST integration.
const supermemory = new Supermemory(components.convexSupermemory, {
apiKey: process.env.SUPERMEMORY_API_KEY!,
});
// Store a fact about a user
const { memoryId } = await supermemory.addMemory(ctx, {
containerTag: userId,
content: "Prefers dark mode and terse responses",
});
// Recall it later with semantic search
const { results } = await supermemory.search({
containerTag: userId,
query: "how does this user like responses formatted?",
});An AI agent is only as good as what it remembers between calls. Supermemory is a memory API purpose-built for this: you write facts and documents into a containerTag (typically one per user or agent), and it handles embedding, chunking, and semantic search so you can recall the right context later — without you running your own vector database.
This component wraps Supermemory's REST API directly (no SDK dependency) and mirrors everything you write into Convex tables, so you get:
- Fast-path memory —
addMemory()stores a single fact immediately, no processing pipeline - Document ingestion —
addDocument()sends larger content through Supermemory's extraction/chunking pipeline, withrefreshDocument()to poll status - Semantic search —
search()proxies Supermemory's hybrid RAG + memory search live - Reactive local state — every memory and document is queryable from Convex without an extra API call
- Forgetting —
forgetMemory()/deleteDocument()keep local state in sync with what Supermemory actually removes
- Install
- Quick Start
- Usage
- Memories vs. Documents
- API Reference
- Type Reference
- Database Schema
- Container Tags
- Testing
- Example App
- Limitations
- Troubleshooting
- Contributing
- Changelog
npm install convex-supermemoryRequirements: Convex v1.33.1 or later, Node.js 18+, a Supermemory API key
Three steps to add memory to your Convex app.
In convex/convex.config.ts:
import { defineApp } from "convex/server";
import convexSupermemory from "convex-supermemory/convex.config";
const app = defineApp();
app.use(convexSupermemory);
export default app;npx convex env set SUPERMEMORY_API_KEY sm_xxxxxxxxxxxxIn convex/memory.ts:
import { components } from "./_generated/api";
import { Supermemory } from "convex-supermemory";
export const supermemory = new Supermemory(components.convexSupermemory, {
apiKey: process.env.SUPERMEMORY_API_KEY!,
});Import supermemory from this file in any Convex action that needs to read or write memory.
export const remember = action({
args: { userId: v.string(), fact: v.string() },
handler: async (ctx, args) => {
return await supermemory.addMemory(ctx, {
containerTag: args.userId,
content: args.fact,
isStatic: true, // long-term fact, not a fading conversational detail
});
},
});
// Returns: { memoryId }export const recall = action({
args: { userId: v.string(), query: v.string() },
handler: async (ctx, args) => {
return await supermemory.search({
containerTag: args.userId,
query: args.query,
limit: 5,
});
},
});
// Returns: { results: [{ documentId, score, summary, chunks, ... }], total }containerTag is required here too — Supermemory scopes the delete by it, not just by memoryId.
export const forget = action({
args: { userId: v.string(), memoryId: v.string() },
handler: async (ctx, args) => {
return await supermemory.forgetMemory(ctx, {
containerTag: args.userId,
memoryId: args.memoryId,
});
},
});
// Returns: { forgotten: boolean }export const getMemories = query({
args: { userId: v.string() },
handler: async (ctx, args) => {
return await supermemory.listMemories(ctx, { containerTag: args.userId });
},
});Supermemory exposes two ways to write content, and this component exposes both:
addMemory()— for short, discrete facts ("the user is on the Pro plan", "prefers metric units"). No processing delay; usable in search immediately.addDocument()— for longer content (a support transcript, a page of documentation, a whole conversation) that needs to be chunked and embedded. Processing is asynchronous — callrefreshDocument()to check onstatus(queued→extracting→chunking→embedding→indexing→done).
| Method | Kind | Description |
|---|---|---|
addMemory(ctx, args) |
action | Stores a single fact immediately |
forgetMemory(ctx, args) |
action | Requests Supermemory forget a memory (scoped by containerTag) |
addDocument(ctx, args) |
action | Ingests larger content through the chunking pipeline |
refreshDocument(ctx, args) |
action | Pulls the latest status/title/summary for a document |
deleteDocument(ctx, args) |
action | Deletes a document from Supermemory and locally |
search(args) |
plain async | Live semantic search — not cached locally |
getMemory(ctx, args) |
query | Fetch one memory by id |
listMemories(ctx, args) |
query | List a container's memories, newest first |
getDocument(ctx, args) |
query | Fetch one document by id |
listDocuments(ctx, args) |
query | List a container's documents, newest first |
getStats(ctx) |
query | Aggregate memory/document counts for a small dashboard |
listRecentMemories(ctx, args?) |
query | Every memory, newest first, regardless of containerTag |
listRecentDocuments(ctx, args?) |
query | Every document, newest first, regardless of containerTag |
type AddMemoryArgs = {
containerTag: string;
content: string;
isStatic?: boolean;
metadata?: Record<string, unknown>;
forgetAfter?: number; // ms epoch
forgetReason?: string;
};
type AddDocumentArgs = {
containerTag: string;
content: string;
customId?: string;
metadata?: Record<string, unknown>;
};
type SearchArgs = {
containerTag: string;
query: string;
limit?: number;
rerank?: boolean;
includeFullDocs?: boolean;
};
type SearchResponse = {
results: Array<{
documentId: string;
title: string | null;
score: number;
summary: string | null;
content: string | null;
chunks: Array<{ content: string; score: number; position: number; isRelevant: boolean }>;
}>;
total: number;
timing?: number; // ms, when Supermemory reports it
};memories: {
memoryId, containerTag, content, isStatic, metadata?,
forgetAfter?, forgetReason?, forgotten,
createdAt, updatedAt,
}
documents: {
documentId, containerTag, content?, customId?, metadata?, status,
title?, summary?,
createdAt, updatedAt,
}getStats(), listRecentMemories(), and listRecentDocuments() read across these tables directly with a full scan — fine for a dashboard or history view, not intended as a high-volume production query path.
Supermemory scopes everything — writes, search, and profiles — by containerTag. Use one per user for personal memory, or one per agent/workspace for shared memory. Search only ever looks within the container tag you pass, so pick a convention early (this component doesn't enforce one).
npm run testComponent logic is tested with convex-test in src/component/lib.test.ts. Import convex-supermemory/test in your own app to register this component's schema against your test instance.
example/ is a full Vite + React demo that exercises the entire component end to end against your own Supermemory account:
- Memories — add a fact with
addMemory()(with suggestion chips to try), forget it withforgetMemory(), all reactive. - Documents — ingest longer content with
addDocument(), poll status withrefreshDocument(), remove it withdeleteDocument(). - Search — run
search()against whichever containerTag is selected, showing result score, matching chunks, and Supermemory's own reportedtotal/timing. - containerTag switcher — a row of chips lets you flip between a few demo users live, so you can see the exact same actions land in completely separate memory spaces.
- History — every memory and document ever written in the deployment, newest first, across every containerTag. Expand a row for full detail, or hit Recreate live to replay it as a brand-new memory/document.
- Activity console — a side-docked live log of every call this app makes into the Supermemory client, including real API errors (e.g. Supermemory returns
409if you try to delete a document that's still processing).
Run it from the repo root:
npm install --legacy-peer-deps
npx convex env set SUPERMEMORY_API_KEY sm_xxxxxxxxxxxx
npm run devnpm run dev starts the Convex backend and the Vite frontend together — there's no need to cd example or run either one separately.
search()always calls Supermemory live — results are not cached or mirrored into Convex tables, since they're a ranked view over the underlying memories/documents rather than durable records themselves.- Document processing is asynchronous; this component does not poll for you. Call
refreshDocument()on a schedule (e.g. a Convex cron or scheduled function) if you need status without a user-triggered refresh. forgetMemory()reflects whateverforgottenvalue Supermemory returns — if Supermemory forgets asynchronously, the local record may briefly still showforgotten: false.deleteDocument()throws if Supermemory reports the document is still processing (409) — retry after the document reaches a terminal status (done/failed).
401/403 from Supermemory — confirm SUPERMEMORY_API_KEY is set and starts with sm_.
Search returns nothing — confirm you're searching the same containerTag you wrote memories/documents into; container tags are exact-match scopes, not fuzzy. If the document was ingested moments ago, give it a few seconds to finish indexing even after refreshDocument() reports done.
Document stuck in queued/extracting — large documents can take longer to process; call refreshDocument() again after a short delay.
Deleting a document fails with a "still processing" error — Supermemory returns 409 for a document that hasn't reached a terminal status yet; wait for refreshDocument() to report done or failed, then delete.
See CONTRIBUTING.md.
See CHANGELOG.md.
