Skip to content

Hermes Plugin

Memoir ships a memory-provider plugin for Hermes, the Nous Research personal-assistant agent. Activate it and Hermes gains versioned, semantic long-term memory: a git-like store (branch / commit / merge) over a Prolly-tree with semantic paths and cryptographic provenance. Unlike vector-only backends (Mem0, Zep, Letta), every write is a commit you can inspect (memoir log), attribute (memoir blame), and time-travel.

The plugin lives in the repo at plugins/hermes/.

How it fits Hermes

Hermes has a pluggable MemoryProvider slot — exactly one external provider is active at a time, selected by memory.provider in ~/.hermes/config.yaml. The built-in MEMORY.md / USER.md layer stays active alongside it. Memoir fills that slot.

It is a directory plugin, not a pip/entry-point package: Hermes loads memory providers by scanning $HERMES_HOME/plugins/<name>/, so the plugin is installed as a directory there. It holds no memoir code in-process — it shells out to the memoir CLI over a stdlib-only subprocess bridge, so the Prolly-tree self-resolves concurrent (fire-and-forget) writes with no locking.

Install

The plugin needs two things on the machine: the plugin directory in your Hermes home, and the memoir CLI reachable on PATH.

1. Install the memoir CLI

pip install memoir-ai        # or: pipx install memoir-ai / uv tool install memoir-ai

The bridge resolves memoir on PATH first, falling back to a pinned uvx --from memoir-ai==<pin> memoir (then uv tool run) when it isn't installed. A direct install is faster (no warmup).

2. Install the plugin

