85 lines
4.8 KiB
Markdown
85 lines
4.8 KiB
Markdown
# @waggle/hive-mind-hooks-hermes
|
|
|
|
Silent-capture shim that wires **Hermes** (`NousResearch/hermes-agent`)
|
|
lifecycle hooks into [hive-mind](https://github.com/marolinik/hive-mind)
|
|
frames. Every Hermes session deterministically captures session-start /
|
|
prompt / response events into your personal memory via `hive-mind-cli` — the
|
|
same every-turn pattern proven by `@waggle/hive-mind-hooks-claude-code`, built
|
|
on the shared `@waggle/hive-mind-hooks-core` foundation.
|
|
|
|
Hermes's hook config is a **YAML installer** with **create-if-missing**
|
|
semantics. It targets the **SHELL-HOOKS** system — the top-level `hooks:`
|
|
block in `~/.hermes/config.yaml`. It does **not** touch Hermes's two other
|
|
"hooks" systems (the directory-based gateway hooks under `~/.hermes/hooks/`,
|
|
or the in-process plugin hooks) — only the shell-hooks block.
|
|
|
|
> **Not hook parity with claude-code.** Hermes ships **only 3 lifecycle
|
|
> hooks** — there is **no PreCompact event** — and splits SessionStart across
|
|
> two events. See the Capture fidelity table below.
|
|
|
|
## Install
|
|
|
|
```bash
|
|
npx @waggle/hive-mind-hooks-hermes install
|
|
# Windows / production: pin the CLI path
|
|
npx @waggle/hive-mind-hooks-hermes install --cli-path "C:\\path\\to\\hive-mind-cli\\dist\\index.js"
|
|
```
|
|
|
|
The installer additively merges hive-mind shell-hook entries into
|
|
`~/.hermes/config.yaml` (creating the file if absent), preserving any existing
|
|
hooks you have, and writes a pointer + **literal byte-identical backup** so
|
|
uninstall is exact. (YAML re-serialization loses comments and key ordering, so
|
|
uninstall restores the original bytes rather than a re-serialized diff.)
|
|
|
|
> **Headless / gateway consent (IMPORTANT).** Hermes keeps a first-use consent
|
|
> allow-list keyed on the exact hook command string. Under a **headless /
|
|
> non-TTY launch** the hooks register **only** if `hooks_auto_accept: true` is
|
|
> in the config (the installer seeds this by default) **or**
|
|
> `HERMES_ACCEPT_HOOKS=1` is set — otherwise they silently never register.
|
|
> Pass `--no-auto-accept` to manage consent yourself.
|
|
|
|
```bash
|
|
npx @waggle/hive-mind-hooks-hermes verify # smoke-check
|
|
npx @waggle/hive-mind-hooks-hermes uninstall # byte-identical restore (or remove if we created it)
|
|
```
|
|
|
|
## Capture fidelity
|
|
|
|
| Lifecycle | Hermes event(s) | Status | Notes |
|
|
|---|---|---|---|
|
|
| SessionStart (recall + inject) | `on_session_start` (observer) **+** `pre_llm_call` (`is_first_turn`) | full (split) | observer side is a no-op; inject side fires on the first turn only and appends `{ "context": "..." }` to the user message (not the system prompt — preserves the prefix cache) |
|
|
| UserPromptSubmit (save temporary) | `pre_llm_call` | full | the user text arrives at `extra.user_message`; saved every turn |
|
|
| Stop (summarize + save) | `post_llm_call` | full | the assistant text arrives at `extra.assistant_response`; `on_session_finalize` is gateway-only and is **not** used |
|
|
| PreCompact (compact memory) | **— (none)** | **opt-in approximation** | Hermes ships **no compaction hook** (`VALID_HOOKS` has no compact entry) — there is genuinely nothing to bind. Set `WAGGLE_HERMES_COMPACT_ON_STOP=1` (default off) to approximate it from the per-turn Stop hook, time-gated by `WAGGLE_HERMES_COMPACT_WINDOW_MIN` (minutes, default 10) so it runs at most once per window |
|
|
|
|
**Disclosures:**
|
|
|
|
- **No PreCompact event at all.** Hermes has no compaction hook, so the
|
|
`cleanup_frames` maintenance step that claude-code runs on PreCompact does
|
|
**not** run under Hermes by default. Memory still accrues correctly; only the
|
|
periodic compaction nudge is absent. **Opt-in:** set
|
|
`WAGGLE_HERMES_COMPACT_ON_STOP=1` to approximate it from the per-turn Stop
|
|
hook, time-gated by `WAGGLE_HERMES_COMPACT_WINDOW_MIN` (minutes, default 10)
|
|
so the maintenance pass fires at most once per window.
|
|
- **SessionStart is split** across `on_session_start` (observer) and
|
|
`pre_llm_call` (inject). One compiled script is registered under both; it
|
|
gates injection on `is_first_turn` so it injects once per session, not every
|
|
turn.
|
|
- **Block/inject is via stdout JSON**, not exit codes — Hermes has no
|
|
exit-code-2 contract. Our hooks are capture-only: they emit `{}` (or the
|
|
SessionStart `{ "context": ... }`) and exit 0.
|
|
- **Headless consent** — without `hooks_auto_accept: true` (seeded by default)
|
|
or `HERMES_ACCEPT_HOOKS=1`, the hooks silently never register under a
|
|
headless launch.
|
|
|
|
## How it works
|
|
|
|
Each hook is a short-lived Node subprocess. Hermes spawns the configured
|
|
`command` (`shlex.split` + `shell=False`), pipes the event JSON to the hook's
|
|
stdin, and reads optional JSON back from stdout. The hook shells to
|
|
`hive-mind-cli` to recall or save frames, then exits 0. **Fail-open:** if
|
|
`hive-mind-cli` is unreachable or the payload is malformed, the hook logs to
|
|
stderr and exits 0 — Hermes is never blocked.
|
|
|
|
License: Apache-2.0.
|