> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentium.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-User Isolation

> How Agentium memory guarantees that one user never sees another user's data.

# Multi-User Isolation

Production agents serve many users. Without strict isolation, a memory layer
becomes a privacy and compliance liability — Alice's facts surface in Bob's
prompt, last week's customer's transactions leak into today's chat, and
auto-extracted "learnings" cross-pollinate between unrelated tenants.

Agentium's memory layer is built so that **every read and every write is
scoped to the calling user**. This page documents the exact contract.

***

## The contract

| Store                | Default scope                      | Storage key                                                                       | Read path                        | Write path                                        |
| -------------------- | ---------------------------------- | --------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------- |
| **Sessions**         | session                            | `sessionId`                                                                       | session-scoped                   | session-scoped                                    |
| **Summaries**        | session                            | `${sessionId}:` (prefix-terminated)                                               | session-scoped                   | session-scoped                                    |
| **User facts**       | user                               | `userId`                                                                          | user-scoped                      | user-scoped                                       |
| **User profile**     | user                               | `userId`                                                                          | user-scoped                      | user-scoped                                       |
| **Entity memory**    | user                               | `memory:entities:${namespace}:user:${userId}`                                     | user-scoped                      | user-scoped                                       |
| **Graph memory**     | user                               | nodes/edges tagged with `_userId` property                                        | user-filtered                    | user-scoped (ids namespaced as `${userId}:${id}`) |
| **Procedure memory** | user → **agent → tenant → global** | `memory:procedures:${scope}:${owner}`                                             | union of scopes the caller is in | scope chosen at save time                         |
| **Decision log**     | agent + session                    | `${agentName}:${id}`, filtered by `sessionId`                                     | session+agent-scoped             | session+agent-scoped                              |
| **Learnings**        | user → **agent → tenant → global** | row carries `scope` + the matching owner; vector search over-fetches then filters | union of scopes the caller is in | scope chosen at save time                         |

***

## Scope hierarchy (Learnings & Procedures)

Some knowledge is genuinely shared — a workflow template like "invoice
reconciliation" belongs to a team, not to whoever first noticed it. **Learnings**
and **Procedures** support an explicit scope:

```
global       ← rarely used; built-in defaults
  ↑
tenant       ← organization-wide policies ("Refunds > $500 need VP sign-off")
  ↑
agent        ← workflow / role knowledge ("Vendor X invoices always drift")
  ↑
user         ← personal preference (default)
```

When the agent **saves** a learning or procedure, it picks the scope:

```typescript theme={null}
save_learning({
  title: "Vendor X line-item drift",
  content: "Vendor X invoices consistently show $0.10–$0.50 drift per line...",
  context: "invoice reconciliation",
  scope: "agent",     // share with everyone using the invoice-recon agent
});
```

When the agent **reads**, it sees the **union** of every scope it's authorized
to access — its personal scope, its current agent's scope, and its tenant's
scope. There is no "see another user's personal scope" path, ever.

### Defaults are safe

* **`scope` defaults to `"user"`** when omitted on a save — so existing code
  that didn't know about scopes keeps the same isolation guarantees.
* **Auto-extracted learnings/procedures always save as `"user"`** — the
  framework never auto-promotes an LLM-extracted insight to a shared scope
  without the caller explicitly choosing that.
* **Pre-v2.3 data without a `scope` field** is treated as user-scoped on read
  (the safe fallback).

### Required identifiers per scope

A scope is only useful if the framework knows the relevant identifier:

| Scope    | Required identifier | Source                                                |
| -------- | ------------------- | ----------------------------------------------------- |
| `user`   | `userId`            | `RunContext.userId`                                   |
| `agent`  | `agentName`         | `RunContext.metadata.agentName` (auto-set by `Agent`) |
| `tenant` | `tenantId`          | `memory.tenantId` config field                        |
| `global` | none                | always visible                                        |

If a caller tries to save with `scope: "agent"` but no `agentName` is in
context, the save fails with a clear error. There is no silent fallback that
could leak data.

### Visibility example

A user `alice` chatting with the `invoice-recon` agent inside tenant `acme`
sees the union of:

* Her personal user-scoped learnings (saved under `userId: "alice"`)
* The agent's shared workflow knowledge (saved under `agentName: "invoice-recon"`)
* The org's policies (saved under `tenantId: "acme"`)
* All `global` learnings (the built-in defaults)

She does **not** see:

