Privacy-preserving plugin bundle for the Hermes agent (NousResearch/hermes-agent)
Project description
mordred-hermes
Privacy-preserving plugin bundle for the Hermes agent:
at-rest encryption for your secrets (.env, config, agent memory), hardware-backed
key management (Secure Enclave / TPM 2.0), Tor / VPN network routing, and policy
enforcement for local-only LLM operation.
Status: active alpha — current release 0.1.0a2
(PyPI, 2026-07-10).
⭐ Recommended: set up with an AI coding agent. The first-run setup (
configure,network init,keyvault init) is a series of interactive ceremonies with several prompts. Running them inside Claude Code or Codex is the recommended path — the agent walks you through each prompt, explains the options, and picks sensible defaults for your platform.
The plugins
Five plugins, exposed via the hermes_agent.plugins entry-point group:
| Plugin | What it does |
|---|---|
mordred_privacy_check |
Skill-metadata policy enforcement and audit logging |
mordred_wizard |
The CLI surface — configure, status, encryption, keyvault, network, audit, … |
mordred_llm_guard |
Strict-mode enforcement of local-only LLM usage |
mordred_network |
Privacy-path management: Tor / VPN / clearnet |
mordred_keyvault |
Hardware-backed key management — Secure Enclave (macOS), TPM 2.0 (Linux), software fallback |
Requirements
- Python ≥ 3.11
hermes-agent≥ 0.11.0 (behavior last verified against 0.18.2, 2026-07-08)- macOS or Linux. No special hardware required — without a Secure Enclave / TPM, the keyvault degrades to a software-protected key automatically.
Install (users, from PyPI)
Install into the same environment that runs hermes-agent (usually
~/.hermes/hermes-agent/venv) so its plugin loader can discover the entry points.
Hermes-managed venvs are often created by uv and ship no pip, so the robust
form is uv pip install --python …:
# macOS — includes the Secure Enclave keyvault stack
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python3 "mordred-hermes[macos]==0.1.0a2"
# Linux — cross-platform crypto stack for `encryption` / `keyvault`
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python3 "mordred-hermes[keyvault]==0.1.0a2"
(If your venv does have pip, ~/.hermes/hermes-agent/venv/bin/pip install … works
the same.) Pin the version explicitly or pass --pre: every release is
currently a pre-release, so an unpinned pip install mordred-hermes only resolves
via pip's all-prereleases fallback.
Optional extras, all opt-in:
| Extra | Adds | Install when you need |
|---|---|---|
keyvault |
cryptography / argon2-cffi / blake3 |
encryption / keyvault commands on any platform |
macos |
keyvault + pyobjc Security / SystemConfiguration / Quartz bridges |
Secure Enclave key protection on macOS |
ethereum |
eth-keys / eth-hash / eth-account / rlp |
HD-wallet commands (keyvault eth new / derive / address) |
tor-control |
stem |
Deep Tor liveness probing for strict-mode operators |
messaging |
qrcode |
Terminal QR rendering for extension pair |
extension |
aiohttp / cryptography |
The browser-extension WebSocket gateway and pairing (since 0.1.0a2) |
Enable the plugins
Add them to ~/.hermes/config.yaml:
plugins:
enabled:
- mordred_privacy_check
- mordred_wizard
- mordred_llm_guard
- mordred_network
- mordred_keyvault
Use it
The CLI is the standalone hermes-mordred console script (installed next to
hermes in the same venv):
M=~/.hermes/hermes-agent/venv/bin/hermes-mordred
# First run — set up, in order:
$M configure # interactive setup — policy / LLM / harness
$M configure --skip-hermes-setup # re-run but skip the upstream `hermes setup` step
$M network init # optional — pick a privacy route (Tor / VPN / clearnet)
$M keyvault init # create the hardware-backed key (interactive ceremony)
$M encryption enable env # encrypt your .env at rest
$M status # verify — the `env` row reads [on] enrolled
# Everyday commands:
$M status # protection at a glance
$M encryption status # what's encrypted (env / config / memory)
$M encryption enable <target> # turn on at-rest encryption for a target
$M network use <tor|vpn|clearnet> # switch the active privacy route
$M network status # show the active route and liveness
$M encryption change-passphrase # rotate the vault recovery passphrase
$M configure # re-run interactive setup anytime
$M configure --skip-hermes-setup # re-run but skip the upstream `hermes setup` step
Network troubleshooting. If network communication drops out now and then, check the active privacy path first —
network statustells you whether Tor / VPN is actually up:$M network status # active_path = tor state = ready last_health = ok ← path is up # active_path = tor state = not ready last_health = FAILED ← path is down
stateisready/not ready,last_healthisok/FAILED. A trailing[warning] path was flagged as DROPPEDline means the liveness worker saw consecutive failures; in strict mode tool calls refuse until you re-establish the path withnetwork use <tor|vpn|clearnet>.
Step-by-step guide with expected output: QUICKSTART. Full command reference and interactive-command walkthroughs: USAGE.
Why not
hermes mordred …? Hermes does not yet wire entry-point CLI commands into its argparse (still true on 0.18.2) —hermes-mordredis the same subcommand tree and works today. Once upstream wires it, both forms will coexist without code changes.
Verify discovery
Use Mordred's own command — it lists the five plugins on any supported Hermes version:
~/.hermes/hermes-agent/venv/bin/hermes-mordred plugins list
# → mordred_keyvault / mordred_llm_guard / mordred_network / mordred_privacy_check / mordred_wizard
The host hermes plugins list scans plugin directories only (bundled /
user / project), so it does not list entry-point plugins like these. To
confirm the loader itself discovers them, query it directly:
~/.hermes/hermes-agent/venv/bin/python3 -c "
from hermes_cli.plugins import PluginManager
mgr = PluginManager(); mgr.discover_and_load(force=True)
print(sorted(k for k, p in mgr._plugins.items() if p.manifest.source == 'entrypoint'))
"
# → ['mordred_keyvault', 'mordred_llm_guard', 'mordred_network', 'mordred_privacy_check', 'mordred_wizard']
Browser-extension WebSocket gateway (preview)
mordred_hermes.extension ships the WebSocket server the Mordred browser
extension talks to — ws://127.0.0.1:7788/ext, localhost-only, no TLS. It was
ported from the full-Hermes gateway layer in #30 and ships on PyPI since
0.1.0a2 (install the extension extra).
How it works
An Origin check admits only chrome-extension:// / moz-extension:// clients,
header-less local processes, and the server's own localhost page. On connect
the server sends an auth_challenge; the extension authenticates with its
paired ext_token (plus a WebAuthn assertion once a credential is registered),
the localhost page with a per-process page token. After auth:
| Message | What it does |
|---|---|
pair_init |
Consume a MORT-… pairing code, establish the shared key (pre-auth) |
chat |
Stream one conversation turn as chat_chunk* + chat_end; replies-in-kind E2E with K_extchat (encrypted in → encrypted out) |
encrypt / decrypt |
Slack-message crypto with the paired key |
accounts_request |
Wallet address + chain id from the keyvault |
sign_request → sign_prompt, then sign_approve → sign_result |
Deterministic risk analysis, user approval, then keyvault signing (personal_sign, eth_signTypedData_v4, eth_sendTransaction incl. RPC fill + broadcast) |
history_get / history_clear |
Encrypted-at-rest conversation history |
Wire protocol: the extension repo's SPEC.ja.md §6 / src/lib/protocol.ts;
server side in src/mordred_hermes/extension/api.py.
Run it (standalone)
Nothing starts the server automatically yet: Hermes exposes no gateway-boot
hook a plugin could use, so register(ctx) cannot launch a long-running server
(see docs/dev/ROADMAP.md § browser-extension gateway counterpart). Until that
lands, start it in the foreground with one command — it needs the extension
extra:
uv sync --extra extension # or: uv pip install -e ".[extension]"
.venv/bin/hermes-mordred extension serve # ws://127.0.0.1:7788/ext — Ctrl+C to stop
# equivalent module form, same --host/--port flags:
.venv/bin/python -m mordred_hermes.extension
Bind failures (port already in use, bad host, privileged port) exit with a
one-line error instead of a traceback — a bound 7788 usually means a full
Hermes gateway is already hosting the extension API, in which case there is
nothing to start. Both Ctrl+C and SIGTERM (systemd, docker stop) shut down
cleanly. One divergence between the two forms: with the extension extra
missing, only hermes-mordred extension serve prints the install hint — the
module form fails on the package import itself with a plain ImportError.
Pairing, auth (incl. WebAuthn), encrypt/decrypt, history, and the
keyvault-backed accounts_request / sign_request flows are fully functional
standalone — they only touch ~/.hermes and the keyvault.
Standalone limitations
chatanswers with a stub. The real handler (extension/chat.py:make_gateway_chat_handler) binds to a live HermesGatewayRunnerand lazily importsgateway.run/run_agentfrom the Hermes-fork runtime; agent-backed chat starts working once the gateway side launches this server itself.- Pairing works end-to-end: run
hermes-mordred extension pairin a second terminal whileextension serveis running — both sides share~/.hermes/extension/pending.json, so codes are also consumable by a full Hermes gateway hosting the WS server. GET http://127.0.0.1:7788/returns 503: the server serves the bundled localhost web app fromextension_web/, but the built page ships inweb/. The WS endpoint/extis unaffected.
Install (development)
Canonical dev flow: editable install into the Hermes-managed venv (see setup.md for the full environment build):
# from this repo's root; add ".[macos]" on macOS
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python3 -e .
Refresh a non-editable live venv. If the live venv holds a wheel instead
of the editable install above — the PyPI wheel, or a prior repo build — repo
edits do not reach the hermes-mordred binary until you rebuild and
reinstall it from the repo root:
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python3 --reinstall --no-deps .
--reinstall is required whenever the version string is unchanged (two builds
both reporting 0.1.0a1, say): without it uv treats the requirement as already
satisfied and no-ops, leaving the binary on stale code — the symptom is a newly
added flag such as configure --skip-hermes-setup failing with unrecognized arguments. --no-deps keeps the live editable hermes-agent checkout
untouched. Re-run the same command if Hermes rebuilds its venv and drops the
wheel.
Local checks run from the repo's own uv-managed venv and mirror the CI test
job (HERMES_HOME keeps the tests away from your real ~/.hermes):
uv sync --all-extras # one-time: builds .venv from uv.lock
uv run ruff check src tests
uv run ruff format --check src tests
uv run mypy
HERMES_HOME=/tmp/hermes-mordred-test-home uv run pytest
The default suite is hermetic — integration tests (-m integration) are
excluded and opt back in per suite:
- Tor: hermetic Docker-based
integration-torjob in CI (ci.yml). - Keyvault live hardware:
MORDRED_KEYVAULT_LIVE=1 pytest -m integration tests/integration/test_keyvault_macos.py - Live VPN:
MORDRED_LIVE_VPN_TEST=1 MORDRED_MULLVAD_ACCOUNT=… pytest -m integration tests/integration/test_vpn.py(also available as theworkflow_dispatch-onlyintegration-vpn.ymljob)
Both live suites were last validated on real devices on 2026-05-25.
Releases are cut via the release.yml workflow (PyPI Trusted Publishing);
bump versions in lockstep with tools/bump_version.py. Runbook:
CI.md.
Uninstall
Reverse the setup in the order below.
⚠️ Decrypt before you remove anything. If you turned on at-rest encryption, uninstalling the package or deleting the key first leaves your
.env, config, and memory permanently unreadable. Run step 1 while the CLI and key are still present.
M=~/.hermes/hermes-agent/venv/bin/hermes-mordred
1. Decrypt everything back to plaintext.
$M encryption disable all # env / config / memory / workspace → plaintext
$M vault disable-config-decrypt # stop transparent config decrypt, restore plaintext config.yaml
$M encryption status # verify — every row reads [off]
2. Destroy the key material — optional and irreversible. Skip it if you plan to reinstall and keep the same vault.
$M keyvault reset --yes # DESTROY the hardware-backed key + remove the keyvault dir
3. Disable the plugins. Remove the five mordred_* entries from the
plugins.enabled list in ~/.hermes/config.yaml (undo
Enable the plugins).
4. Uninstall the package from the Hermes venv. This also removes the
config-decrypt .pth bootstrap from site-packages.
uv pip uninstall --python ~/.hermes/hermes-agent/venv/bin/python3 mordred-hermes
# or, if the venv ships pip:
~/.hermes/hermes-agent/venv/bin/pip uninstall mordred-hermes
5. Remove leftover state — optional, and only after step 1 (the vault lives here):
rm -rf ~/.hermes/mordred/ # audit log, policy, credentials, tor-data, keyvault
Also delete any MORDRED_* entries you added to ~/.hermes/.env. If you ran
keyvault enable-se / enable-tpm, the built helper is not removed by the
steps above — delete it by hand (default location, unless you set
MORDRED_SEKEY_INSTALL_DIR / MORDRED_TPMKEY_INSTALL_DIR):
rm -f ~/.local/bin/mordred-hermes-sekey # macOS Secure Enclave helper
rm -f ~/.local/bin/mordred-hermes-tpmkey # Linux TPM 2.0 helper
Repository layout
src/mordred_hermes/ the five plugins + shared internals
native/ hardware-key helper sources, shipped in the wheel and built
on demand by `keyvault enable-se` / `enable-tpm`:
sekey-helper/ — Swift, Secure Enclave (macOS)
tpmkey-helper/ — Rust, TPM 2.0 (Linux)
skills/mordred-status/ read-only conversational status skill for the agent
packaging/ config-decrypt .pth bootstrap; PyPI name-reservation stub
scripts/ offline verification-digest tool for `keyvault init`
tools/ dev tooling (version bump, hook-payload drift check)
tests/ hermetic unit suite + opt-in tests/integration/
docs/ user and developer documentation (see below)
Documentation
| Audience | Doc | Contents |
|---|---|---|
| Users | QUICKSTART.md | Zero → protected install, step by step |
| Users | USAGE.md | Full command reference, interactive walkthroughs, storage model |
| Developers | setup.md | Development environment from scratch |
| Developers | SPEC.md, POLICY.md | Design spec and policy model |
| Developers | SECRETS_ENV_ENCRYPTION.md, KEYVAULT_BACKENDS.md | At-rest encryption and key-backend design |
| Developers | CI.md, UPSTREAM.md | CI strategy, release runbook, upstream tracking |
More under docs/dev/:
PLAN, TODO, ROADMAP, PATHS, MIGRATION, HARNESS_PRIVACY, HOOK_PAYLOADS.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mordred_hermes-0.1.0a2.tar.gz.
File metadata
- Download URL: mordred_hermes-0.1.0a2.tar.gz
- Upload date:
- Size: 873.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d219dbbf1b1bb3b7c1cffaf14c3c23ef09af24652ab5d13270f9c359b038e25
|
|
| MD5 |
7471e93621db496dc701656ba63a0bfa
|
|
| BLAKE2b-256 |
dbe3a3112f168ac33627e6678cdb3cd40f660eef7d9e0a2279a6a1cf4afb7e48
|
Provenance
The following attestation bundles were made for mordred_hermes-0.1.0a2.tar.gz:
Publisher:
release.yml on InternetMaximalism/mordred-hermes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mordred_hermes-0.1.0a2.tar.gz -
Subject digest:
3d219dbbf1b1bb3b7c1cffaf14c3c23ef09af24652ab5d13270f9c359b038e25 - Sigstore transparency entry: 2135267291
- Sigstore integration time:
-
Permalink:
InternetMaximalism/mordred-hermes@015362a78300fb1101baf2b060fb15a81ab2346e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/InternetMaximalism
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@015362a78300fb1101baf2b060fb15a81ab2346e -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file mordred_hermes-0.1.0a2-py3-none-any.whl.
File metadata
- Download URL: mordred_hermes-0.1.0a2-py3-none-any.whl
- Upload date:
- Size: 591.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc71b8333e6ae2cd7c58fb0fedbe6127186d08b5c7b43536bcc6cca8c06fbbb0
|
|
| MD5 |
29106b379e19fdee03d15b1e04e0e274
|
|
| BLAKE2b-256 |
5f8a77e4d38cc1fbfd61b062f9b034c6b2527f46471ad1333d183519b9d06c9b
|
Provenance
The following attestation bundles were made for mordred_hermes-0.1.0a2-py3-none-any.whl:
Publisher:
release.yml on InternetMaximalism/mordred-hermes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mordred_hermes-0.1.0a2-py3-none-any.whl -
Subject digest:
bc71b8333e6ae2cd7c58fb0fedbe6127186d08b5c7b43536bcc6cca8c06fbbb0 - Sigstore transparency entry: 2135267308
- Sigstore integration time:
-
Permalink:
InternetMaximalism/mordred-hermes@015362a78300fb1101baf2b060fb15a81ab2346e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/InternetMaximalism
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@015362a78300fb1101baf2b060fb15a81ab2346e -
Trigger Event:
workflow_dispatch
-
Statement type: