Skip to main content
Memproof uses an adapter pattern to decouple its policy and audit layer from the underlying memory storage. Every memory operation flows through the control path (risk, policy, events) before the adapter persists it.

Built-in Adapters

A simple dictionary-backed store for local development and testing. No external dependencies. Data is lost when the process exits.
from memproof import Memproof

mp = Memproof(policy="memproof.yaml", adapter="in_memory")

How Adapters Work

Each adapter translates five canonical operations to the backend:
Canonical MethodDescription
create_memory()Persist a new MemoryRecord
update_memory()Apply a partial patch to an existing record
delete_memory()Remove a record by ID
get_memory()Retrieve a single record by ID
search_memories()Run a scoped query, return ranked hits
Adapters also normalize backend errors into canonical error codes: NOT_FOUND, CONFLICT, VALIDATION_ERROR, PERMISSION_DENIED, and PROVIDER_UNAVAILABLE.

Writing a Custom Adapter

Subclass MemoryAdapter and implement all five abstract methods plus the provider_name property.

The Base Interface

from memproof.adapters.base import MemoryAdapter, AdapterError
from memproof.models.core import MemoryRecord, MemorySearchHit

class MemoryAdapter(ABC):
    @property
    @abstractmethod
    def provider_name(self) -> str: ...

    @abstractmethod
    async def create_memory(self, memory: MemoryRecord) -> MemoryRecord: ...

    @abstractmethod
    async def update_memory(self, memory_id: str, patch: dict) -> MemoryRecord: ...

    @abstractmethod
    async def delete_memory(self, memory_id: str) -> bool: ...

    @abstractmethod
    async def get_memory(self, memory_id: str) -> MemoryRecord | None: ...

    @abstractmethod
    async def search_memories(
        self, query: str, scope: dict, limit: int = 20,
        filters: dict | None = None,
    ) -> list[MemorySearchHit]: ...

Example: Redis Adapter

from memproof.adapters.base import MemoryAdapter, AdapterError
from memproof.models.core import MemoryRecord, MemorySearchHit

class RedisAdapter(MemoryAdapter):
    def __init__(self, redis_url: str = "redis://localhost:6379"):
        import redis.asyncio as redis
        self._client = redis.from_url(redis_url)

    @property
    def provider_name(self) -> str:
        return "redis"

    async def create_memory(self, memory: MemoryRecord) -> MemoryRecord:
        key = f"mem:{memory.memory_id}"
        if await self._client.exists(key):
            raise AdapterError("CONFLICT", f"{memory.memory_id} exists", self.provider_name)
        await self._client.set(key, memory.model_dump_json())
        return memory

    async def update_memory(self, memory_id: str, patch: dict) -> MemoryRecord:
        raw = await self._client.get(f"mem:{memory_id}")
        if not raw:
            raise AdapterError("NOT_FOUND", f"{memory_id} not found", self.provider_name)
        data = MemoryRecord.model_validate_json(raw).model_dump()
        for k, v in patch.items():
            if v is not None and k in data:
                data[k] = v
        updated = MemoryRecord(**data)
        await self._client.set(f"mem:{memory_id}", updated.model_dump_json())
        return updated

    async def delete_memory(self, memory_id: str) -> bool:
        result = await self._client.delete(f"mem:{memory_id}")
        if not result:
            raise AdapterError("NOT_FOUND", f"{memory_id} not found", self.provider_name)
        return True

    async def get_memory(self, memory_id: str) -> MemoryRecord | None:
        raw = await self._client.get(f"mem:{memory_id}")
        return MemoryRecord.model_validate_json(raw) if raw else None

    async def search_memories(
        self, query: str, scope: dict, limit: int = 20,
        filters: dict | None = None,
    ) -> list[MemorySearchHit]:
        # Implement using RediSearch or scan-based matching
        return []
Raise AdapterError with one of the canonical error codes. The orchestrator relies on these codes to decide whether to quarantine, retry, or surface the error.

Using a Custom Adapter with Memproof

Pass a pre-built MemproofConfig and override the adapter after construction, or contribute it upstream so it can be selected by name.
from memproof import Memproof
from memproof.config import MemproofConfig

config = MemproofConfig(policy_path="memproof.yaml", adapter="in_memory")
mp = Memproof(config=config)
mp._adapter = RedisAdapter(redis_url="redis://localhost:6379")
For production backends that require HTTP calls, follow the backend pattern used by LangGraphBackend, OpenAISessionsBackend, and MCPMemoryBackend — create a separate backend class and inject it into the adapter constructor.