Metadata-Version: 2.4
Name: hermes-hy-memory
Version: 0.1.10
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.17
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.

**No user action required** — memory works automatically.

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 registers 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`.

### 1. Install the package

```bash
pip install hermes-hy-memory        # pulls the hy-memory SDK as a dependency
```

This registers the `hy-memory` memory provider with Hermes via entry points.

### 2. Configure + activate (recommended: the wizard)

Run the **standalone** wizard — it configures everything *and* activates the
provider for you, so this is the only command you need:

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

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

Requires `questionary` (`pip install "hermes-hy-memory[init]"`). The processing
mode defaults to `pro` (the wizard doesn't ask) — set `HY_MEMORY_MODE` in
`~/.hermes/.env` if you want `ultra` or `lite`.

> **Use `hermes-hy-memory` (standalone), not `hermes hy-memory`, for the first
> run.** The Hermes main CLI only exposes `hermes hy-memory ...` *after* the
> provider is active in `config.yaml` — so before activation `hermes hy-memory
> init` fails with `invalid choice: 'hy-memory'`. The standalone
> `hermes-hy-memory` binary (installed by pip) always works. Once activated,
> both forms are equivalent.

If you prefer to do it by hand, set `memory.provider` yourself:

```yaml
# ~/.hermes/config.yaml
memory:
  provider: hy-memory
```

and set the environment variables manually (see [Configuration](#configuration)).

### 3. Verify

```bash
hermes-hy-memory doctor
# once the provider is active, this also works:
hermes hy-memory doctor
```

## Update

```bash
pip install --upgrade hermes-hy-memory     # upgrades the plugin + its hy-memory SDK dep
```

Your settings live in `~/.hermes/.env` and `~/.hermes/config.yaml`, so they
survive upgrades — no need to re-run `init`. Check the new version with
`hermes-hy-memory --version` (or `pip show hermes-hy-memory`), then restart
Hermes to load it.

## 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. Uninstall the package
pip uninstall hermes-hy-memory

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

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

> Uninstalling the package without first clearing `memory.provider: hy-memory`
> leaves Hermes pointing at a provider it can no longer load. Always do step 1.
> 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 |
| `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
# 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

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

Run the 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: `pip install hy-memory`

### 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

- 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
- Check the log 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.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 |
