Bring Your Own Memory Provider¶
Implement a custom provider when memory must live in an existing data platform.
MemoryProvider is a behavioral contract, not only a CRUD interface: tenant
filtering, ordering, stable identifiers, JSON fidelity, and lifecycle behavior
must match the first-party providers.
Required surface¶
A concrete subclass implements these abstract methods:
| Area | Methods |
|---|---|
| Records | store, retrieve_by_query, retrieve_by_id, retrieve_by_name |
| Mutation | update_by_id, delete_by_id, delete_by_name, delete_all |
| Listing/history | list_all, retrieve_conversation_history_ordered_by_timestamp |
| Agent definitions | store_memagent, delete_memagent, update_memagent_memory_ids, delete_memagent_memory_ids, list_memagents |
| Lifecycle | close |
Read the live signatures in the Python API reference. Optional base implementations cover bounded observability queries and semantic cache invalidation; database providers should override them with indexed, provider-native operations.
Minimal shape¶
from typing import Any
from memorizz import MemoryProvider
_USER_FILTER_UNSET = object()
class PostgresProvider(MemoryProvider):
def __init__(self, config: dict[str, Any]):
self.config = dict(config)
self.connection = open_application_connection(self.config)
def list_all(
self,
memory_store_type: str,
user_id: Any = _USER_FILTER_UNSET,
) -> list[dict[str, Any]]:
filters: dict[str, Any] = {"memory_type": str(memory_store_type)}
if user_id is not _USER_FILTER_UNSET:
filters["user_id"] = user_id
return self._select(filters=filters)
def retrieve_by_query(
self,
query: dict[str, Any] | str,
memory_store_type: str | None = None,
limit: int = 1,
memory_id: str | None = None,
memory_type: str | None = None,
**kwargs: Any,
) -> list[dict[str, Any]]:
resolved_type = memory_type or memory_store_type
user_supplied = "user_id" in kwargs
user_id = kwargs.get("user_id")
return self._scoped_search(
query=query,
memory_type=resolved_type,
memory_id=memory_id,
user_supplied=user_supplied,
user_id=user_id,
limit=max(1, int(limit)),
)
This excerpt demonstrates scope handling; it is intentionally not a complete provider. Do not copy it without implementing every abstract method, resource cleanup, transactions, indexes, serialization, and error mapping.
Three-state tenant filters¶
For a vector-only role behind another document provider, see the optional
NotionProvider composition contract.
Existing custom providers need not implement it unless they opt in with
vector_store=True. Document providers can advertise manages_embeddings=True
to avoid duplicate helper embeddings, and requires_live_read=True when local
semantic-cache hits must not bypass current source permissions/content.
Provider reads use three distinct states:
| Call | Meaning |
|---|---|
omit user_id |
Administrative/unscoped read |
pass user_id=None |
Only anonymous/legacy records |
| pass a string | Only that exact tenant |
Use your own private sentinel; do not import Memorizz's private _UNSET
object. For semantic retrieval, apply tenant, agent, toolbox, status, and other
authorization filters before vector top-k selection. Post-filtering a
global top-k can return too few valid results and can expose cross-tenant
metadata.
Record fidelity¶
- Preserve
_id/idas a stable string and do not silently regenerate it on update. - Round-trip
memory_id,user_id,agent_id,thread_id,memory_type, timestamps, metadata, and embeddings when present. - Include
memory_idanduser_idon every semantic-retrieval result. MemAgent rechecks both fields after the provider call and discards rows that omit or mismatch the authenticated scope before prompt assembly. - Store complete toolbox JSON Schemas, including
required, defaults, enums, nested types, andadditionalProperties. - Preserve summary provenance and source-message markers so expansion is lossless.
- Return JSON-serializable dictionaries and normalize database-native IDs, LOBs, timestamps, and vectors.
- Keep ordered history deterministic and enforce
limitafter exact scope filtering.
Production extensions¶
Consider provider-native implementations for:
- atomic summary creation plus source-message marking;
- scoped observability pages and cursors;
- domain/version semantic-cache invalidation;
- pre-filtered toolbox and Skillbox retrieval;
- transactional scope cleanup with per-store counts;
preflight()reporting schema, permissions, vector dimensions, indexes, and exact-search fallback state.
Conformance checklist¶
- Run the same CRUD, history, cache, summary, agent-persistence, and observability tests used for the filesystem provider.
- Test omitted,
None, and concreteuser_idvalues for every public read. - Test two tenants with identical content and embeddings.
- Test restart/rebind behavior for saved agents and trusted tools.
- Test concurrent writers, transaction rollback, connection loss, and idempotent close.
- Compare
capability_report()and any providerpreflight()result in the target deployment.
Attach the finished provider with
MemAgentBuilder().with_memory_provider(provider) and persist one agent before
checking SDK, CLI, UI, and MCP discovery against the same backend.