Nous Research will not maintain memory providers. These three entries
pointed at NousResearch/hermes-plugin-<name> repos labelled
Nous-maintained; with no maintainer they must not be offered as
official catalog plugins. The bundled copies under plugins/memory/
are unchanged and still win on name, so no user-visible behaviour
changes today.
v1.2.1 adds the first-run notice for the embedded server download and serialises client creation
so the start worker and the first memory operation cannot each start a daemon.
Submitters only saw a stale 5-point summary on the catalog page (it even listed
a 'Released' requirement nothing enforces), while reviewers applied ten README
rules plus criteria written nowhere public (credentials, approvals, disclosure,
compatibility metadata, forks of bundled plugins).
- plugin-catalog/README.md: rules 11-15 write down those review criteria; the
rule list sits between admission-rules markers.
- New developer-guide page 'Submitting to the plugin catalog': before you
submit, the PR flow, the full rule block (verbatim mirror), pin updates,
delisting vs removal, asking for a hook.
- The /plugins 'Submit' links and the user-guide section point at it.
- tests/website/test_catalog_rules_mirror.py fails when the two rule blocks
differ; classify_changes makes a README-only edit run it.
- AGENTS.md, plugins/AGENTS.md and CONTRIBUTING.md reference both files.
Catalog policy (Teknium): a listed plugin extends Hermes only through public
surfaces. A plugin that replaces core functions, methods or Desktop UI in place
collides with every other plugin patching the same seam and breaks on any core
release.
- New `no core override` admission check (hermes_cli/plugin_validate_core_override.py):
static taint pass over the import closure of __init__.py and dashboard/*.py.
Core values are Hermes modules (plus hermes_plugins.*) and attribute chains off
them, reached by import, importlib/sys.modules lookups, getattr, iteration, or a
plugin helper that returns one. Rebinds are attribute assignment/deletion,
setattr/delattr, sys.modules writes, writes into a core module's tables,
mock.patch, and passing a core value to a helper that setattr's its parameter.
Tests, benchmarks and CI/smoke scripts outside the import closure are not judged.
- Desktop lint: new `app DOM reach` rule (document-level queries of the app's own
data-slot/data-tour/data-sidebar/data-testid markup, and document.body
childList/subtree MutationObservers).
- Desktop lint bug: the comment stripper did not skip string literals, so a string
holding `/*` (a glob like '**/*.md') opened a fake comment and hid every line up
to the next `*/` from all rules. Strings are now matched first and kept.
- README rule 9, the plugin-catalog user guide and the plugin developer guide's
compatibility contract state the rule.
A/B over 466 pinned trees (121 open catalog PR entries + 349 listed entries +
humalike): the new checks flag exactly the hand-verified overrides; zero crashes;
no previously reported Desktop finding disappears.
The pinned-source job joined a PR-controlled subdir onto the clone root
with no validation, accepted file:// repos and ref names as the "pin",
and followed committed symlinks out of the clone, so the gate could
validate attacker-planted files instead of the declared commit's
contents. Harden the step end to end:
- validate repo (https://, whitespace-free), sha (40-hex, commit object),
and subdir (plain relative path) in the parser before any use
- run the parser under python3 -I against installed ruamel.yaml so the
PR checkout cannot shadow its imports, capture parser output, and
reset shell variables per entry so a dead parser cannot replay the
previous entry's fields
- confine PLUGIN_DIR and every symlink beneath it to the clone root
- include renamed and typechanged entry files in the changed set
- feed entries over fd 3 and keep plugin-executed commands off stdin so
validate cannot drain the remaining list
- NUL-delimit both self-updater grep stages and sanitize the echoed
filename list so crafted names cannot forge workflow commands
- mirror the same repo/subdir rules in the runtime catalog loader and
the site extractor, and use \Z anchors so a trailing newline cannot
split the verdict between gates
Moves the pin from 176f8c2 (v1.0.1) to eb021da3 on vectorize-io/hindsight main. That range
drops the retired `tools.lazy_deps` call and the `hindsight-all` dependency (#4930), and
starts the embedded daemon without Hermes' PYTHONPATH (#4936, #4946), so local_embedded runs
on PM-managed Hermes. Removes the `known_issues` entry and the description sentence that
documented the old pin's lazy-install loop.
`version` is "1.1.0+eb021da": the v1.1.0 tag predates these fixes, and plugin.yaml at this
commit still says 1.1.0.
Opt-in idle/daily gateway conversation resets, now that core no longer rotates conversations on timers (1d5d059410). Reads the existing session_reset block; #126244 points users with that block at this plugin. Pinned to bb618e08, which declares its pre_gateway_dispatch hook (fastfinge/hermes-session-reset-policy#1).
- Bump pin ae81c5b -> f5a880c (docs-only commit: refreshed screenshot,
new 1280x640 social preview, HOW_TO_USE.md guide linked from README)
- Add image: raw.githubusercontent URL of the social preview (2:1, 1280x640)
- hermes plugins validate passes at the pinned commit
restore now holds the memory tool's own lock while it writes MEMORY.md/USER.md and
refuses when a file changed after its plan, so it never replaces a running agent's
memory write. The plan lists the memory entries it brings back and removes.
Importing a Hermes backup put the backup's copy of the history back and cut off
every version recorded after it, the state right before the import included.
Those versions now stay, and the import is recorded as one change restore can undo.
Re-pins AhmetArif0/hermes-memory-rewind to 5f935c2 (1.3.0). A change made outside the
agent's turns was credited to whatever came next: a write approved with /memory approve
showed up as the agent's next memory call, a curator archive as the next session's. Each
turn now starts with a baseline (pre_llm_call, which declares only session_id and
platform), and the session-start baseline names no session. Adds pre_llm_call to hooks.
Re-pins AhmetArif0/hermes-memory-rewind from b747327 to e5b139b, which only redraws
docs/card.png. The plugin page header crops the 2:1 image to rows 110-490, which cut off
the card's title and last line. The plugin directory and version 1.2.0 are unchanged.
Maintenance follow-up to v1.1.2. Re-pin to d7cab7df (v1.1.3) and correct
the egress disclosure: CLI, Desktop and eligible gateway platforms are
forwarded; Telegram-origin lifecycle, approval-prompt, and (when enabled)
approval-response copies are suppressed because the core Telegram gateway
already delivers them in that chat.
Adopts upstream fix T-099: adapter dropped its pyyaml import in favor of
core-dep hermes_yaml (gateway venv runs with python -I, pyyaml absent);
plugin __init__ fallback hardened to only catch the no-parent-package
case so dependency errors surface unmasked.
Commit range 073f2e8..8de6264 touches only adapter.py (import + one
call site) and __init__.py (fallback guard) in the plugin repo.
## Thinking Path
`plugin-catalog/hindsight.yaml` already documented in prose that
`local_embedded` mode is unsupported on PM-managed Hermes with the current
pin — it still calls the retired lazy-install path for `hindsight-all` and
loops update takeover on every conversation — yet `hermes plugins install`
and the dashboard install path accepted the entry with no gate at all. Users
only discovered the trap after the death loop started. The catalog knew;
the installers didn't act on it.
## What Changed
- `hermes_cli/plugin_catalog.py`: new `known_issues: List[str]` field on
`PluginCatalogEntry` (parsed from `known_issues` in each `plugin-catalog/*.yaml`,
serialized into `to_dict()`, defaults to empty for backward compatibility).
- `plugin-catalog/hindsight.yaml`: declares the documented local-embedded trap
as machine-readable `known_issues`.
- `hermes_cli/plugins_cmd_install.py`:
- `cmd_install`: for catalog entries with `known_issues`, prints each issue
and a bold-red notice, then requires explicit confirmation. Non-interactive
(non-TTY) runs fail closed; interactive `y/N` runs require `y`.
- `dashboard_install_plugin`: non-interactive path refuses the entry outright
(no GUI bypass, mirroring the existing kill-list posture) and returns the
issue list in the error payload.
## Why This Shape
The trap is machine-readable in the catalog, so the enforcement lives in the
install entry points rather than in hindsight-specific code — any future
catalog entry that documents a known trap gets the same gate for free. The
non-interactive path fails closed because it cannot ask for the confirmation
the interactive path requires.
## Verification
- RED/GREEN double proof via git stash: pre-fix 7 failed / 3 passed, post-fix
10/10 passed (catalog parsing round-trip, hindsight.yaml declares the trap,
non-TTY refuse, TTY yes proceeds, TTY no cancels, custom-source install not
gated, dashboard refuse + unaffected path).
- Adjacent suites: test_plugin_catalog.py, test_plugin_validate.py,
test_plugins_hub_live_catalog.py all green (36 passed). 9 errors in
test_plugins_cmd_catalog.py reproduce identically with the fix stashed
(existing local `pm` / uv-sync environment limitation, unrelated).
- ruff clean on all changed files.
## Contract Change
New optional catalog field `known_issues` (list of strings). Entries without
it behave exactly as before. Install behavior changes only for entries that
declare `known_issues`.
## Risks
- Catalog entries that declare `known_issues` become gated installs. Authors
of future entries should only declare issues they want surfaced — this is
the intended trade (a documented trap must not install silently).
- `cmd_install` non-TTY automation installing hindsight by name now fails.
That is intentional: the trap cannot be confirmed non-interactively.
## Model Used
jd-deepseek-v4-flash-0731 (main session); pytest + ruff in the isolated
worktree.
## Related
#124037 (issue). Related: #122326 (resulting takeover loop), #7718
(`local_embedded` needs `hindsight-all`).