# OpenHands Extensions — Agent Notes

This repository (`OpenHands/extensions`) is the **public extensions registry** for OpenHands.
It contains **shareable skills and plugins** that can be loaded by OpenHands (CLI/GUI/Cloud) and by client code using the **Software Agent SDK**.


## Cross-Repository Boundaries

This repository owns the public registry of reusable OpenHands skills, plugins, Agent Canvas Apps, automations, and integrations. These extensions are consumed by OpenHands applications and SDK-based clients.

Related repositories have distinct responsibilities:

- [`OpenHands/software-agent-sdk`](https://github.com/OpenHands/software-agent-sdk) owns the Python SDK, Agent Server, agent/tool behavior, conversations, workspaces, events, canonical API, and browser-compatible TypeScript Agent Server client under `clients/typescript/`.
- [`OpenHands/OpenHands`](https://github.com/OpenHands/OpenHands) owns Agent Canvas UI and local-stack orchestration.
- [`OpenHands/automation`](https://github.com/OpenHands/automation) owns scheduling, webhooks, run history, dispatch, and sandbox lifecycle orchestration.

Put reusable skills, plugins, automations, and integrations here; put backend execution behavior and typed API access in the SDK, application UI in Agent Canvas, and scheduling/dispatch lifecycle code in `automation`. If a PR is opened in the wrong repository, explicitly recommend closing and moving it to the owning repository. PRs must follow this repository's applicable contribution and code-review guidance.

## Review-Facing Extension Checklist

Before opening a PR:

- Keep a content change focused on one skill, plugin, automation bundle, or
  integration. Put shared product runtime behavior in the repository that owns
  it instead of copying machinery into extension content.
- Treat every command, import, environment variable, endpoint, setup step, and
  package name as an executable contract. Test the documented entrypoint with
  released packages in each local or cloud environment the extension claims to
  support; do not rely on developer-only variables or private paths.
- For event-, comment-, or message-triggered work, define who may trigger it,
  whose identity executes it, what scope it may mutate, and which minimal secrets
  it receives. A trigger phrase is not authorization.
- Edit the hand-authored source once and regenerate derived catalogs, commands,
  indexes, and package assets with the existing sync/build command. Document the
  true generation direction and do not introduce a parallel source of truth.
- Verify copy-paste commands and factual documentation claims against the actual
  tool or authoritative source. Extension instructions are executed by agents,
  so incorrect setup or data-flow documentation is a functional defect.

## What this repo contains

- `skills/` — a catalog of skills, **one directory per skill**.
  - `skills/<skill-name>/SKILL.md` — the skill definition (AgentSkills-style progressive disclosure)
  - `skills/<skill-name>/README.md` — optional extra docs/examples for humans

- `plugins/` — a catalog of plugins with executable code components.
  - `plugins/<plugin-name>/.plugin/plugin.json` — the plugin manifest (the canonical location; `.claude-plugin` and `.codex-plugin` are vendor symlinks to `.plugin`)
  - `plugins/<plugin-name>/SKILL.md` — the plugin definition (optional; present only on plugins that are also loadable as a skill)
  - `plugins/<plugin-name>/hooks/` — lifecycle hooks (optional)
  - `plugins/<plugin-name>/scripts/` — utility scripts (optional)

- `apps/` — a catalog of Agent Canvas Apps (beta), **one directory per app**.
  - `apps/<app-name>/canvas-extension.json` — the App manifest (Canvas Extensions API v1)
  - `apps/<app-name>/extension.js` — the browser ESM entrypoint exporting `activate(host)`
  - `apps/catalog.json` — the registry catalog, exposed as `APPS_CATALOG` from `@openhands/extensions/apps`

Most content is Markdown skill definitions and plugin configurations; the executable parts are plugin `scripts/` and `hooks/` plus the browser ESM App packages under `apps/`, each of which may ship its own `scripts/` and build sub-directories.

## How client code uses this repo

### OpenHands Applications

OpenHands can load skills from a project directory (repo-level) and from user-level locations.
This repository is the **global/public** registry referenced by the docs.

### Skill loading models to know

OpenHands supports two complementary mechanisms:

1. **Always-on context (repository rules)**
   - Loaded at conversation start.
   - Prefer a root `AGENTS.md` (and optionally model-specific variants like `CLAUDE.md` / `GEMINI.md`).

2. **AgentSkills (progressive disclosure), with an OpenHands extension for keyword triggers**
   - Each skill lives in its own directory with a `SKILL.md` entry point.
   - The agent is shown a catalog (name/description/location) and decides when to open/read the full content.
   - **OpenHands extension**: the `SKILL.md` may include optional `triggers:` frontmatter to enable keyword-based activation.

This registry primarily provides (2). Client repositories typically add (1) for repo-specific, always-on instructions.

### Software Agent SDK

SDK consumers typically load skills either:

- as **always-loaded context** (e.g., `AGENTS.md`), and/or
- as **trigger-loaded keyword skills**, and/or
- as **progressive-disclosure AgentSkills** by discovering `SKILL.md` files under a directory.

See: https://docs.openhands.dev/sdk/guides/skill

## AgentSkills / Skill authoring rules (follow these)

OpenHands uses an **extended AgentSkills standard**:

- **Compatible with the AgentSkills specification** (https://agentskills.io/specification)
- **Extended with optional `triggers:` frontmatter** for keyword-based activation

When editing or adding skills in this repo, follow these rules (and add new skills to `marketplaces/openhands-extensions.json`):

1. **One skill per directory**
   - Create `skills/<skill-name>/SKILL.md`.
   - Keep the directory name stable; it is used as the skill identifier/location.

2. **SKILL.md should be progressive disclosure**
   - Put a concise summary/description first.
   - Include only the information needed for an agent to decide whether to open/read the skill.
   - If the skill needs large references, keep them in the same directory (e.g., `references/`) and point to them.

3. **Be specific and operational**
   - Prefer checklists, steps, and concrete examples.
   - Avoid vague guidance like “be careful” without actionable criteria.

4. **Avoid repo-local assumptions**
   - Skills here are **public and reusable**.
   - Don’t reference private paths, secrets, or company-specific URLs.

5. **Do not include secrets or sensitive data**
   - Never commit API keys, tokens, credentials, private endpoints, or internal customer data.

6. **Prefer minimal, composable skills**
   - Keep a skill focused on a single domain/task.
   - If it grows large, split it into multiple skills.

7. **Compatibility notes**
   - The legacy `.openhands/microagents/` location may still exist in user repos, but this registry uses the current skills layout.

## Repository conventions

- **Punctuation style**: Use plain hyphens (`-`) instead of em dashes (`—` / `\u2014`) in skill descriptions, SKILL.md content, and marketplace JSON entries.
- **`defaultEnabled` on marketplace skill entries**: a skill entry may carry `"defaultEnabled": true`, which means the skill is enabled for every **new** workspace. Omit the field for everything else - absence already means off, so `"defaultEnabled": false` is not written. Keep the set small and provider-agnostic; anything language-, vendor- or workflow-specific should start off and be opted into from the catalog UI. `npm run build:skills` joins the flag into `skills/index.js` and exports `DEFAULT_ENABLED_SKILL_NAMES` for hosts to seed a workspace from. Seeding is all it does: the contract hosts implement is that a workspace which already saved a selection keeps it, so adding the flag to a skill later will not retroactively enable it for existing users.
- Keep formatting consistent across skills.
- If you change a skill’s behavior or scope, update its `README.md` (if present) accordingly.
- If you change top-level documentation, ensure links still resolve.
- `integrations/catalog/*.json` is the single hand-authored source of truth consumed by `@openhands/extensions`; adding or editing an integration should require changing exactly one JSON file in that directory. Do not reintroduce `integrations/integration-catalog.json`, separate provider files, per-language catalog duplicates, or provider-specific runtime code. Run `npm run build:integrations` after catalog edits to regenerate `integrations/catalog-index.js`, which statically imports each individual JSON file for the JS package. The Python package includes the same individual JSON files via wheel data and reads them directly. Agent-canvas and integrations-hub import this package directly, so integration marketplace fixes belong here rather than in app-local constants. When upstream MCP projects move repos, verify both `docsUrl` and the connection option (`transport`, `command`/`args`, or URL), not just links. JS exposes `listIntegrationCatalog({ mcp, oauth })` / `getIntegrationCatalogEntry`; Python mirrors with `list_integration_catalog_models(mcp=, oauth=)` / `get_integration_catalog_entry_model`, which return validated Pydantic models (the raw-dict accessors were deprecated in 0.10.0 and removed in 0.12.0). The legacy provider-catalog compatibility layer and `managedConnectorMigration` / `legacyScopeBundles` / `canonicalServerUrl` / `errorHints` mechanisms were removed intentionally; providers declare only standard OAuth config as integration data.
- `automations/catalog/<id>/` follows the same rule, as a directory per automation so an automation can ship the scripts it uploads to the automations service alongside its metadata. `manifest.json` is the single hand-authored file, carrying both the card metadata and an optional nested `setup` block (the extension-owned configuration experience). Every entry is validated against `automations/catalog.schema.json`. Run `npm run build:automations` after catalog edits to regenerate `automations/catalog-index.js`. JS exposes `listAutomationCatalog()` / `getAutomationCatalogEntry(id)` alongside `AUTOMATION_CATALOG`. The governing rule is that a manifest states only what varies between automations, and states it once; anything derivable is absent and the host generates it. Derived, so never written in a manifest: the command that launches the card (read from the `triggers:` frontmatter of the skill named by `skill`, which defaults to `id` and is stated only where the two differ - editing an automation must never require editing a skill), the setup route (`/automations/new/<id>`), the trigger variants a deployment can offer (the keys of `setup.form.triggers`; multiple keys mean any supported selected variant can run), schedule limits, timezone lists, event choices, and model profiles (the `cron`, `timezone`, `event-source`, `event-type`, and `llm-profile` field types), the preflight call, the create request's `name`/`repos`/`model`/`timeout`/`trigger` (rebuilt from same-named form fields when present, the repo-picker field, and the selected key and fields under `setup.form.triggers`, where a field is named after the property it fills), the mapping from a rejected payload path back to the input at fault, the review screen, the create endpoint, the post-success navigation, and the analytics stages. `requires.integrations` is keyed by integration id and lives on the entry, not in `setup`, because a card lists its integrations whether or not it ships a setup flow; there is no key anywhere for a credential, because the credential comes with the connection. `setup.form` splits inputs into `triggers` (keyed by trigger kind - what decides when the automation runs) and `args` (everything else), both keyed by field name. `setup` declares a `prompt` (plus a `filter` for an event trigger) when `mode` is `direct`, and a `message` when it is `assisted`. Contract fixtures are test vectors, not catalog data: they live in `tests/fixtures/automations/`, are optional per automation, and are reachable only through the `@openhands/extensions/testing/automations/*.json` subpath - never from the runtime entry point.
- For Python test runs, prefer `uv sync --group test` followed by `uv run pytest -q`; the full suite depends on `openhands-sdk`, which is not available in the base environment.
- Agent-driven plugins (for example `plugins/pr-review` and `plugins/release-notes`) use `uv run --with openhands-sdk --with openhands-tools ...` and require an `LLM_API_KEY` in addition to `GITHUB_TOKEN`.
- For OpenHands Cloud API guidance, automations, and CLI integration, use `plugins/openhands`. It is the canonical unified OpenHands plugin covering the V1 Cloud API, Automations API, and CLI. The individual skills (`skills/openhands-api`, `skills/openhands-automation`) are also available standalone.
- When reviewing or editing `skills/openhands-sdk`, validate copy-paste imports against the released packages with `uv run --with openhands-tools --with openhands-workspace --with openhands-agent-server python ...`. In the current released workspace package, the exported remote workspace classes are `APIRemoteWorkspace` / `OpenHandsCloudWorkspace`; `RemoteAPIWorkspace` is not available.
- For agent-driven plugin scripts, prefer `from openhands.sdk.plugin import PluginSource` and pass `plugins=[PluginSource(source=...)]` into `Conversation`. The current released SDK (`openhands-sdk` 1.49.x) exports both `PluginSource` and `Plugin` from `openhands.sdk.plugin`, so `Plugin.load(...)` imports also work (used by `tests/test_skill_plugin_loading.py`).
- `plugins/qa-changes/action.yml` now has a preflight guard for fork PRs in `pull_request` context: if the PR comes from a fork and `LLM_API_KEY` is unavailable (normal for forks), the action exits successfully with a clear skip notice instead of failing.
- `skills/bitbucket` should not tell agents to rewrite remotes proactively. In OpenHands, `BITBUCKET_TOKEN` is commonly kept in unencoded `user:token` form for API calls like `curl --user "$BITBUCKET_TOKEN" ...`; only split and URL-encode it when constructing a non-interactive HTTPS Git remote URL.

- `plugins/release-notes` now has a standalone validator at `plugins/release-notes/scripts/validate_release_notes.py`; it rebuilds the deterministic tag-range context, fails if a change bullet omits explicit PR/commit refs or matching author handles, and enforces full PR/author coverage by appending a compact `### 🔎 Small Fixes/Internal Changes` appendix grouped by author when the agent omits lower-signal items. New contributor detection in `generate_release_notes.py` should use merged PR history for human authors (excluding bots) rather than commit-author lookup.


## PR-specific documents (`.pr/`)

When working on a PR that requires design documents, live-test logs, development-only scripts, or other temporary artifacts that should be removed after merging to `main`, store them in a `.pr/` directory at the repository root.

```bash
mkdir -p .pr

.pr/
├── design.md       # Design decisions and architecture notes
├── analysis.md     # Investigation or debugging notes
└── notes.md        # Any other PR-specific content
```

The `PR Artifacts` workflow (`.github/workflows/pr-artifacts.yml`) owns the lifecycle of this directory:

1. **Notification**: When a PR contains `.pr/`, a single comment is posted to the PR conversation alerting reviewers.
2. **Post-merge cleanup**: After a same-repository or fork PR merges to `main`, the workflow automatically opens or updates a cleanup PR to remove the directory from `main`.

Important notes:

- Do not put anything in `.pr/` that needs to be preserved.
- The `.pr/` check is informational during development; it posts a notice rather than blocking the PR.
- Cleanup PRs follow the normal review and required-check protections for `main`.
- npm packaging excludes `.pr/` while its cleanup PR is pending, so temporary review artifacts do not ship in releases.
- Release PRs fail the required `test` check while their committed tree contains `.pr/`. Merge the artifact-cleanup PR and let release-please update the release PR before merging it.

## CI / validation gotchas

- The test suite expects **every directory under `skills/`** to be listed in a marketplace. If you add a new skill (or rebase onto a main branch that added skills), update the appropriate marketplace file or CI will fail with `Skills not in any marketplace: [...]` (`test_marketplace_includes_all_skills` in `tests/test_sdk_loading.py`). The `coverage` task in `scripts/sync_extensions.py` checks the same mapping for both `skills/` and `plugins/`, but reports gaps as a non-blocking warning instead of failing.
- `scripts/sync_extensions.py` keeps generated artifacts in sync: Claude Code command files, README catalog section, coverage checks, and vendor symlinks. Run `python scripts/sync_extensions.py --check` (or just push — CI runs it) to verify everything is consistent. Run without `--check` to auto-fix. The "Quick Start" section in `README.md` (OpenHands SDK, Claude Code, and Codex setup instructions) is **manually maintained** above the auto-generated catalog markers and is intentionally not generated by the sync script.
- The sync script uses PyYAML to parse SKILL.md frontmatter. If you add a skill with a slash trigger (e.g., `triggers: ["/mycommand"]`), the script auto-generates `commands/mycommand.md`. **Note:** Slash triggers in SKILL.md frontmatter are deprecated — prefer adding a `commands/command-name.md` file to the plugin's `commands/` directory instead. Keyword triggers (non-slash) remain the recommended way to activate skills by topic.

- The GitHub issue-triage automation and repository writers may grant
  `ready-for-dev`.
  `.github/workflows/issue-readiness-check.yml` removes a newly applied label
  unless a user with `write`, `maintain`, or `admin` permission applied it. An
  `unlabeled` event then lets triage reassess the issue.
  `.github/workflows/pr-description-check.yml` still requires linked issues to
  be ready, and label transitions refresh that check through
  `.github/scripts/refresh_linked_pr_checks.py`. Tests for the PR gates live in
  `tests/test_check_pr_description.py` and
  `tests/test_refresh_linked_pr_checks.py`.

## OpenHands SDK documentation policy

- **Do NOT add SDK-specific or SDK-related documentation to this repo.** The canonical source of truth for SDK documentation is the [OpenHands docs site](https://docs.openhands.dev/sdk) and its structured index at <https://docs.openhands.dev/llms.txt>.
- The `skills/openhands-sdk/SKILL.md` is **auto-generated** by `scripts/sync_openhands_sdk_skill.py`. It pulls class names, guides, examples, and the hello-world snippet directly from the docs site and the SDK repo. **Do not edit SKILL.md by hand** - run the script to regenerate it.
- CI runs `python scripts/sync_openhands_sdk_skill.py --check` on every PR. If the skill is out of date, regenerate it with `python scripts/sync_openhands_sdk_skill.py`.
- If a PR adds or modifies SDK-specific documentation in this repo, **push back**: ask the submitter to contribute those changes to [OpenHands/docs](https://github.com/OpenHands/docs) instead.

## PR review plugin notes

- The `code-review` and `codereview-roasted` skills have been merged into a single `code-review` skill. The `/codereview-roasted` trigger is kept as an alias for backward compatibility. The `review-style` action input is deprecated and ignored.
- `plugins/pr-review` supports an optional `require-evidence` action input that tells the reviewer to require end-to-end proof in the PR description that the code works; test output alone is not sufficient evidence.
- The corresponding `REQUIRE_EVIDENCE` env flag is consumed by `plugins/pr-review/scripts/agent_script.py` and injected into the review prompt via `plugins/pr-review/scripts/prompt.py`.
- `plugins/pr-review` exposes an `enable-uv-cache` input (default `'false'`) that toggles `setup-uv`'s GitHub Actions cache. Default stays off because a prompt-injected reviewer could poison a shared cache that higher-privilege workflows later consume; opt in only on single-tenant self-hosted runners. The README's "Caching and Security" section documents the threat model and recommends a host-level uv cache volume as the preferred alternative for self-hosted setups.
- GitHub review suggestions that only delete lines can look empty in `PullRequestReviewComment.body`; the rendered content is available via `bodyText`/`bodyHTML`, so review-context formatting should fall back there before treating a suggestion as empty.
- Prompt coverage for this behavior lives in `tests/test_pr_review_prompt.py`.
- `plugins/pr-review`'s `collect-feedback` input should append a short thumbs up/down footer to the main GitHub review body via `agent_script.py` / `prompt.py`, rather than posting a separate PR comment. `evaluate_review.py` should read feedback from review-body reactions while still tolerating legacy issue-comment markers.


## When uncertain

- Prefer the official OpenHands docs on skills: https://docs.openhands.dev/overview/skills
- Prefer the SDK skill guide: https://docs.openhands.dev/sdk/guides/skill
