{"repo":"NousResearch/hermes-agent","public_id":"57061","display_label":"Supermemory self-hosted server URL configuration","summary":"These items add support for configuring a custom base URL for self-hosted Supermemory instances via the SUPERMEMORY_BASE_URL environment variable, including routing all requests through that URL.","subsystem":"agent","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":67907,"merged_at":"2026-07-20T07:40:41+00:00"},"resolution":{"state":"resolved","by":"pr-67907"},"freshness":{"state":"verified","last_verified_at":"2026-07-20T07:40:41+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":57061,"pr_number":67907,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-07-20T07:40:41+00:00"}],"members":[{"kind":"issue","number":57061,"title":"feat(memory/supermemory): support self-hosted Supermemory via SUPERMEMORY_BASE_URL","url":null,"state":"closed"},{"kind":"pr","number":57074,"title":"feat(memory/supermemory): support self-hosted server via SUPERMEMORY_BASE_URL","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#43388","number":43388}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":67907,"title":"fix(supermemory): route all requests through self-hosted base URL (salvage #67614, #43388)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["contributors/emails/[redacted:email]","plugins/memory/supermemory/README.md","plugins/memory/supermemory/__init__.py","tests/plugins/memory/test_supermemory_provider.py","website/docs/user-guide/features/memory-providers.md","website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/memory-providers.md"],"fix_hunks":[{"file":"contributors/emails/[redacted:email]","hunks":["@@ -0,0 +1 @@\n+Dhravya"]},{"file":"plugins/memory/supermemory/README.md","hunks":["@@ -5,7 +5,8 @@ Semantic long-term memory with profile recall, semantic search, explicit memory\n ## Requirements\n \n - `pip install supermemory`\n-- Supermemory API key from [app.supermemory.ai/integrations?connect=hermes](http://app.supermemory.ai/integrations?connect=hermes)\n+- Hosted: API key from [app.supermemory.ai/integrations?connect=hermes](http://app.supermemory.ai/integrations?connect=hermes)\n+- Self-hosted: a running [Supermemory local](https://supermemory.ai/docs/self-hosting/overview) server and the API key it prints on first boot\n \n ## Setup\n ","@@ -20,12 +21,32 @@ hermes config set memory.provider supermemory\n echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env\n ```\n \n+For a fully self-hosted setup, start Supermemory local and note the API key it\n+prints on first boot:\n+\n+```bash\n+npx supermemory local\n+```\n+\n+Before running `hermes memory setup`, add the local endpoint to\n+`$HERMES_HOME/supermemory.json`:\n+\n+```json\n+{\n+  \"base_url\": \"http://localhost:6767\"\n+}\n+```\n+\n+Then run `hermes memory setup` and enter the local server's API key. Configuring\n+the endpoint first ensures the setup connection probe also stays local.\n+\n ## Config\n \n Config file: `$HERMES_HOME/supermemory.json`\n \n | Key | Default | Description |\n |-----|---------|-------------|\n+| `base_url` | `https://api.supermemory.ai` | API endpoint for hosted or self-hosted Supermemory. Takes priority over `SUPERMEMORY_BASE_URL`. |\n | `container_tag` | `hermes` | Container tag used for search and writes. Supports `{identity}` template for profile-scoped tags (e.g. `hermes-{identity}` → `hermes-coder`). |\n | `auto_recall` | `true` | Inject relevant memory context before turns |\n | `auto_capture` | `true` | Store cleaned user-assistant turns after each response |","@@ -41,8 +62,13 @@ Config file: `$HERMES_HOME/supermemory.json`\n | Variable | Description |\n |----------|-------------|\n | `SUPERMEMORY_API_KEY` | API key (required) |\n+| `SUPERMEMORY_BASE_URL` | Compatibility fallback for the API endpoint when `base_url` is not configured |\n | `SUPERMEMORY_CONTAINER_TAG` | Override container tag (takes priority over config file) |\n \n+Base URL precedence is `supermemory.json` → `SUPERMEMORY_BASE_URL` →\n+`https://api.supermemory.ai`. Hermes resolves it once and uses the same endpoint\n+for SDK operations, setup/status probes, and full-session conversation ingest.\n+\n ## Tools\n \n Kebab-case names are registered for the agent; snake_case aliases remain supported.","@@ -69,6 +95,7 @@ When enabled, Hermes can:\n - prefetch relevant memory context before each turn\n - buffer the full conversation and ingest it as **one session** at session end (or on `/reset`, branch, compression, or shutdown)\n - ingest the full session to the conversations endpoint for richer profile/graph updates\n+- route every SDK, probe, and conversation-ingest request through the configured hosted or self-hosted endpoint\n - expose explicit tools for search, store, forget, and profile access\n \n The session is written once via the conversations endpoint, which drives Supermemory's entity extraction and profile building while keeping a clean, retrievable full transcript."]},{"file":"plugins/memory/supermemory/__init__.py","hunks":["@@ -31,7 +31,7 @@\n _DEFAULT_API_TIMEOUT = 5.0\n _MIN_CAPTURE_LENGTH = 10\n _MAX_ENTITY_CONTEXT_LENGTH = 1500\n-_CONVERSATIONS_URL = \"https://api.supermemory.ai/v4/conversations\"\n+_DEFAULT_BASE_URL = \"https://api.supermemory.ai\"\n _API_KEY_URL = \"[redacted:secret-assignment]\"\n _TRIVIAL_RE = re.compile(\n     r\"^(ok|okay|thanks|thank you|got it|sure|yes|no|yep|nope|k|ty|thx|np)\\.?$\",","@@ -65,6 +65,7 @@ def _default_config() -> dict:\n         \"search_mode\": _DEFAULT_SEARCH_MODE,\n         \"entity_context\": _DEFAULT_ENTITY_CONTEXT,\n         \"api_timeout\": _DEFAULT_API_TIMEOUT,\n+        \"base_url\": \"\",\n         \"enable_custom_container_tags\": False,\n         \"custom_containers\": [],\n         \"custom_container_instructions\": \"\",","@@ -77,6 +78,18 @@ def _sanitize_tag(raw: str) -> str:\n     return tag.strip(\"_\") or _DEFAULT_CONTAINER_TAG\n \n \n+def _resolve_base_url(config_value: Any = \"\") -> str:\n+    \"\"\"Resolve the API base URL: config > SUPERMEMORY_BASE_URL env var > default.\n+\n+    Supports self-hosted Supermemory servers (e.g. http://localhost:6767).\n+    \"\"\"\n+    raw = (\n+        str(config_value or \"\").strip()\n+        or os.environ.get(\"SUPERMEMORY_BASE_URL\", \"\").strip()\n+    )\n+    return (raw or _DEFAULT_BASE_URL).rstrip(\"/\") or _DEFAULT_BASE_URL\n+\n+\n def _clamp_entity_context(text: str) -> str:\n     if not text:\n         return _DEFAULT_ENTITY_CONTEXT","@@ -129,6 +142,7 @@ def _load_supermemory_config(hermes_home: str) -> dict:\n         config[\"api_timeout\"] = max(0.5, min(15.0, float(config.get(\"api_timeout\", _DEFAULT_API_TIMEOUT))))\n     except Exception:\n         config[\"api_timeout\"] = _DEFAULT_API_TIMEOUT\n+    config[\"base_url\"] = str(config.get(\"base_url\", \"\") or \"\").strip()\n \n     # Multi-container support\n     config[\"enable_custom_container_tags\"] = _as_bool(config.get(\"enable_custom_container_tags\"), False)","@@ -263,7 +277,8 @@ def _is_trivial_message(text: str) -> bool:\n \n \n class _SupermemoryClient:\n-    def __init__(self, api_key: str, timeout: float, container_tag: str, search_mode: str = \"hybrid\"):\n+    def __init__(self, api_key: str, timeout: float, container_tag: str,\n+                 search_mode: str = \"hybrid\", base_url: str = \"\"):\n         # Lazy-install the supermemory SDK on demand. ensure() honors\n         # security.allow_lazy_installs (default true) and, on a sealed Docker\n         # venv, redirects the install to the durable target. On failure we","@@ -276,15 +291,16 @@ def __init__(self, api_key: str, timeout: float, container_tag: str, search_mode\n             pass\n         except Exception:\n             pass\n-\n         from supermemory import Supermemory\n \n         self._api_key = [redacted:secret-assignment]\n         self._container_tag = container_tag\n         self._search_mode = search_mode if search_mode in _VALID_SEARCH_MODES else _DEFAULT_SEARCH_MODE\n         self._timeout = timeout\n+        self._base_url = _resolve_base_url(base_url)\n         self._client = Supermemory(\n             api_key=[redacted:secret-assignment]\n+            base_url=self._base_url,\n             timeout=timeout,\n             max_retries=0,\n             default_headers={\"x-sm-source\": \"hermes\"},","@@ -388,7 +404,7 @@ def ingest_conversation(self, session_id: str, messages: list[dict], metadata: d\n             payload[\"metadata\"] = self._merge_metadata(metadata)\n \n         req = urllib.request.Request(\n-            _CONVERSATIONS_URL,\n+            f\"{self._base_url}/v4/conversations\",\n             data=json.dumps(payload).encode(\"utf-8\"),\n             headers={\n                 \"Authorization\": f\"Bearer {self._api_key}\",","@@ -410,6 +426,7 @@ def _resolve_container_tag_for_setup(hermes_home: str, *, identity: str = \"defau\n \n def _probe_supermemory_connection(api_key: str, hermes_home: str, *, identity: str = \"default\") -> dict:\n     config = _load_supermemory_config(hermes_home)\n+    base_url = _resolve_base_url(config[\"base_url\"])\n     status = {\n         \"ok\": False,\n         \"error\": \"\",","@@ -432,6 +449,7 @@ def _probe_supermemory_connection(api_key: str, hermes_home: str, *, identity: s\n             timeout=config[\"api_timeout\"],\n             container_tag=status[\"container_tag\"],\n             search_mode=config[\"search_mode\"],\n+            base_url=base_url,\n         )\n         profile = client.get_profile()\n         facts = [","@@ -531,6 +549,7 @@ def __init__(self):\n         self._search_mode = _DEFAULT_SEARCH_MODE\n         self._entity_context = _DEFAULT_ENTITY_CONTEXT\n         self._api_timeout = _DEFAULT_API_TIMEOUT\n+        self._base_url = _DEFAULT_BASE_URL\n         self._hermes_home = \"\"\n         self._write_enabled = True\n         self._active = False","@@ -645,6 +664,9 @@ def initialize(self, session_id: str, **kwargs) -> None:\n         self._search_mode = self._config[\"search_mode\"]\n         self._entity_context = self._config[\"entity_context\"]\n         self._api_timeout = self._config[\"api_timeout\"]\n+        # Base URL: config > SUPERMEMORY_BASE_URL env var > api.supermemory.ai.\n+        # Supports self-hosted Supermemory servers.\n+        self._base_url = _resolve_base_url(self._config[\"base_url\"])\n         self._enable_custom_containers = self._config[\"enable_custom_container_tags\"]\n         self._custom_containers = self._config[\"custom_containers\"]\n         self._custom_container_instructions = self._config[\"custom_container_instructions\"]","@@ -663,6 +685,7 @@ def initialize(self, session_id: str, **kwargs) -> None:\n                     timeout=self._api_timeout,\n                     container_tag=self._container_tag,\n                     search_mode=self._search_mode,\n+                    base_url=self._base_url,\n                 )\n             except Exception:\n                 logger.warning(\"Supermemory initialization failed\", exc_info=True)"]},{"file":"tests/plugins/memory/test_supermemory_provider.py","hunks":["@@ -17,11 +17,13 @@\n \n \n class FakeClient:\n-    def __init__(self, api_key: str, timeout: float, container_tag: str, search_mode: str = \"hybrid\"):\n+    def __init__(self, api_key: str, timeout: float, container_tag: str, search_mode: str = \"hybrid\",\n+                 base_url: str = \"\"):\n         self.api_key = [redacted:secret-assignment]\n         self.timeout = timeout\n         self.container_tag = container_tag\n         self.search_mode = search_mode\n+        self.base_url = base_url\n         self.add_calls = []\n         self.search_results = []\n         self.profile_response = {\"static\": [], \"dynamic\": [], \"search_results\": []}","@@ -373,6 +375,107 @@ def test_invalid_search_mode_falls_back_to_default(monkeypatch, tmp_path):\n     assert p._search_mode == \"hybrid\"\n \n \n+# -- Base URL tests -------------------------------------------------------------\n+\n+\n+def test_base_url_defaults_to_cloud(monkeypatch, tmp_path):\n+    \"\"\"Without config or env override, the client targets api.supermemory.ai.\"\"\"\n+    monkeypatch.setenv(\"SUPERMEMORY_API_KEY\", \"test-key\")\n+    monkeypatch.delenv(\"SUPERMEMORY_BASE_URL\", raising=False)\n+    monkeypatch.setattr(\"plugins.memory.supermemory._SupermemoryClient\", FakeClient)\n+    p = SupermemoryMemoryProvider()\n+    p.initialize(\"s1\", hermes_home=str(tmp_path), platform=\"cli\")\n+    assert p._base_url == \"https://api.supermemory.ai\"\n+    assert p._client.base_url == \"https://api.supermemory.ai\"\n+\n+\n+def test_base_url_env_var_override(monkeypatch, tmp_path):\n+    \"\"\"SUPERMEMORY_BASE_URL points the provider at a self-hosted server (trailing slash stripped).\"\"\"\n+    monkeypatch.setenv(\"SUPERMEMORY_API_KEY\", \"test-key\")\n+    monkeypatch.setenv(\"SUPERMEMORY_BASE_URL\", \"http://localhost:6767/\")\n+    monkeypatch.setattr(\"plugins.memory.supermemory._SupermemoryClient\", FakeClient)\n+    p = SupermemoryMemoryProvider()\n+    p.initialize(\"s1\", hermes_home=str(tmp_path), platform=\"cli\")\n+    assert p._base_url == \"http://localhost:6767\"\n+    assert p._client.base_url == \"http://localhost:6767\"\n+\n+\n+def test_base_url_config_overrides_env(monkeypatch, tmp_path):\n+    \"\"\"base_url in supermemory.json takes precedence over the env var.\"\"\"\n+    monkeypatch.setenv(\"SUPERMEMORY_API_KEY\", \"test-key\")\n+    monkeypatch.setenv(\"SUPERMEMORY_BASE_URL\", \"http://env-host:6767\")\n+    monkeypatch.setattr(\"plugins.memory.supermemory._SupermemoryClient\", FakeClient)\n+    _save_supermemory_config({\"base_url\": \"http://config-host:6767/\"}, str(tmp_path))\n+    p = SupermemoryMemoryProvider()\n+    p.initialize(\"s1\", hermes_home=str(tmp_path), platform=\"cli\")\n+    assert p._base_url == \"http://config-host:6767\"\n+    assert p._client.base_url == \"http://config-host:6767\"\n+\n+\n+def test_client_passes_custom_base_url_to_sdk(monkeypatch):\n+    \"\"\"SDK operations and raw conversation ingest share one normalized base URL.\"\"\"\n+    import sys\n+    import types\n+\n+    from plugins.memory.supermemory import _SupermemoryClient\n+\n+    captured = {}\n+\n+    class StubSupermemory:\n+        def __init__(self, **kwargs):\n+            captured.update(kwargs)\n+\n+    module = types.ModuleType(\"supermemory\")\n+    module.Supermemory = StubSupermemory\n+    monkeypatch.setitem(sys.modules, \"supermemory\", module)\n+    monkeypatch.setattr(\"tools.lazy_deps.ensure\", lambda *args, **kwargs: None)\n+\n+    client = _SupermemoryClient(\n+        api_key=\"[redacted:secret-assignment]\",\n+        timeout=1.0,\n+        container_tag=\"hermes\",\n+        base_url=\"http://localhost:6767/\",\n+    )\n+\n+    assert client._base_url == \"http://localhost:6767\"\n+    assert captured[\"base_url\"] == \"http://localhost:6767\"\n+\n+\n[redacted:email](\n+    (\"base_url\", \"expected_url\"),\n+    [\n+        (\"https://api.supermemory.ai\", \"https://api.supermemory.ai/v4/conversations\"),\n+        (\"http://localhost:6767\", \"http://localhost:6767/v4/conversations\"),\n+    ],\n+)\n+def test_ingest_conversation_uses_client_base_url(monkeypatch, base_url, expected_url):\n+    \"\"\"Raw conversation ingest follows the same endpoint as SDK operations.\"\"\"\n+    from plugins.memory.supermemory import _SupermemoryClient\n+\n+    client = _SupermemoryClient.__new__(_SupermemoryClient)\n+    client._api_key = \"[redacted:secret-assignment]\"\n+    client._container_tag = \"hermes\"\n+    client._timeout = 1.0\n+    client._base_url = base_url\n+\n+    captured = {}\n+\n+    class _FakeResponse:\n+        def __enter__(self):\n+            return self\n+\n+        def __exit__(self, *args):\n+            return False\n+\n+    def fake_urlopen(req, timeout=None):\n+        captured[\"url\"] = req.full_url\n+        return _FakeResponse()\n+\n+    monkeypatch.setattr(\"urllib.request.urlopen\", fake_urlopen)\n+    client.ingest_conversation(\"s1\", [{\"role\": \"user\", \"content\": \"hello there\"}])\n+    assert captured[\"url\"] == expected_url\n+\n+\n # -- Multi-container tests ----------------------------------------------------\n \n ","@@ -533,9 +636,13 @@ def fake_import(name, *args, **kwargs):\n \n def test_probe_supermemory_connection_success(monkeypatch, tmp_path):\n     _stub_supermemory_importable(monkeypatch)\n-    monkeypatch.setattr(\"plugins.memory.supermemory._SupermemoryClient\", FakeClient)\n+    seen_base_urls = []\n \n     class CountingClient(FakeClient):\n+        def __init__(self, *args, **kwargs):\n+            super().__init__(*args, **kwargs)\n+            seen_base_urls.append(self.base_url)\n+\n         def get_profile(self, query=None, *, container_tag=None):\n             return {\n                 \"static\": [\"Prefers TypeScript\"],","@@ -544,10 +651,13 @@ def get_profile(self, query=None, *, container_tag=None):\n             }\n \n     monkeypatch.setattr(\"plugins.memory.supermemory._SupermemoryClient\", CountingClient)\n+    monkeypatch.setenv(\"SUPERMEMORY_BASE_URL\", \"http://env-host:6767\")\n+    _save_supermemory_config({\"base_url\": \"http://localhost:6767/\"}, str(tmp_path))\n     status = _probe_supermemory_connection(\"test-key\", str(tmp_path))\n     assert status[\"ok\"] is True\n     assert status[\"profile_facts\"] == 2\n     assert status[\"auto_recall\"] is True\n+    assert seen_base_urls == [\"http://localhost:6767\"]\n \n \n def test_probe_supermemory_connection_client_error(monkeypatch, tmp_path):"]},{"file":"website/docs/user-guide/features/memory-providers.md","hunks":["@@ -537,9 +537,9 @@ Semantic long-term memory with profile recall, semantic search, explicit memory\n | | |\n |---|---|\n | **Best for** | Semantic recall with user profiling and session-level graph building |\n-| **Requires** | `pip install supermemory` + [API key](http://app.supermemory.ai/integrations?connect=hermes) |\n-| **Data storage** | Supermemory Cloud |\n-| **Cost** | Supermemory pricing |\n+| **Requires** | `pip install supermemory` + [cloud API key](http://app.supermemory.ai/integrations?connect=hermes), or a [self-hosted server](https://supermemory.ai/docs/self-hosting/overview) |\n+| **Data storage** | Supermemory Cloud or self-hosted |\n+| **Cost** | Supermemory pricing (cloud) / free (self-hosted) |\n \n **Tools:** `supermemory_store` (save explicit memories), `supermemory_search` (semantic similarity search), `supermemory_forget` (forget by ID or best-match query), `supermemory_profile` (persistent profile + recent context)\n ","@@ -551,10 +551,30 @@ hermes config set memory.provider supermemory\n echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env\n ```\n \n+Self-hosted setup:\n+\n+```bash\n+npx supermemory local\n+```\n+\n+Before running `hermes memory setup`, set `base_url` in\n+`$HERMES_HOME/supermemory.json`:\n+\n+```json\n+{\n+  \"base_url\": \"http://localhost:6767\"\n+}\n+```\n+\n+Then run `hermes memory setup` and enter the API key printed by the local\n+server. Configuring the endpoint first ensures the setup connection probe also\n+stays local.\n+\n **Config:** `$HERMES_HOME/supermemory.json`\n \n | Key | Default | Description |\n |-----|---------|-------------|\n+| `base_url` | `https://api.supermemory.ai` | API endpoint for hosted or self-hosted Supermemory. Takes priority over `SUPERMEMORY_BASE_URL`. |\n | `container_tag` | `hermes` | Container tag used for search and writes. Supports `{identity}` template for profile-scoped tags. |\n | `auto_recall` | `true` | Inject relevant memory context before turns |\n | `auto_capture` | `true` | Store cleaned user-assistant turns after each response |","@@ -564,12 +584,15 @@ echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env\n | `search_mode` | `hybrid` | Search mode: `hybrid`, `memories`, or `documents` |\n | `api_timeout` | `5.0` | Timeout for SDK and ingest requests |\n \n-**Environment variables:** `SUPERMEMORY_API_KEY` (required), `SUPERMEMORY_CONTAINER_TAG` (overrides config).\n+**Environment variables:** `SUPERMEMORY_API_KEY` (required), `SUPERMEMORY_BASE_URL` (compatibility fallback when `base_url` is not configured), `SUPERMEMORY_CONTAINER_TAG` (overrides config).\n+\n+Base URL precedence is `supermemory.json` → `SUPERMEMORY_BASE_URL` → `https://api.supermemory.ai`. SDK operations, setup/status probes, and conversation ingest all use the resolved endpoint.\n \n **Key features:**\n - Automatic context fencing — strips recalled memories from captured turns to prevent recursive memory pollution\n - Full-session ingest — the entire conversation is sent once at session boundaries\n - Session-end conversation ingest (to `/v4/conversations`) for richer profile + graph building in Supermemory\n+- End-to-end self-hosted routing — SDK, probe, and conversation-ingest requests use the same configured endpoint\n - Profile facts injected on first turn and at configurable intervals\n - **Profile-scoped containers** — use `{identity}` in `container_tag` (e.g. `hermes-{identity}` → `hermes-coder`) to isolate memories per Hermes profile\n - **Multi-container mode** — enable `enable_custom_container_tags` with a `custom_containers` list to let the agent read/write across named containers. Automatic operations stay on the primary container.","@@ -624,7 +647,7 @@ hermes memory setup\n | **Holographic** | Local | Free | 2 | None | HRR algebra + trust scoring |\n | **RetainDB** | Cloud | $20/mo | 5 | `requests` | Delta compression |\n | **ByteRover** | Local/Cloud | Free/Paid | 3 | `brv` CLI | Pre-compression extraction |\n-| **Supermemory** | Cloud | Paid | 4 | `supermemory` | Context fencing + session graph ingest + multi-container |\n+| **Supermemory** | Cloud/Self-hosted | Free/Paid | 4 | `supermemory` | Context fencing + session graph ingest + multi-container |\n | **Memori** | Cloud | Free/Paid | 5 | `hermes-memori` | Tool-aware memory + structured recall |\n \n ## Profile Isolation"]},{"file":"website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/memory-providers.md","hunks":["@@ -467,9 +467,9 @@ hermes config set memory.provider byterover\n | | |\n |---|---|\n | **适合场景** | 带用户 profile 和会话级图谱构建的语义召回 |\n-| **依赖** | `pip install supermemory` + [API key](http://app.supermemory.ai/integrations?connect=hermes) |\n-| **数据存储** | Supermemory Cloud |\n-| **费用** | Supermemory 定价 |\n+| **依赖** | `pip install supermemory` + [云端 API key](http://app.supermemory.ai/integrations?connect=hermes)，或[自托管服务器](https://supermemory.ai/docs/self-hosting/overview) |\n+| **数据存储** | Supermemory 云端或自托管 |\n+| **费用** | 云端按 Supermemory 定价 / 自托管免费 |\n \n **工具：** `supermemory_store`（保存显式记忆）、`supermemory_search`（语义相似度搜索）、`supermemory_forget`（按 ID 或最佳匹配查询遗忘）、`supermemory_profile`（持久化 profile + 近期上下文）\n ","@@ -481,10 +481,28 @@ hermes config set memory.provider supermemory\n echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env\n ```\n \n+自托管安装：\n+\n+```bash\n+npx supermemory local\n+```\n+\n+在运行 `hermes memory setup` **之前**，先在\n+`$HERMES_HOME/supermemory.json` 中设置 `base_url`：\n+\n+```json\n+{\n+  \"base_url\": \"http://localhost:6767\"\n+}\n+```\n+\n+然后运行 `hermes memory setup` 并输入本地服务器打印的 API key。先配置端点可确保安装连接探测也只访问本地服务器。\n+\n **配置：** `$HERMES_HOME/supermemory.json`\n \n | 键 | 默认值 | 描述 |\n |-----|---------|-------------|\n+| `base_url` | `https://api.supermemory.ai` | 托管或自托管 Supermemory 的 API 端点。优先级高于 `SUPERMEMORY_BASE_URL`。 |\n | `container_tag` | `hermes` | 用于搜索和写入的容器标签。支持 `{identity}` 模板用于 profile 范围隔离。 |\n | `auto_recall` | `true` | 在每轮对话前注入相关记忆上下文 |\n | `auto_capture` | `true` | 每次响应后存储清理过的用户-助手轮次 |","@@ -494,12 +512,15 @@ echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env\n | `search_mode` | `hybrid` | 搜索模式：`hybrid`、`memories` 或 `documents` |\n | `api_timeout` | `5.0` | SDK 和导入请求的超时时间 |\n \n-**环境变量：** `SUPERMEMORY_API_KEY`（必填）、`SUPERMEMORY_CONTAINER_TAG`（覆盖配置）。\n+**环境变量：** `SUPERMEMORY_API_KEY`（必填）、`SUPERMEMORY_BASE_URL`（未配置 `base_url` 时的兼容回退）、`SUPERMEMORY_CONTAINER_TAG`（覆盖配置）。\n+\n+Base URL 优先级为 `supermemory.json` → `SUPERMEMORY_BASE_URL` → `https://api.supermemory.ai`。SDK 操作、安装/状态探测和会话导入都会使用解析后的同一端点。\n \n **主要特性：**\n - 自动上下文隔离——从捕获的轮次中剥离已召回的记忆，防止递归记忆污染\n - 在会话边界时将整个会话**一次性导入**\n - 会话结束时同时导入到对话端点（`/v4/conversations`），用于 Supermemory 的 profile 和图谱构建\n+- 端到端自托管路由——SDK、探测和会话导入请求使用同一配置端点\n - 在第一轮及可配置间隔注入 profile 事实\n - **Profile 范围容器**——在 `container_tag` 中使用 `{identity}`（例如 `hermes-{identity}` → `hermes-coder`），按 Hermes profile 隔离记忆\n - **多容器模式**——启用 `enable_custom_container_tags` 并配置 `custom_containers` 列表，让 Agent 跨命名容器读写。自动操作（同步、预取）保持在主容器上。","@@ -533,7 +554,7 @@ echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env\n | **Holographic** | 本地 | 免费 | 2 | 无 | HRR 代数 + 信任评分 |\n | **RetainDB** | 云端 | $20/月 | 5 | `requests` | 增量压缩 |\n | **ByteRover** | 本地/云端 | 免费/付费 | 3 | `brv` CLI | 预压缩提取 |\n-| **Supermemory** | 云端 | 付费 | 4 | `supermemory` | 上下文隔离 + 会话图谱导入 + 多容器 |\n+| **Supermemory** | 云端/自托管 | 免费/付费 | 4 | `supermemory` | 上下文隔离 + 会话图谱导入 + 多容器 |\n \n ## Profile 隔离\n ","@@ -546,4 +567,4 @@ echo 'SUPERMEMORY_API_KEY=***' >> ~/.hermes/.env\n \n ## 构建记忆提供者\n \n-参见[开发者指南：Memory Provider 插件](/developer-guide/memory-provider-plugin)了解如何创建自己的提供者。\n\\ No newline at end of file\n+参见[开发者指南：Memory Provider 插件](/developer-guide/memory-provider-plugin)了解如何创建自己的提供者。\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}