OpenHands (openhands)
Output
AGENTS.md # canonical entry-point pointer body + inlined always-on rules (written by sync, shared path)
.agents/agents/<name>.md # one project agent (shared with Goose)
.agents/skills/<name>/SKILL.md # one folder per skill, plus one per path-triggered rule (shared tree with codex/amp/zed/crush)
config.toml # when MCP entries exist
.openhands/hooks.json # when hook entries exist
.openhands/setup.sh # when an environment spec sets `install`
All Hands OpenHands reads the root AGENTS.md natively and loads skills from .agents/skills/, the same cross-tool tree codex, amp, zed, and crush emit. The render is byte-identical, so the shared tree dedupes into one write.
Local conversations also auto-register top-level project agents from .agents/agents/<name>.md, the vendor's primary project path. The shared Goose/OpenHands renderer writes name, description, optional free-form model, and the prompt body byte-identically.
A generic tools list is omitted with a coverage note because OpenHands uses its own file_editor and terminal vocabulary. Set x-openhands.tools with native names and move outputs.openhands.agents-dir to the documented secondary .openhands/agents path when an OpenHands profile must differ from Goose's shared file.
An always-on rule (no globs/paths value and no source-layout or frontmatter scope) inlines into the shared AGENTS.md ## Rules block, same as every other AGENTS.md-family target. A rule that carries one of those instead emits as a path-triggered rule: .agents/skills/<name>/SKILL.md with a paths: frontmatter list, OpenHands' own deterministic per-file mechanism: "guaranteed to load for the files they scope, with no reliance on the model choosing them", and "zero baseline cost" to the context window until a matching file is touched.
The vendor documents two locations for this, a flat .md file and a folder; this adapter writes the folder form, so a path-triggered rule shares outputs.openhands.skills-dir with regular skills instead of needing a separate rules-dir key. A catch-all globs/paths value stays inlined because it scopes to every file.
- Hooks: land in
.openhands/hooks.json(override viaoutputs.openhands.hooks-file), the file OpenHands reads "per-repository" and honors "across Cloud, CLI, and local GUI setups" (docs.openhands.dev/openhands/usage/customization/hooks).- Six events:
PreToolUse,PostToolUse,UserPromptSubmit,Stop,SessionStart,SessionEnd. OpenHands' own layout keys them in snake_case with no wrapper, and documents the Claude form as equally valid: "PascalCase event keys (e.g.,PreToolUse) and the{\"hooks\": {...}}wrapper are both supported, so you can share hook scripts between the two tools". This adapter emits that shared form, so one renderer serves both targets and a hook spec renders comparably across them. - Per entry:
command,type(alwayscommand), optionaltimeout(seconds, vendor default 60) andasync. Amatcheronly applies to the two ToolUse events. - OpenHands names its own tools, and the vendor flags the trap itself ("tool names (e.g.,
terminalvsBash)"), so a matcher carried over from a Claude spec parses and then matches nothing. That case surfaces a coverage note rather than a guessed rename, since onlyterminalis documented alongside*and regex, leaving no vendor-stated counterpart for the rest.
- Six events:
- MCP: merges into
./config.tomlunder a[mcp]table with three arrays instead of atypefield:stdio_servers([[mcp.stdio_servers]]tables carryingname/command/args/env) andsse_servers/shttp_servers(shttp_serversis OpenHands' streamable-HTTP transport, the cross-tool spec'stype: http).- Each remote element is a bare URL string, OpenHands' simplest documented form, or the vendor's
{ url, api_key, timeout }object once the entry sets a top-levelapi_keyand/or (shttp only)timeoutfield. TOML allows mixing both forms in one array, as OpenHands' own example does. timeout(int, 1-3600 seconds, default 60, vendor exampletimeout = 1800) is documented for the SHTTP tab only; an sse entry that sets it gets a coverage note instead of a silent no-op.- The spec's generic
headersfield has no equivalent here (OpenHands documents only the singleapi_keycredential, never a header map) and surfaces a coverage note instead of reaching the target with the credential silently missing. - A transport OpenHands documents no array for (e.g.
type: ws) reaches neither array and surfaces a coverage note instead of guessing one. - The project-tier
config.tomlis managed (its[mcp]table is overwritten each sync); keep unmanaged OpenHands config elsewhere.
- Each remote element is a bare URL string, OpenHands' simplest documented form, or the vendor's
- Environments: an environment spec's
installfield writes.openhands/setup.sh, the vendor's documented repository bootstrap script ("You can add a.openhands/setup.shfile, which will run every time OpenHands begins working with your repository... an ideal location for installing dependencies, setting environment variables, and performing other setup tasks").- The script gets a
#!/bin/bashshebang, then the provenance header, theninstallverbatim as the body. OpenHands chmods the script itself before running it (chmod +x {script} && source {script}), so this file needs no executable bit set on write. terminals(Cursor's long-running dev processes) has no equivalent here, since the script runs once, synchronously, at repo start; it surfaces a coverage note instead of being silently dropped.- Multiple environment specs merge the same way Cursor's do: last spec's
installwins.
- The script gets a
Config keys
| Key | Default |
|---|---|
outputs.openhands.agents-dir | .agents/agents |
outputs.openhands.skills-dir | .agents/skills |
outputs.openhands.mcp-file | config.toml |
outputs.openhands.hooks-file | .openhands/hooks.json |
outputs.openhands.setup-file | .openhands/setup.sh |
Verify
- Install OpenHands (docs).
- Check the tree:
ls AGENTS.md .agents/agents/ .agents/skills/ config.toml .openhands/setup.shtest -f .agents/agents/*.mdtest -f .agents/skills/*/SKILL.mdhead -1 config.tomlfor the provenance comment
- Launch OpenHands:
- The context loads
AGENTS.md. - Each
.agents/skills/<name>/appears as a skill. - A path-triggered rule's
.agents/skills/<name>/SKILL.mdinjects only when a file matching itspaths:globs is touched. - Each
[mcp]server fromconfig.tomlconnects. .openhands/setup.shruns at session start.- Each
.openhands/hooks.jsonentry fires on its event (checkOPENHANDS_EVENT_TYPEin the hook's environment to confirm which).
- The context loads