# NousResearch/hermes-agent · Mem0 self-hosted/OSS mode support efforts

## Open now

- Nothing open on GitHub.

Multiple issues and pull requests addressing the addition or restoration of self-hosted (OSS) Mem0 support, including host configuration, v3 API migration, and related tooling updates. The group reflects ongoing, overlapping work toward enabling a remote or locally-hosted Mem0 endpoint.

## TL;DR

- 10 member(s): 0 open issue(s), 4 closed issue(s), 0 open PR(s), 3 merged PR(s), 3 PR(s) closed without merging.
- A fix was merged to main in PR #50479. Verify it addresses your case.
- **Recommended:** read PR #50479 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-56943`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.17`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #16401: \[Feature\]: feat(mem0) add OSS mode, v3 API migration, update/delete tools
- #31135: \[Feature\]: Proposal: Self-Hosted Mem0 as Memory Provider in Hermes
- #51894: \[Feature\]: Add support to use a remote Mem0 API Endpoint (self-hosted)
- #52478: Bug: mem0 plugin dropped host/self-hosted support after _backend.py refactor (#15624)

## Prior & competing PRs

- **#50479: fix(mem0): add self-hosted support via MEM0_HOST / host config**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `plugins/memory/mem0/README.md`, `plugins/memory/mem0/__init__.py`, `scripts/release.py`
  - fix hunks:
  - `plugins/memory/mem0/README.md`

    ```diff
    @@ -2,30 +2,45 @@
     
     Server-side LLM fact extraction with semantic search, reranking, and automatic deduplication.
     
    +Supports both [Mem0 Cloud](https://app.mem0.ai) and self-hosted instances.
    +
     ## Requirements
     
     - `pip install mem0ai`
    -- Mem0 API key from [app.mem0.ai](https://app.mem0.ai)
    +- Mem0 Cloud API key **or** a self-hosted Mem0 server
     
     ## Setup
     
    +### Cloud
    +
     ```bash
     hermes memory setup    # select "mem0"
     ```
     
     Or manually:
    +
     ```bash
     hermes config set memory.provider mem0
     echo "MEM0_API_KEY=[redacted:secret-assignment]" >> ~/.hermes/.env
     ```
     
    +### Self-Hosted
    +
    +```bash
    +hermes config set memory.provider mem0
    +echo "MEM0_HOST=http://your-mem0-server:24220" >> ~/.hermes/.env
    +echo "MEM0_API_KEY=[redacted:secret-assignment]" >> ~/.hermes/.env   # if auth is enabled
    +```
    +
     ## Config
     
     Config file: `$HERMES_HOME/mem0.json`
     
     | Key | Default | Description |
     |-----|---------|-------------|
    -| `user_id` | `hermes-user` | User identifier on Mem0 |
    +| `api_key` | — | API key (required for cloud; optional for self-hosted without auth) |
    +| `host` | `https://api.mem0.ai` | Self-hosted Mem0 URL. When set, overrides the cloud endpoint. |
    +| `user_id` | `hermes-user` | User identifier |
     | `agent_id` | `hermes` | Agent identifier |
     | `rerank` | `true` | Enable reranking for recall |
     
    ```
  - `plugins/memory/mem0/__init__.py`

    ```diff
    @@ -1,12 +1,13 @@
     """Mem0 memory plugin — MemoryProvider interface.
     
     Server-side LLM fact extraction, semantic search with reranking, and
    -automatic deduplication via the Mem0 Platform API.
    +automatic deduplication via the Mem0 Platform API or self-hosted instance.
     
     Original PR #2933 by kartik-mem0, adapted to MemoryProvider ABC.
     
     Config via environment variables:
    -  MEM0_API_KEY       — Mem0 Platform API key (required)
    +  MEM0_API_KEY       — Mem0 API key (required for cloud, optional for self-hosted)
    +  MEM0_HOST          — Self-hosted Mem0 URL (default: https://api.mem0.ai)
       MEM0_USER_ID       — User identifier (default: hermes-user)
       MEM0_AGENT_ID      — Agent identifier (default: hermes)
     
    ```

    ```diff
    @@ -37,6 +38,7 @@
     # Config
     # ---------------------------------------------------------------------------
     
    +
     def _load_config() -> dict:
         """Load config from env vars, with $HERMES_HOME/mem0.json overrides.
     
    ```

    ```diff
    @@ -48,6 +50,7 @@ def _load_config() -> dict:
     
         config = {
             "api_key": os.environ.get("MEM0_API_KEY", ""),
    +        "host": os.environ.get("MEM0_HOST", ""),
             "user_id": os.environ.get("MEM0_USER_ID", "hermes-user"),
             "agent_id": os.environ.get("MEM0_AGENT_ID", "hermes"),
             "rerank": True,
    ```

    ```diff
    @@ -117,13 +120,18 @@ def _load_config() -> dict:
     # ---------------------------------------------------------------------------
     
     class Mem0MemoryProvider(MemoryProvider):
    -    """Mem0 Platform memory with server-side extraction and semantic search."""
    +    """Mem0 memory with server-side extraction and semantic search.
    +
    +    Supports both Mem0 Cloud (api.mem0.ai) and self-hosted instances
    +    via the ``host`` config key or ``MEM0_HOST`` env var.
    +    """
     
         def __init__(self):
             self._config = None
             self._client = None
             self._client_lock = threading.Lock()
             self._api_key = ""
    +        self._host = ""
             self._user_id = "hermes-user"
             self._agent_id = "hermes"
             self._rerank = True
    ```

    ```diff
    @@ -141,7 +149,9 @@ def name(self) -> str:
     
         def is_available(self) -> bool:
             cfg = _load_config()
    -        return bool(cfg.get("api_key"))
    +        host = cfg.get("host", "")
    +        api_key = [redacted:secret-assignment]"api_key", "")
    +        return bool(host) or bool(api_key)
     
         def save_config(self, values, hermes_home):
             """Write config to $HERMES_HOME/mem0.json."""
    ```

    ```diff
    @@ -160,7 +170,8 @@ def save_config(self, values, hermes_home):
     
         def get_config_schema(self):
             return [
    -            {"key": "api_key", "description": "Mem0 Platform API key", "secret": True, "required": True, "env_var": "MEM0_API_KEY", "url": "https://app.mem0.ai"},
    +            {"key": "api_key", "description": "Mem0 API key (cloud or self-hosted)", "secret": True, "required": True, "env_var": "MEM0_API_KEY", "url": "https://app.mem0.ai"},
    +            {"key": "host", "description": "Self-hosted Mem0 URL (e.g. http://localhost:24220)", "default": "", "env_var": "MEM0_HOST"},
                 {"key": "user_id", "description": "User identifier", "default": "hermes-user"},
                 {"key": "agent_id", "description": "Agent identifier", "default": "hermes"},
                 {"key": "rerank", "description": "Enable reranking for recall", "default": "true", "choices": ["true", "false"]},
    ```

    ```diff
    @@ -173,7 +184,14 @@ def _get_client(self):
                     return self._client
                 try:
                     from mem0 import MemoryClient
    -                self._client = MemoryClient(api_key=[redacted:secret-assignment]
    +                kwargs = {}
    +                if self._host:
    +                    kwargs["host"] = self._host
    +                if self._api_key:
    +                    kwargs["api_key"] = self._api_key
    +                elif not self._host:
    +                    raise ValueError("Mem0: either api_key or host is required")
    +                self._client = MemoryClient(**kwargs)
                     return self._client
                 except ImportError:
                     raise RuntimeError("mem0 package not installed. Run: pip install mem0ai")
    ```

    ```diff
    @@ -204,6 +222,7 @@ def _record_failure(self):
         def initialize(self, session_id: str, **kwargs) -> None:
             self._config = _load_config()
             self._api_key = [redacted:secret-assignment]"api_key", "")
    +        self._host = self._config.get("host", "")
             # Prefer gateway-provided user_id for per-user memory scoping;
             # fall back to config/env default for CLI (single-user) sessions.
             self._user_id = kwargs.get("user_id") or self._config.get("user_id", "hermes-user")
    ```

    ```diff
    @@ -228,8 +247,9 @@ def _unwrap_results(response: Any) -> list:
             return []
     
         def system_prompt_block(self) -> str:
    +        target = self._host or "cloud"
             return (
    -            "# Mem0 Memory\n"
    +            f"# Mem0 Memory ({target})\n"
                 f"Active. User: {self._user_id}.\n"
                 "Use mem0_search to find memories, mem0_conclude to store facts, "
                 "mem0_profile for a full overview."
    ```
  - `scripts/release.py`

    ```diff
    @@ -123,6 +123,7 @@
         "[redacted:email]": "synapsesx",
         "[redacted:email]": "itsflownium",
         "[redacted:email]": "dirtyren",
    +    "[redacted:email]": "buihongduc132",
         "[redacted:email]": "EtherAura",  # PR #45205 salvage (Linux in-app update relaunch / GUI-skew terminal state)
         "[redacted:email]": "devorun",
         "[redacted:email]": "x7peeps",
    ```
- **#56943: feat(mem0): self-hosted dashboard backend + recall tuning (salvage #55614)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `plugins/memory/mem0/README.md`, `plugins/memory/mem0/__init__.py`, `plugins/memory/mem0/_backend.py`, `plugins/memory/mem0/_setup.py`, `plugins/memory/mem0/plugin.yaml`, `tests/plugins/memory/test_mem0_backend.py`, `tests/plugins/memory/test_mem0_setup.py`, `tests/plugins/memory/test_mem0_v3.py`, `website/docs/user-guide/features/memory-providers.md`
  - fix hunks:
  - `plugins/memory/mem0/README.md`

    ```diff
    @@ -25,14 +25,44 @@ Behavioral settings live in `$HERMES_HOME/mem0.json` (set them via `hermes memor
     
     | Key | Default | Description |
     |-----|---------|-------------|
    -| `mode` | `platform` | `platform` (Mem0 Cloud) or `oss` (self-hosted) |
    +| `mode` | `platform` | `platform` (Mem0 Cloud) or `oss` (self-managed, in-process) |
    +| `host` | — | Self-hosted Mem0 server URL (the Docker dashboard). When set, connects over HTTP with `X-API-Key`. Don't combine with `mode: oss` |
     | `user_id` | `hermes-user` | User identifier on Mem0 |
     | `agent_id` | `hermes` | Agent identifier |
    -| `rerank` | `true` | Rerank search results for relevance (platform mode only) |
    +| `rerank` | `false` | Rerank search results for relevance (platform mode only) |
    +
    +The plugin has three connection modes:
    +
    +- **Platform** — Mem0's hosted cloud (`api.mem0.ai`). Set `MEM0_API_KEY`. (default)
    +- **Self-hosted dashboard** — a Mem0 server you run yourself via Docker. Set `host`. See below.
    +- **OSS** — run Mem0 in-process with your own LLM + vector store. Set `mode: oss`. See below.
    +
    +## Self-Hosted Dashboard (Server) Mode
    +
    +Connect the plugin to a standalone Mem0 server you run yourself — the Docker-shipped Mem0 dashboard/server with its own REST API. Unlike OSS mode (which runs `mem0ai` in-process with your own vector store), here the plugin just talks HTTP to your server.
    +
    +1. Run the Mem0 server (FastAPI + pgvector) from its Docker image and note its URL and `ADMIN_API_KEY`.
    +2. Point the plugin at it — either via env vars:
    +   ```bash
    +   echo "MEM0_HOST=http://localhost:8888" >> ~/.hermes/.env
    +   echo "MEM0_API_KEY=[redacted:secret-assignment]" >> ~/.hermes/.env
    +   ```
    +   or in `$HERMES_HOME/mem0.json`:
    +   ```json
    +   {
    +     "host": "http://localhost:8888",
    +     "api_key": "your-admin-api-key"
    +   }
    +   ```
    +3. Start a fresh Hermes session and call `mem0_search` — it connects to your server.
    +
    +The plugin authenticates with `X-API-Key` and uses the server's `/search` and `/memories` routes. `api_key` is optional — omit it only for servers running with `AUTH_DISABLED`.
    +
    +> Setting `host` routes to the self-hosted server automatically. Don't set `mode: oss` — OSS takes precedence and ignores `host`.
     
     ## OSS (Self-Hosted) Mode
     
    -Run Mem0 locally with your own LLM, embedder, and vector store.
    +Run Mem0 locally with your own LLM, embedder, and vector store. This is the in-process SDK mode. To instead connect to a Mem0 server you run via Docker, see [Self-Hosted Dashboard (Server) Mode](#self-hosted-dashboard-server-mode) above.
     
     ### Interactive Setup
     
    ```

    ```diff
    @@ -106,7 +136,6 @@ hermes memory setup mem0 --mode oss --oss-llm-key sk-... --dry-run
     
     | Tool | Description |
     |------|-------------|
    -| `mem0_list` | List all stored memories (paginated) |
     | `mem0_search` | Semantic search by meaning |
     | `mem0_add` | Store a fact verbatim (no LLM extraction) |
     | `mem0_update` | Update a memory's text by ID |
    ```
  - `plugins/memory/mem0/__init__.py`

    ```diff
    @@ -9,10 +9,15 @@
     -------------
     Secret (lives in $HERMES_HOME/.env or the environment):
       MEM0_API_KEY       — Mem0 Platform API key (required for platform mode)
    +  MEM0_HOST          — Base URL of a self-hosted Mem0 server. When set, the
    +                       plugin talks to that server directly over HTTP
    +                       (X-API-Key auth) instead of the cloud API.
     
     Behavioral settings (live in $HERMES_HOME/mem0.json, set via `hermes memory
     setup`):
       mode               — Backend mode: "platform" (default) or "oss"
    +  host               — Self-hosted Mem0 server URL (alt: MEM0_HOST env var).
    +                       When set, routes to the self-hosted HTTP backend.
       user_id            — Canonical user identifier. When set, it is applied
                            uniformly across every gateway (CLI, Telegram, Slack,
                            Discord, …) so the same human gets one merged memory
    ```

    ```diff
    @@ -44,7 +49,7 @@
     # for _BREAKER_COOLDOWN_SECS to avoid hammering a down server.
     _BREAKER_THRESHOLD = 5
     _BREAKER_COOLDOWN_SECS = 120
    -_PREFETCH_WAIT_SECS = 1.5
    +_PREFETCH_WAIT_SECS = 3
     
     _CLIENT_ERROR_TYPES = ("MemoryNotFoundError", "ValidationError")
     
    ```

    ```diff
    @@ -81,6 +86,7 @@ def _load_config() -> dict:
         config = {
             "mode": os.environ.get("MEM0_MODE", "platform"),
             "api_key": os.environ.get("MEM0_API_KEY", ""),
    +        "host": os.environ.get("MEM0_HOST", ""),
             "agent_id": os.environ.get("MEM0_AGENT_ID", "hermes"),
             "oss": {},
         }
    ```

    ```diff
    @@ -107,41 +113,22 @@ def _load_config() -> dict:
     # Tool schemas
     # ---------------------------------------------------------------------------
     
    -LIST_SCHEMA = {
    -    "name": "mem0_list",
    -    "description": (
    -        "List ALL stored memories about the user, unranked and paginated. "
    -        "Use for a full overview/audit at conversation start, or to browse "
    -        "everything when you don't have a specific query. For answering a "
    -        "specific question, prefer mem0_search."
    -    ),
    -    "parameters": {
    -        "type": "object",
    -        "properties": {
    -            "page": {"type": "integer", "description": "Page number (default: 1)."},
    -            "page_size": {"type": "integer", "description": "Results per page (default: 100, max: 200)."},
    -        },
    -        "required": [],
    -    },
    -}
    -
     SEARCH_SCHEMA = {
         "name": "mem0_search",
         "description": (
             "Search the user's memories by meaning; returns facts ranked by "
    -        "relevance. Use this BEFORE answering any question that may depend on "
    +        "relevance. Use this before answering any question that may depend on "
             "what you know about the user (preferences, facts, history, people, "
             "projects, past decisions). For multi-part or multi-hop questions, "
    -        "call it MULTIPLE times — vary the wording and run follow-up searches "
    -        "on what earlier results reveal; one search is rarely enough. Set "
    -        "rerank=true for higher accuracy on important queries."
    +        "call it several times — vary the wording and run follow-up searches "
    +        "on what earlier results reveal; one search is rarely enough."
         ),
         "parameters": {
             "type": "object",
             "properties": {
                 "query": {"type": "string", "description": "What to search for."},
                 "top_k": {"type": "integer", "description": "Max results (default: 10, max: 50)."},
    -            "rerank": {"type": "boolean", "description": "Rerank results for relevance (default: true, platform mode only)."},
    +            "rerank": {"type": "boolean", "description": "Rerank results for relevance (default: false, platform mode only)."},
             },
             "required": ["query"],
         },
    ```

    ```diff
    @@ -169,7 +156,7 @@ def _load_config() -> dict:
         "name": "mem0_update",
         "description": (
             "Replace the text of an existing memory by its ID (take the ID from a "
    -        "mem0_search or mem0_list result). Use when a stored fact has changed "
    +        "mem0_search result). Use when a stored fact has changed "
             "or was wrong — correct it in place instead of adding a duplicate."
         ),
         "parameters": {
    ```

    ```diff
    @@ -185,7 +172,7 @@ def _load_config() -> dict:
     DELETE_SCHEMA = {
         "name": "mem0_delete",
         "description": (
    -        "Delete a memory by its ID (take the ID from a mem0_search or mem0_list "
    +        "Delete a memory by its ID (take the ID from a mem0_search "
             "result). Use when a stored fact is obsolete or the user asks you to "
             "forget it; prefer mem0_update if the fact merely changed."
         ),
    ```

    ```diff
    @@ -214,8 +201,10 @@ def __init__(self):
             self._backend = None
             self._mode = "platform"
             self._api_key = ""
    +        self._host = ""
             self._user_id = _DEFAULT_USER_ID
             self._agent_id = "hermes"
    +        self._rerank_default = False
             self._channel = "cli"  # gateway channel name (cli/telegram/discord/...)
             self._sync_thread = None
             self._prefetch_thread = None
    ```

    ```diff
    @@ -239,7 +228,9 @@ def is_available(self) -> bool:
             mode = cfg.get("mode", "platform")
             if mode == "oss":
                 return bool(cfg.get("oss", {}).get("vector_store"))
    -        return bool(cfg.get("api_key"))
    +        # Platform needs an api_key; self-hosted needs a host (api_key optional
    +        # when the server runs with AUTH_DISABLED).
    +        return bool(cfg.get("api_key") or cfg.get("host"))
     
         def save_config(self, values, hermes_home):
             """Write config to $HERMES_HOME/mem0.json."""
    ```

    ```diff
    @@ -262,9 +253,10 @@ def get_config_schema(self):
             api_key_required = mode != "oss"
             return [
                 {"key": "api_key", "description": "Mem0 Platform API key", "secret": True, "required": api_key_required, "env_var": "MEM0_API_KEY", "url": "https://app.mem0.ai"},
    +            {"key": "host", "description": "Self-hosted Mem0 server URL (leave blank for cloud)", "required": False, "env_var": "MEM0_HOST"},
                 {"key": "user_id", "description": "User identifier", "default": "hermes-user"},
                 {"key": "agent_id", "description": "Agent identifier", "default": "hermes"},
    -            {"key": "rerank", "description": "Enable reranking for recall", "default": "true", "choices": ["true", "false"]},
    +            {"key": "rerank", "description": "Enable reranking for recall", "default": "false", "choices": ["true", "false"]},
             ]
     
         def post_setup(self, hermes_home: str, config: dict) -> None:
    ```

    ```diff
    @@ -288,6 +280,9 @@ def _create_backend(self):
                 if self._mode == "oss":
                     from ._backend import OSSBackend
                     return OSSBackend(self._config.get("oss", {}))
    +            if self._host:
    +                from ._backend import SelfHostedBackend
    +                return SelfHostedBackend(self._api_key, self._host)
                 from ._backend import PlatformBackend
                 return PlatformBackend(self._api_key)
             except Exception as e:
    ```

    ```diff
    @@ -342,6 +337,7 @@ def initialize(self, session_id: str, **kwargs) -> None:
             self._config = _load_config()
             self._mode = self._config.get("mode", "platform")
             self._api_key = [redacted:secret-assignment]"api_key", "")
    +        self._host = self._config.get("host", "")
             # Resolution order for user_id:
             #   1. Operator-configured MEM0_USER_ID (env or $HERMES_HOME/mem0.json) —
             #      the canonical principal, applied across every gateway so the same
    ```

    ```diff
    @@ -358,6 +354,14 @@ def initialize(self, session_id: str, **kwargs) -> None:
                 configured = None
             self._user_id = configured or kwargs.get("user_id") or _DEFAULT_USER_ID
             self._agent_id = self._config.get("agent_id", "hermes")
    +        # Persisted rerank preference (setup wizard / mem0.json). Used as the
    +        # DEFAULT for mem0_search when the model doesn't pass ``rerank``
    +        # explicitly; per-call args still win. Platform-only feature — other
    +        # backends accept-and-ignore the flag.
    +        _rr = self._config.get("rerank", False)
    +        self._rerank_default = (
    +            _rr.lower() in ("true", "1", "yes") if isinstance(_rr, str) else bool(_rr)
    +        )
             self._channel = kwargs.get("platform") or "cli"
             self._backend = self._create_backend()
             if self._backend and not self._atexit_registered:
    ```

    ```diff
    @@ -378,22 +382,32 @@ def _write_metadata(self) -> Dict[str, Any]:
             return {"channel": self._channel} if self._channel else {}
     
         def system_prompt_block(self) -> str:
    -        mode_label = "platform (cloud API)" if self._mode == "platform" else "OSS (self-hosted)"
    -        rerank_note = " Rerank is available on search." if self._mode == "platform" else ""
    +        # Mirror the precedence in _create_backend (oss > host > platform) so
    +        # the label always names the backend that actually runs. Checking
    +        # ``host`` first here would mislabel an ``oss``+``host`` config as
    +        # self-hosted HTTP even though OSS wins the routing.
    +        if self._mode == "oss":
    +            mode_label = "OSS (self-hosted)"
    +        elif self._host:
    +            mode_label = "self-hosted (HTTP API)"
    +        else:
    +            mode_label = "platform (cloud API)"
    +        # Rerank is a Mem0 Platform feature only.
    +        rerank_note = " Rerank is available on search." if (self._mode == "platform" and not self._host) else ""
             return (
                 "# Mem0 Memory\n"
                 f"Active. Mode: {mode_label}. User: {self._user_id}.\n"
                 "You have persistent memory of this user from past conversations. "
    -            "ALWAYS call mem0_search before answering anything that could depend "
    +            "You should call mem0_search before answering anything that could depend "
                 "on prior context (the user's preferences, facts, history, people, "
                 "projects, or earlier decisions) — do not rely on the chat window "
                 "alone, and do not assume you have no memory.\n"
    -            "For multi-part or multi-hop questions, run SEVERAL searches with "
    +            "For multi-part or multi-hop questions, run several searches with "
                 "different wording/angles and follow-up searches on what the first "
                 "results surface; one search is rarely enough. Keep searching until "
                 "you have every fact the question needs before you answer.\n"
                 "Tools: mem0_search to find memories, mem0_add to store facts, "
    -            f"mem0_list for a full overview, mem0_update and mem0_delete to manage by ID.{rerank_note}"
    +            f"mem0_update and mem0_delete to manage by ID.{rerank_note}"
             )
     
         def on_turn_start(self, turn_number: int, message: str, **kwargs) -> None:
    ```

    ```diff
    @@ -426,7 +440,7 @@ def _run():
                 body = ""
                 try:
                     results = backend.search(
    -                    query, filters=self._read_filters(), top_k=10, rerank=True,
    +                    query, filters=self._read_filters(), top_k=10, rerank=False,
                     )
                     lines = [r.get("memory", "") for r in (results or []) if r.get("memory")]
                     if lines:
    ```

    ```diff
    @@ -497,7 +511,7 @@ def _sync():
                 self._sync_thread.start()
     
         def get_tool_schemas(self) -> List[Dict[str, Any]]:
    -        return [LIST_SCHEMA, SEARCH_SCHEMA, ADD_SCHEMA, UPDATE_SCHEMA, DELETE_SCHEMA]
    +        return [SEARCH_SCHEMA, ADD_SCHEMA, UPDATE_SCHEMA, DELETE_SCHEMA]
     
         def handle_tool_call(self, tool_name: str, args: dict, **kwargs) -> str:
             if self._backend is None:
    ```

    ```diff
    @@ -516,36 +530,13 @@ def handle_tool_call(self, tool_name: str, args: dict, **kwargs) -> str:
                     msg += f" Check that your {vs.get('provider', 'vector store')} is running."
                 return json.dumps({"error": msg})
     
    -        if tool_name == "mem0_list":
    -            try:
    -                page = max(1, int(args.get("page", 1)))
    -                page_size = min(max(1, int(args.get("page_size", 100))), 200)
    -                response = self._backend.get_all(
    -                    filters=self._read_filters(), page=page, page_size=page_size,
    -                )
    -                self._record_success()
    -                results = response.get("results", [])
    -                if not results:
    -                    return json.dumps({"result": "No memories stored yet."})
    -                items = [{"id": m.get("id"), "memory": m.get("memory", "")}
    -                         for m in results]
    -                return json.dumps({
    -                    "results": items,
    -                    "count": response.get("count", len(items)),
    -                    "page": page, "page_size": page_size,
    -                })
    -            except Exception as e:
    -                if not _is_client_error(e):
    -                    self._record_failure()
    -                return tool_error(self._format_error("Failed to list memories", e))
    -
    -        elif tool_name == "mem0_search":
    +        if tool_name == "mem0_search":
                 query = args.get("query", "")
                 if not query:
                     return tool_error("Missing required parameter: query")
                 try:
                     top_k = max(1, min(int(args.get("top_k", 10)), 50))
    -                rerank_raw = args.get("rerank", True)
    +                rerank_raw = args.get("rerank", getattr(self, "_rerank_default", False))
                     if isinstance(rerank_raw, str):
                         rerank = rerank_raw.lower() not in ("false", "0", "no")
                     else:
    ```

    ```diff
    @@ -576,7 +567,8 @@ def handle_tool_call(self, tool_name: str, args: dict, **kwargs) -> str:
                     )
                     self._record_success()
                     event_id = result.get("event_id") if isinstance(result, dict) else None
    -                msg = "Fact stored." if self._mode == "oss" else "Fact queued for storage."
    +                # Cloud add is async (server-side extraction); OSS and self-hosted store synchronously.
    +                msg = "Fact stored." if (self._mode == "oss" or self._host) else "Fact queued for storage."
                     return json.dumps({"result": msg, "event_id": event_id})
                 except Exception as e:
                     self._record_failure()
    ```
  - `plugins/memory/mem0/_backend.py`

    ```diff
    @@ -10,11 +10,7 @@ class Mem0Backend(ABC):
         """Unified interface over Platform (MemoryClient) and OSS (Memory) backends."""
     
         @abstractmethod
    -    def search(self, query: str, *, filters: dict, top_k: int = 10, rerank: bool = True) -> list[dict]:
    -        ...
    -
    -    @abstractmethod
    -    def get_all(self, *, filters: dict, page: int = 1, page_size: int = 100) -> dict:
    +    def search(self, query: str, *, filters: dict, top_k: int = 10, rerank: bool = False) -> list[dict]:
             ...
     
         @abstractmethod
    ```

    ```diff
    @@ -57,16 +53,10 @@ def __init__(self, api_key: str):
             from mem0 import MemoryClient
             self._client = MemoryClient(api_key=[redacted:secret-assignment]
     
    -    def search(self, query: str, *, filters: dict, top_k: int = 10, rerank: bool = True) -> list[dict]:
    +    def search(self, query: str, *, filters: dict, top_k: int = 10, rerank: bool = False) -> list[dict]:
             response = self._client.search(query, filters=filters, top_k=top_k, rerank=rerank)
             return _unwrap_results(response)
     
    -    def get_all(self, *, filters: dict, page: int = 1, page_size: int = 100) -> dict:
    -        response = self._client.get_all(filters=filters, page=page, page_size=page_size)
    -        results = response.get("results", []) if isinstance(response, dict) else response
    -        count = response.get("count", len(results)) if isinstance(response, dict) else len(results)
    -        return {"results": results, "count": count}
    -
         def add(
             self,
             messages: list,
    ```

    ```diff
    @@ -90,6 +80,79 @@ def delete(self, memory_id: str) -> dict:
             return {"result": "Memory deleted.", "memory_id": memory_id}
     
     
    +class SelfHostedBackend(Mem0Backend):
    +    """Direct HTTP backend for a self-hosted Mem0 server (the FastAPI ``server/``).
    +
    +    mem0.MemoryClient can't be reused for self-hosted: it is hardwired to the
    +    cloud API — ``Authorization: Token`` auth and a ``GET /v1/ping/`` validation
    +    call in ``__init__`` that the self-hosted server does not expose (it would
    +    404 before any real request). This client talks to that server directly,
    +    using its actual contract: ``X-API-Key`` auth and the ``/memories`` /
    +    ``/search`` routes.
    +    """
    +
    +    def __init__(self, api_key: str, host: str, transport=None):
    +        import httpx
    +
    +        headers = {"Content-Type": "application/json"}
    +        if api_key:
    +            headers["X-API-Key"] = api_key  # omitted only for AUTH_DISABLED servers
    +        # Connect-level retries smooth over transient blips so a single
    +        # dropped SYN doesn't count toward the provider failure breaker.
    +        # ``transport`` is injectable for tests (httpx.MockTransport).
    +        if transport is None:
    +            transport = httpx.HTTPTransport(retries=2)
    +        self._client = httpx.Client(
    +            base_url=host.rstrip("/"), headers=headers, timeout=30.0,
    +            transport=transport,
    +        )
    +
    +    def _json(self, method: str, path: str, **kwargs) -> Any:
    +        resp = self._client.request(method, path, **kwargs)
    +        resp.raise_for_status()
    +        return resp.json() if resp.content else {}
    +
    +    def search(self, query: str, *, filters: dict, top_k: int = 10, rerank: bool = False) -> list[dict]:
    +        # rerank is a platform-only feature; the self-hosted /search ignores it.
    +        body: dict[str, Any] = {"query": query, "top_k": top_k}
    +        if filters:
    +            body["filters"] = filters  # user_id belongs in filters (top-level is deprecated)
    +        return _unwrap_results(self._json("POST", "/search", json=body))
    +
    +    def add(
    +        self,
    +        messages: list,
    +        *,
    +        user_id: str,
    +        agent_id: str,
    +        infer: bool = False,
    +        metadata: dict | None = None,
    +    ) -> dict:
    +        body: dict[str, Any] = {
    +            "messages": messages,
    +            "user_id": user_id,
    +            "agent_id": agent_id,
    +            "infer": infer,
    +        }
    +        if metadata:
    +            body["metadata"] = metadata
    +        return self._json("POST", "/memories", json=body)
    +
    +    def update(self, memory_id: str, text: str) -> dict:
    +        self._json("PUT", f"/memories/{memory_id}", json={"text": text})
    +        return {"result": "Memory updated.", "memory_id": memory_id}
    +
    +    def delete(self, memory_id: str) -> dict:
    +        self._json("DELETE", f"/memories/{memory_id}")
    +        return {"result": "Memory deleted.", "memory_id": memory_id}
    +
    +    def close(self) -> None:
    +        try:
    +            self._client.close()
    +        except Exception:
    +            pass
    +
    +
     class OSSBackend(Mem0Backend):
         """Wraps mem0.Memory for self-hosted (OSS) mode."""
     
    ```

    ```diff
    @@ -189,18 +252,10 @@ def _recreate_collection_if_dims_changed(provider: str, vs_config: dict, expecte
                 except Exception:
                     pass
     
    -    def search(self, query: str, *, filters: dict, top_k: int = 10, rerank: bool = True) -> list[dict]:
    +    def search(self, query: str, *, filters: dict, top_k: int = 10, rerank: bool = False) -> list[dict]:
             response = self._memory.search(query, filters=filters, top_k=top_k)
             return _unwrap_results(response)
     
    -    def get_all(self, *, filters: dict, page: int = 1, page_size: int = 100) -> dict:
    -        response = self._memory.get_all(filters=filters)
    -        all_results = _unwrap_results(response)
    -        total = len(all_results)
    -        start = (page - 1) * page_size
    -        results = all_results[start : start + page_size]
    -        return {"results": results, "count": total}
    -
         def add(
             self,
             messages: list,
    ```
  - `plugins/memory/mem0/_setup.py`

    ```diff
    @@ -230,7 +230,7 @@ def _setup_platform(hermes_home: str, config: dict, flags: dict[str, str]) -> No
             {"key": "api_key", "description": "Mem0 Platform API key", "secret": True, "required": True, "env_var": "MEM0_API_KEY", "url": "https://app.mem0.ai"},
             {"key": "user_id", "description": "User identifier", "default": "hermes-user"},
             {"key": "agent_id", "description": "Agent identifier", "default": "hermes"},
    -        {"key": "rerank", "description": "Enable reranking for recall", "default": "true", "choices": ["true", "false"]},
    +        {"key": "rerank", "description": "Enable reranking for recall", "default": "false", "choices": ["true", "false"]},
         ]
     
         existing_config = {}
    ```

    ```diff
    @@ -293,6 +293,24 @@ def _setup_platform(hermes_home: str, config: dict, flags: dict[str, str]) -> No
             return
     
         provider_config["mode"] = "platform"
    +    # Clear any stale self-hosted host: routing checks ``host`` before platform
    +    # (see _create_backend), so leaving it would silently keep routing to the
    +    # self-hosted server even though the user just chose platform mode. Set it
    +    # to "" rather than pop() — save_config merges into the existing mem0.json
    +    # (existing.update), so a popped key would survive; an empty value overwrites
    +    # it and reads as falsy at routing time.
    +    provider_config["host"] = ""
    +    # The json-file clear above can't help when the host comes from the
    +    # environment: _load_config() seeds ``host`` from MEM0_HOST, and the
    +    # docs tell self-hosted users to put MEM0_HOST in ~/.hermes/.env. Warn
    +    # so the user knows platform mode won't take effect until it's removed.
    +    if os.environ.get("MEM0_HOST", "").strip():
    +        print(
    +            "\n  ⚠ MEM0_HOST is set in your environment "
    +            f"({os.environ['MEM0_HOST']}). It overrides platform mode — "
    +            "remove it from ~/.hermes/.env (or unset it) or Hermes will keep "
    +            "routing to the self-hosted server."
    +        )
     
         from hermes_cli.config import save_config
         config["memory"]["provider"] = "mem0"
    ```
  - `plugins/memory/mem0/plugin.yaml`

    ```diff
    @@ -1,5 +1,5 @@
     name: mem0
    -version: 1.2.0
    -description: "Mem0 — server-side LLM fact extraction with semantic search, reranking, and automatic deduplication."
    +version: 1.3.0
    +description: "Mem0 — server-side LLM fact extraction with semantic search, automatic deduplication, and opt-in reranking (platform mode)."
     pip_dependencies:
    -  - mem0ai>=2.0.7,<3
    +  - mem0ai>=2.0.10,<3
    ```
  - `tests/plugins/memory/test_mem0_backend.py`

    ```diff
    @@ -1,8 +1,13 @@
    -"""Tests for Mem0Backend abstraction — PlatformBackend and OSSBackend."""
    +"""Tests for Mem0Backend abstraction — PlatformBackend, OSSBackend, SelfHostedBackend."""
     
     import pytest
     
    -from plugins.memory.mem0._backend import Mem0Backend, PlatformBackend, OSSBackend
    +from plugins.memory.mem0._backend import (
    +    Mem0Backend,
    +    PlatformBackend,
    +    OSSBackend,
    +    SelfHostedBackend,
    +)
     
     
     class FakePlatformClient:
    ```

    ```diff
    @@ -52,10 +57,10 @@ def test_search_forwards_rerank(self):
             backend.search("q", filters={}, rerank=False)
             assert client.calls[0][2]["rerank"] is False
     
    -    def test_search_rerank_default_true(self):
    +    def test_search_rerank_default_false(self):
             backend, client = self._make()
             backend.search("q", filters={})
    -        assert client.calls[0][2]["rerank"] is True
    +        assert client.calls[0][2]["rerank"] is False
     
         def test_search_returns_list(self):
             backend, _ = self._make()
    ```

    ```diff
    @@ -63,13 +68,6 @@ def test_search_returns_list(self):
             assert isinstance(result, list)
             assert result[0]["id"] == "m1"
     
    -    def test_get_all_forwards_pagination(self):
    -        backend, client = self._make()
    -        result = backend.get_all(filters={"user_id": "u1"}, page=2, page_size=50)
    -        assert client.calls[0][1]["page"] == 2
    -        assert client.calls[0][1]["page_size"] == 50
    -        assert "count" in result
    -
         def test_add_forwards_kwargs(self):
             backend, client = self._make()
             msgs = [{"role": "user", "content": "hi"}]
    ```

    ```diff
    @@ -163,21 +161,6 @@ def test_search_ignores_rerank(self):
             backend.search("q", filters={}, rerank=True)
             assert "rerank" not in memory.calls[0][2]
     
    -    def test_get_all_ignores_pagination(self):
    -        """OSSBackend accepts page/page_size but does NOT forward to Memory.get_all()."""
    -        backend, memory = self._make()
    -        result = backend.get_all(filters={"user_id": "u1"}, page=2, page_size=50)
    -        call_kwargs = memory.calls[0][1]
    -        assert "page" not in call_kwargs
    -        assert "page_size" not in call_kwargs
    -        assert result["count"] == 1
    -
    -    def test_get_all_returns_envelope(self):
    -        backend, _ = self._make()
    -        result = backend.get_all(filters={"user_id": "u1"})
    -        assert "results" in result
    -        assert "count" in result
    -
         def test_add_forwards_kwargs(self):
             backend, memory = self._make()
             msgs = [{"role": "user", "content": "hi"}]
    ```

    ```diff
    @@ -207,3 +190,115 @@ def test_delete_normalizes_response(self):
             backend, _ = self._make()
             result = backend.delete("m1")
             assert result == {"result": "Memory deleted.", "memory_id": "m1"}
    +
    +
    +httpx = pytest.importorskip("httpx")
    +
    +
    +class _StubServer:
    +    """Records requests and serves the real self-hosted server's response shapes."""
    +
    +    def __init__(self, rows=10):
    +        self.requests = []
    +        self._rows = [{"id": f"m{i}", "memory": f"f{i}"} for i in range(rows)]
    +
    +    def handler(self, request):
    +        self.requests.append(request)
    +        path, method = request.url.path, request.method
    +        if path == "/search" and method == "POST":
    +            return httpx.Response(200, json={"results": [{"id": "m1", "memory": "tea", "score": 0.9}]})
    +        if path == "/memories" and method == "GET":
    +            top_k = int(request.url.params.get("top_k", len(self._rows)))
    +            return httpx.Response(200, json={"results": self._rows[:top_k]})
    +        if path == "/memories" and method == "POST":
    +            return httpx.Response(200, json={"results": [{"id": "new", "memory": "stored", "event": "ADD"}]})
    +        if path.startswith("/memories/") and method in ("PUT", "DELETE"):
    +            if path.endswith("/missing"):  # server 404s unknown ids
    +                return httpx.Response(404, json={"detail": "Memory not found"})
    +            verb = "updated" if method == "PUT" else "Memory deleted successfully"
    +            return httpx.Response(200, json={"message": verb})
    +        return httpx.Response(404, json={"detail": "not found"})
    +
    +
    +def _backend(server, api_key="[redacted:secret-assignment]", host="http://sh:8888"):
    +    """Build a SelfHostedBackend routed through the stub transport.
    +
    +    Uses the real __init__ (via the injectable ``transport`` kwarg) so the
    +    constructor's header/base_url setup is exercised by every test here.
    +    """
    +    return SelfHostedBackend(
    +        api_key, host, transport=httpx.MockTransport(server.handler)
    +    )
    +
    +
    +class TestSelfHostedBackend:
    +    # --- constructor / auth setup (the crux of the bug) -------------------
    +
    +    def test_init_uses_x_api_key_not_token_auth(self):
    +        b = SelfHostedBackend("adminkey", "http://sh:8888")
    +        assert b._client.headers["x-api-key"] == "adminkey"
    +        assert "authorization" not in b._client.headers  # NOT the cloud 'Token' scheme
    +
    +    def test_init_strips_trailing_slash(self):
    +        b = SelfHostedBackend("k", "http://sh:8888/")
    +        assert str(b._client.base_url) == "http://sh:8888"
    +
    +    def test_init_omits_api_key_header_when_blank(self):
    +        b = SelfHostedBackend("", "http://sh:8888")  # AUTH_DISABLED server
    +        assert "x-api-key" not in b._client.headers
    +
    +    # --- search ----------------------------------------------------------
    +
    +    def test_search_posts_to_search_with_filters_in_body(self):
    +        s = _StubServer()
    +        results = _backend(s).search("drink", filters={"user_id": "u1"}, top_k=5)
    +        req = s.requests[-1]
    +        assert (req.method, req.url.path) == ("POST", "/search")
    +        import json
    +        body = json.loads(req.content)
    +        assert body == {"query": "drink", "top_k": 5, "filters": {"user_id": "u1"}}
    +        assert results == [{"id": "m1", "memory": "tea", "score": 0.9}]
    +
    +    def test_search_sends_x_api_key_header(self):
    +        s = _StubServer()
    +        _backend(s).search("q", filters={"user_id": "u1"})
    +        req = s.requests[-1]
    +        assert req.headers["x-api-key"] == "adminkey"
    +        assert "authorization" not in req.headers
    +
    +    # --- add / update / delete ------------------------------------------
    +
    +    def test_add_posts_messages_and_identity(self):
    +        s = _StubServer()
    +        msgs = [{"role": "user", "content": "likes tea"}]
    +        result = _backend(s).add(msgs, user_id="u1", agent_id="hermes", infer=False, metadata={"channel": "cli"})
    +        req = s.requests[-1]
    +        assert (req.method, req.url.path) == ("POST", "/memories")
    +        import json
    +        body = json.loads(req.content)
    +        assert body == {"messages": msgs, "user_id": "u1", "agent_id": "hermes",
    +                        "infer": False, "metadata": {"channel": "cli"}}
    +        assert result["results"][0]["id"] == "new"
    +
    +    def test_update_puts_text_to_memory_id(self):
    +        s = _StubServer()
    +        result = _backend(s).update("abc", "new text")
    +        req = s.requests[-1]
    +        assert (req.method, req.url.path) == ("PUT", "/memories/abc")
    +        import json
    +        assert json.loads(req.content) == {"text": "new text"}
    +        assert result == {"result": "Memory updated.", "memory_id": "abc"}
    +
    +    def test_delete_calls_delete_endpoint(self):
    +        s = _StubServer()
    +        result = _backend(s).delete("abc")
    +        req = s.requests[-1]
    +        assert (req.method, req.url.path) == ("DELETE", "/memories/abc")
    +        assert result == {"result": "Memory deleted.", "memory_id": "abc"}
    +
    +    # --- error propagation (feeds the plugin's circuit breaker) ----------
    +
    +    def test_http_error_raises(self):
    +        s = _StubServer()
    +        with pytest.raises(httpx.HTTPStatusError):
    +            _backend(s).delete("missing")  # 404 -> raise_for_status; 'not found' won't trip breaker
    ```
  - `tests/plugins/memory/test_mem0_setup.py`

    ```diff
    @@ -179,6 +179,22 @@ def test_platform_flag_mode(self, tmp_path, monkeypatch):
             mem0_json = json.loads((tmp_path / "mem0.json").read_text())
             assert mem0_json["mode"] == "platform"
     
    +    def test_platform_setup_clears_stale_host(self, tmp_path, monkeypatch):
    +        # A user who previously ran self-hosted has host in mem0.json. Switching
    +        # to platform must drop host — otherwise routing (host > platform) keeps
    +        # sending them to the self-hosted server despite --mode platform.
    +        (tmp_path / "mem0.json").write_text(
    +            json.dumps({"mode": "platform", "host": "http://old-selfhosted:8888"})
    +        )
    +        monkeypatch.setattr("sys.argv", ["hermes", "--mode", "platform", "--api-key", "sk-test"])
    +        monkeypatch.setattr("plugins.memory.mem0._setup.get_hermes_home", lambda: tmp_path)
    +        _inject_fake_hermes_cli(monkeypatch)
    +        config = {"memory": {}}
    +        post_setup(str(tmp_path), config)
    +        mem0_json = json.loads((tmp_path / "mem0.json").read_text())
    +        assert mem0_json["mode"] == "platform"
    +        assert not mem0_json.get("host")  # cleared to falsy so routing → platform
    +
         def test_oss_flag_mode(self, tmp_path, monkeypatch):
             monkeypatch.setattr("sys.argv", [
                 "hermes", "--mode", "oss", "--oss-llm-key", "sk-oai",
    ```
  - `tests/plugins/memory/test_mem0_v3.py`

    ```diff
    @@ -52,33 +52,6 @@ def _make_provider(self, monkeypatch, backend):
             provider._backend = backend
             return provider
     
    -    def test_list_returns_paginated_with_ids(self, monkeypatch):
    -        backend = FakeBackend(all_results={
    -            "count": 2,
    -            "results": [
    -                {"id": "mem-1", "memory": "alpha"},
    -                {"id": "mem-2", "memory": "beta"},
    -            ]
    -        })
    -        provider = self._make_provider(monkeypatch, backend)
    -        result = json.loads(provider.handle_tool_call("mem0_list", {}))
    -        assert result["count"] == 2
    -        assert result["results"][0]["id"] == "mem-1"
    -        assert result["results"][0]["memory"] == "alpha"
    -
    -    def test_list_pagination_params(self, monkeypatch):
    -        backend = FakeBackend()
    -        provider = self._make_provider(monkeypatch, backend)
    -        provider.handle_tool_call("mem0_list", {"page": 2, "page_size": 50})
    -        assert backend.captured[0][1]["page"] == 2
    -        assert backend.captured[0][1]["page_size"] == 50
    -
    -    def test_list_empty(self, monkeypatch):
    -        backend = FakeBackend()
    -        provider = self._make_provider(monkeypatch, backend)
    -        result = json.loads(provider.handle_tool_call("mem0_list", {}))
    -        assert result["result"] == "No memories stored yet."
    -
         def test_search_returns_ids(self, monkeypatch):
             backend = FakeBackend(search_results=[{"id": "mem-1", "memory": "foo", "score": 0.9}])
             provider = self._make_provider(monkeypatch, backend)
    ```

    ```diff
    @@ -92,11 +65,11 @@ def test_search_uses_filters(self, monkeypatch):
             assert backend.captured[0][2]["filters"] == {"user_id": "u123"}
             assert backend.captured[0][2]["top_k"] == 3
     
    -    def test_search_rerank_default_true(self, monkeypatch):
    +    def test_search_rerank_default_false(self, monkeypatch):
             backend = FakeBackend()
             provider = self._make_provider(monkeypatch, backend)
             provider.handle_tool_call("mem0_search", {"query": "test"})
    -        assert backend.captured[0][2]["rerank"] is True
    +        assert backend.captured[0][2]["rerank"] is False
     
         def test_search_rerank_override_false(self, monkeypatch):
             backend = FakeBackend()
    ```

    ```diff
    @@ -104,6 +77,15 @@ def test_search_rerank_override_false(self, monkeypatch):
             provider.handle_tool_call("mem0_search", {"query": "test", "rerank": False})
             assert backend.captured[0][2]["rerank"] is False
     
    +    def test_search_rerank_config_default_used_when_arg_absent(self, monkeypatch):
    +        """The persisted mem0.json ``rerank`` preference is the tool default;
    +        a per-call arg still wins (see the explicit-False override above)."""
    +        backend = FakeBackend()
    +        provider = self._make_provider(monkeypatch, backend)
    +        provider._rerank_default = True  # as initialize() sets from config
    +        provider.handle_tool_call("mem0_search", {"query": "test"})
    +        assert backend.captured[0][2]["rerank"] is True
    +
         def test_add_uses_content_param(self, monkeypatch):
             backend = FakeBackend()
             provider = self._make_provider(monkeypatch, backend)
    ```

    ```diff
    @@ -280,6 +262,8 @@ def test_old_tool_names_return_unknown(self, monkeypatch):
             assert "error" in result
             result = json.loads(provider.handle_tool_call("mem0_conclude", {}))
             assert "error" in result
    +        result = json.loads(provider.handle_tool_call("mem0_list", {}))
    +        assert "error" in result
     
     
     class TestMem0Prefetch:
    ```

    ```diff
    @@ -308,7 +292,7 @@ def test_prefetch_searches_current_query(self):
             assert query == "what theme do I like?"
             assert opts["filters"] == {"user_id": "u123"}
             assert opts["top_k"] == 10
    -        assert opts["rerank"] is True
    +        assert opts["rerank"] is False
             assert "## Mem0 Memory" in result
             assert "user prefers dark mode" in result
     
    ```

    ```diff
    @@ -368,11 +352,11 @@ def test_queue_prefetch_fires_no_search(self):
     
     class TestMem0V3Config:
     
    -    def test_tool_schemas_five_tools(self):
    +    def test_tool_schemas_four_tools(self):
             provider = Mem0MemoryProvider()
             schemas = provider.get_tool_schemas()
             names = [s["name"] for s in schemas]
    -        assert names == ["mem0_list", "mem0_search", "mem0_add", "mem0_update", "mem0_delete"]
    +        assert names == ["mem0_search", "mem0_add", "mem0_update", "mem0_delete"]
     
         def test_system_prompt_new_tool_names(self):
             provider = Mem0MemoryProvider()
    ```

    ```diff
    @@ -380,9 +364,9 @@ def test_system_prompt_new_tool_names(self):
             block = provider.system_prompt_block()
             assert "mem0_search" in block
             assert "mem0_add" in block
    -        assert "mem0_list" in block
             assert "mem0_update" in block
             assert "mem0_delete" in block
    +        assert "mem0_list" not in block
             assert "mem0_profile" not in block
             assert "mem0_conclude" not in block
     
    ```

    ```diff
    @@ -455,7 +439,7 @@ def test_tool_schemas_unchanged(self):
             provider = Mem0MemoryProvider()
             schemas = provider.get_tool_schemas()
             names = [s["name"] for s in schemas]
    -        assert names == ["mem0_list", "mem0_search", "mem0_add", "mem0_update", "mem0_delete"]
    +        assert names == ["mem0_search", "mem0_add", "mem0_update", "mem0_delete"]
     
         def test_system_prompt_includes_mode(self):
             provider = Mem0MemoryProvider()
    ```

    ```diff
    @@ -463,7 +447,6 @@ def test_system_prompt_includes_mode(self):
             provider._mode = "oss"
             block = provider.system_prompt_block()
             assert "mem0_search" in block
    -        assert "mem0_list" in block
             assert "OSS" in block
     
     
    ```

    ```diff
    @@ -547,3 +530,92 @@ def test_sync_turn_passes_channel_metadata(self):
             adds = [c for c in provider._backend.captured if c[0] == "add"]
             assert adds, "expected an add call from sync_turn"
             assert adds[-1][2]["metadata"] == {"channel": "discord"}
    +
    +
    +class _SentinelBackend:
    +    def __init__(self, *args):
    +        self.args = args
    +
    +
    +class TestCreateBackendRouting:
    +    """_create_backend() must pick the backend matching the configured mode/host."""
    +
    +    def _provider(self, monkeypatch, *, mode="platform", api_key="k", host=""):
    +        # Neutralize lazy-install so the routing decision is all we exercise.
    +        monkeypatch.setattr("tools.lazy_deps.ensure", lambda *a, **k: None, raising=False)
    +        provider = Mem0MemoryProvider()
    +        provider._mode = mode
    +        provider._api_key = [redacted:secret-assignment]
    +        provider._host = host
    +        provider._config = {"oss": {"vector_store": {"provider": "qdrant"}}}
    +        return provider
    +
    +    def test_routes_to_selfhosted_when_host_set(self, monkeypatch):
    +        captured = {}
    +
    +        class SH(_SentinelBackend):
    +            def __init__(self, api_key, host):
    +                captured["args"] = (api_key, host)
    +
    +        monkeypatch.setattr("plugins.memory.mem0._backend.SelfHostedBackend", SH)
    +        provider = self._provider(monkeypatch, host="http://sh:8888", api_key="[redacted:secret-assignment]")
    +        backend = provider._create_backend()
    +        assert isinstance(backend, SH)
    +        assert captured["args"] == ("adminkey", "http://sh:8888")
    +
    +    def test_routes_to_platform_when_no_host(self, monkeypatch):
    +        class PB(_SentinelBackend):
    +            def __init__(self, api_key):
    +                pass
    +
    +        monkeypatch.setattr("plugins.memory.mem0._backend.PlatformBackend", PB)
    +        provider = self._provider(monkeypatch, host="")
    +        assert isinstance(provider._create_backend(), PB)
    +
    +    def test_routes_to_oss_when_mode_oss(self, monkeypatch):
    +        class OB(_SentinelBackend):
    +            def __init__(self, cfg):
    +                pass
    +
    +        monkeypatch.setattr("plugins.memory.mem0._backend.OSSBackend", OB)
    +        provider = self._provider(monkeypatch, mode="oss")
    +        assert isinstance(provider._create_backend(), OB)
    +
    +    def test_oss_mode_takes_precedence_over_host(self, monkeypatch):
    +        class OB(_SentinelBackend):
    +            def __init__(self, cfg):
    +                pass
    +
    +        monkeypatch.setattr("plugins.memory.mem0._backend.OSSBackend", OB)
    +        provider = self._provider(monkeypatch, mode="oss", host="http://sh:8888")
    +        assert isinstance(provider._create_backend(), OB)
    +
    +    def test_prompt_label_matches_routing_when_oss_and_host_both_set(self, monkeypatch):
    +        # system_prompt_block must mirror _create_backend precedence: with both
    +        # mode=oss and host set, OSS wins the routing, so the prompt must label
    +        # OSS — not "self-hosted (HTTP API)". Guards the prompt-vs-routing lie.
    +        provider = self._provider(monkeypatch, mode="oss", host="http://sh:8888")
    +        provider._user_id = "test"
    +        block = provider.system_prompt_block()
    +        assert "OSS" in block
    +        assert "HTTP API" not in block
    +
    +
    +class TestSelfHostedConfig:
    +    """Config plumbing for self-hosted (MEM0_HOST env + is_available)."""
    +
    +    def test_load_config_reads_mem0_host_env(self, monkeypatch):
    +        monkeypatch.setenv("MEM0_HOST", "http://localhost:8888")
    +        assert mem0_plugin._load_config()["host"] == "http://localhost:8888"
    +
    +    def test_is_available_true_with_host_only(self, monkeypatch):
    +        monkeypatch.delenv("MEM0_API_KEY", raising=False)
    +        monkeypatch.setenv("MEM0_MODE", "platform")
    +        monkeypatch.setenv("MEM0_HOST", "http://localhost:8888")
    +        assert Mem0MemoryProvider().is_available() is True
    +
    +    def test_is_available_false_without_key_or_host(self, monkeypatch):
    +        monkeypatch.delenv("MEM0_API_KEY", raising=False)
    +        monkeypatch.delenv("MEM0_HOST", raising=False)
    +        monkeypatch.setenv("MEM0_MODE", "platform")
    +        assert Mem0MemoryProvider().is_available() is False
    ```
  - `website/docs/user-guide/features/memory-providers.md`

    ```diff
    @@ -317,16 +317,16 @@ echo "OPENVIKING_API_KEY=..." >> ~/.hermes/.env
     
     ### Mem0
     
    -Server-side LLM fact extraction with semantic search, reranking, and automatic deduplication. Supports both Mem0 Platform (cloud) and OSS (self-hosted) modes.
    +Server-side LLM fact extraction with semantic search, reranking, and automatic deduplication. Three connection modes: **Platform** (Mem0 Cloud), **self-hosted dashboard** (a Mem0 server you run via Docker), and **OSS** (Mem0 in-process with your own LLM + vector store).
     
     | | |
     |---|---|
     | **Best for** | Hands-off memory management — Mem0 handles extraction automatically |
    -| **Requires** | `pip install mem0ai` + API key (platform) or LLM/vector store (OSS) |
    -| **Data storage** | Mem0 Cloud (platform) or self-hosted (OSS) |
    -| **Cost** | Mem0 pricing (platform) / free (OSS) |
    +| **Requires** | `pip install mem0ai` + API key (platform), a running Mem0 server (self-hosted dashboard), or an LLM + vector store (OSS) |
    +| **Data storage** | Mem0 Cloud (platform), your own Mem0 server (self-hosted dashboard), or in-process (OSS) |
    +| **Cost** | Mem0 pricing (platform) / free (self-hosted or OSS) |
     
    -**Tools (5):** `mem0_list` (list all memories, paginated), `mem0_search` (semantic search with reranking in platform mode), `mem0_add` (store verbatim facts), `mem0_update` (update by ID), `mem0_delete` (delete by ID)
    +**Tools (4):** `mem0_search` (semantic search; optional reranking in platform mode, off by default), `mem0_add` (store verbatim facts), `mem0_update` (update by ID), `mem0_delete` (delete by ID)
     
     **Setup (Platform):**
     ```bash
    ```

    ```diff
    @@ -348,14 +348,30 @@ Preview without writing files:
     hermes memory setup mem0 --mode oss --oss-llm-key sk-... --dry-run
     ```
     
    +**Setup (Self-Hosted Dashboard):** connect to a Mem0 server you run via Docker (the dashboard's REST API). Set `host` and an API key — either as env vars:
    +
    +```bash
    +echo "MEM0_HOST=http://localhost:8888" >> ~/.hermes/.env
    +echo "MEM0_API_KEY=[redacted:secret-assignment]" >> ~/.hermes/.env
    +```
    +
    +or in `mem0.json`:
    +
    +```json
    +{ "host": "http://localhost:8888", "api_key": "your-admin-api-key" }
    +```
    +
    +The plugin authenticates with `X-API-Key` and uses the server's `/search` / `/memories` routes. `api_key` is optional (omit only for `AUTH_DISABLED` servers). Don't set `mode: oss` — it takes precedence over `host`.
    +
     **Config:** `$HERMES_HOME/mem0.json` (behavioral settings). Only the secret `MEM0_API_KEY` belongs in `~/.hermes/.env`.
     
     | Key | Default | Description |
     |-----|---------|-------------|
    -| `mode` | `platform` | `platform` (Mem0 Cloud) or `oss` (self-hosted) |
    +| `mode` | `platform` | `platform` (Mem0 Cloud) or `oss` (self-managed, in-process) |
    +| `host` | — | Self-hosted Mem0 server URL (Docker dashboard). Routes over HTTP with `X-API-Key`; don't combine with `mode: oss` |
     | `user_id` | `hermes-user` | User identifier |
     | `agent_id` | `hermes` | Agent identifier |
    -| `rerank` | `true` | Rerank search results for relevance (platform mode only) |
    +| `rerank` | `false` | Rerank search results for relevance (platform mode only) |
     
     **OSS supported providers:**
     
    ```

    ```diff
    @@ -595,7 +611,7 @@ hermes memory setup
     |----------|---------|------|-------|-------------|----------------|
     | **Honcho** | Cloud | Paid | 5 | `honcho-ai` | Dialectic user modeling + session-scoped context |
     | **OpenViking** | Self-hosted | Free | 5 | `openviking` + server | Filesystem hierarchy + tiered loading |
    -| **Mem0** | Cloud/Self-hosted | Free/Paid | 5 | `mem0ai` | Server-side LLM extraction + OSS mode |
    +| **Mem0** | Cloud/Self-hosted | Free/Paid | 4 | `mem0ai` | Server-side LLM extraction + self-hosted/OSS modes |
     | **Hindsight** | Cloud/Local | Free/Paid | 3 | `hindsight-client` | Knowledge graph + reflect synthesis |
     | **Holographic** | Local | Free | 2 | None | HRR algebra + trust scoring |
     | **RetainDB** | Cloud | $20/mo | 5 | `requests` | Delta compression |
    ```
- **#60494: feat(mem0): add self-hosted mode to the setup wizard**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `plugins/memory/mem0/README.md`, `plugins/memory/mem0/_setup.py`, `tests/plugins/memory/test_mem0_setup.py`, `website/docs/user-guide/features/memory-providers.md`
  - fix hunks:
  - `plugins/memory/mem0/README.md`

    ```diff
    @@ -42,7 +42,13 @@ The plugin has three connection modes:
     Connect the plugin to a standalone Mem0 server you run yourself — the Docker-shipped Mem0 dashboard/server with its own REST API. Unlike OSS mode (which runs `mem0ai` in-process with your own vector store), here the plugin just talks HTTP to your server.
     
     1. Run the Mem0 server (FastAPI + pgvector) from its Docker image and note its URL and `ADMIN_API_KEY`.
    -2. Point the plugin at it — either via env vars:
    +2. Point the plugin at it — via the setup wizard:
    +   ```bash
    +   hermes memory setup    # select "mem0" → "Self-hosted server"
    +   # Or non-interactive:
    +   hermes memory setup mem0 --mode selfhosted --host http://localhost:8888 --api-key your-admin-api-key
    +   ```
    +   or via env vars:
        ```bash
        echo "MEM0_HOST=http://localhost:8888" >> ~/.hermes/.env
        echo "MEM0_API_KEY=[redacted:secret-assignment]" >> ~/.hermes/.env
    ```
  - `plugins/memory/mem0/_setup.py`

    ```diff
    @@ -67,6 +67,7 @@ def parse_flags(argv: list[str] | None = None) -> dict[str, str]:
         flags: dict[str, str] = {
             "mode": "",
             "api_key": "",
    +        "host": "",
             "oss_llm": "openai",
             "oss_llm_key": "",
             "oss_llm_model": "",
    ```

    ```diff
    @@ -90,6 +91,7 @@ def parse_flags(argv: list[str] | None = None) -> dict[str, str]:
         flag_map = {
             "--mode": "mode",
             "--api-key": "api_key",
    +        "--host": "host",
             "--oss-llm": "oss_llm",
             "--oss-llm-key": "oss_llm_key",
             "--oss-llm-model": "oss_llm_model",
    ```

    ```diff
    @@ -331,6 +333,102 @@ def _setup_platform(hermes_home: str, config: dict, flags: dict[str, str]) -> No
         print("\n  Start a new session to activate.\n")
     
     
    +def _check_selfhosted_server(host: str) -> None:
    +    """Best-effort reachability check for a self-hosted Mem0 server (non-fatal)."""
    +    import urllib.error
    +    import urllib.request as _urlreq
    +
    +    try:
    +        req = _urlreq.Request(f"{host.rstrip('/')}/docs", method="GET")
    +        _urlreq.urlopen(req, timeout=5)
    +        print(f"  ✓ Mem0 server reachable at {host}")
    +    except urllib.error.HTTPError:
    +        # Any HTTP response (401/403/404) still means something is listening.
    +        print(f"  ✓ Mem0 server responding at {host}")
    +    except Exception:
    +        print(f"  ⚠ Could not reach {host} — check the URL and that the server is running.")
    +
    +
    +def _setup_selfhosted(hermes_home: str, config: dict, flags: dict[str, str]) -> None:
    +    """Self-hosted mode setup — point at an existing Mem0 dashboard server.
    +
    +    For users already running the Dockerized Mem0 FastAPI server: stores the
    +    server URL (behavioral -> mem0.json) and an optional API key
    +    (secret -> .env as MEM0_API_KEY).
    +    """
    +    existing_config = {}
    +    config_path = Path(hermes_home) / "mem0.json"
    +    if config_path.exists():
    +        try:
    +            existing_config = json.loads(config_path.read_text())
    +        except Exception:
    +            pass
    +
    +    provider_config = dict(existing_config)
    +
    +    print("\n  Configuring mem0 (self-hosted server):\n")
    +
    +    host = flags.get("host") or _prompt(
    +        "Mem0 server URL (e.g. http://localhost:8888)",
    +        default=provider_config.get("host") or None,
    +    )
    +    if not host:
    +        print("  Error: a server URL is required for self-hosted mode.", file=sys.stderr)
    +        return
    +    host = host.rstrip("/")
    +
    +    env_writes: dict[str, str] = {}
    +    if flags.get("api_key"):
    +        env_writes["MEM0_API_KEY"] = flags["api_key"]
    +    else:
    +        existing_key = os.environ.get("MEM0_API_KEY", "")
    +        if existing_key:
    +            masked = f"...{existing_key[-4:]}" if len(existing_key) > 4 else "set"
    +            val = _prompt(f"Server API key (current: {masked}, blank to keep)", secret=True)
    +        else:
    +            val = _prompt("Server API key (blank if AUTH_DISABLED)", secret=True)
    +        if val:
    +            env_writes["MEM0_API_KEY"] = val
    +
    +    user_id = flags.get("user_id") or _prompt(
    +        "User identifier", default=provider_config.get("user_id") or "hermes-user"
    +    )
    +    agent_id = _prompt("Agent identifier", default=provider_config.get("agent_id") or "hermes")
    +
    +    if flags.get("dry_run"):
    +        print(f"\n  [dry-run] Would save config: host={host}, user_id={user_id}, agent_id={agent_id}")
    +        if env_writes:
    +            print("  [dry-run] Would write API key to .env")
    +        _check_selfhosted_server(host)
    +        print("  [dry-run] No files written.\n")
    +        return
    +
    +    provider_config["mode"] = "platform"  # routing: oss > host > platform; host wins
    +    provider_config["host"] = host
    +    provider_config["user_id"] = user_id
    +    provider_config["agent_id"] = agent_id
    +
    +    from hermes_cli.config import save_config
    +    config["memory"]["provider"] = "mem0"
    +    save_config(config)
    +
    +    from plugins.memory.mem0 import Mem0MemoryProvider
    +    provider = Mem0MemoryProvider()
    +    provider.save_config(provider_config, hermes_home)
    +
    +    if env_writes:
    +        _write_env(Path(hermes_home) / ".env", env_writes)
    +
    +    _check_selfhosted_server(host)
    +    print("\n  Memory provider: mem0 (self-hosted)")
    +    print(f"  Server: {host}")
    +    print("  Activation saved to config.yaml")
    +    print("  Provider config saved")
    +    if env_writes:
    +        print("  API key saved to .env")
    +    print("\n  Start a new session to activate.\n")
    +
    +
     def _setup_oss(hermes_home: str, config: dict, flags: dict[str, str]) -> None:
         """OSS mode setup — build config from flags or interactive prompts.
     
    ```

    ```diff
    @@ -846,10 +944,10 @@ def _check_min_dep_version() -> None:
     def post_setup(hermes_home: str, config: dict) -> None:
         """Entry point called by hermes memory setup framework.
     
    -    Only intercepts when OSS mode is requested (via --mode oss flag or
    -    interactive picker). For platform mode, returns without action so the
    -    framework's schema-based flow handles it (preserving the original
    -    platform onboarding experience).
    +    Routes on --mode (platform / selfhosted / oss); with no flag it shows an
    +    interactive picker with all three modes. Platform keeps the framework's
    +    original schema-based onboarding; selfhosted points at an existing Mem0
    +    server; oss builds a local SDK config.
         """
         _check_min_dep_version()
         flags = parse_flags(sys.argv[1:])
    ```

    ```diff
    @@ -859,6 +957,10 @@ def post_setup(hermes_home: str, config: dict) -> None:
             _setup_oss(hermes_home, config, flags)
             return
     
    +    if flags["mode"] in ("selfhosted", "self-hosted"):
    +        _setup_selfhosted(hermes_home, config, flags)
    +        return
    +
         if flags["mode"] == "platform":
             _setup_platform(hermes_home, config, flags)
             return
    ```

    ```diff
    @@ -866,10 +968,13 @@ def post_setup(hermes_home: str, config: dict) -> None:
         # No --mode flag: show interactive picker
         mode_items = [
             ("Platform", "Mem0 Cloud API (lightweight, just needs an API key)"),
    +        ("Self-hosted server", "Connect to an existing self-hosted Mem0 server (Docker/FastAPI)"),
             ("Open Source", "Run Mem0 locally (self-hosted LLM + vector store)"),
         ]
         mode_idx = _curses_select("  Select mode", mode_items, 0)
         if mode_idx == 1:
    +        _setup_selfhosted(hermes_home, config, flags)
    +    elif mode_idx == 2:
             flags["_mode_from_flag"] = False
             _setup_oss(hermes_home, config, flags)
         else:
    ```
  - `tests/plugins/memory/test_mem0_setup.py`

    ```diff
    @@ -209,6 +209,52 @@ def test_oss_flag_mode(self, tmp_path, monkeypatch):
             assert mem0_json["mode"] == "oss"
             assert mem0_json["oss"]["llm"]["provider"] == "openai"
     
    +    def test_selfhosted_flag_mode(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr("sys.argv", [
    +            "hermes", "--mode", "selfhosted",
    +            "--host", "http://localhost:8888/", "--api-key", "admin-key",
    +        ])
    +        monkeypatch.setattr("plugins.memory.mem0._setup.get_hermes_home", lambda: tmp_path)
    +        _inject_fake_hermes_cli(monkeypatch)
    +        monkeypatch.setattr("plugins.memory.mem0._setup._check_selfhosted_server", lambda h: None)
    +        config = {"memory": {}}
    +        post_setup(str(tmp_path), config)
    +        assert config["memory"]["provider"] == "mem0"
    +        env_content = (tmp_path / ".env").read_text()
    +        assert "MEM0_API_KEY=[redacted:secret-assignment]" in env_content
    +        mem0_json = json.loads((tmp_path / "mem0.json").read_text())
    +        assert mem0_json["host"] == "http://localhost:8888"  # trailing slash stripped
    +        assert mem0_json["user_id"] == "hermes-user"
    +
    +    def test_selfhosted_no_api_key_auth_disabled(self, tmp_path, monkeypatch):
    +        # AUTH_DISABLED servers need no key — setup must not write one.
    +        monkeypatch.setattr("sys.argv", [
    +            "hermes", "--mode", "self-hosted", "--host", "http://mem0.lan:8888",
    +        ])
    +        monkeypatch.setattr("plugins.memory.mem0._setup.get_hermes_home", lambda: tmp_path)
    +        monkeypatch.delenv("MEM0_API_KEY", raising=False)
    +        _inject_fake_hermes_cli(monkeypatch)
    +        monkeypatch.setattr("plugins.memory.mem0._setup._check_selfhosted_server", lambda h: None)
    +        config = {"memory": {}}
    +        post_setup(str(tmp_path), config)
    +        assert not (tmp_path / ".env").exists()
    +        mem0_json = json.loads((tmp_path / "mem0.json").read_text())
    +        assert mem0_json["host"] == "http://mem0.lan:8888"
    +
    +    def test_selfhosted_dry_run_no_files(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr("sys.argv", [
    +            "hermes", "--mode", "selfhosted",
    +            "--host", "http://localhost:8888", "--api-key", "k", "--dry-run",
    +        ])
    +        monkeypatch.setattr("plugins.memory.mem0._setup.get_hermes_home", lambda: tmp_path)
    +        _inject_fake_hermes_cli(monkeypatch)
    +        monkeypatch.setattr("plugins.memory.mem0._setup._check_selfhosted_server", lambda h: None)
    +        config = {"memory": {}}
    +        post_setup(str(tmp_path), config)
    +        assert not (tmp_path / ".env").exists()
    +        assert not (tmp_path / "mem0.json").exists()
    +        assert "provider" not in config["memory"]
    +
     
     class TestDryRun:
     
    ```
  - `website/docs/user-guide/features/memory-providers.md`

    ```diff
    @@ -348,7 +348,15 @@ Preview without writing files:
     hermes memory setup mem0 --mode oss --oss-llm-key sk-... --dry-run
     ```
     
    -**Setup (Self-Hosted Dashboard):** connect to a Mem0 server you run via Docker (the dashboard's REST API). Set `host` and an API key — either as env vars:
    +**Setup (Self-Hosted Dashboard):** connect to a Mem0 server you run via Docker (the dashboard's REST API):
    +
    +```bash
    +hermes memory setup    # select "mem0" → "Self-hosted server"
    +# Or via flags:
    +hermes memory setup mem0 --mode selfhosted --host http://localhost:8888 --api-key your-admin-api-key
    +```
    +
    +Or configure manually — either as env vars:
     
     ```bash
     echo "MEM0_HOST=http://localhost:8888" >> ~/.hermes/.env
    ```

    ```diff
    @@ -381,7 +389,7 @@ The plugin authenticates with `X-API-Key` and uses the server's `/search` / `/me
     | Embedder | openai, ollama |
     | Vector Store | qdrant (local/server), pgvector |
     
    -**Switching modes:** Re-run `hermes memory setup mem0 --mode <platform|oss>` or edit `mem0.json` directly.
    +**Switching modes:** Re-run `hermes memory setup mem0 --mode <platform|selfhosted|oss>` or edit `mem0.json` directly.
     
     ---
     
    ```
- **#31209: Enhance Mem0 memory provider with self-hosting support**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#50479`, `#13377`
- **#52487: fix(mem0): restore self-hosted support dropped by v3 refactor**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#56943`, `#55614`, `#60494`, `#56943`, `#60494`
- **#55614: feat(mem0): add self-hosted dashboard support and remove list command from the plugin**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `2a14205ff`, `53edf6f98`, `9a322726a`, `#56943`, `#60494`

## Fix status

- Merged to main in PR #50479 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-07`.
- Subsystem: `agent`
