feat: plugins install their declared Python dependencies and keep them across hermes update - #113851
Merged
Merged
Conversation
૮ >ﻌ< ა ci reviewran on 72a5844 — fix: read plugin-home config through the loader; register up debug infoCI timingsCI timings · View report · View jobWall time 6m9s vs 6m14s (-1.3%). 3 job(s) slower, 7 faster, 3 unchanged.
|
|
Independent verification on the PR head (47f8035): all three plugin suites pass locally, 24/24 on Linux (canonical runner). Failing validation on trees with nothing loadable closes a real silent-success hole, and checking dependency declarations at validate time beats discovering them at load. No findings. |
Plugin dependency installs need two things the shared installer ladder did not expose: a constraints file (so a plugin can never move a core pin) and a dry run (so a conflict can be detected before anything is written). Both are threaded through _venv_pip_install; the durable-target path keeps its own core constraints and a dry run skips syspath activation and bytecode warming.
… after hermes update A directory plugin's pyproject.toml [project].dependencies (or manifest python_dependencies) are now installed into the Hermes venv on install/enable/ update, resolved under a constraints file built from Hermes' own pinned dependencies together with every enabled peer's declarations. A candidate that cannot resolve is refused before its tree is moved into place; nothing is installed and no other plugin is touched. --no-deps opts a single install out. hermes update rebuilds the venv from Hermes' lock and strips everything else, so its git, zip and both repair paths now re-apply the union of every profile's enabled plugins. When the union no longer resolves, each plugin is resolved on its own so only culprits are dropped (never an alphabetical neighbour); a plugin-vs-plugin conflict peels non-memory plugins first because a Hermes that boots without memory reads as data loss. Dropped plugins are disabled through the real config writer with a loud message naming the fix. python_runtime: external lets sidecar-venv plugins (Mnemosyne's shape) opt out of the union; hermes-agent self-dependencies and direct-URL requirements are never installed (the latter are surfaced for the user to install by hand).
…ency declarations A plugin.yaml with no __init__.py, desktop/plugin.js or plugin.json beside it installs "successfully" and does nothing (pip-layout repos whose code sits under src/ behind an entry point). The validator and catalog CI now fail that shape and check declared Python dependencies parse and are index specs; direct-URL requirements warn. Tests: conflicting candidate refused with peers untouched; post-update re-apply drops only the culprit and keeps the memory provider. Loader wording test updated to the new hint.
The config-read guard forbids raw yaml loads of config.yaml outside owner modules, and hermes_cli.main's frozen lazy-export surface must list every update_cmd symbol it proxies.
teknium1
force-pushed
the
feat/plugin-python-deps
branch
from
September 17, 2026 07:04
49ce586 to
72a5844
Compare
This was referenced Sep 17, 2026
AxDSan
added a commit
to mnemosyne-oss/hermes-agent
that referenced
this pull request
Sep 17, 2026
subdir moves to integrations/hermes-catalog, the directory plugin shape from NousResearch#113851 (plugin.yaml kind exclusive, __init__.py shim, dependency pyproject). sha 17abb10 is the merge of mnemosyne-oss/mnemosyne#974. Tool list trimmed to the 37 tools register() exposes in an isolated probe; requires_hermes >=0.22.
teknium1
added a commit
that referenced
this pull request
Sep 17, 2026
…e the capability probe Catalog CI validates each pinned tree in a venv that has only hermes-agent, so any plugin whose code arrives through pyproject dependencies (the wrapper shape from #113851) failed the probe with "No module named ...". The flag runs the same constrained install `plugins install` would, then probes; the workflow passes it. Live: mnemosyne pin fails without the flag, passes with it.
konsisumer
pushed a commit
to konsisumer/hermes-agent
that referenced
this pull request
Sep 17, 2026
subdir moves to integrations/hermes-catalog, the directory plugin shape from NousResearch#113851 (plugin.yaml kind exclusive, __init__.py shim, dependency pyproject). sha 17abb10 is the merge of mnemosyne-oss/mnemosyne#974. Tool list trimmed to the 37 tools register() exposes in an isolated probe; requires_hermes >=0.22.
konsisumer
pushed a commit
to konsisumer/hermes-agent
that referenced
this pull request
Sep 17, 2026
…ated Devs-Foundation entry The Devs-Foundation/mnemosyne entry (created 2026-08-10, last push 2026-08-11, 0 stars) is unrelated to the Mnemosyne project per its maintainers and registers the same memory-provider name, which makes memory.provider: mnemosyne ambiguous. It is delisted (plain removal, not the removed.yaml blocklist: nothing malicious) and welcome back under a distinct name. The official mnemosyne-oss submission (NousResearch#113581, wrapper shape validated end to end) takes the bare key. requires_hermes floor set to the first release that carries NousResearch#113851 (0.21.4 or later). README gains the delist-vs-remove distinction and the provider-name collision rule.
teknium1
added a commit
that referenced
this pull request
Sep 18, 2026
… same-name pip entry point The pyproject wrapper shape (#113851) depends on a pip package that often ships its own hermes_agent.plugins entry point under the same name. Discovery appended entry points last with "later wins", so after a catalog install the plugin row became source=entrypoint with no install dir or catalog provenance: Desktop lost "installed from catalog @ version / update to ..." for exactly the shape the catalog now recommends. Entry points no longer displace a directory plugin of the same key, in the loader and in the CLI/RPC listing. Live: catalog install of mnemosyne, row before = entrypoint / mnemosyne_hermes:register / no catalog fields; after = user / ~/.hermes/plugins/mnemosyne / catalog 0.7.0 @ 95ef3be2; provider still discovered. Test red on base.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Directory plugins can now declare Python dependencies (
pyproject.tomlor manifestpython_dependencies) and Hermes installs them under core constraints, refuses conflicts before install, and re-applies them afterhermes update— the contract agreed with the Mnemosyne team, carved onto current main instead of waiting on #102765.Why
python_dependencieswas declare-and-surface only (#15220, #64165): a plugin's packages had to beuv pip installed into the Hermes venv by hand and were lost on every venv rebuild. Mnemosyne asked for exactly this contract in the plugin-API thread; @ethernet8023 built it inside #102765, but that PR ships a whole package manager (2,547 files) and cannot be reviewed or merged as one unit. This PR is the ~1.5k-LOC slice on today's single venv. 23 of the 105 catalog plugins already ship apyproject.toml, so this turns their installs from "installed, won't load" into working installs.What changes
plugins update/ packs / Desktop install —hermes_cli/plugin_python_deps.pyreads the declaration (pyproject wins over manifest;pip_dependenciesaccepted as alias), drops environment-marker-excluded specs,hermes-agentself-deps and direct-URL requirements (the last are surfaced for the user), then installs through the sharedtools.lazy_deps.install_specsladder with constraints built from Hermes' own pinned dependencies and the union of every enabled peer's specs. A satisfied declaration is a no-op.PluginOperationErrornaming the pins, nothing is installed, no peer is touched.--no-depsonhermes plugins installopts one install out.hermes updatere-applies on the git, zip and both venv-repair paths (_reapply_plugin_python_dependencies), across the default home plus every live profile (customHERMES_HOMEroots included). If the union no longer resolves, each plugin is resolved alone so only culprits drop; remaining plugin-vs-plugin conflicts peel non-memory plugins first. Dropped plugins are disabled through the real config writer with a loud line naming the fix. Never blocks the update.python_runtime: external— sidecar-venv plugins (Mnemosyne's shape) declare it; Hermes installs nothing and they never join the union.hermes plugins validate/ catalog CI — aplugin.yamlwith no__init__.py,desktop/plugin.jsorplugin.jsonbeside it now FAILS (loadable), which is what would have caught the hollowmnemosyne-memorysubmission (plugin-catalog: add mnemosyne-memory #113581); declared specs must parse and be index specs (URL specs warn).tools.lazy_deps.install_specsgainsconstraintsanddry_run. Loader still never installs at import time; its hint now points athermes plugins enable <name>.Every new function is CC < 10 (
ruff C901 max-complexity=9on the new module passes; touched pre-existing functions were moved back to theirorigin/maincomplexity by extracting the new branches into helpers).Live evidence (scratch venv + scratch HERMES_HOME, real uv, real PyPI)
tabulate>=0.9+pywin32; sys_platform=='win32'python_dependencies: [art>=6.0]httpx<0.20vs corehttpx==0.28.1tabulate<0.9vs enabled peertabulate>=0.9requests; this is not a markerpython_runtime: external+torch==99in pyprojectmnemosyne-memoryPR tree (no__init__.py)✗ loadable — nothing to loadhttpx<0.20, run re-applyplugins enableon already-enabled plugin with deps removedsecurity.allow_lazy_installs: falsedashboard_install_pluginpython_dependenciesin result; conflict →ok: Falsewith the same messageworkunder customHERMES_HOMEwith an enabled pluginplugin.yaml kind: exclusive+__init__.pyre-exportingmnemosyne_hermes+ pyprojectmnemosyne-hermes>=0.7,<0.8,mnemosyne-memory[embeddings]>=3.11.1)discover_memory_providers()→('mnemosyne', available=True); after uninstalling all five packages, re-apply restores them and the provider is available againlancedbconflicts (requests>=2.34.2vs core CVE pinrequests==2.33.0) → will be refused from the catalog until the author relaxes it;--no-depsis the hatch. Upstream PR opened.cashew/weatherhave direct-URL deps → the rest install, URL ones are listed for the userUnit:
tests/hermes_cli/test_plugin_python_deps.py(2 invariants: conflicting candidate refused with peers untouched; re-apply drops only the culprit and keeps the memory provider). 39 touched test files, 587 pass.Infographic
Credit: design and prior art @ethernet8023 (#102765 §3), contract discussion @dplush / @abdiisan (mnemosyne-oss/mnemosyne#859).