Multi-Tenant Applications¶
Memorizz supports multi-tenant use cases — applications that serve a user base where every end user must have an isolated memory surface. The scope model is layered:
Each level below user_id is agent-level infrastructure. user_id is the
tenant boundary that the framework enforces on every memory read and write.
When to use user_id¶
Pass user_id whenever one deployed agent serves many end-users. Typical
examples:
- A chat product where each signed-in user has their own conversation history
- A B2B SaaS where memory must never cross customer boundaries
- A classroom assistant with per-student context
For single-operator local development (the default memorizz ui experience),
you can leave user_id out entirely — the framework falls back to the
anonymous/legacy scope.
API¶
user_id is an invocation parameter, not a builder parameter. One
MemAgent instance can safely serve every user in your application:
from memorizz.memagent import MemAgent
agent = MemAgent.load(agent_id, memory_provider=provider)
# Alice's turn
agent.run("Remember that my favorite color is purple.", user_id="alice")
# Bob's turn — same agent, isolated memory
agent.run("What's my favorite color?", user_id="bob")
# → Bob's response will NOT reference Alice's purple
The same parameter is accepted by run_stream():
Isolation semantics¶
The framework guarantees the following:
-
Writes carry
user_id. Every memory unit created during a turn — conversation history, summaries, entity memory, workflow steps, tool logs, semantic cache entries — is tagged with theuser_idpassed intorun(). -
Reads are strictly scoped. When a read is issued for
user_id=X, the provider only returns rows whose storeduser_id == X. There is no "match everything" fallback on the public API. -
Noneis a separate agent scope. Passinguser_id=None(or omitting it) torun(),run_stream(), or other agent APIs means "anonymous/legacy scope" — reads return only rows with nouser_id. Legacy data written before you adopted multi-tenant support does not silently leak into authenticated sessions.
Low-level provider read APIs that accept user_id use a three-state
administrative contract: omitting the keyword means unscoped, explicitly
passing None selects anonymous rows, and passing a string selects that
tenant. Never omit the provider filter in a request-serving path; the unscoped
form exists for migration, audit, and maintenance jobs. Agent internals always
pass their scope explicitly.
What is NOT scoped by user_id¶
user_id scopes per-user memory. It does not scope:
MemAgentModel— agent definitions are shared app configuration.- Personas, toolbox entries, MCP server configs, skills.
SHARED_MEMORY— intentionally cross-agent/cross-tenant.- Automation jobs — these are operator-level schedules.
If your use case needs per-user personas or per-user automations, that is a separate feature; open an issue before relying on either.
Provider behavior¶
| Provider | Tenant scoping |
|---|---|
| Oracle | Server-side filter pushed into every read path, including vector search. Requires running the migrations/001_add_user_id.sql migration on existing schemas. |
| MongoDB | Server-side filter on every find() and on the Atlas $vectorSearch.filter block. Add user_id to your Atlas vector index definitions for the conversation, summaries, semantic cache, workflow, and entity collections. |
| Filesystem | Strict client-side filter in every read method. The local index.json files now include user_id so enumeration stays cheap. |
Oracle migration (required for existing installs)¶
Run once, as the Memorizz schema owner:
sqlplus MEMORIZZ/<password>@<service> \
@src/memorizz/memory_provider/oracle/migrations/001_add_user_id.sql
The migration is additive: all new columns are nullable and existing rows
default to user_id = NULL (the legacy/anonymous scope), so the framework
remains backwards-compatible with data written before the upgrade.
MongoDB Atlas vector index update¶
If you rely on MongoDB Atlas vector search, add user_id as a filterable
field to the existing indexes on:
conversation_memorysummariessemantic_cacheworkflow_memoryentity_memory
Without this change, $vectorSearch queries that include a user_id filter
will error or silently return empty result sets.
Local UI and user_id¶
The bundled local UI (memorizz ui) is a single-operator developer tool.
If your production app embeds the UI and proxies requests server-side, the
POST /agents/{agent_id}/run and POST /agents/{agent_id}/playground/stream
endpoints accept an optional user_id form field that is forwarded to
MemAgent.run / run_stream. The UI itself does not surface a per-request
user_id input — exposing one on a localhost dev tool would be misleading
theater rather than real tenancy.
Checklist for multi-tenant deployments¶
- [ ] Run the Oracle migration (if using Oracle).
- [ ] Update Atlas vector indexes to mark
user_idfilterable (if using MongoDB). - [ ] Always pass
user_idthroughrun()/run_stream()at every request. - [ ] Never mix
user_id=Nonetraffic with authenticated traffic on the same agent instance — the two scopes are correctly isolated, but mixing makes auditing harder. - [ ] When deleting a user, query all user-scoped memory types for that
user_idand delete explicitly. A framework-leveldelete_user_data()cascade is intentionally not provided yet (cascade semantics for shared agents, personas, and automations are deployment-specific).