Skip to content

Conversation

@jpshackelford
Copy link
Contributor

@jpshackelford jpshackelford commented Dec 19, 2025

Description

This PR builds on the proof-of-concept from #1399 to achieve full compatibility with Claude Code Plugins and AgentSpec standards. The key insight is that marketplace monorepos and Python packages can share the same plugin structure, enabling plugins to be distributed both ways without modification.


implementation in-progress: this PR description guides on-going work on the branch


Vision: Unified Plugin Structure

The Dual-Distribution Model

A plugin repository can serve as both:

  1. Bare Git Marketplace (Support for bare git repository plugin marketplaces #1453) - Direct installation from git repos
  2. Python Package Source (This PR) - Build and publish to PyPI

The .plugin/ directory structure remains identical in both cases, with Python packaging (pyproject.toml, setup.py) as an optional layer for formal distribution.

Example Marketplace Monorepo Structure

my-marketplace/
├── .plugin/
│   └── marketplace.json              # Marketplace descriptor
├── plugins/
│   ├── plugin-a/
│   │   ├── .plugin/
│   │   │   ├── plugin.json           # Plugin manifest (NOT manifest.json)
│   │   │   └── skills/
│   │   │       └── SKILL.md          # AgentSpec-compatible format
│   │   ├── pyproject.toml            # Optional: for Python packaging
│   │   ├── setup.py                  # Optional: for Python packaging
│   │   └── plugin_a/                 # Optional: Python module
│   │       └── __init__.py
│   └── plugin-b/
│       ├── .plugin/
│       │   ├── plugin.json
│       │   └── skills/
│       ├── pyproject.toml
│       └── plugin_b/

Key Principle: The .plugin/ structure is the same whether used bare or packaged.


Changes Required for Full Compatibility

1. File Naming & Structure ❌ NOT YET IMPLEMENTED

Current State (PR #1399):

  • Uses manifest.json in package root
  • Claims "Claude Desktop Extensions-aligned" but Claude Code doesn't use manifest.json
  • No .plugin/ or .claude-plugin/ directory structure

Required Changes:

  • Rename manifest.jsonplugin.json
  • Move to .plugin/plugin.json (vendor-neutral, per Plugin 1.0 Definition #1440)
  • Support .claude-plugin/plugin.json for backward compatibility
  • Update package_loader.py to check both locations
  • Precedence: .plugin/ > .claude-plugin/

Files to Modify:

  • openhands-sdk/openhands/sdk/context/skills/package_loader.py - Update _load_descriptor()
  • tests/sdk/context/skill/test_package_loader.py - Update all test fixtures
  • docs/claude-code-alignment.md - Correct documentation

2. Schema Alignment ❌ NOT YET IMPLEMENTED

Current State:

{
  "name": "simple-code-review",
  "version": "1.0.0",
  "skills": [...]
}

Claude Code plugin.json Schema:

{
  "name": "plugin-name",
  "version": "1.0.0",
  "displayName": "Human Readable Name",
  "description": "Plugin description",
  "author": {
    "name": "Author Name",
    "email": "author@example.com"
  },
  "keywords": ["tag1", "tag2"],
  "license": "MIT",
  "commands": ["./commands/"],
  "agents": ["./agents/agent.md"],
  "hooks": {...},
  "mcpServers": {...}
}

Required Changes:

  • Compare current schema with Claude Code plugin.json
  • Add missing optional fields
  • Ensure required fields are validated
  • Support component paths (commands, agents, hooks, mcpServers)

Note: Current implementation has skills array in manifest.json, but Claude Code uses directory-based discovery with optional overrides in plugin.json.


3. AgentSpec SKILL.md Compatibility ❌ NOT YET IMPLEMENTED

Current State (from package_loader.py lines 183-199):

  • Parses frontmatter with frontmatter library
  • Looks for triggers field in metadata
  • Creates KeywordTrigger if present
  • Uses simple markdown content

AgentSpec SKILL.md Requirements (from #1452):

Required Frontmatter Fields:

  • name - Skill identifier (kebab-case)
  • description - 1-1024 characters

Optional Frontmatter Fields:

  • license - SPDX identifier
  • compatibility - Compatibility notes
  • metadata - Key-value map (author, version, etc.)
  • allowed-tools - Tool restrictions (experimental)

OpenHands Extensions (Not in AgentSpec):

  • triggers - Keyword list for activation
  • inputs - For task skills with variable substitution
  • mcp_tools - MCP server configuration

Required Changes:

  • Validate required AgentSpec fields (name, description)
  • Store optional AgentSpec fields in Skill metadata
  • Prepend description to Skill.content (AgentSpec pattern)
  • Support kebab-case naming validation
  • Add validation for description length (1-1024 chars)
  • Maintain backward compatibility with OpenHands extensions

Files to Modify:

  • openhands-sdk/openhands/sdk/context/skills/skill.py - Add AgentSpec validation
  • openhands-sdk/openhands/sdk/context/skills/package_loader.py - Use Skill.load_agentspec() method
  • Add new validation in Skill class for AgentSpec format

4. Progressive Disclosure Support ❌ NOT YET IMPLEMENTED

AgentSpec Three-Tier Model:

  1. Discovery (~100 tokens): name, description, location in system prompt
  2. Activation (<5000 tokens): Full SKILL.md content when LLM decides
  3. Resources (variable): scripts/, references/, assets/ loaded on demand

OpenHands Current Model:

  • Repo skills: Always loaded (full content in context)
  • Keyword skills: Loaded on keyword match
  • Task skills: Loaded on match with variable substitution

Required Changes:

  • Support SKILL.md with resource directories (scripts/, references/, assets/)
  • Implement lazy loading for resource directories
  • Add filesystem-based resource access for agents
  • Document progressive disclosure patterns

Note: This is a Phase 2 enhancement, not required for initial compatibility.


5. Directory Structure Support ⚠️ PARTIAL

Current State:

  • Single file per skill (flat structure)
  • All content in one .md file

AgentSpec Structure:

plugin-name/
├── SKILL.md              # Main skill file
├── scripts/              # Executable scripts
│   ├── extract.py
│   └── merge.sh
├── references/           # Documentation
│   ├── REFERENCE.md
│   └── API.md
└── assets/               # Supporting files
    └── template.json

Required Changes:

  • Support skill directories with SKILL.md
  • Optional: Load scripts/, references/, assets/ on demand
  • Maintain compatibility with single-file format
  • Update Skill model to track resource paths

Files to Consider:

  • Skill.load() method to detect directory vs file
  • Resource path tracking in Skill model
  • Lazy loading mechanism

6. Marketplace Integration ❌ NOT YET IMPLEMENTED

Goal: Enable the same .plugin/ structure to work in:

  1. Bare git marketplace repos (direct clone/pull)
  2. Python packages (pip install)

Required Changes:

  • Support loading from filesystem (bare repos)
  • Support loading from installed packages (entry points)
  • Unified discovery mechanism
  • Same plugin.json format in both cases

New Functionality Needed:


Testing Current State

What Works (PR #1399):

✅ Entry point discovery via openhands.skill_packages
✅ JSON descriptor loading
✅ Skill file content loading from packages
✅ Frontmatter parsing with triggers
✅ KeywordTrigger creation
✅ Basic package loading tests

What Doesn't Work:

.plugin/ directory structure
plugin.json naming (uses manifest.json)
❌ AgentSpec SKILL.md validation
❌ Resource directory support (scripts/, references/, assets/)
❌ Progressive disclosure
❌ Filesystem-based marketplace loading
❌ Full schema compatibility with Claude Code


Implementation Priority

Phase 1: Core Compatibility (This PR)

  1. File naming - manifest.json → plugin.json
  2. Directory structure - Move to .plugin/plugin.json
  3. AgentSpec validation - Required frontmatter fields
  4. Schema alignment - Match Claude Code plugin.json

Phase 2: Advanced Features (Future PRs)

  1. Resource directories - scripts/, references/, assets/
  2. Progressive disclosure - Lazy loading
  3. Marketplace integration - Filesystem loading (Support for bare git repository plugin marketplaces #1453)
  4. Enhanced discovery - Unified marketplace + package loading

Related Issues


Questions to Resolve

  1. Skill discovery: Should plugin.json list skills explicitly, or discover SKILL.md files in .plugin/skills/?
  2. Backward compatibility: How to handle existing manifest.json packages during transition?
  3. Naming precedence: If both .plugin/plugin.json and .claude-plugin/plugin.json exist, which wins?
  4. Resource loading: Should scripts/ be automatically available, or require explicit activation?
  5. Package vs Marketplace: Should package_loader.py handle both, or keep separate loaders?

Success Criteria

  • Uses .plugin/plugin.json (not manifest.json)
  • Supports .claude-plugin/plugin.json for compatibility
  • Validates AgentSpec required fields (name, description)
  • Stores AgentSpec optional fields in metadata
  • Schema matches Claude Code plugin.json format
  • Tests pass with new structure
  • Documentation updated to reflect Claude Code alignment
  • Maintains backward compatibility with PR POC: support for Python package-based plugin loading #1399 POC (deprecation path)
  • Works as both bare repo plugin and Python package

Migration Path from PR #1399

For existing packages using manifest.json:

  1. Rename: manifest.jsonplugin.json
  2. Move: package_root/plugin.jsonpackage_root/.plugin/plugin.json
  3. Update pyproject.toml:
    [tool.setuptools.package-data]
    my_package = [".plugin/plugin.json", ".plugin/skills/*.md"]
  4. Add SKILL.md validation: Ensure frontmatter has name and description

Next Steps

  1. ✅ Create branch from PR POC: support for Python package-based plugin loading #1399
  2. ✅ Create this PR with detailed analysis
  3. 🔄 Implement Phase 1 changes (file naming, structure, validation)
  4. 🔄 Update tests
  5. 🔄 Update documentation
  6. ⏳ Review and merge
  7. ⏳ Implement Phase 2 features in follow-up PRs

Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.12-nodejs22 Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:98b2e5e-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-98b2e5e-python \
  ghcr.io/openhands/agent-server:98b2e5e-python

All tags pushed for this build

ghcr.io/openhands/agent-server:98b2e5e-golang-amd64
ghcr.io/openhands/agent-server:98b2e5e-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:98b2e5e-golang-arm64
ghcr.io/openhands/agent-server:98b2e5e-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:98b2e5e-java-amd64
ghcr.io/openhands/agent-server:98b2e5e-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:98b2e5e-java-arm64
ghcr.io/openhands/agent-server:98b2e5e-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:98b2e5e-python-amd64
ghcr.io/openhands/agent-server:98b2e5e-nikolaik_s_python-nodejs_tag_python3.12-nodejs22-amd64
ghcr.io/openhands/agent-server:98b2e5e-python-arm64
ghcr.io/openhands/agent-server:98b2e5e-nikolaik_s_python-nodejs_tag_python3.12-nodejs22-arm64
ghcr.io/openhands/agent-server:98b2e5e-golang
ghcr.io/openhands/agent-server:98b2e5e-java
ghcr.io/openhands/agent-server:98b2e5e-python

About Multi-Architecture Support

  • Each variant tag (e.g., 98b2e5e-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 98b2e5e-python-amd64) are also available if needed

openhands-agent and others added 8 commits December 10, 2025 17:20
This commit adds the ability to load skills from installed OpenHands skill
packages, enabling reusable skill distribution across projects.

Features:
- Package discovery using Python entry points
- Configuration via .openhands/packages.yaml
- Functions to list, load, and inspect skill packages
- Integration with existing skill loading infrastructure
- Comprehensive tests and documentation
- Example demonstrating all features

New files:
- openhands-sdk/openhands/sdk/context/skills/package_loader.py
- tests/sdk/context/skill/test_package_loader.py
- examples/01_standalone_sdk/04_use_skill_packages.py
- docs/skill-packages.md
- .openhands/packages.yaml (template)

Related to: https://github.com/OpenHands/package-poc
Update the OpenHands SDK package loader to support both manifest.json
(Claude Desktop Extensions-aligned) and skill-package.yaml (legacy) formats.

Key Changes:
- Add _load_descriptor() helper that tries JSON first, falls back to YAML
- Update list_skill_packages() to use new descriptor loader
- Update get_skill_package() to use new descriptor loader
- Update load_skills_from_package() to handle both descriptor formats
- Add comprehensive documentation on Claude Code alignment

Features:
- Full backwards compatibility with existing YAML packages
- Cross-platform package support (OpenHands + Claude Desktop)
- Automatic format detection and handling
- No changes required to existing code using package_loader

Documentation:
- New claude-code-alignment.md guide
- Usage examples for both formats
- Migration strategies for existing packages
- Integration guidelines

This change aligns with OpenHands/package-poc PR #17 which implements
the manifest.json format in the ohp package management tool.

Related to OpenHands/package-poc#16

Co-authored-by: openhands <openhands@all-hands.dev>
- Removed YAML fallback logic from _load_descriptor()
- Updated docstrings to reflect JSON-only support
- Simplified load_skills_from_package() to handle only flat JSON structure
- Removed dual-format handling code

This is a clean break for Scenario 1 - packages must use manifest.json.

Co-authored-by: openhands <openhands@all-hands.dev>
- Removed backwards compatibility mentions
- Updated usage examples to show flat JSON structure
- Clarified manifest.json is required for Scenario 1
- Updated migration section
- Simplified benefits section

Co-authored-by: openhands <openhands@all-hands.dev>
- Convert test fixtures from YAML to JSON format
- Update descriptor access to use flat structure (no metadata/spec nesting)
- Fix mock setup for manifest.json instead of skill-package.yaml
- Keep yaml import for packages.yaml configuration support
- Fix line length issues for ruff compliance
- All 13 tests passing

Co-authored-by: openhands <openhands@all-hands.dev>
…ll_packages.py

- Updated example code to access flat manifest.json structure instead of nested metadata/spec
- Fixed descriptor access patterns: descriptor['displayName'] instead of descriptor['metadata']['displayName']
- Renamed from 04_use_skill_packages.py to 31_use_skill_packages.py to match PR #1378
- Updated documentation reference to point to new filename
- Added defensive handling for author and repository fields that can be string or object

Co-authored-by: openhands <openhands@all-hands.dev>
Add isinstance check before passing triggers to KeywordTrigger to handle
untyped frontmatter metadata. This ensures type safety and prevents
passing non-list objects.

Co-authored-by: openhands <openhands@all-hands.dev>
Automated formatting by yamlfmt to add standard YAML document delimiter.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/context/skills
   package_loader.py11710113%38–39, 60, 62, 64–65, 67, 70, 72, 75, 77, 79, 96, 98–100, 102, 105, 107, 112–114, 116, 142–144, 146–147, 149–150, 154, 156, 158–160, 162–163, 166, 168, 170, 174, 179, 181, 183–185, 188–190, 192–194, 197, 202–203, 205, 207, 209–210, 213, 215, 220, 239–240, 242–250, 252, 270, 272–273, 275–278, 280–281, 283, 285–287, 289–291, 293–295, 297–299, 315, 317–319, 321
TOTAL12991589754% 

@openhands-ai
Copy link

openhands-ai bot commented Dec 19, 2025

Looks like there are a few issues preventing this PR from being merged!

  • GitHub Actions are failing:
    • [Optional] Docs example

If you'd like me to help, just leave a comment, like

@OpenHands please fix the failing actions on PR #1457 at branch `plugin-marketplace-compatibility`

Feel free to include any additional details that might help me get this PR into a better state.

You can manage your notification settings

@all-hands-bot
Copy link
Collaborator

[Automatic Post]: It has been a while since there was any activity on this PR. @jpshackelford, are you still working on it? If so, please go ahead, if not then please request review, close it, or request that someone else follow up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants