mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-06 08:16:03 +00:00
71 '<!-- CONFIRM -->' review markers across 19 files used HTML comment
syntax, which MDX cannot parse. One parse error breaks the whole
production build — this is why the deployed site 404'd on every page
while local dev limped along. All converted to {/* */} (code-fence
contents untouched). Also: remove the legacy source-'/' redirect,
replace the phantom architecture-diagram image with an ASCII diagram
until the real one lands.
Verified locally: mintlify broken-links parses all pages clean (one
known-good /api-reference tab link that 307s at runtime), and /,
/overview, /concepts/architecture, /quickstart, /patterns/*,
/versioning all render 200 with content.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
188 lines
9.1 KiB
Text
188 lines
9.1 KiB
Text
---
|
|
title: "User Profiles"
|
|
sidebarTitle: "User Profiles"
|
|
description: "What a profile is, how supermemory derives it from a container's memories, and how to inject it into a prompt without wrecking your cache"
|
|
icon: "circle-user"
|
|
---
|
|
|
|
Every container tag has a profile: supermemory's current understanding of the entity that container is about, derived from everything you've ingested there. You don't write it, update it, or manage it. You fetch it:
|
|
|
|
<CodeGroup>
|
|
|
|
```typescript TypeScript
|
|
import Supermemory from "supermemory";
|
|
|
|
const client = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY });
|
|
|
|
const { profile } = await client.profile({
|
|
containerTag: "user_4f8a",
|
|
});
|
|
```
|
|
|
|
```python Python
|
|
from supermemory import Supermemory
|
|
|
|
client = Supermemory()
|
|
|
|
result = client.profile(container_tag="user_4f8a")
|
|
```
|
|
|
|
```bash cURL
|
|
curl -X POST "https://api.supermemory.ai/v4/profile" \
|
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"containerTag": "user_4f8a"}'
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
And you get back two arrays of plain sentences:
|
|
|
|
```json
|
|
{
|
|
"profile": {
|
|
"static": [
|
|
"Sarah is a senior engineer at Meridian, working on the payments platform",
|
|
"Sarah prefers short, technical answers with code over prose",
|
|
"Sarah works from Lisbon, usually async"
|
|
],
|
|
"dynamic": [
|
|
"[Recent] Sarah's being promoted to VP of Product",
|
|
"[Recent] Sarah is migrating the billing service off the legacy queue",
|
|
"[2026-07-14] Sarah hit a deadlock in the payout worker and is still debugging it"
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
That's the whole interface. The interesting part is where those sentences come from, and what to do with them.
|
|
|
|
## The profile samples the container
|
|
|
|
A profile isn't a record you populate — it's derived. When you [add content](/add-memories), the ingestion pipeline extracts memories, connects them into the [graph](/concepts/graph-memory), and continuously distills the container's memories into a compact summary of the entity behind them. The profile *samples the container*: it's the engine's answer to "given everything in here, what should an assistant know about this entity right now?"
|
|
|
|
```mermaid
|
|
graph LR
|
|
A[documents you ingest] --> B[derived memories]
|
|
B --> C[knowledge graph]
|
|
B --> D["profile (per container tag)"]
|
|
C --> D
|
|
```
|
|
|
|
Two consequences fall out of that:
|
|
|
|
**One container tag, one profile.** The profile is scoped to exactly one container. It never reads across container tags — a profile call for `user_4f8a` can't surface anything derived from another user's container, full stop. That's the same isolation boundary that governs [search and writes](/concepts/permissioning), and it's why the standard pattern is one container tag per user.
|
|
|
|
**Everything in the container is fair game.** If you put ten users' conversations in one container, the profile blends all ten — because as far as the engine can tell, that's one entity. It can bite you in a subtler way too: ingest a user's email and the profile can pick up facts about the people they *correspond with*, not just the user. The fix is `entityContext` — tell the engine who the container is about ("This container is about Sarah Chen, a Meridian employee; other people mentioned are her contacts, not the subject") and extraction prioritizes accordingly. See [Customization](/concepts/customization).
|
|
|
|
## Static vs dynamic
|
|
|
|
The two arrays split by how long-lived a fact is, not by topic:
|
|
|
|
- **`static`** — durable facts: who they are, what they do, standing preferences. This is the stuff that's true next month.
|
|
- **`dynamic`** — recent episodes and current state: what they're working on, what happened lately. Entries carry `[Recent]` or `[YYYY-MM-DD]` prefixes so your model can weigh recency; strip them if you only want the text.
|
|
|
|
Static populates as the engine sees the same durable facts hold up across ingestions — a brand-new container won't have a settled static section after one message, and early on you'll mostly see dynamic entries. {/* CONFIRM: exact static-population timing/threshold */} Older dynamic context doesn't pile up forever either: it gets periodically consolidated into denser summaries, so the profile stays compact instead of growing with the container.
|
|
|
|
Beyond the lifespan axis, you can define **buckets** — topical categories like `preferences` or `goals` that a classifier assigns memories to at ingestion time. Every org starts with a built-in `preferences` bucket; you define more in the console, and request them with `include: ["buckets"]` on the profile call. Buckets and `filterPrompt` (your rules for what's worth remembering at all) are the two levers that shape what lands in a profile — bucket descriptions steer where facts get filed, `filterPrompt` steers what gets extracted in the first place. Mechanics for both are in the [profile API reference](/user-profiles) and [Customization](/concepts/customization).
|
|
|
|
## Combine profile with search
|
|
|
|
Pass `q` and the same call also runs a search in that container, returned alongside the profile:
|
|
|
|
<CodeGroup>
|
|
|
|
```typescript TypeScript
|
|
const result = await client.profile({
|
|
containerTag: "user_4f8a",
|
|
q: "billing migration",
|
|
});
|
|
|
|
result.profile.static; // who Sarah is
|
|
result.profile.dynamic; // what she's up to
|
|
result.searchResults?.results; // memories matching "billing migration"
|
|
```
|
|
|
|
```python Python
|
|
result = client.profile(
|
|
container_tag="user_4f8a",
|
|
q="billing migration",
|
|
)
|
|
|
|
result.profile.static
|
|
result.profile.dynamic
|
|
result.search_results.results if result.search_results else []
|
|
```
|
|
|
|
```bash cURL
|
|
curl -X POST "https://api.supermemory.ai/v4/profile" \
|
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"containerTag": "user_4f8a", "q": "billing migration"}'
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
`searchResults` only appears when you pass `q`. This is the one-round-trip context call: broad understanding plus query-specific memories, instead of a profile fetch and a separate [search](/search).
|
|
|
|
## Inject it without wrecking your prompt cache
|
|
|
|
The profile is built to sit in a prompt: plain sentences, and compact — it stays within roughly a 1k-token budget rather than growing with the container. But *where* you put each piece matters, because prompt caching works on stable prefixes.
|
|
|
|
The pattern that works:
|
|
|
|
- **Static profile goes in the system prompt.** It changes rarely, so the prefix stays byte-identical across turns and your provider's prompt cache keeps hitting.
|
|
- **Dynamic context (and `q` results) get appended to the user message.** They change turn to turn — put them in the system prompt and you bust the cache on every message.
|
|
|
|
To wire that up:
|
|
|
|
```typescript
|
|
async function buildMessages(userId: string, userMessage: string) {
|
|
const result = await client.profile({ containerTag: userId, q: userMessage });
|
|
|
|
const system = `You are a personal assistant.
|
|
|
|
About this user:
|
|
${result.profile.static?.join("\n") ?? "No profile yet."}`;
|
|
|
|
const memories = result.searchResults?.results
|
|
?.map((r) => r.memory)
|
|
.join("\n");
|
|
|
|
// dynamic + search context ride with the message, not the cached prefix
|
|
const user = `${userMessage}
|
|
|
|
<context>
|
|
${result.profile.dynamic?.join("\n") ?? ""}
|
|
${memories ?? ""}
|
|
</context>`;
|
|
|
|
return [
|
|
{ role: "system", content: system },
|
|
{ role: "user", content: user },
|
|
];
|
|
}
|
|
```
|
|
|
|
If you're on the Vercel AI SDK, `withSupermemory` does this injection for you — see the [AI SDK integration](/integrations/ai-sdk).
|
|
|
|
<Note>
|
|
The profile call is a read — it doesn't count as ingestion, so calling it on every message costs you latency, not quota. It's fast enough to sit in the hot path. {/* CONFIRM: profile latency ~100ms publishable */}
|
|
</Note>
|
|
|
|
## What profiles are not
|
|
|
|
**Not a key-value store.** There's no API to set a profile field, and that's deliberate — the profile is derived understanding, so it stays consistent with the memories underneath it. If you need to assert a fact, ingest it ("Sarah's preferred language is Portuguese") and the profile absorbs it.
|
|
|
|
**Not cross-user data.** A profile can't aggregate across users, and you shouldn't try to make it — a shared "everyone" container gives you a mushy profile of no one. For team-wide knowledge, use a shared container for the *content* and per-user containers for the *people*; the [multi-tenant pattern](/patterns/multi-tenant-saas) shows the split.
|
|
|
|
**Not a replacement for search.** The profile answers "who is this entity" in ~1k tokens. Specific recall — "what did Sarah say about the payout worker" — is a [search](/concepts/hybrid-search) question. Use both: profile as the standing context, search (or the `q` param) for the details a given message needs.
|
|
|
|
That's it — your assistant opens every conversation already knowing who it's talking to.
|
|
|
|
## Where next
|
|
|
|
- [Profile API reference](/user-profiles) — every parameter, buckets, response schema
|
|
- [Permissioning](/concepts/permissioning) — container tags, isolation, scoped keys
|
|
- [Customization](/concepts/customization) — `entityContext`, `filterPrompt`, and shaping extraction
|
|
- [AI SDK integration](/integrations/ai-sdk) — automatic profile injection with `withSupermemory`
|