Skip to content

feat: Support multiple marketplace registrations with auto-load semantics #2494

@jpshackelford

Description

@jpshackelford

Problem Statement

PR #2253 added a marketplace_path string that filters which skills to load from the public skills repository. This design has two limitations:

  1. Single source: Users can only load skills from one marketplace (the public skills repo). Enterprises need to register multiple marketplaces — their own internal marketplace, team-specific marketplaces, and optionally the public marketplace.
  2. No registration vs auto-load distinction: The current model is all-or-nothing. There's no way to say "I want this marketplace registered and available, but don't auto-load its plugins into every conversation."

Proposed Solution

Replace the single marketplace_path: str with a list of MarketplaceRegistration objects:

class MarketplaceRegistration(BaseModel):
    name: str                              # Identifier for this registration
    source: str                            # github:owner/repo, git URL, or local path
    ref: str | None = None                 # Optional branch/tag/commit
    repo_path: str | None = None           # Subdirectory within repo (for monorepos)
    auto_load: Literal["all"] | None = None

Behavior:

  • auto_load: "all" — Fetch the marketplace and load all plugins declared in its manifest at conversation start
  • auto_load: None (default) — Marketplace is registered for plugin resolution but plugins are not auto-loaded

Examples:

AgentContext(
    registered_marketplaces=[
        # Standalone marketplace repo
        MarketplaceRegistration(
            name="public", 
            source="github:OpenHands/skills", 
            auto_load="all"
        ),
        # Monorepo with marketplace in subdirectory
        MarketplaceRegistration(
            name="team", 
            source="github:acme/monorepo",
            repo_path="marketplaces/internal",
            auto_load="all"
        ),
        # Registered but not auto-loaded
        MarketplaceRegistration(
            name="experimental", 
            source="github:acme/experimental"
        ),
    ]
)

Plugin Resolution

When a marketplace is registered (regardless of auto_load setting), the SDK resolves plugin references against it.

MarketplaceRegistry (New)

Introduce a new internal MarketplaceRegistry class to manage registered marketplaces:

class MarketplaceRegistry:
    """Manages registered marketplaces with lazy fetching and plugin resolution."""
    
    def __init__(self, registrations: list[MarketplaceRegistration]): ...
    
    def resolve_plugin(self, plugin_ref: str) -> PluginSource:
        """Resolve 'plugin-name' or 'plugin-name@marketplace' to a PluginSource."""
        ...
    
    def prefetch_all(self) -> None:
        """Eagerly fetch all registered marketplaces (optional, for validation)."""
        ...

The registry is an internal implementation detail — users configure registered_marketplaces on AgentContext, and the SDK manages the registry internally.

Lazy Loading

Marketplace fetching happens lazily on first use, not at registration time:

  • Registering a marketplace with auto_load: None has zero startup cost
  • The marketplace repo is only fetched when a plugin from it is first referenced
  • Fetched marketplaces are cached for the session
  • Code managing the registry may optionally call prefetch_all() for eager validation

Manifest Lookup

Uses the existing Marketplace.load() behavior which checks MARKETPLACE_MANIFEST_DIRS (.plugin, .claude-plugin) for marketplace.json.

Reference Formats

  • plugin-name@marketplace-name — Explicit marketplace qualifier
  • plugin-name — Search registered marketplaces (error if ambiguous across marketplaces)

Resolution Flow

  1. Parse the reference to extract plugin name and optional marketplace name
  2. Look up marketplace in registered_marketplaces
  3. Fetch/cache the marketplace repo if not already fetched (lazy)
  4. Load marketplace manifest via existing Marketplace.load()
  5. Find the plugin in the marketplace's manifest
  6. Return resolved plugin source for loading

Deprecations

The following APIs added in #2253 (v1.13.0) will be deprecated:

Deprecated Replacement
AgentContext.marketplace_path AgentContext.registered_marketplaces
load_public_skills(marketplace_path=...) Use marketplace registry
load_available_skills(marketplace_path=...) Use marketplace registry
SkillsRequest.marketplace_path (agent-server) SkillsRequest.registered_marketplaces

Backward compatibility:

  • Deprecated fields/parameters continue to work with deprecation warnings

  • If marketplace_path is set and registered_marketplaces is empty, automatically convert to:

    MarketplaceRegistration(
        name="public",
        source=PUBLIC_SKILLS_REPO,
        ref=PUBLIC_SKILLS_BRANCH,
        auto_load="all"
    )

Implementation Scope

In Scope

  • Add MarketplaceRegistration model to plugin/types.py
  • Add MarketplaceRegistry class (new) for lazy fetching, caching, and resolution
  • Add registered_marketplaces: list[MarketplaceRegistration] to AgentContext
  • Reuse existing Marketplace.load() for manifest loading
  • Add plugin resolution logic (resolve_plugin(name, marketplace?) -> PluginSource)
  • Update plugin loading to use resolution against registered marketplaces
  • Update skill loading to iterate over registered marketplaces with auto_load="all"
  • Deprecate marketplace_path APIs (see table above) with backward compatibility
  • Tests for multiple marketplace loading and plugin resolution

Out of Scope (Future Work)

  • auto_load: ["plugin-a", "plugin-b"] selective loading
  • Conflict resolution UI/UX for ambiguous plugin names (for now: error)

Related Issues

Directly unblocked by this issue:

  • OpenHands/OpenHands#13117 — Alternative to marketplace_path setting
  • APP-708 — Enables org-level default plugins via scoped marketplace registration
  • PRD-71 — SDK provides MarketplaceRegistration model for CLI marketplace management
  • APP-769 — SDK provides plugin resolution for /plugin:command invocation

Builds on:

Metadata

Metadata

Assignees

No one assigned

    Labels

    pluginsAbout plugins and their contents

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions