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_where function in src/memu/app/retrieve.py (lines 87-104) validates that all keys in the where dict exist in self.user_model. It raises ValueError immediately if you supply an undefined field, preventing malformed queries.

  • Filtering layer: The matches_where function in src/memu/database/inmemory/repositories/filter.py performs the actual equality matching between the where filter and stored memory item metadata.

  • Repository layer: All CRUD and search methods in src/memu/database/inmemory/repositories/memory_item_repo.py (such as list_items and vector_search_items) invoke matches_where to 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_id fields and passing it via UserConfig to MemoryService.
  • Scope every operation using the user parameter for memorize and where parameter for retrieve, list_memory_items, and delete_memory_items.
  • Extend isolation by adding optional fields like agent_id or session_id to your user model for multi-dimensional partitioning.
  • Rely on built-in validation via _normalize_where and matches_where to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →