How to Configure memU Memory Service for Multi-User Scenarios with user_id Scoping
Configure memU for multi-user isolation by passing a custom UserConfig with a Pydantic model to MemoryService, then scope every operation using the where filter (or user dict for memorize) containing valid user_id fields.
The NevaMind-AI/memU repository provides a Python memory service that treats user isolation as a core architectural feature. By leveraging the UserConfig model and consistent user_id scoping, you can deploy a single MemoryService instance that securely partitions data across unlimited users without manual filtering logic.
Understanding memU's User-Scoped Architecture
memU implements multi-user isolation through a configurable user model defined in src/memu/app/settings.py. The default DefaultUserModel contains a single optional field:
class DefaultUserModel(BaseModel):
user_id: str | None = None
When you instantiate MemoryService, it stores this model as self.user_model = self.user_config.model (see src/memu/app/service.py lines 59-64). Every subsequent memory operation validates incoming filters against this schema, ensuring type safety and preventing scoping errors.
The service enforces isolation through a where dictionary that must contain fields matching your user model. This filter propagates through all CRUD operations, vector searches, and retrieval calls, automatically partitioning the underlying data store per user.
Configuring the User Model for Multi-User Isolation
To enable strict multi-user scoping, define a custom Pydantic model with user_id as a required field and pass it to MemoryService:
from pydantic import BaseModel, Field
from memu.app import MemoryService
from memu.app.settings import UserConfig
class StrictUserModel(BaseModel):
user_id: str = Field(..., description="Unique user identifier")
user_cfg = UserConfig(model=StrictUserModel)
service = MemoryService(user_config=user_cfg)
This configuration makes user_id mandatory for all operations. If you omit the user_config parameter, memU defaults to DefaultUserModel where user_id remains optional.
Scoping Memory Operations with user_id
Once configured, every memory operation requires a scope filter. The memorize method accepts a user dictionary, while retrieval and CRUD methods accept a where dictionary:
# Store memory for a specific user
await service.memorize(
resource_url="conversation.txt",
modality="conversation",
user={"user_id": "alice"}
)
# Retrieve only alice's memories
results = await service.retrieve(
queries=[{"role": "user", "content": {"text": "What are my preferences?"}}],
where={"user_id": "alice"}
)
As implemented in tests/test_inmemory.py and demonstrated in examples/example_1_conversation_memory.py, consistently passing the user_id ensures complete data isolation.
Extending Scope Beyond user_id for Complex Multi-Tenant Patterns
For scenarios requiring finer granularity—such as per-agent or per-session isolation—extend the user model with additional fields:
class MultiScopeModel(BaseModel):
user_id: str = Field(..., description="Primary user")
agent_id: str | None = None
session_id: str | None = None
service = MemoryService(user_config=UserConfig(model=MultiScopeModel))
# Store with multiple scope levels
await service.memorize(
resource_url="chat.log",
modality="conversation",
user={"user_id": "bob", "agent_id": "assistant-1", "session_id": "sess-42"}
)
# Query specific agent-session combination
memories = await service.retrieve(
queries=[{"role": "user", "content": {"text": "Previous context"}}],
where={"user_id": "bob", "agent_id": "assistant-1"}
)
All fields defined in your model become valid keys for the where filter, enabling flexible multi-tenant architectures.
How Data Isolation Works Under the Hood
memU enforces scoping through three layers in the codebase:
-
Validation layer: The
_normalize_wherefunction insrc/memu/app/retrieve.py(lines 87-104) validates that all keys in thewheredict exist inself.user_model. It raisesValueErrorimmediately if you supply an undefined field, preventing malformed queries. -
Filtering layer: The
matches_wherefunction insrc/memu/database/inmemory/repositories/filter.pyperforms the actual equality matching between thewherefilter and stored memory item metadata. -
Repository layer: All CRUD and search methods in
src/memu/database/inmemory/repositories/memory_item_repo.py(such aslist_itemsandvector_search_items) invokematches_whereto ensure only matching user-scope records are returned.
This architecture guarantees that even if you accidentally omit the where filter in application code, the service validates against the model schema, and the repository layer filters all database hits.
Summary
- Define strict scoping by creating a Pydantic model with required
user_idfields and passing it viaUserConfigtoMemoryService. - Scope every operation using the
userparameter formemorizeandwhereparameter forretrieve,list_memory_items, anddelete_memory_items. - Extend isolation by adding optional fields like
agent_idorsession_idto your user model for multi-dimensional partitioning. - Rely on built-in validation via
_normalize_whereandmatches_whereto prevent data leakage without writing custom filter logic.
Frequently Asked Questions
What happens if I forget to include the user_id filter in a query?
If you configured MemoryService with a model that requires user_id, the _normalize_where validator in src/memu/app/retrieve.py raises a ValueError when it detects missing required fields. If using the default optional model, omitting user_id returns unscoped results from the repository layer, effectively exposing all users' data—always configure a strict model for production multi-user deployments.
Can I scope by multiple identifiers like tenant_id and user_id simultaneously?
Yes. Extend your Pydantic model to include any combination of scope fields (e.g., tenant_id, organization_id, user_id). Pass all required identifiers in the where dict for retrieval operations or the user dict for memorize. The repository layer in memory_item_repo.py enforces equality matching on all provided fields.
How does memU prevent accidental data leakage between users?
The service prevents leakage through schema validation and automatic filtering. The where dict keys are validated against your user model at runtime, and the matches_where function ensures repository queries only return memory items where the stored metadata exactly matches the provided scope values. No cross-user data appears in result sets unless explicitly requested with a broad where filter.
Does user_id scoping work with persistent database backends?
Yes. While the examples reference the in-memory repository (src/memu/database/inmemory/repositories/memory_item_repo.py), the scoping mechanism relies on the abstract repository interface. Any storage backend implementing list_items, vector_search_items, and matches_where respects the where filtering logic, ensuring consistent multi-user isolation across SQLite, PostgreSQL, or vector database implementations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →