fix(providers): skip platform and memory plugins in entry-point discovery - #98463
liuhao1024 wants to merge 2 commits into
Conversation
…very Provider discovery called ep.load() on every enabled hermes_agent.plugins entry point regardless of kind. Platform adapters are owned by the PluginManager (loaded lazily), and their documented entry modules do `from gateway.config import Platform` at the top — when discovery runs inside the gateway.config -> hermes_cli.config import chain, that import hits a half-initialized module and the platform silently disappears (NousResearch#98438). Skip entry points that are provably not model providers, import-free: a `<name>-platform` name (the manager's platform-id convention) or a memory-provider source signature (the classifier the PluginManager already trusts). Everything else keeps the historical load path, so a real provider is never dropped when its source cannot be classified.
|
The only failing check is This looks like the known Windows-only flake rather than something this PR can influence:
All other checks pass on this head (Python tests, e2e, macOS-only tests, lints, builds). A maintainer rerun should clear it; happy to push any change if it fails the same way twice on an otherwise green head. |
|
Verified against the main tree: One hardening thought, non-blocking: the name check |
…ort marker Follow-up to the import-free ownership precheck: the `-platform` name suffix only covers PluginManager-generated ids. A hand-written `plugins.enabled` entry can carry any name, so its adapter would still be imported and hit the same half-initialized `gateway.config` import chain. Scan the (already resolved) module source for the documented `from gateway.config import Platform` adapter base import as a third skip signal; unresolvable sources keep the fail-open load path. Suggested-by: kokhlo
|
Thanks for verifying against the main tree — and for the hardening thought, which was exactly the gap I was uncomfortable leaving: a hand-written I've landed it in 560dc44 as a third skip signal: the already-resolved module source is scanned for the documented
Unresolvable sources keep the fail-open load path, and a plain plugin without any marker is covered by a guard test asserting it still loads. |
|
560dc44 closes the gap cleanly — re-checking the three cover-spellings against the tree sold me: every adapter form is a module-top |
|
Thanks @kokhlo for the thorough re-check and for walking the adapter spellings against the tree — that's exactly the confirmation I was hoping for. Glad the fallback semantics held up too. Nothing further needed from my side; ready for maintainer review. |
What does this PR do?
_discover_entry_point_providers()inproviders/__init__.pycallsep.load()on every entry point listed inplugins.enabled, with no kind check before the import. The sharedhermes_agent.pluginsgroup also contains platform and memory-provider plugins, which are owned by the PluginManager /plugins/memorydiscovery and are meant to be loaded lazily. Importing a platform adapter here is actively harmful: documented adapters dofrom gateway.config import Platformat module top, and when provider discovery runs inside thegateway.config -> hermes_cli.configimport chain (the provider-env injection at the bottom ofhermes_cli/config.py),gateway.configis still half-initialized — the import fails with a circular-import error and the platform silently disappears (#98438).This PR adds an import-free ownership precheck before
ep.load()and skips entry points that are provably not model providers:<name>-platformentry-point name — the naming convention the PluginManager itself relies on to derive platform ids (_platform_name_from_manifest), orexclusive), detected with the same import-free classifier the PluginManager already trusts (_classify_entrypoint_value_kind).Anything else keeps the historical load path unchanged. This is deliberately conservative (blacklist of provable non-providers rather than a whitelist): a thin-
__init__or lazily-importing pip provider whose source cannot be classified asmodel-provideris still loaded exactly as before, so no working provider gets deregistered. The broader "shared classifier for both consumers" refactor in #85559 addresses the same group-ownership problem from the other side; this fix is intentionally scoped to the provider-registry side and is compatible with that direction.Related Issue
Fixes #98438
Type of Change
Changes Made
providers/__init__.py— in_discover_entry_point_providers(), classify each enabled entry point beforeep.load(): skip-platform-named entry points and memory-provider (exclusive) source signatures; classification failures fall through to the existing load path (fail-open, ~29 lines).tests/providers/test_entry_point_discovery.py—_FakeEPnow carries avalue(entry-point target) like a realEntryPoint; added three regression tests: platform-named entry point never imported, memory-provider-signature entry point never imported, unresolvable entry point still loaded (fail-open).How to Test
main(from the issue): create a stub pip plugin namedstub-platformwhose entry module doesfrom gateway.config import Platform, enable it, thenPYTHONPATH=/tmp/stub HERMES_HOME=/tmp/hh python -c "import gateway.config"→ Observed result:WARNING providers: Failed to load entry-point provider plugin 'stub-platform': cannot import name 'Platform' from partially initialized module 'gateway.config' (most likely due to a circular import).import hermes_cli.config; import stubplugstill works (the pre-fix working path is unchanged).pytest tests/providers/ tests/plugins/memory/test_discovery_sources.py tests/agent/test_memory_provider.py -q→ 158 passed (includes the 3 new tests; verified they fail on unpatched code for the two skip cases).Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass (targeted suites:tests/providers/, memory-discovery, plugin-relatedtests/hermes_cli/— the batchtests/hermes_cli/ -k plugin/providerhas 27 order-dependent failures reproducible identically on cleanmainat the same commit; each passes in isolation)Documentation & Housekeeping
docs/, docstrings) — code comments document the precheck; the plugin docs' "avoidgateway.*imports" caveat is a docs question for maintainers, out of scope herecli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Aimportlib.util.find_spec+pathlib, no platform-specific behaviorScreenshots / Logs
Before (main) vs after (this branch) for the issue repro
PYTHONPATH=/tmp/stub HERMES_HOME=/tmp/hh python -c "import gateway.config":