OpenHands / OpenHands/software-agent-sdk
Spec: Support the Agent Plugins (agent-plugins.org) portable package format
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 539
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 137
Description
Background
Agent Plugins is a new open, vendor-neutral standard (v1.0.0, Working Draft) for packaging reusable components that extend AI agents into portable plugins. Its initial Technical Steering Committee includes Core Maintainers from Amazon, Cursor, Microsoft, OpenAI, and Vercel. Compatible clients today include VS Code, Cursor, GitHub Copilot, ChatGPT & Codex, and Kiro — OpenHands is not yet in that list.
The standard defines a small interoperability floor on top of two existing specs we already consume:
- Agent Skills — the
SKILL.mdformat we already load - MCP servers — already supported in OpenHands
So the heavy lift is mostly about the package/manifest layout and discovery contract, not new component technology.
The portable package
An Agent Plugin is a directory with a required manifest and optional components in fixed locations:
my-plugin/
├── plugin.json # required manifest (root-level, not nested)
├── skills/ # immediate child dirs each containing SKILL.md
│ └── summarize/
│ ├── SKILL.md
│ ├── scripts/
│ └── references/
├── mcp.json # root-level MCP config (NOT .mcp.json)
└── com.example.client/ # reverse-domain client extension namespace
└── hooks/
How this differs from our current (Claude Code-style) layout
OpenHands currently follows the Claude Code plugin structure (openhands.sdk.plugin):
| Concern | OpenHands today | Agent Plugins v1.0.0 |
|---|---|---|
| Manifest location | .claude-plugin/plugin.json or .plugin/plugin.json (nested) |
plugin.json at plugin root |
| Manifest schema | Open extra="allow", our own fields |
Closed schema; only $schema, name, version, description, author, homepage, repository, license, keywords, extensions; unknown top-level fields reported-and-ignored (non-fatal) |
| MCP config file | .mcp.json (dotfile, root) |
mcp.json (no dot, root) |
| MCP schema | { "mcpServers": {...} } (FastMCP shape) |
{ "$schema", "mcpServers": {...} } with type field (stdio / streamable-http / sse), literal headers, plugin variables ${PLUGIN_ROOT} / ${PLUGIN_DATA} |
| Skills | skills/<name>/SKILL.md ✅ (same) |
skills/<name>/SKILL.md ✅ (same) |
| Client extensions | N/A (we have commands/, agents/, hooks/) | Reverse-domain top-level dirs + extensions.<namespace> manifest data |
The skills discovery rule is already identical, which is the good news.
Scope of this issue
This issue is a spec / design task, and the implementation should be split into follow-up issues. The goal is to make OpenHands a conformant Agent Plugins client.
Per the conformance checklist, a conformant client:
- Establishes the filesystem-resolved plugin root.
- Locates and validates root
plugin.jsonusing the locally supported schema selected by$schema(does not retrieve the schema over the network). - Rejects on fatal manifest violations; reports-and-ignores unknown top-level fields and a non-object
extensions. - Discovers each supported component type from its fixed location (
skills/,mcp.json). - Applies the narrow failure boundary per component type / entry (skip one skill, disable one MCP server, etc. — never nuke the whole plugin for a single bad component).
- Applies implemented client-extension namespaces and ignores all others.
A conformant client must support at least one of skills or MCP servers (we want both).
What needs to be specced
1. Dual-format loading & backward compatibility
- Decide whether to support both the Claude Code layout (
.claude-plugin/plugin.json+.mcp.json) and the Agent Plugins layout (plugin.json+mcp.json) during a transition window, or to auto-detect by probing for a rootplugin.jsonfirst. - Per channel discussion, we likely need to maintain backward compatibility with Claude Code plugins for a while. The spec should define the detection/precedence rules (e.g., root
plugin.jsonwins; fall back to.claude-plugin//.plugin/). - Map
PluginManifestto the closed Agent Plugins schema while preserving our extra fields (entry_command, commands, agents) under a client extension namespace (e.g.,io.openhands/dev.openhands) per §8.
2. Manifest validation
- Validate
plugin.jsonagainst the plugin schema locally (vendored, never fetched at load time). - Enforce name constraints (1–64 chars, lowercase ASCII letters/digits/hyphens/periods, no
--or.., alphanumeric start/end). - Implement the non-fatal vs. fatal violation split:
- Non-fatal: unknown top-level field (report + ignore), non-object
extensions(report + ignore). - Fatal: any other schema violation → reject the whole plugin, do not discover/execute components.
- Non-fatal: unknown top-level field (report + ignore), non-object
- Handle
$schema: select locally supported validation/interpretation rules; reject if the declared Agent Plugins version is unsupported.
3. Package boundary & path containment (security)
- All package-resolved paths must remain within the filesystem-resolved plugin root after resolving symlinks/junctions/reparse points.
- Plugin-relative paths must begin with
./, resolve against the plugin root, and stay inside it. - Apply the narrowest failure boundary (§4.1): reject plugin if
plugin.jsonescapes; disable a component type if its fixed location escapes; skip one skill if itsSKILL.mdescapes; skip one MCP entry ifcommand/cwdescapes; deny other escaping paths. - Note: this governs package-supplied files, not subprocess sandboxing — but it must compose cleanly with our existing sandbox/runtime model.
4. MCP config (mcp.json) mapping
- Read
mcp.json(no dot) at the plugin root in addition to.mcp.json. - Support transports:
stdio(required-ish),streamable-http(required-ish; we should support both),sse(optional). - Implement plugin variables
${PLUGIN_ROOT}and${PLUGIN_DATA}:PLUGIN_ROOT= absolute, filesystem-resolved plugin root.PLUGIN_DATA= dedicated writable data dir, persisted across plugin updates.- Textual, single-pass, non-recursive expansion in
args,envvalues, andcwdonly — not incommand, env keys, remote URLs, or HTTP headers. - Client-controlled
PLUGIN_ROOT/PLUGIN_DATAset last, overlaying configuredenv; plugins cannot override them.
- Remote: validate absolute HTTP(S) URLs (no user-info/fragments); non-loopback must be HTTPS; headers are literal visible package data, no credentials/secrets.
- Failure isolation: invalid top-level
mcp.jsondisables MCP for the plugin; an invalid/unavailable individual server disables only that entry; continue loading siblings + other component types. - Map to our existing
MCPServer/ FastMCP config without losing the Agent Plugins transport semantics.
5. Skills discovery (already largely aligned)
- Discover immediate child directories of
skills/whoseSKILL.mdresolves to a regular file. - Do not recursively search deeper descendants for additional skills.
- Validate
SKILL.mdagainst the Agent Skills spec (name must match parent dir, description ≤1024 chars, etc.). - Missing
skills/is not an error; wrong filesystem kind disables the skills component type only.
6. Client extensions
- Recognize our own reverse-domain namespace(s) (e.g.,
io.openhands) underextensionsand in top-level extension directories. - Map our Claude-Code-origin concepts that aren't in the portable core —
commands/,agents/,hooks/hooks.json,entry_command— into our namespace. - Ignore namespaces we don't implement without validating their contents (§8).
7. Discovery, installation & UX (client-owned, out of portable spec)
- The standard deliberately leaves these to each client. Spec how OpenHands will:
- Discover plugins (our existing
load_user_plugins/load_project_plugins/ installed dir + marketplace). - Install/enable/update/disable/uninstall with the persisted
PLUGIN_DATAguarantee. - Surface permissions/trust prompts and sandboxing policy (compose with our confirmation policy + security analyzer).
- Present skills to users/models.
- Discover plugins (our existing
Proposed deliverables
- Foundation: extract plugin loading into a
PluginFormatstrategy (base class +ClaudeCodePluginFormat,detect_format()) so a second format can be added without touching the merge/apply path (OpenHands/software-agent-sdk#4420). - ADR / design doc covering detection precedence, manifest mapping, path containment, MCP variable expansion, failure boundaries, and the backward-compat window for Claude Code plugins.
- Vendored JSON schemas (
plugin.schema.json,mcp.schema.json) + local$schemaselection logic. - Implementation issue: Agent Plugins manifest loader (root
plugin.json, closed schema, fatal/non-fatal split). (OpenHands/software-agent-sdk#4450) - Implementation issue:
mcp.jsonloader +${PLUGIN_ROOT}/${PLUGIN_DATA}expansion +PLUGIN_DATApersistence. (OpenHands/software-agent-sdk#4451) - Implementation issue: client extension namespace (
io.openhands) mapping for commands/agents/hooks/entry_command. (OpenHands/software-agent-sdk#4452) - Implementation issue: path-containment enforcement + narrow failure boundaries. (OpenHands/software-agent-sdk#4453)
- Tests against the official example plugin and the conformance checklist. (OpenHands/software-agent-sdk#5156)
- Reach out to the Agent Plugins project to get OpenHands listed on the Compatible Clients page once conformance is met. (OpenHands/software-agent-sdk#5159)
Open questions
- Do we support
sse(deprecated) in addition tostdio+streamable-http? - Exact reverse-domain namespace to claim (
io.openhandsvsdev.openhandsvscom.openhands)? - How long is the Claude Code backward-compat window, and do we eventually deprecate
.claude-plugin//.mcp.json? - Should Agent Plugins be loaded ambiently (like
.skills/project plugins) or require explicit opt-in given the path-containment + subprocess implications? - Where does
PLUGIN_DATAlive on disk and what owns its lifecycle (creation, persistence across updates, cleanup on uninstall)? Analogous to~/.openhands/plugins/installed/, but nothing today defines this. Blocks OpenHands/software-agent-sdk#4451. MCPServer.headersis typeddict[str, SecretStr](mcp/config.py:512), but Agent Plugins headers must be literal, visible, non-secret package data. Needs a load path that doesn't misrepresent plugin-declared headers as secrets. Blocks OpenHands/software-agent-sdk#4451.
References
- Standard: https://agent-plugins.org
- Specification: https://agent-plugins.org/specification
- Plugin manifest: https://agent-plugins.org/plugin-authors/manifest
- MCP servers: https://agent-plugins.org/plugin-authors/mcp-servers
- Skills: https://agent-plugins.org/plugin-authors/skills
- Client extensions: https://agent-plugins.org/plugin-authors/client-extensions
- Implement a client: https://agent-plugins.org/client-implementers
- Loading and discovery: https://agent-plugins.org/client-implementers/loading-and-discovery
- Conformance checklist: https://agent-plugins.org/client-implementers/client-conformance-checklist
- Schemas: https://agent-plugins.org/schemas
- Spec repo: https://github.com/agentplugins/agent-plugins-spec
- Example plugin: https://github.com/agentplugins/agent-plugins-example
- Agent Skills spec: https://agentskills.io/specification
- MCP spec: https://modelcontextprotocol.io/specification
This issue was created by an AI agent (OpenHands) on behalf of @VascoSch92.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the PluginFormat strategy and detect_format() foundation, then inspect mcp/config.py and the implementation issues listed in the deliverables. Draft an ADR covering detection precedence, manifest mapping, path containment, MCP variable expansion, failure boundaries, and Claude Code compatibility. Done means the ADR resolves these design questions and records decisions for the follow-up implementation work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design, security, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100