View source on GitHub
plugins — does the work.
This example is self-contained: it ships its own plugin in
dad-joke/ and loads it straight from this repo on GitHub. No
external marketplace, no API keys beyond your OpenHands key.
A plugin is a small git-hosted bundle of slash commands, skills, hooks, and/or
MCP servers (Claude Code “plugin marketplace” format).
dad-joke ships one of
each kind we need: a /dad-joke:about slash command that tells a dad joke
about an animal, and a keyword-triggered skill that asks for your favorite
animal first.launch-plugin-badge, which builds on this
one.
How It Works
POST /api/v1/app-conversations is asynchronous. It returns a start task,
not a finished conversation. The script polls
GET /api/v1/app-conversations/start-tasks?ids=<task_id> until the task yields
an app_conversation_id, then prints the conversation URL.
(Omitting sandbox_id from the request lets the server provision a fresh
sandbox. To attach to a sandbox you prepared yourself, pass its id — see
clone-and-attach.)
The One Field That Matters
Two Ways to Drive It
Theinitial_message decides what happens once the plugin is loaded:
-
Run a skill immediately via an entry command. Send the plugin’s slash
command as the first message. The SDK registers
/dad-joke:aboutas a keyword trigger, so this tells a joke right away: -
Load the plugin, then prompt normally. Send a natural-language message;
the plugin’s skills are available for the agent to use when relevant. The
bundled skill fires on “dad joke”, asks for your favorite animal, then
delivers:
Run It
Options
Loading a Private Plugin
The bundled plugin is public, butsource also accepts a full Git URL, and a
${VAR} placeholder in the source (or ref) is expanded against the
conversation’s secrets just before the repo is cloned — so you can fetch a
private plugin without hard-coding a token.
Version requirement. Secret expansion in the plugin source landed in
software-agent-sdk#3758
and ships in the agent-server runtime
v1.29.0
(released 2026-06-18). A conversation only gets the fix when its sandbox runs
agent-server:1.29.0-python or newer.In OpenHands Enterprise (tracked by the Replicated openhands chart
version), 0.7.65 is the first release to bundle that image — the VM-based
release being cut for it. Not yet on the Stable channel or OpenHands Cloud
(app.all-hands.dev) as of this writing.On older builds the ${VAR} reaches git clone literally and a private
source fails at conversation start; a public source is unaffected.source:
This script demonstrates scenario 3 with
--secret (which adds a secrets
field to the request):
--secret at all: if you’ve connected GitHub/GitLab/
Bitbucket to OpenHands, just reference e.g. ${GITHUB_TOKEN} and the managed
token is injected for you.
Good to know:
- Braced
${VAR}only — a literal$in a token is never mangled. - Secrets only, not host env — host environment variables are never folded into the URL (that would be a credential-exfiltration vector).
- Missing secret → left untouched — the placeholder stays verbatim, so you get a clear clone failure rather than a surprising default.
- Stays redacted — the persisted plugin spec keeps the
${VAR}placeholder, not the secret value. - HTTPS, not
ssh://— the credential travels inside the URL; SSH authenticates out-of-band (a key), so there is no placeholder to expand.
The Bundled Plugin
Related
launch-plugin-badge
turn this into a no-code launch link, HTML button, or README badge.
Plugin Marketplace
the plugin directory: a browseable catalog of plugins (served at /plugins) loaded from a marketplace source repo.
Plugins overview
what plugins are and the format they follow.
Plugin Launch Flow design doc
the full marketplace → frontend → app server → SDK journey.
software-agent-sdk#3758
(SDK v1.29.0) - the secret expansion behind “Loading a private plugin”.