* Bob's personal learnings (different `userId`)
* Learnings saved against the `hr-agent` (different `agentName`)
* Any other tenant's policies (different `tenantId`)

Every prefix-list query terminates the prefix with the key separator
(`${sessionId}:` rather than `${sessionId}`) so that session `"abc"` cannot
match `"abc123"`.

***

## What the agent sees in the prompt

When `MemoryManager.buildContext(sessionId, userId, currentInput, agentName)`
returns, each section is wrapped in an explicit scope marker:

```xml theme={null}
<memory section="userFacts" scope="current_user">
What you know about this user:
Facts the user told you directly (high confidence):
- User's name is Akash.
- Akash is based in Mumbai.
</memory>

<memory section="entities" scope="current_user">
Known entities:
- Acme Corp (company): Logistics platform
</memory>
```

The `scope="current_user"` attribute is a deliberate signal to the model:
**these are facts about the user you are currently talking to, not about
"some user in the database"**. This is the LLM-side complement to the
storage-side scoping.

***

## What it means for direct API callers

If you bypass the agent's auto-exposed tools and call a memory store directly,
you **must** pass a `userId`. The signatures enforce it at the TypeScript layer:

```typescript theme={null}
const entities = agent.memory!.getEntityMemory()!;

// ✅ Required
await entities.upsertEntity("user-123", { name: "Acme", entityType: "company" });
const list = await entities.listEntities("user-123");

// ❌ TypeScript error: Expected 1-2 arguments, but got 0.
await entities.listEntities();
```

The same applies to `GraphMemory.extractFromConversation()`,
`ProcedureMemory.saveProcedure()`, and friends. There is no "global" read path
on these stores anymore — by design.

***

## What stores share data intentionally

A small number of paths share data deliberately. None of them leak user content.

* **Decision Log** — keyed by `agentName` + `sessionId`. Two different users
  in two different sessions of the same agent will not see each other's
  decisions, but the log is queryable across users by agent for auditing.
* **Tenant namespace on Entity Memory** — the `namespace` field on
  `EntityMemory` is a tenant-level partition orthogonal to `userId`. Set it
  per-tenant to prevent userId collisions across tenants (`alice@apex` vs
  `alice@meridian`).
* **Learnings vector store** — the vector index itself is shared (cost-saving)
  but every result is post-filtered by `userId` before it leaves the store.

***

## Right to be forgotten

The Curator's `clearAll({ userId })` wipes every store's data for that user —
user facts, profile, entities, and so on. It does **not** wipe other users'
data even when called from a "global" code path:

```typescript theme={null}
await agent.memory!.curator.clearAll({ userId: "user-123" });
// Removes:
//   - user-123's facts
//   - user-123's profile
//   - user-123's entities
//   - user-123's procedures (if enabled)
// Does NOT touch any other user's data.
```

This is your GDPR Article 17 implementation path. Pair it with audit logs
(see [Compliance](/compliance/overview)) for a fully documented erasure flow.

***

## Forgotten facts vs superseded facts

When a fact is invalidated, the framework records **why**:

* `invalidationReason: "superseded"` — a newer fact about the same subject
  replaced it. Typical case: user said "I work at Google now" after the
  store knew "I work at Shipment". The old fact becomes a silent tombstone
  used only by the Curator for analytics.
* `invalidationReason: "forgotten"` — the user explicitly asked the agent
  to forget the fact (e.g. "forget my birthday"). The fact is surfaced in
  the prompt's "user asked to forget" block with a strict instruction
  never to restate it, and `recall_user_facts` will not return it.

The distinction matters because, without it, an over-cautious LLM will
refuse to answer "where do I work?" — seeing both the new active fact and
the superseded one in the forget block, it concludes it shouldn't talk
about employment at all.

***

## What this gives you for free

* **Multi-tenant SaaS** — set a tenant-level `namespace` on entity memory,
  user-scope handles the rest.
* **GDPR / CCPA erasure** — one curator call wipes a single user.
* **Audit trail** — every memory mutation emits an event on the bus.
* **Strict TypeScript** — calling memory APIs without a `userId` is a
  compile-time error, not a runtime privacy bug.

***

## Cross-References

* [Memory Overview](/memory/overview) — How the system fits together
* [Memory Stores](/memory/stores) — Per-store details
* [Memory Curator](/memory/curator) — Pruning, dedup, consolidation, and erasure
* [Compliance](/compliance/overview) — PII redaction and audit logs
* [Security](/security) — Framework-wide security posture