Via the Hermes CLI (once the plugin is on the repo's default branch):

hermes plugins install zhangfengcdt/memoir/plugins/hermes

Hermes clones the repo, takes the plugins/hermes subdirectory, reads its plugin.yaml (name: memoir), and installs it to ~/.hermes/plugins/memoir/.

By copy (works today, from a local checkout):

cp -r plugins/hermes ~/.hermes/plugins/memoir
rm -rf ~/.hermes/plugins/memoir/tests ~/.hermes/plugins/memoir/__pycache__

3. Activate it

hermes memory setup        # interactive picker — choose "memoir"

hermes memory setup writes memory.provider: memoir to ~/.hermes/config.yaml. (You can also set that key by hand.) Activating memoir displaces any other external provider (mem0, honcho, …) — only one runs at a time.

The store is created automatically on first run at <hermes_home>/memoir-store (override with store_path in config). You'll need a provider API key in the Hermes environment — see LLM backend.

What ships

Component Count Role
Tools (model-facing) 5 memoir_recall, memoir_remember, memoir_forget, memoir_sync, memoir_status
Lifecycle hooks 5 Auto-capture, recall context, store mirroring
CLI subcommands 2 hermes memoir status, hermes memoir ui
Slash command 1 (opt-in) /memoir … — in-session passthrough to the memoir CLI

The model-facing tools above are the primary in-chat surface (the model calls them). There's also an opt-in /memoir slash command for power users — see The /memoir command.

Tools

The model decides when to call these (guided by their descriptions and the injected system-prompt block); they are not typed by the user.

Tool Purpose
memoir_recall Fetch stored facts about the user (preferences, people, schedule, standing instructions). LLM-free: summarize → batched get, ranked by lexical overlap with the query.
memoir_remember Store an explicit durable fact. Classified into a semantic path and committed. Guarded against obvious secrets.
memoir_forget Delete a fact by its exact taxonomy path (the model finds the path via memoir_recall first). Pre-checks existence so a wrong path can't create a no-op delete commit; prior versions remain in git history (recoverable).
memoir_sync Promote a session fork's memories into main. No args → list fork branches with un-merged memories (preview counts); branch → merge it into main (dry_run to preview). Additive (insert/update, never delete).
memoir_status Store status: branch, commit count, memory count.

Lifecycle hooks

These are called by Hermes automatically — they don't depend on the model invoking a tool, so capture is guaranteed even if the model never calls memoir_remember.

Hook When Purpose
initialize agent startup Derive + ensure the store, resolve the model, cache an overview.
system_prompt_block prompt assembly Inject recall guidance + a short store overview.
sync_turn after every turn Fire-and-forget memoir capture --profile assistant over the turn.
on_pre_compress / on_session_end compression / session end Capture the uncaptured message tail.
on_session_switch /branch, /resume, /reset Route the store's branch to match the session — see Session branching.
on_memory_write built-in memory edit Mirror Hermes's MEMORY.md / USER.md writes into versioned memoir paths.

Capture runs on a background thread and never blocks the response. Writes are skipped for non-primary agent contexts (subagent / cron / flush) so they can't corrupt the user's representation.

The /memoir command

An optional in-session slash command that passes through to the memoir CLI on your store — handy for inspecting memory without leaving the chat:

/memoir status
/memoir summarize --depth 3
/memoir recall "travel plans"
/memoir branch
/memoir blame profile.personal.identity

/memoir with no args prints usage. It's user-invoked (not the model), so it simply runs memoir -s <store> <your args> and returns the output.

It's opt-in and separate from the memory provider. Hermes loads in-session slash commands only for plugins listed in plugins.enabled, which is distinct from memory.provider. So to get /memoir, enable the plugin as well:

hermes memory setup          # activates the provider (tools + auto-capture)
hermes plugins enable memoir # additionally registers the /memoir command

Without the enable step everything else still works — you just won't have the slash command. (Mechanism: the plugin ships kind: standalone so the general plugin manager can load it for the command; its register() feature-detects the load context and registers the provider or the command accordingly.)

Configuration

Config lives in <hermes_home>/memoir.json (all keys optional). Set it via hermes memory setup or by hand.

Key Default Meaning
store_path <hermes_home>/memoir-store Store location.
capture true Auto-capture facts from each turn. false disables the sync_turn / boundary capture; recall and tools still work.
model host's selected model Pin the LLM model for capture/classification. Empty = follow Hermes model.default. See Model selection.
base_url provider default Custom provider endpoint (LLM gateway/proxy). Empty = call the provider directly. See Routing through a proxy.
session_branching true Mirror Hermes session forks onto memoir branches. false keeps everything on main. See Session branching.
scope off Isolate memory per chat/profile via a namespace: chat (per platform+chat), profile (per Hermes profile), off (one shared store). See Scoped memory.

Example — pin cheap Haiku for per-turn capture regardless of your chat model:

{ "model": "claude-haiku-4-5" }

Model selection

memoir resolves the capture/classification model in this order:

  1. model pin in memoir.json — always wins.
  2. Host-selected model — Hermes model.default, tracked across mid-session model switches (on_turn_start).
  3. memoir's built-in defaultclaude-haiku-4-5.

So out of the box, capture runs on the same model your Hermes session uses. That can be expensive: if your session model is Opus, every per-turn extraction is an Opus call. Pin a cheaper model (e.g. claude-haiku-4-5) in memoir.json to decouple capture cost from your chat model.

Which provider key is used?

memoir routes by model name, not by which keys are present — so setting both OPENAI_API_KEY and ANTHROPIC_API_KEY is unambiguous:

Model Provider → key
claude-* / anthropic/* Anthropic → ANTHROPIC_API_KEY
gpt-* OpenAI → OPENAI_API_KEY
gemini/* Gemini → GEMINI_API_KEY
ollama/*, or any model with base_url set that endpoint (no key check)

Each key is consulted only when a model of its provider is selected. memoir's default model is claude-haiku-4-5 (Anthropic), so with no model specified anywhere it uses ANTHROPIC_API_KEY.

Session branching

Hermes lets you fork a conversation (/branch), jump between conversations (/resume), and start fresh (/reset / /new). With session_branching enabled (the default), the plugin mirrors that onto memoir branches so a fork's memories stay isolated from your main timeline:

  • /branch (fork) → creates and checks out hermes/<session-id> off the current branch. Everything captured in the fork lands there, not on main.
  • /resume → checks out that session's branch if it has one, otherwise returns to main. So resuming a normal (never-forked) conversation always comes back to your main timeline.
  • /reset / /new → back to main.
  • context compression → no branch change (it's the same conversation continuing under a rolled-over id, so captures stay on the current branch).

The mapping is stateless — it's derived from the session id and the branches that exist in the store — so it survives Hermes restarts. Forks branch off whatever is currently checked out, so nested forks work too. Inspect or merge branches with the memoir CLI (memoir branch, memoir merge, memoir -s <store> summarize per branch).

Set session_branching: false in memoir.json to disable this and keep all captures on main.

Promoting a fork's memories back to main. A fork's captures stay isolated on its branch. When the user wants to keep them, the memoir_sync tool promotes them into main: with no arguments it lists fork branches that have un-merged memories (with a preview of what each would add); with a branch it merges that branch into main. The merge is additive — it inserts/updates keys and never deletes — and the model drives the choose/confirm flow in conversation. (This mirrors the Claude Code plugin's /memoir:sync.) On exit the store is left on main, so forks don't accumulate as the active branch.

Single-store caveat: branch state is the store's checked-out branch, so this assumes one active session per store at a time (the personal-assistant CLI case). Concurrent sessions sharing one store would contend on the checkout.

Scoped memory

If you run Hermes across multiple chats/platforms/profiles (e.g. a private DM and a public group on the same gateway), global memory is a privacy foot-gun: a fact captured in the DM would otherwise be injected into the group. Set scope in memoir.json to isolate memory structurally:

scope Each scope is… Namespace
off (default) one shared store default
chat a platform + chat/channel scope_<platform>_<chat_id>
profile a Hermes profile scope_profile_<name>

How it works:

  • Hermes already passes platform, chat_id, and agent_identity (profile) into the provider's initialize(), so the scope key needs no extra config.
  • Captures and memoir_remember write to the scope's namespace.
  • memoir_recall reads the scope namespace default — so global (unscoped) facts are visible everywhere, but a scoped fact never appears in another scope. Cross-scope leakage is impossible by construction (default-deny), not dependent on an injection-time filter.
  • memoir_forget deletes from the scope namespace, falling back to default.

Namespace vs. branch. Scoping uses memoir namespaces (parallel, composable partitions), which is the right primitive here — distinct from session branching, which uses branches (divergent timelines you can merge). They compose: a scoped session can still /fork. Namespaces are also routed per call (no shared checkout), so concurrent scoped sessions don't contend.

v1 resolves the scope once at initialize() (the session's chat/profile). Per-message re-scoping for a single provider serving many concurrent gateway sessions is future work — the namespace primitive supports it.

LLM backend

The plugin drives memoir with its litellm (direct provider API) backend only — it never shells out to the claude CLI, even when a claude binary is on PATH. A Hermes host has its own configured provider, and a typical Hermes box has no Claude Code install.

memoir reads the provider credentials from the Hermes process environment (e.g. ANTHROPIC_API_KEY in <hermes_home>/.env). If the selected model's provider key is missing, capture fails loudly ("ANTHROPIC_API_KEY required") rather than silently switching backends.

Recall is LLM-free, so it works even without a provider key. Auto-capture and explicit memoir_remember classification do need one.

Routing through a proxy

By default memoir calls the provider directly (e.g. api.anthropic.com) and does not route through any gateway Hermes itself uses — Hermes keeps its gateway/base-URL internal to its own client and never exports it to the environment. To send memoir's calls through a proxy/gateway too, set base_url in memoir.json:

{ "base_url": "https://your-gateway/v1" }

The bridge exports this as MEMOIR_LLM_BASE_URL; the credential in the environment must be valid for that endpoint.

Verify

hermes memory status            # provider: memoir
hermes memoir status            # store branch / commit count / memory count
memoir capture --help           # confirms the CLI on PATH has `capture`

End-to-end: start a Hermes session and state a durable fact ("from now on call me Captain; my dog Rex sees the vet every March"). Capture runs in the background (a few seconds), then:

memoir -s <hermes_home>/memoir-store summarize --depth 3     # captured paths
memoir -s <hermes_home>/memoir-store blame <path>            # provenance

In a new session, ask "what's my name?" — the agent should call memoir_recall and answer from memory.

Environment variables

The bridge sets these for the memoir subprocess automatically from memoir.json; you normally don't set them by hand.

Variable Set from Effect
MEMOIR_LLM_BACKEND always litellm Forces direct provider APIs; suppresses the claude-cli fallback.
MEMOIR_LLM_MODEL resolved model Model for capture/classification.
MEMOIR_LLM_BASE_URL base_url config Custom provider endpoint, when set.

Provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY, …) are inherited from the Hermes process environment — set them in <hermes_home>/.env.

Manifest

plugins/hermes/plugin.yaml:

name: memoir
version: 0.1.0
description: "Memoir  versioned, semantic long-term memory with git-like branch/commit/merge and cryptographic provenance. Shells out to the memoir CLI; no Python deps."

No pip_dependencies: the provider talks to the memoir CLI over a subprocess bridge (PATH memoir, else a pinned uvx --from memoir-ai).

Limitations (v1)

  • Local store only. Multi-device sync (the merge-based wedge) is future work; it needs a memoir remote story.
  • One external provider at a time. Activating memoir displaces any other Hermes memory provider.

See also

  • CLI — the underlying memoir commands the plugin wraps (including capture).
  • Claude Code — the coding-agent plugin (slash commands, hooks).
  • Architecture — how memoir is structured under the hood.