mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com> Co-authored-by: Mislav Lukach <mislavlukach@gmail.com> Co-authored-by: Hiep Le <69354317+hieptl@users.noreply.github.com> Co-authored-by: hieptl <hieptl.developer@gmail.com> Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: chuckbutkus <chuck@all-hands.dev> Co-authored-by: Ray Myers <ray.myers@gmail.com> Co-authored-by: Xingyao Wang <xingyao@all-hands.dev> Co-authored-by: Tim O'Farrell <tofarr@gmail.com> Co-authored-by: tksrmz <38581613+tksrmz@users.noreply.github.com> Co-authored-by: Kaushik Ashodiya <kashodiya@gmail.com> Co-authored-by: Eliot Jones <eliot.k.jones@gmail.com> Co-authored-by: Engel Nyst <enyst@users.noreply.github.com> Co-authored-by: Boxuan Li <liboxuan@connect.hku.hk> Co-authored-by: mamoodi <mamoodiha@gmail.com> Co-authored-by: Robert Brennan <accounts@rbren.io> Co-authored-by: Alona <alona@all-hands.dev> Co-authored-by: Graham Neubig <neubig@gmail.com> Co-authored-by: enyst <engel.nyst@gmail.com> Co-authored-by: juanmichelini <juan@juan.com.uy> Co-authored-by: Xinyi He <52363993+Betty1202@users.noreply.github.com> Co-authored-by: BenYao21 <cyao22@asu.edu> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tejas Goyal <83608316+tejas-goyal@users.noreply.github.com> Co-authored-by: Tejas Goyal <tejas@Tejass-MacBook-Pro.local>
171 lines
9.6 KiB
Plaintext
171 lines
9.6 KiB
Plaintext
---
|
|
title: Runtime Architecture
|
|
---
|
|
|
|
The OpenHands Docker Runtime is the core component that enables secure and flexible execution of AI agent's action.
|
|
It creates a sandboxed environment using Docker, where arbitrary code can be run safely without risking the host system.
|
|
|
|
## Why do we need a sandboxed runtime?
|
|
|
|
OpenHands needs to execute arbitrary code in a secure, isolated environment for several reasons:
|
|
|
|
1. Security: Executing untrusted code can pose significant risks to the host system. A sandboxed environment prevents malicious code from accessing or modifying the host system's resources
|
|
2. Consistency: A sandboxed environment ensures that code execution is consistent across different machines and setups, eliminating "it works on my machine" issues
|
|
3. Resource Control: Sandboxing allows for better control over resource allocation and usage, preventing runaway processes from affecting the host system
|
|
4. Isolation: Different projects or users can work in isolated environments without interfering with each other or the host system
|
|
5. Reproducibility: Sandboxed environments make it easier to reproduce bugs and issues, as the execution environment is consistent and controllable
|
|
|
|
## How does the Runtime work?
|
|
|
|
The OpenHands Runtime system uses a client-server architecture implemented with Docker containers. Here's an overview of how it works:
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[User-provided Custom Docker Image] --> B[OpenHands Backend]
|
|
B -->|Builds| C[OH Runtime Image]
|
|
C -->|Launches| D[Action Executor]
|
|
D -->|Initializes| E[Browser]
|
|
D -->|Initializes| F[Bash Shell]
|
|
D -->|Initializes| G[Plugins]
|
|
G -->|Initializes| L[Jupyter Server]
|
|
|
|
B -->|Spawn| H[Agent]
|
|
B -->|Spawn| I[EventStream]
|
|
I <--->|Execute Action to
|
|
Get Observation
|
|
via REST API
|
|
| D
|
|
|
|
H -->|Generate Action| I
|
|
I -->|Obtain Observation| H
|
|
|
|
subgraph "Docker Container"
|
|
D
|
|
E
|
|
F
|
|
G
|
|
L
|
|
end
|
|
```
|
|
|
|
1. User Input: The user provides a custom base Docker image
|
|
2. Image Building: OpenHands builds a new Docker image (the "OH runtime image") based on the user-provided image. This new image includes OpenHands-specific code, primarily the "runtime client"
|
|
3. Container Launch: When OpenHands starts, it launches a Docker container using the OH runtime image
|
|
4. Action Execution Server Initialization: The action execution server initializes an `ActionExecutor` inside the container, setting up necessary components like a bash shell and loading any specified plugins
|
|
5. Communication: The OpenHands backend (client: `openhands/runtime/impl/action_execution/action_execution_client.py`; runtimes: `openhands/runtime/impl/docker/docker_runtime.py`, `openhands/runtime/impl/local/local_runtime.py`) communicates with the action execution server over RESTful API, sending actions and receiving observations
|
|
6. Action Execution: The runtime client receives actions from the backend, executes them in the sandboxed environment, and sends back observations
|
|
7. Observation Return: The action execution server sends execution results back to the OpenHands backend as observations
|
|
|
|
The role of the client:
|
|
|
|
- It acts as an intermediary between the OpenHands backend and the sandboxed environment
|
|
- It executes various types of actions (shell commands, file operations, Python code, etc.) safely within the container
|
|
- It manages the state of the sandboxed environment, including the current working directory and loaded plugins
|
|
- It formats and returns observations to the backend, ensuring a consistent interface for processing results
|
|
|
|
## How OpenHands builds and maintains OH Runtime images
|
|
|
|
OpenHands' approach to building and managing runtime images ensures efficiency, consistency, and flexibility in creating and maintaining Docker images for both production and development environments.
|
|
|
|
Check out the [relevant code](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/runtime/utils/runtime_build.py) if you are interested in more details.
|
|
|
|
### Image Tagging System
|
|
|
|
OpenHands uses a three-tag system for its runtime images to balance reproducibility with flexibility.
|
|
The tags are:
|
|
|
|
- **Versioned Tag**: `oh_v{openhands_version}_{base_image}` (e.g.: `oh_v0.9.9_nikolaik_s_python-nodejs_t_python3.12-nodejs22`)
|
|
- **Lock Tag**: `oh_v{openhands_version}_{16_digit_lock_hash}` (e.g.: `oh_v0.9.9_1234567890abcdef`)
|
|
- **Source Tag**: `oh_v{openhands_version}_{16_digit_lock_hash}_{16_digit_source_hash}`
|
|
(e.g.: `oh_v0.9.9_1234567890abcdef_1234567890abcdef`)
|
|
|
|
#### Source Tag - Most Specific
|
|
|
|
This is the first 16 digits of the MD5 of the directory hash for the source directory. This gives a hash
|
|
for only the openhands source
|
|
|
|
#### Lock Tag
|
|
|
|
This hash is built from the first 16 digits of the MD5 of:
|
|
|
|
- The name of the base image upon which the image was built (e.g.: `nikolaik/python-nodejs:python3.12-nodejs22`)
|
|
- The content of the `pyproject.toml` included in the image.
|
|
- The content of the `poetry.lock` included in the image.
|
|
|
|
This effectively gives a hash for the dependencies of Openhands independent of the source code.
|
|
|
|
#### Versioned Tag - Most Generic
|
|
|
|
This tag is a concatenation of openhands version and the base image name (transformed to fit in tag standard).
|
|
|
|
#### Build Process
|
|
|
|
When generating an image...
|
|
|
|
- **No re-build**: OpenHands first checks whether an image with the same **most specific source tag** exists. If there is such an image,
|
|
no build is performed - the existing image is used.
|
|
- **Fastest re-build**: OpenHands next checks whether an image with the **generic lock tag** exists. If there is such an image,
|
|
OpenHands builds a new image based upon it, bypassing all installation steps (like `poetry install` and
|
|
`apt-get`) except a final operation to copy the current source code. The new image is tagged with a
|
|
**source** tag only.
|
|
- **Ok-ish re-build**: If neither a **source** nor **lock** tag exists, an image will be built based upon the **versioned** tag image.
|
|
In versioned tag image, most dependencies should already been installed hence saving time.
|
|
- **Slowest re-build**: If all of the three tags don't exists, a brand new image is built based upon the base
|
|
image (Which is a slower operation). This new image is tagged with all the **source**, **lock**, and **versioned** tags.
|
|
|
|
This tagging approach allows OpenHands to efficiently manage both development and production environments.
|
|
|
|
1. Identical source code and Dockerfile always produce the same image (via hash-based tags)
|
|
2. The system can quickly rebuild images when minor changes occur (by leveraging recent compatible images)
|
|
3. The **lock** tag (e.g., `runtime:oh_v0.9.3_1234567890abcdef`) always points to the latest build for a particular base image, dependency, and OpenHands version combination
|
|
|
|
## Volume mounts: named volumes and overlay
|
|
|
|
OpenHands supports both bind mounts and Docker named volumes in SandboxConfig.volumes:
|
|
|
|
- Bind mount: "/abs/host/path:/container/path[:mode]"
|
|
- Named volume: "volume:`<name>`:/container/path[:mode]" or any non-absolute host spec treated as a named volume
|
|
|
|
Overlay mode (copy-on-write layer) is supported for bind mounts by appending ":overlay" to the mode (e.g., ":ro,overlay").
|
|
To enable overlay COW, set SANDBOX_VOLUME_OVERLAYS to a writable host directory; per-container upper/work dirs are created under it. If SANDBOX_VOLUME_OVERLAYS is unset, overlay mounts are skipped.
|
|
|
|
Implementation references:
|
|
- openhands/runtime/impl/docker/docker_runtime.py (named volumes in _build_docker_run_args; overlay mounts in _process_overlay_mounts)
|
|
- openhands/core/config/sandbox_config.py (volumes field)
|
|
|
|
|
|
## Runtime Plugin System
|
|
|
|
The OpenHands Runtime supports a plugin system that allows for extending functionality and customizing the runtime environment. Plugins are initialized when the action execution server starts up inside the runtime.
|
|
|
|
## Ports and URLs
|
|
|
|
- Host port allocation uses file-locked ranges for stability and concurrency:
|
|
- Main runtime port: find_available_port_with_lock on configured range
|
|
- VSCode port: SandboxConfig.sandbox.vscode_port if provided, else find_available_port_with_lock in VSCODE_PORT_RANGE
|
|
- App ports: two additional ranges for plugin/web apps
|
|
- DOCKER_HOST_ADDR (if set) adjusts how URLs are formed for LocalRuntime/Docker environments.
|
|
- VSCode URL is exposed with a connection token from the action execution server endpoint /vscode/connection_token and rendered as:
|
|
- Docker/Local: http://localhost:{port}/?tkn={token}&folder={workspace_mount_path_in_sandbox}
|
|
- RemoteRuntime: scheme://vscode-{host}/?tkn={token}&folder={workspace_mount_path_in_sandbox}
|
|
|
|
References:
|
|
- openhands/runtime/impl/docker/docker_runtime.py (port ranges, locking, DOCKER_HOST_ADDR, vscode_url)
|
|
- openhands/runtime/impl/local/local_runtime.py (vscode_url factory)
|
|
- openhands/runtime/impl/remote/remote_runtime.py (vscode_url mapping)
|
|
- openhands/runtime/action_execution_server.py (/vscode/connection_token)
|
|
|
|
|
|
Examples:
|
|
- Jupyter: openhands/runtime/plugins/jupyter/__init__.py (JupyterPlugin, Kernel Gateway)
|
|
- VS Code: openhands/runtime/plugins/vscode/* (VSCodePlugin, exposes tokenized URL)
|
|
- Agent Skills: openhands/runtime/plugins/agent_skills/*
|
|
|
|
Key aspects of the plugin system:
|
|
|
|
1. Plugin Definition: Plugins are defined as Python classes that inherit from a base `Plugin` class
|
|
2. Plugin Registration: Available plugins are registered in `openhands/runtime/plugins/__init__.py` via `ALL_PLUGINS`
|
|
3. Plugin Specification: Plugins are associated with `Agent.sandbox_plugins: list[PluginRequirement]`. Users can specify which plugins to load when initializing the runtime
|
|
4. Initialization: Plugins are initialized asynchronously when the runtime starts and are accessible to actions
|
|
5. Usage: Plugins extend capabilities (e.g., Jupyter for IPython cells); the server exposes any web endpoints (ports) via host port mapping
|