Skip to content

feat(plugin): Add entry_command field to PluginManifest#2230

Open
jpshackelford wants to merge 2 commits intomainfrom
openhands/plugin-entry-command
Open

feat(plugin): Add entry_command field to PluginManifest#2230
jpshackelford wants to merge 2 commits intomainfrom
openhands/plugin-entry-command

Conversation

@jpshackelford
Copy link
Contributor

@jpshackelford jpshackelford commented Feb 26, 2026

Summary

Add an optional entry_command field to PluginManifest that allows plugin authors to specify which command should be invoked by default when launching their plugin.

Closes #2229

Changes

  1. PluginManifest (in types.py): Added entry_command field with description
  2. Plugin (in plugin.py): Added entry_slash_command property that returns the full slash command (e.g., /city-weather:now) or None if no entry_command is defined
  3. MarketplacePluginEntry (in types.py): Added entry_command field and updated to_plugin_manifest() to preserve it when converting marketplace entries to manifests

Example Usage

plugin.json:

{
  "name": "city-weather",
  "version": "1.0.0",
  "description": "Get current weather for any city",
  "entry_command": "now"
}

Consumer code:

plugin = Plugin.load(path)

if plugin.entry_slash_command:
    # Returns "/city-weather:now"
    initial_message = f"{plugin.entry_slash_command} {user_arguments}"

Design Rationale

Primary Consumer: Plugin Launch UI

The entry_command field enables the OpenHands Cloud UI for launching plugins to construct the initial message when users click "Launch" on a plugin:

  1. The launch UI indexes plugins by reading their manifests
  2. When a user clicks "Launch", it reads entry_command and constructs the initial message
  3. The launch URL includes the full slash command: /launch?message=/city-weather:now%20Tokyo
  4. This message flows through the App Server → Agent Server → SDK as initial_message

The entry_slash_command property encapsulates the /<plugin-name>:<command> format so external consumers don't need to know the slash command syntax.

Other Possible Consumers

  • Headless CLI - A CLI tool could load a plugin and automatically invoke its entry command without user interaction
  • CI/CD pipelines - Automated workflows that spin up plugin-based agents for specific tasks
  • Custom integrations - Any system that programmatically launches plugins and needs to know the default entry point

Why entry_command instead of a free-form initial message?

Constraining to a command reference (rather than arbitrary message content) provides two benefits:

  1. Enforces good practices - Prevents plugin authors from loading up the initial message with content that belongs in a command or skill file. Better to constrain initially and widen flexibility later if needed.

  2. Enables clean multi-plugin handling - When multiple plugins specify auto-start entry points, expressing behavior in terms of commands makes it easier to define invocation semantics (serial, parallel, subagents, etc.).

Relationship to existing commands

This builds on the existing commands/ directory support. Plugins already define commands; entry_command simply declares which one should be invoked by default in headless/launch scenarios.

For the full plugin launch architecture, see Plugin Launch Flow.

Claude Code Compatibility

This is an OpenHands extension to the Claude Code plugin format. The entry_command field is not part of the official Claude Code schema. However, this should not break compatibility because:

  • JSON parsers typically ignore unknown fields
  • Our PluginManifest already uses extra="allow" which preserves unknown fields
  • Claude Code should similarly ignore fields it doesn't recognize

Checklist

  • If the PR is changing/adding functionality, are there tests to reflect this?
  • If there is an example, have you run the example to make sure that it works?
  • If there are instructions on how to run the code, have you followed the instructions and made sure that it works?
  • If the feature is significant enough to require documentation, is there a PR open on the OpenHands/docs repository with the same branch name?
  • Is the github CI passing?

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:c0dee6c-python

Run

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

All tags pushed for this build

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

About Multi-Architecture Support

  • Each variant tag (e.g., c0dee6c-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., c0dee6c-python-amd64) are also available if needed

Add an optional entry_command field to PluginManifest that allows plugin
authors to specify which command should be invoked by default when
launching their plugin.

Changes:
- Add entry_command field to PluginManifest (types.py)
- Add entry_slash_command property to Plugin class (plugin.py)
- Add entry_command field to MarketplacePluginEntry and update
  to_plugin_manifest() to pass it through
- Add comprehensive tests for the new functionality

Example usage in plugin.json:
{
  "name": "city-weather",
  "version": "1.0.0",
  "entry_command": "now"
}

The Plugin.entry_slash_command property returns the full slash command
(e.g., '/city-weather:now') or None if no entry_command is defined.

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

github-actions bot commented Feb 26, 2026

API breakage checks (Griffe)

Result: Passed

Action log

@github-actions
Copy link
Contributor

github-actions bot commented Feb 26, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/plugin
   plugin.py2101692%424–425, 432–433, 450–451, 454–456, 474–476, 492–493, 511–512
   types.py200796%59, 62, 65, 121, 254, 678, 686
TOTAL19007568170% 

Copy link
Collaborator

@enyst enyst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick note. I'm a little confused why isn't this the first user message. Or a field of the first user message, e.g. an additional TextContent field.

It seems like it behaves the same as the user triggering a command-like skill themselves, by writing, e.g. "OK now start a conversation on the API to ask for the /weather in Tokio"... maybe?

@neubig
Copy link
Contributor

neubig commented Feb 27, 2026

One other design question related to this, plugins already support commands.

Would using this already-existing mechanism suffice for this use case? Or is there some other functionality that is needed that is not yet included in commands. I'm just thinking about whether we can keep the schema simple.

@jpshackelford
Copy link
Contributor Author

jpshackelford commented Feb 27, 2026

@neubig This assumes the existing support of commands. What it provides in addition is that it allows a plugin to declare which of the commands should be called when we are using the in plugin in an auto-start launch scenario. The auto-start launch scenario probably doesn't apply to CLI, except perhaps in a headless case, but it is relevant for Cloud when customers are building complex solutions that involve many skills but which are kicked-off via REST API and do not expose the conversation to end-users.

@jpshackelford
Copy link
Contributor Author

@enyst You are correct that it would be more flexible to have a plugin declare an initial message rather than an entry point command but constraining it to the command does two things:

  1. It prevents users from a bad-practice of loading up the initial message with content which is more appropriate for a command or skill file. My thinking is that it is better to constrain to good practices initially and widen flexibility if needed later.

  2. It makes it easier to deal with edge case scenarios where multiple plugins are specified all with auto-start and entry points because we can express behavior clearly in terms of how the commands are invoked--serially, parallel, all at once, subagents or not, etc.

@jpshackelford jpshackelford marked this pull request as ready for review February 27, 2026 13:35
@github-actions
Copy link
Contributor

Agent server REST API breakage checks (OpenAPI)

Result: Passed

Action log

all-hands-bot

This comment was marked as outdated.

@jpshackelford jpshackelford added the review-this This label triggers a PR review by OpenHands label Feb 27, 2026
all-hands-bot

This comment was marked as outdated.

@jpshackelford
Copy link
Contributor Author

@enyst @neubig I've added additional context to the PR description. Any remaining concerns?

@enyst
Copy link
Collaborator

enyst commented Feb 27, 2026

You are correct that it would be more flexible to have a plugin declare an initial message rather than an entry point command but constraining it to the command does two things:

  1. It prevents users from a bad-practice of loading up the initial message with content which is more appropriate for a command or skill file. My thinking is that it is better to constrain to good practices initially and widen flexibility if needed later.

No, sorry, we would not load up the initial message with content of a command / skill file.

Just the command, not its content: e.g.

  • user chooses a plugin, or it was already installed; and starts a conversation
  • user tells the agent: "tell me the /weather in Tokio"

That's all they should need, right? They don't need to insert the contents of the "/weather" skill. Instead, we already do automatically insert the contents?

