import importlib.util import json import os import sys from pathlib import Path HERMES_ROOT = Path("/home/tom/hermes-agent") PLUGIN_PATH = Path(__file__).resolve().parents[1] / "plugins" / "memory" / "memory_system" / "__init__.py" def load_plugin_module(): if str(HERMES_ROOT) not in sys.path: sys.path.insert(0, str(HERMES_ROOT)) spec = importlib.util.spec_from_file_location("memory_system_plugin", PLUGIN_PATH) module = importlib.util.module_from_spec(spec) assert spec.loader is not None spec.loader.exec_module(module) return module class FakeClient: def __init__(self, commit_status="success"): self.posts = [] self.gets = [] self.commit_status = commit_status def post(self, path, payload=None): self.posts.append((path, payload or {})) if path == "/memory-system/search": return { "status": "success", "items": [{"source": "openviking", "content": "likes latte", "score": 0.9}], } if path.endswith("/commit"): return {"status": self.commit_status} return {"status": "success"} def get(self, path): self.gets.append(path) return {"status": "success", "profile": {"coffee": "latte"}} def make_provider(): module = load_plugin_module() provider = module.MemorySystemMemoryProvider() provider._client = FakeClient() provider._endpoint = "http://127.0.0.1:1934" provider._user_id = "user-1" provider._session_id = "session-1" return provider def wait_for_sync(provider): thread = provider._sync_thread if thread and thread.is_alive(): thread.join(timeout=2.0) def test_initialize_loads_config_from_hermes_env_file(tmp_path, monkeypatch): module = load_plugin_module() hermes_home = tmp_path / ".hermes" hermes_home.mkdir() env_file = hermes_home / ".env" env_file.write_text( "\n".join( [ "MEMORY_SYSTEM_ENDPOINT=http://127.0.0.1:1934", "MEMORY_SYSTEM_USER_ID=file-user", "MEMORY_SYSTEM_COMMIT_EVERY_TURNS=3", "MEMORY_SYSTEM_COMMIT_INTERVAL_SECONDS=60", "MEMORY_SYSTEM_TIMEOUT_SECONDS=123", ] ), encoding="utf-8", ) for key in list(os.environ): if key.startswith("MEMORY_SYSTEM_"): monkeypatch.delenv(key, raising=False) class InitClient(FakeClient): def __init__(self, endpoint, api_key="", timeout=0): super().__init__() self.endpoint = endpoint self.api_key = api_key self.timeout = timeout def health(self): return True monkeypatch.setattr(module, "_MemorySystemClient", InitClient) provider = module.MemorySystemMemoryProvider() provider.initialize("session-1", hermes_home=str(hermes_home)) assert provider._endpoint == "http://127.0.0.1:1934" assert provider._user_id == "file-user" assert provider._commit_every_turns == 3 assert provider._commit_interval_seconds == 60 assert provider._timeout == 123 assert provider._client.endpoint == "http://127.0.0.1:1934" assert provider._client.timeout == 123 def test_sync_turn_posts_user_and_assistant_messages(): provider = make_provider() provider.sync_turn("hello", "hi there") wait_for_sync(provider) assert provider._client.posts == [ ( "/memory-system/messages", { "user_id": "user-1", "session_id": "session-1", "user_message": "hello", "assistant_message": "hi there", "metadata": {"source": "hermes", "provider": "memory_system"}, }, ) ] def test_on_session_end_commits_after_turn_sync(): provider = make_provider() provider.sync_turn("hello", "hi there") provider.on_session_end([]) assert provider._client.posts[-1] == ( "/memory-system/sessions/session-1/commit", {"user_id": "user-1"}, ) def test_sync_turn_commits_every_configured_turns(): provider = make_provider() provider._commit_every_turns = 2 provider._commit_interval_seconds = 0 provider.sync_turn("turn 1", "reply 1") wait_for_sync(provider) provider.sync_turn("turn 2", "reply 2") wait_for_sync(provider) assert provider._client.posts[-1] == ( "/memory-system/sessions/session-1/commit", {"user_id": "user-1"}, ) assert provider._last_commit_turn == 2 def test_partial_commit_does_not_mark_turns_committed(): provider = make_provider() provider._client = FakeClient(commit_status="partial_success") provider._turn_count = 2 response = provider._commit_session("session-1") assert response["status"] == "partial_success" assert provider._last_commit_turn == 0 def test_on_session_end_skips_when_periodic_commit_is_current(): provider = make_provider() provider._commit_every_turns = 1 provider._commit_interval_seconds = 0 provider.sync_turn("hello", "hi there") wait_for_sync(provider) provider.on_session_end([]) commit_posts = [ post for post in provider._client.posts if post[0] == "/memory-system/sessions/session-1/commit" ] assert len(commit_posts) == 1 def test_search_tool_uses_memory_system_api(): provider = make_provider() result = json.loads(provider.handle_tool_call("memory_system_search", {"query": "coffee", "limit": 3})) assert result["status"] == "success" assert result["items"][0]["content"] == "likes latte" assert provider._client.posts[-1] == ( "/memory-system/search", { "user_id": "user-1", "session_id": "session-1", "query": "coffee", "use_llm": False, "limit": 3, }, ) def test_profile_tool_reads_user_profile(): provider = make_provider() result = json.loads(provider.handle_tool_call("memory_system_profile", {})) assert result["profile"] == {"coffee": "latte"} assert provider._client.gets == ["/memory-system/users/user-1/profile"] def test_remember_tool_writes_and_commits(): provider = make_provider() result = json.loads(provider.handle_tool_call("memory_system_remember", {"content": "likes latte"})) assert result["status"] == "success" assert provider._client.posts == [ ( "/memory-system/messages", { "user_id": "user-1", "session_id": "session-1", "user_message": "likes latte", "metadata": {"source": "hermes", "provider": "memory_system"}, }, ), ( "/memory-system/sessions/session-1/commit", {"user_id": "user-1"}, ), ] def test_register_adds_provider(): module = load_plugin_module() class Ctx: def __init__(self): self.providers = [] def register_memory_provider(self, provider): self.providers.append(provider) ctx = Ctx() module.register(ctx) assert len(ctx.providers) == 1 assert ctx.providers[0].name == "memory_system"