agentic-memory-hermes 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,184 @@
1
+ # Agentic Memory for Hermes Agent
2
+
3
+ This package is a showcase Hermes Agent integration for Agentic Memory. It ships
4
+ two Hermes provider plugins side by side:
5
+
6
+ - `plugins/memory/agentic-memory`
7
+ - a Hermes `MemoryProvider`
8
+ - recalls Agentic Memory context before model calls
9
+ - exposes explicit memory search/read tools
10
+ - persists completed turns to Agentic Memory in a background thread
11
+ - `plugins/context_engine/agentic-memory`
12
+ - a Hermes `ContextEngine`
13
+ - replaces default compression with Agentic Memory-backed context resolution
14
+ - keeps Hermes token counters and returns valid OpenAI-format messages
15
+
16
+ The directory layout follows the Hermes docs:
17
+
18
+ ```text
19
+ plugins/
20
+ ├── memory/
21
+ │ └── agentic-memory/
22
+ │ ├── __init__.py
23
+ │ ├── cli.py
24
+ │ └── plugin.yaml
25
+ └── context_engine/
26
+ └── agentic-memory/
27
+ ├── __init__.py
28
+ └── plugin.yaml
29
+ ```
30
+
31
+ ## One-Command Install
32
+
33
+ Install the Hermes integration with `uvx` from the published wheel:
34
+
35
+ ```bash
36
+ uvx --from https://github.com/jarmen423/agentic-memory/releases/download/hermes-v0.1.1/agentic_memory_hermes-0.1.1-py3-none-any.whl agentic-memory-hermes install --force
37
+ ```
38
+
39
+ That command installs both Hermes plugin slots and configures Hermes to use
40
+ Agentic Memory:
41
+
42
+ - memory provider: `~/.hermes/plugins/agentic-memory`
43
+ - context engine: `~/.hermes/hermes-agent/plugins/context_engine/agentic-memory`
44
+ - `memory.provider = agentic-memory`
45
+ - `context.engine = agentic-memory`
46
+
47
+ For an isolated Hermes profile, pass the profile home explicitly:
48
+
49
+ ```bash
50
+ uvx --from https://github.com/jarmen423/agentic-memory/releases/download/hermes-v0.1.1/agentic_memory_hermes-0.1.1-py3-none-any.whl agentic-memory-hermes install --force --hermes-home "$HERMES_HOME"
51
+ ```
52
+
53
+ If you install the package directly into Hermes' own Python environment, Hermes
54
+ can also discover the plugin through the `hermes_agent.plugins` entry point:
55
+
56
+ ```bash
57
+ ~/.hermes/hermes-agent/venv/bin/pip install agentic-memory-hermes
58
+ ```
59
+
60
+ Once the project is published to PyPI, the shorter command will be:
61
+
62
+ ```bash
63
+ uvx agentic-memory-hermes install --force
64
+ ```
65
+
66
+ ## Manual Install Into Hermes
67
+
68
+ Copy the memory provider into the Hermes profile plugin directory, then copy
69
+ the context engine into the Hermes project plugin directory. Hermes v0.13 scans
70
+ those two plugin types differently:
71
+
72
+ ```bash
73
+ cp -R packages/am-hermes/plugins/memory/agentic-memory \
74
+ ~/.hermes/plugins/agentic-memory
75
+
76
+ cp -R packages/am-hermes/plugins/context_engine/agentic-memory \
77
+ ~/.hermes/hermes-agent/plugins/context_engine/agentic-memory
78
+ ```
79
+
80
+ Configure Hermes:
81
+
82
+ ```bash
83
+ hermes memory setup
84
+ hermes config set memory.provider agentic-memory
85
+ hermes config set context.engine agentic-memory
86
+ ```
87
+
88
+ The setup wizard writes non-secret configuration to:
89
+
90
+ ```text
91
+ $HERMES_HOME/agentic-memory.json
92
+ ```
93
+
94
+ Secrets are expected in the Hermes `.env` file via:
95
+
96
+ ```bash
97
+ AGENTIC_MEMORY_API_KEY=...
98
+ ```
99
+
100
+ If you need a managed account key during Hermes setup, use the
101
+ Hermes-attributed account URL:
102
+
103
+ ```text
104
+ https://backend.agentmemorylabs.com/account/auth/google/start?connector_source=hermes
105
+ ```
106
+
107
+ Direct website signups do not receive a free hosted runtime trial by default.
108
+ Connector-attributed signups can receive a configured trial when Agent Memory
109
+ Labs is running that acquisition campaign.
110
+
111
+ Optional environment overrides:
112
+
113
+ ```bash
114
+ AGENTIC_MEMORY_BACKEND_URL=https://backend.agentmemorylabs.com
115
+ AGENTIC_MEMORY_DATA_PLANE_URL=
116
+ AGENTIC_MEMORY_WORKSPACE_ID=hermes
117
+ AGENTIC_MEMORY_DEVICE_ID=hermes-local
118
+ AGENTIC_MEMORY_AGENT_ID=hermes-agent
119
+ AGENTIC_MEMORY_PROJECT_ID=
120
+ ```
121
+
122
+ For managed hosted accounts, `AGENTIC_MEMORY_BACKEND_URL` is the control-plane
123
+ URL. During setup the memory provider tries to fetch
124
+ `/product/account/runtime-route`; when the account runtime is ready it saves a
125
+ branded data-plane URL such as `https://rt-abc123.agentmemorylabs.app` in
126
+ `agentic-memory.json`. Normal memory and context calls then use that owned
127
+ runtime URL. Raw provider origins are not written to Hermes config.
128
+
129
+ ## Troubleshooting
130
+
131
+ If Hermes reports `HTTP 403` with `error code: 1010`, the request was blocked
132
+ at the Cloudflare edge before it reached Agentic Memory. Check these first:
133
+
134
+ - `AGENTIC_MEMORY_BACKEND_URL` should normally be
135
+ `https://backend.agentmemorylabs.com`.
136
+ - `AGENTIC_MEMORY_DATA_PLANE_URL` should be empty unless setup saved a current
137
+ account runtime route in `~/.hermes/agentic-memory.json`.
138
+ - `AGENTIC_MEMORY_WORKSPACE_ID` should match the workspace bound to the API key
139
+ when using workspace-bound keys. Account-scoped keys can use the default
140
+ `hermes` workspace.
141
+ - `AGENTIC_MEMORY_API_KEY` must be present in the Hermes environment.
142
+ - Hosted edge rules must allow the `agentic-memory-hermes/0.1.0` User-Agent.
143
+
144
+ The plugin records background write failures instead of printing thread
145
+ tracebacks, so search/context tool errors are the canonical symptom to inspect.
146
+
147
+ ## Backend Contract
148
+
149
+ The Hermes plugins use the same backend routes as the OpenClaw integration:
150
+
151
+ - `GET /product/account/runtime-route`
152
+ - `POST /openclaw/session/register`
153
+ - `POST /openclaw/memory/search`
154
+ - `POST /openclaw/memory/read`
155
+ - `POST /openclaw/memory/ingest-turn`
156
+ - `POST /openclaw/context/resolve`
157
+
158
+ That keeps this integration a thin host adapter. Agentic Memory remains the
159
+ system of record for storage, retrieval, project scope, and hosted/self-hosted
160
+ backend behavior.
161
+
162
+ ## Verify From This Repo
163
+
164
+ The tests use local Hermes ABC shims so this module can be checked without a
165
+ Hermes checkout:
166
+
167
+ ```powershell
168
+ .\.venv-agentic-memory\Scripts\python.exe -m pytest packages/am-hermes/tests -q
169
+ ```
170
+
171
+ ## WSL Paths
172
+
173
+ When this repo is checked out on Windows at `D:\code\agentic-memory`, the WSL
174
+ copy commands are:
175
+
176
+ ```bash
177
+ mkdir -p ~/.hermes/plugins ~/.hermes/hermes-agent/plugins/context_engine
178
+
179
+ cp -R /mnt/d/code/agentic-memory/packages/am-hermes/plugins/memory/agentic-memory \
180
+ ~/.hermes/plugins/agentic-memory
181
+
182
+ cp -R /mnt/d/code/agentic-memory/packages/am-hermes/plugins/context_engine/agentic-memory \
183
+ ~/.hermes/hermes-agent/plugins/context_engine/agentic-memory
184
+ ```
@@ -0,0 +1,9 @@
1
+ """Installer package for the Agentic Memory Hermes Agent plugins.
2
+
3
+ The package does not implement the Hermes memory provider directly. Instead it
4
+ ships the two Hermes plugin directories as package data and exposes a small CLI
5
+ that copies them into the active Hermes profile.
6
+ """
7
+
8
+ __version__ = "0.1.1"
9
+
@@ -0,0 +1,70 @@
1
+ """Hermes entry-point wrapper for Agentic Memory plugins.
2
+
3
+ Hermes can discover Python packages that expose the `hermes_agent.plugins`
4
+ entry-point group, but the existing Hermes memory provider and context engine
5
+ live in file-based plugin directories because Hermes also supports direct
6
+ profile installs. This wrapper lets both distribution styles share the same
7
+ runtime code by loading those bundled plugin modules and forwarding Hermes'
8
+ registration context to each one.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib.util
14
+ import sys
15
+ from importlib import resources
16
+ from pathlib import Path
17
+ from types import ModuleType
18
+ from typing import Any
19
+
20
+ PACKAGE_NAME = "agentic_memory_hermes"
21
+
22
+
23
+ def _bundled_plugins_root() -> Path:
24
+ """Return the bundled plugin directory from a wheel or source checkout."""
25
+
26
+ source_root = Path(__file__).resolve().parents[2] / "plugins"
27
+ if source_root.is_dir():
28
+ return source_root
29
+
30
+ packaged_root = resources.files(PACKAGE_NAME).joinpath("plugins")
31
+ if packaged_root.is_dir():
32
+ return Path(str(packaged_root))
33
+
34
+ raise FileNotFoundError("Could not locate bundled Hermes plugin files.")
35
+
36
+
37
+ def _load_plugin_module(relative_init: str, module_name: str) -> ModuleType:
38
+ """Load one bundled Hermes plugin module from its `__init__.py` file."""
39
+
40
+ init_path = _bundled_plugins_root() / relative_init
41
+ spec = importlib.util.spec_from_file_location(module_name, init_path)
42
+ if spec is None or spec.loader is None:
43
+ raise ImportError(f"Cannot create module spec for {init_path}")
44
+
45
+ module = importlib.util.module_from_spec(spec)
46
+ sys.modules[module_name] = module
47
+ spec.loader.exec_module(module)
48
+ return module
49
+
50
+
51
+ def register(ctx: Any) -> None:
52
+ """Register both Agentic Memory Hermes plugin surfaces.
53
+
54
+ Args:
55
+ ctx: Hermes' plugin registration context. It is expected to expose
56
+ `register_memory_provider` and `register_context_engine`.
57
+ """
58
+
59
+ memory_module = _load_plugin_module(
60
+ "memory/agentic-memory/__init__.py",
61
+ "agentic_memory_hermes._memory_plugin",
62
+ )
63
+ context_module = _load_plugin_module(
64
+ "context_engine/agentic-memory/__init__.py",
65
+ "agentic_memory_hermes._context_engine_plugin",
66
+ )
67
+
68
+ memory_module.register(ctx)
69
+ context_module.register(ctx)
70
+
@@ -0,0 +1,180 @@
1
+ """Install Agentic Memory's Hermes Agent plugins into a Hermes profile.
2
+
3
+ Hermes discovers memory providers and context engines from two different
4
+ directories. That makes a raw Python package insufficient by itself: after the
5
+ package is downloaded by `uvx` or `pipx`, the plugin files still need to be
6
+ placed under the user's Hermes home. This module is the bridge from package
7
+ manager install to Hermes' file-based plugin discovery.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import os
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ from importlib import resources
18
+ from pathlib import Path
19
+
20
+ PACKAGE_NAME = "agentic_memory_hermes"
21
+ PLUGIN_NAME = "agentic-memory"
22
+
23
+
24
+ def _default_hermes_home() -> Path:
25
+ """Return the Hermes profile home used by Hermes Agent.
26
+
27
+ Hermes honors `HERMES_HOME` for profile isolation. When that variable is
28
+ absent, Hermes defaults to `~/.hermes`.
29
+ """
30
+
31
+ return Path(os.environ.get("HERMES_HOME", "~/.hermes")).expanduser()
32
+
33
+
34
+ def _bundled_plugins_root() -> Path:
35
+ """Locate bundled plugin files in a wheel or in the source checkout.
36
+
37
+ Wheels include `agentic_memory_hermes/plugins` through Hatch's
38
+ `force-include` setting. During repo-local tests, the files live beside the
39
+ packaging metadata under `packages/am-hermes/plugins`, so the fallback keeps
40
+ the installer runnable before the wheel is built.
41
+ """
42
+
43
+ source_root = Path(__file__).resolve().parents[2] / "plugins"
44
+ if source_root.is_dir():
45
+ return source_root
46
+
47
+ packaged_root = resources.files(PACKAGE_NAME).joinpath("plugins")
48
+ if packaged_root.is_dir():
49
+ return Path(str(packaged_root))
50
+
51
+ raise FileNotFoundError("Could not locate bundled Hermes plugin files.")
52
+
53
+
54
+ def _copy_tree(source: Path, target: Path, *, force: bool, dry_run: bool) -> None:
55
+ """Copy one Hermes plugin directory to its discovery target.
56
+
57
+ Args:
58
+ source: Bundled plugin directory inside this package.
59
+ target: Hermes discovery directory to create.
60
+ force: Remove an existing target before copying.
61
+ dry_run: Print intended actions without touching the filesystem.
62
+
63
+ Raises:
64
+ FileExistsError: If the target exists and `force` is false.
65
+ """
66
+
67
+ if target.exists() and not force:
68
+ raise FileExistsError(
69
+ f"{target} already exists. Re-run with --force to replace the installed plugin."
70
+ )
71
+
72
+ print(f"{'Would install' if dry_run else 'Installing'} {source} -> {target}")
73
+ if dry_run:
74
+ return
75
+
76
+ if target.exists():
77
+ shutil.rmtree(target)
78
+ target.parent.mkdir(parents=True, exist_ok=True)
79
+ ignore = shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo")
80
+ shutil.copytree(source, target, ignore=ignore)
81
+
82
+
83
+ def _run_hermes_config(key: str, value: str, *, dry_run: bool) -> bool:
84
+ """Set one Hermes config key through Hermes' own CLI when available.
85
+
86
+ Returns:
87
+ True when the command was run successfully, or False when Hermes is not
88
+ on PATH or rejected the setting.
89
+ """
90
+
91
+ command = ["hermes", "config", "set", key, value]
92
+ print(f"{'Would run' if dry_run else 'Running'}: {' '.join(command)}")
93
+ if dry_run:
94
+ return True
95
+
96
+ try:
97
+ subprocess.run(command, check=True)
98
+ except (FileNotFoundError, subprocess.CalledProcessError) as exc:
99
+ print(f"Warning: could not run {' '.join(command)} ({exc}).", file=sys.stderr)
100
+ return False
101
+ return True
102
+
103
+
104
+ def install(args: argparse.Namespace) -> int:
105
+ """Install the memory provider and context engine into Hermes."""
106
+
107
+ hermes_home = Path(args.hermes_home).expanduser()
108
+ plugins_root = _bundled_plugins_root()
109
+
110
+ memory_source = plugins_root / "memory" / PLUGIN_NAME
111
+ context_source = plugins_root / "context_engine" / PLUGIN_NAME
112
+ memory_target = hermes_home / "plugins" / PLUGIN_NAME
113
+ context_target = hermes_home / "hermes-agent" / "plugins" / "context_engine" / PLUGIN_NAME
114
+
115
+ _copy_tree(memory_source, memory_target, force=args.force, dry_run=args.dry_run)
116
+ _copy_tree(context_source, context_target, force=args.force, dry_run=args.dry_run)
117
+
118
+ if args.configure:
119
+ _run_hermes_config("memory.provider", PLUGIN_NAME, dry_run=args.dry_run)
120
+ _run_hermes_config("context.engine", PLUGIN_NAME, dry_run=args.dry_run)
121
+
122
+ print("Agentic Memory Hermes plugin install complete.")
123
+ print(f"Hermes home: {hermes_home}")
124
+ print(f"Memory provider: {memory_target}")
125
+ print(f"Context engine: {context_target}")
126
+ return 0
127
+
128
+
129
+ def build_parser() -> argparse.ArgumentParser:
130
+ """Build the command-line parser for the installer."""
131
+
132
+ parser = argparse.ArgumentParser(
133
+ prog="agentic-memory-hermes",
134
+ description="Install Agentic Memory's Hermes Agent plugins.",
135
+ )
136
+ subparsers = parser.add_subparsers(dest="command")
137
+
138
+ install_parser = subparsers.add_parser(
139
+ "install",
140
+ help="Copy Agentic Memory's Hermes plugins into the active Hermes profile.",
141
+ )
142
+ install_parser.add_argument(
143
+ "--hermes-home",
144
+ default=str(_default_hermes_home()),
145
+ help="Hermes profile home. Defaults to HERMES_HOME or ~/.hermes.",
146
+ )
147
+ install_parser.add_argument(
148
+ "--force",
149
+ action="store_true",
150
+ help="Replace existing Agentic Memory Hermes plugin directories.",
151
+ )
152
+ install_parser.add_argument(
153
+ "--no-configure",
154
+ dest="configure",
155
+ action="store_false",
156
+ help="Copy files only; do not run `hermes config set`.",
157
+ )
158
+ install_parser.add_argument(
159
+ "--dry-run",
160
+ action="store_true",
161
+ help="Show intended file copies and config commands without changing anything.",
162
+ )
163
+ install_parser.set_defaults(func=install, configure=True)
164
+
165
+ return parser
166
+
167
+
168
+ def main(argv: list[str] | None = None) -> int:
169
+ """Entry point used by `uvx agentic-memory-hermes` and `pipx run`."""
170
+
171
+ parser = build_parser()
172
+ args = parser.parse_args(argv)
173
+ if not hasattr(args, "func"):
174
+ parser.print_help()
175
+ return 2
176
+ return args.func(args)
177
+
178
+
179
+ if __name__ == "__main__": # pragma: no cover
180
+ raise SystemExit(main())