Skip to main content
doc0blade47/mem0documentation home
Sign in

Mem0 Strands

Last updated Oct 11, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

The mem0-strands integration provides persistent long-term memory for Strands agents, bridging the framework's MemoryStore abstraction with Mem0 backend capabilities. By plugging directly into the agent loop via MemoryManager, it handles cross-session recall and automated memory injection and extraction without requiring explicit model-called tool invocations.

Sources: integrations/mem0-strands/python/src/mem0_strands/init.py:1-11, integrations/mem0-strands/python/src/mem0_strands/store.py:1-10

Strands Package Architecture and Package Setup

Package Structure and Build Configuration

The mem0-strands package is structured around the hatchling build backend, defining its core metadata, dependencies, and development environments inside pyproject.toml. The package requires Python >=3.10 and licenses under Apache-2.0.

Sources: integrations/mem0-strands/python/pyproject.toml:1-25

The package relies on strands-agents>=1.45.0 for the strands.memory module, which introduces the MemoryStore and MemoryManager abstractions, alongside mem0ai>=2.0.11 for memory management functionality.

# strands-agents>=1.45.0: first release shipping the `strands.memory` module
# (MemoryStore / MemoryManager) that Mem0MemoryStore implements.
dependencies = [
    "strands-agents>=1.45.0",
    "mem0ai>=2.0.11",
]

Sources: integrations/mem0-strands/python/pyproject.toml:27-32

Note

strands-agents>=1.45.0 is a strict dependency because it is the first release shipping the strands.memory module that Mem0MemoryStore implements.

Sources: integrations/mem0-strands/python/pyproject.toml:27-28

High-Level Module Exports

The package root exposes key integration classes through __all__, allowing direct imports from mem0_strands. Version resolution is handled dynamically using importlib.metadata, falling back to 0.0.0+unknown if the package metadata is missing in source tree executions.

from importlib.metadata import PackageNotFoundError, version
 
from mem0_strands.client import Mem0ServiceClient
from mem0_strands.store import Mem0MemoryStore
 
__all__ = [
    "Mem0MemoryStore",
    "Mem0ServiceClient",
]
 
try:
    __version__ = version("mem0-strands")
except PackageNotFoundError:  # pragma: no cover - only when running from a source tree
    __version__ = "0.0.0+unknown"

Sources: integrations/mem0-strands/python/src/mem0_strands/init.py:13-26

Mem0 Memory Store Implementation

Initialization and Entity Scoping

Mem0MemoryStore extends the Strands MemoryStore abstraction to provide cross-session recall and automated memory synchronization. During instantiation, the store validates that at least one entity scope field is provided among user_id, agent_id, run_id, or app_id.

Scope FieldDescriptionSources
user_idMem0 user namespace that owns the memoriesintegrations/mem0-strands/python/src/mem0_strands/store.py:66-84
agent_idMem0 agent namespaceintegrations/mem0-strands/python/src/mem0_strands/store.py:67-85
run_idMem0 run/session namespaceintegrations/mem0-strands/python/src/mem0_strands/store.py:68-86
app_idMem0 app namespace (platform only)integrations/mem0-strands/python/src/mem0_strands/store.py:69-87

Sources: integrations/mem0-strands/python/src/mem0_strands/store.py:50-124

Caution

The app_id scope is restricted to the Mem0 platform. Attempting to combine app_id with a self-hosted configuration dictionary via config triggers a ValueError during initialization because self-hosted Mem0 instances do not support app-level scoping on write operations.

Sources: integrations/mem0-strands/python/src/mem0_strands/store.py:115-123

Client Management and Search Execution

The underlying Mem0ServiceClient is constructed lazily on first access. Because initializing the client can block the event loop—such as validating platform API keys over HTTP or building vector stores and embedders for self-hosted instances—client resolution and initial telemetry recording execute inside worker threads via asyncio.to_thread.