Edited to add: TBH that might be a bad example. Maybe the way commands should work is that it starts the message. Like people typically expect them, e.g. "/review but be nice okay?"

@enyst
Copy link
Collaborator

enyst commented Feb 27, 2026

If I understand correctly, and please correct me if not, what this PR would do is that:

  • same happens, plugin must be loaded somehow (maybe from start) and user wants to start conversation
  • so they say "tell me the time in Tokio".

But we inserted "/weather" in the plugin source, so we execute "/weather" even if the user wanted the time?

@jpshackelford
Copy link
Contributor Author

jpshackelford commented Feb 27, 2026

Just the command, not its content: e.g.

user chooses a plugin, or it was already installed; and starts a conversation
user tells the agent: "tell me the /weather in Tokio"

That would be the flow for a normal conversation and you could use the initial message that is already part of the Cloud API for that--though I suppose we'd need to add some logic in another PR that would register the entry-point command with just the /weather and not /weather:now part. That certainly makes sense to do. I just hadn't envisioned it as part of this PR, though it certainly could be.

What I DID have in mind is to allow a plugin to launch with no initial message at the press of a button and have the agent start working.

@enyst
Copy link
Collaborator

enyst commented Feb 27, 2026

What I DID have in mind is to allow a plugin to launch with no initial message at the press of a button and have the agent start working.

Sure, please let me elaborate, I don't think I meant differently, sorry for the confusing examples. 🤔

My point is, any conversation has an initial message. I mean, the LLM request/responses array always needs an initial message

  • can't send to the LLM an empty string
  • can't send just the system prompt
  • we always send system+initial message.

So, it seems to me, at the press of a button, the application should insert "/weather" or "/weather:now" or whatever it likes, in the place of the initial message, instead of opening the conversation tab with an input field waiting for the user. It just starts.

From the user perspective, it's exactly "I pressed a button, the conversation just started, and it does weather things"

From the backend perspective, the conversation started with the initial message "/weather" or "/weather:now", so it does what is supposed to do in that case. Please tell if I misunderstand?

@jpshackelford
Copy link
Contributor Author

My point is, any conversation has an initial message

Exactly. So we are on the same page.

So in an auto-executing plugin scenario, some system will construct the initial message from the plugin metadata. I am arguing we don't need the plugin to declare an initial message if it has commands and can declare the entry-point command. That way the message is always part of a skill and not part of a new "initial message" plugin metadata which creates ambiguity for newly initiated plugin authors.

Copy link
Collaborator

@enyst enyst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the issue here is that of course some system (a client application) can send "/weather:now", as initial message, regardless that the weather plugin never specified any initial message nor entry command.

In that case, the plugin itself is not auto-executing, strictly speaking, but the entire system client+sdk does auto-execute plugins at a button press. 🤔

That said, I don't feel strongly about it. I understand you really like the concept of auto-executing plugins, and I think we may well try it. (I think Joe was calling it somewhere "autostart", the autostart equivalent of this CC plugin concept)

So I'm fine with this if @neubig is ok with trying it.


Please allow me to express here quickly what's haunting me. We had .md microagents long before CC existed, even, not to mention its Agentskills; and we had python Agentskills (python scripts for the agent to call) long before, too. "Long" in LLM-lifetimes, of which we had several since 2024 😅

I know microagents were useful to people; I'm not fully sure agentskills were. Then CC came along and combined them. They just, like, combined them. Named them Agentskills; declared them open standard. Suddenly, it's like, it clicked; a lot of users find them incredibly helpful and they pop up all over the place.

There are some caveats, of course; but this is humbling. I really think nobody knows the right UX for agents. The right things that will both feel intuitive to humans AND allow agentic workflows that feel - if not are - more suitable for real world tasks than before. Maybe this one is one of them. Idk. Worth trying! So thank you for pushing it forward.

@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

review-this This label triggers a PR review by OpenHands

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Plugins]: Add entry_command field to PluginManifest (to support Initial Prompts)

5 participants