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.deletes = [] self.commit_status = commit_status def post(self, path, payload=None): self.posts.append((path, payload or {})) if path == "/memory-system/users": return { "status": "success", "account": { "status": "ok", "result": { "account_id": f"{payload['user_id']}_account", "admin_user_id": payload["user_id"], "user_key": f"{payload['user_id']}-key", }, }, } if path == "/memory-system/search": return { "status": "success", "items": [ { "source_backend": "openviking", "content": "likes latte", "score": 0.9, "uri": "viking://user/user-1/memories/a", "vector": [0.1, 0.2], }, { "source_backend": "everos", "memory": "prefers warm coffee", "memory_type": "profile", "original_data": {"large": "payload"}, }, ], "backends": { "openviking": {"status": "success", "result": {"verbose": True}}, "everos": {"status": "success", "result": {"verbose": True}}, }, } if path.endswith("/commit"): return {"status": self.commit_status} if path == "/memory-system/memories": return {"status": "success", "memory": {"status": "ok", "result": {"uri": payload.get("uri")}}} return {"status": "success"} def get(self, path): self.gets.append(path) if path.startswith("/memory-system/memories/content"): return {"status": "success", "memory": {"status": "ok", "result": {"content": "# Python"}}} if path.startswith("/memory-system/memories"): return {"status": "success", "memory": {"status": "ok", "result": {"children": []}}} return {"status": "success", "profile": {"coffee": "latte"}} def delete(self, path): self.deletes.append(path) return {"status": "success", "memory": {"status": "ok", "result": {"estimated_deleted_count": 1}}} 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._user_key = "user-1-key" provider._session_id = "session-1" provider._commit_every_turns = 0 provider._commit_interval_seconds = 0 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_USER_KEY=file-user-key", "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) monkeypatch.chdir(tmp_path) 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._user_key == "file-user-key" 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", "user_key": "user-1-key", "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", "user_key": "user-1-key"}, ) 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", "user_key": "user-1-key"}, ) 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"] == [ { "source_backend": "openviking", "score": 0.9, "text": "likes latte", "uri": "viking://user/user-1/memories/a", }, { "source_backend": "everos", "memory_type": "profile", "text": "prefers warm coffee", }, ] assert "backends" not in result assert "vector" not in json.dumps(result) assert "original_data" not in json.dumps(result) assert provider._client.posts[-1] == ( "/memory-system/search", { "user_id": "user-1", "user_key": "user-1-key", "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?user_key=user-1-key"] 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", "user_key": "user-1-key", "session_id": "session-1", "user_message": "likes latte", "metadata": {"source": "hermes", "provider": "memory_system"}, }, ), ( "/memory-system/sessions/session-1/commit", {"user_id": "user-1", "user_key": "user-1-key"}, ), ] def test_initialize_creates_user_when_user_key_is_not_configured(tmp_path, monkeypatch): module = load_plugin_module() for key in list(os.environ): if key.startswith("MEMORY_SYSTEM_"): monkeypatch.delenv(key, raising=False) monkeypatch.setenv("MEMORY_SYSTEM_ENDPOINT", "http://127.0.0.1:1934") monkeypatch.setenv("MEMORY_SYSTEM_USER_ID", "new-user") monkeypatch.chdir(tmp_path) class InitClient(FakeClient): def __init__(self, endpoint, api_key="", timeout=0): super().__init__() self.endpoint = endpoint def health(self): return True monkeypatch.setattr(module, "_MemorySystemClient", InitClient) provider = module.MemorySystemMemoryProvider() provider.initialize("session-1") assert provider._user_key == "new-user-key" assert provider._client.posts == [("/memory-system/users", {"user_id": "new-user"})] def test_memory_tool_schemas_include_memory_management_tools(): provider = make_provider() tool_names = {schema["name"] for schema in provider.get_tool_schemas()} assert { "memory_system_memory_list", "memory_system_memory_read", "memory_system_memory_write", "memory_system_memory_delete", } <= tool_names def test_memory_list_tool_calls_memories_endpoint(): provider = make_provider() result = json.loads(provider.handle_tool_call("memory_system_memory_list", {"recursive": True})) assert result["status"] == "success" assert provider._client.gets == [ "/memory-system/memories?user_id=user-1&user_key=user-1-key&uri=viking%3A%2F%2Fuser%2Fmemories&recursive=true" ] def test_memory_read_tool_calls_memory_content_endpoint(): provider = make_provider() result = json.loads(provider.handle_tool_call( "memory_system_memory_read", {"uri": "viking://user/memories/preferences/python.md"}, )) assert result["status"] == "success" assert provider._client.gets == [ "/memory-system/memories/content?user_id=user-1&user_key=user-1-key&uri=viking%3A%2F%2Fuser%2Fmemories%2Fpreferences%2Fpython.md" ] def test_memory_write_tool_posts_memory_payload(): provider = make_provider() result = json.loads(provider.handle_tool_call( "memory_system_memory_write", { "uri": "viking://user/memories/preferences/python.md", "content": "# Python", "mode": "replace", "wait": True, }, )) assert result["status"] == "success" assert provider._client.posts == [ ( "/memory-system/memories", { "user_id": "user-1", "user_key": "user-1-key", "uri": "viking://user/memories/preferences/python.md", "content": "# Python", "mode": "replace", "wait": True, }, ) ] def test_memory_delete_tool_deletes_memory_uri_non_recursive_by_default(): provider = make_provider() result = json.loads(provider.handle_tool_call( "memory_system_memory_delete", {"uri": "viking://user/memories/preferences/python.md"}, )) assert result["status"] == "success" assert provider._client.deletes == [ "/memory-system/memories?user_id=user-1&user_key=user-1-key&uri=viking%3A%2F%2Fuser%2Fmemories%2Fpreferences%2Fpython.md&recursive=false" ] 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"