OpenHands
Categories:
OpenHands deploys as separate components because each owns a distinct responsibility and trust boundary:
| Component | Responsibility |
|---|---|
| Agent Canvas | Browser client for conversations, files, settings, backends, automations |
| Agent Server | Runs conversations, agents, tools, and workspace operations |
| Automation Server | Stores schedules and triggers, tracks runs, dispatches conversations |
The agent server runs directly on the host with the service account’s permissions. Its workspace is a working directory; operating-system account permissions define its access to the host. Treat the agent server host as trusted infrastructure.
Layout
tf_setupcreates the Agent Canvas, secured agent server, and automation server VMs in thesrc_infra_openhandsXen Orchestra resource set.ansibledeploys the three VM components. The host-bot project owns and deploys its unsecured agent server.dnsconfig.jsonowns the component hostnames and static addresses.al.luaauthenticates with thesrc_infra_openhandsVault AppRole, signs host keys for Ansible, and injects the component secrets.
There is no tf package: these components keep their state in files and
SQLite on their own hosts, so no API-side resources exist to declare.
Canvas serves the packaged browser assets through its static-server service on loopback. Configure the browser’s backend with the canvas origin as its host and the agent server session key as its credential.
Traefik and TLS
Each component host runs Traefik from the shared role with certificates issued
by src_infra_openhands_pki_server. The canvas origin proxies /api/*,
/api/automation, /sockets, and /server_info to the agent server and
automation server, so the browser talks to one origin and issues no
cross-origin request.
The public canvas.openhands.alwaldend.com record is a CNAME to
ingress.alwaldend.com, and infra/ingress forwards it to
dc1.canvas.openhands.alwaldend.com. That ingress keeps its
RequireAndVerifyClientCert client-auth policy, so reaching canvas from
outside still requires a client certificate.
Secrets
agent_server holds session_api_key and secret_key; automation holds
kv_secret. The session API key is shared by the agent server, the automation
server, and the browser because all three authenticate the same
X-Session-API-Key value.
The unsecured host-bot server has a separate secret_key reference owned by
its host’s AL configuration.
Prepare one private JSON input for each path through the
secret-handling workflow.
Set OPENHANDS_AGENT_SERVER_JSON, OPENHANDS_AUTOMATION_JSON, and
OPENHANDS_HOST_BOT_JSON to those input file paths. Each JSON object contains
the fields listed above for its path. Initialize the paths through standard
input; -cas=0 prevents overwriting existing data:
bazel_agent bazel run //infra/openhands:vault.kv_put -- -cas=0 \
alwaldend.com/vault1/approles/src_infra_openhands/agent_server \
- < "$OPENHANDS_AGENT_SERVER_JSON"
bazel_agent bazel run //infra/openhands:vault.kv_put -- -cas=0 \
alwaldend.com/vault1/approles/src_infra_openhands/automation \
- < "$OPENHANDS_AUTOMATION_JSON"
bazel_agent bazel run //users/simeonwarren:vault.kv_put -- -cas=0 \
alwaldend.com/vault1/approles/user_simeonwarren/openhands \
- < "$OPENHANDS_HOST_BOT_JSON"
Deployment
bazel run //infra/openhands/tf_setup:tf_setup.plan
bazel run //infra/openhands/tf_setup:tf_setup.apply
bazel run //infra/openhands/ansible
The component AppRole must first exist in Vault. Follow the
XO authentication and resource-set workflow to
synchronize its OIDC identity and apply its resource-set assignment before
tf_setup runs. The assignment in
resource_set_inventory supplies the template,
storage, and network by name. Component secrets must be written to Vault
before Ansible runs. State and locking use the repository’s tf_backend
plugin, which stores them as Vault KV entries and creates the entries on first
use, so no separate state bucket is required.