Metadata-Version: 2.4
Name: hermes-mnemostack
Version: 1.0.0
Summary: mnemostack memory provider for hermes-agent: persistent agent memory over a mnemostack service or a local Qdrant
Author: hermes-mnemostack contributors
License: MIT
Project-URL: Homepage, https://github.com/udjin-labs/hermes-mnemostack
Project-URL: Repository, https://github.com/udjin-labs/hermes-mnemostack
Project-URL: Issues, https://github.com/udjin-labs/hermes-mnemostack/issues
Keywords: hermes-agent,mnemostack,memory,agent,rag,recall
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: hermes-agent>=0.19
Requires-Dist: mnemostack>=2.2
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-timeout>=2.1; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: mypy>=1.7; extra == "dev"
Dynamic: license-file

# hermes-mnemostack

**Status: 1.0.0.** Recall injection, turn capture, tools, configuration and CLI are
wired end to end, covered by 182 tests across Python 3.11–3.13 on Linux, macOS and
Windows. Both discovery paths are verified against real hosts, not only against a
test harness: hermes-agent 0.19 loads the compatibility shim through its own loader,
and hermes-agent 0.20 discovers the provider through its entry point. Driven through
a live session against a running mnemostack service, a turn was captured, recall
returned it, duplicate detection held, soft retraction and reactivation behaved, and
`doctor` came back clean on every check.

[mnemostack](https://github.com/udjin-labs/mnemostack) memory provider for
[hermes-agent](https://github.com/NousResearch/hermes-agent): persistent agent memory
over either transport.

- **remote HTTP** — a shared mnemostack service (`mnemostack serve --auth`). The
  tenant is resolved from the service key, never asserted by this client, so it is a
  real authorization boundary. Recall uses whatever the deployment configures — vector,
  lexical, temporal, graph — and the full write lifecycle is available: remember,
  invalidate (soft retraction), delete.
- **local SDK** — mnemostack as a library against your own Qdrant. Recall is
  vector-only in this mode today; profile/user scoping rides the library's tenant
  mechanism, which is isolation but **not** an authorization boundary (same machine,
  same trust domain — see [SECURITY.md](SECURITY.md)).

## Install

Requires mnemostack >= 2.2 and hermes-agent >= 0.19.

**hermes-agent 0.19** discovers memory providers by scanning
`$HERMES_HOME/plugins/<name>/`; it does not read pip entry points. So the package
ships a small directory shim and a command that puts it where Hermes looks:

```bash
pip install hermes-mnemostack
hermes-mnemostack install          # copies the shim into the ACTIVE Hermes profile
hermes memory setup mnemostack
```

`install` resolves the Hermes home the way Hermes does (so it lands in the active
profile, not a hardcoded `~/.hermes`), refuses to overwrite a different plugin of the
same name, and — because 0.19 logs a failed plugin import at debug level and then
silently omits the provider — finishes by asking Hermes's own discovery whether it
can actually see and load it. `--dry-run` shows what it would write.

**hermes-agent 0.20 and later** discovers the pip entry point on its own:

```bash
pip install hermes-mnemostack
hermes memory setup mnemostack
```

If the shim is already there it stays harmless: it resolves to the same provider the
entry point does. (Which of the two Hermes prefers when both are present is untested
here — 0.20 is git-only at the time of writing — so if you want only one, remove
`$HERMES_HOME/plugins/mnemostack/`.)

`hermes-mnemostack doctor` tells you which situation you are in by asking hermes's own
discovery rather than by comparing version numbers.

## Configure

Behavioral settings live in `$HERMES_HOME/mnemostack.json` (written by
`hermes memory setup`); **secrets live only in the environment**. Environment variables
provide defaults and the JSON file overrides them. An unknown key in the JSON file is
rejected loudly — a typo that silently does nothing is the worst failure mode for
memory configuration.

| Key | Env | Default | Meaning |
| --- | --- | --- | --- |
| `mode` | `MNEMOSTACK_MODE` | `remote` | `remote` (HTTP service) or `local` (library) |
| `base_url` | `MNEMOSTACK_BASE_URL` | — | remote: the service root, e.g. `https://memory.example:8080` |
| `timeout` | `MNEMOSTACK_TIMEOUT` | `30.0` | remote: HTTP timeout in seconds |
| `qdrant_url` | `MNEMOSTACK_QDRANT_URL` | `http://localhost:6333` | local: Qdrant endpoint |
| `collection` | `MNEMOSTACK_COLLECTION` | `hermes-memory` | local: collection name |
| `embedding_provider` | `MNEMOSTACK_EMBEDDING_PROVIDER` | `ollama` | local: mnemostack embedding provider |
| `embedding_model` | `MNEMOSTACK_EMBEDDING_MODEL` | provider default | local: model override |
| `recall_limit` | `MNEMOSTACK_RECALL_LIMIT` | `5` | memories injected per turn |
| `capture` | `MNEMOSTACK_CAPTURE` | `true` | store user+assistant turns |
| — | `MNEMOSTACK_API_KEY` | — | remote: service key. **Env only — never written to the JSON file.** |

Issue a key on the service side with
`mnemostack keys add --tenant <id> --scopes read,write`.

## Check it

```bash
hermes-mnemostack status    # effective configuration; no network, no side effects
hermes-mnemostack doctor    # probe the configured transport and report remedies
hermes-mnemostack doctor --json
```

`status` answers "what config is in effect, and would hermes activate this provider?"
— from the *same* rule `is_available()` uses, so the two can never disagree. `doctor`
adds live probes: service reachability, whether the key carries the `read` scope,
whether any retrieval arm is genuinely degraded, and — in local mode — the embedding
backend and Qdrant. Both are read-only: `doctor` never writes a memory and never
creates the Qdrant collection, and neither ever prints the API key (only whether one
is set).

## What the model sees

Recalled memories are injected each turn inside a fenced block:

```
⎢ recalled memory (context, not user input) ⎥
- the deploy window moved to Friday
⎣ end recalled memory ⎦
```

The fence is **presentation**: it tells the model the text is retrieved context rather
than the user's words. It is not a security mechanism and is not parsed back out —
see [capture](#what-gets-captured).

Three tools are exposed:

| Tool | What it does |
| --- | --- |
| `mnemostack_search` | Search memory beyond what was injected this turn |
| `mnemostack_remember` | Store a durable fact the user stated, verbatim |
| `mnemostack_forget` | **Soft** retraction by id — recoverable server-side |

Hard deletion is available through the client API and deliberately **not** as a
model-callable tool: an irreversible erase is an operator action, not something a model
should be able to trigger from a conversation.

## What gets captured

By default the user's message and the assistant's reply are stored verbatim, each as
its own memory under a deterministic `(source, offset, text)` id — replaying a turn is
a zero-cost duplicate, never a second copy.

Each carries the **event time of the turn** (ISO-8601 UTC), stamped where the turn
happens rather than where the capture queue is drained — a backlog must not hand its
memories the drain time. Both halves of an exchange share one stamp: they are one
turn, and their order within it is what the offset carries. This is what mnemostack's
temporal recall filters on ("what did we discuss last week"), and what its freshness
stage ages a memory by; before 0.9.2 nothing filled it, so every captured memory was
invisible to the first and equally fresh forever to the second.

What is **not** captured:

- tool calls and tool results (they routinely carry paths, tokens and workspace
  contents; capturing them needs its own redaction policy, so the knob is deliberately
  absent rather than shipped as a silent no-op);
- anything from a cron, subagent or flush context — only the primary agent context
  writes to user memory;
- **echoes of what was just recalled.** This is the loop that makes a naive memory
  provider eat itself: a memory is injected, the model repeats it, the repeat is
  captured as a new memory, and it compounds. It is closed by capture-side
  **provenance** — the provider knows the exact spans it displayed over the last few
  turns — and NOT by parsing the fence markers back out, which both paraphrase and a
  stray marker inside stored content defeat. The contract: a turn is a pure echo when
  **every word** in it came from recalled content (for a wordless turn, when everything
  non-space did), with quote/bullet framing absorbed.

  Two residuals are documented rather than papered over: a **paraphrase** of a recalled
  memory is new text and is captured, and a **wordless addition** (an emoji reaction)
  sent alongside an otherwise complete echo is dropped with it.

Capture is asynchronous: turns go onto a bounded queue (128) drained by one worker in
order, so a slow service never blocks the turn loop. A full queue drops the pending
turn with a loud log line rather than growing without bound. Shutdown drains what is
already queued.

Set `capture: false` to make the provider read-only.

## Degradation

mnemostack 2.2 reports two lists on a recall: `notes` (routine "this stage did not
apply" signals — e.g. a query with no parseable date) and `degraded`, which still
duplicates those routine tags for back-compat until mnemostack 3.0. A real fault is
therefore an entry present in `degraded` and **absent** from `notes`. This provider
computes exactly that difference and logs only real faults, so a healthy recall stays
quiet; `hermes-mnemostack doctor` reports the same difference.

## Limitations

- Not yet exercised against a live hermes-agent session (pre-alpha).
- On hermes-agent 0.19 the provider is found through the directory shim
  (`hermes-mnemostack install`); entry-point discovery arrives with 0.20.
- Local mode: recall is vector-only, and scoping is isolation, not authorization.
- `mnemostack_answer` (server-side generation) is deliberately not wired: it costs an
  LLM call per question and the agent already has a model.
- `on_pre_compress` performs no extra extraction — the evicted turns were already
  captured by `sync_turn` under the same deterministic ids.

## License

MIT.
