OptionalAutonomous AI AgentsVersion 0.1.0

OpenHands CLI Skill for Hermes Agent: Headless Coding Delegation

Delegate coding to OpenHands CLI (model-agnostic, LiteLLM).

Written by Neura Market from the official Hermes Agent documentation for Openhands. Commands, paths, and version numbers are reproduced from the source unchanged.

Read the official documentation

The OpenHands skill lets Hermes Agent hand a coding job to the OpenHands CLI and read back a structured event stream. It is the right tool when you want a coding agent that is not tied to one model vendor, and when the work is a batch or one-shot task rather than an interactive session. If you already run Hermes and want DeepSeek, Qwen, Ollama, vLLM, or Nous models doing file edits and shell work inside a project, this skill is the bridge.

What it does

The skill wraps the upstream OpenHands CLI in headless mode and drives it through Hermes's terminal tool. OpenHands itself is model-agnostic because it routes model calls through LiteLLM, so any provider LiteLLM supports can back the agent. The skill deliberately does not use the interactive textual UI; everything goes through the non-interactive path so Hermes can capture output and act on it.

In practice you give OpenHands a task prompt, a working directory, and a set of environment variables naming the model and credentials. OpenHands then works inside that directory, editing files and running shell commands, and emits a JSONL event stream that Hermes can parse. That stream is the contract: it tells you which tool the agent picked, what the tool returned, and what the agent's final message was.

This is a delegation skill, not a chat skill. You use it when the job has a clear end state and you want the result back as data.

Before you start

Three things must be true before the first run.

First, OpenHands must be installed upstream. The source requires Python 3.12+ and uv, and the install command is:

terminal(command="uv tool install openhands --python 3.12")

You can check the install with openhands --version. At the time the source was written this reported OpenHands CLI 1.16.0 and SDK v1.21.0, but treat those as a snapshot rather than a requirement.

Second, you need a model and credentials exposed as environment variables. The skill relies on --override-with-envs, which reads LLM_MODEL, LLM_API_KEY, and LLM_BASE_URL:

export LLM_MODEL=openrouter/openai/gpt-4o-mini       # or any LiteLLM slug
export LLM_API_KEY=$OPENROUTER_API_KEY
export LLM_BASE_URL=https://openrouter.ai/api/v1     # omit for native OpenAI

The LLM_MODEL value is a LiteLLM slug, not a provider-native model name. That distinction matters. When the provider is OpenRouter the slug carries a double prefix, for example openrouter/anthropic/claude-sonnet-4.5. Native Anthropic uses anthropic/claude-sonnet-4-5, and native OpenAI uses openai/gpt-4o-mini. The source calls out the hyphen versus dot difference explicitly: anthropic/claude-sonnet-4-5 is native Anthropic, while openrouter/anthropic/claude-sonnet-4.5 goes through OpenRouter.

Third, suppress the startup banner so it does not pollute JSON output:

export OPENHANDS_SUPPRESS_BANNER=1

Without that variable, every run begins with a multi-line +--+ ASCII box advertising the SDK. It is cosmetic, but it makes parsing harder and it wastes tokens if you feed output back to a model.

On platforms: the skill is gated to linux and macos. Upstream OpenHands does not support Windows natively and its docs require WSL, which is why the skill carries that gate.

How to run it

Always invoke OpenHands through the terminal tool. For automation, always pass --headless --json --override-with-envs --exit-without-confirmation. Those four flags together produce a non-interactive run that emits machine-readable output and does not stop to ask you to confirm exit.

One-shot task

terminal(
  command="OPENHANDS_SUPPRESS_BANNER=1 LLM_MODEL=openrouter/openai/gpt-4o-mini LLM_API_KEY=$OPENROUTER_API_KEY LLM_BASE_URL=https://openrouter.ai/api/v1 openhands --headless --json --override-with-envs --exit-without-confirmation -t 'Add error handling to all API calls in src/'",
  workdir="/path/to/project",
  timeout=600
)

This is the shape you will use most. The environment variables are set inline so the run is self-contained, the task goes in -t, and workdir is the project root. The source uses a 600 second timeout, which is a reasonable starting point for a task that touches many files. Note that the workspace is not a flag: it is the workdir you pass to the terminal tool.

Background for long tasks

terminal(command="<same as above>", workdir="/path/to/project", background=true, notify_on_complete=true)
process(action="poll", session_id="<id>")
process(action="log", session_id="<id>")

When a task is likely to outlast a comfortable synchronous wait, run it in the background and poll. process(action="poll", ...) gives you status, and process(action="log", ...) retrieves the accumulated output. This pattern keeps Hermes responsive while OpenHands works.

Resume a previous conversation

OpenHands prints a Conversation ID: line and a Hint: openhands --resume line at the end of each run. To continue that conversation, use the dashed form of the ID:

terminal(
  command="OPENHANDS_SUPPRESS_BANNER=1 LLM_MODEL=... openhands --headless --json --override-with-envs --exit-without-confirmation --resume <dashed-uuid> -t 'Now fix the bug you found'",
  workdir="/path/to/project"
)

Resuming is useful when a first pass surfaces a problem and you want the same agent context to address it, rather than starting cold with a new prompt.

The real flag list

