> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Memory Adapters

> Plug in your own memory backend with the adapter registry

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, MemoryConfig

agent = Agent(
    name="memory-agent",
    instructions="Remember facts across turns.",
    memory=MemoryConfig(provider="my_backend"),
)
agent.start("Store that my favourite colour is teal.")
```

PraisonAI's memory backends are pluggable — register your own adapter to store agent memory in any system.

The user chats with the agent; your custom memory adapter persists and retrieves facts from your chosen backend.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    A[Agent] --> B["memory='my_backend'"]
    B --> C[Registry lookup]
    C --> D[Custom Adapter]
    D --> E[Storage]

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef config fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef registry fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef adapter fill:#10B981,stroke:#7C90A0,color:#fff
    classDef storage fill:#189AB4,stroke:#7C90A0,color:#fff

    class A agent
    class B config
    class C registry
    class D adapter
    class E storage

    classDef tool fill:#189AB4,color:#fff

    classDef agent fill:#8B0000,color:#fff
```

## Quick Start

<Steps>
  <Step title="Simple Usage">
    Use a built-in adapter via string to anchor the mental model:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    agent = Agent(
        name="assistant",
        instructions="Remember user preferences",
        memory="in_memory",  # Ephemeral dict-backed storage
    )
    agent.start("My favourite colour is blue")
    ```
  </Step>

  <Step title="With a Custom Adapter">
    Register your adapter, then point the agent at it:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from typing import Any, Dict, List, Optional
    from praisonaiagents import Agent, add_memory_adapter


    class RedisMemoryAdapter:
        def __init__(self, **kwargs):
            self._store: List[Dict[str, Any]] = []

        def store_short_term(self, text: str, metadata: Optional[Dict[str, Any]] = None, **kwargs) -> str:
            doc_id = str(len(self._store))
            self._store.append({"id": doc_id, "text": text, "metadata": metadata or {}})
            return doc_id

        def search_short_term(
            self,
            query: str,
            limit: int = 5,
            metadata_filter: Optional[Dict[str, Any]] = None,
            user_id: Optional[str] = None,
            **kwargs,
        ) -> List[Dict[str, Any]]:
            hits = [e for e in self._store if query.lower() in e["text"].lower()]
            if user_id is not None:
                hits = [e for e in hits if e["metadata"].get("user_id") == user_id]
            if metadata_filter:
                hits = [e for e in hits if all(e["metadata"].get(k) == v for k, v in metadata_filter.items())]
            return hits[:limit]

        def store_long_term(self, text: str, metadata: Optional[Dict[str, Any]] = None, **kwargs) -> str:
            return self.store_short_term(text, metadata, **kwargs)

        def search_long_term(
            self,
            query: str,
            limit: int = 5,
            metadata_filter: Optional[Dict[str, Any]] = None,
            user_id: Optional[str] = None,
            **kwargs,
        ) -> List[Dict[str, Any]]:
            return self.search_short_term(query, limit, metadata_filter=metadata_filter, user_id=user_id, **kwargs)

        def get_all_memories(self, **kwargs) -> List[Dict[str, Any]]:
            return list(self._store)


    add_memory_adapter("redis", RedisMemoryAdapter)

    agent = Agent(
        name="assistant",
        memory={"provider": "redis"},
    )
    ```
  </Step>
</Steps>

## The Adapter Is the Single Source of Truth

<Note>
  As of PR #3515, a configured adapter handles **store, search, reset, and delete** for every provider — not just `sqlite` / `in_memory`. Data written through a registered adapter (e.g. `dakera`) is now findable, resettable, and deletable through that same adapter. No `sqlite` fallback or dual-write pattern is needed.
</Note>

Previously, `search`, `reset`, and `delete` only delegated to the adapter when the provider was hardcoded `sqlite` or `in_memory`. Any other registered adapter stored data correctly but reads hit the empty legacy tables and returned `[]` — memory was effectively write-only. All four operation paths now gate symmetrically on whether an adapter is configured.

Round-trip through a custom adapter — store then find:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(
    name="Assistant",
    instructions="Remember what the user tells you.",
    memory={"provider": "dakera"},  # or any registered adapter
)

agent.start("Remember that my project deadline is Friday.")
agent.start("What did I tell you about my deadline?")  # now finds it
```

<Warning>
  Memory-storage failures now surface as explicit errors when an adapter is configured. A failed long-term store raises `RuntimeError` instead of silently dropping the write to a schema-incompatible legacy table. Wrap `agent.start()` when using a custom adapter that may reject writes.
</Warning>

## How It Works

When `Memory` initialises, it resolves the provider through the adapter registry — the only code path for backend setup since PR #2060 removed orphaned legacy `_init_*` methods.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Memory
    participant Registry
    participant Adapter
    participant Storage

    User->>Agent: memory={"provider": "sqlite"}
    Agent->>Memory: __init__(config)
    Memory->>Registry: get_memory_adapter(name, **config)
    Registry->>Adapter: instantiate
    Adapter->>Storage: connect / prepare
    Memory->>Memory: copy adapter attrs (backward-compat shim)
    Memory-->>Agent: ready
```

