Compare commits

..

26 commits

Author SHA1 Message Date
Mahesh Sanikommu
225fbf0e0c
fix(deps): bump happy-dom to 21.0.4 and zod 4 copies to 4.6.7 (#1843)
Some checks are pending
Publish Memory Graph / publish (push) Waiting to run
Publish Tools / publish (push) Waiting to run
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 13:16:46 -07:00
Muskan jain
9f55a37013
Docs: name the Mem0 dashboard in the migration guide without linking it (#1840)
Co-authored-by: Muskan <muskan@Muskans-MacBook-Air.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:53:44 -07:00
sreedharsreeram
f355dc8039 feat(mcp): validate API keys via /session (#1839)
## What

The Supermemory API now serves the session endpoint unversioned at `/session` (supermemoryai/mono#3626). `sm_` API-key validation in the MCP worker calls `/session` instead of `/v3/session`. One-line code change.

- `apps/mcp/src/server/auth/index.ts`: `fetchSession` requests `/session`.
- `apps/mcp/src/server/auth/index.test.ts`: asserts the new URL and that a refused key makes exactly one request.
- `apps/mcp/src/shared/types.ts`: comment updated.

## Deploy order

This worker depends on the API serving `/session`. **Deploy mono#3626 first.** Until then, production answers `/session` with 404 for full keys and 403 for scoped keys (the scope allowlist is checked before routing), and every `sm_` key would be rejected.

No fallback to `/v3/session` on purpose: the only "old server" in scope is that deploy window, and a failed-request fallback would blur 403/404 semantics for a case merge ordering already covers.

## Verification

- `bun run check-types`, `vitest run src` (29/29).
- Against the API with mono#3626 running: full key → `/session` 200 valid; scoped key → `/session` 200 valid; bad key → `/session` 401 rejected, single call.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-24 23:59:06 +00:00
sreedharsreeram
f3001517d1 docs: explain optional content in v5 document lists (#1830)
## Why

The document operations guide did not describe how to retrieve extracted text from a v5 document list, and the migration guide incorrectly said the list `include` object was for memories only.

## What changed

- Document `include.content`, its `false` default, and a cURL request for uploaded files in the same section pattern as the surrounding guide.
- Explain that list content is the last published extracted text, including the behavior before first publication and during failed replacement.
- Correct the v5 migration and reference overview copy.

```mermaid
flowchart LR
  A[Document operations guide] --> B[include.content request]
  B --> C[Published extracted text in list response]
```

## Validation

- Compared the guide and migration snippet with adjacent v5 docs for format and terminology.
- `git diff --check` — passed.
- `python3 -m json.tool apps/docs/docs.json` — passed.

Related API PR: https://github.com/supermemoryai/mono/pull/3606

The written docs should publish with or after the API change. The generated v5 reference reads the deployed OpenAPI source, so it updates when that source is available and the docs site refreshes.
2026-10-21 18:19:21 +00:00
Muskan jain
f81fad00e4
docs: permanent redirects for /self-hosting and /supermemory-mcp (#1821)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:37:33 -07:00
Mahesh Sanikommu
00f48d0378
fix(mcp): log expected auth rejections at warn and reserve error for auth outages (#1822)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-16 17:59:26 -07:00
Dhravya
d40b2f5a07 fix(mcp): share the accessible default with prompts and resources, prefer writable spaces
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-15 23:16:56 -07:00
Dhravya
5381882fb6 fix(mcp): only hand out IDs and spaces the caller can use
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-15 23:16:56 -07:00
MaheshtheDev
ccf0485869 fix(mcp): forget memories by ID instead of LLM matching (#1820)
forget_memory called the semantic forget-matching endpoint (two LLM calls plus a widening loop) to pick one memory, so most calls hit the 30s timeout since the v5 move. It now takes ids from search_memory and forgets them with the exact-ID endpoint (~0.5s on prod).

- Text-only calls forget only an exact text match; otherwise nothing is deleted and the closest memories come back with their IDs.

- Legacy add_memory action=forget keeps working through the same path.
2026-10-15 20:43:16 +00:00
sohamd22
23983bffae docs: align billing, analytics and legacy reference pages with the API (#1814)
Fixes billing, analytics, security and legacy reference pages.

- **Operations.** `rewriteQuery`, rerank/aggregate and forget-matching each bill one operation.
- **Profile metering.** v5 profile is documented as 1 search query per call.
- **Analytics:**
  - The errors response shape is fixed.
  - Usage `byKey` includes `tokensUsed`, and the log `output` example has the right shape.
  - The 90-day range limit doesn't exist.
- **Smaller fixes:**
  - Only 2 billing endpoints need an admin.
  - The `credits` response shape.
  - The 2FA gate is in the feature table.
  - The security page points to the one-call namespace delete for data erasure.
  - Container-tag merge takes several sources.
  - A stale "after API deploy" note is removed.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:42 +00:00
sohamd22
acbb05f837 docs(self-hosting): align self-hosting docs with the server (#1813)
Fixes self-hosting docs to match the `supermemory-server` binary.

- **Ingest example.** `?dreaming=instant` was in the URL in the quickstart; it's now in the body.
- **Startup.** The banner example is accurate. The plaintext `env` file is legacy: it's moved into the encrypted `env.enc` and then deleted.
- **Providers:**
  - The provider order is OpenAI → Anthropic → Gemini → Vertex → Groq → Workers AI.
  - PDF extraction needs `GEMINI_API_KEY` (Vertex doesn't help).
  - URL ingestion uses Firecrawl and needs `FIRECRAWL_API_KEY`.
- **Smaller fixes:**
  - The SDK option is `baseUrl`.
  - Documented the environment switches the binary honors.
  - Embedding defaults are corrected.
  - Removed the claim that the hosted platform uses the same embedding stack.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:41 +00:00
sohamd22
2a546ff654 docs(connectors): align connector docs with the v5 API (#1812)
Fixes connector docs.

- **`authorization.url` examples.** They now show the URL the API actually returns (`/v3/connections/auth/redirect?state=…`), not provider URLs.
- **Broken examples:**
  - TS `connectors.create` examples were missing commas.
  - Python dicts now use the SDK's snake_case keys.
  - `connector.createdAt` → `connector.system.createdAt`.
  - The list default `limit` is 10.
- **Pending connectors.** They are visible through get (`pending_authorization`), just not in list.
- **Gmail:**
  - It needs the Max plan.
  - Nothing syncs until labels are picked.
  - The watch covers the selected labels, not just INBOX.
  - `from` filters use `contains`.
- **Smaller fixes:**
  - The OAuth redirect-URI troubleshooting.
  - Notion property metadata keys.
  - Picker error codes.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:41 +00:00
sohamd22
2197c04ac1 docs(api): fix namespace move, scoped keys and SDK call shape in v5 docs (#1811)
Fixes namespace, auth and SDK-shape docs.

- **Namespace move example deleted instead.** It sent `moveTo` in a JSON body on `DELETE /ns/{namespace}`. The route only reads `moveTo` from the query string and ignores the body, so following the docs permanently deleted the namespace. It now uses `?moveTo=`, and the overview warns about this.
- **SDK call shape.** URL values are positional (`namespace` first), followed by one options object; the docs described one object with `body`. Python 5.0.0 is the v5 client, so "no v5 Python SDK" is gone.
- **Scoped keys** (`authentication.mdx`):
  - The allowed-endpoints list now includes the `/ns/*` routes; all legacy routes are kept.
  - `containerTags` (several namespaces) is documented.
  - Namespace names don't allow dots.
- **Key permissions.** Read/write applies to the whole key; only restricted members have per-namespace permissions.
- **Smaller fixes:**
  - The move response has no `success` field.
  - `namespaceCount` only matches the namespace list for an unrestricted key.
  - `setup --json` does nothing.
  - The agent prompt's ban on `memories.*` now covers only the calls v5 removed.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:41 +00:00
sohamd22
4347970d16 docs(api): align v5 ingestion and document operations docs with the API (#1810)
Fixes v5 ingestion and document-operation docs.

- **Query params return `400`.** `dreaming`, `fileType` and `mimeType` were documented as query params; they are JSON body or multipart form fields.
- **Errors.** Out-of-balance quota is `402`, not `429`; `429` is only rate limiting. Added the `409` returned during a namespace merge.
- **Statuses.** There is no `processing` status. The real ones are queued, extracting, chunking, embedding, done and failed; treat anything else as in progress.
- **`group` on updates.** `group` is ingestion-side: it picks which existing memories serve as context while content is processed. Changing it only takes effect on re-ingestion, so it's sent with `content`/`file`. The "group-only PATCH" claims are removed from the migration doc.
- **Lists, deletes and forget:**
  - Valid `sort` values per type, a `limit` max of 100, and `include.forgotten`.
  - Deletes skip documents that are still processing.
  - Forget-matching has a 100-match cap and a 2000-character `query` limit.
- **Failed documents stay `failed`.** The docs said they're deleted after 2 minutes. They actually stay `failed`, and resending the same `id` reprocesses them.
- **Smaller fixes:**
  - Metadata allows `string[]`.
  - The batch response field is `count`.
  - `audio` is a valid `fileType`.
  - Chunk overlap is 200 characters.
  - URL extraction no longer claims Markdowner.
  - Search examples no longer index the response object as an array.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:40 +00:00
sohamd22
8452a00434 docs(api): keep the default preferences bucket and drop v4 profile labels in v5 docs (#1809)
Fixes v5 profile and bucket docs.

- **The default `preferences` bucket can't be replaced.** It's always part of the effective set. Custom buckets are added alongside it, `deleteBuckets` can't remove it, and defining a bucket named `preferences` only overrides its description.
- **No `[Summary]`/`[Recent]` labels in v5.** Only v4 adds them. Summaries are now told apart by an `aggregated_` id, and individual `dynamic` entries keep their `[YYYY-MM-DD]` date. Example responses are updated, and the migration snippet calls out the change.
- **Name clashes.** A namespace bucket with an org bucket's name returns `409`; the docs said the org's definition silently wins.
- **Smaller fixes:**
  - `filterPrompt` → `organizationalContext`.
  - An invalid `organization.update` example (stray `},`).
  - OAuth redirect URIs now use `/v3/connections/auth/callback/{provider}`.
  - The v4 `include` example in the migration snippet.
  - Broken anchors and links.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:40 +00:00
sohamd22
ca89d161cc docs(api): move v5 search and ingest options into the request body (#1808)
Fixes v5 search and recall docs that didn't match the API.

- **Options in the URL.** Search examples passed `searchMode`/`limit` as query params, and ingest examples passed `dreaming`/`taskType` the same way. v5 rejects every query param on these routes with `400`. They now go in the JSON body (quickstart, rules, super-rag, recall/search).
- **`include`, not `attach`.** Renamed the search option. Its `forgotten` flag also admits forgotten memories into the main results, not just related context.
- **Search response.** Added the required `isInference`. `metadata` is always an object, and `system.createdAt` is optional.
- **Search add-ons are billed.** `rewriteQuery` and `rerank` each bill one extra operation; the docs said "no extra cost".
- **Removed claims that don't match current behavior:**
  - inferred memories being down-weighted in search;
  - AST code chunking via `code-chunk` (super-rag now describes how chunking actually works);
  - a 64-character metadata key limit.
- **Smaller fixes:**
  - The namespace is the SDK's first argument; the docs called it a top-level key.
  - "No v5 Python SDK" was stale.
  - Memory review: `total` semantics, the internal review stamp, and the missing 403.

Checked against the current API and the published SDKs (TS 5.0.1, Python 5.0.0). Legacy v3/v4 docs are unchanged apart from factual fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-15 18:52:40 +00:00
MaheshtheDev
5323f427d0 fix(python): community fixes for the openai, cartesia and pipecat wrappers (#1799)
Some checks failed
Publish Pipecat SDK Python / publish (push) Has been cancelled
Publish Cartesia SDK Python / publish (push) Has been cancelled
Publish OpenAI SDK Python / publish (push) Has been cancelled
Bundles 10 community fixes for the Python wrappers, each commit kept with its original author. Ships supermemory-openai-sdk 1.0.10, supermemory-cartesia 0.1.5 and supermemory-pipecat 0.1.5.

- openai-sdk-python: shared OpenAI clients no longer leak memories across tenants (#1503), memory search fails open instead of crashing the chat (#1288), the requests fallback runs off the event loop (#1660), get_profile works for users with no memories (#1759).
- cartesia: the wrapped agent runs exactly once per event (#1280, #1670), plain-string and chunk-only search results are kept (#1683, #1693).
- pipecat: memory text stays literal when replacing the injected block (#1647), `InputParams` is exported from the package root (#1575).
- #1503 was hand-ported onto main's newer middleware. #1751 is left out: the v5 agent-framework migration (#1786) appends to the conversation document by design.

Co-Authored-By: abhinav7x94 <204053250+abhinav7x94@users.noreply.github.com>
Co-Authored-By: shamAnimates <145093437+shamAnimates@users.noreply.github.com>
Co-Authored-By: aloktripathi1 <142301030+aloktripathi1@users.noreply.github.com>
Co-Authored-By: Cintu07 <178455858+Cintu07@users.noreply.github.com>
Co-Authored-By: Atharvsinh-codez <201381760+Atharvsinh-codez@users.noreply.github.com>
Co-Authored-By: abhay-codes07 <182421137+abhay-codes07@users.noreply.github.com>
Co-Authored-By: nkootstra <545768+nkootstra@users.noreply.github.com>
Co-Authored-By: Agnik47 <140933190+Agnik47@users.noreply.github.com>
2026-10-14 21:08:38 +00:00
Dhravya Shah
669d2697a9
fix(ci): remove retired AI SDK publishing workflow (#1777) 2026-10-14 14:04:27 -07:00
Mahesh Sanikommu
92f511df9b
chore: remove the Raycast extension (#1818) 2026-10-14 14:03:45 -07:00
rishibalanagu0108
b7534f9c50
fix(ui): remove unused dependencies (#1815) 2026-10-14 13:58:42 -07:00
MaheshtheDev
d42e71f8bb fix(docs-test): Windows venvs and failing TypeScript examples (#1804)
Community fixes for the docs-test runner.

- Python examples run on Windows: the runner picks `.venv/Scripts/python.exe` and reports a missing venv as a failed test (#1487).
- TypeScript examples exit 1 when `main()` rejects, so broken examples no longer count as passed (#1495).

Co-Authored-By: abhinav7x94 <204053250+abhinav7x94@users.noreply.github.com>
2026-10-13 19:59:35 +00:00
MaheshtheDev
87af03a55f docs: community fixes for self-hosting, contributing and the sidebar (#1802)
Community docs fixes, checked against the self-hosted server source.

- CONTRIBUTING.md: setup steps and project structure match the repo (#1745).
- README: link the self-hosting quickstart (#1713).
- Self-hosting: database snapshot behavior (#1687) and embedding env vars, local model dimensions and the v0.0.6 upgrade lock (#1659), trimmed to what the code does.
- Docs sidebar: hover and selected state CSS from #1784, without the duplicated bold icon set.
- `SUPERMEMORY_DATA_DIR` now defaults to `~/.supermemory` (server-v0.0.9).

Co-Authored-By: Agnik47 <140933190+Agnik47@users.noreply.github.com>
Co-Authored-By: Fullstop000 <fullstop1005@gmail.com>
Co-Authored-By: Adityakk9031 <143548997+Adityakk9031@users.noreply.github.com>
Co-Authored-By: parthivdzns <parthiv.des@gmail.com>
2026-10-13 04:41:23 +00:00
Dhravya Shah
575ee2eaf5
docs: add Docker installation and safe upgrade instructions (#1801)
Some checks failed
Publish Tools / publish (push) Has been cancelled
2026-10-12 16:54:58 -07:00
Mahesh Sanikommu
570bc1bf47
fix(tools): community fixes for 3.1.2 (#1798)
Co-authored-by: abhinav7x94 <raj.abhinav7x94@gmail.com>
Co-authored-by: Rohit <71192000+rohitsux@users.noreply.github.com>
Co-authored-by: Aniruddha Adak <aniruddhaadak80@users.noreply.github.com>
Co-authored-by: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com>
Co-authored-by: Rikinshah787 <Rikinshah787@users.noreply.github.com>
Co-authored-by: Agnik47 <140933190+Agnik47@users.noreply.github.com>
2026-10-12 16:23:11 -07:00
sohamd22
6e5ec07819 docs(api): document v5 system.dreamingStatus (#1797)
Docs for supermemoryai/mono#3519: v5 document reads and list items now return `system.dreamingStatus` (`"dreaming" | "done"`), the signal for when a document's memories are ready, separate from `system.status`.

- Migration guide (content-management snippet): the field, why it differs from `status`, and the move from v3's top-level `dreamingStatus`.
- Document operations: a "Memory generation status" section, plus the field in the get, list, and processing-queue examples.
- One-line pointers in the SDK mapping table, batch ingest, and quickstart.

Draft until mono#3519 is deployed and the v5 SDK is regenerated: `supermemory@5.0.1` doesn't type `system.dreamingStatus` yet, so the new TS examples won't compile before then. The endpoint reference pages come from the live v5 spec and update on their own.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-12 21:56:52 +00:00
Mahesh Sanikommu
8c70ffdc8b
fix(tools): save AI SDK 7 images and keep provider peer optional (3.1.1) (#1796) 2026-10-12 13:39:50 -07:00
147 changed files with 4060 additions and 5220 deletions

View file

@ -1,102 +0,0 @@
name: Publish AI SDK
on:
push:
branches:
- main
paths:
- "packages/ai-sdk/package.json"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: ./packages/ai-sdk
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Upgrade npm for trusted publishing support
run: npm install -g npm@latest
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
working-directory: .
run: bun install --frozen-lockfile
- name: Check if version changed
id: version-check
run: |
PACKAGE_NAME=$(jq -r '.name' package.json)
LOCAL_VERSION=$(jq -r '.version' package.json)
if npm view "$PACKAGE_NAME@$LOCAL_VERSION" version >/dev/null 2>&1; then
echo "Version $LOCAL_VERSION already published, skipping."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "Publishing $LOCAL_VERSION."
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Wait for the Tools dependency
if: steps.version-check.outputs.changed == 'true'
run: |
TOOLS_SPEC=$(jq -r '.dependencies["@supermemory/tools"]' package.json)
TOOLS_VERSION=${TOOLS_SPEC#^}
TOOLS_VERSION=${TOOLS_VERSION#~}
if [[ ! "$TOOLS_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
echo "Unsupported @supermemory/tools dependency spec: $TOOLS_SPEC" >&2
exit 1
fi
for attempt in {1..20}; do
PUBLISHED_VERSION=$(npm view "@supermemory/tools@$TOOLS_VERSION" version 2>/dev/null || true)
if [ "$PUBLISHED_VERSION" = "$TOOLS_VERSION" ]; then
echo "@supermemory/tools@$TOOLS_VERSION is available on npm."
exit 0
fi
echo "Waiting for @supermemory/tools@$TOOLS_VERSION (attempt $attempt/20)."
sleep 15
done
echo "@supermemory/tools@$TOOLS_VERSION was not published within five minutes." >&2
exit 1
- name: Build Tools dependency
if: steps.version-check.outputs.changed == 'true'
run: bun run --cwd ../tools build
- name: Build AI SDK package
if: steps.version-check.outputs.changed == 'true'
run: bun run build
- name: Verify packed artifact
if: steps.version-check.outputs.changed == 'true'
run: |
npm pack --dry-run --json > "$RUNNER_TEMP/ai-sdk-pack.json"
jq -e '
(.[0].files | any(.path == "dist/index.js")) and
(.[0].files | any(.path == "dist/index.d.ts"))
' "$RUNNER_TEMP/ai-sdk-pack.json" >/dev/null
- name: Publish
if: steps.version-check.outputs.changed == 'true'
run: npm publish --access public --provenance

View file

@ -26,25 +26,17 @@ Before you begin, ensure you have the following installed:
bun install
```
3. **Set Up Environment Variables**
```bash
# Copy the example environment file
cp .env.example .env.local
# Edit the file with your configuration
# You'll need to add your API keys and database URLs
```
4. **Start the Development Server**
3. **Start the Development Server**
```bash
bun run dev:local
```
This starts each app on plain `localhost` ports (web on 3000, mcp on 8788, docs on 3003, graph on 3004) and points the web app at the public API at `https://api.supermemory.ai` via `NEXT_PUBLIC_BACKEND_URL` in `.env.example`. Sign in with magic-link/email-OTP — Google/GitHub OAuth sign-in won't round-trip back to localhost.
This starts each app on plain `localhost` ports (web on 3000, mcp on 8788, docs on 3003, graph playground on 3004, sdk playground on 3005).
> **Internal team note:** `bun run dev` (without `:local`) routes through [portless](https://github.com/portless/portless) to give every app a stable `*.dev.supermemory.ai` HTTPS URL with shared cookies and a working OAuth proxy. That path requires `bun run setup:dev` once (binds port 443, trusts a local CA). OSS contributors don't need it.
> **Note:** `apps/web` only redirects to the console at `https://console.supermemory.ai`, so it needs no env setup.
> **Internal team note:** `bun run dev` (without `:local`) routes through [portless](https://github.com/portless/portless) to give every app a stable `*.dev.supermemory.ai` HTTPS URL with shared cookies and a working OAuth proxy. That path needs portless set up once (it binds port 443 and trusts a local CA). OSS contributors don't need it.
## 📁 Project Structure
@ -53,24 +45,28 @@ supermemory is organized as a monorepo using Turbo:
```
supermemory/
├── apps/
│ ├── web/ # Next.js web application
│ ├── browser-extension/ # Browser extension (WXT-based)
│ ├── docs/ # Documentation site
│ └── raycast-extension/ # Raycast extension
│ ├── web/ # Redirects to the console
│ ├── mcp/ # Model Context Protocol server
│ ├── docs/ # Documentation site
│ ├── memory-graph-playground/ # Demo app for the memory graph component
│ └── sdk-playground/ # Playground for the SDK integrations
├── packages/
│ ├── ui/ # Shared UI components
│ ├── lib/ # Shared utilities and logic
│ ├── hooks/ # Shared React hooks
│ ├── validation/ # Zod schemas and validation
│ ├── ai-sdk/ # AI SDK for memory operations
│ ├── tools/ # Development tools and utilities
│ ├── openai-sdk-python/ # Python SDK for OpenAI integration
│ ├── openai-sdk-ts/ # TypeScript SDK for OpenAI integration
│ ├── eslint-config/ # ESLint configurations
│ └── typescript-config/ # TypeScript configurations
├── turbo.json # Turbo configuration
├── biome.json # Biome configuration
└── package.json # Root package configuration
│ ├── tools/ # Memory tools for AI SDK, OpenAI, Voltagent and Mastra
│ ├── memory-graph/ # Interactive memory graph component
│ ├── openai-sdk-python/ # Memory tools for OpenAI function calling (Python)
│ ├── agent-framework-python/ # Microsoft Agent Framework integration (Python)
│ ├── pipecat-sdk-python/ # Pipecat integration (Python)
│ ├── cartesia-sdk-python/ # Cartesia Line integration (Python)
│ ├── livekit-sdk-python/ # LiveKit Agents integration (Python)
│ ├── ui/ # Shared UI components
│ ├── lib/ # Shared utilities and logic
│ ├── hooks/ # Shared React hooks
│ ├── validation/ # Zod schemas and validation
│ └── docs-test/ # Runs the code samples from the docs
├── skills/ # Supermemory agent skill
├── turbo.json # Turbo configuration
├── biome.json # Biome configuration
└── package.json # Root package configuration
```
## 🛠️ Development Workflow
@ -101,7 +97,7 @@ bun run build
### Tech Stack
- **Frontend**: Next.js 15, React 19, TypeScript
- **Frontend**: Next.js 16, React 19, TypeScript
- **Styling**: Tailwind CSS, Radix UI components
- **State Management**: Zustand, TanStack Query
- **Build Tool**: Turbo (monorepo)

View file

@ -337,10 +337,28 @@ const client = new Supermemory({
- **Bring any model** — OpenAI, Anthropic, Gemini, Groq, or any OpenAI-compatible endpoint. An interactive wizard walks you through it on first boot.
- **Embeddings** — local `Xenova/bge-base-en-v1.5` by default (no API key); optionally OpenAI, Gemini, or Ollama. Same provider stack as cloud.
- **Offline text memories if you want** — point it at Ollama (`gpt-oss:20b` works great), disable telemetry with `SUPERMEMORY_DISABLE_TELEMETRY=1`, and use local text or file inputs after the first embedding-model download. URL ingestion uses a hosted reader service.
- **Your data, one directory** — everything lives in `./.supermemory`, easy to back up or move.
- **Your data, one directory** — everything lives in `~/.supermemory`, easy to back up or move.
- **Same API as the platform** — prototype locally, ship on the hosted platform by changing `baseURL`.
Read the [self-hosting docs](https://supermemory.ai/docs/self-hosting/overview) — quickstart, [configuration](https://supermemory.ai/docs/self-hosting/configuration), [embeddings](https://supermemory.ai/docs/self-hosting/embeddings), and [local vs. Enterprise](https://supermemory.ai/docs/self-hosting/local-vs-enterprise).
### Run with Docker
Prebuilt images support Linux amd64 and arm64. You don’t need to build or clone the server source.
For a new installation, run these commands in an empty directory and set your own LLM provider key:
```bash
release_url=https://github.com/supermemoryai/supermemory/releases
curl -fL "$release_url/download/server-v0.0.9/compose.yaml" -o compose.yaml
export OPENAI_API_KEY=your_openai_api_key_here
export SUPERMEMORY_VERSION=0.0.9
docker compose up -d --wait --wait-timeout 180
```
The API listens on `http://localhost:6767`. Data and credentials persist in the `supermemory_supermemory-data` volume. Get your local API key with `docker compose exec supermemory cat /data/api-key`; requests require it even on localhost.
Read the [Docker setup and upgrade instructions](apps/docs/self-hosting/docker.mdx) for persistent provider configuration, backups and upgrades. Keep the existing volume and keys when upgrading; **don’t run `docker compose down -v`**.
Read the [self-hosting docs](https://supermemory.ai/docs/self-hosting/overview) — [quickstart](https://supermemory.ai/docs/self-hosting/quickstart), [configuration](https://supermemory.ai/docs/self-hosting/configuration), [embeddings](https://supermemory.ai/docs/self-hosting/embeddings), and [local vs. Enterprise](https://supermemory.ai/docs/self-hosting/local-vs-enterprise).
---

View file

@ -28,7 +28,6 @@ Useful for coding agents:
```bash
npx supermemory setup # detect project, launch/print integration flow
npx supermemory setup --prompt # print integration prompt only
npx supermemory setup --json # machine-readable output
npx supermemory help --json # agent-readable command catalog
npx supermemory help --all
npx supermemory migrate # move a v3/v4 project to v5 with your agent
@ -210,7 +209,7 @@ DO NOT USE — deprecated, undocumented, or fabricated:
- Endpoints: /v1/anything, /v3/documents, /v3/search, /v4/search, /v4/profile, /v4/memories (use /ns/{namespace}/...)
- Headers: x-supermemory-api-key, x-api-key, x-sm-user-id (for API auth)
- Body keys: containerTag, containerTags, customId (use `id`), entityContext (use `supportingContext`), filterByMetadata (use `group`), documentDate (use `date`), q (use `query`), filters (use `filter`), userId, spaces
- SDK: `client.add({ containerTag })`, `client.search.execute`, `client.memories.*`, `client.profile({ q })`
- SDK: `client.add({ containerTag })`, `client.search.execute`, `client.memories.add` / `client.memories.updateMemory`, `client.profile({ q })`
SCOPING IS LOAD-BEARING. Every write and every search MUST name one namespace in the path.

View file

@ -16,7 +16,7 @@ v5 calls these **namespaces**. Same values. New integrations should use [v5 name
| `GET /v3/container-tags/{containerTag}` | Read tag settings |
| `PATCH /v3/container-tags/{containerTag}` | Update tag settings |
| `DELETE /v3/container-tags/{containerTag}` | Delete a container and its data |
| `POST /v3/container-tags/merge` | Merge one tag into another |
| `POST /v3/container-tags/merge` | Merge exactly two listed tags into either listed tag or a new target tag |
| `GET /v3/container-tags/merge/{mergeId}` | Poll merge status |
**Guide:** [Container tags](/concepts/container-tags) · [Filtering](/concepts/filtering)

View file

@ -63,7 +63,7 @@ Official clients wrap this API:
See [Supermemory SDK](/integrations/supermemory-sdk).
Playground snippets come from the OpenAPI spec: official **TypeScript / Python SDK** samples via `x-codeSamples`, plus cURL. (After API deploy — until then you may still see generic HTTP snippets.)
The API's OpenAPI generator supplies official **TypeScript / Python SDK** samples via `x-codeSamples`, plus cURL. Playground snippets use the deployed spec, so their availability depends on the API version serving it.
SDK generation is migrating off Stainless SaaS to **stlc** soon; documented OpenAPI samples will then be produced by the SDK build instead of a hand-maintained map.

View file

@ -50,11 +50,15 @@ This works for Google Drive, Notion, and OneDrive. See the full setup in [Custom
## Scoped API keys
Scoped keys are restricted to one namespace (what v3/v4 called a container tag). They can only access documents and search within that namespace — use them to give a client, session, or tenant limited access without shipping your org master key.
Scoped keys are restricted to one namespace (what v3/v4 called a container tag), or to a fixed list of namespaces. They can read and write content only inside those namespaces, subject to the key's read/write permission and member restrictions — use them to give a client, session, or tenant limited access without shipping your org master key.
<Warning>
Namespace scoping is not read-only access. Writable scoped keys can update and delete documents and forget memories. Deleting or moving a namespace additionally requires an org admin or owner with write access to the source and, for moves, the destination.
</Warning>
Pairs with [namespaces](/concepts/container-tags) for multi-tenant isolation.
**Allowed endpoints:** `/v3/documents`, `/v3/memories`, `/v4/memories`, `/v3/search`, `/v4/search`, `/v4/profile`
**Allowed endpoints:** `/ns/{namespace}/document`, `/ns/{namespace}/list`, `/ns/{namespace}/search`, `/ns/{namespace}/profile`, and `/ns/{namespace}/memories`; `/namespaces` and `/ns/{namespace}`; and the legacy `/v3/documents`, `/v3/memories`, `/v4/memories`, `/v4/conversations`, `/v3/search`, `/v4/search`, `/v4/profile`, `/v3/container-tags/list`, and `/v3/session` routes. Any other route returns `403`.
Scoped keys **cannot** read billing, manage org settings, or mint further keys.
@ -78,7 +82,8 @@ curl https://api.supermemory.ai/v3/auth/scoped-key \
| Parameter | Required | Default | Description |
| --- | --- | --- | --- |
| `containerTag` | Yes | — | The namespace to scope the key to. Alphanumeric, hyphens, underscores, colons, dots |
| `containerTag` | Yes, unless `containerTags` is set | — | The namespace to scope the key to. Up to 100 characters: letters, numbers, hyphens, underscores, colons |
| `containerTags` | No | — | Scope the key to several namespaces instead of one. Takes precedence over `containerTag` |
| `name` | No | `scoped_{containerTag}` | Display name for the key |
| `expiresInDays` | No | — | 1–365 days |
| `rateLimitMax` | No | `500` | Max requests per window (1–10,000) |
@ -92,8 +97,9 @@ curl https://api.supermemory.ai/v3/auth/scoped-key \
"id": "key-id",
"name": "scoped_my-project",
"containerTag": "my-project",
"containerTags": ["my-project"],
"expiresAt": "2026-03-08T00:00:00.000Z",
"allowedEndpoints": ["/v3/documents", "/v3/memories", "/v4/memories", "/v3/search", "/v4/search", "/v4/profile"]
"allowedEndpoints": ["/v3/session", "/v3/documents", "/v3/memories", "/v4/memories", "/v4/conversations", "/ns/*/memories", "/v3/search", "/v4/search", "/v4/profile", "/v3/container-tags/list", "/ns/*/document", "/ns/*/list", "/ns/*/search", "/ns/*/profile", "/namespaces$", "/ns$", "/ns/*$"]
}
```
@ -111,4 +117,4 @@ curl https://api.supermemory.ai/v3/auth/scoped-key/KEY_ID \
```json
{ "success": true }
```
```

View file

@ -97,7 +97,7 @@ The colon is intentionally allowed so you can build **hierarchical** namespaces
## Scope lives in the URL
In v5 the namespace is a path segment, not a body field. Every scoped endpoint starts with `/ns/{namespace}/`, and every SDK call takes `namespace` as a top-level key next to `body`.
In v5 the namespace is a path segment, not a body field. Every scoped endpoint starts with `/ns/{namespace}/`, and every scoped SDK call takes `namespace` as its first argument.
| Legacy (v3/v4) | v5 |
|-----------|------|
@ -138,8 +138,8 @@ await supermemory.list("project_q1", "documents");
Namespaces are also an **authorization boundary**, not just an organizational one. Two mechanisms can restrict which namespaces a given caller may touch:
- **API key scopes** — an API key can be limited to a specific set of namespaces, with read or write permission per namespace.
- **Member restrictions** — an organization member can be granted access to only certain namespaces.
- **API key scopes** — an API key can be limited to a specific set of namespaces, and a key can be read-only.
- **Member restrictions** — an organization member can be granted access to only certain namespaces, with read or write permission per namespace.
When a request is restricted, Supermemory validates the namespace in the URL against the caller's allowed set:
@ -159,11 +159,13 @@ The `namespaces.*` SDK methods read and change a namespace itself, as opposed to
| `supermemory.namespaces.list()` | Page through namespaces with `documentCount` and `memoryCount`. |
| `supermemory.namespaces.get(namespace)` | Read one namespace and its `supportingContext`. |
| `supermemory.namespaces.update(namespace, { supportingContext })` | Set a context prompt applied when processing documents in this namespace. Useful for steering extraction per project or tenant. |
| `supermemory.namespaces.delete(namespace)` | Delete the namespace and all its documents and memories. |
| `supermemory.namespaces.delete(namespace)` | Delete the namespace, its namespace-owned memory records, and documents exclusive to it. Shared documents are retained. |
| `supermemory.namespaces.delete(namespace, { moveTo })` | Move the content into another namespace, then remove this one. Queued; returns an `operationId`. |
`supportingContext` is what v3/v4 called `entityContext`. The legacy "merge" operation is now a delete with `moveTo`, one source namespace per request.
Namespace deletion and moves require an org admin or owner with write access to the source namespace; moves also require write access to the destination. The default `sm_project_default` namespace cannot be permanently deleted. See [deletion scope and erasure limits](/overview/security).
<CodeGroup>
```typescript TypeScript
@ -178,7 +180,7 @@ const { namespaces, pagination } = await supermemory.namespaces.list();
await supermemory.namespaces.delete("project_research_old", {
moveTo: "project_research",
});
// { success, status: "queued", operationId, namespace, moveTo }
// { status: "queued", operationId, namespace, moveTo }
```
```bash curl
@ -190,10 +192,8 @@ curl -X PATCH "https://api.supermemory.ai/ns/project_research" \
curl "https://api.supermemory.ai/namespaces" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
curl -X DELETE "https://api.supermemory.ai/ns/project_research_old" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "moveTo": "project_research" }'
curl -X DELETE "https://api.supermemory.ai/ns/project_research_old?moveTo=project_research" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
```
</CodeGroup>

View file

@ -5,7 +5,7 @@ description: "All the content formats Supermemory can ingest and process"
icon: "/icons/hugeicons/files-01.svg"
---
Supermemory automatically extracts and indexes content from various formats. There are two entry points: `supermemory.add()` for text and URLs, `supermemory.documents.uploadFile()` for actual files. Both take the target `namespace` as a top-level key. See [Add Memories](/ingestion/add-memories) to learn how to ingest content via the API.
Supermemory automatically extracts and indexes content from various formats. There are two entry points: `supermemory.add()` for text and URLs, `supermemory.documents.uploadFile()` for actual files. Both take the target `namespace` as the first argument. See [Add Memories](/ingestion/add-memories) to learn how to ingest content via the API.
## Text content
@ -31,7 +31,7 @@ await supermemory.add("documentation", {
});
```
**Extracts:** Article text, headings, metadata. Strips navigation, ads, boilerplate. URL extraction is powered by [Markdowner](https://md.dhr.wtf).
**Extracts:** Article text, headings, metadata. Strips navigation, ads, boilerplate.
---
@ -102,13 +102,13 @@ await supermemory.add("user_123", {
**Extracts:** Structure, headings, code blocks with syntax awareness.
Code is chunked using [code-chunk](https://github.com/supermemoryai/code-chunk), which understands AST boundaries to keep functions, classes, and logical blocks intact. See [Super RAG](/concepts/super-rag) for how Supermemory optimizes chunking for each content type.
See [Super RAG](/concepts/super-rag) for how Supermemory optimizes chunking for each content type.
---
## Images
Set `fileType: "image"` and `mimeType` so Supermemory knows exactly how to process it. Both are query parameters, so they sit next to `namespace` rather than inside `body`:
Set `fileType: "image"` and `mimeType` so Supermemory knows exactly how to process it. Both are multipart form fields, sent in the same object as `file` and `metadata`:
```typescript
await supermemory.documents.uploadFile("user_123", {
@ -127,7 +127,7 @@ await supermemory.documents.uploadFile("user_123", {
## Audio & video
Video has a dedicated `fileType`; audio is uploaded the same way and detected from the file itself:
Video and audio each have a dedicated `fileType` (`"video"`, `"audio"`); set it with `mimeType` when inference is not enough:
```typescript
// Video
@ -142,6 +142,7 @@ await supermemory.documents.uploadFile("user_123", {
await supermemory.documents.uploadFile("user_123", {
file: fs.createReadStream('call-recording.mp3'),
metadata: JSON.stringify({ title: "Customer Call Recording" }),
fileType: "audio",
mimeType: "audio/mpeg",
});
```
@ -215,7 +216,7 @@ await supermemory.add("user_123", { content: "https://example.com/page" });
await supermemory.add("user_123", { content: "User said they prefer email contact" });
```
For files, `uploadFile` detects type from the file itself in most cases. `fileType` and `mimeType` only exist to force specific processing when inference is not enough — set them for images and video.
For files, `uploadFile` detects type from the file itself in most cases. `fileType` and `mimeType` only exist to force specific processing when inference is not enough — set them for images, video, and audio.
---
@ -227,7 +228,7 @@ For files, `uploadFile` detects type from the file itself in most cases. `fileTy
| Files | 50MB |
| URLs | Fetched content up to 10MB |
**Typical processing time:** text is near-instant; PDFs take 1-5s; images 2-10s; video 10s+; webpages 1-3s. Text content is chunked at the sentence level with a 2-sentence overlap between chunks.
**Typical processing time:** text is near-instant; PDFs take 1-5s; images 2-10s; video 10s+; webpages 1-3s. Text content is split on sentence boundaries, with about 200 characters of overlap carried between chunks.
<Tip>
For large files, consider chunking or using [connectors](/connectors/overview) for automatic sync.

View file

@ -27,7 +27,6 @@ await supermemory.organization.update({
- Outdated brand materials (pre-2024)
- Internal discussions about brand changes
- Competitor analysis docs`,
},
});
```
@ -103,6 +102,10 @@ Supporting context persists on the namespace and combines with the org-level org
---
<Note>
The Chunk size and Connector branding sections below use the legacy settings resource. Their `client.settings.update(...)` examples require a legacy SDK that exposes `settings`; SDK 5.x users should call `PATCH /v3/settings` directly with an org admin or owner credential. These options are not fields on the v5 organization resource.
</Note>
## Chunk size
Control how documents are split into searchable pieces. Smaller chunks = more precise retrieval but less context per result.
@ -133,7 +136,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
<AccordionGroup>
<Accordion title="Google Drive">
1. Create OAuth credentials in [Google Cloud Console](https://console.cloud.google.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/google-drive/callback`
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/google-drive`
```typescript
await client.settings.update({
@ -145,7 +148,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
</Accordion>
<Accordion title="Notion">
1. Create integration at [Notion Developers](https://developers.notion.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/notion/callback`
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/notion`
```typescript
await client.settings.update({
@ -157,7 +160,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
</Accordion>
<Accordion title="OneDrive">
1. Register app in [Azure Portal](https://portal.azure.com/)
2. Redirect URI: `https://api.supermemory.ai/v3/connections/onedrive/callback`
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/onedrive`
```typescript
await client.settings.update({

View file

@ -22,7 +22,7 @@ Both can be used independently or together for precise filtering.
## Namespaces
Namespaces create isolated memory spaces. Use them to separate memories by user, project, or any logical boundary. The namespace is part of the URL (`/ns/{namespace}/...`), so every SDK call takes it as a top-level key.
Namespaces create isolated memory spaces. Use them to separate memories by user, project, or any logical boundary. The namespace is part of the URL (`/ns/{namespace}/...`), so namespace-scoped SDK calls take it as the first argument.
### Adding Memories to a Namespace
@ -42,7 +42,7 @@ const results = await supermemory.search("user_123", {
```
<Note>
Each search is scoped to a single namespace. Passing `namespace: "user_123"` restricts results to memories stored in that namespace.
Each search is scoped to a single namespace. Passing `"user_123"` as the namespace restricts results to memories stored in that namespace.
</Note>
### Recommended patterns
@ -336,7 +336,6 @@ const results = await supermemory.search("user_123", { // Scopes results to this
### Metadata key rules
- Allowed characters: `a-z`, `A-Z`, `0-9`, `_`, `-`, `.`
- Max length: 64 characters
- No spaces or special characters
- Keys are literal: `customer.plan` is one key named `customer.plan`, not a nested path

View file

@ -27,7 +27,7 @@ await supermemory.add("user_123", {
dreaming: "instant", // memories land in the graph right away
});
const results = await supermemory.search("user_123", {
const { results } = await supermemory.search("user_123", {
query: "where does Alex work?",
include: { related: true },
searchMode: "memories",
@ -154,7 +154,7 @@ await supermemory.add("user_123", {
content: "Alex mentioned he just started at Stripe",
});
const results = await supermemory.search("user_123", {
const { results } = await supermemory.search("user_123", {
query: "where does Alex work?",
include: { related: true },
searchMode: "memories",

View file

@ -61,10 +61,10 @@ results = client.search("user_123", query="where does the user want to travel?")
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_123/document?dreaming=instant" \
curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-d '{ "content": "The user loves Paris." }'
-d '{ "content": "The user loves Paris.", "dreaming": "instant" }'
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
-H "Authorization: Bearer sm_..." \
@ -118,7 +118,7 @@ A document with status `done` has its chunks indexed for search. Memories (the g
This is when the content is passed through the memory model and merged, arranged and organized for the future.
Pass `dreaming` on [add](/ingestion/add-memories) (a query parameter, so a top-level key in the SDK):
Pass `dreaming` on [add](/ingestion/add-memories) (a field in the JSON body, next to `content` in the SDK):
| Mode | Default? | Behavior | When to use |
| --- | --- | --- | --- |

View file

@ -30,7 +30,7 @@ Agents benefit most from having a _general_ idea of the topic alongside tools to
Two things about this table that trip people up.
**"Remember" and "look up" are both supermemory, but different reads.** You [ingest documents](/ingestion/add-memories); the pipeline derives memories from them and maintains a profile per [namespace](/concepts/container-tags). `supermemory.search({ searchMode: "memories" })` recalls the derived facts. `supermemory.search({ searchMode: "chunks" })` recalls the source material itself. A support agent usually needs both: memories for "this customer runs self-hosted and already tried reinstalling", documents for the actual troubleshooting guide.
**"Remember" and "look up" are both supermemory, but different reads.** You [ingest documents](/ingestion/add-memories); the pipeline derives memories from them and maintains a profile per [namespace](/concepts/container-tags). `supermemory.search(namespace, { query, searchMode: "memories" })` recalls the derived facts. `supermemory.search(namespace, { query, searchMode: "chunks" })` recalls the source material itself. A support agent usually needs both: memories for "this customer runs self-hosted and already tried reinstalling", documents for the actual troubleshooting guide.
**Supermemory is not your system of record.** There's no SQL over memories, no joins, no aggregates, no querying by primary key. Keep transactional data in your database, and ingest the narrative *around* it ("the customer disputed invoice #4821 and churned over it") so your AI understands what the rows mean.
@ -56,10 +56,10 @@ client.add(
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/test/document?taskType=superrag" \
curl -X POST "https://api.supermemory.ai/ns/test/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "testing" }'
-d '{ "content": "testing", "taskType": "superrag" }'
```
</CodeGroup>
@ -86,10 +86,10 @@ results = client.search(
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/test/search?searchMode=hybrid" \
curl -X POST "https://api.supermemory.ai/ns/test/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "test" }'
-d '{ "query": "test", "searchMode": "hybrid" }'
```
</CodeGroup>
@ -252,11 +252,12 @@ results = client.search(
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search?searchMode=chunks" \
curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "preferred language",
"searchMode": "chunks",
"filter": { "field": "team", "operator": "eq", "value": "Research" }
}'
```

View file

@ -33,7 +33,7 @@ No chunking strategies to configure. No embedding models to choose. It just work
By default, every `supermemory.add()` call runs on the **memory** path (`taskType: "memory"`): Supermemory chunks and embeds the content for retrieval, *and* runs it through the memory pipeline — extracting facts, updating the profile, and linking it into the knowledge graph.
If you're ingesting content that's purely reference material — documentation, a large PDF, a knowledge base article — and you don't need Supermemory to derive personal facts or update a profile from it, set `taskType: "superrag"` (a query parameter, so a top-level key in the SDK). It skips the memory pipeline entirely and only does the chunk → embed → index work needed to make the content searchable.
If you're ingesting content that's purely reference material — documentation, a large PDF, a knowledge base article — and you don't need Supermemory to derive personal facts or update a profile from it, set `taskType: "superrag"` in the request body. It skips the memory pipeline entirely and only does the chunk → embed → index work needed to make the content searchable.
<CodeGroup>
@ -53,10 +53,10 @@ client.add(
```
```bash cURL
curl -X POST "https://api.supermemory.ai/ns/docs_kb/document?taskType=superrag" \
curl -X POST "https://api.supermemory.ai/ns/docs_kb/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "..." }'
-d '{ "content": "...", "taskType": "superrag" }'
```
</CodeGroup>
@ -100,21 +100,9 @@ PDFs and documents are chunked by **semantic sections** — headers, paragraphs,
### Code
Code is chunked using [code-chunk](https://github.com/supermemoryai/code-chunk), our open-source library that understands AST (Abstract Syntax Tree) boundaries:
When content is recognized as Markdown, fenced code blocks are kept whole when they fit in a chunk. Larger blocks are split and re-wrapped in the original fence, including its language tag. Overlap can contain part of a previous line, so chunks are not guaranteed to be independently valid code.
- Functions and methods stay intact
- Classes are chunked by method
- Import statements grouped separately
- Comments attached to their code blocks
```typescript
// A 500-line file becomes meaningful chunks:
// - Imports + type definitions
// - Each function as a separate chunk
// - Class methods individually indexed
```
This means searching for "authentication middleware" finds the actual function, not a random slice of code.
Raw source files and content not recognized as Markdown use plain-text chunking. A lone fenced block may not be enough for automatic Markdown detection.
### Web pages

View file

@ -94,7 +94,7 @@ Recent context and temporary states:
Static and dynamic split facts by how long-lived they are. **Buckets** split them by *topic* — a third, independent axis you define, like `preferences`, `goals`, or `work`. As content is ingested, a classifier sorts each fact into the buckets it matches.
Every org starts with a default `preferences` bucket. Add your own in console settings at the organization level, or per namespace with `supermemory.profiles.setBuckets()` — namespace buckets are add-only, so a namespace always keeps every org-level bucket.
Every org starts with a default `preferences` bucket, which is always present — custom buckets are added alongside it, never in place of it. Add your own in console settings at the organization level, or per namespace with `supermemory.profiles.setBuckets()` — namespace buckets are add-only, so a namespace always keeps every org-level bucket.
```typescript
// Define a namespace-level bucket (name → description)
@ -110,7 +110,7 @@ console.log(profile.buckets.preferences); // [{ id, memory }, ...]
console.log(profile.buckets.goals);
```
Bucket descriptions steer the classifier, so a precise description ("explicit first-person preferences only, exclude inferred traits") produces cleaner buckets than a vague one. Buckets are separate from [`filterPrompt`](/concepts/customization), which controls what gets ingested at all — buckets only organize facts that already made it into the profile.
Bucket descriptions steer the classifier, so a precise description ("explicit first-person preferences only, exclude inferred traits") produces cleaner buckets than a vague one. Buckets are separate from [`organizationalContext`](/concepts/customization#organizational-context) (called `filterPrompt` in v3/v4), which steers what gets extracted at all — buckets only organize facts that already made it into the profile.
<Card title="Profile Buckets reference" icon="/icons/hugeicons/tag-01.svg" href="/user-profiles/buckets">
Request bucketed profiles, create buckets at the org or namespace level, and see validation limits.

View file

@ -24,7 +24,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
const connector = await supermemory.connectors.create("user-123", {
provider: "github",
redirectUrl: "https://yourapp.com/auth/github/callback"
redirectUrl: "https://yourapp.com/auth/github/callback",
documentLimit: 5000,
})
@ -44,8 +44,8 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
"user-123",
request={
"provider": "github",
"redirectUrl": "https://yourapp.com/auth/github/callback",
"documentLimit": 5000,
"redirect_url": "https://yourapp.com/auth/github/callback",
"document_limit": 5000,
},
)
@ -68,7 +68,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://github.com/login/oauth/authorize?...",
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
@ -85,7 +85,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
### 2. Handle OAuth callback
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, GitHub redirects to your `redirectUrl`. A pending OAuth connector is not visible in list or get until the user finishes authorization. Once it is visible, the user can select which repositories to sync.
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Supermemory sends them to a hosted repository picker; once they choose repositories, they return to your `redirectUrl`. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
### 3. Select Repositories
@ -224,7 +224,9 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
print(f"Webhooks: {connector.capabilities.webhooks}")
print(f"Last synced: {connector.system.last_successful_sync_at}")
print(f"Last sync: {connector.latest_run.system.status} {connector.latest_run.error}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
if connector.latest_run and connector.latest_run.error:
print(connector.latest_run.error)
repos = (connector.selection or {}).get("repos") or []
print(f"Repositories: {[r.name for r in repos]}")
print(f"Recent runs: {connector.syncs}")
@ -255,7 +257,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.createdAt}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log(`Repositories: ${connector.selection?.repos?.length ?? 0}`)
console.log("---")
@ -271,7 +273,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.created_at}")
print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}")
repos = (connector.selection or {}).get("repos") or []
print(f"Repositories: {len(repos)}")

View file

@ -24,7 +24,7 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
const connector = await supermemory.connectors.create("user-123", {
provider: "gmail",
redirectUrl: "https://yourapp.com/auth/gmail/callback"
redirectUrl: "https://yourapp.com/auth/gmail/callback",
documentLimit: 5000,
})
@ -44,8 +44,8 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
"user-123",
request={
"provider": "gmail",
"redirectUrl": "https://yourapp.com/auth/gmail/callback",
"documentLimit": 5000,
"redirect_url": "https://yourapp.com/auth/gmail/callback",
"document_limit": 5000,
},
)
@ -68,7 +68,7 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://accounts.google.com/o/oauth2/v2/auth?...",
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
@ -78,7 +78,9 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
### 2. Handle OAuth callback
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Google redirects to your `redirectUrl` and the initial sync begins. A pending OAuth connector is not visible in list or get until the user finishes authorization.
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Supermemory sends them to a hosted label picker; once they choose labels, they return to your `redirectUrl` and the initial sync begins. Nothing syncs until at least one label is selected. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
To change the labels later, read the connector with `include: "picker"` and send the user to `picker.url`, or set label ids directly with `connectors.update(namespace, id, { selection: { labels: ["INBOX"] } })`. See [Managing Connector Selection](/connectors/managing-resources).
### 3. Check Connector Status
@ -106,7 +108,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Connected email: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Threads synced: {connector.document_count}")
# List synced email threads in the namespace
@ -155,8 +157,8 @@ Each synced thread includes searchable metadata:
| `type` | Always `gmail_thread` |
| `subject` | Email subject line |
| `threadId` | Gmail thread ID |
| `from` | Sender email address |
| `to` | Recipient email addresses |
| `from` | Sender, as the raw `From` header (for example `Team <team@company.com>`) |
| `to` | Recipient addresses, comma-separated |
| `date` | Date of first message |
| `messageCount` | Number of messages in thread |
| `attachmentCount` | Number of attachments (if any) |
@ -171,7 +173,7 @@ const results = await supermemory.search("user-123", {
operator: "and",
operands: [
{ field: "type", operator: "eq", value: "gmail_thread" },
{ field: "from", operator: "eq", value: "team@company.com" },
{ field: "from", operator: "contains", value: "team@company.com" },
],
},
searchMode: "chunks",
@ -192,7 +194,7 @@ const results = await supermemory.search("user-123", {
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.createdAt}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log("---")
})
@ -207,7 +209,7 @@ const results = await supermemory.search("user-123", {
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.created_at}")
print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}")
print("---")
```
@ -308,13 +310,13 @@ Gmail connector supports multiple sync methods:
### How real-time sync works
1. When a connector is created, supermemory registers a Gmail API "watch" subscription
1. After labels are selected, supermemory registers a Gmail API "watch" subscription when Pub/Sub is configured
2. Gmail sends notifications to a Google Cloud Pub/Sub topic when emails change
3. supermemory receives these notifications and fetches updated threads
4. Watch subscriptions expire after 7 days and are automatically renewed
<Note>
Real-time sync monitors the **INBOX** label. Emails in other labels are synced via scheduled/manual sync.
Real-time sync monitors the selected labels, including labels outside **INBOX**. If multiple connectors use the same mailbox, its watch covers their combined label selections.
</Note>
## Permissions & scopes

View file

@ -32,7 +32,7 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
const connector = await supermemory.connectors.create("user-123", {
provider: "google-drive",
redirectUrl: "https://yourapp.com/auth/google-drive/callback"
redirectUrl: "https://yourapp.com/auth/google-drive/callback",
documentLimit: 3000,
config: { syncScope: "selected" },
})
@ -53,9 +53,9 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
"user-123",
request={
"provider": "google-drive",
"redirectUrl": "https://yourapp.com/auth/google-drive/callback",
"documentLimit": 3000,
"config": {"syncScope": "selected"},
"redirect_url": "https://yourapp.com/auth/google-drive/callback",
"document_limit": 3000,
"config": {"sync_scope": "selected"},
},
)
@ -79,7 +79,7 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://accounts.google.com/o/oauth2/v2/auth?...",
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
@ -93,7 +93,7 @@ For **whole Drive** sync, send `"config": { "syncScope": "full" }` on the same `
### 2. Handle OAuth callback
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Google redirects through Supermemory to finish the connector. A pending OAuth connector is not visible in list or get until the user finishes authorization.
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Google redirects through Supermemory to finish the connector. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
With **scoped** sync (`syncScope` omitted or `"selected"`), the user is sent to Supermemory’s **hosted file and folder picker**; they must complete that step before syncs run. With **`syncScope: "full"`**, Supermemory redirects to your `redirectUrl` **without** the picker.
@ -173,7 +173,7 @@ If you already know the Drive file and folder ids, set them directly. A new sele
print(f"Account: {connector.account}")
print(f"Selection: {connector.selection}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
# List synced documents in the namespace
documents = client.list("user-123", "documents").documents
@ -205,6 +205,8 @@ Based on the API type definitions, Google Drive documents are identified with th
- `google_slide` - Google Slides
- `google_sheet` - Google Sheets
PDF and Markdown files stored in Drive are synced too.
<Note>
Drive documents are converted to markdown before ingestion. This conversion is lossy — some formatting may not be preserved.
</Note>
@ -223,7 +225,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
console.log(`Provider: ${connector.provider}`)
console.log(`ID: ${connector.id}`)
console.log(`Account: ${connector.account}`)
console.log(`Created: ${connector.createdAt}`)
console.log(`Created: ${connector.system.createdAt}`)
console.log(`Document limit: ${connector.documentLimit}`)
console.log("---")
})
@ -238,7 +240,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
print(f"Provider: {connector.provider}")
print(f"ID: {connector.id}")
print(f"Account: {connector.account}")
print(f"Created: {connector.created_at}")
print(f"Created: {connector.system.created_at}")
print(f"Document limit: {connector.document_limit}")
print("---")
```
@ -263,7 +265,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
# }
# ],
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# }
```
</Tab>

View file

@ -61,9 +61,9 @@ The console limits connector setup to 500 documents. Use the API setup below for
request={
"provider": "granola",
"config": {
"apiKey": os.environ["GRANOLA_API_KEY"],
"api_key": os.environ["GRANOLA_API_KEY"],
},
"documentLimit": 1000,
"document_limit": 1000,
},
)

View file

@ -83,7 +83,7 @@ curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=pi
```
<Note>
The picker link works once, in any browser. `picker` is `null` for connectors that sync everything.
The picker link works once, in any browser. `picker` is `null` for providers that do not support selection. A Google Drive connector with full-drive sync can still open the picker; saving a selection switches it to scoped sync. Because the link changes what syncs, `include=picker` needs an admin key with write access to the namespace; otherwise the request returns `403`.
</Note>
## Set the Selection Directly
@ -121,7 +121,7 @@ connector = client.connectors.update(
)
print(f"Selection: {connector.selection}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
```
```bash cURL
@ -173,9 +173,11 @@ The updated connector object, including the saved `selection` as `{ id, name }`
### Error Responses
- `400`: Connector does not support selection
- `400`: Connector does not support selection, the selection is empty or has unknown ids, or the connector needs to be reconnected
- `401`: Unauthorized
- `403`: The key lacks admin role or write access to the connector's namespaces
- `404`: Connector not found
- `409`: Connector is still waiting for the user to finish authorizing
<Note>
**Automatic Sync:** After a selection is saved, a sync starts for the connector. You don't need to call `connectors.sync` afterwards.
@ -193,10 +195,11 @@ const created = await supermemory.connectors.create("user-123", {
redirectUrl: "https://yourapp.com/callback"
})
// 2. Send the user to GitHub; the connector becomes visible once they authorize
// 2. Send the user to GitHub. After they authorize, Supermemory shows the hosted
// repository picker, then returns them to redirectUrl
if (created.authorization) window.location.href = created.authorization.url
// 3. After the callback, open the hosted picker so the user chooses repositories
// 3. To change repositories later, open the hosted picker again
const connector = await supermemory.connectors.get("user-123", created.id, {
include: "picker",
returnUrl: "https://yourapp.com/settings/integrations"
@ -215,14 +218,15 @@ created = client.connectors.create(
"user-123",
request={
"provider": "github",
"redirectUrl": "https://yourapp.com/callback",
"redirect_url": "https://yourapp.com/callback",
},
)
# 2. Send the user to GitHub; the connector becomes visible once they authorize
# 2. Send the user to GitHub. After they authorize, Supermemory shows the hosted
# repository picker, then returns them to redirect_url
print(f"Redirect to: {created.authorization.url}")
# 3. After the callback, open the hosted picker so the user chooses repositories
# 3. To change repositories later, open the hosted picker again
connector = client.connectors.get(
"user-123",
created.id,
@ -247,9 +251,9 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
-H "Content-Type: application/json" \
-d '{"provider": "github", "redirectUrl": "https://yourapp.com/callback"}'
# 2. ... (user authorizes at authorization.url) ...
# 2. ... (user authorizes at authorization.url and picks repositories in the hosted picker) ...
# 3. Get a one-time hosted picker URL
# 3. To change repositories later, get a one-time hosted picker URL
curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=picker" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"

View file

@ -19,7 +19,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
const connector = await supermemory.connectors.create("user-123", {
provider: "notion",
redirectUrl: "https://yourapp.com/auth/notion/callback"
redirectUrl: "https://yourapp.com/auth/notion/callback",
documentLimit: 2000,
})
@ -38,8 +38,8 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
"user-123",
request={
"provider": "notion",
"redirectUrl": "https://yourapp.com/auth/notion/callback",
"documentLimit": 2000,
"redirect_url": "https://yourapp.com/auth/notion/callback",
"document_limit": 2000,
},
)
@ -61,7 +61,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.notion.com/v1/oauth/authorize?...",
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
@ -71,7 +71,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
### 2. Handle OAuth flow
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants workspace access, Notion redirects to your `redirectUrl` and the first sync starts. A pending OAuth connector is not visible in list or get until the user finishes authorization.
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants workspace access, Notion redirects to your `redirectUrl` and the first sync starts. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
### 3. Monitor sync progress
@ -97,7 +97,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Connected workspace: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Documents synced: {connector.document_count}")
# List synced pages and databases
@ -268,15 +268,15 @@ Notion connector respects workspace permissions:
### Database properties
Notion database properties are mapped to metadata:
Notion database properties are mapped to metadata keys named `notion_prop_<property name>`, lowercased with spaces replaced by underscores (a `Status` property becomes `notion_prop_status`). The page title is stored as `title`, and every page has `type: "notion_doc"`.
```typescript
// List documents in the namespace (there is no per-connector document list in v5)
const { documents } = await supermemory.list("user-123", "documents")
// Find database entries
// Find database entries by a property value
const projectEntries = documents.filter(doc =>
doc.metadata?.database === "Projects"
doc.metadata?.notion_prop_status === "In Progress"
)
// Database properties become searchable metadata
@ -285,8 +285,8 @@ const projectWithStatus = await supermemory.search("user-123", {
filter: {
operator: "and",
operands: [
{ field: "status", operator: "eq", value: "In Progress" },
{ field: "priority", operator: "eq", value: "High" },
{ field: "notion_prop_status", operator: "eq", value: "In Progress" },
{ field: "notion_prop_priority", operator: "eq", value: "High" },
],
},
searchMode: "chunks",

View file

@ -21,13 +21,13 @@ const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "onedrive",
redirectUrl: "https://yourapp.com/auth/onedrive/callback"
redirectUrl: "https://yourapp.com/auth/onedrive/callback",
documentLimit: 1500,
})
// Send the user to Microsoft to authorize
if (connector.authorization) window.location.href = connector.authorization.url
// Output: Redirects to https://login.microsoftonline.com/oauth2/authorize?...
// Output: Redirects to https://api.supermemory.ai/v3/connections/auth/redirect?state=...
```
```python Python
@ -40,14 +40,14 @@ connector = client.connectors.create(
"user-123",
request={
"provider": "onedrive",
"redirectUrl": "https://yourapp.com/auth/onedrive/callback",
"documentLimit": 1500,
"redirect_url": "https://yourapp.com/auth/onedrive/callback",
"document_limit": 1500,
},
)
# Send the user to Microsoft to authorize
print(f"Redirect to: {connector.authorization.url}")
# Output: Redirect to: https://login.microsoftonline.com/oauth2/authorize?...
# Output: Redirect to: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
```
```bash cURL
@ -63,7 +63,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://login.microsoftonline.com/oauth2/authorize?...",
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
@ -73,7 +73,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
### 2. Handle Microsoft OAuth
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Microsoft redirects to your `redirectUrl` and the initial sync begins. A pending OAuth connector is not visible in list or get until the user finishes authorization.
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Microsoft redirects to your `redirectUrl` and the initial sync begins. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
### 3. Monitor sync status
@ -96,7 +96,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Account: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Documents synced: {connector.document_count}")
# List synced Office documents in the namespace

View file

@ -7,7 +7,7 @@ icon: "/icons/hugeicons/layers-01.svg"
Connect external platforms to automatically sync documents into supermemory. Supported connectors include Google Drive, Gmail, Notion, OneDrive, GitHub, Granola and Web Crawler with real-time synchronization and intelligent content processing.
Every connector belongs to one namespace. The namespace goes in the URL (`/ns/{namespace}/connectors`) and is a top-level `namespace` key in every SDK call.
New v5 connectors belong to one namespace. The namespace goes in the URL (`/ns/{namespace}/connectors`) and is the first argument of namespace-scoped SDK calls. Organization-wide listing uses `connectors.listAll()` in TypeScript or `connectors.list_all()` in Python without a namespace argument. Legacy connectors can span multiple namespaces or have no namespace.
## Supported connectors
@ -69,7 +69,7 @@ const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
const connector = await supermemory.connectors.create("user-123", {
provider: "notion",
redirectUrl: "https://yourapp.com/callback"
redirectUrl: "https://yourapp.com/callback",
documentLimit: 5000,
})
@ -77,7 +77,7 @@ const connector = await supermemory.connectors.create("user-123", {
console.log("Connector:", connector.id)
console.log("Auth URL:", connector.authorization?.url)
console.log("Expires at:", connector.authorization?.expiresAt)
// Output: Auth URL: https://api.notion.com/v1/oauth/authorize?...
// Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
```
```python Python
@ -90,8 +90,8 @@ connector = client.connectors.create(
"user-123",
request={
"provider": "notion",
"redirectUrl": "https://yourapp.com/callback",
"documentLimit": 5000,
"redirect_url": "https://yourapp.com/callback",
"document_limit": 5000,
},
)
@ -99,7 +99,7 @@ connector = client.connectors.create(
print(f"Connector: {connector.id}")
print(f"Auth URL: {connector.authorization.url}")
print(f"Expires at: {connector.authorization.expires_at}")
# Output: Auth URL: https://api.notion.com/v1/oauth/authorize?...
# Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
```
```bash cURL
@ -115,7 +115,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
# Response: {
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "authorization": {
# "url": "https://api.notion.com/v1/oauth/authorize?...",
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
# "expiresAt": "2024-01-15T11:30:00.000Z"
# }
# }
@ -125,7 +125,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
### 2. Handle OAuth callback
Send the user to `authorization.url` before `authorization.expiresAt`. When the user authorizes, the provider returns them to your `redirectUrl` and the first sync starts. A pending OAuth connector is not visible in list or get until the user finishes authorization.
Send the user to `authorization.url` before `authorization.expiresAt`. When the user authorizes, the provider returns them to your `redirectUrl` and the first sync starts. GitHub, Gmail and Google Drive (with the default `syncScope: "selected"`) first send the user through Supermemory's hosted picker to choose what to sync. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
Config providers (Web Crawler, S3, Granola) return `authorization: null` and start syncing immediately.
@ -167,7 +167,9 @@ connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=
print(f"Provider: {connector.provider}")
print(f"Account: {connector.account}")
print(f"Last sync: {connector.latest_run.system.status} {connector.latest_run.error}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
if connector.latest_run and connector.latest_run.error:
print(connector.latest_run.error)
print(f"Last synced at: {connector.system.last_successful_sync_at}")
print(f"Documents: {connector.document_count}")
print(f"Recent runs: {connector.syncs}")
@ -210,9 +212,9 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
### Authentication flow
1. **Create Connector**: Call `POST /ns/{namespace}/connectors` with a `provider`. OAuth providers return an `authorization` link; config providers (Web Crawler, S3, Granola) take a `config` object and return `authUrl: null`
1. **Create Connector**: Call `POST /ns/{namespace}/connectors` with a `provider`. OAuth providers return an `authorization` link; config providers (Web Crawler, S3, Granola) take a `config` object and return `authorization: null`
2. **User Authorization**: Send the user to `authorization.url` when the provider requires it
3. **Automatic Setup**: Connector established, sync begins immediately
3. **Automatic Setup**: The connector is established. Save any required label, repository, or file selection before content sync can proceed; providers without a selection step start their initial sync after setup
4. **Continuous Sync**: Real-time updates via webhooks + scheduled sync every 4 hours (or scheduled recrawling for Web Crawler)
### Document processing pipeline
@ -301,7 +303,7 @@ curl "https://api.supermemory.ai/ns/org-123/connectors" \
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "2024-01-15T10:45:00.000Z" }
# }
# ],
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# }
# Connectors across every namespace in the organization

View file

@ -48,8 +48,8 @@ The S3 connector requires a **Scale Plan** or higher. You can also create S3 con
"config": {
"bucket": "my-documents-bucket",
"region": "us-east-1",
"accessKeyId": os.environ["AWS_ACCESS_KEY_ID"],
"secretAccessKey": os.environ["AWS_SECRET_ACCESS_KEY"],
"access_key_id": os.environ["AWS_ACCESS_KEY_ID"],
"secret_access_key": os.environ["AWS_SECRET_ACCESS_KEY"],
},
},
)
@ -270,7 +270,9 @@ Minimum required permissions:
## Error codes
| Code | Message | Solution |
Supermemory checks the credentials against the bucket before it creates the connector. If that check fails, `POST /ns/{namespace}/connectors` returns `400` with one of these messages. The code column is the status S3 returned.
| S3 status | Message | Solution |
|------|---------|----------|
| 401 | Authentication failed | Verify access key and secret |
| 403 | Access denied | Check IAM permissions and bucket policy |

View file

@ -72,7 +72,7 @@ curl "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?i
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filter": {"field": "source", "operator": "eq", "value": "notion"}}'
-d '{"filter": {"field": "type", "operator": "eq", "value": "notion_doc"}}'
```
</CodeGroup>
@ -85,30 +85,26 @@ There is no per-connector document list in v5. `supermemory.list(namespace, "doc
### Connector Missing From List
**Problem:** `connectors.create` returned an `id`, but `connectors.list` and `connectors.get` do not show it
**Problem:** `connectors.create` returned an `id`, but `connectors.list` does not show it
**Solution:** A pending OAuth connector is not visible in list or get until the user finishes authorization. Send the user to `authorization.url` before `authorization.expiresAt`. If the link expired, create the connector again.
**Solution:** A pending OAuth connector is not returned by list until the user finishes authorization. `connectors.get` still returns it with `system.status: "pending_authorization"`. Send the user to `authorization.url` before `authorization.expiresAt`. If the link expired, create the connector again.
### OAuth callback fails
**Problem:** "Invalid redirect URI" error after user grants permissions
**Solution:** Ensure your redirect URL matches EXACTLY what's configured in your OAuth app:
**Solution:** The provider always redirects to Supermemory's callback, not to your `redirectUrl`. If you use a [custom OAuth application](/connectors/overview#custom-oauth-applications), its redirect URI must be exactly `https://api.supermemory.ai/v3/connections/auth/callback/{provider}`. Your `redirectUrl` is where Supermemory sends the user after it handles that callback, and must be an `http` or `https` URL:
```typescript
// correct - exact match with OAuth app settings
const connector = await supermemory.connectors.create("user-123", {
provider: "notion",
redirectUrl: "https://yourapp.com/auth/notion/callback"
})
// Wrong - URL doesn't match
// redirectUrl: "https://yourapp.com/callback"
```
**Prevention:**
- Use HTTPS for production URLs
- Copy the exact URL from your OAuth app settings
- Register `https://api.supermemory.ai/v3/connections/auth/callback/{provider}` in your custom OAuth app settings
- Test the flow in development first
### Documents not syncing
@ -159,7 +155,7 @@ if (next.authorization) window.location.href = next.authorization.url
```typescript
const connector = await supermemory.connectors.create("user-123", {
provider: "onedrive",
redirectUrl: "https://yourapp.com/callback"
redirectUrl: "https://yourapp.com/callback",
documentLimit: 500, // Start with fewer documents
})
@ -247,9 +243,9 @@ const next = await supermemory.connectors.create("user-123", {
})
```
**Scale Plan Required Error**
**Max Plan Required Error**
Gmail connector requires Scale Plan or Enterprise Plan. If you see access errors:
Gmail connector requires Max Plan or above. If you see access errors:
- Verify your organization has the required plan
- Contact support to upgrade your plan

View file

@ -48,16 +48,16 @@ The web crawler connector requires a **Scale Plan** or **Enterprise Plan**.
request={
"provider": "web-crawler",
"config": {
"startUrl": "https://docs.example.com",
"crawlDepth": 3,
"start_url": "https://docs.example.com",
"crawl_depth": 3,
},
"documentLimit": 5000,
"document_limit": 5000,
},
)
# Web crawler doesn't require OAuth; crawling starts immediately
print(f"Connector ID: {connector.id}")
# Note: connector.authorization.url is None for web-crawler
# Note: connector.authorization is None for web-crawler
```
</Tab>
<Tab title="cURL">
@ -120,7 +120,7 @@ Unlike OAuth connectors, the web crawler doesn't require user authorization. `au
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
print(f"Last sync: {connector.latest_run.system.status}")
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
print(f"Pages synced: {connector.document_count}")
# List synced web pages in the namespace
@ -229,7 +229,7 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
webCrawlerConnectors.forEach(connector => {
console.log(`Start URL: ${connector.config?.startUrl}`)
console.log(`Connector ID: ${connector.id}`)
console.log(`Created: ${connector.createdAt}`)
console.log(`Created: ${connector.system.createdAt}`)
})
```
</Tab>
@ -241,7 +241,7 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
for connector in connectors:
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
print(f"Connector ID: {connector.id}")
print(f"Created: {connector.created_at}")
print(f"Created: {connector.system.created_at}")
```
</Tab>
<Tab title="cURL">
@ -256,12 +256,12 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
# "id": "PTzGiUYei7pgzg5buzZHgA",
# "provider": "web-crawler",
# "namespace": "user-123",
# "createdAt": "2024-01-15T10:30:00.000Z",
# "documentLimit": 5000,
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3}
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3},
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
# }
# ],
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
# }
```
</Tab>

View file

@ -216,6 +216,7 @@
"pages": [
"self-hosting/overview",
"self-hosting/quickstart",
"self-hosting/docker",
"self-hosting/configuration",
"self-hosting/embeddings",
"self-hosting/providers",
@ -977,6 +978,11 @@
"permanent": true,
"source": "/supermemory-mcp/technology"
},
{
"destination": "/supermemory-mcp/mcp",
"permanent": true,
"source": "/supermemory-mcp"
},
{
"destination": "/",
"permanent": true,
@ -1213,6 +1219,11 @@
"source": "/self-hosting/embed",
"destination": "/self-hosting/configuration"
},
{
"source": "/self-hosting",
"destination": "/self-hosting/overview",
"permanent": true
},
{
"source": "/concepts/memory",
"destination": "/concepts/graph-memory"

View file

@ -66,7 +66,7 @@ Every write goes to one `namespace` (what v3/v4 called a container tag). The nam
```
<Warning>
If an irrecoverable processing error occurs, the document is automatically deleted after 2 minutes.
If an irrecoverable processing error occurs, the document's status becomes `failed`. Send the content again with the same `id` to reprocess it.
</Warning>
---
@ -193,16 +193,16 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
## Parameters
`namespace`, `taskType`, and `dreaming` are top-level keys on the SDK call (`taskType` and `dreaming` are query parameters on the HTTP route). Everything else goes in `body`.
`namespace` is the first argument on the SDK call and the URL path on the HTTP route. Every other parameter, including `taskType` and `dreaming`, goes in the request object (the JSON body over HTTP). The route accepts no query parameters; sending one returns `400`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `namespace` | string | **Required.** Group by user/project. Goes in the URL path. Required for user profiles |
| `content` | string | **Required.** Any raw content — text, conversations, URLs, HTML |
| `id` | string | **Recommended.** Your ID for the content (conversation ID, doc ID). Enables updates and deduplication. The response `id` echoes it back |
| `metadata` | object | Key-value pairs for filtering (strings, numbers, booleans) |
| `metadata` | object | Key-value pairs for filtering (strings, numbers, booleans, or arrays of strings) |
| `group` | object | Filter which existing memories are used as context during ingestion. See [Filtered Writes](#filtered-writes) |
| `supportingContext` | string | Context for memory extraction on this namespace. Max 1500 chars. See [Customization](/concepts/customization#entity-context) |
| `supportingContext` | string | Context for memory extraction on this namespace. Max 1500 chars. See [Customization](/concepts/customization#supporting-context) |
| `date` | string | ISO 8601 date the content was originally created. Use when backfilling. See [Backfill historical data](/ingestion/batch-ingest-historical-data) |
| `dreaming` | `"dynamic" \| "instant"` | Processing mode. Default `"dynamic"`. `"instant"` processes each document on its own and bills one extra operation. See [Processing Modes](#processing-modes) |
| `taskType` | `"memory" \| "superrag"` | Pipeline to run. Default `"memory"`. `"superrag"` skips fact extraction and profile updates, doing only chunk/embed/index — at 5x cheaper per token. See [SuperRAG ingestion](/concepts/super-rag#ingesting-as-pure-superrag-tasktype-superrag) |
@ -223,16 +223,16 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
{ content: "# Project Docs\n\n## Features\n- Real-time sync" }
```
**Namespaces:**
**Namespaces** (first argument; letters, numbers, `-`, `_`, and `:`, up to 100 characters):
```typescript
// By user
{ namespace: "user_123" }
await supermemory.add("user_123", { content: "..." });
// By project
{ namespace: "project_alpha" }
await supermemory.add("project_alpha", { content: "..." });
// Hierarchical
{ namespace: "org_456_team_backend" }
await supermemory.add("org_456_team_backend", { content: "..." });
```
**IDs (Recommended):**
@ -262,8 +262,8 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
}
}
```
- No nested objects or arrays
- Values: string, number, or boolean only
- No nested objects
- Values: string, number, boolean, or an array of strings
**Supporting Context:**
```typescript
@ -363,7 +363,7 @@ The metadata itself is still written to the document, but the memories will only
### How it works
When `group` is provided:
- **Profile memories** (static context) are filtered to only those from documents matching the metadata
- **Profile memories** (static context) are filtered by the existing memories' metadata
- **Similar memories** used as context during ingestion are filtered the same way
- The new document's own metadata is written normally — the filter only affects which **existing** memories are used as context
@ -371,7 +371,7 @@ When `group` is provided:
| Key | Type | Description |
|-----|------|-------------|
| `group` | `Record<string, string \| number \| boolean \| string[]>` | Key-value pairs to filter existing memories by their source document metadata |
| `group` | `Record<string, string \| number \| boolean \| string[]>` | Key-value pairs to filter existing memories by their own metadata, rather than a live join to source-document metadata |
- **Scalar values** (string, number, boolean) match exactly
- **Array values** match if **any** value in the array matches (OR logic)
@ -404,7 +404,7 @@ When you add content, Supermemory:
Track progress with `GET /ns/{namespace}/document/{id}`:
```typescript
const doc = await supermemory.documents.get("user_123", "abc123");
console.log(doc.system.status); // "queued" | "processing" | "done"
console.log(doc.system.status); // "queued" | "extracting" | "chunking" | "embedding" | "done" | "failed"
```
<AccordionGroup>
@ -444,17 +444,19 @@ console.log(doc.system.status); // "queued" | "processing" | "done"
<Accordion title="Error handling">
| Status | Error | Cause |
|--------|-------|-------|
| 400 | Bad request | Missing required fields, invalid parameters |
| 400 | Bad request | Missing required fields, invalid parameters, unexpected query parameters |
| 401 | Unauthorized | Invalid or missing API key |
| 403 | Forbidden | Insufficient permissions |
| 429 | Rate limited | Too many requests or quota exceeded |
| 402 | Payment required | Insufficient balance or document quota |
| 403 | Forbidden | No write access to this namespace |
| 409 | Conflict | A namespace merge is in progress |
| 429 | Rate limited | Too many requests |
| 500 | Internal error | Processing failure |
```typescript
try {
await supermemory.add("user_123", { content: "..." });
} catch (error) {
// Back off on 429, fix the request on 400, retry later on 5xx
// Back off on 429, top up on 402, fix the request on 400, retry later on 5xx
console.error("add failed:", error);
}
```

View file

@ -102,13 +102,13 @@ curl -X POST "https://api.supermemory.ai/ns/historical_import/document/batch" \
</CodeGroup>
Batch results preserve input order. Inspect `success`, `failed`, and every item in `results`; a batch can contain successful and failed items together.
`results` lists accepted documents first, in request order, then failed ones, so match each result by `id`. Inspect `count` (accepted), `failed`, and every item in `results`; a batch can contain successful and failed items together.
## Optional: wait for processing to finish
**Endpoint:** [`GET /ns/{namespace}/document/{id}`](/api-reference/documents/get-document)
The batch endpoint returns after accepting the documents. If a later step depends on the documents being indexed, poll the returned document IDs until `system.status` is `done`. If that later step also needs the extracted memories, send the batch with `dreaming: "instant"`; under the default `dynamic` mode memory extraction is batched and can lag `done` by minutes.
The batch endpoint returns after accepting the documents. If a later step depends on the documents being indexed, poll the returned document IDs until `system.status` is `done`. If that later step also needs the extracted memories, send the batch with `dreaming: "instant"`; under the default `dynamic` mode memory extraction is batched and can lag `done` by minutes. Each document reports `system.dreamingStatus: "done"` once its memories are ready.
<CodeGroup>

View file

@ -49,9 +49,12 @@ Retrieve paginated documents with filtering. One `list` call serves documents, c
"id": "doc_abc123",
"title": "Meeting notes",
"type": "text",
"summary": "Team sync covering the Q3 roadmap",
"metadata": { "source": "slack" },
"url": null,
"system": {
"status": "done",
"dreamingStatus": "done",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
@ -72,16 +75,31 @@ Every response contains `documents`, `chunks`, `memories`, and `pagination`. Onl
### Parameters
`namespace` and `type` are required. Pagination and sorting are query parameters (top-level keys in the SDK); `filter` goes in `body`.
`namespace` and `type` are required. Over HTTP, pagination and sorting are query parameters and `filter` and `include` go in the JSON body; the SDK takes them all in one options object after `namespace` and `type`. Unknown query or body keys return `400`.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `type` | string | required | `documents`, `chunks`, or `memories` |
| `limit` | number | 10 | Items per page |
| `limit` | number | 10 | Items per page (maximum 100) |
| `page` | number | 1 | Page number |
| `sort` | string | `createdAt` | Sort by `createdAt`, `updatedAt`, or `position` |
| `sort` | string | `createdAt` | `createdAt` or `updatedAt` for documents and memories; `createdAt` or `position` for chunks |
| `order` | string | `desc` | `desc` (newest) or `asc` (oldest) |
| `body.filter` | object | — | Metadata filter. See [Organizing & Filtering](/concepts/filtering) |
| `body.include.content` | boolean | `false` | Documents only: include last published extracted text as `content` |
| `body.include.forgotten` | boolean | `false` | Memories only: include forgotten or expired rows that remain latest, returned with `isForgotten: true`. Normal forgetting makes a memory non-latest, so it still will not appear in this list |
### Include extracted content
Document lists omit `content` by default. To include the last published extracted text, including text from uploaded files, set `include.content` in the JSON body:
```bash
curl -X POST "https://api.supermemory.ai/ns/user_123/list/documents?limit=10" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"include":{"content":true}}'
```
`content` is `null` until the first extraction publishes. When you replace a file, the previous text remains until the new extraction publishes. It also remains if the replacement fails. Including content can make pages large.
<Accordion title="Pagination example">
```typescript
@ -132,7 +150,7 @@ Get a specific document with its processing status. Pass `include` to include it
include: ["chunks", "memories"], // optional
});
console.log(doc.system.status); // "queued" | "processing" | "done" | "failed"
console.log(doc.system.status); // "queued" | "extracting" | ... | "done" | "failed"
console.log(doc.content);
```
</Tab>
@ -144,7 +162,7 @@ Get a specific document with its processing status. Pass `include` to include it
include=["chunks", "memories"], # optional
)
print(doc.system.status) # "queued" | "processing" | "done" | "failed"
print(doc.system.status) # "queued" | "extracting" | ... | "done" | "failed"
print(doc.content)
```
</Tab>
@ -158,6 +176,10 @@ Get a specific document with its processing status. Pass `include` to include it
The `id` may be the Supermemory document ID or the `id` you supplied at ingestion. It resolves only inside the namespace in the path.
<Note>
The current HTTP response also includes `system.dreamingStatus`. TypeScript SDK 5.0.1 and Python SDK 5.0.0 do not expose that field in their generated models; Python's `system.dreaming_status` is not available. Use the raw HTTP response when monitoring memory generation with these versions, or upgrade the TypeScript SDK to 5.0.2 for typed `system.dreamingStatus`. `system.status: "done"` alone does not mean memories are ready.
</Note>
### Processing status
| Status | Description |
@ -169,6 +191,19 @@ The `id` may be the Supermemory document ID or the `id` you supplied at ingestio
| `done` | Ready for search |
| `failed` | Processing failed |
Treat any status other than `done` or `failed` as still in progress.
### Memory generation status
`system.dreamingStatus` tracks memory generation separately from `system.status`.
| Status | Description |
|--------|-------------|
| `dreaming` | Memories for the latest content are queued, being generated, or waiting for a retry |
| `done` | Memories are generated, or the document needed none |
With the default `dreaming: "dynamic"`, Supermemory batches related documents before forming memories, so `status` can be `done` minutes before `dreamingStatus` is. If your next step reads memories or profiles, wait for `dreamingStatus` to be `done` too.
<Accordion title="Poll for completion">
```typescript
async function waitForProcessing(namespace: string, id: string) {
@ -188,7 +223,7 @@ The `id` may be the Supermemory document ID or the `id` you supplied at ingestio
## Update document
Update a document's content or metadata. **Content changes** trigger full reprocessing; **metadata-only changes** (e.g. updating `accepted`, `version`) do not reindex. The body accepts any non-empty subset of `content`, `metadata`, `supportingContext`, `group`, or `date`.
Update a document's content or metadata. **Content changes** trigger full reprocessing; **metadata-only changes** (e.g. updating `accepted`, `version`) do not reindex. The body accepts any non-empty subset of `content`, `metadata`, `supportingContext`, or `date`. Metadata merges key by key. `group` is an ingestion-side setting: it picks which existing memories serve as context while content is processed, so changing it only takes effect when the document is re-ingested. Send it together with `content`.
<Tabs>
<Tab title="TypeScript">
@ -277,7 +312,7 @@ Permanently remove documents. One call deletes 1 to 100 documents by Supermemory
</Tab>
</Tabs>
Inspect both `count` and per-ID `errors`; HTTP success can include partial failures.
Inspect both `count` and per-ID `errors`; HTTP success can include partial failures. Only documents whose `system.status` is `done` or `failed` can be deleted; a document that is still processing is reported in `errors` as `Document is still processing`. Memories derived from a deleted document are forgotten.
<Warning>
Deletes are permanent — no recovery.
@ -287,7 +322,7 @@ Deletes are permanent — no recovery.
## Processing queue
There is no separate processing endpoint. List documents and read `system.status` on each item.
There is no separate processing endpoint. List documents and read `system.status` and `system.dreamingStatus` on each item in the HTTP response. The SDK examples below count documents still being indexed; use raw HTTP for memory-generation status with the SDK versions noted above.
<Tabs>
<Tab title="TypeScript">

View file

@ -182,7 +182,7 @@ Paths are normalized for storage: `/memories/preferences` is stored as `--memori
```typescript
{
command: "rename",
path: "/memories/old-name.txt",
old_path: "/memories/old-name.txt",
new_path: "/memories/new-name.txt"
}
```

View file

@ -28,8 +28,7 @@ You can obtain an API key from [console.supermemory.ai](https://console.supermem
Supermemory integration is provided through the `SupermemoryPipecatService` class in Pipecat:
```python
from supermemory_pipecat import SupermemoryPipecatService
from supermemory_pipecat.service import InputParams
from supermemory_pipecat import InputParams, SupermemoryPipecatService
memory = SupermemoryPipecatService(
api_key=os.getenv("SUPERMEMORY_API_KEY"),
@ -157,8 +156,7 @@ from pipecat.transports.websocket.fastapi import (
FastAPIWebsocketTransport,
)
from supermemory_pipecat import SupermemoryPipecatService
from supermemory_pipecat.service import InputParams
from supermemory_pipecat import InputParams, SupermemoryPipecatService
app = FastAPI()

View file

@ -49,7 +49,7 @@ print("Migration complete!")
Mem0 provides two ways to export your memories:
### Option 1: Export via dashboard (recommended)
1. Log into your [Mem0 dashboard](https://app.mem0.ai)
1. Log into your Mem0 dashboard (app.mem0.ai)
2. Navigate to the export section
3. Download your memories as JSON

View file

@ -12,7 +12,7 @@ The Analytics API provides comprehensive insights into your Supermemory usage:
- **Usage Statistics**: Track API calls by type, hourly trends, and per-API key breakdown
- **Error Monitoring**: Identify top error types and patterns
- **Detailed Logs**: Access complete request/response logs for debugging
- **Detailed Logs**: Inspect stored request logs and compacted response data for debugging
- **Performance Metrics**: Monitor average response times and processing duration
<Note>
@ -33,7 +33,7 @@ Get comprehensive usage statistics including hourly breakdowns and per-key metri
|-----------|------|-------------|
| `from` | string (ISO 8601) | Start date/time for the period |
| `to` | string (ISO 8601) | End date/time for the period |
| `period` | string | Alternative to `from`: `1h`, `24h`, `7d`, `30d` |
| `period` | string | Alternative to `from`: `24h`, `7d`, `30d`, `all`. Use `from` and `to` for other windows |
| `page` | integer | Page number for pagination (default: 1) |
| `limit` | integer | Items per page (default: 20, max: 100) |
@ -105,7 +105,8 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/usage?from=2024-01-01T00:00
"keyName": "Production API",
"count": 2341,
"avgDuration": 98.7,
"lastUsed": "2024-01-15T14:35:00Z"
"lastUsed": "2024-01-15T14:35:00Z",
"tokensUsed": 182340
}
],
"totalMemories": 45678,
@ -118,6 +119,8 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/usage?from=2024-01-01T00:00
}
```
`tokensUsed` sums recorded `finalTokens` for the listed keys, including chat usage. It is not the billable ingestion-token meter, and ordinary memory request logs do not necessarily populate it.
## Error monitoring
Track and analyze errors to identify issues and improve reliability.
@ -148,7 +151,7 @@ const data = await errors.json();
```python Python
# Monitor errors and alert on spikes
response = requests.get(
'https://api.supermemory.ai/v3/analytics/errors?period=1h',
'https://api.supermemory.ai/v3/analytics/errors?period=24h',
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
)
@ -167,41 +170,53 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/errors?period=7d" \
```json
{
"totalErrors": 234,
"errorRate": 0.023,
"topErrors": [
"summary": [
{
"type": "ValidationError",
"type": "search_v4",
"totalRequests": 3421,
"successRate": 97.4,
"errorRate": 2.6,
"lastRequest": "2024-01-15T14:35:00Z"
}
],
"errors": [
{
"type": "search_v4",
"statusCode": 400,
"count": 89,
"statusCodes": [400],
"lastOccurred": "2024-01-15T14:30:00Z"
},
{
"type": "RateLimitError",
"count": 45,
"statusCodes": [429],
"lastOccurred": "2024-01-15T13:15:00Z"
"percentage": 100
}
],
"timeline": [
"hourly": [
{
"time": "2024-01-15T14:00:00Z",
"count": 12,
"types": ["ValidationError", "NotFoundError"]
"hour": "2024-01-15T14:00:00Z",
"count": 156,
"errorCount": 4,
"errorRate": 2.56
}
],
"byStatusCode": {
"400": 89,
"404": 34,
"429": 45,
"500": 66
"byKey": [
{
"keyId": "key_abc123",
"keyName": "Production API",
"count": 2341,
"errorCount": 61,
"errorRate": 2.61,
"lastUsed": "2024-01-15T14:35:00Z"
}
],
"pagination": {
"currentPage": 1,
"limit": 20,
"totalItems": 3421,
"totalPages": 172
}
}
```
## Detailed logs
Access complete request/response logs for debugging and auditing.
Inspect stored request logs for debugging. Response data is compacted: strings, arrays, fields, and nesting can be truncated. The ordinary request tracker skips HTTP `402`, `404`, and `500` responses, so this is not a complete audit trail.
### Endpoint
@ -209,18 +224,14 @@ Access complete request/response logs for debugging and auditing.
### Parameters
Same as usage endpoint, plus optional filters:
- `type`: Filter by request type (add, search, update, delete)
- `statusCode`: Filter by HTTP status code
- `keyId`: Filter by specific API key
Use `from`, `to`, `period`, `page`, and `limit` as above. The current `/logs` handler filters by organization and time only; it does not apply `type`, `statusCode`, or `keyId` filters.
### Example request
<CodeGroup>
```typescript TypeScript
// Get recent failed requests
const logs = await fetch('https://api.supermemory.ai/v3/analytics/logs?period=1h&statusCode=500', {
const logs = await fetch('https://api.supermemory.ai/v3/analytics/logs?period=24h', {
headers: {
'Authorization': `Bearer ${SUPERMEMORY_API_KEY}`
}
@ -230,11 +241,9 @@ const data = await logs.json();
```
```python Python
# Debug specific API key usage
response = requests.get(
'https://api.supermemory.ai/v3/analytics/logs',
params={
'keyId': 'key_abc123',
'period': '24h'
},
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
@ -246,7 +255,7 @@ logs = response.json()['logs']
```bash cURL
# Get all logs for debugging
curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=24h&limit=50" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
```
@ -263,13 +272,10 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
"type": "search",
"statusCode": 200,
"duration": 89,
"input": {
"q": "user query",
"limit": 10
},
"input": "{\"q\":\"user query\",\"limit\":10}",
"output": {
"results": 10,
"processingTime": 85
"type": "search",
"response": { "results": [], "timing": 85, "total": 0 }
}
}
],
@ -282,13 +288,10 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
}
```
## Rate limits
## Limits and retention
Analytics endpoints have the following rate limits:
- 100 requests per minute per organization
- Maximum time range: 90 days
- Maximum page size: 100 items
The maximum page size is 100 items. Normal API request rate limits also apply; this page does not establish a separate analytics-specific allowance.
<Warning>
Analytics data is retained for 90 days. For longer retention, export and store the data in your own systems.
An accepted date range does not guarantee that historical logs are still retained. Confirm the applicable service retention policy with Supermemory, and export data to your own systems when you need longer retention.
</Warning>

View file

@ -29,7 +29,7 @@ USD CREDIT BALANCE ──draw──► METERS
1. You hold a **USD credit balance** (`usd_credits`).
2. Product activity increments **meters** (tokens, queries, operations).
3. Each meter unit multiplies by a **USD-per-unit** rate and debits the balance.
4. **Re-ingesting the same document under the same `id` only bills the net-new token delta** — already-seen content is effectively **fully discounted**.
4. **Re-ingesting a document under the same `id` can reuse unchanged content** — token charges follow added or changed text, not simply growth in the document's total length.
---
@ -41,8 +41,8 @@ USD CREDIT BALANCE ──draw──► METERS
| `sm_tokens_rich` | Memory tokens (rich) | Billable **new** tokens on Memory-path ingest for multimodal / extraction-heavy types (PDF, media, etc.) | tokens |
| `sm_superrag_text` | SuperRAG tokens (text) | Billable **new** tokens when the task type is SuperRAG (text) | tokens |
| `sm_superrag_rich` | SuperRAG tokens (rich) | Billable **new** tokens when the task type is SuperRAG (rich) | tokens |
| `sm_search_queries` | Search queries | Each search / profile call that runs retrieval (v3 search, v4 search, v4 profile with search) | queries |
| `sm_operations` | Operations | Counted platform operations not fully attributed to token meters (e.g. certain ingest modes such as **instant dreaming**, and other non-token platform actions) | operations |
| `sm_search_queries` | Search queries | Gated v5 namespace search, v3/v4 search, and v4 profile calls that include a non-blank `q` query | queries |
| `sm_operations` | Operations | Counted platform operations not attributed to token or query meters: **instant dreaming**, and search add-ons such as query rewriting, reranking and aggregation | operations |
### List rates (USD)
@ -73,7 +73,8 @@ Text-like document types bill on the **text** meters; everything else bills as *
**`sm_operations`** is the meter for discrete platform work that is not “how many tokens did we embed.” Examples include:
- `dreaming: "instant"` extracts a document's memories immediately instead of waiting for the next dynamic batch. It costs **one extra operation per document** on top of normal token metering. See [processing modes](/ingestion/add-memories#processing-modes).
- Other non-token platform actions the product attributes to the operations meter (console: “counted platform operations not attributed to token or search meters”).
- Search add-ons: query rewriting (`rewriteQuery`), reranking and aggregation (v5 `rerank: "order"` / `"aggregate"`, v3/v4 `rerank`, v4 `aggregate`) each add **one operation** when they run successfully. Failed or skipped add-ons do not necessarily charge an operation.
- Query-based `POST /v4/memories/forget-matching` adds **one operation per search round**, up to five, including `dryRun` previews. Applying an explicit `ids` list bypasses that query-operation charge.
Search is primarily metered as **`sm_search_queries`** (one unit per gated search/profile request). Prefer thinking of operations as **extra platform work**, not as a synonym for “API call.”
@ -81,14 +82,18 @@ Search is primarily metered as **`sm_search_queries`** (one unit per gated searc
| Call | Typical meter |
|---|---|
| `POST /ns/{namespace}/search`, `supermemory.search()` | `sm_search_queries` (+1) |
| `POST /ns/{namespace}/profile`, `supermemory.profile()` when it runs retrieval | `sm_search_queries` (+1) |
| `POST /ns/{namespace}/search`, `supermemory.search()` | `sm_search_queries` (+1), plus `sm_operations` for any add-ons that run |
| `POST /ns/{namespace}/profile`, `supermemory.profile()` | No search-query meter in the current API implementation |
| Legacy `POST /v3/search`, `POST /v4/search` | `sm_search_queries` (+1), plus `sm_operations` for any add-ons that run |
| Legacy `POST /v4/profile` with a non-empty `q` | `sm_search_queries` (+1) |
If the org is out of balance, search/profile can return **402** (payment required) depending on gate configuration.
Blocking metered cloud calls can return **402** (payment required) when the applicable balance or quota is exhausted. Billing exclusions, unlimited contracts, disabled billing, and gate configuration can differ. The current v5 profile route does not install a search-query billing gate; this is not a guarantee of future pricing.
---
## Full discount on already-seen tokens (diff billing)
<span id="full-discount-on-already-seen-tokens-diff-billing" />
## Reusing unchanged content (diff billing)
This is the most important cost control in production agents.
@ -96,17 +101,13 @@ This is the most important cost control in production agents.
When you re-add content under the **same `id`** (same org / document identity), the pipeline:
1. Loads the **previous extracted content** for that document
2. Computes **full** token count of the merged/updated document
3. Bills only:
1. Loads the document's active chunks and plans the updated content.
2. Reuses unchanged chunks where possible.
3. Meters newly appended text on an append path, or added replacement chunks on a replace path.
```text
billableTokens = max(0, fullTokenCount - previousTokenCount)
```
This is not a subtraction of the old total token count from the new one. A same-length replacement can still incur charges if its content changed; an identical replacement can reuse its chunks without new token charges. Chunk boundaries and overlap can also affect the billable delta.
So **tokens Supermemory has already processed are not billed again**. Unchanged content costs nothing on the token meters; only the net-new delta, and any new extraction on it, draws credits.
This is why long-lived agent loops stay cheap: re-sync the same conversation `id`, re-upload the same policy doc, or connector re-sync with stable IDs — you pay for **new** material, not the whole history every time.
Stable IDs let long-lived conversations and repeated document syncs reuse unchanged work rather than billing the whole history every time. Operation surcharges can still apply.
### Requirements
@ -114,7 +115,7 @@ This is why long-lived agent loops stay cheap: re-sync the same conversation `id
|---|---|
| **Stable `id`** | Identity for “this is the same document.” Max length 255. |
| **Same org / key** | Documents are org-scoped. |
| **Update path, not full replace** | Full replace clears previous content for billing purposes (`isFullReplace` treats previous tokens as 0). Prefer append/update with the same `id` for chat. |
| **Choose append or replace deliberately** | Append meters the newly appended text; replace compares chunks and can reuse unchanged ones. Changed replacement chunks can bill even without net document growth. |
### Practical patterns
@ -127,12 +128,12 @@ await supermemory.add(userId, {
});
```
Connectors already use stable IDs (e.g. Drive file id, Gmail thread id, `s3://bucket/key`) so re-syncs diff-bill automatically.
Connectors use stable IDs (e.g. Drive file id, Gmail thread id, `s3://bucket/key`), so re-syncs can reuse unchanged content. Changed replacement chunks can still incur token charges.
### What is not free
- **First** ingest of content still bills full token count
- **Search / profile** still bill query meters every call
- **Gated searches** still bill the query meter every call; legacy v4 profile does so only with a non-blank `q`. The current v5 profile implementation does not charge this meter.
- **Instant dreaming** still bills the **operation** surcharge when used
- **Brand-new `id`** = brand-new document = full tokens
@ -191,6 +192,7 @@ Usage meters are shared; **feature gates** differ by tier.
| Gmail connector | — | — | Yes | Yes | Yes |
| GitHub, S3, Web Crawler connectors | — | — | — | Yes | Yes |
| User Insights | — | — | — | Yes | Yes |
| Two-factor authentication | — | — | — | Yes | Yes |
| Org seat limit (members) | 1 | 3 | 3 | Unlimited | Unlimited |
| Auto top-up in the console | — | Yes | Yes | Yes | Contract |
| Auto top-up purchase limits | — | Yes | Yes | Yes | Contract |
@ -211,7 +213,7 @@ Overrides can be applied per org in metadata (`featureOverrides`) for enterprise
## Programmatic access
All of the following require an **org-admin capable** credential. **Scoped API keys cannot read billing** (403).
**Scoped API keys cannot read billing** (403). `GET /v3/auth/billing` and `POST /v3/auth/billing/auto-topups` also require an org **admin or owner**.
Base: `https://api.supermemory.ai`
Auth: `Authorization: Bearer YOUR_API_KEY`
@ -234,7 +236,7 @@ Typical fields:
"tokens": { "used": 120000, "limit": 0 },
"queries": { "used": 4200, "limit": 0 }
},
"credits": {},
"credits": { "balance": 14.62, "label": "USD" },
"resetDate": "2026-08-01T00:00:00.000Z",
"orgName": "Acme",
"billingEmail": "billing@acme.com"

View file

@ -41,7 +41,7 @@ Supermemory is infrastructure for *your* agents. Your customer content is never
### Data residency and deployment options
- **Managed cloud** — default multi-tenant SaaS.
- **Self-hosted binary** — full engine on your machine or VPC; embeddings and storage stay where you run it. See [Self-hosting](/self-hosting/overview).
- **Self-hosted binary** — the engine, storage, and default local embeddings run on your machine or VPC. If you configure remote LLM or embedding providers, their processing inputs are sent to those providers. See [Self-hosting](/self-hosting/overview).
- **Enterprise / dedicated** — for stricter residency, air-gap, or custom deployment requirements. See [Local vs Enterprise](/self-hosting/local-vs-enterprise).
## Privacy operations you should design for
@ -51,10 +51,10 @@ Supermemory is infrastructure for *your* agents. Your customer content is never
The practical GDPR-style path for app builders:
1. Scope each end-user (or tenant) to a **namespace**.
2. When the user requests deletion, delete that namespace’s content via the API / console workflows for documents and memories under that namespace.
2. When the user requests deletion, use `DELETE /ns/{namespace}` with an org admin or owner credential that has write access. It deletes the namespace, its namespace-owned memory records, and documents exclusive to it; documents shared with other namespaces are retained. The default `sm_project_default` namespace cannot be deleted.
3. Revoke any **scoped keys** issued for that user.
Designing isolation up front makes erasure a single boundary operation instead of a forensic search.
Designing isolation up front still makes identifying the user's content easier.
### Connectors and third-party sources

View file

@ -97,13 +97,14 @@ print(conv.id, conv.status) # e.g. "queued"
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document?dreaming=instant" \
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "user: Just got back from Tokyo — the team offsite went great.\nassistant: Glad it went well! Anything stand out?\nuser: Sarah presented the Q3 roadmap at the offsite.\nassistant: Sounds like a big moment for her.\nuser: She is being promoted to VP of Product.\nassistant: Congrats to Sarah — that is huge.\nuser: I need a gift idea for my VP of Product.\nassistant: Happy to help brainstorm something personal.",
"id": "chat_offsite_2026",
"metadata": { "type": "conversation" }
"metadata": { "type": "conversation" },
"dreaming": "instant"
}'
```
</CodeGroup>
@ -164,13 +165,14 @@ print(doc.id, doc.status)
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document?taskType=superrag" \
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "# Team notes — gifts & recognition\n\nWhen someone is promoted to VP or above, the company recommends a thoughtful gift in the $75–$150 range. Experiences tied to recent team milestones land better than generic swag.\n\nFor product leadership, books on platform strategy or a dinner near the last offsite city are common picks. Tokyo offsites often inspire travel-themed gifts.",
"id": "doc_gift_policy",
"metadata": { "type": "document", "source": "handbook" }
"metadata": { "type": "document", "source": "handbook" },
"taskType": "superrag"
}'
```
</CodeGroup>
@ -219,7 +221,7 @@ curl "https://api.supermemory.ai/ns/user_4f8a/document/DOC_ID" \
```
</CodeGroup>
Short text with instant dreaming usually finishes in a few seconds. Larger PDFs take longer. If you omit `dreaming` (default `"dynamic"`), document RAG can work after `done` while memory extraction may still be batching — use `"instant"` when the next step is memory search or profiles.
Short text with instant dreaming usually finishes in a few seconds. Larger PDFs take longer. If you omit `dreaming` (default `"dynamic"`), document RAG can work after `done` while memory extraction may still be batching — use `"instant"` when the next step is memory search or profiles. `system.dreamingStatus` turns `done` once a document's memories are ready.
## 4. Three ways to get context back
@ -253,10 +255,14 @@ for hit in rag.results:
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search?searchMode=chunks&limit=3" \
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "gift ideas for a VP promotion after a Tokyo offsite" }'
-d '{
"query": "gift ideas for a VP promotion after a Tokyo offsite",
"searchMode": "chunks",
"limit": 3
}'
```
</CodeGroup>
@ -300,12 +306,14 @@ print(memories.to_json())
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search?searchMode=memories&limit=5" \
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "What gift should I get, and why?",
"include": { "related": true }
"searchMode": "memories",
"include": { "related": true },
"limit": 5
}'
```
</CodeGroup>

View file

@ -29,13 +29,14 @@ v5 has no direct memory-create route. Ingest a document instead and Supermemory
</Tab>
<Tab title="cURL">
```bash
curl -X POST "https://api.supermemory.ai/ns/user_123/document?dreaming=instant" \
curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "John prefers dark mode.\nJohn is from Seattle.",
"id": "prefs_john",
"metadata": { "source": "user_preference" }
"metadata": { "source": "user_preference" },
"dreaming": "instant"
}'
```
</Tab>
@ -88,7 +89,7 @@ Soft-delete memories by ID. Forgotten memories are excluded from search results
|-------|------|-------------|
| `count` | number | Number of memories forgotten; always equals `matches.length` |
| `matches` | array | The memories that were forgotten (`{ id, memory }`) |
| `errors` | array | IDs that were missing or not eligible (`{ id, error }`) |
| `errors` | array | IDs that were missing, already forgotten, or in another namespace (`{ id, error }`) |
HTTP success does not imply every requested ID changed. Check `errors`.
@ -99,7 +100,7 @@ HTTP success does not imply every requested ID changed. Check `errors`.
Forget by meaning. Give a `query` (a prompt or topic) and the service semantically searches the namespace, decides which memories are genuinely about your target, and soft-deletes those — use this for "forget everything about X".
<Warning>
This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun: true`** first to review what would be forgotten, then apply. The match is semantic, so a too-broad query can select more than you intend.
This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun: true`** first to review what would be forgotten, then apply. The match is semantic, so a too-broad query can select more than you intend. Each request selects at most 100 memories. After applying a batch, repeat the preview-and-apply workflow to find more; repeated previews alone do not paginate and may return the same matches.
</Warning>
<Tabs>
@ -144,7 +145,7 @@ This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `namespace` | string | yes | Namespace to scope the operation to (URL path) |
| `query` | string | yes | What to forget — a natural-language instruction ("forget everything about Project Titan") or a bare topic ("Project Titan") |
| `query` | string | yes | What to forget — a natural-language instruction ("forget everything about Project Titan") or a bare topic ("Project Titan"). 1 to 2000 characters |
| `dryRun` | boolean | yes | `true` returns what *would* be forgotten without changing anything. `false` forgets the memories selected when that request runs |
### Response
@ -161,7 +162,7 @@ Both dry-run and applied requests return the same shape as [Forget Memories](#fo
}
```
The payload does not say which mode was sent, so keep your own record of `dryRun` next to audit logs.
`errors` is always empty for semantic requests. The payload does not say which mode was sent, so keep your own record of `dryRun` next to audit logs.
<Tip>
**Exact, bound deletes.** Re-running `forgetMatching` with `dryRun: false` re-runs the semantic match, so the result can drift from the preview if the namespace changed in between. To forget *precisely* what you reviewed, take the `id`s from the `dryRun` preview and send them to `memories.forget` — the delete is then bound to exactly that set.

View file

@ -7,8 +7,8 @@ icon: "/icons/hugeicons/check-list.svg"
Supermemory's graph derives new facts from patterns across your existing memories
(see [graph memory](/concepts/graph-memory)). Because nobody stated these facts
directly, they are flagged as inferred (`isInference: true`) and **down-weighted in
search until someone confirms them**.
directly, they are flagged as inferred (`isInference: true`) until someone confirms
them.
These two endpoints let you build a review flow on top of that queue: list the
inferred memories awaiting review, then approve, decline or undo a decision on each one.
@ -20,19 +20,17 @@ These endpoints are scoped to a single [namespace](/concepts/container-tags)
path parameter.
</Info>
## How review affects ranking
## What review changes
While a memory is unreviewed and inferred it is down-weighted in search, so the
engine's guesses rank below facts you stated explicitly. Reviewing it resolves that
either way:
Reviewing an inferred memory resolves it either way:
| Action | Result | Effect on search |
|--------|--------|------------------|
| **Approve** | `isInference` cleared | Ranks like a stated fact — no longer down-weighted |
| **Approve** | `isInference` cleared | Stays in search, no longer flagged as inferred |
| **Decline** | `isForgotten` set | Removed from search entirely — a rejected guess is forgotten |
| **Undo** | back to unreviewed | Returns to the queue; inferred and down-weighted again |
| **Undo** | back to unreviewed | Returns to the queue, flagged as inferred again |
A reviewed memory is stamped with `reviewStatus` in its metadata so it drops out of the
A reviewed memory gets an internal review stamp in its metadata so it drops out of the
review queue (declined memories also leave search, since they're forgotten). **Undo**
clears that stamp — and un-forgets a declined memory — bringing it back.
@ -98,7 +96,7 @@ GET /v3/container-tags/{containerTag}/inferred
| `memories[].createdAt` | string | ISO 8601 timestamp |
| `memories[].updatedAt` | string | ISO 8601 timestamp |
| `memories[].metadata` | object \| null | Arbitrary metadata stored on the memory |
| `total` | number | Count of unreviewed inferred memories returned |
| `total` | number | Total unreviewed inferred memories in the queue; can exceed the number returned in `memories` |
<Note>
The queue returns up to **50** memories, ordered by `parentCount` descending (most
@ -166,8 +164,8 @@ The reject action is named **`decline`**. There is no `reject` value.
**Action semantics:**
- **`approve`** — Promote the memory: clears `isInference`, so it ranks like a stated
fact instead of a down-weighted guess. Stamps `reviewStatus: "approved"`.
- **`approve`** — Promote the memory: clears `isInference`, so it's treated as a
confirmed fact. Stamps `reviewStatus: "approved"`.
- **`decline`** — Reject the suggestion: the memory is **forgotten**
(`isForgotten: true`) and stamped `reviewStatus: "declined"`, so it leaves both
search and the review queue.
@ -198,6 +196,7 @@ The reject action is named **`decline`**. There is no `reject` value.
| Status | When |
|--------|------|
| `401` | Missing or invalid authentication |
| `403` | Your key can't access this namespace, or lacks write access to it |
| `404` | The namespace or memory was not found in your organization |
| `409` | The memory isn't reviewable for this action — it's not an inferred memory, or there's no prior review to undo |
@ -286,4 +285,4 @@ a request and the memory simply stays in the queue for a later session.
- [Graph Memory](/concepts/graph-memory) — How inferred (`derive`) memories are created
- [Memory Operations](/recall/memory-operations) — Forget memories
- [Search](/recall/search) — How inferred memories are ranked in results
- [Search](/recall/search) — Results flag inferred memories with `isInference`

View file

@ -12,7 +12,7 @@ Search through your memories and documents with a single API call. Each search r
</Tip>
<Note>
**TypeScript SDK:** one call, `supermemory.search(namespace, { query, searchMode })`. `searchMode` (`"memories"`, `"chunks"`, or `"hybrid"`) picks what comes back. There is no v5 Python SDK yet; the Python tab shows the legacy client.
**SDKs:** one call, `supermemory.search(namespace, { query, searchMode })` in TypeScript or `client.search(namespace, query=..., search_mode=...)` in Python. `searchMode` (`"memories"`, `"chunks"`, or `"hybrid"`) picks what comes back.
</Note>
## Quick start
@ -54,10 +54,10 @@ Search through your memories and documents with a single API call. Each search r
</Tab>
<Tab title="cURL">
```bash
curl -X POST "https://api.supermemory.ai/ns/user_123/search?searchMode=hybrid&limit=5" \
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "machine learning" }'
-d '{ "query": "machine learning", "searchMode": "hybrid", "limit": 5 }'
```
</Tab>
</Tabs>
@ -72,6 +72,7 @@ Search through your memories and documents with a single API call. Each search r
"similarity": 0.91,
"metadata": { "topic": "interests" },
"isLatest": true,
"isInference": false,
"system": { "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" }
},
{
@ -80,6 +81,7 @@ Search through your memories and documents with a single API call. Each search r
"similarity": 0.87,
"metadata": { "source": "onboarding_doc" },
"isLatest": true,
"isInference": false,
"system": { "createdAt": "2024-01-14T09:15:00.000Z", "updatedAt": "2024-01-14T09:15:00.000Z" }
}
],
@ -95,7 +97,7 @@ In hybrid mode, results contain either a `memory` field (extracted facts) or a `
## Parameters
`namespace` is the URL path. Every other option goes in the JSON body (the second argument in the SDKs).
`namespace` is the URL path. Every other option goes in the JSON body: the second argument in TypeScript, or keyword arguments in Python. Search takes no query-string parameters; sending any returns `400`.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
@ -104,10 +106,10 @@ In hybrid mode, results contain either a `memory` field (extracted facts) or a `
| `searchMode` | string | `"hybrid"` | `"hybrid"` (recommended), `"memories"`, or `"chunks"` |
| `limit` | number | 10 | Max results (max 100) |
| `threshold` | 0-1 | 0.3 | Similarity cutoff (higher = fewer, better results) |
| `rerank` | string | `"none"` | `"none"`, `"order"` (re-score for relevance, +100ms), or `"aggregate"` |
| `rewriteQuery` | boolean | false | Generate multiple rewrites, search all of them, and merge results. No extra cost, but adds latency |
| `rerank` | string | `"none"` | `"none"`, `"order"` (re-score for relevance, +100ms), or `"aggregate"`. Successful reranking or aggregation can bill one extra [operation](/overview/billing#what-is-an-operation); setting the option alone does not guarantee it runs |
| `rewriteQuery` | boolean | false | Generate multiple rewrites, search all of them, and merge results. Adds latency and can bill one extra [operation](/overview/billing#what-is-an-operation) when rewriting runs successfully |
| `filter` | object | — | Metadata filter expression. See [Filtering](#filtering) |
| `include` | object | — | `{ documents, related, forgotten }` — opt in to extra context per result |
| `include` | object | all `false` | `{ documents, related, forgotten }` — opt in to extra context per result |
<Note>
Defaults changed from v4: `searchMode` was `memories` and is now `hybrid`; `threshold` was `0.6` and is now `0.3`. Set both explicitly if you are comparing against v4 results.
@ -196,9 +198,9 @@ await supermemory.search("user_123", { query: "...", threshold: 0.8 });
### Attachments
- `attach.documents` adds the most relevant source document to each result.
- `attach.related` adds parent, child, and sibling memories (see [graph memory](/concepts/graph-memory)).
- `attach.forgotten` allows forgotten memories in related context. By default, search excludes memories that have been forgotten or have passed their `forgetAfter` expiration.
- `include.documents` adds the most recently updated source document to each result when one is available.
- `include.related` adds parent, child, and sibling memories (see [graph memory](/concepts/graph-memory)).
- `include.forgotten` lets forgotten and expired memories appear in results. By default, search excludes memories that have been forgotten or have passed their `forgetAfter` expiration.
```typescript
await supermemory.search("user_123", {
@ -238,10 +240,11 @@ async function getContext(userId: string, message: string) {
memory?: string; // Present for memory results
chunk?: string; // Present for document chunk results
similarity: number; // 0-1
metadata: object | null;
metadata: Record<string, unknown>; // {} when none
isLatest: boolean;
system: { createdAt: string; updatedAt: string };
included?: { // Only when you pass attach
isInference: boolean; // false for chunks
system: { createdAt?: string; updatedAt: string };
included?: { // Only when you pass include
document?: object;
related?: { parents: object[]; children: object[]; siblings: object[] };
};

View file

@ -62,16 +62,18 @@ Profiles are built automatically as you [ingest content](/ingestion/add-memories
{ "id": "mem_3", "memory": "User prefers dark mode interfaces" }
],
"dynamic": [
{ "id": "mem_4", "memory": "User is working on Project Alpha" },
{ "id": "mem_5", "memory": "User recently started learning Rust" },
{ "id": "mem_6", "memory": "User is debugging authentication issues" }
{ "id": "mem_4", "memory": "[2026-10-02] User is working on Project Alpha" },
{ "id": "mem_5", "memory": "[2026-10-05] User recently started learning Rust" },
{ "id": "mem_6", "memory": "[2026-10-09] User is debugging authentication issues" }
],
"buckets": {}
"buckets": {
"preferences": []
}
}
}
```
`static` and `dynamic` are always returned. Each entry is `{ id, memory }`; the `id` is the memory ID, so you can [forget](/recall/memory-operations#forget-memories) a profile entry directly.
`static`, `dynamic`, and `buckets` are always returned. Omitting the request's `buckets` returns every effective bucket, including the built-in `preferences` bucket; specifying names returns only those buckets, and an empty list returns an empty bucket section. Each entry is `{ id, memory }`; the `id` is the memory ID, so you can [forget](/recall/memory-operations#forget-memories) a profile entry directly. The exception is synthesized summaries of older memories, whose `id` starts with `aggregated_` — they don't map to a single memory. Individual `dynamic` entries start with a `[YYYY-MM-DD]` date.
---
@ -123,7 +125,7 @@ The v5 profile call takes no query. When you also need query-ranked memories, ru
## Parameters
`namespace` is a top-level key (URL path). `body` is optional and accepts only `filter` and `buckets`.
`namespace` is the first argument (URL path). The options object is optional and accepts only `filter` and `buckets`.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|

View file

@ -7,17 +7,19 @@ icon: "/icons/hugeicons/settings-01.svg"
The self-hosted server aims for **zero configuration** — the only required input is one LLM provider key, which the first-boot wizard collects interactively (or set via env var for non-interactive deployments). Embeddings default to local English; you can pick another provider in the optional wizard step or via env. Everything else below is opt-in.
The installer writes API keys to `~/.supermemory/env`, which is loaded on every launch. You can also set variables in your shell or a process manager.
The installer writes API keys to `~/.supermemory/env`, and the first-boot wizard saves them encrypted to `env.enc` in the data directory. Saved keys are loaded on every launch. When the data directory is `~/.supermemory`, the server copies the installer's parsed plaintext `env` values into `env.enc` on the next launch before attempting to remove `env`. A failed migration or removal can leave the plaintext file in place. You can also set variables in your shell or a process manager.
## Core
| Variable | Purpose | Default |
|---|---|---|
| `PORT` (or `SUPERMEMORY_PORT`) | HTTP listen port | `6767` |
| `SUPERMEMORY_HOST` | In the next release, controls the listen address; not supported by v0.0.8 | `127.0.0.1` in the next release |
| `SUPERMEMORY_DATA_DIR` | Where the graph engine's data, auth secret, and model cache live | `./.supermemory` |
| `SUPERMEMORY_HOST` | Listen address (v0.0.9 and later). Set `0.0.0.0` only when you protect the server with TLS and firewall rules | `127.0.0.1` |
| `SUPERMEMORY_DATA_DIR` | Where the graph engine's data, auth secret, and model cache live | `~/.supermemory` |
**The currently published v0.0.8 binds all interfaces and its implicit local authentication is unsafe when exposed to untrusted networks. Do not expose it; restrict access with a firewall or run it on an isolated machine.** The next release will bind loopback and require the generated key for every API request, including requests from localhost. Its browser welcome page will no longer reveal the key; enter the key printed at first boot in the Memory tab. If you later expose that release through a reverse proxy, protect the endpoint with TLS and access controls.
From v0.0.9, an unset `SUPERMEMORY_DATA_DIR` uses `~/.supermemory`. An existing `./.supermemory` store in the current directory is still used if `~/.supermemory` has none. If both have a store, the server refuses to start until you set `SUPERMEMORY_DATA_DIR`. v0.0.8 and older always use `./.supermemory`.
From v0.0.9, the server binds `127.0.0.1` and every API request needs the generated key, including requests from localhost. The welcome page does not show the key; enter the key printed at first boot in the Memory tab. If you expose the server through a reverse proxy, protect it with TLS and access controls. **v0.0.8 and older bind all interfaces with implicit local authentication. Do not expose them; upgrade, or restrict access with a firewall.**
## LLM providers
@ -28,18 +30,20 @@ In production, Supermemory uses its own proprietary models tuned for long-horizo
| `OPENAI_API_KEY` | OpenAI — or any OpenAI-compatible endpoint, see below |
| `ANTHROPIC_API_KEY` | Anthropic |
| `GEMINI_API_KEY` | Google AI Studio (Gemini) |
| `GOOGLE_VERTEX_PROJECT_ID` + `GOOGLE_VERTEX_LOCATION` | GCP Vertex AI |
| `GROQ_API_KEY` | Groq |
| `WORKERS_AI_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Workers AI |
| `GOOGLE_VERTEX_PROJECT_ID` + `GOOGLE_VERTEX_LOCATION` | GCP Vertex AI |
<Tip>
No key set? The server walks you through it. On first boot, an interactive setup wizard asks which provider you want, securely prompts for the key, and saves it encrypted — including a custom base URL and model name if you pick an OpenAI-compatible endpoint.
</Tip>
With multiple providers configured, the first one in the order above is used.
For fast/text LLM tasks, the first configured provider in the order above is used. Image and video tasks use Vertex AI first, then Gemini.
<Note>
Image, video, and high-fidelity PDF understanding require a Gemini or Vertex AI key. Text ingestion, memory extraction, and search work with any provider.
Image and video understanding require a Gemini or Vertex AI key. PDF extraction requires `GEMINI_API_KEY`; Vertex AI doesn't cover it. Text ingestion, memory extraction, and search work with any provider.
Webpage ingestion fetches pages through Firecrawl and needs `FIRECRAWL_API_KEY`. Without it, webpage documents fail. URLs pointing to PDFs or media use their corresponding extractors instead.
</Note>
### Fully offline with local models
@ -60,22 +64,26 @@ OPENAI_MODEL=gpt-oss:20b
| `OPENAI_FAST_MODEL` | Override for fast/light tasks | `OPENAI_MODEL` |
| `OPENAI_TEXT_MODEL` | Override for heavier text tasks | `OPENAI_MODEL` |
## File storage
## Storage & persistence
### File storage
Uploaded files (PDFs, images) are stored on local disk inside `$SUPERMEMORY_DATA_DIR` and served by the server at `/files/:key`.
### Database snapshots & resource usage
The server saves its database and vector indices as one encrypted snapshot file at `$SUPERMEMORY_DATA_DIR/data`.
- **Idle (v0.0.7 and later):** if nothing changed since the last snapshot, the server skips it, so an idle server does no snapshot work.
- **While writing:** each snapshot is a full dump, taken every 10 seconds while writes continue. The interval is not configurable.
- **Shutdown:** on `SIGINT` or `SIGTERM`, pending changes are saved before the process exits.
Nothing to configure. Uploaded files (PDFs, images) are stored on local disk inside `$SUPERMEMORY_DATA_DIR` and served by the server at `/files/:key`.
## Embeddings
By default, vectors are computed locally with `Xenova/bge-base-en-v1.5` (768d) — no embedding API key. On interactive first boot you can pick a different provider after the LLM key step; for Docker/CI set env vars instead.
The full provider table, multilingual guidance, remote examples (OpenAI, Gemini, Ollama) and the dimension-lock warning are in [embeddings for self-hosting](/self-hosting/embeddings).
| Variable | Purpose | Default |
|---|---|---|
| `SUPERMEMORY_EMBEDDING_PROVIDER` | `local`, `openai`, `gemini`, or OpenAI-compatible remote | `local` |
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider | `Xenova/bge-base-en-v1.5` |
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match model and stored data | `768` |
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs | unset |
The `SUPERMEMORY_EMBEDDING_*` env vars, the local model table, remote examples (OpenAI, Gemini, Ollama) and the dimension lock are in [embeddings for self-hosting](/self-hosting/embeddings).
### Embedding performance
@ -132,7 +140,7 @@ These exist in the codebase but are exclusive to the [hosted platform](https://c
- **Optimized memory extraction** — the platform's extraction pipeline is tuned for higher quality at lower cost than bring-your-own-key
- **Managed scale** — globally distributed infrastructure, no capacity planning
Any other environment variables you may find referenced in the codebase are platform-only: the self-hosted binary ignores them even when set.
Besides the variables on this page, the binary honors a few operational switches: `SUPERMEMORY_VERBOSE=1` (detailed logs), `SUPERMEMORY_HIDE_API_KEY_IN_LOGS=1` (omit the key from the startup banner), `SUPERMEMORY_NO_OPEN=1` (don't open a browser on boot), and `SUPERMEMORY_NO_UPDATE_CHECK=1` (skip the update notification). The Docker image sets the last three.
## Example: production-ish `.env`

View file

@ -0,0 +1,109 @@
---
title: "Run Supermemory with Docker"
sidebarTitle: "Docker"
description: "Start the prebuilt Local image, keep your data, and upgrade an existing Compose installation."
icon: "docker"
---
Run Supermemory Local with the prebuilt Docker image. Docker chooses the Linux amd64 or arm64 image for your machine. You don’t need the server source, a local build, or a registry login.
You need Docker with the Compose plugin. Docker Desktop on macOS or Windows must run Linux containers. These shell examples use Bash or a compatible shell.
## Start a new installation
Use an empty directory for this installation. If you already run Supermemory with Compose, follow [Upgrade an existing installation](#upgrade-an-existing-installation) instead of overwriting your configuration.
Download the Compose file from the [0.0.9 release](https://github.com/supermemoryai/supermemory/releases/tag/server-v0.0.9):
```bash
release_url=https://github.com/supermemoryai/supermemory/releases
curl -fL "$release_url/download/server-v0.0.9/compose.yaml" -o compose.yaml
```
Create a `.env` file beside `compose.yaml`, replacing the placeholder with your provider key:
```dotenv
OPENAI_API_KEY=your_openai_api_key_here
SUPERMEMORY_VERSION=0.0.9
```
You can use `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, or `GROQ_API_KEY` instead of OpenAI. Keep `.env` private and don’t commit it. Docker runs non-interactively, so there’s no first-boot provider wizard.
Protect the file, pull the image, and start the service:
```bash
chmod 600 .env
unset SUPERMEMORY_VERSION
docker compose pull
docker compose up -d --wait --wait-timeout 180
docker compose ps
```
The published image is `docker.io/supermemory/supermemory`. `SUPERMEMORY_VERSION=0.0.9` pins this release; `latest` follows stable releases. The Compose file binds port `6767` to `127.0.0.1`, not the public network. Allow the first startup to finish before checking health:
```bash
curl --fail http://127.0.0.1:6767/v3/health
```
Local embeddings are the default. The first model download needs network access, and your chosen LLM provider may make external requests.
## Authenticate API requests
The server creates a local API key, separate from your LLM provider key. The Docker image hides that key from startup logs, so read it from the container:
```bash
SUPERMEMORY_API_KEY="$(docker compose exec -T supermemory cat /data/api-key)"
curl --fail -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
http://127.0.0.1:6767/v3/projects
```
Treat this key as a secret. API requests require it even on localhost. Use the same key with your SDK and set `baseUrl` (Python: `base_url`) to `http://localhost:6767`.
## Preserve data and configuration
The Compose project name is fixed as `supermemory`. Its named volume, `supermemory_supermemory-data`, mounts at `/data` and contains your store, API key, encryption identity, configuration, and model cache. Container replacement keeps that volume.
<Warning>
Don’t run `docker compose down -v` or delete the volume: that removes your stored data and credentials. Keep the project name and volume mapping unchanged when upgrading. Changing the provider or embedding settings is a separate operation.
</Warning>
The Compose file passes through the provider keys listed above and `FIRECRAWL_API_KEY`. To disable telemetry, add `SUPERMEMORY_DISABLE_TELEMETRY=1` to `.env`. Other [configuration variables](/self-hosting/configuration) need entries in the service’s `environment` block; setting them only in the host shell doesn’t pass them into the container.
## Upgrade an existing installation
Back up the complete stopped store before replacing its image. Run these commands from the directory containing your existing Compose file.
### Back up the store
Stop the service, then copy everything under `/data`, including its keys and identity files:
```bash
docker compose stop
backup_dir="backups/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$backup_dir"
chmod 700 "$backup_dir"
docker compose cp supermemory:/data/. "$backup_dir/"
```
Keep this backup and your existing `.env` file private. Confirm the copy completed before upgrading. These steps cover an existing Docker store; moving a native installation into Docker requires preserving that installation’s complete store and keys separately.
### Pull and start the new image
Keep your existing Compose configuration. If it uses an older GHCR image reference, change only its `image` line to the public Docker Hub image:
```yaml
image: docker.io/supermemory/supermemory:${SUPERMEMORY_VERSION:-latest}
```
Set `SUPERMEMORY_VERSION=0.0.9` in your existing `.env`, then pull and recreate the service without deleting its volume. Unset a previously exported version so `.env` takes precedence:
```bash
unset SUPERMEMORY_VERSION
docker compose pull
docker compose up -d --wait --wait-timeout 180
curl --fail http://127.0.0.1:6767/v3/health
```
Local verifies a complete original backup before migrating an existing store. Existing cleanup can remove older duplicate documents and their source links. Keep your manual backup too; don’t point an older image at a migrated store. To roll back, restore the complete pre-upgrade backup and use its matching previous version.
Native Windows remains on 0.0.8; these Docker images run Linux containers.

View file

@ -5,7 +5,7 @@ description: "Local and remote embedding providers for Supermemory local: defaul
icon: "/icons/hugeicons/route-02.svg"
---
Self-hosted Supermemory uses the **same embedding provider stack** as the hosted platform: local ONNX models, OpenAI, Gemini, or any OpenAI-compatible embeddings endpoint (including Ollama). LLM keys power extraction and summarization; embeddings are configured separately.
Self-hosted Supermemory supports local ONNX models, OpenAI, Gemini, or any OpenAI-compatible embeddings endpoint (including Ollama). LLM keys power extraction and summarization; embeddings are configured separately.
## Defaults
@ -28,7 +28,7 @@ On first boot with a TTY, Supermemory asks for an LLM API key (required), then o
1. Choose or paste an LLM provider key (OpenAI, Anthropic, Gemini, Groq, or OpenAI-compatible).
2. Optionally pick an embedding provider/model. **Press Enter to keep the local English model.**
3. Choices are saved encrypted under your data directory (`$SUPERMEMORY_DATA_DIR`, typically `./.supermemory` / `~/.supermemory`).
3. Choices are saved encrypted under your data directory (`$SUPERMEMORY_DATA_DIR`, `~/.supermemory` by default from v0.0.9, `./.supermemory` before).
Boot order is intentional: LLM keys load first so remote embedding options can reuse them (for example OpenAI or Gemini embeddings with the same key).
@ -42,10 +42,11 @@ For Docker, CI, or any non-interactive deploy, set env vars — there is **no in
| Variable | Purpose | Default |
|---|---|---|
| `SUPERMEMORY_EMBEDDING_PROVIDER` | Embedding backend: `local`, `openai`, `gemini`, or an OpenAI-compatible remote (`ollama` / custom base URL) | `local` |
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider | `Xenova/bge-base-en-v1.5` (local) |
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match the model and any already-stored data | `768` (local default) |
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs (Ollama, vLLM, etc.) | unset |
| `SUPERMEMORY_EMBEDDING_PROVIDER` | Embedding backend: `local`, `openai`, `gemini`, or `openai-compatible` (Ollama, vLLM, or any custom base URL) | `local` |
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider (e.g. `Xenova/bge-m3`, `text-embedding-3-small`) | `Xenova/bge-base-en-v1.5` (local), `text-embedding-3-small` (OpenAI and compatible), `gemini-embedding-001` (Gemini) |
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match the model's output size (native, or a reduced size for `text-embedding-3-*` and Gemini) and any stored data. Required for models not in the [table below](#local-models-with-known-dimensions) or the defaults | Inferred for known models (`768` for the local default) |
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs (Ollama, vLLM, etc.) | unset (`openai-compatible` falls back to `OPENAI_BASE_URL`) |
| `SUPERMEMORY_EMBEDDING_API_KEY` | API key for embedding endpoint (falls back to `OPENAI_API_KEY` / `GEMINI_API_KEY`; `openai-compatible` needs this or `OPENAI_API_KEY`; use any non-empty string if the endpoint has no auth) | unset |
| `OPENAI_API_KEY` | Used when provider is `openai` (or compatible) if not otherwise supplied | unset |
| `GEMINI_API_KEY` | Used when provider is `gemini` | unset |
@ -91,6 +92,22 @@ SUPERMEMORY_EMBEDDING_MODEL=Xenova/bge-base-en-v1.5
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
```
#### Local models with known dimensions
Local models do not support dimension reduction, so `SUPERMEMORY_EMBEDDING_DIMENSIONS` must match the model's native size:
| Model ID | Dimensions | Notes |
|---|---|---|
| `Xenova/bge-base-en-v1.5` | `768` | Default local model (English) |
| `Xenova/bge-m3` | `1024` | Multilingual (uses `cls` pooling) |
| `Xenova/bge-small-en-v1.5` | `384` | English (lightweight) |
| `Xenova/bge-large-en-v1.5` | `1024` | English (high capacity) |
| `Xenova/multilingual-e5-small` | `384` | Multilingual (lightweight) |
| `Xenova/multilingual-e5-base` | `768` | Multilingual |
| `Xenova/multilingual-e5-large` | `1024` | Multilingual (high capacity) |
| `Xenova/all-MiniLM-L6-v2` | `384` | English general purpose |
| `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | `384` | Multilingual sentence similarity |
### OpenAI
```bash
@ -105,16 +122,16 @@ SUPERMEMORY_EMBEDDING_DIMENSIONS=1536
```bash
GEMINI_API_KEY=...
SUPERMEMORY_EMBEDDING_PROVIDER=gemini
SUPERMEMORY_EMBEDDING_MODEL=text-embedding-004
SUPERMEMORY_EMBEDDING_MODEL=gemini-embedding-001
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
```
### Ollama (OpenAI-compatible)
```bash
SUPERMEMORY_EMBEDDING_PROVIDER=openai
SUPERMEMORY_EMBEDDING_PROVIDER=openai-compatible
SUPERMEMORY_EMBEDDING_BASE_URL=http://localhost:11434/v1
OPENAI_API_KEY=ollama
SUPERMEMORY_EMBEDDING_API_KEY=ollama
SUPERMEMORY_EMBEDDING_MODEL=nomic-embed-text
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
```
@ -127,15 +144,22 @@ Use the dimension published for your chosen model. A mismatch with vectors alrea
**Not supported in place.** Embeddings from different models (or different dimensions) are not comparable. Start from a fresh data directory or re-ingest all content so vectors stay in one space. If configured dimensions disagree with stored data, the server **refuses to boot**.
</Warning>
**Changing embeddings later:** Not supported in place. Start from a fresh data directory or re-ingest all content so vectors stay comparable.
<Note>
**Embedding env vars are ignored in v0.0.6 and v0.0.7-rc.2**
> [!IMPORTANT]
> **Model Mixing Bug in v0.0.5 (Exact match returns nothing)**
>
> In version `v0.0.5`, there was a bug where the server could mix different embedding models between write and read paths (e.g., document ingestion using OpenAI but memory queries using local default embeddings). In multilingual contexts like Japanese (which lacks space tokenization for fallback lexical FTS matching), this caused exact-text memory searches through `/v4/search` and `/v4/profile` to silently return `{"results":[],"total":0}`.
>
> **Resolution:**
> This was fully resolved in `v0.0.7` by locking the embedding plan uniformly across all document and query embedding paths (enforced via a locked plan in the database store). If you are running `v0.0.5` and experiencing this issue, you should upgrade to `v0.0.7` or later.
Those two releases ignore `SUPERMEMORY_EMBEDDING_*` and always use local `Xenova/bge-base-en-v1.5`. The env vars work in `v0.0.7` and later.
A data directory created on `v0.0.6` has no `embedding-plan.json`. From `v0.0.7`, if such a directory already has embeddings, the server locks it to `local · Xenova/bge-base-en-v1.5 · 768d`. To use another provider or model, point `SUPERMEMORY_DATA_DIR` at a fresh directory (or delete the old one) before starting with the new env vars.
</Note>
<Note>
**Model Mixing Bug in v0.0.5 (Exact match returns nothing)**
In version `v0.0.5`, there was a bug where the server could mix different embedding models between write and read paths (e.g., document ingestion using OpenAI but memory queries using local default embeddings). In multilingual contexts like Japanese (which lacks space tokenization for fallback lexical FTS matching), this caused exact-text memory searches through `/v4/search` and `/v4/profile` to silently return `{"results":[],"total":0}`.
**Resolution:**
This was fully resolved in `v0.0.7` by locking the embedding plan uniformly across all document and query embedding paths (enforced via a locked plan in the database store). If you are running `v0.0.5` and experiencing this issue, you should upgrade to `v0.0.7` or later.
</Note>
## Related

View file

@ -5,7 +5,7 @@ description: "Supermemory local is for builders. Supermemory Enterprise is for o
icon: "/icons/hugeicons/building-03.svg"
---
Supermemory local — the self-hosted binary — is free within its lite license limit and built for individual developers: local-first workflows, prototyping, and privacy-sensitive side projects. Its server source is not in the public repository. Text-memory processing can run offline after the first embedding-model download; URL ingestion uses a hosted reader service.
Supermemory local — the self-hosted binary — is free within its lite license limit and built for individual developers: local-first workflows, prototyping, and privacy-sensitive side projects. Its server source is not in the public repository. Text-memory processing can run offline after the first embedding-model download when the LLM is also local; webpage ingestion goes through Firecrawl and needs `FIRECRAWL_API_KEY`.
**Supermemory Enterprise** is the full platform, run for your organization: the same memory engine with proprietary models, organizational controls, and infrastructure that scales with you — without you operating any of it.
@ -44,7 +44,7 @@ Local is bounded by one machine — which is the point. Enterprise runs on globa
## Moving between them
The two speak the same API. Code written against your local server moves to Enterprise by changing the `baseUrl` — and vice versa. Prototype locally, ship on Enterprise.
The two speak the same API. Point your code at the destination's `baseUrl` and use a credential issued there. Changing the connection does not transfer your local data. Prototype locally, ship on Enterprise.
<CardGroup cols={2}>
<Card title="Talk to us" icon="/icons/hugeicons/mail-01.svg" href="mailto:dhravya@supermemory.com">

View file

@ -24,7 +24,7 @@ No Docker. No database to provision. No config files. It boots in seconds with e
Run the binary with nothing set and you get a complete memory system:
- **The Supermemory graph engine, embedded** — created automatically on first boot. No database to stand up, no connection strings.
- **Built-in local embeddings** — default `Xenova/bge-base-en-v1.5` (768d) on your machine, no API key. Same provider stack as cloud if you opt into OpenAI, Gemini, or Ollama — see [Embeddings](/self-hosting/embeddings).
- **Built-in local embeddings** — default `Xenova/bge-base-en-v1.5` (768d) on your machine, no API key. Opt into OpenAI, Gemini, or Ollama instead — see [Embeddings](/self-hosting/embeddings).
- **An API key, generated for you** — printed on first boot, ready to paste into any SDK.
- **The full Memory API** — `/ns/{namespace}/document`, `/ns/{namespace}/search`, `/ns/{namespace}/profile`, namespaces, the works.
@ -41,7 +41,7 @@ OPENAI_MODEL=gpt-oss:20b \
supermemory-server
```
Local graph engine, local embeddings, local LLM. Text-memory processing can stay on your machine after the embedding model's first download. Supermemory collects telemetry; you can disable it with `SUPERMEMORY_DISABLE_TELEMETRY=1`. URL ingestion uses a hosted reader service; use local text or file inputs for offline operation.
Local graph engine, local embeddings, local LLM. Text-memory processing can stay on your machine after the embedding model's first download. Supermemory collects telemetry; you can disable it with `SUPERMEMORY_DISABLE_TELEMETRY=1`. Webpage ingestion sends the URL to Firecrawl and needs `FIRECRAWL_API_KEY`; use local text inputs for offline operation. Also set `SUPERMEMORY_NO_UPDATE_CHECK=1` if the server must not check for releases.
## Drop-in with your existing code
@ -68,14 +68,14 @@ Self-hosted is free within its lite license limit and useful for local developme
|---|---|---|
| Full Memory API | ✅ | ✅ |
| Hybrid semantic search | ✅ | ✅ |
| Embeddings | Local default (or OpenAI / Gemini / Ollama) | Same provider stack, managed |
| File ingestion (PDFs, images) | ✅ | ✅ |
| Embeddings | Local default (or OpenAI / Gemini / Ollama) | Managed |
| File ingestion (PDFs, images) | ✅ (PDFs need `GEMINI_API_KEY`; images use Gemini or Vertex AI) | ✅ |
| [Connectors](/connectors/overview) (Google Drive, Notion, Gmail, OneDrive) | — | ✅ |
| [Supermemory MCP](/supermemory-mcp/mcp) | — | ✅ |
| Memory extraction | Your model, your key | Proprietary long-horizon models — higher quality, cheaper at scale |
| Infrastructure | Your machine | Globally distributed, scales with you |
If you outgrow a single machine — or want connectors, MCP, and the best-tuned extraction pipeline — [the platform](https://console.supermemory.ai) is one `serverURL` change away. Running this for a team or organization? See [Local vs. Enterprise](/self-hosting/local-vs-enterprise).
If you outgrow a single machine — or want connectors, MCP, and the best-tuned extraction pipeline — switch to [the platform](https://console.supermemory.ai) with its `baseUrl` and a hosted API key. Changing the connection does not transfer your local data. Running this for a team or organization? See [Local vs. Enterprise](/self-hosting/local-vs-enterprise).
## Next steps

View file

@ -45,7 +45,7 @@ Runs on `claude-haiku-4-5` — this one isn't currently configurable via env var
GEMINI_API_KEY=...
```
Runs on `gemini-3.1-flash-lite-preview` — also not currently overridable. This is the only key that also unlocks image, video, and high-fidelity PDF understanding (see the [full provider table](/self-hosting/configuration#llm-providers)).
Runs on `gemini-3.1-flash-lite-preview` — also not currently overridable. This is the only key on this page that also unlocks image, video, and PDF understanding (see the [full provider table](/self-hosting/configuration#llm-providers)).
</Tab>
<Tab title="OpenRouter">

View file

@ -58,12 +58,18 @@ supermemory-server
First boot sets everything up — the embedded Supermemory graph engine, local embeddings, and your credentials:
```
┌──────────────────────────────────────────────────┐
│ url http://localhost:6767 │
│ database ./.supermemory │
│ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
│ org id xxxxxxxxxxxxxxxxxxxxxx │
└──────────────────────────────────────────────────┘
╭───────────────────────────────────────────────────────────────╮
│ → supermemory ready │
│ │
│ url http://localhost:6767 │
│ database encrypted local storage (~/.supermemory) │
│ embeddings local · Xenova/bge-base-en-v1.5 · 768d │
│ workflow rivet │
│ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
│ org id xxxxxxxxxxxxxxxxxxxxxx │
│ boot 4.2s │
╰───────────────────────────────────────────────────────────────╯
every request needs this key: Authorization: Bearer <api key>
```
Save that API key — it's your bearer token for every request.
@ -76,6 +82,8 @@ In production, Supermemory runs proprietary models tuned for long-horizon data u
**Docker / non-interactive:** set an LLM key via env and, if you don’t want local embeddings, set `SUPERMEMORY_EMBEDDING_PROVIDER` / `MODEL` / `DIMENSIONS`. There is no wizard without a TTY.
</Tip>
For the prebuilt Docker image, follow [Run Supermemory with Docker](/self-hosting/docker) for installation, authentication, backups, and upgrades.
## Add your first memory
<Note>
@ -116,11 +124,12 @@ client.add(
</Tab>
<Tab title="curl">
```bash
curl "http://localhost:6767/ns/user_dhravya/document?dreaming=instant" \
curl http://localhost:6767/ns/user_dhravya/document \
-H "Authorization: Bearer sm_..." \
-H "Content-Type: application/json" \
-d '{
"content": "I am Dhravya. I love building dev tools and I am allergic to peanuts."
"content": "I am Dhravya. I love building dev tools and I am allergic to peanuts.",
"dreaming": "instant"
}'
```
</Tab>
@ -161,8 +170,9 @@ By default, all state lives in a single directory you can back up or move:
| Path | Contents |
|---|---|
| `./.supermemory/` (or `$SUPERMEMORY_DATA_DIR`) | The Supermemory graph engine's data, auth secret, embedding model cache |
| `~/.supermemory/env` | API keys saved by the installer, loaded on every launch |
| `~/.supermemory/` (or `$SUPERMEMORY_DATA_DIR`; `./.supermemory/` before v0.0.9) | The Supermemory graph engine's data, API key, auth secret, embedding model cache |
| `~/.supermemory/env.enc` | Provider keys and embedding choices saved by the first-boot wizard, encrypted and loaded on every launch |
| `~/.supermemory/env` | Plaintext API keys written by the installer. On launch, the server copies parsed values into `env.enc` before attempting to remove this file; it can remain if migration or removal fails |
## Next steps

View file

@ -16,11 +16,13 @@ GET /ns/{namespace}/document/{id}?include=chunks,memories
Pass `include` as a comma-separated list to return chunks, memories, or both. Keys you omit are absent; requested keys with no results are empty arrays. Lifecycle fields move under `system`:
```json
{"system":{"status":"done","createdAt":"...","updatedAt":"..."}}
{"system":{"status":"done","dreamingStatus":"dreaming","createdAt":"...","updatedAt":"..."}}
```
`system.status` is one of `unknown`, `queued`, `extracting`, `chunking`, `embedding`, `indexing`, `done`, or `failed`.
`system.dreamingStatus` tracks memory generation separately. It is `dreaming` while memories for the latest content are queued, being generated, or waiting for a retry, and `done` once they are generated or when the document needed none. Under the default `dreaming: "dynamic"`, `status` can reach `done` minutes before `dreamingStatus` does, so wait on `dreamingStatus` when the next step reads memories or profiles. Legacy `GET /v3/documents/{id}` returned it as a top-level `dreamingStatus`; v5 nests it under `system` on document reads and on document list items.
### Retrieve a memory and its history
```bash v5
@ -29,7 +31,7 @@ GET /ns/{namespace}/memories/{id}?include=related,documents&relatedLimit=10
Returns one memory with `id`, `memory`, `metadata`, `isStatic`, `isInference`, `isLatest`, `isForgotten`, `version`, and `system.createdAt/updatedAt`. A memory from another namespace returns `404`; a forgotten memory, or one past its `forget_after`, is still returned with `isForgotten: true`.
- `include=related` adds `included.related.parents` (earlier versions), `children` (newer versions), and `siblings` (memories connected by `extends` or `derives`). Each list walks outward from the memory until it holds `relatedLimit` items (default `10`, maximum `100`). Forgotten memories and memories in other namespaces are left out.
- `include=related` adds `included.related.parents` (earlier versions), `children` (newer versions), and `siblings` (memories directly connected by `extends` or `derives`). Parents and children reach up to three versions away, nearest first. Each list holds at most `relatedLimit` items (default `10`, maximum `100`). Forgotten memories and memories in other namespaces are left out.
- `include=documents` adds `included.document`, the most recently updated source document. With both values, every related memory also carries its own `document`.
### List documents, chunks, or memories
@ -46,9 +48,11 @@ POST /ns/{namespace}/list/{type}?page=1&limit=100&sort=createdAt&order=desc
```
</CodeGroup>
Set `type` to `documents`, `chunks`, or `memories`. Pagination and sorting move to the query string as plain integers and values; the body takes an optional `filter` and, for memories, `include`. Defaults are `page=1`, `limit=10` (maximum 100), `sort=createdAt`, and `order=desc`.
Set `type` to `documents`, `chunks`, or `memories`. Pagination and sorting move to the query string as plain integers and values; the body takes an optional `filter` and `include` object. Defaults are `page=1`, `limit=10` (maximum 100), `sort=createdAt`, and `order=desc`. `sort` accepts `createdAt` or `updatedAt` for documents and memories, and `createdAt` or `position` for chunks; any other combination returns `400`.
Memory lists hide forgotten memories and memories past their `forget_after`. Send `"include": {"forgotten": true}` to list them too; they come back with `isForgotten: true`. Document memories (`include=memories` on `GET /ns/{namespace}/document/{id}`) report the same flag.
Document lists omit `content` by default. Send `{"include":{"content":true}}` to return the last published extracted text, including text from uploaded files. `content` is `null` before the first extraction publishes. During a replacement, previous text remains until new text publishes and also remains if extraction fails. Including content can make responses large.
Memory lists return only the latest version of each memory and hide forgotten memories and memories past their `forget_after`. Sending `"include": {"forgotten": true}` admits forgotten or expired rows only if they remain latest; they come back with `isForgotten: true`. Normal forgetting makes a memory non-latest, so this list does not recover it even with that flag. To search such memories, use search with `include.forgotten`. Document memories (`include=memories` on `GET /ns/{namespace}/document/{id}`) report the same forgotten flag.
Every response contains `documents`, `chunks`, `memories`, and `pagination`. Only the selected resource array is populated. Replace legacy `memories` assumptions in document-list callers with `documents`.
@ -66,7 +70,7 @@ DELETE /ns/{namespace}/document
```
</CodeGroup>
The v5 array accepts 1–100 Supermemory or caller-defined IDs. Inspect both `count` and per-ID `errors`; HTTP success can include partial failures.
The v5 array accepts 1–100 Supermemory or caller-defined IDs. Inspect both `count` and per-ID `errors`; HTTP success can include partial failures. Only documents whose `system.status` is `done` or `failed` are deleted; one that is still processing comes back in `errors` as `Document is still processing`.
### With the SDK
@ -98,7 +102,7 @@ const { count, errors } = await supermemory.documents.delete("user_1", {
```
</CodeGroup>
There is no separate processing list. Read `system.status` on each item returned by `supermemory.list(namespace, "documents")`. Chunks come from `supermemory.list(namespace, "chunks")` or `documents.get` with `include: ["chunks"]`.
There is no separate processing list. Read `system.status` and `system.dreamingStatus` on each item returned by `supermemory.list(namespace, "documents")`. Chunks come from `supermemory.list(namespace, "chunks")` or `documents.get` with `include: ["chunks"]`.
### Forget memories
@ -114,6 +118,7 @@ See [memory forgetting](/migration/api-v5-memory-forgetting) for the complete dr
### Verification
- Assert requested empty includes are `[]`, while omitted includes are absent.
- Move readers of the legacy top-level `dreamingStatus` to `system.dreamingStatus`, and gate memory or profile reads on it rather than on `system.status`.
- Paginate each resource type until `currentPage >= totalPages`; unselected arrays stay empty.
- Verify chunk rows contain their parent `documentId`.
- Exercise partial document-delete failures and semantic dry runs.

View file

@ -6,7 +6,7 @@ v5 makes the difference between adding new information and replacing the canonic
| --- | --- | --- |
| Add information to a stable caller ID | `POST /ns/{namespace}/document` | Append/diff |
| Replace text or URL content | `PATCH /ns/{namespace}/document/{id}` | Replace and reprocess |
| Update only supporting fields | Same `PATCH` without `content` | Canonical content unchanged |
| Update only metadata, context, or date | Same `PATCH` without `content` | Canonical content unchanged |
| Replace a source file and its user metadata | `POST /ns/{namespace}/document/file/{id}` | Full replacement and reprocess |
| Partially update a file or its supporting fields | `PATCH /ns/{namespace}/document/file/{id}` | Omitted fields remain unchanged |
@ -24,7 +24,7 @@ PATCH /ns/user_1/document/doc_1
```
</CodeGroup>
The v5 body accepts any non-empty subset of `content`, `supportingContext`, `metadata`, `group`, or `date`, plus optional `taskType` and `dreaming` (which alone do not count as a change). Supplying `content` makes it the new canonical source; facts supported only by the previous source can disappear after reprocessing.
The v5 body accepts any non-empty subset of `content`, `supportingContext`, `metadata`, or `date`, plus optional `taskType` and `dreaming` (which alone do not count as a change). `group` is an ingestion-side setting: it picks which existing memories serve as context while content is processed, so changing it only takes effect when the document is re-ingested. Send it together with `content`. Supplying `content` makes it the new canonical source; facts supported only by the previous source can disappear after reprocessing.
### Replace a file-backed document
@ -37,7 +37,7 @@ metadata={"revision":2}
dreaming=dynamic
```
POST requires `file` and replaces the canonical source plus user-controlled metadata, grouping, context, and date. Omitted supporting fields are cleared. Use it when the submitted request is the complete new representation of the file-backed document.
POST requires `file` and replaces the canonical source plus caller metadata, including `date`. Omitted metadata and date are cleared. `supportingContext` is stored on the namespace, so omitting it leaves the namespace's existing context unchanged. Use it when the submitted request is the complete new representation of the file-backed document.
### Partially update a file-backed document
@ -49,7 +49,7 @@ metadata={"reviewed":true}
dreaming=dynamic
```
PATCH changes only supplied fields. Include `file` to replace the source while retaining omitted supporting fields, or omit `file` for metadata-, group-, context-, or date-only changes. `metadata` and `group` are JSON-encoded strings; `supportingContext` and `date` are plain strings.
PATCH changes only supplied fields. Include `file` to replace the source while retaining omitted supporting fields, or omit `file` for metadata-, context-, or date-only changes. Like the JSON update, `group` only takes effect when the file is re-ingested, so send it together with `file`. `metadata` and `group` are JSON-encoded strings; `supportingContext` and `date` are plain strings.
<Warning>
There is no public v5 `PUT /ns/{namespace}/document/file/{id}` operation. Use POST for a complete replacement and PATCH for a partial update.
@ -90,7 +90,7 @@ The path `id` may be the Supermemory document ID or your caller-defined ID. It i
### Processing and conflicts
Content or file replacement is accepted before downstream processing completes. A document still processing, a namespace conflict, or a conflicting internal file path can return `409`; retry only after the conflicting operation reaches a terminal state.
Content or file replacement is accepted before downstream processing completes. A namespace merge in progress or a conflicting internal file path returns `409`; retry a merge conflict after the merge finishes.
### Verification

View file

@ -83,7 +83,7 @@ await supermemory.search("user_1", {
```
</CodeGroup>
`filter` is a typed object under `body`, not a JSON string. The same shape works in `supermemory.list` and `supermemory.profile`. Keys are literal: `customer.plan` is one field name, not a nested path.
`filter` is a typed object in the request body (a field of the options object in the SDKs), not a JSON string. The same shape works in `supermemory.list` and `supermemory.profile`. Keys are literal: `customer.plan` is one field name, not a nested path.
### Deterministic conversion

View file

@ -16,7 +16,7 @@ Content-Type: application/json
{"ids":["mem_1","mem_2"]}
```
Send 1–500 IDs. The response reports successful IDs in `matches` and missing or ineligible IDs in `errors`, so HTTP success does not imply every requested ID changed.
Send 1–500 IDs. The response reports successful IDs in `matches` and missing, already forgotten, or out-of-namespace IDs in `errors`, so HTTP success does not imply every requested ID changed.
### Preview a semantic request
@ -27,7 +27,7 @@ Content-Type: application/json
{"query":"outdated home address","dryRun":true}
```
`dryRun: true` performs selection without changing memory state. `dryRun: false` forgets the memories selected when that request executes.
`dryRun: true` performs selection without changing memory state. `dryRun: false` forgets the memories selected when that request executes. `query` is 1–2000 characters, and each request selects at most 100 memories. After applying a batch, repeat the preview-and-apply workflow to find more; repeated previews alone do not paginate and may return the same matches.
### Avoid selection drift
@ -53,7 +53,7 @@ Use this workflow when a human or policy must approve the exact set. Re-running
}
```
`count` always equals `matches.length`. Both dry-run and applied semantic requests use this shape; the payload alone does not replace your record of which mode was sent.
`count` always equals `matches.length`. Semantic requests always return an empty `errors` array. Both dry-run and applied semantic requests use this shape; the payload alone does not replace your record of which mode was sent.
### With the SDK

View file

@ -69,6 +69,6 @@ await supermemory.organization.update({
- Compare the v5 context with the legacy `filterPrompt` before cutover.
- Set, replace, and clear organizational context.
- Confirm `namespaceCount` agrees with `pagination.totalItems` from `GET /namespaces` for the same credentials.
- Confirm `namespaceCount` agrees with `pagination.totalItems` from `GET /namespaces` when called with an unrestricted org key. `namespaceCount` always counts every namespace in the organization, while `GET /namespaces` only counts the namespaces the caller can read.
- Verify non-admin callers receive `403` on PATCH.
- Confirm removed fields are not required by downstream configuration code.

View file

@ -6,7 +6,7 @@
The API base URL and bearer keys do not change.
**Where parameters live:** `GET` options go in the query string. `POST`, `PATCH`, and `PUT` options go in the JSON body, or as form fields on file uploads; the one exception is list pagination (`page`, `limit`, `sort`, `order`), which stays in the query string. `DELETE` options such as `moveTo` go in the query string, while bulk deletes send their `ids` in the JSON body. Routes reject options sent in the wrong place with `400`. v5 application routes are unversioned; [`/v5/reference`](https://api.supermemory.ai/v5/reference) is the interactive v5 reference, not an API path prefix.
**Where parameters live:** `GET` options go in the query string. `POST`, `PATCH`, and `PUT` options go in the JSON body, or as form fields on file uploads; the one exception is list pagination (`page`, `limit`, `sort`, `order`), which stays in the query string. `DELETE` options such as `moveTo` go in the query string, while bulk deletes send their `ids` in the JSON body. Most routes reject options sent in the wrong place with `400`. `DELETE /ns/{namespace}` does not: it ignores a JSON body, so a `moveTo` sent in the body permanently deletes the namespace instead of moving it. v5 application routes are unversioned; [`/v5/reference`](https://api.supermemory.ai/v5/reference) is the interactive v5 reference, not an API path prefix.
## Recommended migration process
@ -55,7 +55,7 @@ await supermemory.add("user_1", { content: "new turn" })
```
</CodeGroup>
Every v5 call takes one object. `namespace`, path IDs, and query parameters are top-level keys; the JSON body goes under `body`. Legacy `containerTag` never appears in a body again. There is no v5 Python SDK yet.
URL values are positional (`namespace` first, then `id` where there is one); everything else goes in one object. Legacy `containerTag` never appears in a body again. The Python SDK speaks v5 from 5.0.0 with the same shape and keyword arguments: `client.add("user_1", content="new turn")`.
## Endpoint map

View file

@ -5,7 +5,7 @@ v5 returns a maintained profile directly and gives profile bucket definitions th
<CodeGroup>
```bash Legacy
POST /v4/profile
{"containerTag":"user_1","q":"work preferences","threshold":0.6,"include":{}}
{"containerTag":"user_1","q":"work preferences","threshold":0.6,"include":["static","dynamic","buckets"]}
```
```bash v5
@ -22,13 +22,18 @@ Remove legacy `q`, `threshold`, and `include`. If the caller needs query-ranked
{
"profile": {
"static": [{ "id": "mem_1", "memory": "The user works in design" }],
"dynamic": [{ "id": "mem_2", "memory": "The user is preparing a launch" }],
"buckets": { "work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }] }
"dynamic": [{ "id": "mem_2", "memory": "[2026-10-01] The user is preparing a launch" }],
"buckets": {
"preferences": [],
"work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }]
}
}
}
```
`static` and `dynamic` are always returned and cannot be disabled. Omit `buckets` in the request to return every effective custom bucket; pass up to 50 names to narrow only the bucket section.
`static` and `dynamic` are always returned and cannot be disabled. Omit `buckets` in the request to return every effective bucket, including the built-in `preferences` bucket; pass up to 50 names to narrow only the bucket section.
Entries change from v4 strings to `{ id, memory }` objects, and the v4 `[Recent]`/`[Summary]` text prefixes are dropped. Synthesized summaries of older memories now carry an `id` starting with `aggregated_`; individual `dynamic` entries keep their `[YYYY-MM-DD]` date prefix.
### Read bucket definitions
@ -65,7 +70,7 @@ DELETE /ns/user_1/profile/buckets
{"buckets":["work"]}
```
Names must be unique. Organization-owned buckets can appear in the effective GET response but cannot be changed or removed through namespace PUT or DELETE calls.
Names must be unique. Organization-owned buckets can appear in the effective GET response but cannot be changed or removed through namespace PUT or DELETE calls — naming one returns `409`. The built-in `preferences` bucket always stays in the effective set; DELETE only removes namespace-owned buckets.
### With the SDK
@ -92,7 +97,7 @@ await supermemory.profiles.deleteBuckets("user_1", {
```
</CodeGroup>
`supermemory.profile` takes no query. `body` is optional and accepts only `filter` and `buckets`. Read `profile.static`, `profile.dynamic`, and `profile.buckets[name]` as arrays of `{ id, memory }`.
`supermemory.profile(namespace, options?)` takes no query. The options object is optional and accepts only `filter` and `buckets`. Read `profile.static`, `profile.dynamic`, and `profile.buckets[name]` as arrays of `{ id, memory }`.
### Verification

View file

@ -1,4 +1,4 @@
Check which client you call the API through before you translate requests. Both official SDKs speak v5; the integration packages do not yet.
Check which client you call the API through before you translate requests. Both official SDKs and `@supermemory/tools` 3.x speak v5; check the table before upgrading anything else.
| Client | v5 support | What to do |
| --- | --- | --- |
@ -50,7 +50,7 @@ Some v4 methods are gone from the v5 SDK. Most have a v5 way to do the same thin
| `memories.updateMemory(...)` | Update the source document with `client.documents.update(namespace, id, { content })`. |
| `documents.search(...)` | `client.search(namespace, { query, searchMode: "chunks" })` |
| `documents.chunks(id)` | `client.documents.get(namespace, id, { include: ["chunks"] })` |
| `documents.listProcessing()` | `client.list(namespace, "documents")` and read `system.status` on each item |
| `documents.listProcessing()` | `client.list(namespace, "documents")` and read `system.status` and `system.dreamingStatus` on each item |
| `containerTags.merge(...)`, `mergeStatus(...)` | `client.namespaces.delete(source, { moveTo: target })`. One source per call; the response carries an `operationId`. |
| `memories.forget({ content })` | `client.memories.forgetMatching(namespace, { query, dryRun: true })` to preview, then `memories.forget(namespace, { ids })` |

View file

@ -1781,6 +1781,97 @@ a[class*="-ml-10"] {
font-weight: inherit !important;
}
/* Mintlify paints group-hover:bg-* behind anchor icons; on an <img> that fills the whole box */
img[src*="/icons/hugeicons/"] {
background-color: transparent !important;
}
/* Icons are black/white <img>s; opacity tones them to the label colour (#3e3f3f light, #9e9f9f dark) */
#sidebar-content {
--sm-nav-icon-rest: 0.76;
}
.dark #sidebar-content {
--sm-nav-icon-rest: 0.62;
}
#sidebar-content a img[src*="/icons/hugeicons/"] {
opacity: var(--sm-nav-icon-rest);
transition: opacity var(--sm-dur-hover) var(--sm-ease-out) !important;
}
#sidebar-content a:hover img[src*="/icons/hugeicons/"],
#sidebar-content a[aria-current] img[src*="/icons/hugeicons/"] {
opacity: 1;
}
/* Anchors ship as a bare 24px row; give them the page items' 32px box (36px pitch kept via the margin) */
#sidebar-content a.nav-anchor {
column-gap: 12px;
padding: 6px 12px 6px 16px !important;
margin-bottom: 4px !important;
line-height: 20px !important;
border-radius: var(--sm-radius-lg) !important;
transition:
color var(--sm-dur-hover) var(--sm-ease-out),
background-color var(--sm-dur-hover) var(--sm-ease-out);
}
#sidebar-content a.nav-anchor:not([aria-current]):hover {
background-color: var(--sm-control-hover) !important;
color: var(--sm-text) !important;
}
/* Mintlify fakes bold on selected items with a 0.2px text-shadow; keep true Geist Medium */
#navigation-items a[aria-current],
#navigation-items a[aria-current] *,
a.nav-tabs-item,
a.nav-tabs-item * {
text-shadow: none !important;
}
/* Inactive top tabs ship in the active ink, so hover had nothing to change */
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])) {
color: var(--sm-text-2) !important;
}
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])):hover {
color: var(--sm-text) !important;
}
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])) img {
opacity: var(--sm-nav-icon-rest, 0.76);
transition: opacity var(--sm-dur-hover) var(--sm-ease-out) !important;
}
.dark a.nav-tabs-item img {
--sm-nav-icon-rest: 0.62;
}
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])):hover img {
opacity: 1;
}
#search-bar-entry:hover {
background-color: rgb(9 17 32 / 0.06) !important;
}
.dark #search-bar-entry:hover {
background-color: rgb(255 255 255 / 0.06) !important;
}
.dark [class*="text-tooltip-foreground"] {
background-color: #262626 !important;
}
.sm-home-link-icon {
transition: background-color var(--sm-dur-hover) var(--sm-ease-out);
}
.sm-home-link:hover .sm-home-link-icon {
background: var(--sm-surface);
}
.dark img[src*="/icons/hugeicons/"],
.sm-scene .m-ic.is-on img[src*="/icons/hugeicons/"],
.sm-scene .m-badge img[src*="/icons/hugeicons/"] {

View file

@ -11,7 +11,7 @@ Buckets are **custom topical categories** for a profile — an axis that sits al
New to buckets? Read the [conceptual overview](/concepts/user-profiles#buckets) first — this page is the API reference for reading, creating, and managing them.
</Tip>
Every org starts with a built-in `preferences` bucket. You can define your own at the organization level, and add more per namespace (what v3/v4 called a container tag) — all covered below.
Every org starts with a built-in `preferences` bucket that is always present — buckets you define at the organization level, or add per namespace (what v3/v4 called a container tag), are added alongside it. All covered below.
---
@ -50,11 +50,11 @@ Every org starts with a built-in `preferences` bucket. You can define your own a
"dynamic": [],
"buckets": {
"preferences": [
{ "id": "mem_1", "memory": "[Summary] Prefers concise, technical answers and dark-mode tooling" },
{ "id": "mem_2", "memory": "[Recent] Switched their editor to Zed" }
{ "id": "mem_2", "memory": "Switched their editor to Zed" },
{ "id": "aggregated_0_1760000000000", "memory": "Tooling and style: Prefers concise, technical answers and dark-mode tooling" }
],
"goals": [
{ "id": "mem_3", "memory": "[Recent] Wants to ship the billing revamp this quarter" }
{ "id": "mem_3", "memory": "Wants to ship the billing revamp this quarter" }
]
}
}
@ -62,7 +62,7 @@ Every org starts with a built-in `preferences` bucket. You can define your own a
```
<Note>
**`[Recent]` and `[Summary]` labels.** To keep profiles dense, an entity's older memories are periodically aggregated into a short synthesis. Entries prefixed `[Summary]` are that aggregated context; entries prefixed `[Recent]` were ingested since the last aggregation and aren't summarized yet. The `dynamic` section uses the same `[Recent]` prefix (plus a `[YYYY-MM-DD]` date). Strip the prefixes if you only want raw text, or keep them to signal recency to your model.
**Summaries vs. individual memories.** To keep profiles dense, once a bucket grows large enough its older memories are periodically aggregated into a short synthesis. Synthesized entries have an `id` starting with `aggregated_` and don't map to a single memory; entries with a regular memory ID were ingested since the last aggregation and aren't summarized yet. v5 returns plain text — the `[Recent]`/`[Summary]` prefixes from v4 are not included, so use the `aggregated_` ID prefix to tell summaries apart. Individual `dynamic` entries still start with a `[YYYY-MM-DD]` date.
</Note>
### List bucket definitions
@ -106,7 +106,7 @@ Bucket definitions live at two levels: **organization** (the default set every n
### Namespace buckets
`setBuckets` sends 1 to 50 name-to-description entries. Existing namespace names are updated, new names are added, and omitted namespace buckets remain unchanged. These are **add-only** on top of org buckets — a namespace always keeps every org bucket, and if a namespace bucket's name collides with an org bucket, the org's definition wins in the merged, effective set used at ingestion and read time.
`setBuckets` sends 1 to 50 name-to-description entries. Existing namespace names are updated, new names are added, and omitted namespace buckets remain unchanged. These are **add-only** on top of org buckets — a namespace always keeps every org bucket. Sending a name the org already defines returns `409`, and if an org bucket is added later with the same name as a namespace bucket, the org's definition wins in the merged, effective set used at ingestion and read time.
<Tabs>
<Tab title="TypeScript">
@ -179,7 +179,7 @@ If you'd rather start from a template than write descriptions from scratch, thes
### Default bucket
If neither the org nor the namespace has configured any buckets, ingestion falls back to a single built-in `preferences` bucket, scoped tightly to explicit first-person statements ("prefers X over Y", "always uses W") — not inferred traits or general observations. Configuring your own buckets replaces this default.
Every namespace has a built-in `preferences` bucket, scoped tightly to explicit first-person statements ("prefers X over Y", "always uses W") — not inferred traits or general observations. It is always part of the effective set: configuring your own buckets adds to it rather than replacing it, and `deleteBuckets` can't remove it. Defining a bucket named `preferences` only overrides its description; an empty description falls back to the built-in one.
---
@ -203,7 +203,7 @@ Bucket descriptions steer classification. A precise description ("Explicit first
### Instructions
Bucket descriptions only steer classification *within* a bucket — they don't tell the model anything about the namespace itself. For that, set [`supportingContext`](/concepts/customization#entity-context) on the namespace: a free-text field that's appended alongside the org-level `organizationalContext` into the same prompt the extraction/classification step uses, so it shapes bucket assignment too, not just fact extraction.
Bucket descriptions only steer classification *within* a bucket — they don't tell the model anything about the namespace itself. For that, set [`supportingContext`](/concepts/customization#supporting-context) on the namespace: a free-text field that's appended alongside the org-level `organizationalContext` into the same prompt the extraction/classification step uses, so it shapes bucket assignment too, not just fact extraction.
<Tabs>
<Tab title="TypeScript">

View file

@ -13,7 +13,7 @@ icon: "/icons/hugeicons/book-open-01.svg"
| `PATCH /ns/{namespace}/connectors/{id}` | Change what it syncs: `selection` and `documentLimit` |
| `POST /ns/{namespace}/connectors/{id}/sync` | Trigger a sync now. Returns 409 while one is already running |
| `DELETE /ns/{namespace}/connectors/{id}` | Disconnect, optionally deleting the documents it imported |
| `GET /connectors` | List every connector in the organization, across namespaces |
| `GET /connectors` | List connectors across every namespace this key can read |
Providers: `notion`, `google-drive`, `onedrive`, `gmail`, `github` authenticate with OAuth. `web-crawler`, `s3`, and `granola` take their configuration in the create body and need no login.
@ -32,7 +32,7 @@ const { id, authorization } = await supermemory.connectors.create("user_123", {
// Config provider: starts syncing immediately, authorization is null
const crawler = await supermemory.connectors.create("user_123", {
provider: "web-crawler",
config: { startUrl: "https://docs.example.com" }
config: { startUrl: "https://docs.example.com" },
documentLimit: 200,
})
@ -48,7 +48,7 @@ curl -X POST "https://api.supermemory.ai/ns/user_123/connectors" \
</CodeGroup>
<Note>
A pending OAuth connector is not returned by list or get until the user completes authorization. The `authorization.url` expires after one hour (see `authorization.expiresAt`).
A pending OAuth connector is not returned by list until the user completes authorization. Get still returns it, with `system.status: "pending_authorization"`. The `authorization.url` expires after one hour (see `authorization.expiresAt`).
</Note>
See the [connector guides](/connectors/overview) for per-provider setup, selection rules, and troubleshooting.

View file

@ -41,4 +41,6 @@ curl -X POST "https://api.supermemory.ai/ns/user_1/list/documents?page=1&limit=5
```
</CodeGroup>
Semantic forgetting supports `dryRun: true`, so you can review matches before deleting them. See [document read migration](/migration/api-v5-document-reads) and [memory migration](/migration/api-v5-recall).
Document lists omit `content` by default. Send `{"include":{"content":true}}` to return published extracted text, including text from uploaded files. See [Document operations](/ingestion/document-operations) for response and processing details.
Semantic forgetting supports `dryRun: true`, so you can review matches before deleting them. See [document read migration](/migration/api-v5-document-reads) and [memory forgetting migration](/migration/api-v5-memory-forgetting).

View file

@ -27,10 +27,10 @@ const { id, status } = await supermemory.add("user_1", {
```
```bash curl
curl -X POST "https://api.supermemory.ai/ns/user_1/document?dreaming=instant" \
curl -X POST "https://api.supermemory.ai/ns/user_1/document" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"The user prefers concise project updates","id":"conv_1","metadata":{"source":"chat"}}'
-d '{"content":"The user prefers concise project updates","id":"conv_1","metadata":{"source":"chat"},"dreaming":"instant"}'
```
</CodeGroup>

View file

@ -36,7 +36,7 @@ import { Supermemory } from "supermemory"
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
```
Every SDK call takes one object. `namespace`, path IDs, and query parameters are top-level keys; the JSON body goes under `body`. Each reference page below shows the SDK call next to the equivalent `curl`. There is no v5 Python SDK yet.
URL values are positional arguments (`namespace` first, then `id` where there is one), and everything else goes in one object: `supermemory.add("user_123", { content })`. Each reference page below shows the SDK call next to the equivalent `curl`. The Python SDK (`pip install supermemory`, 5.0.0 and later) follows the same shape with keyword arguments: `client.add("user_123", content="...")`.
<CardGroup cols={2}>
<Card title="Migrate to v5" icon="arrow-up-right" href="/migration/api-v5">

View file

@ -36,4 +36,4 @@ curl -X PUT "https://api.supermemory.ai/ns/user_1/profile/buckets" \
Profiles always contain static and dynamic sections, giving applications both durable knowledge and fresh context without composing them manually. Organization-owned buckets may be inherited but cannot be changed through namespace bucket endpoints.
See [profile migration](/migration/api-v5-recall).
See [profile migration](/migration/api-v5-profiles).

View file

@ -41,7 +41,7 @@
"react-dom": "^19.2.4",
"supermemory": "^5.0.0",
"tailwind-merge": "^3.4.0",
"zod": "^4.4.3"
"zod": "^4.6.6"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250620.0",

View file

@ -83,16 +83,21 @@ describe("MCP authentication", () => {
})
it("rejects a token issued for a different audience", async () => {
vi.spyOn(console, "error").mockImplementation(() => {})
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
const token = await signToken({ audience: "https://api.example.com" })
await expect(
validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet),
).resolves.toBeNull()
expect(warn).toHaveBeenCalledTimes(1)
expect(warn.mock.calls[0]?.[0]).toMatch(/^OAuth token rejected: /)
expect(error).not.toHaveBeenCalled()
})
it("rejects expired tokens and tokens without a subject", async () => {
vi.spyOn(console, "error").mockImplementation(() => {})
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
const expired = await signToken({ expiresIn: "-1s" })
const noSubject = await signToken({ subject: "" })
@ -102,10 +107,34 @@ describe("MCP authentication", () => {
await expect(
validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet),
).resolves.toBeNull()
expect(warn).toHaveBeenCalledTimes(1)
expect(warn.mock.calls[0]?.[0]).toMatch(/^OAuth token rejected: .*exp/)
expect(error).not.toHaveBeenCalled()
})
it("logs a JWKS failure as an auth backend outage and surfaces TransientAuthError", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
const token = await signToken()
const failingKeySet = () =>
Promise.reject(
Object.assign(new Error("JWKS endpoint unavailable"), {
code: "ERR_JWKS_TIMEOUT",
}),
)
await expect(
validateOAuthToken(token, API_URL, MCP_RESOURCE, failingKeySet),
).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(error.mock.calls[0]?.[0]).toBe(
"Auth backend unavailable during OAuth token validation: JWKS fetch failed: JWKS endpoint unavailable",
)
expect(warn).not.toHaveBeenCalled()
})
it("rejects opaque API keys without an API request", async () => {
vi.spyOn(console, "error").mockImplementation(() => {})
vi.spyOn(console, "warn").mockImplementation(() => {})
const fetchSpy = vi.fn()
vi.stubGlobal("fetch", fetchSpy)
@ -148,7 +177,7 @@ describe("MCP authentication", () => {
scopes: [],
})
expect(fetchSpy).toHaveBeenCalledWith(
`${API_URL}/v3/session`,
`${API_URL}/session`,
expect.objectContaining({
headers: { Authorization: `Bearer ${key}` },
}),
@ -166,15 +195,23 @@ describe("MCP authentication", () => {
})
it("rejects an API key the session endpoint refuses", async () => {
vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response(null, { status: 401 })),
)
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response(null, { status: 401 }))
vi.stubGlobal("fetch", fetchSpy)
await expect(
validateApiKey("sm_revoked_key_0123456789abcdef", API_URL),
).resolves.toBeNull()
expect(fetchSpy).toHaveBeenCalledTimes(1)
expect(fetchSpy.mock.calls[0]?.[0]).toBe(`${API_URL}/session`)
expect(warn).toHaveBeenCalledTimes(1)
expect(warn).toHaveBeenCalledWith(
"API key rejected: Session request failed with status 401",
)
expect(error).not.toHaveBeenCalled()
})
it("rejects malformed API keys without an API request", async () => {
@ -187,7 +224,8 @@ describe("MCP authentication", () => {
})
it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => {
vi.spyOn(console, "error").mockImplementation(() => {})
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response(null, { status: 500 })),
@ -196,10 +234,16 @@ describe("MCP authentication", () => {
await expect(
validateApiKey("sm_outage_key_0123456789abcdef", API_URL),
).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(error.mock.calls[0]?.[0]).toBe(
"Auth backend unavailable during API key validation: Session endpoint returned 500",
)
expect(warn).not.toHaveBeenCalled()
})
it("surfaces a session-endpoint timeout as TransientAuthError", async () => {
vi.spyOn(console, "error").mockImplementation(() => {})
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal(
"fetch",
vi.fn().mockRejectedValue(
@ -212,5 +256,7 @@ describe("MCP authentication", () => {
await expect(
validateApiKey("sm_timeout_key_0123456789abcd", API_URL),
).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(warn).not.toHaveBeenCalled()
})
})

View file

@ -31,7 +31,7 @@ export async function fetchSession(
bearerToken: string,
apiUrl: string,
): Promise<SessionInfo> {
const response = await fetch(`${apiUrl.replace(/\/+$/, "")}/v3/session`, {
const response = await fetch(`${apiUrl.replace(/\/+$/, "")}/session`, {
method: "GET",
headers: { Authorization: `Bearer ${bearerToken}` },
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
@ -132,9 +132,17 @@ export async function validateApiKey(
})
return user
} catch (error) {
console.error("API key validation error:", error)
const transient = transientAuthErrorFor(error)
if (transient) throw transient
if (transient) {
console.error(
`Auth backend unavailable during API key validation: ${transient.message}`,
error,
)
throw transient
}
console.warn(
`API key rejected: ${error instanceof Error ? error.message : String(error)}`,
)
return null
}
}
@ -183,9 +191,17 @@ export async function validateOAuthToken(
expiresAt: payload.exp,
}
} catch (error) {
console.error("OAuth token validation error:", error)
const transient = transientAuthErrorFor(error)
if (transient) throw transient
if (transient) {
console.error(
`Auth backend unavailable during OAuth token validation: ${transient.message}`,
error,
)
throw transient
}
console.warn(
`OAuth token rejected: ${error instanceof Error ? error.message : String(error)}`,
)
return null
}
}

View file

@ -57,8 +57,8 @@ export type { MemoryEntry, MemoryEntryHistory }
export type MemoryEntriesResponse = MemoriesList
export type Memory =
| { id: string; memory: string; similarity: number }
| { id: string; chunk: string; similarity: number }
| { id: string; memory: string; similarity: number; documentId?: string }
| { id: string; chunk: string; similarity: number; documentId?: string }
export interface SearchResult {
results: Memory[]
@ -85,17 +85,20 @@ function limitByChars(text: string, maxChars = MAX_CHARS): string {
function mapSearchResults(results: SearchResponseResultsItem[]): Memory[] {
return results.map((result) => {
const documentId = result.included?.document?.id
if (result.chunk && !result.memory) {
return {
id: result.id,
similarity: result.similarity,
chunk: limitByChars(result.chunk),
...(documentId && { documentId }),
}
}
return {
id: result.id,
similarity: result.similarity,
memory: limitByChars(result.memory ?? ""),
...(documentId && { documentId }),
}
})
}
@ -207,39 +210,24 @@ export class SupermemoryClient {
}
}
// v5 has no exact-content forget: preview semantic matches, then forget the best one by ID.
async forgetMemory(
content: string,
): Promise<{ success: boolean; message: string; namespace: string }> {
// Retrying a committed mutation can turn its success into a misleading not-found result.
async forgetByIds(ids: string[]): Promise<{
forgotten: { id: string; memory: string }[]
errors: { id: string; error: string }[]
namespace: string
}> {
try {
const preview = await this.client.memories.forgetMatching(
const result = await this.client.memories.forget(
this.namespace,
{ query: content, dryRun: true },
{ ids },
{ maxRetries: 0 },
)
const match = preview.matches[0]
if (!match) {
return {
success: false,
message: "No matching memory found to forget.",
namespace: this.namespace,
}
}
const result = await this.client.memories.forget(this.namespace, {
ids: [match.id],
})
if (result.count === 0) {
const reason = result.errors[0]?.error ?? "unknown error"
return {
success: false,
message: `Could not forget memory ${match.id}: ${reason}`,
namespace: this.namespace,
}
}
return {
success: true,
message: `Forgot memory (ID: ${match.id}): "${limitByChars(match.memory, 100)}"`,
forgotten: result.matches.map((m) => ({
id: m.id,
memory: limitByChars(m.memory, 200),
})),
errors: result.errors,
namespace: this.namespace,
}
} catch (error) {
@ -252,8 +240,9 @@ export class SupermemoryClient {
const result = await this.client.search(this.namespace, {
query,
limit,
searchMode: "hybrid",
searchMode: "memories",
threshold: SEARCH_THRESHOLD,
include: { documents: true },
})
const results = mapSearchResults(result.results)
return {
@ -371,9 +360,25 @@ export class SupermemoryClient {
}
}
// Older search results handed out memory IDs as document IDs, so resolve those to their source document.
async getDocument(id: string): Promise<DocumentDetails> {
try {
return await this.client.documents.get(this.namespace, id)
} catch (error) {
if (objectProperty(error, "statusCode") !== 404) this.handleError(error)
}
const documentId = await this.client.memories
.get(this.namespace, id, { include: "documents" })
.then((memory) => memory.included?.document?.id)
.catch(() => undefined)
if (!documentId) {
throw new Error(
`No document with ID "${id}" in space "${this.namespace}". Use a document ID from list_documents, or the document ID shown on a search_memory or list_memories result.`,
)
}
try {
return await this.client.documents.get(this.namespace, documentId)
} catch (error) {
this.handleError(error)
}

View file

@ -1,5 +1,5 @@
import type { McpServer } from "@modelcontextprotocol/server"
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client"
import type { SupermemoryClient } from "../client"
import {
compactDescription,
formatFactSection,
@ -15,6 +15,7 @@ export function registerContextPrompt(
server: McpServer,
getClient: (namespace?: string) => SupermemoryClient,
resolveNamespace: () => Promise<string | undefined>,
getDefaultNamespace: () => Promise<string>,
) {
server.registerPrompt(
"context",
@ -24,7 +25,7 @@ export function registerContextPrompt(
async () => {
try {
const selectedNamespace = await resolveNamespace()
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE
const activeKey = selectedNamespace ?? (await getDefaultNamespace())
const [profileResult, spaces] = await Promise.all([
getClient(activeKey).getProfile(),
getClient().listNamespaces(),

View file

@ -1,11 +1,12 @@
import type { McpServer } from "@modelcontextprotocol/server"
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client"
import type { SupermemoryClient } from "../client"
import { formatSpaceRow, sortSpaces } from "../space-presentation"
export function registerNamespacesResource(
server: McpServer,
getClient: () => SupermemoryClient,
resolveNamespace: () => Promise<string | undefined>,
getDefaultNamespace: () => Promise<string>,
) {
server.registerResource("My Spaces", "supermemory://spaces", {}, async () => {
const client = getClient()
@ -13,7 +14,7 @@ export function registerNamespacesResource(
client.listNamespaces(),
resolveNamespace(),
])
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE
const activeKey = selectedNamespace ?? (await getDefaultNamespace())
const rows = sortSpaces(namespaces, activeKey).map((space) =>
formatSpaceRow(space, activeKey),
)

View file

@ -1,5 +1,5 @@
import type { McpServer } from "@modelcontextprotocol/server"
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client"
import type { SupermemoryClient } from "../client"
import {
compactDescription,
formatFactSection,
@ -12,6 +12,7 @@ export function registerProfileResource(
server: McpServer,
getClient: (namespace?: string) => SupermemoryClient,
resolveNamespace: () => Promise<string | undefined>,
getDefaultNamespace: () => Promise<string>,
) {
server.registerResource(
"Active Space Profile",
@ -19,7 +20,7 @@ export function registerProfileResource(
{},
async () => {
const selectedNamespace = await resolveNamespace()
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE
const activeKey = selectedNamespace ?? (await getDefaultNamespace())
const [profileResult, spaces] = await Promise.all([
getClient(activeKey).getProfile(),
getClient().listNamespaces(),

View file

@ -14,6 +14,7 @@ import { registerAllTools } from "./tools"
import { errorResult } from "./tools/types"
import type { ActorContext, ServerEnv } from "./types"
import {
accessibleDefaultNamespace,
resolveNamespace as resolveSpaceNamespace,
spaceStateName,
} from "./space"
@ -24,8 +25,39 @@ const UPLOAD_SESSION_TTL_MS = 2 * 60 * 1000
const SERVER_INSTRUCTIONS =
"Supermemory is the authenticated user's persistent memory and knowledge layer across conversations and spaces. Use these tools whenever the user wants to recall something they may have saved, inspect stored sources or extracted memories, remember or upload new information, check their Supermemory account or access, change their active space, or explore their memory graph, even if they do not mention Supermemory by name. Use the active or account-default space when none is named. Resolve a named space with list_spaces and pass its key to the relevant tool; change the active space only when the user explicitly asks."
const DEFAULT_NAMESPACE_CACHE_TTL_MS = 60_000
const DEFAULT_NAMESPACE_CACHE_MAX_ENTRIES = 1000
type ClientInfo = { name: string; version?: string }
const defaultNamespaceCache = new Map<
string,
{ namespace: string; expiresAt: number }
>()
// Stateless HTTP builds a server per request, so cache per isolate to keep the session lookup off most calls.
async function defaultNamespaceFor(
bearerToken: string,
apiUrl: string,
): Promise<string> {
const cached = defaultNamespaceCache.get(bearerToken)
if (cached && cached.expiresAt > Date.now()) return cached.namespace
const namespace = await fetchSession(bearerToken, apiUrl)
.then(accessibleDefaultNamespace)
.catch(() => undefined)
if (!namespace) return DEFAULT_NAMESPACE
if (defaultNamespaceCache.size >= DEFAULT_NAMESPACE_CACHE_MAX_ENTRIES) {
defaultNamespaceCache.clear()
}
defaultNamespaceCache.set(bearerToken, {
namespace,
expiresAt: Date.now() + DEFAULT_NAMESPACE_CACHE_TTL_MS,
})
return namespace
}
function clientInfoFromContext(context: ServerContext): ClientInfo | null {
const envelope = context.mcpReq.envelope
if (!envelope) return null
@ -66,8 +98,10 @@ export function createSupermemoryServer(
spaceState.setActiveNamespace(namespace)
const resolveSelectedNamespace = (explicit?: string) =>
resolveSpaceNamespace(explicit, getActiveNamespace)
const getDefaultNamespace = () =>
defaultNamespaceFor(actor.bearerToken, apiUrl)
const resolveNamespace = async (explicit?: string) =>
(await resolveSelectedNamespace(explicit)) ?? DEFAULT_NAMESPACE
(await resolveSelectedNamespace(explicit)) ?? (await getDefaultNamespace())
const createUploadSession = async (namespace: string) => {
const uploadId = crypto.randomUUID()
const uploadToken = [crypto.randomUUID(), crypto.randomUUID()]
@ -106,14 +140,25 @@ export function createSupermemoryServer(
} catch {}
}
registerProfileResource(server, getClient, resolveSelectedNamespace)
registerProfileResource(
server,
getClient,
resolveSelectedNamespace,
getDefaultNamespace,
)
registerNamespacesResource(
server,
() => getClient(),
resolveSelectedNamespace,
getDefaultNamespace,
)
registerWidgetResource(server, mcpOrigin)
registerContextPrompt(server, getClient, resolveSelectedNamespace)
registerContextPrompt(
server,
getClient,
resolveSelectedNamespace,
getDefaultNamespace,
)
return server
}

View file

@ -1,6 +1,10 @@
import { describe, expect, it, vi } from "vitest"
import { optionalNamespaceSchema } from "./namespace"
import { resolveNamespace, spaceStateName } from "./space"
import {
accessibleDefaultNamespace,
resolveNamespace,
spaceStateName,
} from "./space"
describe("space application state", () => {
it("keys active state by organization and user without collisions", () => {
@ -42,4 +46,63 @@ describe("space application state", () => {
expect(optionalNamespaceSchema.description).toContain("list_spaces")
expect(optionalNamespaceSchema.description).toContain("active space")
})
it("keeps the default space unless the caller is limited to other spaces", () => {
const user = { id: "user" }
expect(accessibleDefaultNamespace({ user })).toBe("sm_project_default")
expect(
accessibleDefaultNamespace({
user,
scope: { type: "scoped", tags: ["work", "sm_project_default"] },
}),
).toBe("sm_project_default")
expect(
accessibleDefaultNamespace({
user,
scope: { type: "scoped", tag: "work", tags: ["work", "notes"] },
}),
).toBe("work")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [{ namespace: "team", permission: "read" }],
}),
).toBe("team")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [
{ namespace: "team", permission: "read" },
{ namespace: "notes", permission: "write" },
],
scope: { type: "scoped", tags: ["notes"] },
}),
).toBe("notes")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [
{ namespace: "readonly", permission: "read" },
{ namespace: "team", permission: "write" },
],
}),
).toBe("team")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [{ namespace: "readonly", permission: "read" }],
}),
).toBe("readonly")
expect(
accessibleDefaultNamespace({
user,
accessType: "restricted",
namespaces: [],
}),
).toBe("sm_project_default")
})
})

View file

@ -1,3 +1,5 @@
import type { SessionInfo } from "../shared/types"
import { DEFAULT_NAMESPACE } from "./client"
import type { ActorContext } from "./types"
export function spaceStateName(
@ -12,3 +14,31 @@ export async function resolveNamespace(
): Promise<string | undefined> {
return explicit ?? (await getActiveNamespace())
}
// Mirrors the API's hard allowlist (scoped keys, restricted members), preferring a space writes can reach.
export function accessibleDefaultNamespace(session: SessionInfo): string {
const keyTags =
session.scope?.type === "scoped"
? (session.scope.tags ?? (session.scope.tag ? [session.scope.tag] : null))
: null
const memberTags =
session.accessType === "restricted" && session.namespaces
? session.namespaces.map((access) => access.namespace)
: null
const allowed =
keyTags && memberTags
? keyTags.filter((tag) => memberTags.includes(tag))
: (keyTags ?? memberTags)
if (!allowed?.[0] || allowed.includes(DEFAULT_NAMESPACE)) {
return DEFAULT_NAMESPACE
}
const writable =
session.scope?.permission === "read"
? undefined
: allowed.find(
(tag) =>
session.namespaces?.find((access) => access.namespace === tag)
?.permission !== "read",
)
return writable ?? allowed[0]
}

View file

@ -1,4 +1,5 @@
import { z } from "zod"
import { getMemoryText } from "../client"
import { optionalNamespaceSchema } from "../namespace"
import { MEMORY_TOOL_ANNOTATIONS } from "./annotations"
import {
@ -9,22 +10,110 @@ import {
import { forgetMemoryOutputSchema } from "./output-schemas"
import { textContent, type ToolDeps } from "./types"
export async function forgetMemory(
deps: ToolDeps,
args: { content: string; namespace?: string; containerTag?: string },
const MAX_IDS = 100
const CANDIDATE_LIMIT = 5
type ForgetArgs = {
ids?: string[]
content?: string
namespace?: string
containerTag?: string
}
function forgetResult(
namespace: string,
success: boolean,
message: string,
extra: Record<string, unknown> = {},
) {
return {
content: [textContent(message)],
structuredContent: withLegacyNamespace({
action: "forget" as const,
success,
namespace,
message,
...extra,
}),
}
}
async function forgetIds(deps: ToolDeps, namespace: string, ids: string[]) {
const result = await deps.getClient(namespace).forgetByIds(ids)
const lines = result.forgotten.map((m) => `- ${m.id}: "${m.memory}"`)
for (const e of result.errors)
lines.push(`- ${e.id}: not forgotten (${e.error})`)
const count = result.forgotten.length
const head =
count > 0
? `Forgot ${count} memor${count === 1 ? "y" : "ies"}.`
: "No memories were forgotten."
return forgetResult(
result.namespace,
count > 0,
[head, ...lines].join("\n"),
{
id: result.forgotten[0]?.id,
forgotten: result.forgotten,
errors: result.errors,
},
)
}
export async function forgetMemory(deps: ToolDeps, args: ForgetArgs) {
try {
const namespace = await deps.resolveNamespace(namespaceArg(args))
const result = await deps.getClient(namespace).forgetMemory(args.content)
return {
content: [textContent(result.message)],
structuredContent: withLegacyNamespace({
action: "forget" as const,
success: result.success,
namespace: result.namespace,
message: result.message,
}),
if (args.ids !== undefined) {
const ids = [...new Set(args.ids.map((id) => id.trim()))]
if (ids.length === 0 || ids.length > MAX_IDS || ids.some((id) => !id)) {
throw new Error(`Pass between 1 and ${MAX_IDS} nonempty memory IDs.`)
}
return await forgetIds(deps, namespace, ids)
}
const content = args.content
if (!content?.trim()) {
return forgetResult(
namespace,
false,
"Pass ids (from search_memory) to forget memories.",
)
}
const search = await deps
.getClient(namespace)
.search(content, CANDIDATE_LIMIT)
const candidates = search.results
.filter((r) => "memory" in r)
.map((r) => ({
id: r.id,
text: getMemoryText(r),
similarity: r.similarity,
}))
const exact = candidates.find((c) => c.text === content)
if (exact) return await forgetIds(deps, namespace, [exact.id])
if (candidates.length === 0) {
return forgetResult(
namespace,
false,
"Nothing was forgotten. No matching memory found.",
{ candidates },
)
}
const lines = candidates.map(
(c) => `- ${c.id} [${(c.similarity * 100).toFixed(0)}%] ${c.text}`,
)
return forgetResult(
namespace,
false,
[
"Nothing was forgotten yet. Closest memories:",
...lines,
"Call forget_memory again with the ids to forget.",
].join("\n"),
{ candidates },
)
} catch (error) {
return deps.errorResult(error)
}
@ -35,12 +124,21 @@ export function register(deps: ToolDeps) {
"forget_memory",
{
description:
"Forget a memory matching the supplied content in the user's ACTIVE space. Use when information is outdated or the user asks to remove it. The target space is the one selected via select-space; pass namespace only to override it.",
"Forget memories by ID in the user's ACTIVE space. Find the IDs with search_memory first, then pass them in ids. If you pass only content, an exact text match is forgotten; otherwise nothing is forgotten and the closest memories are returned with their IDs so you can choose. Use when information is outdated or the user asks to remove it. Pass namespace only to override the active space.",
inputSchema: z.object({
ids: z
.array(z.string().trim().min(1))
.min(1)
.max(MAX_IDS)
.optional()
.describe("IDs of the memories to forget, from search_memory"),
content: z
.string()
.max(200000, "Content exceeds maximum length")
.describe("The memory content to forget"),
.optional()
.describe(
"Text of the memory to forget. Used only when ids is not given.",
),
namespace: optionalNamespaceSchema,
...legacyNamespaceInput,
}),

View file

@ -19,7 +19,9 @@ export function register(deps: ToolDeps) {
.string()
.min(1, "Document ID is required")
.max(255, "Document ID exceeds maximum length")
.describe("Document ID returned by list_documents or a memory result"),
.describe(
"Document ID from list_documents, or the document ID shown on a search_memory or list_memories result. Memory IDs are not document IDs.",
),
namespace: optionalNamespaceSchema,
...legacyNamespaceInput,
})
@ -29,7 +31,7 @@ export function register(deps: ToolDeps) {
{
title: "Get Document",
description:
"Read one stored document by ID from one space, including its summary and available content. Use list_documents to discover document IDs. When the document is in a named space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
"Read one stored document by ID from one space, including its summary and available content. Get document IDs from list_documents or from the document shown on a search_memory result. When the document is in a named space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
inputSchema,
outputSchema: getDocumentOutputSchema,
annotations: READ_ONLY_TOOL_ANNOTATIONS,

View file

@ -25,6 +25,15 @@ export const addMemoryOutputSchema = z.object({
message: z.string(),
id: z.string().optional(),
status: z.string().optional(),
forgotten: z
.array(z.object({ id: z.string(), memory: z.string() }))
.optional(),
errors: z.array(z.object({ id: z.string(), error: z.string() })).optional(),
candidates: z
.array(
z.object({ id: z.string(), text: z.string(), similarity: z.number() }),
)
.optional(),
})
export type AddMemoryOutput = z.infer<typeof addMemoryOutputSchema>
@ -77,6 +86,7 @@ export const searchMemoryOutputSchema = z.object({
id: z.string(),
text: z.string(),
similarity: z.number(),
documentId: z.string().optional(),
title: z.string().optional(),
}),
),

View file

@ -27,7 +27,7 @@ export function register(deps: ToolDeps) {
"search_memory",
{
description:
"Search memories in one space with a natural-language query. Returns matching memories only — not the space profile. If you need who-the-user-is, preferences, or recent context after searching, call get_profile. When the user names a space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
"Search memories in one space with a natural-language query. Returns matching memories only — not the space profile. Each result has a memory ID (for forget_memory) and, when it has one, the ID of its source document (for get_document). If you need who-the-user-is, preferences, or recent context after searching, call get_profile. When the user names a space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
inputSchema,
outputSchema: searchMemoryOutputSchema,
annotations: READ_ONLY_TOOL_ANNOTATIONS,
@ -42,6 +42,7 @@ export function register(deps: ToolDeps) {
id: result.id,
text: getMemoryText(result),
similarity: result.similarity,
...(result.documentId && { documentId: result.documentId }),
}))
const parts: string[] = []
@ -50,7 +51,10 @@ export function register(deps: ToolDeps) {
for (const result of searchResult.results) {
const text = getMemoryText(result)
const similarity = (result.similarity * 100).toFixed(0)
parts.push(`- [${similarity}%] ${text}`)
const source = result.documentId
? ` (document: ${result.documentId})`
: ""
parts.push(`- ${result.id} [${similarity}%] ${text}${source}`)
}
} else {
parts.push("No matching memories found.")

View file

@ -11,7 +11,7 @@ export const namespaceAccessSchema = z.object({
export type NamespaceAccess = z.infer<typeof namespaceAccessSchema>
// /v3/session has no v5 replacement and still sends container-tag field names.
// /session still sends container-tag field names.
const sessionNamespaceAccessSchema = z
.object({ containerTag: z.string(), permission: z.enum(["read", "write"]) })
.transform(({ containerTag, permission }) => ({

View file

@ -1,14 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# Raycast specific files
raycast-env.d.ts
.raycast-swift-build
.swiftpm
compiled_raycast_swift
compiled_raycast_rust
# misc
.DS_Store

View file

@ -1,19 +0,0 @@
# Supermemory Changelog
## [Quick Add from Selection] - 2025-11-05
- Select text anywhere and quickly add it as a memory - the content field is automatically filled
- Select text and search memories instantly - the search field is automatically filled
- Save time by selecting text before opening commands
## [Search Projects + Enhancements] - 2025-10-13
- Added command to search and add projects
- Removed `useEffect`
- Simplified `getApiKey` since `Preferences` will be enforced for presence and trim automatically
- Moved API Key check into its own HoC
## [Initial Version] - 2025-10-02
- Added Supermemory integration with Add Memory and Search Memories commands
- Added project organization support for memories

View file

@ -1,35 +0,0 @@
# Raycast Extension for Supermemory
A Raycast extension that lets you add memories and search through your Supermemory collection directly from Raycast.
## Setup
1. Install the extension in Raycast
2. Get your API key from [app.supermemory.ai](https://app.supermemory.ai)
3. Open the extension preferences and enter your API key
## Features
### Add Memory
- Add new memories to your Supermemory collection
- Organize memories by project
- Add optional titles and URLs
- Keyboard shortcut: Cmd+Enter to submit
### Search Memories
- Search through your entire Supermemory collection
- Real-time search with debouncing
- View detailed memory information
- Copy content or open related URLs
- Shows relevance scores and creation dates
## Commands
- `Add Memory` - Add a new memory to your collection
- `Search Memories` - Search through your existing memories
## Authentication
This extension requires a Supermemory API key. You can get your API key from [supermemory.link/raycast](https://supermemory.link/raycast).
The API key is stored securely in Raycast preferences and is required for all operations.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -1,4 +0,0 @@
const { defineConfig } = require("eslint/config")
const raycastConfig = require("@raycast/eslint-config")
module.exports = defineConfig([...raycastConfig])

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

File diff suppressed because it is too large Load diff

View file

@ -1,74 +0,0 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "supermemory",
"title": "Supermemory",
"description": "Add and search memories with your personal AI-powered knowledge base",
"icon": "extension-icon.png",
"author": "supermemory",
"contributors": [
"xmok",
"maheshthedev"
],
"platforms": [
"macOS",
"Windows"
],
"categories": [
"Productivity",
"Web"
],
"license": "MIT",
"commands": [
{
"name": "add-memory",
"title": "Add Memory",
"subtitle": "Supermemory",
"description": "Add text, URLs, or documents to your supermemory knowledge base",
"mode": "view"
},
{
"name": "search-memories",
"title": "Search Memories",
"subtitle": "Supermemory",
"description": "Search through your saved memories and find relevant information",
"mode": "view"
},
{
"name": "search-projects",
"title": "Search Projects",
"subtitle": "Supermemory",
"description": "Search through your saved projects and find relevant information",
"mode": "view"
}
],
"preferences": [
{
"name": "apiKey",
"type": "password",
"required": true,
"title": "API Key",
"description": "Your Supermemory API Key. Get it from https://supermemory.link/raycast",
"placeholder": "Enter your Supermemory API Key"
}
],
"dependencies": {
"@raycast/api": "^1.103.3",
"@raycast/utils": "^2.2.1"
},
"devDependencies": {
"@raycast/eslint-config": "^2.0.4",
"@types/node": "22.13.10",
"@types/react": "19.0.10",
"eslint": "^9.22.0",
"prettier": "^3.5.3",
"typescript": "^5.8.2"
},
"scripts": {
"build": "ray build",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint",
"prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1",
"publish": "npx @raycast/api@latest publish"
}
}

View file

@ -1,114 +0,0 @@
import {
Form,
ActionPanel,
Action,
showToast,
Toast,
useNavigation,
Icon,
getSelectedText,
} from "@raycast/api"
import { useState, useEffect } from "react"
import { addMemory, fetchProjects } from "./api"
import { usePromise } from "@raycast/utils"
import { withSupermemory } from "./withSupermemory"
interface FormValues {
content: string
project: string
}
export default withSupermemory(Command)
function Command() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [initialContent, setInitialContent] = useState("")
const { pop } = useNavigation()
const { isLoading, data: projects = [] } = usePromise(fetchProjects)
useEffect(() => {
async function loadSelectedText() {
try {
const selectedText = await getSelectedText()
if (selectedText) {
setInitialContent(selectedText)
}
} catch {
// No text selected or error getting selected text - silently fail
// User can still manually enter content
}
}
loadSelectedText()
}, [])
async function handleSubmit(values: FormValues) {
if (!values.content.trim()) {
await showToast({
style: Toast.Style.Failure,
title: "Content Required",
message: "Please enter some content for the memory",
})
return
}
try {
setIsSubmitting(true)
const containerTags = values.project ? [values.project] : undefined
await addMemory({
content: values.content.trim(),
containerTags,
})
pop()
} catch (error) {
console.error("Failed to add memory:", error)
} finally {
setIsSubmitting(false)
}
}
return (
<Form
isLoading={isLoading || isSubmitting}
actions={
!isLoading && (
<ActionPanel>
<Action.SubmitForm
icon={Icon.Plus}
title="Add Memory"
onSubmit={handleSubmit}
/>
</ActionPanel>
)
}
>
<Form.TextArea
id="content"
title="Content"
value={initialContent}
placeholder="Enter the memory content..."
info="The main content of your memory. This is required."
onChange={(value) => setInitialContent(value)}
/>
<Form.Separator />
<Form.Dropdown
id="project"
title="Project"
info="Select a project to organize this memory"
storeValue
>
<Form.Dropdown.Item value="" title="No Project" />
{projects.map((project) => (
<Form.Dropdown.Item
key={project.id}
value={project.containerTag}
title={project.name}
/>
))}
</Form.Dropdown>
</Form>
)
}

View file

@ -1,222 +0,0 @@
import { getPreferenceValues, showToast, Toast } from "@raycast/api"
export interface Project {
id: string
name: string
containerTag: string
description?: string
}
export interface Memory {
id: string
content: string
title?: string
url?: string
containerTag?: string
createdAt: string
}
export interface SearchResult {
documentId: string
chunks: unknown[]
title?: string
metadata: Record<string, unknown>
score?: number
createdAt: string
updatedAt: string
type: string
}
export interface AddMemoryRequest {
content: string
containerTags?: string[]
title?: string
url?: string
metadata?: Record<string, unknown>
}
interface AddProjectRequest {
name: string
}
export interface SearchRequest {
q: string
containerTags?: string[]
limit?: number
}
export interface SearchResponse {
results: SearchResult[]
timing: number
total: number
}
const API_BASE_URL = "https://api.supermemory.ai"
class SupermemoryAPIError extends Error {
constructor(
message: string,
public status?: number,
) {
super(message)
this.name = "SupermemoryAPIError"
}
}
class AuthenticationError extends Error {
constructor(message: string) {
super(message)
this.name = "AuthenticationError"
}
}
function getApiKey(): string {
const { apiKey } = getPreferenceValues<Preferences>()
return apiKey
}
async function makeAuthenticatedRequest<T>(
endpoint: string,
options: RequestInit = {},
): Promise<T> {
const apiKey = getApiKey()
const url = `${API_BASE_URL}${endpoint}`
try {
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
...options.headers,
},
})
if (!response.ok) {
if (response.status === 401) {
throw new AuthenticationError(
"Invalid API key. Please check your API key in preferences. Get a new one from https://supermemory.link/raycast",
)
}
let errorMessage = `API request failed: ${response.statusText}`
try {
const errorBody = (await response.json()) as { message?: string }
if (errorBody.message) {
errorMessage = errorBody.message
}
} catch {
// Ignore JSON parsing errors, use default message
}
throw new SupermemoryAPIError(errorMessage, response.status)
}
if (!response.headers.get("content-type")?.includes("application/json")) {
throw new SupermemoryAPIError("Invalid response format from API")
}
const data = (await response.json()) as T
return data
} catch (err) {
if (
err instanceof AuthenticationError ||
err instanceof SupermemoryAPIError
) {
throw err
}
// Handle network errors or other fetch errors
throw new SupermemoryAPIError(
`Network error: ${err instanceof Error ? err.message : "Unknown error"}`,
)
}
}
export async function fetchProjects(): Promise<Project[]> {
try {
const response = await makeAuthenticatedRequest<{ projects: Project[] }>(
"/v3/projects",
)
return response.projects || []
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to fetch projects",
message:
error instanceof Error ? error.message : "Unknown error occurred",
})
throw error
}
}
export async function addProject(request: AddProjectRequest): Promise<Project> {
const response = await makeAuthenticatedRequest<Project>("/v3/projects", {
method: "POST",
body: JSON.stringify(request),
})
await showToast({
style: Toast.Style.Success,
title: "Project Added",
message: "Successfully added project to Supermemory",
})
return response
}
export async function addMemory(request: AddMemoryRequest): Promise<Memory> {
try {
const response = await makeAuthenticatedRequest<Memory>("/v3/documents", {
method: "POST",
body: JSON.stringify(request),
})
await showToast({
style: Toast.Style.Success,
title: "Memory Added",
message: "Successfully added memory to Supermemory",
})
return response
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to add memory",
message:
error instanceof Error ? error.message : "Unknown error occurred",
})
throw error
}
}
export async function searchMemories(
request: SearchRequest,
): Promise<SearchResult[]> {
try {
const response = await makeAuthenticatedRequest<SearchResponse>(
"/v3/search",
{
method: "POST",
body: JSON.stringify(request),
},
)
return response.results || []
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: "Failed to search memories",
message:
error instanceof Error ? error.message : "Unknown error occurred",
})
throw error
}
}
// Helper function to check if API key is configured and valid
export async function fetchSettings(): Promise<object> {
const response = await makeAuthenticatedRequest<object>("/v3/settings")
return response
}

View file

@ -1,226 +0,0 @@
import {
ActionPanel,
Detail,
List,
Action,
Icon,
showToast,
Toast,
getSelectedText,
} from "@raycast/api"
import { useState, useEffect } from "react"
import { searchMemories, type SearchResult } from "./api"
import { usePromise } from "@raycast/utils"
import { withSupermemory } from "./withSupermemory"
const extractContent = (memory: SearchResult) => {
if (memory.chunks && memory.chunks.length > 0) {
return memory.chunks
.map((chunk: unknown) => {
if (typeof chunk === "string") return chunk
if (
chunk &&
typeof chunk === "object" &&
"content" in chunk &&
typeof chunk.content === "string"
)
return chunk.content
if (
chunk &&
typeof chunk === "object" &&
"text" in chunk &&
typeof chunk.text === "string"
)
return chunk.text
return ""
})
.filter(Boolean)
.join(" ")
}
return "No content available"
}
// metadata.url comes from ingested content, so only http(s) reaches the OS opener.
const extractUrl = (memory: SearchResult) => {
if (memory.metadata?.url && typeof memory.metadata.url === "string") {
const url = memory.metadata.url
try {
const parsed = new URL(url)
if (parsed.protocol === "https:" || parsed.protocol === "http:") {
return url
}
} catch {
return null
}
}
return null
}
const truncateContent = (content: string, maxLength = 100) => {
if (content.length <= maxLength) return content
return `${content.substring(0, maxLength)}...`
}
export default withSupermemory(Command)
function Command() {
const [searchText, setSearchText] = useState("")
useEffect(() => {
async function loadSelectedText() {
try {
const selectedText = await getSelectedText()
if (selectedText) {
setSearchText(selectedText)
}
} catch {
// No text selected or error getting selected text - silently fail
}
}
loadSelectedText()
}, [])
const { isLoading, data: searchResults = [] } = usePromise(
async (query: string) => {
const q = query.trim()
if (!q) return []
const results = await searchMemories({
q,
limit: 50,
})
if (!results.length) {
await showToast({
style: Toast.Style.Success,
title: "Search Complete",
message: "No memories found for your query",
})
}
return results
},
[searchText],
)
const formatDate = (dateString: string) => {
try {
return new Date(dateString).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})
} catch {
return "Unknown date"
}
}
const hasSearched = !isLoading && !searchResults.length
return (
<List
isLoading={isLoading}
onSearchTextChange={setSearchText}
searchText={searchText}
searchBarPlaceholder="Search your memories..."
throttle
>
{hasSearched && !searchText.trim() ? (
<List.EmptyView
icon={Icon.MagnifyingGlass}
title="Search Your Memories"
description="Type to search through your Supermemory collection"
/>
) : hasSearched ? (
<List.EmptyView
icon={Icon.Document}
title="No Memories Found"
description={`No memories found for "${searchText}"`}
/>
) : isLoading && searchText.trim() ? (
<List.EmptyView
icon={Icon.MagnifyingGlass}
title="Searching Your Memories"
/>
) : (
searchResults.map((memory) => {
const content = extractContent(memory)
const url = extractUrl(memory)
return (
<List.Item
key={memory.documentId}
icon={url ? Icon.Link : Icon.Document}
title={memory.title || "Untitled Memory"}
subtitle={{ value: truncateContent(content), tooltip: content }}
accessories={[
{ text: formatDate(memory.createdAt) },
...(memory.score
? [{ text: `${Math.round(memory.score * 100)}%` }]
: []),
]}
actions={
<ActionPanel>
<Action.Push
title="View Details"
target={<MemoryDetail memory={memory} />}
icon={Icon.Eye}
/>
<Action.CopyToClipboard
title="Copy Content"
shortcut={{ modifiers: ["cmd"], key: "c" }}
content={content}
/>
{url && (
<Action.OpenInBrowser
title="Open URL"
url={url}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
)}
</ActionPanel>
}
/>
)
})
)}
</List>
)
}
function MemoryDetail({ memory }: { memory: SearchResult }) {
const content = extractContent(memory)
const url = extractUrl(memory)
const markdown = `
# ${memory.title || "Untitled Memory"}
${content}
---
**Created:** ${new Date(memory.createdAt).toLocaleString()}
${url ? `**URL:** ${url}` : ""}
${memory.score ? `**Relevance:** ${Math.round(memory.score * 100)}%` : ""}
`
return (
<Detail
markdown={markdown}
actions={
<ActionPanel>
<Action.CopyToClipboard
title="Copy Content"
shortcut={{ modifiers: ["cmd"], key: "c" }}
content={content}
/>
{url && (
<Action.OpenInBrowser
title="Open URL"
url={url}
shortcut={{ modifiers: ["cmd"], key: "o" }}
/>
)}
</ActionPanel>
}
/>
)
}

View file

@ -1,106 +0,0 @@
import {
ActionPanel,
List,
Action,
Icon,
Form,
useNavigation,
} from "@raycast/api"
import { useState } from "react"
import { fetchProjects, addProject } from "./api"
import {
FormValidation,
showFailureToast,
useCachedPromise,
useForm,
} from "@raycast/utils"
import { withSupermemory } from "./withSupermemory"
export default withSupermemory(Command)
function Command() {
const { isLoading, data: projects, mutate } = useCachedPromise(fetchProjects)
return (
<List isLoading={isLoading} searchBarPlaceholder="Search your projects">
{!isLoading && !projects?.length ? (
<List.EmptyView
title="No Projects Found"
actions={
<ActionPanel>
<Action.Push
icon={Icon.Plus}
title="Create Project"
target={<CreateProject />}
onPop={mutate}
/>
</ActionPanel>
}
/>
) : (
projects?.map((project) => (
<List.Item
key={project.id}
icon={Icon.Folder}
title={project.name}
subtitle={project.description}
accessories={[{ tag: project.containerTag }]}
actions={
<ActionPanel>
<Action.Push
icon={Icon.Plus}
title="Create Project"
target={<CreateProject />}
onPop={mutate}
/>
</ActionPanel>
}
/>
))
)}
</List>
)
}
function CreateProject() {
const { pop } = useNavigation()
const [isLoading, setIsLoading] = useState(false)
const { handleSubmit, itemProps } = useForm<{ name: string }>({
async onSubmit(values) {
setIsLoading(true)
try {
await addProject(values)
pop()
} catch (error) {
await showFailureToast(error, { title: "Failed to add project" })
} finally {
setIsLoading(false)
}
},
validation: {
name: FormValidation.Required,
},
})
return (
<Form
navigationTitle="Search Projects / Add"
isLoading={isLoading}
actions={
<ActionPanel>
<Action.SubmitForm
icon={Icon.Plus}
title="Create Project"
onSubmit={handleSubmit}
/>
</ActionPanel>
}
>
<Form.TextField
title="Name"
placeholder="My Awesome Project"
info="This will help you organize your memories"
{...itemProps.name}
/>
</Form>
)
}

View file

@ -1,48 +0,0 @@
import { usePromise } from "@raycast/utils"
import { fetchSettings } from "./api"
import {
Action,
ActionPanel,
Detail,
Icon,
List,
openExtensionPreferences,
} from "@raycast/api"
import type { ComponentType } from "react"
export function withSupermemory<P extends object>(Component: ComponentType<P>) {
return function SupermemoryWrappedComponent(props: P) {
const { isLoading, data } = usePromise(fetchSettings, [], {
failureToastOptions: {
title: "Invalid API Key",
message:
"Invalid API key. Please check your API key in preferences. Get a new one from https://supermemory.link/raycast",
},
})
if (!data) {
return isLoading ? (
<Detail isLoading />
) : (
<List>
<List.EmptyView
icon={Icon.ExclamationMark}
title="API Key Required"
description="Please configure your Supermemory API key to search memories"
actions={
<ActionPanel>
<Action
title="Open Extension Preferences"
onAction={openExtensionPreferences}
icon={Icon.Gear}
/>
</ActionPanel>
}
/>
</List>
)
}
return <Component {...props} />
}
}

View file

@ -1,16 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["src/**/*", "raycast-env.d.ts"],
"compilerOptions": {
"lib": ["ES2023"],
"module": "commonjs",
"target": "ES2023",
"strict": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react-jsx",
"resolveJsonModule": true
}
}

View file

@ -86,7 +86,7 @@
"react-dom": "^19.2.4",
"supermemory": "^5.0.0",
"tailwind-merge": "^3.4.0",
"zod": "^4.4.3",
"zod": "^4.6.6",
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250620.0",
@ -215,7 +215,7 @@
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@vitejs/plugin-react": "^5.1.0",
"happy-dom": "^20.9.0",
"happy-dom": "^21.0.4",
"typescript": "^5.9.3",
"vite": "7.3.5",
"vitest": "3.2.6",
@ -227,12 +227,12 @@
},
"packages/tools": {
"name": "@supermemory/tools",
"version": "3.1.0",
"version": "3.1.2",
"dependencies": {
"lru-cache": "^11.2.6",
"openai": "^4.104.0",
"supermemory": "^5.0.0",
"zod": "^4.1.5",
"zod": "^4.6.6",
},
"devDependencies": {
"@ai-sdk/anthropic": "^2.0.25",
@ -255,6 +255,7 @@
"ai": "^5.0.0 || ^6.0.0 || ^7.0.0",
},
"optionalPeers": [
"@ai-sdk/provider",
"@voltagent/core",
"ai",
],
@ -263,7 +264,6 @@
"name": "@repo/ui",
"version": "0.0.0",
"dependencies": {
"@pixi/react": "^8.0.3",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-avatar": "^1.1.10",
@ -289,7 +289,6 @@
"lucide-react": "^0.525.0",
"motion": "^12.26.2",
"next-themes": "^0.4.6",
"pixi.js": "^8.12.0",
"recharts": "2.15.4",
"sonner": "^2.0.6",
"tailwindcss": "^4.1.11",
@ -1055,10 +1054,6 @@
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
"@pixi/colord": ["@pixi/colord@2.9.6", "", {}, "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA=="],
"@pixi/react": ["@pixi/react@8.0.5", "", { "dependencies": { "its-fine": "^2.0.0", "react-reconciler": "0.31.0" }, "peerDependencies": { "pixi.js": "^8.2.6", "react": ">=19.0.0" } }, "sha512-Z1VRdnv9Gh+lTLzNKjpS7GaTNDjUxRVJNtIdtK2i0sCpigvjx5mvJ72EPLhBFgepB6j3ZCkL0YBb6BqgTGbhGA=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
@ -1619,8 +1614,6 @@
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
"@types/earcut": ["@types/earcut@3.0.0", "", {}, "sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ=="],
"@types/es-aggregate-error": ["@types/es-aggregate-error@1.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
@ -1671,8 +1664,6 @@
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="],
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
@ -1723,12 +1714,8 @@
"@voltagent/internal": ["@voltagent/internal@1.0.3", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-tw2L2PptjfsPThO6S4SjouwhAiXDGcjN6kKh9rc7IqWNNGZN+4mwj8gsOroMYOMmhIbfQEr2xT7TqZGNkteDlw=="],
"@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
"@workflow/serde": ["@workflow/serde@4.1.0", "", {}, "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ=="],
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.15", "", {}, "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
@ -1883,6 +1870,8 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="],
"bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="],
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
@ -2151,8 +2140,6 @@
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
"earcut": ["earcut@3.0.2", "", {}, "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
@ -2253,7 +2240,7 @@
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
@ -2389,8 +2376,6 @@
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
"gifuct-js": ["gifuct-js@2.1.2", "", { "dependencies": { "js-binary-schema-parser": "^2.0.3" } }, "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg=="],
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
@ -2419,7 +2404,7 @@
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
"happy-dom": ["happy-dom@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ=="],
"happy-dom": ["happy-dom@21.0.4", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-tFm+GaLBgo6o0awHnvHaNoraVo27qJ+2tZEtZ+cIa08hXbeitrxAJajLI05hsUy/2/YTKcUjQYzHe5mCcraLBw=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
@ -2629,12 +2614,8 @@
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"ismobilejs": ["ismobilejs@1.1.1", "", {}, "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw=="],
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
"its-fine": ["its-fine@2.0.0", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng=="],
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@ -2645,8 +2626,6 @@
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
"js-binary-schema-parser": ["js-binary-schema-parser@2.0.3", "", {}, "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg=="],
"js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
@ -3093,8 +3072,6 @@
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
"parse-svg-path": ["parse-svg-path@0.1.2", "", {}, "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="],
@ -3155,8 +3132,6 @@
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
"pixi.js": ["pixi.js@8.16.0", "", { "dependencies": { "@pixi/colord": "^2.9.6", "@types/earcut": "^3.0.0", "@webgpu/types": "^0.1.69", "@xmldom/xmldom": "^0.8.11", "earcut": "^3.0.2", "eventemitter3": "^5.0.1", "gifuct-js": "^2.1.2", "ismobilejs": "^1.1.1", "parse-svg-path": "^0.1.2", "tiny-lru": "^11.4.7" } }, "sha512-gu2xw3sZGAn3cWBtk0HqTQT+v19YAfiaYXwUGgWoJl5NKz4cEZJUgWrwkmdfDszGyYBAGqOvJNbd2M9+vzLLMg=="],
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
"pony-cause": ["pony-cause@1.1.1", "", {}, "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g=="],
@ -3257,7 +3232,7 @@
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
"react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="],
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
@ -3581,8 +3556,6 @@
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
"tiny-lru": ["tiny-lru@11.4.7", "", {}, "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
@ -4007,11 +3980,11 @@
"@base-ui/utils/@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="],
"@better-auth/api-key/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"@better-auth/api-key/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@better-auth/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
"@better-auth/core/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"@better-auth/core/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@cloudflare/ai-chat/agents": ["agents@0.4.1", "", { "dependencies": { "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/sdk": "1.26.0", "cron-schedule": "^6.0.0", "escape-html": "^1.0.3", "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "mimetext": "^3.0.28", "nanoid": "^5.1.6", "partyserver": "^0.1.4", "partysocket": "1.1.13", "yargs": "^18.0.0" }, "peerDependencies": { "@ai-sdk/openai": "^3.0.0", "@ai-sdk/react": "^3.0.0", "@cloudflare/ai-chat": "^0.0.8", "@cloudflare/codemode": "^0.0.7", "@x402/core": "^2.0.0", "@x402/evm": "^2.0.0", "ai": "^6.0.0", "react": "^19.0.0", "viem": ">=2.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@ai-sdk/openai", "@ai-sdk/react", "@x402/core", "@x402/evm", "viem"], "bin": { "agents": "dist/cli/index.js" } }, "sha512-1ARZ5AXpPEn1+Wv/Dt++nH3iLStDxlci7Bm47sfE6qLlJpY9oG5nzyH2TRKwFFt9bMAgYz177i6p2NiEd/coVQ=="],
@ -4145,19 +4118,19 @@
"@modelcontextprotocol/client/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@modelcontextprotocol/client/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@modelcontextprotocol/client/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@modelcontextprotocol/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@modelcontextprotocol/core/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@modelcontextprotocol/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@modelcontextprotocol/server/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@modelcontextprotocol/server/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@mymediset/sap-ai-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"@mymediset/sap-ai-provider/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@mymediset/sap-ai-provider/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
@ -4251,7 +4224,7 @@
"@rolldown/plugin-babel/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"@sap-ai-sdk/prompt-registry/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@sap-ai-sdk/prompt-registry/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@sap-cloud-sdk/util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@ -4259,7 +4232,7 @@
"@scalar/types/type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="],
"@scalar/types/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@scalar/types/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
@ -4297,7 +4270,7 @@
"@supermemory/tools/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@supermemory/tools/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"@supermemory/tools/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
@ -4373,7 +4346,7 @@
"axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
"better-auth/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"better-auth/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="],
@ -4411,7 +4384,7 @@
"docs-test/supermemory": ["supermemory@4.25.4", "", { "bin": { "supermemory": "bin/cli" } }, "sha512-97ME3rlmu7OmsXJTb9OgXOD+3VUv4Wej0ZX9xezG+LKkMwrzi4xeeAZaOJFcr0oI/QQjcHG2WOzm+und1e7MFA=="],
"docs-test/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"docs-test/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@ -4471,8 +4444,6 @@
"ink/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
"ink/react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
"ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"inquirer/@inquirer/prompts": ["@inquirer/prompts@7.10.1", "", { "dependencies": { "@inquirer/checkbox": "^4.3.2", "@inquirer/confirm": "^5.1.21", "@inquirer/editor": "^4.2.23", "@inquirer/expand": "^4.0.23", "@inquirer/input": "^4.3.1", "@inquirer/number": "^3.0.23", "@inquirer/password": "^4.0.23", "@inquirer/rawlist": "^4.1.11", "@inquirer/search": "^3.2.2", "@inquirer/select": "^4.4.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg=="],
@ -4543,12 +4514,10 @@
"react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="],
"react-reconciler/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
"react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"recharts/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"rehype-recma/hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="],
"remark-gfm/mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
@ -4615,7 +4584,7 @@
"supermemory-mcp/wrangler": ["wrangler@4.147.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.2", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "5.20261001.0-alpha", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20261001.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20261001.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-pQYRoiq8PTAxphaG69z8+GC1DkSGd19EDZehQ8zxjo/Ko3mRB6Qs1mTrd8ZuKAarLklIjTqr1lUdCK9r4q2hUg=="],
"supermemory-mcp/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
"supermemory-mcp/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"tailwindcss-v3/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
@ -4661,7 +4630,7 @@
"xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"zod-from-json-schema/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"zod-from-json-schema/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@a2a-js/sdk/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
@ -4849,7 +4818,7 @@
"@mastra/core/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@mastra/core/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@mastra/core/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@mintlify/cli/inquirer/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
@ -5047,7 +5016,7 @@
"@voltagent/core/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@voltagent/core/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@voltagent/core/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@voltagent/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.1.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ=="],
@ -5167,8 +5136,6 @@
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"ink/react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"is-online/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="],
"is-online/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="],
@ -5457,11 +5424,11 @@
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],

View file

@ -16,7 +16,6 @@
"packageManager": "bun@1.3.6",
"workspaces": [
"apps/*",
"!apps/raycast-extension",
"!tools/test/chatapp",
"packages/*"
],

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "supermemory-cartesia"
version = "0.1.4"
version = "0.1.5"
description = "Supermemory integration for Cartesia Line - memory-enhanced voice agents"
readme = "README.md"
license = "MIT"
@ -13,7 +13,7 @@ authors = [
{ name = "Supermemory", email = "support@supermemory.ai" }
]
keywords = [
"supermemory>=3.16.0,<5",
"supermemory",
"cartesia",
"line",
"memory",
@ -32,7 +32,7 @@ classifiers = [
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
"supermemory>=3.16.0",
"supermemory>=3.16.0,<5",
"cartesia-line>=0.2.0,<0.3.0",
"pydantic>=2.10.0",
"loguru>=0.7.3",

View file

@ -56,7 +56,7 @@ try:
__version__ = version("supermemory-cartesia")
except PackageNotFoundError:
# Source checkouts do not have installed distribution metadata.
__version__ = "0.1.3"
__version__ = "0.1.5"
__all__ = [
# Main agent

View file

@ -477,12 +477,12 @@ class SupermemoryCartesiaAgent:
Yields:
Output events from the wrapped agent.
"""
try:
if type(event).__name__ == "UserTurnEnded":
if type(event).__name__ == "UserTurnEnded":
memory_context = None
try:
logger.info("[Supermemory] Processing UserTurnEnded event")
event, memory_context = await self._enrich_event_with_memories(event)
# Store conversation in background
if hasattr(event, 'history') and event.history:
new_messages = self._new_history_messages(event.history)
if new_messages:
@ -505,15 +505,12 @@ class SupermemoryCartesiaAgent:
task = asyncio.create_task(self._store_messages(new_messages))
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.discard)
except Exception as e:
logger.error(f"[Supermemory] Failed to enrich or queue the conversation turn: {e}")
async for output in self._process_agent(env, event, memory_context):
yield output
else:
async for output in self.agent.process(env, event):
yield output
except Exception as e:
logger.error(f"[Supermemory] Error in process: {e}")
async for output in self._process_agent(env, event, memory_context):
yield output
else:
async for output in self.agent.process(env, event):
yield output

View file

@ -5,11 +5,12 @@ from datetime import datetime, timezone
from typing import Any, Dict, List, Union
def get_last_user_message(messages: List[Dict[str, str]]) -> str | None:
def get_last_user_message(messages: List[Dict[str, Any]]) -> str | None:
"""Extract the last user message content from a list of messages."""
for msg in reversed(messages):
if msg["role"] == "user":
return msg["content"]
content = msg.get("content")
if msg.get("role") == "user" and isinstance(content, str):
return content
return None
@ -70,6 +71,17 @@ def _field(item: Any, *names: str, default: Any = None) -> Any:
return default
def _memory_text(item: Any) -> str:
"""First non-empty memory field; `_field` alone would stop at an empty `memory`."""
if isinstance(item, str):
return item.strip()
for name in ("memory", "chunk", "content"):
value = _field(item, name)
if isinstance(value, str) and value.strip():
return value.strip()
return ""
_MEMORY_DATE_PREFIX = re.compile(
r"^\s*(?:\[recent\]\s*)?(?:\[\d{4}-\d{2}-\d{2}\]\s*)?",
re.IGNORECASE,
@ -124,10 +136,7 @@ def deduplicate_memories(
out = []
for r in results:
# v4 search.memories/hybrid uses `memory` or `chunk`.
memory = _field(r, "memory", "chunk", "content", default="")
if not isinstance(memory, str):
memory = ""
memory = memory.strip()
memory = _memory_text(r)
key = _memory_key(memory)
if key and key not in seen:
seen.add(key)
@ -174,7 +183,7 @@ def format_memories_to_text(
lines.append(f"- {item}")
continue
memory = _field(item, "memory", "chunk", "content", default="")
memory = _memory_text(item)
updated_at = _field(item, "updatedAt", "updated_at", default="")
time_str = format_relative_time(updated_at) if updated_at else ""
if time_str:

Some files were not shown because too many files have changed in this diff Show more