hermes-brain-memory 0.1.0__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.
- hermes_brain_memory/__init__.py +18 -0
- hermes_brain_memory/_vendor/brain/README.md +51 -0
- hermes_brain_memory/_vendor/brain/__init__.py +31 -0
- hermes_brain_memory/_vendor/brain/cli.py +119 -0
- hermes_brain_memory/_vendor/brain/plugin.yaml +7 -0
- hermes_brain_memory/_vendor/brain/provider.py +1221 -0
- hermes_brain_memory/installer.py +308 -0
- hermes_brain_memory-0.1.0.dist-info/METADATA +114 -0
- hermes_brain_memory-0.1.0.dist-info/RECORD +12 -0
- hermes_brain_memory-0.1.0.dist-info/WHEEL +4 -0
- hermes_brain_memory-0.1.0.dist-info/entry_points.txt +2 -0
- hermes_brain_memory-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""hermes-brain-memory — Brain Memory provider for Hermes Agent.
|
|
2
|
+
|
|
3
|
+
This package is an *installer + payload*: it vendors the Brain Memory
|
|
4
|
+
provider (a Hermes ``exclusive``/memory plugin, stdlib only) and ships the
|
|
5
|
+
``hermes-brain-memory`` console script that copies it into
|
|
6
|
+
``$HERMES_HOME/plugins/brain/``, where Hermes' user-plugin memory discovery
|
|
7
|
+
picks it up.
|
|
8
|
+
|
|
9
|
+
The provider itself lives in ``_vendor/brain/`` and is deliberately
|
|
10
|
+
self-contained — Hermes loads it from the plugins directory, never through
|
|
11
|
+
this package's import path.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Brain Memory provider for Hermes Agent
|
|
2
|
+
|
|
3
|
+
Backs Hermes Agent's memory slot with [Brain Memory](https://brainmemory.ai) — a local-first, human-readable Markdown memory store in `~/.brain/`, shared across Hermes, Claude Code, Codex, OpenCode, and OpenClaw. Memories decay, strengthen with use, and connect through an associative network — the model decides *what* to remember; the `brain` CLI handles the plumbing deterministically.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- The `brain` CLI: `npm install -g brain-memory` (then run its installer once so `~/.brain/` exists)
|
|
8
|
+
- Python 3.10+ (no pip dependencies)
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
1. Copy this directory to `$HERMES_HOME/plugins/brain/` (usually `~/.hermes/plugins/brain/`), or use it in-tree at `plugins/memory/brain/`.
|
|
13
|
+
2. Activate it:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
hermes memory setup # guided
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
or in `~/.hermes/config.yaml`:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
memory:
|
|
23
|
+
provider: brain
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. Verify: `hermes memory status`
|
|
27
|
+
|
|
28
|
+
## What it does
|
|
29
|
+
|
|
30
|
+
| Lifecycle | Behavior |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Session start | Injects a budget-bounded context block from `brain session-start`: status line, pinned facts, skills index, relevant memory titles, memorize guidance |
|
|
33
|
+
| Pre-turn | Prefetches relevant memories for the user's message (background thread) |
|
|
34
|
+
| Tools | `brain_recall` (recall + auto-reinforce + full bodies), `brain_memorize` (validated store), `brain_reinforce` |
|
|
35
|
+
| Pre-compression | Reminds the model to store un-memorized notable content before context is discarded |
|
|
36
|
+
| Session end | Appends a session entry to `~/.brain/contexts.json` (last 20 kept) |
|
|
37
|
+
| Built-in memory writes | Mirrors MEMORY.md entries into `~/.brain` as deduplicated observations |
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
Stored in `$HERMES_HOME/brain.json`; environment variables override defaults.
|
|
42
|
+
|
|
43
|
+
| Key | Default | Env var | Description |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| `project` | `hermes` | `BRAIN_PROJECT` | Project label for context-dependent recall |
|
|
46
|
+
| `top_recall` | `6` | `BRAIN_TOP_RECALL` | Max memories per recall (1–25) |
|
|
47
|
+
| `auto_reinforce` | `true` | `BRAIN_AUTO_REINFORCE` | Reinforce memories surfaced by `brain_recall` |
|
|
48
|
+
| `brain_bin` | `brain` | `BRAIN_BIN` | Path to the brain CLI |
|
|
49
|
+
| `sync_on_memorize` | `false` | `BRAIN_SYNC_ON_MEMORIZE` | Push each store to Brain Cloud / Git remote |
|
|
50
|
+
|
|
51
|
+
The built-in MEMORY.md/USER.md memory keeps running alongside — this provider supplements it with a long-lived, cross-agent store. See `integrations/hermes/README.md` in the brain-memory repository for hooks-based and MCP-based alternatives.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Brain Memory provider plugin for Hermes Agent.
|
|
2
|
+
|
|
3
|
+
Local-first, human-readable Markdown memory (~/.brain) shared across Hermes,
|
|
4
|
+
Claude Code, Gemini CLI, Codex, OpenCode, and OpenClaw. See README.md.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
# Normal case: loaded as a package (in-tree plugins/memory/brain or
|
|
9
|
+
# $HERMES_HOME/plugins/brain when the loader imports the package).
|
|
10
|
+
from .provider import BrainMemoryProvider
|
|
11
|
+
except ImportError: # pragma: no cover
|
|
12
|
+
# Fallback: some loaders exec __init__.py as a standalone module, which
|
|
13
|
+
# breaks relative imports. Load provider.py from the same directory.
|
|
14
|
+
import importlib.util as _ilu
|
|
15
|
+
import os as _os
|
|
16
|
+
|
|
17
|
+
_spec = _ilu.spec_from_file_location(
|
|
18
|
+
"hermes_brain_memory_provider",
|
|
19
|
+
_os.path.join(_os.path.dirname(_os.path.abspath(__file__)), "provider.py"),
|
|
20
|
+
)
|
|
21
|
+
_mod = _ilu.module_from_spec(_spec)
|
|
22
|
+
assert _spec.loader is not None
|
|
23
|
+
_spec.loader.exec_module(_mod)
|
|
24
|
+
BrainMemoryProvider = _mod.BrainMemoryProvider
|
|
25
|
+
|
|
26
|
+
__all__ = ["BrainMemoryProvider", "register"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register(ctx) -> None:
|
|
30
|
+
"""Register Brain Memory as a memory provider plugin."""
|
|
31
|
+
ctx.register_memory_provider(BrainMemoryProvider())
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Standalone diagnostics CLI for the Brain Memory provider.
|
|
2
|
+
|
|
3
|
+
Hermes loads user-installed memory providers through its memory-discovery
|
|
4
|
+
path, whose plugin context only accepts ``register_memory_provider`` — it has
|
|
5
|
+
no CLI registration (that API belongs to regular, non-exclusive plugins). So
|
|
6
|
+
these commands run as a plain module instead:
|
|
7
|
+
|
|
8
|
+
python3 ~/.hermes/plugins/brain/cli.py status
|
|
9
|
+
python3 ~/.hermes/plugins/brain/cli.py recall "what did we decide about X"
|
|
10
|
+
|
|
11
|
+
``register_cli(subparser)`` is kept for a possible future in-tree bundling
|
|
12
|
+
where a real CLI hook exists. Stdlib only; shells out to the ``brain`` CLI
|
|
13
|
+
(brain-memory npm package). In-session, use the provider's ``brain_recall`` /
|
|
14
|
+
``brain_memorize`` tools — the agent calls those itself.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import shutil
|
|
22
|
+
import subprocess
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from .provider import BrainMemoryProvider, brain_dir
|
|
27
|
+
except ImportError: # pragma: no cover — standalone loading
|
|
28
|
+
import importlib.util as _ilu
|
|
29
|
+
|
|
30
|
+
_spec = _ilu.spec_from_file_location(
|
|
31
|
+
"hermes_brain_memory_provider_cli",
|
|
32
|
+
os.path.join(os.path.dirname(os.path.abspath(__file__)), "provider.py"),
|
|
33
|
+
)
|
|
34
|
+
_mod = _ilu.module_from_spec(_spec)
|
|
35
|
+
assert _spec.loader is not None
|
|
36
|
+
_spec.loader.exec_module(_mod)
|
|
37
|
+
BrainMemoryProvider = _mod.BrainMemoryProvider
|
|
38
|
+
brain_dir = _mod.brain_dir
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _brain_bin() -> str:
|
|
42
|
+
return os.environ.get("BRAIN_BIN", "brain")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cmd_status(args) -> int:
|
|
46
|
+
bin_name = _brain_bin()
|
|
47
|
+
found = shutil.which(bin_name) or (os.path.isfile(os.path.expanduser(bin_name)) and bin_name)
|
|
48
|
+
store = brain_dir()
|
|
49
|
+
index = store / "index.json"
|
|
50
|
+
print(f"brain CLI : {found or 'NOT FOUND (npm install -g brain-memory)'}")
|
|
51
|
+
print(f"store : {store} ({'present' if store.exists() else 'missing'})")
|
|
52
|
+
print(f"index.json : {'present' if index.exists() else 'missing'}")
|
|
53
|
+
if found and index.exists():
|
|
54
|
+
try:
|
|
55
|
+
env = dict(os.environ)
|
|
56
|
+
env["BRAIN_AGENT"] = "hermes"
|
|
57
|
+
proc = subprocess.run(
|
|
58
|
+
[bin_name, "session-start", "--project", os.environ.get("BRAIN_PROJECT", "hermes")],
|
|
59
|
+
capture_output=True,
|
|
60
|
+
text=True,
|
|
61
|
+
timeout=15,
|
|
62
|
+
env=env,
|
|
63
|
+
)
|
|
64
|
+
if proc.returncode == 0:
|
|
65
|
+
payload = json.loads(proc.stdout)
|
|
66
|
+
print(f"memories : {payload.get('memory_count', '?')}")
|
|
67
|
+
print(f"pinned : {len(payload.get('pinned') or [])}")
|
|
68
|
+
print(f"skills : {len(payload.get('skills_index') or [])}")
|
|
69
|
+
except (OSError, subprocess.TimeoutExpired, ValueError):
|
|
70
|
+
print("memories : (could not query)")
|
|
71
|
+
return 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _cmd_recall(args) -> int:
|
|
75
|
+
provider = BrainMemoryProvider()
|
|
76
|
+
provider.initialize("cli", hermes_home=os.environ.get("HERMES_HOME", ""))
|
|
77
|
+
print(
|
|
78
|
+
provider.handle_tool_call(
|
|
79
|
+
"brain_recall",
|
|
80
|
+
{"query": " ".join(args.query), "reinforce": False},
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _dispatch(args) -> int:
|
|
87
|
+
command = getattr(args, "brain_command", None)
|
|
88
|
+
if command == "status":
|
|
89
|
+
return _cmd_status(args)
|
|
90
|
+
if command == "recall":
|
|
91
|
+
return _cmd_recall(args)
|
|
92
|
+
print("usage: hermes memory {status|recall <query>}")
|
|
93
|
+
return 1
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def register_cli(subparser) -> None:
|
|
97
|
+
"""Attach brain subcommands to an argparse subparser (in-tree use only —
|
|
98
|
+
Hermes exposes no CLI hook to user-installed memory providers)."""
|
|
99
|
+
subs = subparser.add_subparsers(dest="brain_command")
|
|
100
|
+
subs.add_parser("status", help="Show Brain Memory store status")
|
|
101
|
+
recall = subs.add_parser("recall", help="Recall memories from ~/.brain")
|
|
102
|
+
recall.add_argument("query", nargs="+", help="What to recall")
|
|
103
|
+
subparser.set_defaults(func=_dispatch)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main(argv=None) -> int:
|
|
107
|
+
import argparse
|
|
108
|
+
|
|
109
|
+
parser = argparse.ArgumentParser(
|
|
110
|
+
prog="brain-provider",
|
|
111
|
+
description="Brain Memory provider diagnostics (standalone)",
|
|
112
|
+
)
|
|
113
|
+
register_cli(parser)
|
|
114
|
+
args = parser.parse_args(argv)
|
|
115
|
+
return _dispatch(args)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
name: brain
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
description: "Brain Memory — local-first, human-readable Markdown memory (~/.brain) with decay, spreading activation, and spaced reinforcement, shared across Hermes, Claude Code, Codex, OpenCode, and OpenClaw."
|
|
4
|
+
hooks:
|
|
5
|
+
- on_session_end
|
|
6
|
+
- on_pre_compress
|
|
7
|
+
- on_memory_write
|