## Every operation routes through the adapter

Once you register an adapter (e.g. `add_memory_adapter("dakera", DakeraAdapter)`) and set `provider="dakera"` — or pass the adapter directly — **every read and every write** routes through it: `store_*`, `search_*`, `reset_*`, and `delete_*`. There is no `provider in {sqlite, in_memory}` gate.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Configured adapter"
        S[store_*] --> AD[memory_adapter]
        Q[search_*] --> AD
        R[reset_*] --> AD
        D[delete_*] --> AD
    end

    classDef op fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef adapter fill:#10B981,stroke:#7C90A0,color:#fff

    class S,Q,R,D op
    class AD adapter
```

Before this became symmetric, a registered adapter's writes went to its own table while every `search_*` looked at the legacy `long_mem` table the adapter never created — so searches returned `[]` and resets/deletes no-op'd. Store, search, reset, and delete now share one source of truth. The dedicated `chroma` / `mem0` / `mongodb` write paths still take priority for those providers.

<Warning>
  An adapter that implements `store_long_term` **must** implement `search_long_term` (and, for reset/delete to work, `reset_short_term` / `reset_long_term` / `delete_memory(memory_id, tier=...)`). If a store succeeds but the adapter store fails, `store_long_term` now raises `RuntimeError("Long-term store failed via memory adapter; the legacy long_mem table is not schema-compatible.")` — a surfaced failure rather than the previous silent drop.
</Warning>

## Built-in Adapters

| Adapter               | Registry name | Backend           | When to use                              |
| --------------------- | ------------- | ----------------- | ---------------------------------------- |
| `SqliteMemoryAdapter` | `"sqlite"`    | SQLite file       | Default persistent local storage         |
| `InMemoryAdapter`     | `"in_memory"` | Python dict       | Tests, ephemeral workflows               |
| Factory               | `"chroma"`    | ChromaDB (lazy)   | Vector search, local RAG                 |
| Factory               | `"mongodb"`   | MongoDB (lazy)    | Document store, Atlas Vector Search      |
| Factory               | `"mem0"`      | Mem0 cloud (lazy) | Managed graph / cloud memory             |
| Factory               | `"dakera"`    | Dakera (lazy)     | Self-hosted decay-weighted vector memory |

Heavy backends register as **factories** in `praisonaiagents.memory.adapters.factories` so optional dependencies load only when requested.

**Adapter operations (as of PR #3515):** store ✅ · search ✅ · reset ✅ · delete ✅ — every operation routes through the configured adapter, for any provider, not just `sqlite` / `in_memory`.

### Reset Capability

`Memory.reset_short_term()` / `.reset_long_term()` delegate to the active adapter. Adapters that don't implement the method log a warning stating the tier was NOT cleared.

| Adapter                         | `reset_short_term` | `reset_long_term` | Notes                                                         |
| ------------------------------- | ------------------ | ----------------- | ------------------------------------------------------------- |
| `ChromaMemoryAdapter` (default) | ✅                  | ✅                 | STM + LTM share one collection — resetting either clears both |
| `DakeraMemoryAdapter`           | ✅                  | ✅                 | Tier-scoped (`working` / `episodic`)                          |
| `Mem0MemoryAdapter`             | ❌ (logs warning)   | ❌ (logs warning)  |                                                               |
| `MongoDBMemoryAdapter`          | ❌ (logs warning)   | ❌ (logs warning)  |                                                               |

<Note>
  **Since PraisonAI PR [#4020](https://github.com/MervinPraison/PraisonAI/pull/4020)**, the default Chroma adapter supports reset, and unsupported adapters warn instead of silently succeeding.
</Note>

## Register Your Own Adapter

Implement `MemoryProtocol` — at minimum: `store_short_term`, `search_short_term`, `store_long_term`, `search_long_term`, and `get_all_memories`.

<Note>
  As of PR #3855, `search_short_term` / `search_long_term` accept `metadata_filter` and `user_id` as first-class kwargs. Add both to your adapter signature so the class-body methods can delegate scoping to it — `user_id` takes precedence over any `metadata_filter["user_id"]`, keeping tenant isolation intact.
</Note>

<Note>
  As of [PraisonAI PR #4819](https://github.com/MervinPraison/PraisonAI/pull/4819), even if a custom adapter's own query ignores `user_id`, the `SearchMixin` layer still filters matching rows against the passed `user_id` (defense-in-depth) — so custom adapters cannot accidentally leak cross-tenant rows through the search API.
</Note>

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import add_memory_adapter, get_memory_adapter

add_memory_adapter("my_backend", MyAdapter)
adapter = get_memory_adapter("my_backend")
```

