Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions hermes-plugin/memory/memory_tencentdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,73 @@ def _resolve_gateway_api_key() -> Optional[str]:
return None


def _flag_is_false(value: Any) -> bool:
if value is False:
return True
if isinstance(value, str):
return value.strip().lower() in {"false", "0", "no", "off"}
return False


def _get_host_memory_flag(kwargs: Dict[str, Any], name: str) -> Any:
"""Best-effort lookup for Hermes memory flags passed through initialize()."""
if name in kwargs:
return kwargs[name]

memory = kwargs.get("memory")
if isinstance(memory, dict) and name in memory:
return memory[name]

config = kwargs.get("config")
if isinstance(config, dict):
config_memory = config.get("memory")
if isinstance(config_memory, dict) and name in config_memory:
return config_memory[name]

return None


def _log_startup_observability(
*,
session_id: str,
user_id: str,
host: str,
port: int,
gateway_cmd: Optional[str],
api_key: Optional[str],
kwargs: Dict[str, Any],
) -> None:
gateway_mode = "auto-start" if gateway_cmd else "connect-only"
auth_mode = "bearer" if api_key else "none"
logger.info(
"memory-tencentdb provider loaded: reason=Hermes memory.provider selected "
"memory_tencentdb; session=%s user=%s gateway=http://%s:%d "
"gatewayMode=%s auth=%s. The provider will attach/start the Gateway, "
"serve recall/search tools, and capture turns for L0-L3 memory.",
session_id,
user_id,
host,
port,
gateway_mode,
auth_mode,
)

suspicious_flags = [
name
for name in ("memory_enabled", "user_profile_enabled")
if _flag_is_false(_get_host_memory_flag(kwargs, name))
]
if suspicious_flags:
logger.warning(
"memory-tencentdb loaded even though Hermes %s false. In Hermes "
"these fields only control system-prompt/tool advertisement; they "
"do not disable this provider once memory.provider=memory_tencentdb. "
"To disable memory-tencentdb, unset memory.provider or set it to a "
"different provider.",
" and ".join(suspicious_flags),
)


# Candidate locations searched by _discover_gateway_cmd() when the user has not
# set MEMORY_TENCENTDB_GATEWAY_CMD. Order matters: in-tree checkout (next to
# this file) wins over ad-hoc clones in ``$HOME``.
Expand Down Expand Up @@ -751,6 +818,16 @@ def initialize(self, session_id: str, **kwargs) -> None:
# Gateway side directly so both ends agree on the secret.
api_key = _resolve_gateway_api_key()

_log_startup_observability(
session_id=session_id,
user_id=self._user_id,
host=host,
port=port,
gateway_cmd=gateway_cmd,
api_key=api_key,
kwargs=kwargs,
)

self._supervisor = GatewaySupervisor(
host=host,
port=port,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Tests for Hermes startup observability diagnostics."""

from __future__ import annotations

import logging
import pathlib
import sys
import types
import unittest

_THIS_FILE = pathlib.Path(__file__).resolve()
_HERE = _THIS_FILE.parent
for candidate in (
_HERE.parents[2] if len(_HERE.parents) >= 3 else None,
_HERE.parents[3] if len(_HERE.parents) >= 4 else None,
_HERE.parents[4] if len(_HERE.parents) >= 5 else None,
):
if candidate is not None and ((candidate / "plugins").is_dir() or (candidate / "memory").is_dir()):
if str(candidate) not in sys.path:
sys.path.insert(0, str(candidate))

if "agent.memory_provider" not in sys.modules:
agent_module = types.ModuleType("agent")
memory_provider_module = types.ModuleType("agent.memory_provider")

class MemoryProvider: # minimal Hermes test stub
pass

memory_provider_module.MemoryProvider = MemoryProvider
sys.modules.setdefault("agent", agent_module)
sys.modules["agent.memory_provider"] = memory_provider_module

try:
try:
import plugins.memory.memory_tencentdb as mod
except ImportError:
import memory.memory_tencentdb as mod
except ImportError as e: # pragma: no cover - env-dependent
raise unittest.SkipTest(
f"memory_tencentdb provider not importable ({e}); set HERMES_AGENT_ROOT "
"to a hermes-agent checkout if running from the plugin repo."
)


class StartupObservabilityTest(unittest.TestCase):
def test_startup_banner_explains_provider_reason(self):
with self.assertLogs(mod.logger.name, level="INFO") as logs:
mod._log_startup_observability(
session_id="s1",
user_id="u1",
host="127.0.0.1",
port=8420,
gateway_cmd="node gateway",
api_key=None,
kwargs={},
)

text = "\n".join(logs.output)
self.assertIn("memory.provider selected memory_tencentdb", text)
self.assertIn("gateway=http://127.0.0.1:8420", text)
self.assertIn("capture turns for L0-L3 memory", text)

def test_startup_warns_when_hermes_prompt_flags_are_false(self):
with self.assertLogs(mod.logger.name, level="WARNING") as logs:
mod._log_startup_observability(
session_id="s1",
user_id="u1",
host="127.0.0.1",
port=8420,
gateway_cmd=None,
api_key="secret",
kwargs={"memory": {"memory_enabled": False, "user_profile_enabled": "false"}},
)

text = "\n".join(logs.output)
self.assertIn("memory_enabled and user_profile_enabled false", text)
self.assertIn("do not disable this provider", text)


if __name__ == "__main__":
unittest.main()