Repository navigation
examples: OpenHands agent-server in any image, with OpenHands' CodeAct agent; ate-env manifest template --env - #93
Draft
Tomer Glottmann (tomergee) wants to merge 3 commits into
Conversation
A runtime layer's daemon is configured through its environment (the OpenHands agent-server reads its API key from OH_SESSION_API_KEYS_0), and the guest passes its environment to sidecars, but the template command had no way to set a variable. --env adds one per flag after PORT, in order. Names must be POSIX names; PORT is the guest's and cannot be overridden; a name given twice is an error. Templates derived per rootfs image keep them.
Tomer Glottmann (tomergee)
force-pushed
the
openhands-agent-server-example
branch
from
October 15, 2026 19:15
e4256b9 to
820eb99
Compare
…deAct agent The OpenHands agent-server mounted read-only into an unmodified image as a runtime layer, started by the guest as a sidecar, readiness gated on its /health, and reached from outside through ate-env-api's port proxy. The OpenHands SDK takes the proxy's path form (/v1alpha/envs/<env>/ports/<port>) as its workspace URL, so RemoteWorkspace and RemoteConversation, the agent loop with its event WebSocket, work unchanged. - Dockerfile: the layer, for amd64 and arm64. The published agent-server binary is a PyInstaller build that needs glibc 2.36 and fails on older distributions (Ubuntu 22.04 included), also through its own image's loader, so the layer is python-build-standalone CPython (checksum-pinned) with openhands-agent-server installed by pip on Debian bullseye: wheels for glibc 2.31 and newer, nothing used from the image but the kernel and /bin/sh. - demo.sh: registers a base template and drives the agent-server in one environment per image over HTTP; PORT, AGENT_SERVER_ENV, COMMAND, WORKDIR and KEEP make it general. - ate_env_workspace.py: workspace_for(env), a RemoteWorkspace on the path form. - codeact_agent.py: runs OpenHands' default (CodeAct) agent in an environment with any litellm model, or --scripted with scripted_llm.py, a stand-in model that replays a fixed trajectory through the real agent loop and tools when no model is reachable from the actor. - README: build, register, use, the glibc finding, live results, limits.
Tomer Glottmann (tomergee)
force-pushed
the
openhands-agent-server-example
branch
from
October 15, 2026 19:15
820eb99 to
de87677
Compare
…tside run_trajectory.py connects to an environment through ate-env-api and runs a CodeAct trajectory there, one tool call at a time, in OpenHands' vocabulary: terminal commands through the agent-server's command endpoint, file edits through OpenHands' own file_editor, served inside the environment by oh_tool.py on the layer's Python and reached through the port proxy's path form. Observations print and, with --out, are recorded next to their calls. Nothing inside the environment calls a model, so this is also the shape of a client-side agent: the model wherever it is reachable, the environment only executing. oh_tool.py is one long-lived server, so OpenHands' imports happen once (file edits 0.3 s instead of 9 s cold) and undo_edit sees earlier edits. It listens on all interfaces: the router delivers to the actor's address, and a loopback-only listener answers 502, which RUNTIMES.md and the README now say for any runtime. trajectories/: hello.json for any image; django-11099.json fixes SWE-bench django__django-11099 and django-11099-grade.json applies the instance's hidden test change and runs its target tests with and without the fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #92 (base branch
runtime-port-endpoints); retarget tomainonce #92 lands. Draft until then.A working example of a real daemon behind #92: the OpenHands agent-server in any unmodified image, driven from outside the cluster: commands and files, OpenHands' own CodeAct agent, and CodeAct trajectories executed step by step from a client. Details in
examples/openhands-agent-server/README.md.What
ate-env manifest template --env NAME=VALUE(repeatable). Daemons in layers are configured through the environment (the agent-server reads its API key fromOH_SESSION_API_KEYS_0), and the guest passes its environment to sidecars, but the template command could not set a variable. POSIX names only,PORTreserved for the guest, duplicates rejected; derived per-image templates keep them. Tests included.examples/openhands-agent-server/:Dockerfile: the agent-server as a runtime layer, amd64 and arm64 (see the finding below).demo.sh: registers a base template (layer at/opt/oh, sidecarpython -m openhands.agent_server, readiness on/health, key via--env) and drives the agent-server in one environment per image over HTTP.PORT,AGENT_SERVER_ENV,COMMAND,WORKDIR,KEEPmake it general.ate_env_workspace.py:workspace_for(env), an OpenHandsRemoteWorkspacewhose host is Reach daemons inside an environment: ate-env-api port proxy, NeMo Gym endpoint() #92's path form,/v1alpha/envs/<env>/ports/<port>. The SDK appends its/api/...paths and builds its event WebSocket from the same host, soRemoteWorkspaceandRemoteConversationwork unchanged.codeact_agent.py: runs OpenHands' default agent (terminal, file editor, task tracker, finish; the successor of CodeActAgent) on the agent-server inside an environment, with any litellm model, or--scripted.scripted_llm.py: a stand-in model (standard library, OpenAI-compatible) that replays a fixed CodeAct trajectory through the real agent loop and tools, for runs where no model is reachable from the actor.run_trajectory.py+oh_tool.py: execute a CodeAct trajectory (terminal/file_editor/finishcalls) in an environment from outside. Terminal commands go through the agent-server's command endpoint; file edits through OpenHands' ownfile_editor, served inside the environment byoh_tool.pyon the layer's Python and reached through the path form. Observations are recorded next to their calls (--out). No model inside the environment: with a model call before each step it is a client-side agent, which needs no actor egress.trajectories/:hello.jsonfor any image;django-11099.json(a fix for SWE-benchdjango__django-11099) anddjango-11099-grade.json(applies the instance's hidden test change and runs its target tests with and without the fix).docs/rootfs-images/RUNTIMES.mdlinks the example and lists--envin the layer model.Finding: the published agent-server binary does not run on older distributions
ghcr.io/openhands/agent-server:1.55.0-*ships a PyInstaller binary built on Debian bookworm (glibc 2.36). On Ubuntu 22.04 (glibc 2.35), the base of many coding-task images:Ubuntu 20.04 fails the same way on
GLIBC_2.33. Starting it through the image's own bookworm loader fails too (PyInstaller re-executes itself:Floating point exceptionon 22.04, anld.soassertion on 20.04). The layer is python-build-standalone CPython 3.12.15 (built for glibc 2.17, relocatable, checksum-pinned per architecture) withopenhands-agent-server==1.55.0installed by pip on Debian bullseye: every wheel runs on glibc 2.31 and newer, and nothing from the image is used but the kernel and/bin/sh. About 210 MB compressed.Verification
go test ./cmd/ate-envok (new--envtests);make verify-gofmt verify-boilerplatepass; ruff and shellcheck clean on the example.Live on GKE (gVisor workers, Substrate from 2026-10-12, ate-env-api from Reach daemons inside an environment: ate-env-api port proxy, NeMo Gym endpoint() #92 at
593f7e6), agent-server answering after create:ubuntu:20.04swebench/sweb.eval.x86_64.django_1776_django-11099namanjain12/pandas_final(R2E-Gym)node:22-slimpython:3.12-slimdemo.shon each: commands ran with the image's own shell and tools, file download worked, calls without the key got 401; also withPORT=3000and an extra--envsetting.CodeAct through ate-env:
codeact_agent.py --scriptedin the SWE-bench environment ran the full OpenHands agent loop on the in-sandbox agent-server (terminal, file editor create, terminal check, finish) in 2.0 s, with its event WebSocket going through ate-env-api's path form and the Substrate router. A direct WebSocket handshake through the same path returned 101 and the agent-server's own close frame.Trajectory from outside:
run_trajectory.pywithdjango-11099.jsonin the SWE-bench environment fixed the issue in 9 steps and 5.2 s (reproduction flips from "accepts" to "rejects a trailing newline", 22 validator tests OK, two-line diff); file edits 0.3 s each with the long-lived tool runner (9 s per edit when each started a fresh process).django-11099-grade.json: target testsOKwith the fix,FAILED (failures=2)with it stashed.hello.jsonran on the same image.ate_env_workspace.pyonnode:22-slimsaw the image's ownnode v22; on the SWE-bench image the image's Python 3.11.5, not the layer's. After a suspend, the next SDK call woke the environment within 9 s.The arm64 layer answered
/healthin an arm64 Ubuntu 22.04 container (local); the CodeAct flow also ran locally against the layer before the cluster run.Actors on the test cluster had no internet egress (connection reset), hence
--scriptedfor the live CodeAct run. Environments, templates and the API image were reverted after the runs.Limits (also in the README)
codeact_agent.pycalls the model from inside the environment: it must be allowed by Substrate's egress policy or served in-cluster.run_trajectory.py(and a client-side agent built on it) does not.502 bad gatewayfrom the router (found withoh_tool.py; now in RUNTIMES.md).