@property
def client(self) -> Mem0ServiceClient:
    """The Mem0 service client, constructed lazily on first use.
 
    Note: constructing the underlying SDK client can block (the platform client
    validates the API key over HTTP; the OSS client builds embedders / vector
    stores), so first use is deferred and always happens inside a worker thread
    via :func:`asyncio.to_thread`, never on the event loop.
    """
    client_injected = self._client is not None
    if self._client is None:
        self._client = Mem0ServiceClient(api_key=self._api_key, host=self._host, config=self._config)
    if not self._recorded_init:
        self._recorded_init = True
        telemetry.record(
            "store.init",
            self._client,
            scopes=sorted(self.scope),
            writable=self.writable,
            extraction_enabled=bool(self.extraction),
            has_default_metadata=bool(self.metadata),
            max_search_results=self.max_search_results,
            host_overridden=bool(self._host),
            client_injected=client_injected,
        )
    return self._client

Sources: integrations/mem0-strands/python/src/mem0_strands/store.py:141-166

Semantic search queries retrieve matching entries ordered by relevance. The search method resolves the maximum result limit by checking search options, falling back to the store's default configuration, or defaulting to 5 (DEFAULT_MAX_SEARCH_RESULTS).

DEFAULT_MAX_SEARCH_RESULTS = 5

Sources: integrations/mem0-strands/python/src/mem0_strands/store.py:48-48

async def search(self, query: str, options: SearchOptions | None = None) -> list[MemoryEntry]:
    """Search Mem0 for entries matching ``query``, ordered by relevance."""
    top_k = options.get("max_search_results") if options is not None else None
    if top_k is None:
        top_k = self.max_search_results
    if top_k is None:
        top_k = DEFAULT_MAX_SEARCH_RESULTS
 
    # ``self.client`` is resolved inside the thread so lazy construction (a
    # blocking call) does not run on the event loop.
    started = time.perf_counter()
    try:
        memories = await asyncio.to_thread(lambda: self.client.search_memories(query, self.scope, top_k))
    except Exception as exc:
        self._record("store.search", started, False, top_k=top_k, error_kind=telemetry.error_kind(exc))
        raise
    self._record("store.search", started, True, top_k=top_k, result_count=len(memories))
    return [self._to_entry(memory) for memory in memories]
 

Sources: integrations/mem0-strands/python/src/mem0_strands/store.py:168-186

Service Client Wrapper and Client Abstractions

Backend Initialization and Resolution Strategy

The Mem0ServiceClient class acts as an adaptation layer bridging Mem0MemoryStore to either the hosted Mem0 platform client (mem0.MemoryClient) or the self-hosted OSS client (mem0.Memory). Instantiation follows a strict priority order based on the parameters passed by callers:

  • Pre-built client: Uses the provided client instance directly. Asynchronous client types are explicitly rejected during initialization, raising a ValueError because the store executes synchronous SDK operations inside worker threads via asyncio.to_thread.
  • Configuration config: Imports Memory from mem0 and constructs a self-hosted backend via Memory.from_config(config), setting is_platform to False.
  • Default hosted platform: Imports MemoryClient from mem0, resolves the API key from the explicit api_key argument or the MEM0_API_KEY environment variable, and initializes the platform client with an optional custom host.
def __init__(
    self,
    api_key: str | None = None,
    host: str | None = None,
    config: dict[str, Any] | None = None,
    client: Any | None = None,
) -> None:
    """Initialize the Mem0 client.
 
    Args:
        api_key: Mem0 platform API key. Falls back to ``$MEM0_API_KEY``.
        host: Mem0 platform base URL. Defaults to the SDK default
            (``https://api.mem0.ai``).
        config: A Mem0 OSS config dict; when given, a self-hosted
            :class:`mem0.Memory` is built instead of the platform client.
        client: A pre-built Mem0 client to use directly (platform or OSS).
 
    Raises:
        ValueError: If ``client`` is an async Mem0 client (its coroutines
            would never be awaited off the worker thread).
    """
    if client is not None:
        if _is_async_client(client):
            raise ValueError(
                "Async Mem0 clients are not supported. Pass a synchronous "
                "mem0.MemoryClient (or a mem0.Memory / config): the store runs the "
                "SDK in a worker thread, so an async client's coroutines would "
                "never be awaited and every write would silently no-op."
            )
        self.mem0 = client
        self.is_platform = _is_platform_client(client)
        return
 
    if config is not None:
        try:
            from mem0 import Memory
        except ImportError as err:  # pragma: no cover - exercised via install docs
            raise ImportError(
                "The mem0ai package is required. Install it with: pip install 'mem0-strands'"
            ) from err
        self.mem0 = Memory.from_config(config)
        self.is_platform = False
        return
 
    try:
        from mem0 import MemoryClient
    except ImportError as err:  # pragma: no cover - exercised via install docs
        raise ImportError("The mem0ai package is required. Install it with: pip install 'mem0-strands'") from err
    api_key = api_key or os.environ.get("MEM0_API_KEY")
    # MemoryClient(host=None) would override the SDK default with None, so only
    # pass host when the caller actually set one.
    self.mem0 = MemoryClient(api_key=api_key, host=host) if host else MemoryClient(api_key=api_key)
    self.is_platform = True
 

