Skip to content

Commit a307c53

Browse files
docs: Add multiple marketplace registrations section to plugins guide
- Document auto_load strategies (auto-load vs on-demand) - Explain plugin@marketplace reference syntax (matches Claude Code) - Add expandable example code block - Include MarketplaceRegistration field reference table Related: OpenHands/software-agent-sdk#2495
1 parent f205ee0 commit a307c53

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed

sdk/guides/plugins.mdx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,133 @@ enable_plugin(info.name)
510510
uninstall_plugin(info.name)
511511
```
512512

513+
## Multiple Marketplace Registrations
514+
515+
For enterprise and team scenarios, you can register multiple plugin marketplaces
516+
with different loading strategies. This allows you to:
517+
518+
- Register internal team marketplaces alongside the public marketplace
519+
- Control which plugins auto-load at conversation start vs load on-demand
520+
- Reference plugins from specific marketplaces using the `plugin@marketplace` syntax
521+
522+
### Loading Strategies
523+
524+
| Strategy | Behavior |
525+
|----------|----------|
526+
| `auto_load="all"` | All plugins from the marketplace load automatically when a conversation starts |
527+
| `auto_load=None` (default) | Marketplace is registered but plugins are loaded on-demand via `load_plugin()` |
528+
529+
### Plugin Reference Syntax
530+
531+
Use the `plugin-name@marketplace-name` format to explicitly specify which
532+
marketplace a plugin comes from. This syntax follows the same convention as
533+
[Claude Code's plugin install command](https://code.claude.com/docs/en/plugins-reference).
534+
535+
```python icon="python"
536+
# Load a specific plugin from a registered marketplace
537+
conversation.load_plugin("greeter@demo")
538+
539+
# If only one marketplace has the plugin, the marketplace name is optional
540+
conversation.load_plugin("greeter")
541+
```
542+
543+
### Example: Auto-load and On-demand Loading
544+
545+
The example below demonstrates registering two marketplaces with different
546+
loading strategies, then loading an additional plugin on-demand.
547+
548+
<Note>
549+
Source: [examples/05_skills_and_plugins/04_multiple_marketplace_registrations/main.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/05_skills_and_plugins/04_multiple_marketplace_registrations/main.py)
550+
</Note>
551+
552+
```python icon="python" expandable examples/05_skills_and_plugins/04_multiple_marketplace_registrations/main.py
553+
"""Example: Multiple Marketplace Registrations
554+
555+
Demonstrates two loading strategies for marketplace plugins:
556+
557+
- auto_load="all": Plugins loaded automatically at conversation start
558+
- auto_load=None: Plugins loaded on-demand via conversation.load_plugin()
559+
560+
This example uses pre-created marketplaces in:
561+
- ./auto_marketplace/ - auto-loaded at conversation start
562+
- ./demo_marketplace/ - loaded on-demand
563+
"""
564+
565+
import os
566+
from pathlib import Path
567+
568+
from openhands.sdk import LLM, Agent, AgentContext, Conversation
569+
from openhands.sdk.plugin import MarketplaceRegistration
570+
571+
SCRIPT_DIR = Path(__file__).parent
572+
573+
574+
def main():
575+
llm = LLM(
576+
model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"),
577+
api_key=os.getenv("LLM_API_KEY"),
578+
base_url=os.getenv("LLM_BASE_URL"),
579+
)
580+
581+
# Register two marketplaces with different loading strategies
582+
agent_context = AgentContext(
583+
registered_marketplaces=[
584+
# Auto-loaded: plugins available immediately when conversation starts
585+
MarketplaceRegistration(
586+
name="auto",
587+
source=str(SCRIPT_DIR / "auto_marketplace"),
588+
auto_load="all",
589+
),
590+
# On-demand: registered but not loaded until explicitly requested
591+
MarketplaceRegistration(
592+
name="demo",
593+
source=str(SCRIPT_DIR / "demo_marketplace"),
594+
# auto_load=None (default) - use load_plugin() to load
595+
),
596+
],
597+
)
598+
599+
agent = Agent(llm=llm, tools=[], agent_context=agent_context)
600+
conversation = Conversation(agent=agent, workspace=os.getcwd())
601+
602+
# The "auto" marketplace plugins are already loaded
603+
# Now load an additional plugin on-demand from "demo" marketplace
604+
# Format: "plugin-name@marketplace-name" (same as Claude Code plugin syntax)
605+
conversation.load_plugin("greeter@demo")
606+
607+
resolved = conversation.resolved_plugins
608+
if resolved:
609+
print(f"Loaded {len(resolved)} plugin(s):")
610+
for plugin in resolved:
611+
print(f" - {plugin.source}")
612+
613+
# Use skills from both plugins
614+
conversation.send_message("Give me a tip, then greet me!")
615+
conversation.run()
616+
617+
print(f"\nEXAMPLE_COST: {llm.metrics.accumulated_cost:.4f}")
618+
619+
620+
if __name__ == "__main__":
621+
if not os.getenv("LLM_API_KEY"):
622+
print("Set LLM_API_KEY to run this example")
623+
print("EXAMPLE_COST: 0")
624+
else:
625+
main()
626+
```
627+
628+
<RunExampleCode path_to_script="examples/05_skills_and_plugins/04_multiple_marketplace_registrations/main.py"/>
629+
630+
### MarketplaceRegistration Fields
631+
632+
| Field | Type | Description |
633+
|-------|------|-------------|
634+
| `name` | `str` | Identifier for this marketplace registration |
635+
| `source` | `str` | Plugin source: `github:owner/repo`, git URL, or local path |
636+
| `ref` | `str \| None` | Optional branch, tag, or commit for the marketplace repo |
637+
| `repo_path` | `str \| None` | Subdirectory within repo (for monorepos) |
638+
| `auto_load` | `"all" \| None` | Loading strategy (default: `None`) |
639+
513640
## Next Steps
514641

515642
- **[Skills](/sdk/guides/skill)** - Learn more about skills and triggers

0 commit comments

Comments
 (0)