diff --git a/apps/docs/concepts/container-tags.mdx b/apps/docs/concepts/container-tags.mdx new file mode 100644 index 000000000..71b8b0d76 --- /dev/null +++ b/apps/docs/concepts/container-tags.mdx @@ -0,0 +1,176 @@ +--- +title: "Container Tags" +sidebarTitle: "Container Tags" +description: "The isolation boundary that groups and partitions memories by user, project, or any logical scope" +icon: "folder" +--- + +A **container tag** is the primary way you organize and isolate memories in Supermemory. It's a simple string identifier you attach to content when you add it — and that you pass back when you search, list, or update it. + +Think of a container tag as a **namespace**: every memory tagged with `user_alex` lives in its own isolated space, completely separate from memories tagged `user_jordan`. This is what makes Supermemory safe to use in multi-tenant applications — one user can never see another user's memories unless you explicitly query across both tags. + + + + Bucket memories by user, project, agent, workspace, or any boundary that makes sense for your app. + + + Each container tag maps to its own vector namespace, so search and retrieval never leak across boundaries. + + + +--- + +## How it works + +When you add a memory with a container tag, Supermemory automatically creates a **space** for that tag (scoped to your organization) if one doesn't already exist. You don't need to provision anything ahead of time — the first write with a new tag creates the container, and subsequent writes reuse it. + +```typescript +// First call auto-creates the "user_alex" container +await client.add({ + content: "Alex prefers dark mode and concise answers", + containerTag: "user_alex", +}); + +// Later, retrieve only Alex's memories +const results = await client.search.memories({ + q: "what are the user's UI preferences?", + containerTag: "user_alex", +}); +``` + +Under the hood, each container tag is hashed into a dedicated vector namespace. Embeddings, chunks, and memory entries for one tag are stored and searched independently of every other tag — there is no shared index to filter through, which is why isolation is strict rather than best-effort. + + +A container tag is an **opaque identifier you choose**. Supermemory does not parse meaning out of it — `user_123`, `project_mobile`, and `org:acme:team:growth` are all equally valid. Pick a convention that mirrors the access boundaries in your own application. + + +--- + +## Naming rules + +Container tags are validated on every request. A tag must: + +- Be **100 characters or less** +- Contain only **alphanumeric characters, hyphens (`-`), underscores (`_`), and colons (`:`)** + +Matching pattern: `^[a-zA-Z0-9_:-]+$` + +```typescript +// ✅ Valid +"user_123" +"project-mobile-app" +"org:acme:user:john" +"tenant_42_workspace_7" + +// ❌ Invalid — spaces, slashes, and other symbols are rejected +"user 123" +"project/mobile" +"team@acme" +``` + +The colon is intentionally allowed so you can build **hierarchical** tags (for example `org:acme:user:john`) that encode several levels of structure in a single identifier. + +--- + +## `containerTag` vs `containerTags` + +Supermemory's current API uses a **single** `containerTag` string per request. + + +The plural `containerTags` array field is **deprecated**. It still works for backward compatibility on older (`/v3`) endpoints, but new integrations should use the singular `containerTag` string. The `/v4` API only accepts `containerTag`. + + +| API field | Type | Status | +|-----------|------|--------| +| `containerTag` | `string` | ✅ Current — use this | +| `containerTags` | `string[]` | ⚠️ Deprecated | + +--- + +## Where container tags are used + +The same tag flows through the entire lifecycle of a memory. Pass it consistently and your data stays neatly partitioned. + +| Operation | Behavior | +|-----------|----------| +| **Add** | Writes the memory into the tag's container (auto-creating the space). | +| **Search** | Restricts retrieval to the given tag's namespace. | +| **List** | Returns only memories belonging to the tag(s). | +| **Update / Delete** | Targets the memory inside the specified tag's container. | + +```typescript +// Add +await client.add({ content: "Q1 planning notes", containerTag: "project_q1" }); + +// Search within the same container +await client.search.memories({ q: "planning", containerTag: "project_q1" }); + +// List everything in the container +await client.documents.list({ containerTags: ["project_q1"] }); +``` + +--- + +## Access control + +Container tags are also an **authorization boundary**, not just an organizational one. Two mechanisms can restrict which tags a given caller may touch: + +- **API key scopes** — an API key can be limited to a specific set of container tags, with read or write permission per tag. +- **Member restrictions** — an organization member can be granted access to only certain container tags. + +When a request is restricted, Supermemory validates the requested tag against the caller's allowed set: + +- Requesting a tag outside the allowed set returns `403 Forbidden`. +- A write (add/update/delete) to a read-only tag returns `403 Forbidden`. +- If no tag is supplied by a restricted caller, the request is automatically scoped to their allowed tag(s). + +This means you can hand out an API key that is physically incapable of reading or writing another tenant's data, enforced at the data layer rather than in your application code. + +--- + +## Per-container settings + +Each container tag can carry its own configuration, independent of other tags in the same organization: + +| Setting | Purpose | +|---------|---------| +| `name` | A human-friendly display name for the container. | +| `entityContext` | A custom context prompt applied when processing documents in this container — useful for steering extraction and summarization per project or tenant. | + +```typescript +await client.containerTags.update("project_research", { + entityContext: "This project contains research papers about machine learning.", +}); +``` + +Container tags can also be **merged** when you need to consolidate two buckets of memories into one. + +--- + +## Choosing a convention + +Pick a tagging scheme that maps onto the isolation boundaries your application actually needs. + +| Pattern | Example | Use case | +|---------|---------|----------| +| User isolation | `user_{userId}` | Per-user memory in a consumer app | +| Project grouping | `project_{projectId}` | Project- or workspace-scoped content | +| Agent scoping | `agent_{agentId}` | Separate long-term memory per AI agent | +| Hierarchical | `org:{orgId}:user:{userId}` | Multi-level, multi-tenant SaaS | + + +Keep tags **deterministic** — derive them directly from IDs you already have (a user ID, a tenant ID) so you can always reconstruct the right tag at query time without a lookup. + + +--- + +## Next steps + + + + Combine container tags with metadata filters for precise retrieval. + + + See container tags in action across the add API. + + diff --git a/apps/docs/docs.json b/apps/docs/docs.json index f77b1882a..6be467ff7 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -88,6 +88,7 @@ "concepts/content-types", "concepts/super-rag", "concepts/memory-vs-rag", + "concepts/container-tags", "concepts/filtering", "concepts/user-profiles", "concepts/customization",