From e5cc53a30af29209f98fc3a8fab11b4cd5e786c1 Mon Sep 17 00:00:00 2001 From: Dave Heritage Date: Wed, 6 May 2026 10:26:08 -0500 Subject: [PATCH 1/2] Update Memori integration for Hermes Agent - Clarified the optional nature of `MEMORI_PROJECT_ID` in README and documentation, specifying its fallback to Hermes' active context. - Enhanced the README for the Memori integration, detailing the structured memory capabilities and the types of agent activity captured. - Updated version to 0.1.1 in `pyproject.toml` and added `memori` as a dependency in `plugin.yaml`. - Improved error handling for missing `memori` dependency in the client, with corresponding tests to ensure proper reporting. - Adjusted internal logic to utilize project context more effectively, ensuring compatibility with Hermes' project scoping. --- README.md | 3 +- docs/memori-cloud/hermes/overview.mdx | 3 +- docs/memori-cloud/hermes/quickstart.mdx | 3 +- integrations/hermes/README.md | 6 +- integrations/hermes/plugin.yaml | 2 + integrations/hermes/pyproject.toml | 2 +- .../hermes/src/memori_hermes/__init__.py | 55 ++++++++++++------- .../hermes/src/memori_hermes/client.py | 18 +++++- .../hermes/src/memori_hermes/plugin.yaml | 2 + integrations/hermes/tests/test_client.py | 19 ++++++- integrations/hermes/tests/test_provider.py | 6 +- 11 files changed, 88 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 34aaa92a..a67a38a4 100644 --- a/README.md +++ b/README.md @@ -181,9 +181,10 @@ HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}" mkdir -p "$HERMES_HOME" echo "MEMORI_API_KEY=YOUR_MEMORI_API_KEY" >> "$HERMES_HOME/.env" echo "MEMORI_ENTITY_ID=your-app-user-id" >> "$HERMES_HOME/.env" -echo "MEMORI_PROJECT_ID=hermes" >> "$HERMES_HOME/.env" ``` +`MEMORI_PROJECT_ID` is optional; when omitted, the provider uses Hermes' active project context for scoping. + For setup and configuration, see the [Hermes Quickstart](docs/memori-cloud/hermes/quickstart.mdx). For architecture and lifecycle details, see the [Hermes Overview](docs/memori-cloud/hermes/overview.mdx). ## MCP (Connect Your Agent in One Command) diff --git a/docs/memori-cloud/hermes/overview.mdx b/docs/memori-cloud/hermes/overview.mdx index a142188c..c725d8f2 100644 --- a/docs/memori-cloud/hermes/overview.mdx +++ b/docs/memori-cloud/hermes/overview.mdx @@ -5,7 +5,7 @@ description: Give Hermes Agent structured, persistent memory with the Memori mem # Hermes Overview -Memori gives Hermes Agent a structured long-term memory provider. It plugs into Hermes' memory-provider interface, captures completed turns after responses, and exposes explicit tools for memory search, summaries, quota checks, signup, and feedback. +Memori gives Hermes Agent a structured long-term memory provider. It plugs into Hermes' memory-provider interface, captures completed agent activity after responses, and exposes explicit tools for memory search, summaries, quota checks, signup, and feedback. ## How It Works @@ -14,6 +14,7 @@ The Hermes integration is a Python memory provider named `memori`. - **Agent-controlled recall** is available through `memori_recall` and `memori_recall_summary`. - **Advanced augmentation** captures completed turns in the background after responses. - **Account helpers** are available through `memori_signup`, `memori_quota`, and `memori_feedback`. +- **Project scoping** uses `MEMORI_PROJECT_ID` when configured, or Hermes' active project context when omitted. Hermes' built-in `MEMORY.md` and `USER.md` files remain active. Memori is additive and provides durable, structured, cross-session memory in Memori Cloud. It does not mirror edit, replace, or remove operations from those built-in files. diff --git a/docs/memori-cloud/hermes/quickstart.mdx b/docs/memori-cloud/hermes/quickstart.mdx index 5d5001a8..ae151dbe 100644 --- a/docs/memori-cloud/hermes/quickstart.mdx +++ b/docs/memori-cloud/hermes/quickstart.mdx @@ -38,9 +38,10 @@ HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}" mkdir -p "$HERMES_HOME" echo "MEMORI_API_KEY=your-key" >> "$HERMES_HOME/.env" echo "MEMORI_ENTITY_ID=your-user-or-workspace-id" >> "$HERMES_HOME/.env" -echo "MEMORI_PROJECT_ID=hermes" >> "$HERMES_HOME/.env" ``` +`MEMORI_PROJECT_ID` is optional. If omitted, the provider uses Hermes' active workspace, agent identity, user ID, session title, or session ID as the project scope. + ## Verify ```bash diff --git a/integrations/hermes/README.md b/integrations/hermes/README.md index 542cf434..87a1dae0 100644 --- a/integrations/hermes/README.md +++ b/integrations/hermes/README.md @@ -1,6 +1,8 @@ # Memori for Hermes Agent -Memori gives Hermes Agent structured long-term memory. It captures completed user/assistant exchanges after each turn and exposes explicit tools for memory search, summaries, quota checks, signup, and feedback. +Official Memori Labs provider for Hermes Agent, enabling structured long-term memory from agent trace and execution. + +Memori gives Hermes Agent structured, long-term memory from agent trace and execution. It captures completed agent activity, including user goals, assistant decisions, workflow steps, outcomes, constraints, failures, and feedback, and structures that activity into durable memory primitives for future recall. The provider exposes explicit tools for memory recall, summaries, quota checks, signup, and feedback. ## Requirements @@ -58,6 +60,8 @@ Environment variables override file config: - `MEMORI_PROJECT_ID` - `MEMORI_PROCESS_ID` +`MEMORI_PROJECT_ID` is optional. When it is not configured, Hermes-provided project context such as the active workspace, agent identity, user ID, session title, or session ID is used as the Memori project scope. + ## Tools - `memori_recall` diff --git a/integrations/hermes/plugin.yaml b/integrations/hermes/plugin.yaml index c321f8d4..2dbf2a46 100644 --- a/integrations/hermes/plugin.yaml +++ b/integrations/hermes/plugin.yaml @@ -1,5 +1,7 @@ name: memori version: 0.1.0 description: Structured long-term memory for Hermes Agent using Memori Cloud. +pip_dependencies: + - memori hooks: - on_session_end diff --git a/integrations/hermes/pyproject.toml b/integrations/hermes/pyproject.toml index c4f3cb04..32225259 100644 --- a/integrations/hermes/pyproject.toml +++ b/integrations/hermes/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hermes-memori" -version = "0.1.0" +version = "0.1.1" description = "Official Memori Labs long-term memory provider for Hermes Agent" authors = [{name = "Memori Labs Team", email = "noc@memorilabs.ai"}] license = "Apache-2.0" diff --git a/integrations/hermes/src/memori_hermes/__init__.py b/integrations/hermes/src/memori_hermes/__init__.py index 252c5b1d..257ebd9b 100644 --- a/integrations/hermes/src/memori_hermes/__init__.py +++ b/integrations/hermes/src/memori_hermes/__init__.py @@ -24,15 +24,15 @@ class MemoryProvider: # type: ignore[no-redef] logger = logging.getLogger(__name__) PLUGIN_NAME = "memori" -DEFAULT_PROJECT_ID = "hermes" SYNC_JOIN_TIMEOUT_SECS = 5.0 +HERMES_PLATFORM = "hermes" @dataclass class MemoriConfig: api_key: str entity_id: str - project_id: str = DEFAULT_PROJECT_ID + project_id: str | None = None process_id: str | None = None base_url: str | None = None @@ -72,9 +72,7 @@ def _load_config(hermes_home: str | Path | None = None) -> MemoriConfig | None: api_key = _env("MEMORI_API_KEY") or str(file_config.get("apiKey") or "") entity_id = _env("MEMORI_ENTITY_ID") or str(file_config.get("entityId") or "") project_id = ( - _env("MEMORI_PROJECT_ID") - or str(file_config.get("projectId") or "") - or DEFAULT_PROJECT_ID + _env("MEMORI_PROJECT_ID") or str(file_config.get("projectId") or "") or None ) process_id = _env("MEMORI_PROCESS_ID") or file_config.get("processId") base_url = _env("MEMORI_API_URL_BASE") or file_config.get("baseUrl") @@ -98,7 +96,7 @@ def __init__(self, client: MemoriAgentClient | None = None) -> None: self._client = client self._config: MemoriConfig | None = None self._session_id = "" - self._platform = "" + self._project_id = "" self._agent_identity = "" self._sync_thread: threading.Thread | None = None @@ -120,14 +118,15 @@ def initialize(self, session_id: str, **kwargs: Any) -> None: self._config = config self._session_id = str(session_id) - self._platform = str(kwargs.get("platform") or "hermes") self._agent_identity = str(kwargs.get("agent_identity") or "") - process_id = config.process_id or self._agent_identity or self._platform or None + project_id = config.project_id or self._project_id_from_agent(kwargs) + self._project_id = project_id + process_id = config.process_id or self._agent_identity or HERMES_PLATFORM self._client = self._client or MemoriAgentClient( api_key=config.api_key, entity_id=config.entity_id, - project_id=config.project_id, + project_id=project_id, process_id=process_id, base_url=config.base_url, ) @@ -200,7 +199,7 @@ def _sync_turn_background( user_content=user_content, assistant_content=assistant_content, session_id=session_id, - platform=self._platform or "hermes", + platform=HERMES_PLATFORM, ) except MemoriApiError as exc: logger.warning("Memori sync_turn failed: %s", exc) @@ -283,29 +282,47 @@ def get_config_schema(self) -> list[dict[str, Any]]: { "key": "project_id", "description": "Project scope for Memori recall and summaries", - "default": DEFAULT_PROJECT_ID, }, ] def save_config(self, values: dict[str, Any], hermes_home: str) -> None: path = _config_path(hermes_home) path.parent.mkdir(parents=True, exist_ok=True) - config = { - "entityId": values.get("entity_id") or values.get("entityId"), - "projectId": values.get("project_id") - or values.get("projectId") - or DEFAULT_PROJECT_ID, - } + config = _read_file_config(hermes_home) + + entity_id = values.get("entity_id") or values.get("entityId") + if entity_id: + config["entityId"] = entity_id + + project_id = values.get("project_id") or values.get("projectId") + if project_id: + config["projectId"] = project_id + path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8") + def _project_id_from_agent(self, kwargs: dict[str, Any]) -> str: + project_id = str( + kwargs.get("agent_workspace") + or kwargs.get("agent_identity") + or kwargs.get("user_id") + or kwargs.get("session_title") + or self._session_id + ).strip() + if not project_id: + raise RuntimeError( + "Memori project_id is not configured and Hermes did not provide " + "an agent project scope." + ) + return project_id + def _with_project_defaults(self, args: dict[str, Any]) -> dict[str, Any]: params = {k: v for k, v in args.items() if v not in (None, "")} if ( - self._config + self._project_id and not params.get("projectId") and not params.get("project_id") ): - params["projectId"] = self._config.project_id + params["projectId"] = self._project_id return params diff --git a/integrations/hermes/src/memori_hermes/client.py b/integrations/hermes/src/memori_hermes/client.py index a230bd12..8ff234e7 100644 --- a/integrations/hermes/src/memori_hermes/client.py +++ b/integrations/hermes/src/memori_hermes/client.py @@ -4,9 +4,8 @@ from typing import Any -from memori import Memori - DEFAULT_TIMEOUT_SECS = 30 +MEMORI_PLATFORM = "hermes" class MemoriApiError(RuntimeError): @@ -26,6 +25,18 @@ def __init__( base_url: str | None = None, timeout: int = DEFAULT_TIMEOUT_SECS, ) -> None: + try: + from memori import Memori + except ModuleNotFoundError as exc: + missing = exc.name or "memori" + raise RuntimeError( + f"Memori SDK dependency missing: {missing}. Run: pip install memori" + ) from exc + except ImportError as exc: + raise RuntimeError( + "Memori SDK could not be imported. Run: pip install memori" + ) from exc + self.entity_id = entity_id self.project_id = project_id self.memori = Memori(api_key=api_key, base_url=base_url).attribution( @@ -42,13 +53,14 @@ def capture_turn( session_id: str, platform: str, ) -> None: + del platform try: self.memori.capture_agent_turn( user_content=user_content, assistant_content=assistant_content, project_id=self.project_id, session_id=session_id, - platform=platform or "hermes", + platform=MEMORI_PLATFORM, ) except Exception as exc: # noqa: BLE001 raise MemoriApiError(str(exc)) from exc diff --git a/integrations/hermes/src/memori_hermes/plugin.yaml b/integrations/hermes/src/memori_hermes/plugin.yaml index c321f8d4..2dbf2a46 100644 --- a/integrations/hermes/src/memori_hermes/plugin.yaml +++ b/integrations/hermes/src/memori_hermes/plugin.yaml @@ -1,5 +1,7 @@ name: memori version: 0.1.0 description: Structured long-term memory for Hermes Agent using Memori Cloud. +pip_dependencies: + - memori hooks: - on_session_end diff --git a/integrations/hermes/tests/test_client.py b/integrations/hermes/tests/test_client.py index e8033f6f..1ea9c8b2 100644 --- a/integrations/hermes/tests/test_client.py +++ b/integrations/hermes/tests/test_client.py @@ -2,6 +2,7 @@ import sys from pathlib import Path +from unittest.mock import patch import pytest @@ -10,6 +11,22 @@ from memori_hermes.client import MemoriAgentClient, MemoriApiError # noqa: E402 +def test_client_reports_missing_memori_dependency() -> None: + def fake_import(name, *args, **kwargs): + if name == "memori": + raise ModuleNotFoundError("No module named 'memori'", name="memori") + return original_import(name, *args, **kwargs) + + original_import = __import__ + with patch("builtins.__import__", side_effect=fake_import): + with pytest.raises(RuntimeError, match="pip install memori"): + MemoriAgentClient( + api_key="key", + entity_id="entity", + project_id="project", + ) + + def make_client() -> MemoriAgentClient: return MemoriAgentClient( api_key="key", @@ -74,7 +91,7 @@ def fake_capture_agent_turn(**kwargs): "assistant_content": "a", "project_id": "project", "session_id": "session", - "platform": "cli", + "platform": "hermes", } diff --git a/integrations/hermes/tests/test_provider.py b/integrations/hermes/tests/test_provider.py index ff78e84e..77979e67 100644 --- a/integrations/hermes/tests/test_provider.py +++ b/integrations/hermes/tests/test_provider.py @@ -65,18 +65,17 @@ def test_sync_turn_runs_background_capture() -> None: client = FakeClient() provider = MemoriMemoryProvider(client=client) provider._session_id = "session-1" - provider._platform = "cli" provider.sync_turn("hello", "hi") provider.shutdown() - assert client.captured == [("hello", "hi", "session-1", "cli")] + assert client.captured == [("hello", "hi", "session-1", "hermes")] def test_handle_recall_adds_project_default() -> None: client = FakeClient() provider = MemoriMemoryProvider(client=client) - provider._config = type("Config", (), {"project_id": "project-1"})() + provider._project_id = "project-1" result = json.loads(provider.handle_tool_call("memori_recall", {"query": "prefs"})) @@ -103,3 +102,4 @@ def test_config_schema_contains_required_setup_fields() -> None: keys = {field["key"] for field in schema} assert {"api_key", "entity_id", "project_id"} <= keys assert schema[0]["env_var"] == "MEMORI_API_KEY" + assert "default" not in schema[2] From c877563e62bf0a48d9ac720b62b3e866209b9678 Mon Sep 17 00:00:00 2001 From: Dave Heritage Date: Wed, 6 May 2026 10:29:53 -0500 Subject: [PATCH 2/2] update hermes readme --- integrations/hermes/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/hermes/README.md b/integrations/hermes/README.md index 87a1dae0..6efd6d77 100644 --- a/integrations/hermes/README.md +++ b/integrations/hermes/README.md @@ -2,7 +2,7 @@ Official Memori Labs provider for Hermes Agent, enabling structured long-term memory from agent trace and execution. -Memori gives Hermes Agent structured, long-term memory from agent trace and execution. It captures completed agent activity, including user goals, assistant decisions, workflow steps, outcomes, constraints, failures, and feedback, and structures that activity into durable memory primitives for future recall. The provider exposes explicit tools for memory recall, summaries, quota checks, signup, and feedback. +Memori gives Hermes Agent structured, long-term memory from agent trace and execution. It captures completed agent activity — including user goals, assistant decisions, tool usage, workflow steps, outcomes, constraints, failures, and feedback — and structures that activity into durable memory primitives for future recall. This allows Hermes agents to learn from prior execution, preserve workflow context, avoid repeated mistakes, and become more efficient over time. The provider exposes explicit tools for memory recall, summaries, quota checks, signup, and feedback. ## Requirements