<Note>
  `add_memory_adapter` and `register_memory_adapter` are synonyms; both work. `add_memory_adapter` is the canonical name per the SDK naming convention (`add_X` = register something).
</Note>

Then use it from an agent:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
agent = Agent(memory={"provider": "my_backend"})
```

## Registry API at a Glance

| Function                             | Purpose                            | Alias                     |
| ------------------------------------ | ---------------------------------- | ------------------------- |
| `add_memory_adapter(name, cls)`      | Register an adapter class          | `register_memory_adapter` |
| `add_memory_factory(name, fn)`       | Register a lazy factory function   | `register_memory_factory` |
| `get_memory_adapter(name, **kwargs)` | Instantiate a registered adapter   | —                         |
| `has_memory_adapter(name)`           | Check whether a name is registered | —                         |
| `list_memory_adapters()`             | List all registered names          | —                         |

## Common Patterns

**Async adapter** — implement `AsyncMemoryProtocol` (`astore_short_term`, `asearch_short_term`, etc.) when your backend is async-native.

**Lazy-loaded heavy backend** — use `add_memory_factory(name, create_fn)` (or its synonym `register_memory_factory`) so imports like `chromadb` or `pymongo` happen inside the factory, not at package import time.

**Extending an existing adapter** — subclass `SqliteMemoryAdapter` or wrap `InMemoryAdapter` and register under a new name.

**Inspect the registry** — check what's registered before wiring an agent:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import has_memory_adapter, list_memory_adapters, add_memory_adapter

if not has_memory_adapter("redis"):
    add_memory_adapter("redis", RedisMemoryAdapter)

print(list_memory_adapters())  # ['sqlite', 'in_memory', 'mem0', 'chroma', 'mongodb', 'dakera', 'redis']
```

## Best Practices

<AccordionGroup>
  <Accordion title="Prefer the registry over patching Memory">
    Register adapters with `add_memory_adapter` or `add_memory_factory` instead of monkey-patching `Memory` internals.
  </Accordion>

  <Accordion title="Implement sync and async when possible">
    Sync methods satisfy `MemoryProtocol`; add `AsyncMemoryProtocol` methods if your store supports non-blocking I/O.
  </Accordion>

  <Accordion title="Close connections in .close()">
    Implement `close()` on adapters that hold clients (MongoDB, ChromaDB). `Session.close()` calls `memory.close_connections()` which forwards to the adapter.
  </Accordion>

  <Accordion title="List registered adapters at runtime">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import list_memory_adapters
    print(list_memory_adapters())  # ['sqlite', 'in_memory', 'mem0', 'chroma', 'mongodb', 'dakera', ...]
    ```
  </Accordion>

  <Accordion title="Make mutating methods thread-safe for async callers">
    Task-callback and knowledge-search paths dispatch writes to memory adapters via `asyncio.to_thread`. If your adapter will be shared by multiple async agents — or multiple parallel tasks batched with `asyncio.gather(...)` — guard its mutating methods with a lock. The built-in `InMemoryAdapter` uses an `RLock` for exactly this reason. See [Async Safety](/docs/features/async-safety).

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import threading

    class MyAdapter:
        def __init__(self):
            self._lock = threading.RLock()
            self._store = {}

        def store_long_term(self, memory_id, content, **kwargs):
            with self._lock:
                self._store[memory_id] = content
    ```
  </Accordion>
</AccordionGroup>

## Related

<CardGroup cols={2}>
  <Card title="Memory Concepts" icon="brain" href="/docs/features/advanced-memory">
    Provider strings, short-term vs long-term, and configuration basics.
  </Card>

  <Card title="Memory Cleanup" icon="broom" href="/docs/best-practices/memory-cleanup">
    Session teardown and adapter `close()` lifecycle.
  </Card>

  <Card title="MongoDB Memory" icon="database" href="/docs/features/mongodb-memory">
    Atlas Vector Search and `use_vector_search` configuration.
  </Card>

  <Card title="Dakera Memory" icon="database" href="/docs/features/dakera-memory">
    Self-hosted, decay-weighted vector recall via the Dakera server.
  </Card>

  <Card title="Memory Troubleshooting" icon="triangle-exclamation" href="/docs/features/memory-troubleshooting">
    ImportError and fallback behaviour when providers are missing.
  </Card>
</CardGroup>
