convex-supermemory
v0.0.5
Published
Give your Convex app long-term, semantically searchable memory with Supermemory. Reactive local memory and document state, direct REST integration.
Maintainers
Readme
convex-supermemory
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?",
});What this does
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
Table of Contents
- Install
- Quick Start
- Usage
- Memories vs. Documents
- API Reference
- Type Reference
- Database Schema
- Container Tags
- Testing
- Example App
- Limitations
- Troubleshooting
- Contributing
- Changelog
Install
npm install convex-supermemoryRequirements: Convex v1.33.1 or later, Node.js 18+, a Supermemory API key
Quick Start
Three steps to add memory to your Convex app.
1. Add the component
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;2. Set environment variables
npx convex env set SUPERMEMORY_API_KEY sm_xxxxxxxxxxxx3. Initialize the client
In 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.
Usage
Remember a fact
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 }Recall with semantic search
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 }Forget a memory
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 }List a user's memories reactively
export const getMemories = query({
args: { userId: v.string() },
handler: async (ctx, args) => {
return await supermemory.listMemories(ctx, { containerTag: args.userId });
},
});Memories vs. Documents
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).
API Reference
| 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 Reference
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
};Database Schema
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.
Container Tags
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).
Testing
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 App
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.
Limitations
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).
Troubleshooting
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.
Contributing
See CONTRIBUTING.md.
Changelog
See CHANGELOG.md.