Sources: integrations/mem0-strands/python/src/mem0_strands/client.py:60-113

Warning

Passing an asynchronous Mem0 client such as AsyncMemoryClient triggers a ValueError during initialization. Because add and search on async clients are coroutine functions, running them inside asyncio.to_thread would return un-awaited coroutine objects, causing every memory write and search query to silently no-op.

Sources: integrations/mem0-strands/python/src/mem0_strands/client.py:24-28, integrations/mem0-strands/python/src/mem0_strands/client.py:81-89

Scope Validation and Write Adapters

Mem0ServiceClient validates entity scopes prior to executing storage or search requests against the underlying SDK. Specifically, the _check_scope method enforces backend capabilities by rejecting app_id usage when interacting with self-hosted OSS instances, as OSS Memory implementations lack support for app-level namespaces.

def _check_scope(self, scope: dict[str, str]) -> None:
    """Reject scope the selected backend cannot honor.
 
    ``app_id`` exists only on the platform; the OSS ``Memory`` API has no
    ``app_id`` parameter, so we fail loudly here rather than let it surface as
    a ``TypeError`` on ``add`` or silently miss on ``search``.
    """
    if not self.is_platform and "app_id" in scope:
        raise ValueError(
            "app_id is a Mem0 platform-only scope. The OSS backend supports "
            "user_id, agent_id, and run_id; drop app_id or use the platform client."
        )
 

Sources: integrations/mem0-strands/python/src/mem0_strands/client.py:114-126

Platform write operations automatically attach a telemetry source tag via _write_extras, appending {"source": "STRANDS"} to attribute generated memories correctly within the Mem0 backend allowlist. Self-hosted OSS writes omit this tag to prevent signature validation errors from unknown keyword arguments.

# Tags platform writes so Mem0's backend attributes the memory to this integration
# in telemetry (recognized values live in the backend's KNOWN_EVENT_SOURCES
# allowlist; unknown ones bucket into "OTHERS"). Platform only.
_SOURCE = "STRANDS"
 

Sources: integrations/mem0-strands/python/src/mem0_strands/client.py:30-34

def _write_extras(self) -> dict[str, str]:
    """Extra kwargs attached to platform writes: the telemetry ``source`` tag."""
    return {"source": _SOURCE} if self.is_platform else {}

Sources: integrations/mem0-strands/python/src/mem0_strands/client.py:127-129

The client exposes three core methods for data manipulation and retrieval:

MethodParametersDescriptionSources
store_memorycontent, scope, metadataStores a single discrete fact verbatim with infer=False and merged write extrasintegrations/mem0-strands/python/src/mem0_strands/client.py:131-144
store_messagesmessages, scopeSubmits rendered conversation turns for server-side extraction with infer=Trueintegrations/mem0-strands/python/src/mem0_strands/client.py:146-154
search_memoriesquery, scope, top_kPerforms semantic search wrapping scope filters and normalizes the payload via _extract_resultsintegrations/mem0-strands/python/src/mem0_strands/client.py:155-163

Sources: integrations/mem0-strands/python/src/mem0_strands/client.py:131-164

Search responses handle structural variations across Mem0 API versions by inspecting result payloads. The helper function _extract_results checks whether the response is a dictionary containing a "results" key or a bare list, returning a safe list of memory dictionaries in both cases.

