Type to search the docs and updates.

Browse documentation

OpenHands (openhands)

Set up this tool

OpenHands gets AGENTS.md, agents and skills under .agents/, hooks, and a setup script. Add MCP servers in OpenHands itself.

First rule

Select openhands in agnostic-ai.yaml, then follow First rule to add the shared commit-prefix rule and sync it. With the default paths, it reaches AGENTS.md.

MCP servers have no output. Add them in Agent Canvas, as MCP describes.

For existing files, check what import openhands reads before syncing.

Verify

Run agnostic-ai sync --check and inspect AGENTS.md for the commit-prefix rule. Open the project in OpenHands and ask for a commit subject for a small fix; it should use fix:.

Next, add a skill.

Output

AGENTS.md                          # pointer body + inlined always-on rules (shared path)
.agents/agents/<name>.md           # one project agent (shared with Goose)
.agents/skills/<name>/SKILL.md     # one folder per skill or path-triggered rule (shared tree)
.openhands/hooks.json              # when hook entries exist
.openhands/setup.sh                # when an environment spec sets `install`

Native mappings

Field mappings, discovery, and compatibility

Skills

Written identically to the other .agents/skills/ tools, so the shared folder is written once.

The OpenHands skills docs document no field that keeps a skill user-only. The SDK reads disable-model-invocation, but such a skill then runs only when a triggers keyword appears anywhere in a message, not by name. Sync does not write it: disable-model-invocation is dropped with a note, and commands are skipped.

Agents

OpenHands loads .agents/agents/<name>.md in local conversations. The writer is shared with Goose and writes name, description, an optional free-form model, and the prompt body.

  • Each server an agent names in mcpServers is written in full under mcp_servers, the only form OpenHands takes. Goose has no such key, so while Goose writes the same folder sync writes no servers and prints a note; set outputs.openhands.agents-dir to .openhands/agents. x-openhands.mcp_servers replaces the list.
  • A generic tools list is dropped with a sync note, because OpenHands uses its own tool names (file_editor, terminal). Set x-openhands.tools with those names instead. For an OpenHands profile that differs from Goose's, point outputs.openhands.agents-dir at .openhands/agents.
  • A portable color is dropped with a sync note, because Goose's agent frontmatter has no color. Set x-openhands.color to a Rich color name to write it.

Rules

An always-on rule goes into the ## Rules block of AGENTS.md. So does a rule with a catch-all globs or paths. A rule is always-on when it has no globs or paths and no scope from its folder or frontmatter.

Path-triggered rules

A scoped rule becomes .agents/skills/<name>/SKILL.md with a paths: list (its scope directory plus file patterns). OpenHands loads it only when a matching file is touched. It uses the folder form (a flat .md also works), so these rules share outputs.openhands.skills-dir with skills.

Hooks

OpenHands reads .openhands/hooks.json (outputs.openhands.hooks-file) per repository in Agent Canvas, OpenHands Cloud, and OpenHands Enterprise. agnostic-ai hook run tests them before a session, using OpenHands's event data, shell, and timeout.

Hooks load only from the working folder, so a root .openhands/hooks.json does not apply when OpenHands works in a subfolder.

  • Six events: PreToolUse, PostToolUse, UserPromptSubmit, Stop, SessionStart, SessionEnd.
  • Sync writes the Claude form (PascalCase keys inside a {"hooks": {...}} wrapper). OpenHands also accepts its own unwrapped snake_case form.
  • Each entry has command, type (always command), and optional timeout (seconds, default 60) and async. matcher applies only to the two ToolUse events.
  • A Claude matcher such as Bash matches nothing, because OpenHands calls that tool terminal. Sync adds a note and does not rename it. OpenHands documents only terminal, *, and regex.

MCP

No file. OpenHands keeps MCP servers in its settings store: add them in Agent Canvas under Customize > MCP Servers, or pass mcp_config to the SDK. It reads no project file, and ignores the [mcp] section of a project config.toml (legacy V0).

  • A project or home MCP spec gets one sync note that points at Customize > MCP Servers.
  • An agent's mcpServers still reach its mcp_servers, as Agents describes. There a remote api_key becomes an Authorization: Bearer <key> header, as OpenHands sends it. An Authorization entry in headers wins.
  • timeout gets a sync note: sync does not map it into an agent's mcp_servers. Agent Canvas separately offers an optional SHTTP timeout in seconds in its server editor.

Environments

install writes .openhands/setup.sh, the repository setup script OpenHands runs each time it opens the repo.

  • The file has a #!/bin/bash line, the generated-file header, then install as written. OpenHands runs chmod +x itself, so sync does not set the executable bit.
  • terminals (Cursor's long-running dev processes) gets a sync note, because the script runs once when the repo starts.
  • With several environment specs, the last install wins, as for Cursor.
Legacy config.toml and mcp.json

The next sync removes a config.toml that an earlier release wrote. import openhands still reads the [mcp] table of a legacy config.toml into specs.

Only the unmaintained OpenHands-CLI reads ~/.openhands/mcp.json. sync --global no longer writes it. The next sync --global removes the servers sync recorded as its own there. That includes a hand-written server an earlier sync adopted because it matched a spec exactly. Sync also removes the file when it created it and nothing else is left. Other servers stay. Run sync --global --backup to keep a copy of the file first. import --global openhands still reads the file into specs.

Config keys

KeyDefault
outputs.openhands.agents-dir.agents/agents
outputs.openhands.skills-dir.agents/skills
outputs.openhands.hooks-file.openhands/hooks.json
outputs.openhands.setup-file.openhands/setup.sh

outputs.openhands.mcp-file no longer has an effect.

Import

agnostic-ai import openhands reads the OpenHands files back into specs:

SourceBecomes
AGENTS.md embedded ## Rules block (### <name> children)<rules>/<name>.md per rule
.agents/skills/<name>/SKILL.md with paths (path-triggered rule)<rules>/<name>.md with paths
.agents/skills/<name>/SKILL.md (+ bundled files)<skills>/<name>/SKILL.md
.openhands/skills/ and .openhands/microagents/ (legacy)the same, read after .agents/skills/
flat <name>.md in any of those three directoriesa rule, or a skill when it has triggers (kept under x-openhands)
.agents/agents/<name>.md<agents>/<name>.md, with native skills under x-openhands
.openhands/hooks.jsonone hook spec per matcher group
config.toml [mcp] table (legacy V0)<mcps>/<name>.yaml per server
.openhands/setup.sh<environments>/openhands-setup.yaml with the script as install
AGENTS.md.agnostic-ai/AGNOSTIC_AI.md

.agents/skills/ wins a name clash with the legacy folders, as in OpenHands. Hooks import from both layouts. Snake_case pre_tool_use comes back as PreToolUse.

Import loses these details (none change what OpenHands loads):

  • Unnamed sse_servers and shttp_servers entries are named after the URL host (docs-example-test).
  • A rule scoped by its folder comes back as the paths glob it was widened to.
  • An environment spec keeps only install. Its name and terminals are lost.

Protected paths

Not enforced. This target takes no settings specs, so sync reports a spec with a protected block as unsupported. See protected-path alternatives.