The source verified this table against openhands --help for CLI 1.16.0. Anything not listed is not a flag; pass it through an environment variable or a settings file instead.

FlagEffect
--headlessNo UI, requires -t or -f. Auto-approves all actions (no --llm-approve in this mode).
--jsonJSONL event stream (requires --headless).
-t TEXTTask prompt.
-f PATHRead task from file.
--resume [ID]Resume conversation. No ID → list recent.
--lastResume most recent (with --resume).
--override-with-envsApply LLM_API_KEY / LLM_BASE_URL / LLM_MODEL env vars. Without this, OpenHands uses ~/.openhands/settings.json and ignores the env.
--exit-without-confirmationDon't show the "are you sure" exit dialog.
--always-approve / --yoloAuto-approve every action (default in --headless).
--llm-approveLLM-based security gate (interactive only, does NOT work in headless).
--version / -vPrint version and exit.

There is no --model, --max-iterations, --workspace, --sandbox, or --sandbox-type flag. Model selection is LLM_MODEL. Workspace is the workdir you pass to the terminal tool. Sandbox and runtime configuration come from the RUNTIME and SANDBOX_VOLUMES environment variables.

Reading the JSON event stream

With --json --headless, OpenHands emits JSONL: one JSON object per line, mixed with a handful of non-JSON status lines such as Initializing agent..., Agent is working, Agent finished, the final summary box, Goodbye!, Conversation ID:, and Hint:. Filter for lines starting with { and ignore the rest.

The top-level kind field tells you what each event is:

  • MessageEvent is a user or agent text turn. Its source is user or agent.
  • ActionEvent means the agent picked a tool. Read tool_name (file_editor, terminal, finish) and action.kind (FileEditorAction, TerminalAction, FinishAction).
  • ObservationEvent is a tool result. observation.is_error is the success flag, and source is environment.
  • A FinishAction inside an ActionEvent carries the agent's final message in action.message.

Parse only stdout, line by line, and skip lines that do not start with {. The CLI prints all stderr from LiteLLM and Authlib first, so mixing streams will corrupt your parse.

Pitfalls

The source lists several failure modes worth knowing before you debug a run.

LiteLLM warnings appear on every invocation. The CLI prints bedrock-runtime and sagemaker-runtime warnings to stderr because botocore is not installed, plus an Authlib deprecation notice. These are noise, not failures. Pipe stderr to /dev/null or filter it before showing output to a user.

Banner spam is the second. Without OPENHANDS_SUPPRESS_BANNER=1, every run starts with the multi-line +--+ ASCII box. Always export it.

--override-with-envs is mandatory for automation. Without it, OpenHands ignores LLM_API_KEY, LLM_BASE_URL, and LLM_MODEL and falls back to ~/.openhands/settings.json. On a fresh install that file does not exist and the CLI hangs waiting for first-run setup. This is the single most common reason a first attempt appears to freeze.

Model slugs are LiteLLM's, not the provider's. openrouter/openai/gpt-4o-mini works; openai/gpt-4o-mini while pointed at OpenRouter does not. A wrong slug produces a cryptic LiteLLM 400.

The wrong package is a trap. pip install openhands-ai is the legacy V0 SDK, not the CLI. The correct install is uv tool install openhands --python 3.12. There is no maintained conda package.

Resume IDs are fiddly. The CLI ends with Conversation ID: f46573d9cfdb45e492ca189bde40019b (no dashes) and then a Hint: openhands --resume f46573d9-cfdb-45e4-92ca-189bde40019b (with dashes). Use the dashed form.

Headless ignores --llm-approve. Passing it produces an argparse error because headless mode hardcodes always-approve.

Windows is not supported upstream. The OpenHands docs require WSL on Windows, and the skill is gated [linux, macos] accordingly.

Conversation storage accumulates. Each run persists a trajectory under ~/.openhands/conversations//, so clean it up if you run batches.

The install is heavy, around 200 packages. Use uv tool install so OpenHands lives in an isolated venv and does not conflict with the active project's dependencies.

Verification

Before trusting the skill in a workflow, run the source's smoke test:

terminal(
  command="OPENHANDS_SUPPRESS_BANNER=1 LLM_MODEL=openrouter/openai/gpt-4o-mini LLM_API_KEY=$OPENROUTER_API_KEY LLM_BASE_URL=https://openrouter.ai/api/v1 openhands --headless --json --override-with-envs --exit-without-confirmation -t 'Print the string OPENHANDS_OK to stdout via the terminal tool.'",
  workdir="~/.hermes/cache/scratch",
  timeout=120
)

If the JSONL stream ends with a FinishAction whose action.message mentions OPENHANDS_OK, the install is working. This checks the whole chain: install, model credentials, environment override, headless mode, and JSON output.

When not to use it

Reach for a sibling skill when the vendor is fixed. For Claude-native work, prefer claude-code. For OpenAI-native work, prefer codex. For Hermes-native subagents, use delegate_task rather than shelling out to OpenHands. The opencode skill is the other multi-provider option and may suit you better depending on your setup. Choose OpenHands when you specifically want OpenHands, or when you need a coding agent on a provider the vendor-tied skills cannot reach, such as DeepSeek, Qwen, Ollama, vLLM, or Nous.

Related

Skills the docs pair this with

More Autonomous AI Agents skills