fix(mem0): skip platform key lookup in OSS mode

Conflict resolution on top of main also routes MEM0_MODE / MEM0_HOST / MEM0_AGENT_ID /
MEM0_USER_ID through get_secret: identity and host are .env values like the key, and a
raw environ read hands a secondary profile the default profile's mem0 account.

(cherry picked from commit 7a5863ebafdb193d8ec2f89a7aca309774d6d69a)
This commit is contained in:
이민재
2026-08-31 13:56:31 +09:00
committed by Teknium
parent 778ad61017
commit a1b689f12a
2 changed files with 68 additions and 6 deletions

View File

@@ -12,7 +12,6 @@ from __future__ import annotations
import atexit
import json
import logging
import os
import threading
import time
from contextlib import suppress
@@ -79,11 +78,22 @@ def _load_config() -> dict:
Layering avoids a silent failure when the JSON file exists but lacks fields
like ``api_key`` that the user set in ``.env``."""
from hermes_constants import get_hermes_home
config = {"mode": os.environ.get("MEM0_MODE", "platform"), "api_key": get_secret("MEM0_API_KEY", ""), "host": os.environ.get("MEM0_HOST", ""), "agent_id": os.environ.get("MEM0_AGENT_ID", "hermes"), "oss": {}}
if os.environ.get("MEM0_USER_ID"): # only when explicitly configured, so initialize() can fall back to the gateway-native id
config["user_id"] = os.environ["MEM0_USER_ID"]
# Identity (user/agent id), host and mode are .env values like the key: read them through the
# profile scope too, or a secondary profile's memories land in the default profile's account.
config = {"mode": get_secret("MEM0_MODE", "") or "platform", "host": get_secret("MEM0_HOST", "") or "",
"agent_id": get_secret("MEM0_AGENT_ID", "") or "hermes", "oss": {}}
if user_id := get_secret("MEM0_USER_ID", ""): # only when explicitly configured, so initialize() can fall back to the gateway-native id
config["user_id"] = user_id
file_cfg = _read_mem0_json(get_hermes_home() / "mem0.json")
config.update({k: v for k, v in file_cfg.items() if v is not None and v != ""})
# MEM0_API_KEY authenticates the Platform and self-hosted HTTP backends; pure OSS mode builds its
# backend from the local ``oss`` config and has no platform credential to resolve. Decide after
# mem0.json overrode the env fallback so a scope-less multiplex caller can load an OSS config
# without weakening fail-closed reads for credentialed modes.
if config.get("mode", "platform") == "oss":
config.setdefault("api_key", "")
elif not config.get("api_key"):
config["api_key"] = get_secret("MEM0_API_KEY", "")
return config

View File

@@ -5,6 +5,7 @@ import threading
import time
import pytest
from agent import secret_scope
import plugins.memory.mem0 as mem0_plugin
from plugins.memory.mem0 import Mem0MemoryProvider
@@ -309,6 +310,59 @@ class TestMem0V3Config:
class TestMem0ModeSwitch:
def test_oss_mode_initializes_without_unscoped_platform_key(
self, monkeypatch, tmp_path
):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.delenv("MEM0_API_KEY", raising=False)
(tmp_path / "mem0.json").write_text(
json.dumps(
{
"mode": "oss",
"oss": {"vector_store": {"provider": "qdrant"}},
}
)
)
token = secret_scope.set_secret_scope(None)
secret_scope.set_multiplex_active(True)
try:
provider = Mem0MemoryProvider()
provider._create_backend = lambda: None # type: ignore[method-assign]
provider.initialize("test")
available = provider.is_available()
finally:
secret_scope.set_multiplex_active(False)
secret_scope.reset_secret_scope(token)
assert provider._mode == "oss"
assert provider._api_key == ""
assert available is True
def test_platform_config_still_fails_closed_without_profile_scope(
self, monkeypatch, tmp_path
):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.delenv("MEM0_API_KEY", raising=False)
token = secret_scope.set_secret_scope(None)
secret_scope.set_multiplex_active(True)
try:
with pytest.raises(secret_scope.UnscopedSecretError):
Mem0MemoryProvider().is_available()
finally:
secret_scope.set_multiplex_active(False)
secret_scope.reset_secret_scope(token)
def test_file_api_key_still_overrides_environment(self, monkeypatch, tmp_path):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("MEM0_API_KEY", "env-key")
(tmp_path / "mem0.json").write_text(
json.dumps({"api_key": "file-key"})
)
assert mem0_plugin._load_config()["api_key"] == "file-key"
def test_default_mode_is_platform(self, monkeypatch, tmp_path):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("MEM0_API_KEY", "test-key")
@@ -453,5 +507,3 @@ class TestSelfHostedConfig:
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"