Bug Description
Hermes v2026.8.13 / v0.20.1 shipped memory-provider registration parity through NousResearch/hermes-agent#85527. Its _ProviderCollector now delegates register_command, register_skill, and the other register_* APIs through a real PluginContext.
The Basic Memory integration still unconditionally calls its private _register_via_plugin_manager() fallback after using the supported context APIs. On modern Hermes, that fallback overwrites the lifecycle-owned command and skill entries that Hermes just registered.
This is no longer only technical debt: it defeats Hermes's ownership ledger and can leave stale Basic Memory slash commands installed after the provider is unloaded.
Relevant Basic Memory paths:
integrations/hermes/__init__.py
integrations/hermes/tests/test_commands.py
integrations/hermes/plugin.yaml
integrations/hermes/MONKEYPATCH.md
integrations/hermes/README.md
Why This Happens
The current registration flow is effectively:
ctx.register_command(...) and ctx.register_skill(...) run.
- Hermes records lifecycle handles for those exact registry objects.
_register_via_plugin_manager() reaches into the private PluginManager registries and replaces the entries with new raw dictionaries.
- Hermes later tries to restore/remove the objects it originally registered using identity checks.
- The replacement entries do not match, so cleanup does not remove them.
The current unit test also locks in the legacy raw command shape:
{
"handler",
"description",
"plugin",
"args_hint",
}
Modern Hermes's supported registration adds ownership metadata such as plugin_key.
Reproduction / Evidence
Run the current Basic Memory provider against the released Hermes v2026.8.13 plugin runtime, register it through the modern provider collector, then dispose Hermes's lifecycle ledger.
Observed immediately after registration:
{
'provider': 'basic-memory',
'entry_keys': ['args_hint', 'description', 'handler', 'plugin'],
'ledger_count': 1,
'entry_has_plugin_key': False
}
Observed after disposing the ledger:
{'command_present_after_ledger_dispose': True}
The provider command remains present and holds the old provider closure.
Hermes Plugin Doctor currently reports the plugin as valid but does not catch this lifecycle/identity failure:
Plugin Doctor: integrations/hermes
manifest: basic-memory 0.22.1 (exclusive)
OK...
registrations: 0 tool(s), 0 hook(s)
Upstream implementation references:
Expected Behavior
On Hermes v2026.8.13/v0.20.1 and newer:
- Basic Memory registers slash commands and the bundled skill only through
PluginContext.
- Hermes owns their lifecycle.
- Disposing/unloading the provider removes its registrations.
- Basic Memory does not mutate private Hermes registries after supported registration succeeds.
Older Hermes versions can retain a narrowly gated compatibility fallback if we still intend to support them.
Proposed Solution
- Treat
ctx.register_command() and ctx.register_skill() as the primary and complete path.
- Do not call
_register_via_plugin_manager() for a capability that the supplied context supports.
- Preserve legacy compatibility, if desired, only as a per-capability fallback when the context lacks
register_command or register_skill.
- This is the recommended transition because existing Basic Memory users may still be on Hermes v0.17/v0.18.
- Alternatively, declare Hermes v0.20.1 as the minimum supported version and remove the private reach-in entirely.
- Build each slash-command handler once so the supported and legacy paths cannot produce competing closure identities.
- Update tests to cover both the modern lifecycle path and any intentionally supported legacy collector path.
- Upgrade
plugin.yaml to the current manifest contract:
manifest_version: 2
api_version: 1
kind: exclusive
python_dependencies: ["mcp>=2,<3"]
- retain bounded legacy
pip_dependencies only if old Hermes compatibility remains required
- Update
MONKEYPATCH.md and the Hermes README to distinguish the shipped collector fix from the remaining upstream startup gap.
Remaining Upstream Gap
The original Hermes tracker comment says the expansion wave is complete, and the collector half of our monkeypatch is now obsolete. However, released hermes_cli.plugins.get_plugin_commands() still only returns the current command registry; it does not first load the configured exclusive memory provider.
That means native /bm-* discovery at gateway boot may still require the active-provider-load half of the workaround or an upstream change. This should stay separate from Basic Memory's registration lifecycle fix.
Related upstream references:
Acceptance Criteria
Bug Description
Hermes v2026.8.13 / v0.20.1 shipped memory-provider registration parity through NousResearch/hermes-agent#85527. Its
_ProviderCollectornow delegatesregister_command,register_skill, and the otherregister_*APIs through a realPluginContext.The Basic Memory integration still unconditionally calls its private
_register_via_plugin_manager()fallback after using the supported context APIs. On modern Hermes, that fallback overwrites the lifecycle-owned command and skill entries that Hermes just registered.This is no longer only technical debt: it defeats Hermes's ownership ledger and can leave stale Basic Memory slash commands installed after the provider is unloaded.
Relevant Basic Memory paths:
integrations/hermes/__init__.pyintegrations/hermes/tests/test_commands.pyintegrations/hermes/plugin.yamlintegrations/hermes/MONKEYPATCH.mdintegrations/hermes/README.mdWhy This Happens
The current registration flow is effectively:
ctx.register_command(...)andctx.register_skill(...)run._register_via_plugin_manager()reaches into the privatePluginManagerregistries and replaces the entries with new raw dictionaries.The current unit test also locks in the legacy raw command shape:
{ "handler", "description", "plugin", "args_hint", }Modern Hermes's supported registration adds ownership metadata such as
plugin_key.Reproduction / Evidence
Run the current Basic Memory provider against the released Hermes v2026.8.13 plugin runtime, register it through the modern provider collector, then dispose Hermes's lifecycle ledger.
Observed immediately after registration:
Observed after disposing the ledger:
The provider command remains present and holds the old provider closure.
Hermes Plugin Doctor currently reports the plugin as valid but does not catch this lifecycle/identity failure:
Upstream implementation references:
Expected Behavior
On Hermes v2026.8.13/v0.20.1 and newer:
PluginContext.Older Hermes versions can retain a narrowly gated compatibility fallback if we still intend to support them.
Proposed Solution
ctx.register_command()andctx.register_skill()as the primary and complete path._register_via_plugin_manager()for a capability that the supplied context supports.register_commandorregister_skill.plugin.yamlto the current manifest contract:manifest_version: 2api_version: 1kind: exclusivepython_dependencies: ["mcp>=2,<3"]pip_dependenciesonly if old Hermes compatibility remains requiredMONKEYPATCH.mdand the Hermes README to distinguish the shipped collector fix from the remaining upstream startup gap.Remaining Upstream Gap
The original Hermes tracker comment says the expansion wave is complete, and the collector half of our monkeypatch is now obsolete. However, released
hermes_cli.plugins.get_plugin_commands()still only returns the current command registry; it does not first load the configured exclusive memory provider.That means native
/bm-*discovery at gateway boot may still require the active-provider-load half of the workaround or an upstream change. This should stay separate from Basic Memory's registration lifecycle fix.Related upstream references:
Acceptance Criteria
plugin_key.plugin.yamluses manifest v2 metadata and bounds the MCP dependency tomcp>=2,<3.MONKEYPATCH.mddocuments that collector delegation shipped in Hermes v0.20.1 and identifies only the remaining active-provider startup load gap.just package-check-hermespasses.