Metadata-Version: 2.4
Name: hermes-hy-memory
Version: 0.2.7
Summary: HY Memory provider plugin for Hermes Agent (native, 100% passive injection)
Author: alvinfei
License: MIT
Keywords: hermes,memory,llm,agent,plugin
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: hy-memory>=1.2.18
Provides-Extra: init
Requires-Dist: questionary>=2.0; extra == "init"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: questionary>=2.0; extra == "dev"

# HY Memory Provider for Hermes

A native Memory Provider plugin for [Hermes Agent](https://hermes-agent.nousresearch.com) — **first-class, 100% passive injection**.

## How it works

Hermes calls `prefetch(query)` before every LLM turn. This provider:
1. searches HY Memory with the query (chat path, three-channel recall),
2. formats the hits by layer (`§ [profile]` / `§ [intent]` / `§`),
3. lets Hermes inject that block into the system prompt.

**Once installed, memory works automatically** — no per-turn user action; recall and capture are passive.

### Unified home (`~/.hy-memory/`)

All HY Memory plugins (OpenClaw, OpenCode, Hermes) share one on-disk layout:

```
~/.hy-memory/
├── .venv/          Python + hy-memory SDK (auto-managed)
├── db/             vector DB / cache / logs (MEMORY_DATA_DIR)
└── config.json     last init/doctor snapshot
```

- **Server reuse:** Hermes prefers an existing HTTP server on port **19527** (e.g. started by OpenClaw). It only spawns a new server when none is healthy and `HY_MEMORY_AUTO_START_SERVER` is not disabled.

At the end of each turn Hermes calls `sync_turn(user, assistant)`. To save tokens and avoid duplicate extraction, the provider **buffers turns and writes once every `HY_MEMORY_WRITE_TURN_WINDOW` turns** (default 5) as a single batched, asynchronous write — so the main loop is never blocked. A partial tail (fewer than N turns, e.g. a session that ends after 3 turns) is always flushed on session end / pre-compress / shutdown, so no turns are lost.

## Install

> **Two names, don't confuse them:**
> - `hermes-hy-memory` — the **PyPI package** you `pip install`.
> - `hy-memory` — the **provider name** Hermes activates it under (set in `config.yaml`).
>
> The SDK package `hy-memory` is on public PyPI — a plain `pip install` works, no extra index or credentials needed.

> **The chroma backend needs `sqlite3 >= 3.35`** (default `MEMORY_VECTOR_STORE=chroma`). On systems with an older sqlite (some CentOS/Linux report `unsupported version of sqlite3`): `pip install pysqlite3-binary` and swap it in at process entry (`import sys; sys.modules["sqlite3"] = __import__("pysqlite3")`), or switch to `MEMORY_VECTOR_STORE=qdrant` / `faiss`.

> ⚠️ **pip install alone is NOT enough.** Hermes discovers *memory* providers by
> scanning a directory (`~/.hermes/plugins/<name>/`), **not** via pip entry
> points. So you need two things: (a) the package importable by the Python that
> Hermes actually runs, and (b) a directory/symlink at
> `~/.hermes/plugins/hy-memory/`. The `install` command below does both for you.

### 1. Install the package

```bash
pip install "hermes-hy-memory[init]"   # plugin + hy-memory SDK + wizard deps
```

This gives you the `hermes-hy-memory` CLI. It does **not** yet activate the
provider inside Hermes (see the warning above).

### 2. Configure (the wizard)

```bash
hermes-hy-memory init
```

The 4-step wizard (LLM → embedding → vector store → userId):
1. writes the SDK configuration to `~/.hermes/.env`,
2. sets `memory.provider: hy-memory` in `~/.hermes/config.yaml`, and
3. runs the `install` step below automatically (links the plugin into Hermes).

Processing mode defaults to `pro` (the wizard doesn't ask) — set
`HY_MEMORY_MODE` in `~/.hermes/.env` if you want `ultra` or `lite`.

### 3. Activate in Hermes (`install`)

If you skipped the wizard, or want to (re)link the plugin explicitly:

```bash
hermes-hy-memory install
```

This:
1. **auto-detects the Python that Hermes runs** (follows the `hermes`
   launcher; Hermes usually lives in its own venv that does **not** see your
   normal site-packages),
2. ensures the `hy-memory` SDK is importable there (installs it into that venv
   via `uv`/`pip` if missing),
3. **symlinks** `~/.hermes/plugins/hy-memory/` → the installed package dir
   (so a later `pip install -U` keeps it current — no stale copies),
4. reminds you to restart Hermes.

If auto-detection fails, pass the interpreter explicitly:

```bash
hermes-hy-memory install --hermes-python /usr/local/lib/hermes-agent/venv/bin/python
# other flags: --copy (copy files instead of symlink), --no-sdk (skip SDK install)
```

> Manual equivalent, if you'd rather not use `install`:
> ```bash
> HPY=/usr/local/lib/hermes-agent/venv/bin/python   # the Python Hermes runs
> uv pip install --python "$HPY" hermes-hy-memory    # or: "$HPY" -m pip install
> PKG=$("$HPY" -c "import hy_memory_hermes,os;print(os.path.dirname(hy_memory_hermes.__file__))")
> mkdir -p ~/.hermes/plugins && ln -sfn "$PKG" ~/.hermes/plugins/hy-memory
> ```

### 4. Restart Hermes, then verify

The memory provider is loaded once at agent startup, so **restart Hermes**
(start a new `hermes chat`, or `hermes gateway restart` if you run the gateway
service) before checking.

```bash
hermes memory status        # Hermes side — expect: Plugin: installed ✓ / available ✓
hermes-hy-memory doctor     # SDK side — client builds, vector store reachable
```

#### `hermes memory status` vs `hermes-hy-memory doctor`

These answer two **different** questions — `status` is the decisive one for
"is memory actually turned on in Hermes?":

| | `hermes memory status` | `hermes-hy-memory doctor` |
|---|---|---|
| Whose view | **Hermes side** — the source of truth for activation | **SDK side** — internal health probe |
| Answers | *Will Hermes load & use this provider?* | *Does the memory engine itself work?* |
| Checks | `memory.provider` in `config.yaml`; plugin **directory discovered** (`~/.hermes/plugins/hy-memory/`); `is_available()` (env present) | `HY_MEMORY_USER_ID` set; `HyMemoryClient` constructs (LLM/embedder keys valid); `list_memories` runs (vector store reachable) |
| Healthy output | `Plugin: installed ✓` / `Status: available ✓` / `hy-memory … ← active` | `All checks passed.` |
| If it fails | provider not active — likely the **directory/symlink is missing** (pip alone isn't enough — see step 3) or `memory.provider` not set | the engine can't run — missing API key, unreachable vector store, etc. |

**Rule of thumb:** if `hermes memory status` doesn't say `installed ✓ / available
✓`, Hermes is **not** using HY Memory at all — fix that first (it's almost
always a missing `~/.hermes/plugins/hy-memory/` directory or an unset
`memory.provider`). Only once `status` is green does `doctor` tell you whether
the backend itself is healthy. `doctor` passing while `status` fails means the
engine works but Hermes isn't wired to it.



## Update

The plugin Hermes runs lives in **Hermes' own venv** (the symlink points there),
which is usually **not** the env your `pip install` touches. So upgrading your
shell's copy alone does **not** update Hermes. The reliable one-liner:

```bash
hermes-hy-memory install        # detects the version in Hermes' venv; upgrades it if stale, re-links
```

`install` compares the plugin version already in Hermes' venv against the
latest and **upgrades in place when they differ** (use `-U/--upgrade` to force a
reinstall regardless). Then it refreshes the `~/.hermes/plugins/hy-memory`
symlink.

Manual equivalent, if you prefer:

```bash
uv pip install --python /usr/local/lib/hermes-agent/venv/bin/python -U hermes-hy-memory
# (or: <hermes-python> -m pip install -U hermes-hy-memory)
```

Your settings in `~/.hermes/.env` and `~/.hermes/config.yaml` survive upgrades —
no need to re-run `init`. After upgrading, check the version and **restart
Hermes**:

```bash
hermes-hy-memory --version
hermes memory status            # confirm Plugin: installed ✓ still holds
```


## Uninstall

```bash
# 1. Deactivate the provider — remove (or change) memory.provider in ~/.hermes/config.yaml:
#      memory:
#        provider: hy-memory   ← delete this line (or set another provider)
#
# 2. Remove the plugin directory/symlink Hermes scans
rm -rf ~/.hermes/plugins/hy-memory

# 3. Uninstall the package (from whichever env you installed it in)
pip uninstall hermes-hy-memory
#    if you installed into Hermes' venv:
#    uv pip uninstall --python /usr/local/lib/hermes-agent/venv/bin/python hermes-hy-memory

# 4. (optional) drop the SDK too, if nothing else uses it
pip uninstall hy-memory

# 5. (optional) remove saved settings
#    edit ~/.hermes/.env and delete the HY_MEMORY_* / MEMORY_* lines the wizard added
```

> Removing the package without first clearing `memory.provider: hy-memory` (step
> 1) and the plugin dir (step 2) leaves Hermes pointing at a provider it can no
> longer load. Your stored memories (the vector store / DB) are **not** deleted
> by uninstall — remove that data directory manually if you want a clean slate.


## Configuration

### Environment variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `HY_MEMORY_USER_ID` | ✅ | — | First-level isolation key (your memory namespace) |
| `HY_MEMORY_AGENT_ID` | ❌ | `hermes` | Second-level isolation key |
| `HY_MEMORY_MODE` | ❌ | `pro` | Processing mode: `lite` / `pro` / `ultra` |
| `HY_MEMORY_WRITE_TURN_WINDOW` | ❌ | `5` | Write throttle: buffer turns and persist once every N turns (one batched extraction → saves tokens, avoids per-turn dup writes). `1` = write every turn. Tail (< N turns) is flushed on session end / pre-compress / shutdown. |
| `HY_MEMORY_PREFETCH_MAX_CHARS` | ❌ | `2000` | Max chars of injected prefetch text |
| `HY_MEMORY_SYNC_WORKERS` | ❌ | `2` | sync_turn background thread-pool size |
| `HY_MEMORY_SHUTDOWN_GRACE_SEC` | ❌ | `10` | Max seconds to wait for in-flight writes on shutdown |
| `HY_MEMORY_LOG_LEVEL` | ❌ | (unset) | Set `INFO`/`DEBUG` to attach a console handler (logger `hermes-hy-memory`) and see search/write activity. Unset → inherit Hermes' root logging (usually WARNING, i.e. quiet). |
| `MEMORY_LOG_PROPAGATE` | ❌ | `true` (set by the plugin) | Routes the **SDK's** own `hy_memory.*` logs into Hermes' root logger, so they appear in `hermes logs` and follow its `--level`. The plugin sets this automatically; set `false` to keep SDK logs in their own file only. |
| `OPENAI_API_KEY` (or matching LLM/embedder key) | ✅* | — | Required for `pro`/`ultra`; `lite` only needs the embedder |

*The HY Memory SDK's own LLM/Embedder configuration (see the `hy-memory` docs).

Environment variables take precedence and can be persisted in `~/.hermes/.env` (written automatically by the `init` wizard).

## Processing modes (`HY_MEMORY_MODE`)

| Mode | Write pipeline | Speed | Recall quality |
|---|---|---|---|
| `lite` | pure embedding (no LLM) | fastest | vector similarity only |
| `pro` (default) | LLM fact/identity extraction + reconcile/evolution | medium | profile + fact layers |
| `ultra` | pro + System2 async cognition (Schema/Intention) | fullest | + cross-domain induction + proactive intent |

> ⚠️ **`lite` mode is not suitable for Hermes passive injection / recall.** lite only embeds and skips LLM extraction, so memories stay at the `L1_RAW` layer; the SDK's `list` / `search` filter out `L1_RAW`, meaning lite-written memories **cannot be recalled by `prefetch`** (writes succeed but search is always empty). Use `pro` (default) or `ultra` for Hermes; `lite` only fits write-only / no-semantic-recall scenarios.

## CLI

After `pip install`, the `hermes-hy-memory` subcommands let you run diagnostics and manual operations (outside the Hermes main process):

```bash
# activate the plugin in Hermes (symlink + ensure SDK in Hermes' venv)
hermes-hy-memory install

# health check: env present / client constructs / list works (read-only)
hermes-hy-memory doctor

# manual write
hermes-hy-memory add "I like K-Pop but prefer Jazz"

# manual search
hermes-hy-memory search "music taste" --limit 5

# list recent 20
hermes-hy-memory list

# cross-user test (override env)
hermes-hy-memory search "x" --user-id other_user --agent-id test

# reset: delete this user's memories (DESTRUCTIVE — prompts for confirmation)
hermes-hy-memory reset                 # only the current agent (HY_MEMORY_AGENT_ID)
hermes-hy-memory reset --all-agents    # every agent under this user
hermes-hy-memory reset --all-agents -y # skip the confirmation prompt
```

> `reset` calls the SDK's `delete_all` for the resolved `user_id`. Default
> scope is the current agent only; `--all-agents` wipes every agent for that
> user. It asks for confirmation unless you pass `-y/--yes`. This deletes the
> stored memories themselves (not your `.env`/`config.yaml`).

When loaded by the Hermes main CLI:

```bash
hermes hy-memory doctor
```

## Hooks

| Hook | When | Behavior |
|---|---|---|
| `prefetch(query)` | before each LLM call | search memory → inject into system prompt |
| `sync_turn(user, ast)` | end of each turn | buffer the turn; flush a batched async write every `HY_MEMORY_WRITE_TURN_WINDOW` turns (default 5) |
| `on_session_end(msgs)` | session end | flush the partial (< window) tail buffer + wait for in-flight |
| `on_pre_compress(msgs)` | before context compression | same as `on_session_end`, preserves about-to-be-trimmed content |
| `on_memory_write(action, target, content)` | Hermes built-in memory commands | `add` syncs to HY Memory; `delete` is skipped (target IDs are not interchangeable) |

## Tools (LLM-invoked, optional)

| Tool | Purpose |
|---|---|
| `memory_search(query, limit)` | search memories |
| `memory_add(content)` | write a memory |
| `memory_delete(memory_id)` | delete a memory |
| `memory_list(limit)` | list memories for the current user/agent |

Even with tools disabled, the passive `prefetch` injection guarantees relevant memories reach every LLM call.

## Troubleshooting

### `hermes memory status` shows `Plugin: NOT installed ✗`

The single most common issue: you `pip install`-ed but never created the
directory Hermes scans. **pip alone does not activate a Hermes memory
provider.** Fix:
```bash
hermes-hy-memory install      # symlinks ~/.hermes/plugins/hy-memory + ensures SDK in Hermes' venv
```
then **restart Hermes** and re-check `hermes memory status`. If detection of
Hermes' Python failed, pass it explicitly with `--hermes-python`. See
[Install → step 3](#3-activate-in-hermes-install).

Also check `memory.provider: hy-memory` is set in `~/.hermes/config.yaml`.

### `hermes memory status` shows `Status: not available ✗`

The plugin is discovered but `is_available()` is false — usually
`HY_MEMORY_USER_ID` isn't set in the env Hermes loads (`~/.hermes/.env`). The
status output lists the missing env vars. Re-run `hermes-hy-memory init`.

### `provider not initialized` or all hooks silently no-op

Run the SDK-side health check:
```bash
hermes-hy-memory doctor
```

Common causes:
1. `HY_MEMORY_USER_ID` not set
2. embed/LLM key not set (`OPENAI_API_KEY`, etc.) → `HyMemoryClient(mode="pro")` fails to construct
3. SDK not installed in Hermes' venv: `hermes-hy-memory install` (or pip into that venv)

### I see no logs for search / write

By default the `hermes-hy-memory` logger inherits Hermes' root level (usually
WARNING), so INFO activity is hidden. Set `HY_MEMORY_LOG_LEVEL=INFO` (or
`DEBUG`) in `~/.hermes/.env` to attach a console handler and see
`prefetch/search` and `sync_turn` lines.

### prefetch injects nothing

- There really is no relevant memory — confirm with `hermes-hy-memory list`
- Query too short (< 3 chars) or in the skip list (`ok` / `thanks`, etc.) — by design
- Using `lite` mode? lite memories stay at `L1_RAW` and are never recalled — use `pro`/`ultra`

### sync_turn doesn't seem to write

- Writes are **throttled**: by default they flush every 5 turns (`HY_MEMORY_WRITE_TURN_WINDOW`). A short session flushes its tail on session end. Set `HY_MEMORY_WRITE_TURN_WINDOW=1` to write every turn while debugging
- Default daemon threads are killed when the main process exits. Use Hermes daemon mode in production
- Raise `HY_MEMORY_SHUTDOWN_GRACE_SEC` to let shutdown wait a few more seconds
- Set `HY_MEMORY_LOG_LEVEL=INFO` and check for `[hermes] sync_turn failed: ...`

### `cross-loop` errors (multi-client deployments)

If your Hermes deployment runs multiple `HyMemoryClient` instances (e.g. a multi-tenant server), use `SharedRuntime`:

```python
from hy_memory import HyMemoryClient, SharedRuntime
runtime = await SharedRuntime.create(base_config)
client = HyMemoryClient(cfg, runtime=runtime)
```

A single-process Hermes deployment uses a solo-mode client by default and does not need this.

## Comparison with Mem0-style integrations

[Mem0's Hermes integration](https://github.com/mem0ai/mem0) relies on a **TypeScript SDK**; this plugin uses the **Python SDK**. HY Memory offers three processing depths — lite/pro/ultra (lite skips the LLM, pro does standard extraction, ultra adds System2 cognition) — whereas Mem0 is single-tier LLM extraction.

## Development

```bash
cd plugins/native/hermes
python -m pytest tests/ -v
```

Tests mock `HyMemoryClient` and need no external dependencies (no OPENAI_API_KEY, no running Qdrant).

## Versions

| Plugin | SDK dependency | Notes |
|---|---|---|
| 0.2.6 | `hy-memory>=1.2.18` | Inject format now matches OpenClaw: memories wrapped in `<relevant-memories>…</relevant-memories>`, each `- [N] <time>  <content>`; **evolution chains expanded oldest→newest** (`[v1]…[Latest]`). Inject block logged at **INFO** (visible in `hermes logs`; turn off after debugging). |
| 0.2.5 | `hy-memory>=1.2.18` | `prefetch` now logs the **formatted memory block** it injects into the system prompt at DEBUG — see exactly what context reached the LLM via `hermes logs --level DEBUG` (or `HY_MEMORY_LOG_LEVEL=DEBUG`). |
| 0.2.4 | `hy-memory>=1.2.18` | **`install` now upgrades the SDK too**: it ran `-U hermes-hy-memory` only, which left an already-satisfying `hy-memory` SDK at the old version (Hermes kept running stale SDK code). Now upgrades **both** packages (`-U hermes-hy-memory hy-memory`) and prints honest before→after for each; removed the misleading version-direction message. |
| 0.2.3 | `hy-memory>=1.2.18` | SDK logs now flow into `hermes logs` and follow its `--level`: the plugin sets `MEMORY_LOG_PROPAGATE=true` so the SDK's `hy_memory.*` loggers propagate to Hermes' root logger (instead of writing only to their own file). Requires SDK 1.2.18 (adds the host-managed logging mode). |
| 0.2.2 | `hy-memory>=1.2.17` | **`install` now actually upgrades**: previously it only checked "is the SDK importable?" and skipped — so an old plugin already in Hermes' venv was never updated (Hermes kept running stale code). Now compares the plugin version in Hermes' venv and reinstalls when stale; `-U/--upgrade` to force. `plugin_version` reads via `python -I` so a stray `*.egg-info` in CWD can't report the wrong version. |
| 0.2.1 | `hy-memory>=1.2.17` | **Fix**: `handle_tool_call` now returns a JSON **string** (was a dict) — a dict in the tool-result `content` caused `HTTP 400: content should be a string or a list` from OpenAI-compatible APIs (DeepSeek) right after a successful `memory_add`. Matches bundled providers' `-> str` contract. |
| 0.2.0 | `hy-memory>=1.2.17` | **Install fix**: new `install` subcommand (auto-detect Hermes venv → ensure SDK there → symlink `~/.hermes/plugins/hy-memory/`); `init` now runs it automatically. Corrects the install story — pip entry points do **not** activate Hermes memory providers; the directory scan does. Also: `HY_MEMORY_LOG_LEVEL` for opt-in console logging; logger key renamed to `hermes-hy-memory`; INFO logs for search/write. |
| 0.1.10 | `hy-memory>=1.2.17` | New `reset` subcommand — delete a user's memories (`delete_all`), per-agent by default or `--all-agents`, confirmation unless `-y` |
| 0.1.9 | `hy-memory>=1.2.17` | README: Update / Uninstall sections; `hermes-hy-memory --version` flag |
| 0.1.8 | `hy-memory>=1.2.17` | Turn-window write throttle (`HY_MEMORY_WRITE_TURN_WINDOW`, default 5) — batch writes every N turns, flush partial tail on session end; wizard no longer asks for mode (defaults to `pro`) |
| 0.1.7 | `hy-memory>=1.2.17` | Standalone CLI (`doctor`/`add`/`search`/`list`) now loads `~/.hermes/.env`, so wizard-written settings are visible outside the Hermes daemon |
| 0.1.6 | `hy-memory>=1.2.17` | `init` auto-activates `memory.provider` in `config.yaml`; docs steer to standalone `hermes-hy-memory init` (avoids pre-activation `invalid choice` error) |
| 0.1.5 | `hy-memory>=1.2.17` | Single-level standalone CLI; corrected install docs (real pip + `config.yaml` flow) |
| 0.1.4 | `hy-memory>=1.2.17` | English README; `init` wizard (questionary) |
| 0.1.3 | `hy-memory>=1.2.17` | `init` interactive setup wizard |
| 0.1.2 | `hy-memory>=1.2.17` | Published to public PyPI; channel-dict flatten fix |
