Runtime

How to run OpenHands in a cloud sandbox

Install the OpenHands CLI with uv tool install openhands in a Linux microVM, pass the model by environment and run openhands --headless.

Runtime is the isolated machine OpenHands' headless mode assumes, and the LLM key never lands on it. The OpenHands docs state that headless mode always runs in always-approve mode: every action the agent chooses executes 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 September 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 it ignores them and uses ~/.openhands/settings.json. Store the key under LLM_API_KEY, tied to the provider's host. For Anthropic:

Terminalprintf %s "$ANTHROPIC_API_KEY" | npx withruntime secrets set LLM_API_KEY --host api.anthropic.com

Every 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 and leaves it worthless everywhere else (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.12 is the install OpenHands recommends. uv ships in the sandbox image and finds its Python 3.12, and the openhands command lands in /workspace/.local/bin, first on PATH.
  • 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 and reaches the CLI through -f, so a long issue description with code blocks arrives intact. -t "<text>" works for short ones.
  • --headless runs with no UI and approves every action; it needs -t or -f. --json streams the run's events as JSON Lines.
  • --override-with-envs makes the CLI read LLM_MODEL and the placeholder in LLM_API_KEY. The model string is in LiteLLM's provider/model form.
  • The working directory is the checkout. The CLI works where it is started, or in OPENHANDS_WORK_DIR when 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. Remove it for a project whose dependencies are already installed, and the agent can reach the model and nothing else (turn off sandbox internet).

CLI flags for automation

From the OpenHands CLI docs and its --help, 25 September 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

Conversations are saved under ~/.openhands/conversations in the sandbox, so --resume works on a kept sandbox. Pause it 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 takes the command, then its arguments after --:

Terminalopenhands mcp add runtime --transport stdio npx -- -y withruntime mcp

The same entry by hand, in ~/.openhands/mcp.json:

JSON{  "mcpServers": {    "runtime": { "command": "npx", "args": ["-y", "withruntime", "mcp"] }  }}

The first Runtime call answers with a link and a code. Approve Connect agent in the browser and every tool appears; no key goes in the file (MCP). Scripts and CI set RUNTIME_API_KEY from a secret manager instead.

Tool What OpenHands can do
runtime_sandbox_create Start a microVM for a risky command or a test run
runtime_sandbox_exec Run it and read exit code, stdout and stderr
runtime_sandbox_process Follow a long job started in the background
runtime_volume_create Keep a cache disk between sandboxes
runtime_sandbox_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, never less than a twentieth of a vCPU, and $0.0075 per GiB-hour for reserved memory. At 2 vCPUs and 4 GiB that is $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. The free trial covers 50 sandbox hours, no card:

Terminalnpx withruntime sandbox run --trial -- python3 --version

To score many headless runs side by side, see agent evals and SWE-bench. Similar setups: Aider and Goose.

Sources

Checked 25 September 2026.

Facts on this page were checked on 25 September 2026.