Runtime is the isolated machine that OpenHands' headless mode assumes, and
the LLM key never reaches it. The OpenHands docs state that headless mode
always runs in always-approve mode. Every action the agent chooses runs with no
confirmation. On Runtime, each run gets a Firecracker microVM with its own
kernel, Ubuntu 24.04 and Python 3.12, the exact Python version the openhands
package requires. The key is a Runtime secret, so LLM_API_KEY inside the
sandbox is only a placeholder. A 2 vCPU, 4 GiB sandbox costs $0.03125 an
hour while the agent waits on the model. The CLI was at version 1.16.0 on PyPI
on 25 October 2026.
| Question | Answer on Runtime |
|---|---|
| Where do OpenHands' commands run? | In the sandbox's own Linux, not on your runner |
| Where does the LLM key live? | On Runtime's servers; the sandbox holds a placeholder |
| What can the agent reach? | The hosts you allow, such as the model API and PyPI |
| Can OpenHands on a laptop use sandboxes? | Yes, through Runtime's MCP server |
Store the key under OpenHands' variable
The CLI reads its model settings from three variables when you pass
--override-with-envs: LLM_MODEL, LLM_API_KEY and, optionally,
LLM_BASE_URL. Without the flag, the CLI ignores them and reads
~/.openhands/settings.json. Store the key under LLM_API_KEY, bound to the
provider's host. For Anthropic:
Terminalprintf %s "$ANTHROPIC_API_KEY" | npx withruntime secrets set LLM_API_KEY --host api.anthropic.comEvery sandbox then has LLM_API_KEY set to a placeholder. The proxy replaces
it with the real key in HTTPS requests to api.anthropic.com. Anywhere else,
the placeholder is useless (secrets sandboxes never see).
LLM_MODEL is not sensitive, so it goes in the command's environment.
Run a task headless
TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";const repo = "https://github.com/your-org/your-repo";const task = "Issue #212: the retry decorator ignores max_attempts. Reproduce it with a test, then fix it.";await using sbx = await Sandbox.create({ diskMiB: 10240, timeoutSeconds: 3600 });const setup = { check: true, timeoutMs: 900_000 } as const;await sbx.exec("uv tool install openhands --python 3.12", setup);await sbx.exec(["git", "clone", "--depth", "1", repo, "/workspace/app"], setup);await sbx.files.write("/workspace/task.md", task);await sbx.network.set({ internet: true, allow: ["api.anthropic.com", "pypi.org", "files.pythonhosted.org"],});const run = await sbx.exec( ["openhands", "--headless", "--override-with-envs", "--json", "-f", "/workspace/task.md"], { cwd: "/workspace/app", env: { LLM_MODEL: "anthropic/claude-sonnet-4-5-20250929" }, timeoutMs: 2_400_000, onStderr: (text) => process.stderr.write(text), },);const events = run.stdout.split("\n").filter((line) => line.startsWith("{"));console.log(run.exitCode, `${events.length} events`);await sbx.exec("git add -A && git diff --cached > /workspace/openhands.patch", { cwd: "/workspace/app", check: true,});await writeFile("openhands.patch", await sbx.files.readText("/workspace/openhands.patch"));Pythonimport sysfrom withruntime import Sandboxrepo = "https://github.com/your-org/your-repo"task = "Issue #212: the retry decorator ignores max_attempts. Reproduce it with a test, then fix it."with Sandbox.create(disk_mib=10240, timeout_seconds=3600) as sbx: sbx.exec("uv tool install openhands --python 3.12", check=True, timeout_ms=900_000) sbx.exec(["git", "clone", "--depth", "1", repo, "/workspace/app"], check=True, timeout_ms=900_000) sbx.files.write("/workspace/task.md", task) sbx.network.set(internet=True, allow=["api.anthropic.com", "pypi.org", "files.pythonhosted.org"]) run = sbx.exec( ["openhands", "--headless", "--override-with-envs", "--json", "-f", "/workspace/task.md"], cwd="/workspace/app", env={"LLM_MODEL": "anthropic/claude-sonnet-4-5-20250929"}, timeout_ms=2_400_000, on_stderr=sys.stderr.write, ) events = [line for line in run.stdout.splitlines() if line.startswith("{")] print(run.exit_code, len(events), "events") sbx.exec("git add -A && git diff --cached > /workspace/openhands.patch", cwd="/workspace/app", check=True) with open("openhands.patch", "w") as file: file.write(sbx.files.read_text("/workspace/openhands.patch"))Reading the code:
uv tool install openhands --python 3.12is the install that OpenHands recommends. uv ships in the sandbox image and finds its Python 3.12. Theopenhandscommand lands in/workspace/.local/bin, which is first onPATH.- A bigger disk. The package brings its SDK and tools with it. 10 GiB leaves room for them and for the project's own dependencies.
- The task goes in a file. It reaches the CLI through
-f, so a long issue description with code blocks arrives intact.-t "<text>"works for short tasks. --headlessruns with no UI and approves every action. It needs-tor-f.--jsonstreams the run's events as JSON Lines.--override-with-envsmakes the CLI readLLM_MODELand the placeholder inLLM_API_KEY. The model string uses LiteLLM'sprovider/modelform.- The working directory is the checkout. The CLI works where it is started,
or in
OPENHANDS_WORK_DIRwhen that is set. - The patch comes back as a file, so a large change is never cut at the 64 KiB result limit.
PyPI stays on the allow list because the agent usually installs the project's dependencies to run its tests. For a project whose dependencies are already installed, remove it. The agent can then reach the model and nothing else (turn off sandbox internet).
CLI flags for automation
From the OpenHands CLI docs and its 1.16.0 source, 25 October 2026:
| Flag | Meaning |
|---|---|
--headless |
No UI, every action approved; needs -t or -f |
-t, --task "<text>" |
Task text |
-f, --file <path> |
Task from a file |
--json |
JSONL events, with --headless only |
--override-with-envs |
Read LLM_MODEL, LLM_API_KEY, LLM_BASE_URL |
--resume <id>, --last |
Carry on an earlier conversation |
--always-approve, --llm-approve |
Interactive approval modes, for runs outside headless |
The CLI saves conversations under ~/.openhands/conversations in the sandbox.
So --resume works on a sandbox you keep. Pause the sandbox
between tasks. The checkout, the history and any running services stay, and
compute billing stops.
Point OpenHands at Runtime's tools
When OpenHands runs on your own machine, Runtime's MCP server gives it
sandboxes to run code in. The CLI's mcp add command takes the command, then
its arguments after --:
Terminalopenhands mcp add runtime --transport stdio npx -- -y withruntime mcpThe same entry by hand, in ~/.openhands/mcp.json:
JSON{ "mcpServers": { "runtime": { "command": "npx", "args": ["-y", "withruntime", "mcp"] } }}The first Runtime call returns a link and a code. Approve Connect agent in
the browser, and every tool appears. The file holds no key
(MCP). Scripts and CI set RUNTIME_API_KEY
from a secret manager instead.
| Tool | What OpenHands can do |
|---|---|
runtime_sandboxes_create |
Start a microVM for a risky command or a test run |
runtime_sandboxes_exec |
Run it and read exit code, stdout and stderr |
runtime_sandboxes_process |
Follow a long job started in the background |
runtime_volumes |
Keep a cache disk between sandboxes |
runtime_sandboxes_manage |
Stop the sandbox when the job is done |
Cost of a headless run
Runtime charges $0.025 per vCPU-hour for CPU actually used, with a floor of a twentieth of a vCPU. It charges $0.0075 per GiB-hour for reserved memory. At 2 vCPUs and 4 GiB, a run costs $0.03125 an hour while OpenHands reasons, and $0.08 an hour with both cores building (pricing). The LLM provider bills tokens on your key. Every account has 100 free hours of a 1 GB machine in the first 30 days, with no card:
Terminalnpx withruntime sandboxes run -- python3 --versionTo score many headless runs side by side, see agent evals and SWE-bench. Similar setups: Aider and Goose.
Sources
Checked 25 October 2026.
- Installation:
uv tool install openhands --python 3.12,~/.openhands/settings.json - Headless mode:
--headless,-t,-f,--json, always-approve - MCP servers:
openhands mcp addand~/.openhands/mcp.json - OpenHands CLI source:
--override-with-envs,LLM_MODEL,LLM_API_KEY,LLM_BASE_URL,OPENHANDS_WORK_DIR - openhands on PyPI: version 1.16.0, Python 3.12
Facts on this page were checked on 25 October 2026.