feat(marketplace): auto-load user-installed plugins from ~/.openhands (parity with load_user_skills)#3877
Draft
jpshackelford wants to merge 1 commit into
Draft
feat(marketplace): auto-load user-installed plugins from ~/.openhands (parity with load_user_skills)#3877jpshackelford wants to merge 1 commit into
jpshackelford wants to merge 1 commit into
Conversation
Add `AgentContext.load_user_plugins` (default False) so local conversations auto-load enabled plugins from `~/.openhands/plugins/installed/` at startup, mirroring `load_user_skills` for plugins and closing the SDK-vs-Claude-Code gap where installed plugins had install/list/load APIs but no startup auto-load path. - AgentContext.load_user_plugins: opt-in flag, resolved lazily by LocalConversation (like load_project_skills) since it needs runtime context. - LocalConversation._ensure_plugins_loaded: after marketplace/explicit plugin loading, merge enabled installed plugins via load_installed_plugins(). Installed plugins are already on disk and version-pinned, so they are loaded directly (not fetched) and not added to _resolved_plugins. Loading is best-effort so a bad installed plugin cannot block conversation startup. - Refactor the per-plugin merge into a local _merge_loaded_plugin helper reused by both the fetched-plugin loop and the installed-plugin path. - Tests: enabled load, default-off (loader not even queried), and best-effort-on-error; plus an AgentContext field default/serialization test. Co-authored-by: openhands <openhands@all-hands.dev>
Contributor
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
Contributor
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
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.
Why
The marketplace-registration stack (#3824–#3831) gave plugins a great deal of new loading machinery (registry, registered-marketplace auto-load, runtime
load_plugin, agent-server APIs). But it left one asymmetry with skills: there is no startup path that auto-loads user-installed plugins from~/.openhands/plugins/installed/the wayAgentContext.load_user_skillsdoes for skills.Today:
install_*→~/.openhands/.../installed/list/load_installed_*APIload_user_skills)LocalConversation._ensure_plugins_loaded()only loaded plugins from (1)registered_marketplaceswithauto_load=Trueand (2) explicitplugins=[...]specs — it never read the installed-plugins home dir. The only callers ofload_installed_plugins()were an example. This is the gap where Claude Code supports "drop a plugin in your home dir and it's picked up," and we did not.What changed
AgentContext.load_user_plugins: bool(defaultFalse) — opt-in, mirroringload_user_skills. Likeload_project_skills, it is resolved lazily byLocalConversation(the workspace/runtime context isn't known atAgentContextvalidation time).LocalConversation._ensure_plugins_loaded()— after marketplace/explicit plugin loading, ifload_user_pluginsis set, merge enabled installed plugins viaload_installed_plugins(). Installed plugins are already on disk and version-pinned byinstall_plugin, so they are loaded directly (not fetched) and are not added to_resolved_plugins(which tracks remote sources for deterministic resume). Loading is best-effort so a single broken installed plugin can't block conversation startup (same guard pattern asload_user_skills/ project-skills)._merge_loaded_pluginhelper reused by both the fetched-plugin loop and the new installed-plugin path — no behavior change for existing plugin loading.Precedence / semantics
Installed plugins are merged after marketplace/explicit plugins using the same
add_skills_to/add_mcp_config_to/ hook / agent merge as fetched plugins, so behavior is consistent with the existing plugin path. Default isFalse, so this is fully opt-in and non-breaking.Tests
tests/sdk/conversation/test_local_conversation_plugins.py::TestLocalConversationUserPluginstest_load_user_plugins_loads_installed_plugins— installs a plugin into a temp installed dir and asserts its skill is merged into the agent context; confirms it is not tracked inresolved_plugins.test_load_user_plugins_disabled_by_default— with the default flag the installed-plugins loader is not even queried.test_load_user_plugins_best_effort_on_loader_error— a failing loader does not block startup.tests/sdk/context/test_agent_context.py::test_load_user_plugins_defaults_false_and_is_settable— default/serialization round-trip.uv run pytest tests/sdk/conversation/test_local_conversation_plugins.py tests/sdk/context/test_agent_context.py -q # broader: tests/sdk/context tests/sdk/marketplace tests/sdk/plugin tests/sdk/conversation all green (1247 passed) uv run pre-commit run --files \ openhands-sdk/openhands/sdk/context/agent_context.py \ openhands-sdk/openhands/sdk/conversation/impl/local_conversation.py \ tests/sdk/conversation/test_local_conversation_plugins.py \ tests/sdk/context/test_agent_context.pyNotes / scope
main(the marketplace foundation feat(marketplace): add marketplace registry foundation #3824–feat(marketplace): support runtime local plugin loading #3826 is already merged and released in v1.29.2), so this can land independently as a sibling follow-up to the open stack rather than being stacked behind the in-flight PRs.load_user_pluginsthrough the/api/skills-style request models could be a natural follow-up, matching howload_user_skillsis surfaced.This PR was created by an AI agent (OpenHands) on behalf of @jpshackelford.
@jpshackelford can click here to continue refining the PR
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:986e075-pythonRun
All tags pushed for this build
About Multi-Architecture Support
986e075-python) is a multi-arch manifest supporting both amd64 and arm64986e075-python-amd64) are also available if needed