fix: scope plugin manager by profile home - #28109
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Scopes plugin discovery/loading (and context-engine registration) to the active HERMES_HOME so profile switches in long-lived processes don’t leak plugin/context-engine state across profiles.
Changes:
- Key the global
PluginManagersingleton by resolved Hermes home and recreate it when the active home changes. - Add a regression test ensuring context engines differ across different
HERMES_HOMEvalues. - Document the decision and consequences in an ADR.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
hermes_cli/plugins.py |
Reworks get_plugin_manager() to be home/profile-scoped and adds bookkeeping helpers. |
tests/hermes_cli/test_plugins.py |
Adds coverage for profile-switch behavior to prevent context-engine reuse across homes. |
docs/ADR.md |
Records the architectural decision to scope plugin manager state by Hermes home/profile. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if _plugin_manager is None or _plugin_manager_home != current_home: | ||
| _plugin_manager = PluginManager() | ||
| _plugin_manager_home = current_home | ||
| _plugin_manager_obj_id = id(_plugin_manager) | ||
| return _plugin_manager |
| # Tests and embedders historically monkeypatch ``_plugin_manager`` | ||
| # directly. If that happened, adopt the injected manager for the current | ||
| # home instead of throwing it away because our profile-scope bookkeeping is | ||
| # stale. | ||
| if _plugin_manager is not None and current_obj_id != _plugin_manager_obj_id: | ||
| _plugin_manager_home = current_home | ||
| _plugin_manager_obj_id = current_obj_id | ||
| return _plugin_manager |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
outsourc-e
left a comment
There was a problem hiding this comment.
I like the profile-scoped cache direction, but I can’t get the touched plugin test file clean locally yet. Running python -m pytest tests/hermes_cli/test_plugins.py -q on this branch still fails test_plugin_context_engine_is_scoped_by_hermes_home, with engine_a is None where the new regression expects the first profile-specific context engine to load. Since that’s the exact surface this PR is trying to fix, I’d want that passing before merge.
|
Hi — just wanted to clarify the relationship between this PR and #50346 ( #50346 exposes the current profile name inside a plugin's execution context — useful for plugins that need to know which profile they're running under. This PR (#28109) addresses a different problem: the plugin manager itself is a process-global singleton, so when two profiles share the same process (e.g. gateway running multiple profiles), they also share the same plugin manager instance and its internal state, including the The fix scopes the plugin manager cache to Would appreciate a review when you get a chance — happy to add more tests or adjust the approach if needed. Thanks! |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for identifying the remaining profile-isolation gap; current main still has a single process-global manager at hermes_cli/plugins.py:2027-2035, while discovery reads user plugins from the active home at hermes_cli/plugins.py:1347-1352.
Problems
- The regression switches
HERMES_HOMEin the process environment, but the multi-profile gateway usesset_hermes_home_override()(gateway/run.py:1438-1444), whose contract deliberately avoids mutatingos.environ(hermes_constants.py:23-30). It does not cover the production mechanism. _make_plugin_dir()writesplugins.enabledusing the ambientHERMES_HOMEwhen present (tests/hermes_cli/test_plugins.py:67-84); the new test invokes that helper before selecting either target home. This aligns with the existing failing-test review reportingengine_a is None.- A fresh manager alone does not clear
hermes_plugins.<slug>.*submodules._load_plugin()replaces only the package module insys.modules(hermes_cli/plugins.py:1843-1865), so relative imports can reuse code/state from the prior profile.
Suggested changes
- Test with
set_hermes_home_override()and explicit per-profile config, and purge or home-qualify plugin submodules. Use a keyed manager cache rather than repeatedly replacing one global slot.
Automated hermes-sweeper review.
| _plugin_manager = PluginManager() | ||
| _plugin_manager_home = current_home | ||
| _plugin_manager_obj_id = id(_plugin_manager) | ||
| return _plugin_manager |
There was a problem hiding this comment.
Creating a fresh manager does not clear hermes_plugins.<slug>.*; _load_plugin() only replaces the package parent, so relative imports can reuse profile A submodules under profile B. Purge the package and its prefixed submodules, or use a home-qualified import namespace, with a relative-import regression.
|
A cleaner re-implementation of the same fix is now available at PR #63702 ( This version:
Feel free to close this PR in favor of #63702, or cherry-pick as needed. Happy to help with anything needed for merge. Comment from terry197913 (Luna) |
Summary\n- Scope the plugin manager cache to the active Hermes home/profile instead of one process-global singleton\n- Prevent context-engine plugins like hermes-lcm from reusing another profile's engine and lcm.db\n- Add regression coverage for switching HERMES_HOME between profiles\n- Document the decision in docs/ADR.md\n\n## Verification\n- venv/bin/python -m pytest tests/hermes_cli/test_plugins.py tests/agent/test_context_engine.py tests/hermes_cli/test_plugins_cmd.py -q\n- venv/bin/python -m pytest /Users/blake/.hermes/plugins/hermes-lcm/tests -q\n- Manual smoke: daneel/friend/quentin/argus/dessin each resolve LCM to their own profile lcm.db\n