--- name: runtime-api-configs description: >- Manage warm sandbox pools on OpenHands Enterprise by driving the runtime-api admin endpoints (list, save, delete warm runtime configurations) from a Python CLI. Use when an OpenHands Enterprise administrator asks to add, update, delete, or inspect custom sandbox images, warm pools, or `warm-runtime-configs`, or when the docs point to `enterprise/custom-sandbox-images/multiple-images-warm-pools`. triggers: - warm-runtime-config - warm runtime config - warm pool - warm-runtime-configs - custom sandbox image - runtime-api admin --- # Runtime API - Warm Runtime Configurations This plugin manages the warm sandbox pools used by OpenHands Enterprise. It replaces the ad-hoc `warm-runtime-configs.sh` shell script embedded in the docs and gives administrators a single, testable Python CLI for the same workflow. The CLI talks HTTP to the runtime-api service. It never modifies the cluster directly, and it uses only the Python standard library, so it runs anywhere `python3` is available. ## When to use this skill Reach for this skill whenever the user wants to: - **List** the effective warm-runtime configurations on an OHE install. - **Add or update** a custom sandbox image (e.g. `openhands-php:8.4-v1`) so users can select it from the **Settings → Application → Default Sandbox** dropdown. - **Delete** a database-managed configuration, restoring an installer-managed entry with the same name. - **Refresh configurations after upgrading OHE**, when API-managed frozen snapshots need to be re-derived from the new installer template. - **Bootstrap credentials** (`RUNTIME_API_URL`, `API_KEY`, `ADMIN_PASSWORD`) from Kubernetes secrets so subsequent runs need no cluster access. If the user is instead asking how to **build** a custom sandbox image (Dockerfile, versioning), point them at [Building a Custom Image](https://docs.openhands.dev/enterprise/custom-sandbox-images/building-custom-images) - image building is out of scope for this skill. ## Prerequisites - OpenHands Enterprise **0.64.0 or later**. - Python **3.9+** on the machine running the CLI. No third-party packages. - The **Runtime API Admin Password**: - **VM (Replicated) installs**: set via *Admin Console → Config → Sandbox Configuration → Runtime API Admin Password*. - **Helm installs**: the value stored in the `admin-password` Kubernetes secret in the runtime-api namespace. Helm has no Admin Console. - **Helm installs only**: the runtime-api chart must be running in **overlay mode** and have warm pools enabled, so `v1_current` shows up in `list` and can be used as a `template` source. Set both in your umbrella values: ```yaml runtime-api: warmRuntimes: enabled: true env: WARM_RUNTIME_CONFIG_OVERLAY: "1" ``` Overlay mode is on by default on Replicated VM installs. Without it on Helm, `list` returns `[]` on a fresh install and `template v1_current` fails. ## Setup On any machine with HTTPS reachability to the runtime-api, export two env vars: ```bash export RUNTIME_API_URL=https://runtime-api. export ADMIN_PASSWORD= # see Prerequisites above ``` That is the full setup on any install where the runtime-api ingress is reachable from the machine running the CLI. No `kubectl`, no SSH, no cluster access. If the runtime-api is cluster-internal (Helm default), see the Helm entry in the Advanced section below to port-forward first. Under the hood, the CLI uses the admin password directly for `save` and `delete` (via the PBKDF2 handshake), and for `list` and `template` it logs in as admin and fetches the `default` read-only API key over HTTPS from `/api/admin/api-keys`. If you already know the read-only API key (for example, from an operator runbook), export it too and the CLI will skip the extra admin-login round trip on reads: ```bash export API_KEY= ``` If you have `kubectl` access to the cluster and prefer to pull all three env vars from Kubernetes secrets in one step, see **[Advanced: bootstrap from Kubernetes](#advanced-bootstrap-from-kubernetes)** at the bottom of this file. ## CLI reference Run with `python3 scripts/warm_runtime_configs.py `. Each subcommand exits non-zero on error and prints the runtime-api's HTTP body on failure. | Subcommand | Auth | Purpose | |---|---|---| | `list` | `API_KEY` if set, else admin login | Print the effective set of configurations. | | `template ` | `API_KEY` if set, else admin login | Fetch a config, strip identity fields, override image/count, print JSON ready to `save`. | | `save [--file F]` | Admin password | Upsert a configuration. Reads JSON from `--file` or stdin. | | `delete ` | Admin password | Delete a database-managed configuration. | | `bootstrap` | kubectl | Print `export` lines for `RUNTIME_API_URL`, `API_KEY`, `ADMIN_PASSWORD`. Optional; see [Advanced](#advanced-bootstrap-from-kubernetes) below. | Full help: `python3 scripts/warm_runtime_configs.py --help` and `python3 scripts/warm_runtime_configs.py --help`. ## Typical workflow Register a new PHP sandbox image once credentials are in the environment: ```bash # 1. Derive a config JSON from the current default, changing only the image python3 scripts/warm_runtime_configs.py template v1_current \ --image ghcr.io/your-org/openhands-php:8.4-v1 --count 1 \ --output php-web.json # 2. Save it python3 scripts/warm_runtime_configs.py save php-web --file php-web.json # 3. Confirm python3 scripts/warm_runtime_configs.py list ``` Within about a minute the pool is ready and `php-web` appears in **Settings → Application → Default Sandbox**. Deleting an API-managed configuration removes it from the effective set: ```bash python3 scripts/warm_runtime_configs.py delete php-web ``` `delete` operates on database rows only, so it 404s for names that have never been saved through this CLI (installer-managed entries like `v1_current` on a fresh install fall in that bucket). In overlay mode, if you had previously saved a database row that shadowed a same-named ConfigMap entry, deleting the row reverts to the ConfigMap entry on the next reconciler cycle. ## After an OHE upgrade Frozen API-managed configurations do not follow release bumps. After every OHE upgrade, re-derive each API-managed configuration from the refreshed default template, keeping each configuration's current image tag and pool size unless you deliberately change them: ```bash # One "name image count" tuple per API-managed config; edit for your fleet. # Preserving the existing pool count matters: hardcoding it would silently # shrink pools on every refresh. while read -r name image count; do [ -z "$name" ] && continue python3 scripts/warm_runtime_configs.py template v1_current \ --image "$image" --count "$count" \ | python3 scripts/warm_runtime_configs.py save "$name" --file - done <<'EOF' php-web ghcr.io/your-org/openhands-php:8.4-v2 3 ruby-app ghcr.io/your-org/openhands-ruby:3.3-v2 2 node-monorepo ghcr.io/your-org/openhands-node:24-v2 1 EOF ``` Two caveats worth spelling out: - `template` refreshes the *command* and *environment* to match the new `v1_current`, but it keeps whatever image tag you pass. To actually pick up a new agent-server version, the image itself must be rebuilt against the matching agent-server base. This loop alone does not do that. - Skipping this step leaves configurations pointing at the previous agent-server command/environment, and new conversations fail with a version mismatch until the configurations are updated. ## Environment variables | Variable | Used by | Notes | |---|---|---| | `RUNTIME_API_URL` | all HTTP subcommands | e.g. `https://runtime-api.example.com` or `http://localhost:5000`. | | `ADMIN_PASSWORD` | `save`, `delete`; also `list`/`template` when `API_KEY` is unset | Runs the PBKDF2 challenge-response handshake. | | `ADMIN_TOKEN` | Anywhere `ADMIN_PASSWORD` is used | Pre-obtained admin JWT. When set, it wins over `ADMIN_PASSWORD` and skips the handshake. | | `API_KEY` | `list`, `template` (optional) | Sent as `X-API-Key`. Optional: when unset, the CLI fetches the `default` key over HTTPS via admin login. | | `NAMESPACE` | `bootstrap` | Defaults to `openhands`. Overridable via `--namespace`. | ## Advanced: bootstrap from Kubernetes `bootstrap` extracts all three env vars from Kubernetes secrets in one step. Handy for cluster operators and CI, but the default HTTPS-only workflow above is preferred for interactive admin use. ### VM (Replicated) install The k0s kubeconfig is root-owned, so the invocation runs under sudo: ```bash eval "$(sudo -E python3 scripts/warm_runtime_configs.py bootstrap --namespace openhands)" ``` ### Helm install The runtime-api is cluster-internal on Helm (chart default is `ingress.enabled: false`). The Kubernetes service name is release-prefixed via `include "runtime-api.fullname"`, so it is not literally `runtime-api` - discover it and port-forward, then bootstrap without touching `RUNTIME_API_URL`: ```bash SVC=$(kubectl -n openhands get svc \ -l app.kubernetes.io/name=runtime-api \ -o jsonpath='{.items[0].metadata.name}') kubectl -n openhands port-forward "svc/$SVC" 5000:5000 & export RUNTIME_API_URL=http://localhost:5000 eval "$(python3 scripts/warm_runtime_configs.py bootstrap --namespace openhands --skip-url)" ``` `--skip-url` leaves `RUNTIME_API_URL` alone. If the operator set `nameOverride` on the runtime-api subchart, adjust the label selector to match. ## See also - Docs: [Configuring Custom Sandbox Images](https://docs.openhands.dev/enterprise/custom-sandbox-images/multiple-images-warm-pools) - Docs: [Using Custom Images](https://docs.openhands.dev/enterprise/custom-sandbox-images/using-custom-images)