
# OpenHands

[OpenHands](https://openhands.dev/) is an open-source autonomous
software agent. It reaches models through LiteLLM, so any
OpenAI-compatible endpoint works once the model name carries the
right prefix.

Verified against the OpenHands CLI **1.16.0**. The config below is
re-run against that release every week, including a tool call
round trip. Upstream has stopped maintaining the standalone CLI;
it still runs, and it is the only OpenHands entry point with a
headless mode. The OpenHands web app takes the same three values
(see [Web app](#web-app) below).

## Install

The CLI needs Python 3.12 or later:

```bash
uv tool install openhands==1.16.0 --python 3.12
```

## Configure

Three environment variables describe the model:

<!-- verify: openhands-env -->
```bash
export LLM_MODEL=openai/auto/mistralai/mistral-large-2512
export LLM_BASE_URL=https://api.lowrouter.ai/v1
export LLM_API_KEY=sk-lr-...
```

The CLI ignores them unless you pass `--override-with-envs`. Without
the flag it reads the model from its saved settings in
`~/.openhands/`, and the variables have no effect. The override is
not written back to those settings.

```bash
openhands --headless --override-with-envs -t "Summarise what this repository does"
```

`--headless` runs without the terminal UI and approves every action
the agent takes, shell commands included. Run it in a directory, or a
container, where that is acceptable. Drop `--headless` for the
interactive UI.

Two details in the variables are easy to get wrong:

- `LLM_MODEL` starts with `openai/`. That prefix tells LiteLLM to use
  its OpenAI-compatible client; LiteLLM strips it, and LowRouter
  receives `auto/mistralai/mistral-large-2512` unchanged. Without the
  prefix LiteLLM does not recognise the provider and no request is
  sent.
- `LLM_BASE_URL` ends in `/v1`. LiteLLM appends only
  `/chat/completions`, so the bare origin gives a `404`.

Neither mistake shows up in the exit code. Headless mode exits `0`
and prints its own system prompt as the "last message sent by the
agent" when the model call failed. If the summary shows a system
prompt, check the two variables above.

## Picking a model

OpenHands works through function calling for every step: reading
files, editing them, running commands. Pick a model with the
*function-calling* tag. [The model browser filtered to
them](/models?function_calling=yes) lists every one.

Tasks run for many turns and resend the whole conversation on each
one, so input tokens dominate the bill. The example uses
`auto/mistralai/mistral-large-2512`, served from the EU. Models we
have run through OpenHands' tool loop:

- `auto/qwen/qwen3-coder-30b-a3b-instruct`, a coding model served
  from the EU (128k context).
- `auto/anthropic/claude-haiku-4.5`, a cheaper Anthropic model
  (200k context).

OpenHands sends model IDs containing `gpt-5` or `gpt-6` to the
Responses API (`/v1/responses`) instead of Chat Completions.
LowRouter serves both, so those models work without extra settings.

## Cost tracking

OpenHands' Conversation Metrics shows a per-conversation cost. On
LowRouter the number comes from the response itself, so `auto/…` and
`alias/…` models report it like any other:

- Non-streaming calls carry an `x-litellm-response-cost` response
  header — the figure OpenHands' metrics read. It is in USD and
  informational; your invoice is in your billing currency, and the
  `x-lowrouter-response-cost` + `x-lowrouter-response-cost-currency`
  headers carry that billed figure.
- Streaming calls cannot carry a late-computed header, so their cost
  rides the final chunk's `usage.cost` field, which LiteLLM forwards.
- Tracing setups read the same figures as OpenTelemetry span
  attributes (`gen_ai.usage.cost` in USD — but only when a stored
  EUR/USD rate exists — `lowrouter.cost.amount` +
  `lowrouter.cost.currency` in the billing currency).

## Web app

The OpenHands web app sets the same values in its LLM settings under
**Advanced**: custom model `openai/auto/mistralai/mistral-large-2512`,
base URL `https://api.lowrouter.ai/v1`, and your API key. The web
app has no headless mode, so this path is documented but not re-run
by the weekly check.

## Recommended setup

- Use a dedicated key. An autonomous agent left running overnight can
  spend a lot, and a key of its own keeps that spend attributable and
  revocable without touching anything else.
- Cap the run. OpenHands stops a conversation after
  `max_iterations` steps, 500 by default; lower it in the saved
  settings for unattended tasks.
- Keep it in a container or a throwaway checkout when using
  `--headless`, which approves every command.

## Troubleshooting

- The summary shows the system prompt, and nothing happened:
  `LLM_MODEL` is missing the `openai/` prefix, or `LLM_BASE_URL` is
  missing `/v1`.
- The CLI uses a different model from the one you exported:
  `--override-with-envs` is missing, so the saved settings won.
- Tool calls fail or the agent loops without acting: check that the
  model supports function calling on its page in the model browser.
