mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-07-09 17:18:51 +00:00
docs update for profile buckets and inferred memory review (#1158)
### TL;DR
Adds documentation for the Memory Review endpoints and Profile Buckets feature.
### What changed?
**Memory Review (`memory-review.mdx`)**
- Added a new documentation page covering the two inferred memory review endpoints: `GET /v3/container-tags/{containerTag}/inferred` and `POST /v3/container-tags/{containerTag}/inferred/{memoryId}/review`.
- Documents the three review actions (`approve`, `decline`, `undo`) and how each affects search ranking and memory state (`isInference`, `isForgotten`, `reviewStatus`).
- Includes request/response examples in both `fetch` and cURL, a field reference table, error codes, and a collapsible React Query hooks example for building a review UI.
- Registered the new page in `docs.json` under the "Manage Content" group and linked to it from the Memory Operations next steps.
**Profile Buckets (`user-profiles.mdx`)**
- Added a "Profile Buckets" section explaining custom topical categories (`preferences`, `goals`, `work`, etc.) as a complement to `static`/`dynamic` profile sections.
- Documents the `include`, `buckets`, and `filters` query parameters on the profile endpoint.
- Covers the `GET /v4/profile/buckets` endpoint for listing configured bucket definitions, with request/response examples and a field reference.
- Explains the `[Recent]` / `[Summary]` label convention used in bucket and dynamic profile entries.
- Updated the `ProfileResponse` TypeScript interface to mark `static` and `dynamic` as optional and add the `buckets` field.
### How to test?
- Navigate to the docs site and confirm "Memory Review" appears in the sidebar under "Manage Content".
- Verify all code examples render correctly and tabs switch between `fetch` and cURL variants.
- Confirm the React Query accordion expands and displays the TypeScript snippet.
- Check that the Profile Buckets section renders inline within the User Profiles page, including the response JSON blocks and the tip/note callouts.
### Why make this change?
Inferred (derived) graph memories are down-weighted in search until reviewed, but there was no documentation explaining how to surface or act on them. Similarly, profile buckets were a shipped feature with no public-facing docs. These additions give developers the reference material needed to build review UIs and use topical bucket filtering in their integrations.
This commit is contained in:
parent
603d0512fd
commit
7d1428e797
4 changed files with 436 additions and 3 deletions
|
|
@ -104,7 +104,11 @@
|
|||
{
|
||||
"group": "Manage Content",
|
||||
"icon": "folder-cog",
|
||||
"pages": ["document-operations", "memory-operations"]
|
||||
"pages": [
|
||||
"document-operations",
|
||||
"memory-operations",
|
||||
"memory-review"
|
||||
]
|
||||
},
|
||||
"overview/use-cases"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ Update a memory by creating a new version. The original is preserved with `isLat
|
|||
|
||||
## Next Steps
|
||||
|
||||
- [Review Inferred Memories](/memory-review) — Approve or decline low-confidence memories
|
||||
- [Document Operations](/document-operations) — Manage documents (SDK supported)
|
||||
- [Search](/search) — Query your memories
|
||||
- [Ingesting Content](/add-memories) — Add new content
|
||||
|
|
|
|||
288
apps/docs/memory-review.mdx
Normal file
288
apps/docs/memory-review.mdx
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
---
|
||||
title: "Review Inferred Memories"
|
||||
sidebarTitle: "Memory Review"
|
||||
description: "List and act on low-confidence inferred memories — approve, decline, or undo"
|
||||
icon: "list-checks"
|
||||
---
|
||||
|
||||
Supermemory's graph automatically **derives** new facts from patterns across your
|
||||
existing memories (see [Graph Memory](/concepts/graph-memory)). These derived facts
|
||||
are guesses — the engine wasn't told them directly — so they are flagged as
|
||||
**inferred** (`isInference: true`) and **down-weighted in search** until confirmed.
|
||||
|
||||
These two endpoints let you build a review experience on top of that queue: list the
|
||||
inferred memories awaiting review, then **approve**, **decline**, or **undo** a
|
||||
decision on each one.
|
||||
|
||||
<Info>
|
||||
These endpoints are scoped to a single [container tag](/concepts/container-tags)
|
||||
(space), under `/v3/container-tags/{containerTag}`.
|
||||
</Info>
|
||||
|
||||
## How review affects ranking
|
||||
|
||||
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:
|
||||
|
||||
| Action | Result | Effect on search |
|
||||
|--------|--------|------------------|
|
||||
| **Approve** | `isInference` cleared | Ranks like a stated fact — no longer down-weighted |
|
||||
| **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 |
|
||||
|
||||
A reviewed memory is stamped with `reviewStatus` 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.
|
||||
|
||||
---
|
||||
|
||||
## List Inferred Memories
|
||||
|
||||
Return the inferred memories for a container tag that are still awaiting review (the
|
||||
review queue). Reviewed memories are excluded.
|
||||
|
||||
```
|
||||
GET /v3/container-tags/{containerTag}/inferred
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<Tab title="fetch">
|
||||
```typescript
|
||||
const res = await fetch(
|
||||
"https://api.supermemory.ai/v3/container-tags/user_123/inferred",
|
||||
{ headers: { "Authorization": `Bearer ${API_KEY}` } }
|
||||
);
|
||||
|
||||
const { memories, total } = await res.json();
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl "https://api.supermemory.ai/v3/container-tags/user_123/inferred" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `containerTag` | string | The container tag / space to read the review queue for |
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"memories": [
|
||||
{
|
||||
"id": "mem_abc123",
|
||||
"memory": "Alex likely works on Stripe's core payments product",
|
||||
"parentCount": 3,
|
||||
"createdAt": "2025-01-15T10:30:00.000Z",
|
||||
"updatedAt": "2025-01-15T10:30:00.000Z",
|
||||
"metadata": { "source": "derive" }
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `memories[].id` | string | Memory entry ID — pass to the review endpoint |
|
||||
| `memories[].memory` | string | The inferred memory text |
|
||||
| `memories[].parentCount` | number | How many source memories this was derived from. Higher = stronger signal |
|
||||
| `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 |
|
||||
|
||||
<Note>
|
||||
The queue returns up to **50** memories, ordered by `parentCount` descending (most
|
||||
strongly supported first), then by `createdAt` descending. It excludes anything that
|
||||
is forgotten, expired, or already reviewed. An unknown or empty container tag returns
|
||||
`{ "memories": [], "total": 0 }`.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Review an Inferred Memory
|
||||
|
||||
Record a decision on a single inferred memory.
|
||||
|
||||
```
|
||||
POST /v3/container-tags/{containerTag}/inferred/{memoryId}/review
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<Tab title="fetch">
|
||||
```typescript
|
||||
const res = await fetch(
|
||||
"https://api.supermemory.ai/v3/container-tags/user_123/inferred/mem_abc123/review",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ action: "approve" })
|
||||
}
|
||||
);
|
||||
|
||||
const result = await res.json();
|
||||
// { id: "mem_abc123", isInference: false, isForgotten: false, reviewStatus: "approved" }
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl -X POST \
|
||||
"https://api.supermemory.ai/v3/container-tags/user_123/inferred/mem_abc123/review" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"action": "approve"}'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Path parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `containerTag` | string | The container tag / space the memory belongs to |
|
||||
| `memoryId` | string | The memory entry ID from the list endpoint |
|
||||
|
||||
### Body parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `action` | string | yes | One of `approve`, `decline`, or `undo` |
|
||||
|
||||
<Warning>
|
||||
The reject action is named **`decline`**. There is no `reject` value.
|
||||
</Warning>
|
||||
|
||||
**Action semantics:**
|
||||
|
||||
- **`approve`** — Promote the memory: clears `isInference`, so it ranks like a stated
|
||||
fact instead of a down-weighted guess. 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.
|
||||
- **`undo`** — Revert a prior `approve`/`decline` back to the unreviewed inferred
|
||||
state: restores `isInference: true`, un-forgets the memory (`isForgotten: false`),
|
||||
and clears the review stamp, so it returns to the queue.
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mem_abc123",
|
||||
"isInference": false,
|
||||
"isForgotten": false,
|
||||
"reviewStatus": "approved"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | string | The reviewed memory ID |
|
||||
| `isInference` | boolean | `false` after approve; `true` after decline or undo |
|
||||
| `isForgotten` | boolean | `true` after decline (the memory is forgotten); `false` otherwise |
|
||||
| `reviewStatus` | `"approved"` \| `"declined"` \| `null` | The new status; `null` after an undo |
|
||||
|
||||
### Errors
|
||||
|
||||
| Status | When |
|
||||
|--------|------|
|
||||
| `401` | Missing or invalid authentication |
|
||||
| `404` | The container tag 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 |
|
||||
|
||||
---
|
||||
|
||||
## Building a review experience
|
||||
|
||||
The endpoints are designed for an optimistic, one-at-a-time review UI (swipe to keep /
|
||||
decline, with undo). A typical client fetches the queue once, then pops each card off
|
||||
locally as the user decides — `undo` re-adds it.
|
||||
|
||||
<Accordion title="React Query hooks (TypeScript)">
|
||||
```typescript
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
const BASE = "https://api.supermemory.ai/v3";
|
||||
const key = (tag: string) => ["inferred-memories", tag] as const;
|
||||
|
||||
export type InferredMemory = {
|
||||
id: string;
|
||||
memory: string;
|
||||
parentCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
metadata: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
export type ReviewAction = "approve" | "decline" | "undo";
|
||||
|
||||
export function useInferredMemories(containerTag: string) {
|
||||
return useQuery({
|
||||
queryKey: key(containerTag),
|
||||
queryFn: async (): Promise<InferredMemory[]> => {
|
||||
const res = await fetch(`${BASE}/container-tags/${containerTag}/inferred`, {
|
||||
headers: { Authorization: `Bearer ${API_KEY}` },
|
||||
});
|
||||
if (!res.ok) throw new Error("Failed to load review queue");
|
||||
const data = await res.json();
|
||||
return data.memories ?? [];
|
||||
},
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useReviewInferredMemory(containerTag: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (vars: { memoryId: string; action: ReviewAction }) => {
|
||||
const res = await fetch(
|
||||
`${BASE}/container-tags/${containerTag}/inferred/${vars.memoryId}/review`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ action: vars.action }),
|
||||
},
|
||||
);
|
||||
if (!res.ok) throw new Error("Review failed");
|
||||
return res.json();
|
||||
},
|
||||
onSuccess: (_data, { memoryId, action }) => {
|
||||
// approve/decline remove the card; undo brings it back, so refetch.
|
||||
if (action === "undo") {
|
||||
queryClient.invalidateQueries({ queryKey: key(containerTag) });
|
||||
return;
|
||||
}
|
||||
queryClient.setQueryData<InferredMemory[]>(key(containerTag), (prev) =>
|
||||
prev?.filter((m) => m.id !== memoryId),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Note>
|
||||
There is no separate "skip" action. A swipe-to-skip is purely client-side — don't send
|
||||
a request and the memory simply stays in the queue for a later session.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Graph Memory](/concepts/graph-memory) — How inferred (`derive`) memories are created
|
||||
- [Memory Operations](/memory-operations) — Create, forget, and update memories
|
||||
- [Search](/search) — How inferred memories are ranked in results
|
||||
|
|
@ -121,6 +121,9 @@ Get profile and search results in one call by adding the `q` parameter:
|
|||
| `containerTag` | string | Yes | User/project identifier |
|
||||
| `q` | string | No | Search query (includes search results in response) |
|
||||
| `threshold` | 0-1 | No | Filter search results by relevance score |
|
||||
| `filters` | object | No | Metadata filters applied to profile and search results |
|
||||
| `include` | string[] | No | Sections to return — any of `"static"`, `"dynamic"`, `"buckets"`. Omit to return all |
|
||||
| `buckets` | string[] | No | Restrict the `buckets` section to specific keys. Omit for all configured buckets |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -180,6 +183,142 @@ ${result.searchResults?.results.map(m => m.memory).join('\n') || 'None'}
|
|||
|
||||
---
|
||||
|
||||
## Profile Buckets
|
||||
|
||||
Buckets are **custom topical categories** for a profile — an axis that sits alongside
|
||||
`static` and `dynamic`. Where static/dynamic split facts by how long-lived they are,
|
||||
buckets group them by subject (e.g. `preferences`, `goals`, `work`). As content is
|
||||
ingested, a classifier assigns each memory to the buckets it matches, so you can pull
|
||||
just the slice of context a given surface needs.
|
||||
|
||||
Every org starts with a built-in `preferences` bucket. You can define your own at the
|
||||
organization or space level in your console settings; space-level buckets are
|
||||
**add-only** — a container tag inherits all org buckets and may add more, but cannot
|
||||
disable them.
|
||||
|
||||
### Requesting buckets
|
||||
|
||||
Pass `include: ["buckets"]` to return bucket-organized memories, and optionally
|
||||
`buckets` to limit the response to specific keys. `include` also lets you skip
|
||||
sections you don't need — `["buckets"]` alone omits `static` and `dynamic`.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="fetch">
|
||||
```typescript
|
||||
const res = await fetch("https://api.supermemory.ai/v4/profile", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
containerTag: "user_123",
|
||||
include: ["buckets"],
|
||||
buckets: ["preferences", "goals"] // optional — omit for all buckets
|
||||
})
|
||||
});
|
||||
|
||||
const { profile } = await res.json();
|
||||
console.log(profile.buckets.preferences);
|
||||
console.log(profile.buckets.goals);
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl -X POST "https://api.supermemory.ai/v4/profile" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"containerTag": "user_123",
|
||||
"include": ["buckets"],
|
||||
"buckets": ["preferences", "goals"]
|
||||
}'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"profile": {
|
||||
"buckets": {
|
||||
"preferences": [
|
||||
"[Summary] Prefers concise, technical answers and dark-mode tooling",
|
||||
"[Recent] Switched their editor to Zed"
|
||||
],
|
||||
"goals": [
|
||||
"[Recent] Wants to ship the billing revamp this quarter"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<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.
|
||||
</Note>
|
||||
|
||||
### List bucket definitions
|
||||
|
||||
To see which buckets are configured for a container tag (org buckets merged with any
|
||||
space-level additions), call `/v4/profile/buckets`:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="fetch">
|
||||
```typescript
|
||||
const res = await fetch("https://api.supermemory.ai/v4/profile/buckets", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ containerTag: "user_123" })
|
||||
});
|
||||
|
||||
const { buckets } = await res.json();
|
||||
// [{ key: "preferences", description: "..." }, ...]
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl -X POST "https://api.supermemory.ai/v4/profile/buckets" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"containerTag": "user_123"}'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"buckets": [
|
||||
{
|
||||
"key": "preferences",
|
||||
"description": "Explicit first-person preferences the person directly stated."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `buckets[].key` | string | Stable slug, also stored on each memory. Lowercase alphanumeric with `-`/`_`, 1–64 chars |
|
||||
| `buckets[].description` | string | What belongs in the bucket — guides the ingestion classifier |
|
||||
|
||||
<Tip>
|
||||
Bucket descriptions steer classification. A precise description ("Explicit
|
||||
first-person preferences only — exclude inferred traits") yields cleaner buckets than
|
||||
a vague one. `static` and `dynamic` are reserved and can't be used as bucket keys.
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## Framework Examples
|
||||
|
||||
<Accordion title="Express.js Middleware">
|
||||
|
|
@ -249,8 +388,9 @@ ${result.searchResults?.results.map(m => m.memory).join('\n') || 'None'}
|
|||
```typescript
|
||||
interface ProfileResponse {
|
||||
profile: {
|
||||
static: string[]; // Long-term facts
|
||||
dynamic: string[]; // Recent context
|
||||
static?: string[]; // Long-term facts
|
||||
dynamic?: string[]; // Recent context
|
||||
buckets?: Record<string, string[]>; // Topical buckets, keyed by bucket key
|
||||
};
|
||||
searchResults?: { // Only if q parameter provided
|
||||
results: SearchResult[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue