Skip to content

Releases: mordredagent/hermes-mordred

hermes-mordred 0.1.0a17

Pre-release

Choose a tag to compare

@spiderings spiderings released this 13 Aug 13:20
e36e80f

Alpha release of the privacy-preserving plugin suite for hermes-agent.

Changes

  • Gate interactive network setup prompts and summaries by the selected privacy route (#118).
  • Ignore editor state, GitHub Actions credential files, and local scratch notes (#120).
  • Update repository URLs after the distribution and repository rename (#121, #130).
  • Add the re-runnable, fail-closed hermes-mordred setup first-run orchestrator (#122).
  • Correct extension setup guidance and document optional extension bundles (#123).
  • Add secure Discord channel and thread-context resolution to the extension API (#124).
  • Add installer --with-extension, exact --version, help, and argument validation (#127).
  • Consolidate canonical documentation and add documentation-contract guards (#128).
  • Bump the canonical package and compatibility shim to 0.1.0a17 (#129).

Fixes

  • Correct pairing dependencies, Hermes lifecycle, Firefox availability, protocol messages, and extension port guidance (#123).
  • Prevent Discord resolution from bypassing Tor or an unverifiable VPN route, preserve dotenv precedence, load vault-managed tokens, and enforce the client deadline (#124, #126).
  • Document file-vault recovery as macOS-only until Linux has a device-anchor store (#128).
  • Preserve exact installer version pins when adding the messaging extra (#128).
  • Preserve current setup, installer, package-rename, and extension behavior while removing superseded documentation (#128).

Distribution

  • Canonical package: hermes-mordred==0.1.0a17
  • Compatibility shim: mordred-hermes==0.1.0a17
  • Both packages were verified on TestPyPI and PyPI before this prerelease was tagged.

hermes-mordred 0.1.0a16

Pre-release

Choose a tag to compare

@spiderings spiderings released this 12 Aug 09:34
aa07fbd

This release moves the real PyPI distribution to hermes-mordred.
Existing mordred-hermes installs resolve through a metadata-only compatibility shim.

The Python import package remains mordred_hermes; the CLI remains
hermes-mordred; plugin IDs, native helper names, state paths, keys, and
other persistent compatibility identifiers do not change.

Changes

  • Rename the public PyPI distribution to hermes-mordred in 0.1.0a16 and retain mordred-hermes as a metadata-only compatibility shim.

Fixes

  • Prevent old/new distribution file-ownership overlap during upgrades and fail safely when the canonical package cannot be installed.

Verification

  • Canonical and compatibility artifacts passed TestPyPI and production PyPI fresh-environment checks.
  • Six Hermes plugin entry points load from the canonical wheel.
  • Removing only the compatibility shim leaves the canonical runtime and CLI intact.

v0.1.0a15

v0.1.0a15 Pre-release
Pre-release

Choose a tag to compare

@spiderings spiderings released this 12 Aug 04:31

mordred-hermes 0.1.0a15

Packaging-hygiene release on top of 0.1.0a14. No runtime code changed:
src/ is identical to a14 apart from the version string.

The published a14 sdist still shipped six dev-era PoC harnesses; a15
excludes them.

a14 sdist: install.sh, keyvault_offline_digest.py,
poc_outbound_roundtrip.py, poc_pre_gateway_dispatch.py,
poc_reply_encryption_regression.py,
poc_reply_key_failclosed.py,
poc_thread_reply_encryption.py, poc_wrap_live_adapters.py
a15 sdist: install.sh, keyvault_offline_digest.py

Changes

  • Exclude scripts/poc_*.py from the sdist so published artifacts carry
    only the two scripts users need (#109)
  • Tighten CI config and scripts hygiene; translate remaining Japanese
    comments in release.yml and docs/dev/CI.md to English (#109)
  • Bump the GitHub Actions group: checkout v7.0.1, setup-python v7.0.0,
    upload-artifact v7.0.1, download-artifact v8.0.1, labeler v7.0.0,
    cache v6.1.0, github-script v9.0.0 (#108)
  • Bump every pinned version surface to 0.1.0a15 (#110)

v0.1.0a14

v0.1.0a14 Pre-release
Pre-release

Choose a tag to compare

@spiderings spiderings released this 12 Aug 03:34
be1a11d

mordred-hermes 0.1.0a14

Headline: scripts/install.sh — users install with one command instead of
assembling uv pip install --python ... by hand.

Supersedes 0.1.0a12 and 0.1.0a13, which reached main but were never
published to PyPI or tagged; these notes therefore cover everything since
v0.1.0a11.

Changes

  • Add scripts/install.sh: resolves the Hermes-managed environment, gates on
    the minimum Hermes version, selects the macOS/Linux extra, installs from
    PyPI, and exposes hermes-mordred (#103)
  • Resolve the interpreter from the hermes on PATH first, reading the
    official launcher's exec line, so a second Hermes install cannot silently
    receive the plugins (#103)
  • Write a launcher mirroring Hermes's PYTHONPATH/PYTHONHOME scrub instead of
    a bare symlink (#103)
  • Use --upgrade-package so upgrading Mordred does not upgrade hermes-agent;
    refuse non-virtualenv targets; run uv pip check before and after (#103)
  • Add tests/test_user_installer.py: 35 hermetic tests plus a real-uv
    integration test (#103)
  • Rewrite the user docs around the installer; drop the $M idiom (#103)
  • Simplify user/developer docs; add Extension and dev doc indexes; add local
    Markdown link validation (#100)
  • Add Dependabot for the SHA-pinned actions; restructure agent guidance into
    AGENTS.md (#101)
  • Parse state.json once per generation instead of per WebSocket frame (#94)
  • Split keyvault/_secret_ops.py and llm_guard/enforce.py into cohesive
    modules (#95, #96)
  • Harden extension protocol compatibility (#97)

Fixes

  • install_launcher: set -e is suppressed inside command substitution, so a
    failed mktemp ran on and reported failure after a clean install (#103)
  • version_at_least read a zero-padded minor as octal (#103)
  • Reject interpreter paths with shell metacharacters (#103)
  • ${HOME} under set -u aborted instead of printing guidance (#103)
  • Correct the extension serve failure mode and the ~/.local/bin claim in the
    docs (#103)
  • Rate-limit pre-auth needs-key notices (#93)
  • state.json read racing a write no longer caches a pre-write snapshot (#94)
  • guard_hermes_home: block ceremonies with interleaved flags (#101)
  • Refuse empty-text inbound on mandatory-E2E platforms (#88)
  • Bump gh-action-pypi-publish to v1.14.2 so Twine v7 accepts hatchling's
    Metadata-Version 2.5 (#106)

0.1.0a11

0.1.0a11 Pre-release
Pre-release

Choose a tag to compare

@spiderings spiderings released this 01 Aug 08:04
d053b13

Release 0.1.0a11.

An ENC:v3 fix release. Every fix here was found by verifying the browser
extension's new ENC:v3 support against a real install (Slack #my-hermes and
Discord #general) rather than by unit testing — on a real install, no v3
command could authenticate at all
before this.

Fixes

  • Resolve a v3 channel key stored under the extension's composite id, so
    existing installs work with no state migration. The extension pushes
    channel_key_set keyed by slack:{team}:{cid} / discord:{guild}:{cid}
    and save_channel_key stores that verbatim, while a gateway event only
    carries the platform's native chat_id — so the exact-match lookup missed
    every stored key and refused the command as key_not_bound_to_channel. The
    composite is accepted only when its last segment is exactly the channel
    id and its first segment is the event's platform, so the binding
    stays as tight as the exact match it replaces.
  • Log the fixed InvalidEncryptedEnvelope reason identifier on refusal (never
    message text, keys or plaintext) — every refusal previously logged the same
    sentence while needing completely different fixes, which made a live
    rejection undiagnosable without replaying the wire by hand.
  • Authenticate a top-level Slack command under its real context. The Slack
    adapter's default reply_in_thread session keying stamps thread_id with a
    synthetic root equal to a top-level message's own ts; Slack assigns that
    ts only after the send, so the extension provably encrypted with
    thread_root=None and every genuine top-level v3 command was refused as
    authentication_failed. The command AAD now canonicalizes to
    thread_root=None when the routed root IS the command's own message_id
    (mirror of the documented Discord auto-thread canonicalization), while reply
    routing keeps the synthetic thread. Canonicalizing requires two
    adapter-stamped markers to agree, so a captured top-level token still cannot
    be replayed into a genuine thread.

Changes

  • Bump the pinned version surfaces to 0.1.0a11.
  • Document the Slack synthetic-thread-root canonicalization in
    docs/dev/SLACK_E2E.md §2.

Test plan

  • uv run pytest — full suite green; 11 new tests in
    tests/extension/test_extension_e2e_v3.py (composite-id resolution,
    platform-mismatch and partial-suffix refusals, synthetic-root top-level
    acceptance, genuine-thread acceptance, top-level-token-in-thread
    refusal, thread-token-at-top-level refusal, non-Slack no-canonicalize,
    degenerate message_id fail-closed, replay through the dispatch hook,
    reply_in_thread=false parity)
  • ruff check / ruff format --check / mypy --strict src tools
  • Adversarial review (independent agent): verdict SOUND, no
    CRITICAL/HIGH/MEDIUM findings; its two hardening suggestions and five
    coverage gaps were all landed
  • Live gateway round trip on the production install (2026-08-01): a
    top-level ENC:v3 command from the real Slack client decrypted by the
    gateway, agent reply re-encrypted in-thread with the inbound kid; same
    round trip verified on Discord including the auto-thread path
  • TestPyPI dry run + fresh-venv discovery check (release workflow)

Pairs with mordredagent/mordred-extension#11 and #12 — the extension
side of the same wire format.

0.1.0a10

0.1.0a10 Pre-release
Pre-release

Choose a tag to compare

@spiderings spiderings released this 31 Jul 10:46
8777978

Release 0.1.0a10 — a single follow-up fix to the audit-log encryption indicator shipped in 0.1.0a9 (#80, #81).

No breaking changes. The 0.1.0a9 notes still apply for the ENC:v3 gateway wire and the removal of keyvault enable-se --unattended.

Fixes

  • status reported audit log plaintext (no audit wrapping key) for a legacy keyvault profile whose audit log is in fact MRAL-encrypted (#80). The 0.1.0a9 indicator decided solely on the AUDIT_KEY_FIELD / PENDING_AUDIT_KEY_FIELD manifest records, but those are written only by wizard/_keyvault_init, so a keyvault created before 0.1.0a9's profile-scoped native key ids never carries either. privacy_check/audit.py treats exactly that shape as a supported branch ("Pre-profile-scoping metadata selects the legacy global audit key"), resolves the legacy global key, and encrypts normally — so the new indicator contradicted the on-disk state on every pre-0.1.0a9 install. The verdict now has a legacy branch that reads the audit log's own MRAL header, because the manifest cannot prove either outcome there and claiming encrypted unconditionally would be a false positive (the legacy key has a real history of becoming unusable with a silent plaintext fallback). Committed, pending, and scoped verdicts are unchanged. The risk was operator action rather than data exposure: the line erred pessimistic, but someone trusting it could have run destructive keyvault ceremonies against a healthy encrypted setup.

Changes

  • Version bumped to 0.1.0a10 across all pinned surfaces (#81).

0.1.0a9

0.1.0a9 Pre-release
Pre-release

Choose a tag to compare

@spiderings spiderings released this 30 Jul 14:55
11cee82

Release 0.1.0a9 — security/privacy hardening sweep across all six plugins, aggregating #71, #74, #75, #76, #77.

Breaking changes

  • The gateway ENC:v3 command/reply wire (#71): a v3-capable external Mordred Extension must be deployed together with this version. Legacy v1/v2 helpers remain for the WebSocket API/history paths only.
  • keyvault enable-se no longer accepts --unattended (#76): the authorization policy is chosen at key generation, not helper install. Use MORDRED_SEKEY_UNATTENDED=1 on init/recovery instead; scripts passing the old flag now exit 2.

Changes

  • Audit/privacy (#71, #76): process-wide, encryption-aware, conflict-safe audit writer; shared _audit_io primitives (O_NOFOLLOW/O_NONBLOCK opens, inode-stability checks, sidecar flock, no-follow gzip rotation); cross-process serialized whole-entry appends with truncate rollback; audit purge/decrypt bind an O_NOFOLLOW directory descriptor and purge requires --yes.
  • Policy enforcement (#71, #76): strict provider/network policy enforced against the effective runtime request (repeated gateway calls, exact loopback local-LLM endpoints, verified Tor/provider transport); strict mode binds cloud routes to provider-owned endpoints including ambient SDK overrides (ANTHROPIC_BASE_URL, OPENAI_BASE_URL, …); new auxiliary_guard covers Hermes 0.19's auxiliary client resolvers that never emit pre_api_request; sibling-disable integrity checks registered from every runtime Mordred plugin.
  • Extension/gateway (#71, #74, #75, #76): context-bound gateway E2E v3 (direction/platform/channel/thread AAD, channel-key binding, persistent replay protection, single-token grammar, Slack :lock: normalization, multiplex profile routing, Discord auto-thread canonicalization); hardened Host/Origin/token/WebAuthn lifecycle checks with stale pairing-generation revocation; WebAuthn split from pairing state with lazy extension submodules (pairing/help paths work without aiohttp); pairing commit ordered against CLI cancellation under one state lock; JSON-RPC responses bounded (2 MiB) and envelope-validated; wallet.json validated, locked, atomically written.
  • Keyvault (#76): profile-scoped native wrapping-key ids (two HERMES_HOME profiles can no longer collide in the machine-global native store); keyvault_lock gains an out-of-tree lifecycle lock, fork-safe descriptors, and reentrancy; export_backup verifies the passphrase before opening any envelope; import_backup requires a fresh destination and rolls back only what it created; .env/config.yaml reseal captures the live inode atomically; native helper installers stage/verify before atomic rename.
  • Network (#74, #75, #76): centralized provider resolution and network-setting validation across registration, hooks, and wizard; every network-path subprocess runs under an explicit timeout (SubprocessRunner is a Protocol); Tor picks an adjacent free (SOCKS, ControlPort) pair; process-global Tor/VPN routing kept alive across Hermes turns and concurrent gateway sessions.
  • Runtime integrity (#74): new MordredIntegrityRefused(BaseException) policy signal so Hermes' except Exception hook wrapper cannot swallow strict refusals (stderr-printed before raising, #75).
  • Release/CI (#71): pinned GitHub Actions, restored CI/release gates, artifact validation, declared direct runtime dependencies (incl. PySocks).
  • Version (#77): bump to 0.1.0a9 across all pinned surfaces.

Fixes

  • Strict mode refused every allow-listed cloud provider on its SDK-default endpoint (#76): absent base_url now means "the vendor default" and is accepted when the provider owns one; tenant/region-scoped providers (azure-foundry, bedrock, vertex) still fail closed.
  • An interrupted configure silently forced strict mode forever (#76): a stranded policy-write transaction marker now surfaces in status/policy show with the remedy.
  • A stranded audit wrapping key left the audit log permanently plaintext (#76): provisioning now discards the untrusted key so the next attempt succeeds, and status reports audit-log encryption state.
  • Tor liveness (#74, #75): two-stage circuit-status probe — a BUILT circuit is healthy on its own; empty/in-progress replies resolve via GETINFO network-liveness — so idle Tor sessions are no longer sticky-dropped and a running-but-circuit-less Tor is still detected; bootstrap reader now works on Windows (thread+queue instead of selectors-on-pipe).
  • WebAuthn (#74, #75): registration keys validated as DER EC P-256 before persistence with byte-for-byte preservation of the prior credential on rejection; webauthn registered in the lazy-import registry with pre-split patch seams mirrored.
  • Wallet (#71, #76): transactions canonicalized and validated before approval, then only the exact approved representation is signed; sign approval requires a literal boolean true (the JSON string "false" no longer crosses the approval boundary); fee_data falls back on HTTP-level method rejection too.
  • Keyvault portability (#75): fcntl guarded so the extension stack imports off POSIX (with POSIX re-raise); O_NOFOLLOW and parent-dir fsync made portable.
  • Misc (#76): BIP39 mnemonics canonicalized before validation; mullvad status/lockdown-mode parsing requires exact tokens and never conflates query failure with OFF; policy=off restores the pre-runtime proxy environment; workspace purge no longer refuses on a stray .DS_Store; E2E replay capacity exhaustion reports recovery; provider_overrides preserved through configure/upgrade/OpenClaw migration (#71); broken pairing installs present as exit-2 ExtensionGatewayUnavailable instead of a raw traceback (#75).

v0.1.0a8

v0.1.0a8 Pre-release
Pre-release

Choose a tag to compare

@spiderings spiderings released this 27 Jul 06:57
d2f2389

Changes

  • extension page: ship the regenerated localhost app with restart recovery, cache-safe token delivery, and immediate correlated wallet RPC errors instead of timeouts (#69).
  • extension provenance: regenerate the bundled page from Mordred-Extension main so the latest Discord/Teams i18n and protocol changes are included (#70).
  • E2E operations: add focused reproduction scripts and restore the Slack E2E operator guide for outbound encryption regressions (#68).
  • chore: bump all package, plugin-manifest, README, and setup surfaces to 0.1.0a8 (#72).

Fixes

  • Prevent Slack replies from falling through to cleartext by normalizing platform enums and aligning channel/thread fallback behavior (#68).
  • Patch the actual live Slack/Discord adapter classes from GatewayRunner.adapters, rather than import-path guesses that could wrap unused classes (#68).
  • Preserve Discord thread metadata during outbound encryption lookup so encrypted conversations cannot miss their key and delegate to plaintext send (#68).
  • Fail closed when no valid reply key is known instead of falling back to an unreadable master key (#68).
  • Mark token-bearing localhost HTML no-store/nosniff/no-referrer and return correlated generic errors for wallet lookup failures (#69).

Install

pip install "mordred-hermes==0.1.0a8"

v0.1.0a7

v0.1.0a7 Pre-release
Pre-release

Choose a tag to compare

@spiderings spiderings released this 20 Jul 23:24
2f825ab

Changes

  • wizard: fix mordred_e2e not being enabled by configure — the 0.1.0a6 E2E plugin was silently inert for every configure user; add mordred_e2e to MORDRED_PLUGIN_NAMES + regression tests (#63)
  • docs: update stale "5 plugins" wording repo-wide (CLAUDE.md, README, setup.md, SPEC.md, PLAN.md, release runbook) to reflect the 6 entry-point plugins shipped since 0.1.0a6 (#63, #66)
  • extension: cap-relief refactors — extract wallet/sign helpers from api.py into extension/wallet.py (810→675 lines), split serve()'s event-loop lifecycle into _run_forever (48/50→14/50 statements), split verify_webauthn_assertion into fail-closed phase helpers (complexity 10→6, was at the hard cap) (#64)
  • extension: first automated test coverage for outbound E2E send wrappers (tests/extension/test_extension_outbound.py, 7 tests) — Slack/Discord ciphertext round-trips, fail-closed no-key paths, plaintext passthrough, Discord parent-channel key lookup (#64)
  • deps: resync the provider-alias replica (_provider_identity.py) and uv.lock to hermes-agent 0.19.0 (#65)
  • chore: version bump to 0.1.0a7 (#66)

Fixes

  • configure never wrote mordred_e2e into plugins.enabled (regression-by-omission from #60) (#63)
  • test isolation bug: outbound tests leaked gateway* modules into sys.modules, silently defeating an unrelated test's missing-gateway simulation (#64)
  • CI red on dev post-merge: test_replica_matches_hermes_source drift against hermes-agent 0.19.0's 2 new provider aliases (#65)

Install

pip install "mordred-hermes==0.1.0a7"

v0.1.0a6

v0.1.0a6 Pre-release
Pre-release

Choose a tag to compare

@spiderings spiderings released this 20 Jul 11:36
23ab1ef

PyPI: https://pypi.org/project/mordred-hermes/0.1.0a6/

Changes

  • E2E transport now runs as a stock hermes-agent plugin — inbound decrypt via the pre_gateway_dispatch hook, outbound reply-in-kind encryption via adapter send wrappers; no hermes-agent core fork (#60)
  • Mandatory encryption on Slack/Discord: a message that cannot be decrypted (plaintext, or ciphertext with an unheld key) is never handed to the agent — the sender is asked to set up or obtain an encryption key and the message is skipped; non-enforced platforms pass through unchanged (#60)
  • Key-exchange (🔑) control messages are dropped before dispatch so the agent never reacts to them (#60)
  • Outbound fail-closed: a missing key in a known-encrypted thread posts a locked notice, never plaintext (#60)
  • New mordred_e2e entry-point plugin (mordred_hermes.extension.gateway_plugin) — entry-point discovery is now 6 plugins (#60)
  • Bump package version to 0.1.0a6 (#61)

Fixes

  • (none)