def _extract_results(response: Any) -> list[dict[str, Any]]:
    """Normalize a Mem0 search response to a list of memory dicts.
 
    Mem0 returns ``{"results": [...]}`` (v1.1) or, on older paths, a bare list.
    """
    if isinstance(response, dict):
        results = response.get("results", [])
        return list(results) if isinstance(results, list) else []
    if isinstance(response, list):
        return response
    return []

Sources: integrations/mem0-strands/python/src/mem0_strands/client.py:166-176

Strands Memory Ingestion and Message Serialization

Message Ingestion and Extraction Sinks

Mem0MemoryStore provides dual write mechanisms for capturing conversation history: single-fact ingestion via add and batch message processing via add_messages. The add method writes a single distilled fact verbatim by passing infer=False to the underlying client, serving as the storage sink for explicit tools like add_memory. Conversely, add_messages takes raw conversation turns and routes them directly to Mem0 for server-side extraction with infer=True, eliminating client-side model overhead while leveraging server-side deduplication.

``Mem0MemoryStore`` implements both write sinks, which is what sets it apart from a
vector-DB-style store:
 
- :meth:`add` writes a single distilled fact verbatim (``infer=False``). This is
  the sink for the ``add_memory`` tool and for a client-side extractor.
- :meth:`add_messages` renders raw conversation turns to text and hands them to
  Mem0 for **server-side extraction** (``infer=True``). Because this sink exists,
  enabling ``extraction`` routes messages straight to Mem0's own extraction
  pipeline -- no extra client-side model call, and Mem0's de-duplication applies.
 

Sources: integrations/mem0-strands/python/src/mem0_strands/store.py:10-19

Note

Extraction writes using add_messages operate with at-least-once semantics, safely tolerating duplicate content submissions because Mem0 handles de-duplication natively on the server side.

Sources: integrations/mem0-strands/python/src/mem0_strands/store.py:187-191

Telemetry Integration and Usage Diagnostics

Anonymous Event Dispatch and Privacy Controls

The telemetry subsystem records anonymous usage diagnostics for the Strands memory store without introducing extra dependencies by leveraging the Mem0 SDK's built-in PostHog client. Events are dispatched via client_telemetry.capture_event under the namespace strands.* and include standard properties such as language, integration source, package version, and backend type.

def record(event: str, client: Mem0ServiceClient, **properties: Any) -> None:
    """Send one strands.* usage event. Never raises."""
    try:
        client_telemetry.capture_event(
            f"strands.{event}",
            {
                "source": SOURCE,
                "language": "python",
                "strands_store_version": _VERSION,
                "backend": "platform" if client.is_platform else "oss",
                **properties,
            },
            _distinct_id(client),
        )

Sources: integrations/mem0-strands/python/src/mem0_strands/telemetry.py:55-68

Warning

Telemetry collection strictly omits sensitive data. Captured properties never include queries, memory text, message content, entity identifiers, metadata dictionaries, or API keys.

Sources: integrations/mem0-strands/python/src/mem0_strands/telemetry.py:9-13

Users can completely opt out of anonymous usage tracking by setting the environment variable MEM0_TELEMETRY=false. Additionally, all telemetry dispatch calls are wrapped in exception-suppressing blocks so that telemetry failures never interrupt agent execution.

Opt out with MEM0_TELEMETRY=false.

Sources: integrations/mem0-strands/python/src/mem0_strands/telemetry.py:12-12

except Exception:
    pass

Sources: integrations/mem0-strands/python/src/mem0_strands/telemetry.py:69-70

Distinct Identity and Error Classification

Identity attribution adapts dynamically depending on whether the store connects to the hosted platform or a self-hosted backend. The helper function _distinct_id inspects the underlying client instance for a user_email attribute, falling back to None so the SDK automatically uses its machine-local anonymous identifier for OSS deployments.

Sources: integrations/mem0-strands/python/src/mem0_strands/telemetry.py:49-53

Failures encountered during operations are categorized into content-free error kinds before being recorded in telemetry properties. The classifier inspects exception type names and string representations to map errors into standard diagnostic labels:

Sources: integrations/mem0-strands/python/src/mem0_strands/telemetry.py:33-46

Was this page helpful?