OpenHands
OpenHands CLI runs an agent with local tools. Its 1.x CLI can run without Docker; the full GUI server uses Docker. rapid-mlx supplies its model through OpenHands' OpenAI-compatible LiteLLM transport.
Quick start
Needs: Python 3.12+, uv, and a server (rapid-mlx serve qwen3.5-4b-4bit).
$ export LLM_BASE_URL=http://localhost:8000/v1 $ export LLM_API_KEY=not-needed $ export LLM_MODEL=openai/qwen3.5-4b-4bit $ openhands --override-with-envs --headless --always-approve -t "List this directory"
--override-with-envs is required: OpenHands 1.x otherwise ignores the exported values. The openai/ prefix selects LiteLLM's OpenAI-compatible transport. These explicit exports work with the current CLI.
/v1/chat/completions ·
Setup: env vars LLM_BASE_URL /
LLM_API_KEY / LLM_MODEL ·
Matrix cell (pinned OpenHands 0.9.0 Docker image):
✅ ✅ ✅ XFAIL
(Qwen3.6 / Gemma 4 / DeepSeek pass; gpt-oss is
XFAIL (format) — its native harmony output never emits the
<execute_bash> text-action tags CodeActAgent
parses, an upstream OpenHands gap
#15167).
Install
$ uv tool install openhands --python 3.12 $ openhands --version
Config
Export these values for the model the server reports. The
agents openhands --setup command prints a bare model ID; add
openai/ when exporting LLM_MODEL. OpenHands' saved
CLI settings live at ~/.openhands/agent_settings.json:
$ export LLM_BASE_URL=http://localhost:8000/v1 $ export LLM_API_KEY=not-needed $ export LLM_MODEL=openai/qwen3.5-4b-4bit
Run
$ rapid-mlx serve qwen3.5-4b-4bit $ LLM_BASE_URL=http://localhost:8000/v1 \ LLM_API_KEY=not-needed \ LLM_MODEL=openai/qwen3.5-4b-4bit \ openhands --override-with-envs --headless --always-approve -t "List this directory"
Recommended aliases (per rapid-mlx agents openhands):
qwen3.5-9b-4bit, qwen3.6-35b-4bit,
qwen3.5-4b-4bit.
Docker E2E harness
The integration test
tests/integrations/test_openhands.sh
drives the pinned OpenHands 0.9.0 app + runtime images against a
running rapid-mlx serve. These historical cells do not
verify the current 1.x CLI:
# pinned by manifest-list digest so a moved tag can't silently swap the image OPENHANDS_IMAGE="ghcr.io/all-hands-ai/openhands:0.9.0@sha256:d4b028…" OPENHANDS_RUNTIME_IMAGE="ghcr.io/all-hands-ai/runtime:od_v0.9.0_…" $ docker run --rm \ -e SANDBOX_CONTAINER_IMAGE="$OPENHANDS_RUNTIME_IMAGE" \ -e LLM_BASE_URL="http://host.docker.internal:8000/v1" \ -e LLM_MODEL="openai/$MODEL" \ -e LLM_API_KEY="rapidmlx" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$WORKDIR:/opt/workspace_base" \ --add-host host.docker.internal:host-gateway \ "$OPENHANDS_IMAGE" \ python -m openhands.core.main \ -i 10 -d /opt/workspace_base \ -t "Fix the bug in add.py — this function should add, not subtract."
When the LLM is running on the same Mac as Docker Desktop,
localhost inside the container refers to the container
itself. The harness rewrites the URL host to
host.docker.internal so the container can reach the
rapid-mlx server. Any non-loopback host (remote-serve node, RFC1918
IP, DNS name) is preserved as-is.
To accept the container's request, bind the server on a reachable
interface and include host.docker.internal (or the chosen
remote name or IP) in --trusted-hosts. Include loopback
names too if local clients connect, and use an API key.
Gotchas
- Docker daemon required. OpenHands' sandbox runtime is a docker-in-docker sock passthrough — non-Docker CI skips the harness cleanly.
-
Pinned 0.9.0 matrix only: text-action format. That older OpenHands image parses
<execute_bash>/<execute_ipython>blocks out of plain text. This is why R1-Distill (which can't emit function calls) still passes OpenHands cells — it can emit text-action tags. The current 1.x CLI uses OpenAI-shaped tool calls. -
gpt-oss — XFAIL (upstream format gap). On the wire, rapid-mlx
applies user-supplied
stop=['</execute_ipython>', ...]only to the harmony final channel, so analysis-channel reasoning does not trigger a premature stop. The cell still XFAILs: gpt-oss's native harmony output never emits the<execute_bash>/<execute_ipython>text-action tags CodeActAgent parses — an upstream OpenHands parser gap tracked at OpenHands #15167, not a rapid-mlx bug. - Small local models struggle with the complex action format. 7B-and-below can loop; 12B-and-up (Gemma 4, Qwen 3.5 9B, Qwen 3.6 35B-A3B) hit the AST whitelist reliably.
Empirical
The 2026-07-07 pilot ran the harness across all four Tier-1
families with the pinned image pair. Family × wall-time (single
cell, cached-image path):
Qwen 3.5-4B-4bit 32.14 s (2 CodeAct steps: read →
edit_file_by_replace → finish),
Gemma-4-31B-4bit 47.87 s,
DeepSeek R1-Distill-32B-4bit 72.08 s (long analysis-channel CoT
before the edit action, still one-shot),
gpt-oss-20B-MXFP4-Q8 XFAIL (harmony output emits no
<execute_bash> text-action tag for CodeActAgent to
parse — upstream OpenHands
#15167,
see the gotcha above). The three tool-emitting families satisfied the
AST whitelist on add.py; gpt-oss stays strict-xfail.