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
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:Defaults are safe
scopedefaults 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
scopefield 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:
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 useralice 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
globallearnings (the built-in defaults)
- Bob’s personal learnings (different
userId) - Learnings saved against the
hr-agent(differentagentName) - Any other tenant’s policies (different
tenantId)
${sessionId}: rather than ${sessionId}) so that session "abc" cannot
match "abc123".
What the agent sees in the prompt
WhenMemoryManager.buildContext(sessionId, userId, currentInput, agentName)
returns, each section is wrapped in an explicit scope marker:
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 auserId. The signatures enforce it at the TypeScript layer:
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
namespacefield onEntityMemoryis a tenant-level partition orthogonal touserId. Set it per-tenant to prevent userId collisions across tenants (alice@apexvsalice@meridian). - Learnings vector store — the vector index itself is shared (cost-saving)
but every result is post-filtered by
userIdbefore it leaves the store.
Right to be forgotten
The Curator’sclearAll({ 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:
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, andrecall_user_factswill not return it.
What this gives you for free
- Multi-tenant SaaS — set a tenant-level
namespaceon 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
userIdis a compile-time error, not a runtime privacy bug.
Cross-References
- Memory Overview — How the system fits together
- Memory Stores — Per-store details
- Memory Curator — Pruning, dedup, consolidation, and erasure
- Compliance — PII redaction and audit logs
- Security — Framework-wide security posture