Jesse A. Green / notebook

OpenHands Administrator & Developer Guide

Generated May 2026 from OpenHands/OpenHands at c228f80

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

OpenHands (formerly OpenDevin) is an autonomous AI software development agent platform. Agents write code, execute terminal commands, browse the web, and interact with APIs inside sandboxed runtime environments. This guide covers architecture, deployment, configuration, security, and developer internals for administrators and contributors operating OpenHands at any scale.


1. Architecture Overview

1.1 System Architecture

                        ┌──────────────────────────────────┐
                        │          Frontend (React)         │
                        │   Vite 7 / React 19 / Zustand 5  │
                        │        React Router 7 / i18n      │
                        └──────────────┬───────────────────┘
                                       │ WebSocket / REST
                        ┌──────────────▼───────────────────┐
                        │        Application Server         │
                        │  ┌─────────┐  ┌────────────────┐ │
                        │  │ V1 App  │  │ V0 Legacy API  │ │
                        │  │ Server  │  │   (FastAPI)    │ │
                        │  └────┬────┘  └───────┬────────┘ │
                        │       │               │          │
                        │  ┌────▼───────────────▼────────┐ │
                        │  │     Agent Controller         │ │
                        │  │  (execution loop, state)     │ │
                        │  └────────────┬─────────────────┘ │
                        │               │                   │
                        │  ┌────────────▼─────────────────┐ │
                        │  │        Event Stream           │ │
                        │  │  (append-only, subscribers)   │ │
                        │  └────────────┬─────────────────┘ │
                        └───────────────┼───────────────────┘
                                        │
              ┌─────────────────────────┼─────────────────────────┐
              │                         │                         │
    ┌─────────▼──────────┐  ┌───────────▼──────────┐  ┌──────────▼─────────┐
    │    LLM Layer       │  │     Runtime Layer     │  │   Storage Layer    │
    │  litellm (100+     │  │  ┌─────────────────┐  │  │  Local / S3 / GCS  │
    │  providers)        │  │  │ Docker Runtime   │  │  │  Memory / Webhook  │
    │  LLM / AsyncLLM /  │  │  │ K8s Runtime     │  │  └────────────────────┘
    │  StreamingLLM      │  │  │ Local Runtime   │  │
    │  LLMRegistry       │  │  │ CLI Runtime     │  │
    └────────────────────┘  │  │ Remote Runtime  │  │
                            │  └────────┬────────┘  │
                            │           │           │
                            │  ┌────────▼────────┐  │
                            │  │   Sandboxed      │  │
                            │  │   Container      │  │
                            │  │  (code exec,     │  │
                            │  │   browser, tools) │  │
                            │  └─────────────────┘  │
                            └───────────────────────┘

1.2 Component Inventory

Component Path Role
Core Config openhands/core/config/ TOML config parsing (14 files, 106K chars)
Agent Controller openhands/controller/ Agent execution loop, state management
Runtime openhands/runtime/ Sandbox execution (Docker, K8s, Local, CLI, Remote)
LLM Layer openhands/llm/ LLM integration via litellm (LLM, AsyncLLM, StreamingLLM)
Events openhands/events/ Event system (Event, EventStream, EventFilter)
MCP openhands/mcp/ Model Context Protocol client
Storage openhands/storage/ File stores (Local, S3, Google Cloud, Memory, Webhook)
V0 Server (Legacy) openhands/server/ Legacy FastAPI server (deprecated v1.0.0)
V1 App Server openhands/app_server/ New application server
Frontend frontend/ React/TypeScript UI (Vite, React Router, Zustand)
Enterprise enterprise/ SaaS features (auth, billing, orgs)
UI Components openhands-ui/ Shared React component library (@openhands/ui)
Containers containers/ Docker build configs
Kind kind/ Local Kubernetes development

1.3 Technology Stack

Layer Technologies
Backend Python 3.12+, FastAPI, uvicorn, Pydantic, SQLAlchemy (enterprise)
Frontend React 19, TypeScript 5.9, Vite 7, React Router 7, Zustand 5, TailwindCSS 4, i18next
LLM litellm (supports 100+ LLM providers)
Container Docker, Kubernetes (KIND for local dev)
Storage Local filesystem, S3, Google Cloud Storage
Enterprise Keycloak (auth), Stripe (billing), PostgreSQL, Redis, Alembic (migrations)
Testing pytest (backend), Vitest (frontend), Playwright (e2e)
Build Poetry (Python), npm (frontend)

1.4 V0 vs V1 Architecture

OpenHands is undergoing a major architectural migration from V0 to V1 (Software Agent SDK).

Aspect V0 (Legacy) V1 (Current)
Server openhands/server/ (FastAPI) openhands/app_server/
Feature flag Default ENABLE_V1=true
Status Deprecated as of v1.0.0 Active development
Resolvers Monolithic ENABLE_V1_GITHUB_RESOLVER, ENABLE_V1_SLACK_RESOLVER, ENABLE_V1_GITLAB_RESOLVER

Important

The V0 server in openhands/server/ is deprecated as of v1.0.0. New deployments should use the V1 app server with ENABLE_V1=true. The V0 code path remains for backward compatibility but receives no new features.


2. System Requirements

2.1 Hardware Requirements

Deployment CPU Memory Storage Notes
Development (single agent) 2+ cores 4 GB 10 GB Docker Desktop required
Small team (1-5 concurrent) 4 cores 16 GB 50 GB SSD recommended
Production (10+ concurrent) 8+ cores 32+ GB 100+ GB Kubernetes recommended
Enterprise 16+ cores 64+ GB 500+ GB Multi-node K8s cluster

2.2 Software Dependencies

Dependency Version Purpose
Python 3.12+ Backend runtime
Docker 20.10+ Container runtime for sandboxes
Node.js 18+ Frontend build
Poetry Latest Python dependency management
Git 2.x Version control, repo cloning

2.3 Runtime Resource Limits

The runtime sandbox enforces configurable resource constraints:

  • Memory: Controlled by RUNTIME_MAX_MEMORY_GB (no default limit unless set)
  • Memory monitoring: Enable with RUNTIME_MEMORY_MONITOR=true
  • CPU: Kubernetes deployments use resource_cpu_request (default: 1)
  • Storage: Kubernetes PVC default 2Gi, configurable via pvc_storage_size

Note

Each agent session spawns its own sandboxed container. Plan capacity for N concurrent sessions * per-session resource limits. The SANDBOX_BASE_CONTAINER_IMAGE defaults to the official OpenHands runtime image from ghcr.io/openhands/runtime.


3. Configuration Reference

3.1 Configuration System

OpenHands uses TOML-based configuration with 14 config files in openhands/core/config/. Configuration is loaded in this precedence order:

  1. Environment variables (highest priority)
  2. config.toml file
  3. Default values (lowest priority)

The config system uses Pydantic models for validation. Key config classes:

Class File Purpose
AppConfig app_config.py Top-level application config
LLMConfig llm_config.py LLM provider settings
AgentConfig agent_config.py Agent behavior settings
SandboxConfig sandbox_config.py Sandbox/container settings
SecurityConfig security_config.py Security policies
MCPConfig mcp_config.py Model Context Protocol
CondenserConfig condenser_config.py Context condensation strategies

3.2 Core / Server Variables

Variable Default Description
LOG_LEVEL INFO Logging verbosity (DEBUG, INFO, WARNING, ERROR)
DEBUG false Enable debug mode
DEBUG_LLM false Log raw LLM request/response payloads
LOG_JSON false Output structured JSON logs
LOG_TO_FILE false Write logs to file
LOG_ALL_EVENTS false Log every event in the event stream
DEBUG_RUNTIME false Verbose runtime container logging
SERVE_FRONTEND true Serve the React frontend from the backend
port 3000 Server listen port
OH_PERSISTENCE_DIR ~/.openhands Persistent data directory
FILE_STORE_PATH — Override file store root path
JWT_SECRET — Secret for JWT token signing
ENABLE_V1 false Enable V1 app server
REDIS_HOST — Redis server hostname
REDIS_PASSWORD — Redis authentication password
REDIS_PORT 6379 Redis server port
REDIS_DB 0 Redis database number

3.3 LLM Variables

Variable Default Description
OPENHANDS_FORCE_VISION — Force vision/multimodal mode
OPENHANDS_PROVIDER_BASE_URL — Override LLM provider base URL
LLM_BASE_URL — Alternative LLM base URL override

LLM retry configuration (set in config.toml under [llm]):

[llm]
model = "claude-sonnet-4-20250514"
api_key = "sk-..."
num_retries = 8
retry_min_wait = 15
retry_max_wait = 120
retry_multiplier = 2

Warning

Issue #14028 documents a known bug where Gemini model base URLs get doubled, causing 404 errors. If using Google Gemini models, verify your base_url does not include the /v1 suffix — litellm appends it automatically.

3.4 Runtime Variables

Variable Default Description
RUNTIME docker Runtime type: docker, kubernetes, local, cli, remote
SESSION_API_KEY — API key for runtime session auth
RUNTIME_MAX_MEMORY_GB — Max memory per sandbox container (GB)
RUNTIME_ID — Explicit runtime instance identifier
NO_CHANGE_TIMEOUT_SECONDS 10 Seconds to wait when no output changes
INIT_PLUGIN_TIMEOUT 120 Plugin initialization timeout (seconds)
RUNTIME_MEMORY_MONITOR false Enable memory usage monitoring
DISABLE_VSCODE_PLUGIN false Disable VS Code server in sandbox
SANDBOX_HOST_PORT — Host port binding for sandbox
SANDBOX_CONTAINER_URL_PATTERN — URL pattern for sandbox containers
SANDBOX_STARTUP_GRACE_SECONDS — Grace period for sandbox startup
SANDBOX_VOLUMES — Additional volume mounts (host:container format)
SANDBOX_BASE_CONTAINER_IMAGE — Base Docker image for sandboxes
SANDBOX_GROUP_ID root Group ID inside sandbox
OH_RUNTIME_RUNTIME_IMAGE_REPO ghcr.io/openhands/runtime Runtime image repository
RUNTIME_USERNAME — Username inside sandbox
SU_TO_USER true Switch to non-root user in sandbox
RUNTIME_UID — UID for sandbox user

3.5 Storage Variables

Variable Default Description
FILE_STORE local Storage backend: local, s3, google_cloud, memory, webhook
AWS_ACCESS_KEY_ID — AWS access key for S3 storage
AWS_SECRET_ACCESS_KEY — AWS secret key for S3 storage
AWS_S3_ENDPOINT — Custom S3 endpoint (for MinIO, etc.)
AWS_S3_SECURE true Use HTTPS for S3 connections
SHARED_EVENT_STORAGE_PROVIDER — Shared event storage backend
DOCKER_CONTAINER — Docker container name for storage

3.6 Agent & Skill Variables

Variable Default Description
OPENAI_API_KEY — OpenAI API key for agent skills
SANDBOX_ENV_OPENAI_API_KEY — OpenAI key injected into sandbox
OPENAI_BASE_URL https://api.openai.com/v1 OpenAI API base URL
OPENAI_MODEL gpt-4o OpenAI model for skills
MAX_TOKEN 500 Max tokens for skill responses
TAVILY_API_KEY — Tavily search API key
SEARCH_API_KEY — Alternative search API key

3.7 Secret Management Variables

Variable Default Description
OH_MAX_API_SECRETS_COUNT 50 Max number of stored API secrets
OH_MAX_API_SECRET_NAME_LENGTH 256 Max secret name length (chars)
OH_MAX_API_SECRET_VALUE_LENGTH 65536 Max secret value length (chars)
ALLOW_INSECURE_GIT_ACCESS false Allow HTTP (non-HTTPS) Git operations

3.8 Enterprise Variables

Variable Default Description
OPENHANDS_CONFIG_CLS — Custom config class for enterprise
POSTHOG_CLIENT_KEY — PostHog analytics key
GITHUB_APP_CLIENT_ID — GitHub App OAuth client ID
GITHUB_APP_CLIENT_SECRET — GitHub App OAuth client secret
GITHUB_APP_PRIVATE_KEY — GitHub App private key (PEM)
GITHUB_APP_WEBHOOK_SECRET — GitHub webhook HMAC secret
AUTH_URL — Authentication service URL
ENABLE_BILLING false Enable Stripe billing
HIDE_LLM_SETTINGS false Hide LLM config from UI
LITELLM_DEFAULT_MODEL — Default model for LiteLLM proxy
LITE_LLM_API_URL — LiteLLM proxy URL
LITE_LLM_TEAM_ID — LiteLLM team identifier
LITE_LLM_API_KEY — LiteLLM API key
STRIPE_API_KEY — Stripe secret key
REQUIRE_PAYMENT 0 Require payment before usage
KEYCLOAK_SERVER_URL — Keycloak server URL
KEYCLOAK_CLIENT_ID — Keycloak OAuth client ID
KEYCLOAK_CLIENT_SECRET — Keycloak OAuth client secret
KEYCLOAK_ADMIN_PASSWORD — Keycloak admin password
KEYCLOAK_REALM_NAME — Keycloak realm
GITLAB_APP_* — GitLab App OAuth credentials
BITBUCKET_APP_* — Bitbucket Cloud App credentials
BITBUCKET_DATA_CENTER_* — Bitbucket Data Center credentials
ENABLE_JIRA false Enable Jira integration
ENABLE_LINEAR false Enable Linear integration
ENABLE_ENTERPRISE_SSO false Enable enterprise SSO
SLACK_CLIENT_ID — Slack app client ID
SLACK_CLIENT_SECRET — Slack app client secret
SLACK_SIGNING_SECRET — Slack request signing secret
SLACK_WEBHOOKS_ENABLED false Enable Slack webhook processing
RESEND_API_KEY — Resend email API key
RESEND_FROM_EMAIL — Sender email address
RESEND_AUDIENCE_ID — Resend audience ID
RECAPTCHA_PROJECT_ID — Google reCAPTCHA project
RECAPTCHA_SITE_KEY — reCAPTCHA site key
RECAPTCHA_HMAC_SECRET — reCAPTCHA HMAC secret
RECAPTCHA_BLOCK_THRESHOLD 0.3 reCAPTCHA score threshold
AUTOMATION_SERVICE_URL — External automation service URL
AUTOMATION_WEBHOOK_SECRET — Automation webhook HMAC secret
AUTOMATION_EVENT_FORWARDING_ENABLED false Forward events to automation service
AUTOMATION_SERVICE_TIMEOUT 30 Automation service timeout (seconds)
ENABLE_PROACTIVE_CONVERSATION_STARTERS false Enable AI-initiated conversations
ENABLE_SOLVABILITY_ANALYSIS false Enable issue solvability scoring
ENABLE_V1_GITHUB_RESOLVER false V1 GitHub issue resolver
ENABLE_V1_SLACK_RESOLVER false V1 Slack resolver
ENABLE_V1_GITLAB_RESOLVER false V1 GitLab resolver
ENABLE_MCP_SEARCH_ENGINE false Enable MCP-based tool search
AUTOMATIONS_SERVICE_KEY — Automations service API key
FRONTEND_DIRECTORY ./frontend/build Path to frontend build output

3.9 Enterprise Database Variables

Variable Default Description
DB_USER postgres PostgreSQL username
DB_PASS — PostgreSQL password
DB_HOST localhost PostgreSQL host
DB_PORT 5432 PostgreSQL port
DB_NAME openhands PostgreSQL database name
GCP_DB_INSTANCE — GCP Cloud SQL instance
GCP_PROJECT — GCP project ID
GCP_REGION — GCP region
DB_POOL_SIZE 25 Connection pool size
DB_MAX_OVERFLOW 10 Max overflow connections

3.10 MCP Variables

Variable Default Description
OPENHANDS_MCP_CONFIG_CLS openhands.core.config.mcp_config.OpenHandsMCPConfig MCP config class

3.11 Sample config.toml

[core]
workspace_base = "./workspace"
run_as_openhands = true
max_iterations = 100
max_budget_per_task = 4.0

[llm]
model = "claude-sonnet-4-20250514"
api_key = "sk-ant-..."
temperature = 0.0
num_retries = 8
retry_min_wait = 15
retry_max_wait = 120

[agent]
codeact_enable_browsing = true
codeact_enable_jupyter = true
codeact_enable_llm_editor = false

[sandbox]
base_container_image = "ghcr.io/openhands/runtime:latest"
timeout = 120
enable_auto_lint = true

[security]
confirmation_mode = false
security_analyzer = ""

[mcp]
# MCP servers configuration

4. Deployment & Scaling

4.1 Docker Deployment (Single Node)

The simplest production deployment uses Docker directly:

# Pull the latest image
docker pull ghcr.io/openhands/openhands:latest

# Run with Docker socket mounted (required for sandbox containers)
docker run -d \
  --name openhands \
  -p 3000:3000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v ~/.openhands:/home/openhands/.openhands \
  -e JWT_SECRET="$(openssl rand -hex 32)" \
  -e LOG_LEVEL=INFO \
  ghcr.io/openhands/openhands:latest

Caution

Mounting the Docker socket (/var/run/docker.sock) grants the container full Docker access on the host. In production, consider using Docker-in-Docker or a remote Docker daemon with TLS authentication. Never expose the Docker socket to untrusted workloads.

4.2 Docker Compose Deployment

version: "3.8"
services:
  openhands:
    image: ghcr.io/openhands/openhands:latest
    ports:
      - "3000:3000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - openhands-data:/home/openhands/.openhands
    environment:
      - JWT_SECRET=${JWT_SECRET}
      - LOG_LEVEL=INFO
      - SERVE_FRONTEND=true
    restart: unless-stopped

volumes:
  openhands-data:

4.3 Kubernetes Deployment

OpenHands supports native Kubernetes runtime for sandbox containers. Set RUNTIME=kubernetes and configure the Kubernetes section:

[kubernetes]
namespace = "openhands"
ingress_domain = "openhands.example.com"
pvc_storage_size = "2Gi"
pvc_storage_class = "standard"
resource_cpu_request = "1"
resource_memory_request = "1Gi"
resource_memory_limit = "2Gi"
image_pull_secret = "ghcr-pull-secret"
ingress_tls_secret = "openhands-tls"
node_selector_key = "workload-type"
node_selector_val = "compute"
tolerations_yaml = ""
privileged = false

Each agent session creates a Kubernetes Pod with:

  • A PersistentVolumeClaim for workspace data
  • Resource requests and limits as configured
  • Optional node selectors and tolerations for scheduling
  • Optional ingress with TLS termination

4.4 KIND (Local Kubernetes Development)

The kind/ directory provides a complete local Kubernetes development environment:

# Create KIND cluster
kind create cluster --config kind/kind-config.yaml

# Apply manifests
kubectl apply -f kind/manifests/

# Set runtime to kubernetes
export RUNTIME=kubernetes

4.5 Runtime Types

_DEFAULT_RUNTIME_CLASSES = {
    'eventstream': DockerRuntime,
    'docker': DockerRuntime,
    'remote': RemoteRuntime,
    'local': LocalRuntime,
    'kubernetes': KubernetesRuntime,
    'cli': CLIRuntime,
}
Runtime Use Case Docker Required Network Isolation
docker Default, production single-node Yes Container network
kubernetes Production multi-node No (K8s manages) Pod network policies
local Development/testing No None (host process)
cli Headless/scripted usage Depends on config Varies
remote Distributed deployments No (remote host) Remote network

Third-party runtimes are auto-discovered from third_party.runtime.impl via pkgutil, enabling custom runtime implementations without modifying core code.

Tip

For production deployments serving multiple users, Kubernetes runtime is strongly recommended. It provides proper resource isolation, automatic cleanup of terminated sessions, and horizontal scaling via standard Kubernetes primitives (HPA, node autoscaling).

4.6 Enterprise Deployment

Enterprise deployments add authentication, billing, and organization management:

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Keycloak   │     │  PostgreSQL  │     │    Redis     │
│   (Auth)     │     │   (Data)     │     │   (Cache)    │
└──────┬───────┘     └──────┬───────┘     └──────┬───────┘
       │                    │                    │
       └────────────────────┼────────────────────┘
                            │
                   ┌────────▼────────┐
                   │   OpenHands     │
                   │   Enterprise    │
                   │   App Server    │
                   └────────┬────────┘
                            │
                   ┌────────▼────────┐
                   │     Stripe      │
                   │   (Billing)     │
                   └─────────────────┘

Required services for enterprise:

  • Keycloak: Authentication and SSO (KEYCLOAK_* variables)
  • PostgreSQL: User data, organizations, API keys (Alembic migrations)
  • Redis: Session cache, rate limiting (REDIS_* variables)
  • Stripe: Subscription and credit billing (STRIPE_API_KEY, ENABLE_BILLING=true)

4.7 Container Build System

The containers/ directory holds Docker build configurations for:

  • Runtime sandbox images
  • Application server images
  • Development images

Custom sandbox images extend the base runtime:

FROM ghcr.io/openhands/runtime:latest
RUN apt-get update && apt-get install -y your-custom-tools

5. Operations

5.1 Health Monitoring

The health endpoint provides system status:

curl http://localhost:3000/api/v1/health

For enterprise deployments, a readiness probe is also available:

curl http://localhost:3000/api/v1/ready

5.2 Log Management

Configure logging granularity with these variables:

# Standard logging
export LOG_LEVEL=INFO

# Debug everything
export DEBUG=true
export DEBUG_LLM=true
export DEBUG_RUNTIME=true
export LOG_ALL_EVENTS=true

# Production structured logging
export LOG_JSON=true
export LOG_TO_FILE=true

Key log categories:

  • LLM interactions: Enable DEBUG_LLM to see raw request/response payloads. Useful for diagnosing prompt issues or token budget overruns.
  • Runtime operations: Enable DEBUG_RUNTIME for sandbox container lifecycle events.
  • Event stream: Enable LOG_ALL_EVENTS to trace every action and observation through the system.

Warning

DEBUG_LLM=true logs full LLM payloads including any secrets or sensitive content passed in prompts. Never enable in production environments with user data. Rotate any API keys that appear in debug logs.

5.3 Data Persistence

All persistent data lives under OH_PERSISTENCE_DIR (default: ~/.openhands):

~/.openhands/
├── sessions/          # Active/historical session data
├── file_store/        # Default local file store
└── config.toml        # User configuration

For S3-backed persistence:

export FILE_STORE=s3
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_S3_ENDPOINT=https://s3.amazonaws.com

For Google Cloud Storage:

export FILE_STORE=google_cloud
# Authenticate via GOOGLE_APPLICATION_CREDENTIALS or workload identity

5.4 Session Management

Each agent interaction creates a session with:

  • A unique session ID
  • An event stream (append-only log of all actions and observations)
  • A sandboxed runtime container
  • LLM metrics (cost, token counts)

Sessions persist to the configured file store. The event stream supports:

  • Subscriber-based real-time notifications
  • Filtered queries by type, source, date range, or content
  • Batch retrieval for bulk operations

5.5 Backup and Recovery

Local file store: Back up OH_PERSISTENCE_DIR with standard filesystem tools.

S3 file store: Enable S3 versioning on the bucket for point-in-time recovery.

Enterprise PostgreSQL: Standard pg_dump / pg_restore procedures. Alembic migrations track schema version for safe upgrades.

# PostgreSQL backup
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > backup_$(date +%Y%m%d).sql

# Restore
psql -h $DB_HOST -U $DB_USER -d $DB_NAME < backup_20260506.sql

5.6 Upgrade Procedures

  1. Pull the new image: docker pull ghcr.io/openhands/openhands:latest
  2. Stop the running container gracefully (allow active sessions to complete)
  3. Run database migrations (enterprise): alembic upgrade head
  4. Start the new container
  5. Verify health endpoint returns OK

6. Performance

6.1 LLM Performance Tuning

The LLM layer is typically the performance bottleneck. Key tuning parameters:

Parameter Impact Recommendation
num_retries Resilience to transient failures 5-10 for production
retry_min_wait Minimum backoff between retries 15s for rate-limited APIs
retry_max_wait Maximum backoff ceiling 120s
retry_multiplier Exponential backoff factor 2
temperature Response determinism 0.0 for code generation
max_iterations Max agent steps per task Balance thoroughness vs cost
max_budget_per_task Dollar cap per task Set based on model pricing

6.2 Context Window Management

OpenHands implements multiple context condensation strategies to manage long conversations:

Condenser Behavior
NoOpCondenser No condensation (full history)
ObservationMaskingCondenser Masks large observation payloads
RecentEventsCondenser Keeps only N most recent events
LLMSummarizingCondenser Uses an LLM to summarize older context
AmortizedForgettingCondenser Gradually forgets older events
BrowserOutputCondenser Condenses browser interaction output

Configure in config.toml:

[agent.condenser]
type = "llm_summarizing"
# or
type = "recent_events"
keep_last = 50

6.3 Runtime Performance

  • Container startup: SANDBOX_STARTUP_GRACE_SECONDS controls how long to wait for sandbox readiness. Increase for slow Docker hosts.
  • Plugin initialization: INIT_PLUGIN_TIMEOUT (default 120s) governs plugin setup. Increase for environments with slow network access.
  • Memory: Set RUNTIME_MAX_MEMORY_GB to prevent runaway containers. Enable RUNTIME_MEMORY_MONITOR to track usage.

6.4 Model Routing (Experimental)

OpenHands supports experimental model routing for optimizing which model handles different request types:

Router Behavior
noop_router All requests go to the configured model (default)
multimodal_router Routes multimodal requests to vision-capable models

6.5 Database Connection Pooling (Enterprise)

For high-concurrency enterprise deployments, tune the PostgreSQL connection pool:

export DB_POOL_SIZE=25       # Base pool size
export DB_MAX_OVERFLOW=10    # Additional connections under load

Total max connections = DB_POOL_SIZE + DB_MAX_OVERFLOW = 35 per application instance.


7. Security

7.1 Sandbox Isolation

Every agent session runs inside an isolated sandbox container. The security model:

┌─────────────────────────────────────────┐
│              Host System                │
│                                         │
│  ┌───────────────────────────────────┐  │
│  │     OpenHands App Server          │  │
│  │  (no direct code execution)       │  │
│  └───────────────┬───────────────────┘  │
│                  │ Docker API / K8s API  │
│  ┌───────────────▼───────────────────┐  │
│  │     Sandbox Container             │  │
│  │  ┌─────────────────────────────┐  │  │
│  │  │  Agent code execution       │  │  │
│  │  │  Terminal commands          │  │  │
│  │  │  Web browsing               │  │  │
│  │  │  File operations            │  │  │
│  │  └─────────────────────────────┘  │  │
│  │  Resource limits enforced         │  │
│  │  No host filesystem access        │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Key isolation controls:

  • User separation: SU_TO_USER=true (default) runs agent code as non-root inside the sandbox
  • Network: Container networking provides L3 isolation; Kubernetes adds network policies
  • Filesystem: Sandboxes get ephemeral filesystems; SANDBOX_VOLUMES controls explicit mounts
  • Resources: Memory and CPU limits enforced by container runtime
  • Privileged mode: privileged = false by default in Kubernetes config

7.2 Authentication

Mode Mechanism Variables
Local/Dev JWT tokens JWT_SECRET
Enterprise SSO Keycloak OAuth KEYCLOAK_*, ENABLE_ENTERPRISE_SSO
GitHub App OAuth2 GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRET
GitLab App OAuth2 GITLAB_APP_*
Bitbucket OAuth2 BITBUCKET_APP_*, BITBUCKET_DATA_CENTER_*
API Keys Header-based Managed via enterprise API key endpoints
OAuth Device Flow Device authorization /authorize, /token, /verify-authenticated

7.3 Secret Management

OpenHands enforces limits on stored secrets:

  • Maximum 50 secrets per account (OH_MAX_API_SECRETS_COUNT)
  • Secret names up to 256 characters (OH_MAX_API_SECRET_NAME_LENGTH)
  • Secret values up to 64KB (OH_MAX_API_SECRET_VALUE_LENGTH)

Secrets injected into the sandbox environment (e.g., SANDBOX_ENV_OPENAI_API_KEY) are scoped to that session's container and destroyed when the session ends.

7.4 Git Security

# Default: HTTPS-only git operations
export ALLOW_INSECURE_GIT_ACCESS=false

Caution

Setting ALLOW_INSECURE_GIT_ACCESS=true permits HTTP (unencrypted) Git operations. This exposes credentials and source code in transit. Only enable for air-gapped environments with no TLS capability.

7.5 Webhook Security

All webhook integrations use HMAC signature verification:

Integration Secret Variable
GitHub GITHUB_APP_WEBHOOK_SECRET
Automation AUTOMATION_WEBHOOK_SECRET
Slack SLACK_SIGNING_SECRET

7.6 reCAPTCHA (Enterprise)

Enterprise deployments can enable Google reCAPTCHA to prevent automated abuse:

export RECAPTCHA_PROJECT_ID="project-id"
export RECAPTCHA_SITE_KEY="6Le..."
export RECAPTCHA_HMAC_SECRET="..."
export RECAPTCHA_BLOCK_THRESHOLD=0.3  # Block scores below this threshold

7.7 Security Analyzer

The SecurityConfig supports pluggable security analyzers that evaluate agent actions before execution:

[security]
confirmation_mode = true          # Require confirmation for sensitive ops
security_analyzer = "invariant"   # Pluggable analyzer name

When confirmation_mode is enabled, potentially dangerous operations (file deletion, network access, system commands) require explicit user approval.


8. Developer Internals

8.1 Event System Deep Dive

The event system is the backbone of OpenHands. Every agent interaction produces events flowing through the EventStream:

User Input → UserAction (Event)
    → AgentController processes
    → Agent produces AgentAction (Event)
    → Runtime executes
    → Observation (Event) returned
    → Agent processes observation
    → ... (loop until AgentFinishAction or max_iterations)

Event hierarchy:

Event (base)
├── Action
│   ├── CmdRunAction          # Shell command execution
│   ├── FileEditAction        # Edit file content
│   ├── FileReadAction        # Read file content
│   ├── FileWriteAction       # Write entire file
│   ├── IPythonRunCellAction  # Execute Jupyter cell
│   ├── BrowseURLAction       # Navigate to URL
│   ├── BrowseInteractiveAction  # Interactive browser
│   ├── AgentFinishAction     # Agent declares task complete
│   ├── AgentDelegateAction   # Delegate to sub-agent
│   └── MessageAction         # Send message to user
│
└── Observation
    ├── CmdOutputObservation       # Command output
    ├── FileEditObservation        # Edit result
    ├── FileReadObservation        # File content
    ├── FileWriteObservation       # Write confirmation
    ├── IPythonRunCellObservation  # Jupyter output
    ├── ErrorObservation           # Error details
    ├── BrowserOutputObservation   # Browser page content
    └── FileDownloadObservation    # Downloaded file info

Event properties:

  • id: Unique event identifier
  • timestamp: ISO 8601 creation time
  • source: EventSource.AGENT, EventSource.USER, or EventSource.ENVIRONMENT
  • cause: ID of the event that triggered this one
  • timeout: Optional timeout for the action
  • llm_metrics: Token usage and cost tracking

EventFilter supports filtering by:

  • Event type (action vs observation, specific subtypes)
  • Source (agent, user, environment)
  • Date range
  • Content query (text search within event data)

8.2 Agent Architecture

Agents follow a registry pattern:

class Agent(ABC):
    """Abstract base class for all agents."""
    _registry: dict[str, type[Agent]] = {}

    @classmethod
    def register(cls, name: str, agent_cls: type[Agent]):
        cls._registry[name] = agent_cls

    @classmethod
    def get_cls(cls, name: str) -> type[Agent]:
        return cls._registry[name]

The default agent is CodeActAgent, which uses a ReAct-style loop with tool calling. Agent capabilities:

Tool Purpose Config Flag
cmd Execute shell commands Always enabled
jupyter Run Python in Jupyter codeact_enable_jupyter
browsing Web browsing codeact_enable_browsing
editor File editing (str_replace or LLM) codeact_enable_llm_editor
think Chain-of-thought reasoning Always enabled
finish Declare task complete Always enabled
mcp Model Context Protocol tools MCP config
task_tracker Track subtask progress Agent config

Prompt management: Templates use Jinja2 (system_prompt.j2, system_prompt_long_horizon.j2). Microagents extend base behavior and can be disabled via disabled_microagents in agent config.

8.3 AgentController Execution Loop

The AgentController orchestrates the agent lifecycle:

1. Initialize session (create runtime, load config)
2. Set up event stream with subscribers
3. Enter main loop:
   a. Get agent's next action (LLM call)
   b. Validate action against security policy
   c. Execute action in runtime
   d. Collect observation
   e. Append both to event stream
   f. Check termination conditions:
      - AgentFinishAction received
      - max_iterations exceeded
      - max_budget_per_task exceeded
      - Error threshold reached
4. Cleanup (destroy runtime, persist session)

8.4 LLM Layer Internals

Three LLM client classes handle different execution models:

Class Use Case
LLM Synchronous, blocking calls
AsyncLLM Async/await for concurrent operations
StreamingLLM Server-sent events for real-time output

LLMRegistry manages multiple named LLM instances, allowing different models for different purposes (e.g., a cheap model for summarization, a capable model for code generation).

Function call conversion (fn_call_converter.py): Converts between different tool-calling formats (OpenAI function calling, Anthropic tool use, raw XML) to ensure compatibility across LLM providers.

Model features detection (model_features.py): Auto-detects model capabilities (vision, function calling, streaming) to adapt behavior.

Metrics tracking: The Metrics class accumulates:

  • Total tokens (input + output)
  • Cost in dollars
  • Number of API calls
  • Cache hit/miss rates

Note

Issue #13843 documents that LLM cost tracking stops working after a session resume. Costs accumulated before the resume are preserved, but the counter resets for the resumed portion. This is a known limitation tracked for future fix.

8.5 MCP Integration

Model Context Protocol enables agents to use external tools via standardized interfaces:

[mcp]
# Stdio-based MCP server
[[mcp.stdio_servers]]
name = "filesystem"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]

# Remote MCP server (HTTP/SSE)
[[mcp.remote_servers]]
name = "custom-tools"
url = "https://mcp.example.com/sse"

The MCP client:

  • Connects via Stdio (subprocess) or HTTP/SSE (remote) transport
  • Wraps discovered tools as MCPClientTool objects for agent consumption
  • Handles connection lifecycle (connect, disconnect, reconnect)
  • Collects errors via MCPErrorCollector for diagnostic reporting
  • Supports dynamic server updates via POST /update_mcp_server endpoint

8.6 Storage Layer

All storage backends implement the FileStore interface:

class FileStore(ABC):
    @abstractmethod
    def write(self, path: str, contents: str | bytes) -> None: ...
    @abstractmethod
    def read(self, path: str) -> str: ...
    @abstractmethod
    def list(self, path: str) -> list[str]: ...
    @abstractmethod
    def delete(self, path: str) -> None: ...
    @abstractmethod
    def exists(self, path: str) -> bool: ...
Backend Class Use Case
Local filesystem LocalFileStore Default, single-node
AWS S3 S3FileStore Distributed, durable
Google Cloud GoogleCloudFileStore GCP deployments
In-memory InMemoryFileStore Testing only
Webhook WebHookFileStore Event-driven storage
Batched Webhook BatchedWebHookFileStore Buffered webhook writes

8.7 Code Intelligence

The src/code_intelligence.py module provides AST-based extraction:

  • Function/method definitions
  • Class hierarchies
  • Import graphs
  • CLI command registration patterns

The src/repo_analyzer.py handles repository cloning and intelligent triage, scoring folders as HIGH/LOW/SKIP to focus analysis on the most important code paths.

8.8 Project Structure

openhands/
├── core/
│   ├── config/           # 14 config files, Pydantic models
│   ├── logger/           # Structured logging
│   └── exceptions.py     # Error hierarchy
├── controller/           # AgentController, state machine
├── events/               # Event, EventStream, EventFilter
├── llm/                  # LLM, AsyncLLM, StreamingLLM, LLMRegistry
├── runtime/              # Docker, K8s, Local, CLI, Remote runtimes
├── storage/              # FileStore implementations
├── mcp/                  # Model Context Protocol client
├── agent/                # Agent base class, CodeActAgent
├── server/               # V0 legacy FastAPI server
├── app_server/           # V1 application server
└── utils/                # Shared utilities

frontend/                 # React/TypeScript UI
enterprise/               # SaaS features
openhands-ui/             # Shared component library
containers/               # Docker build configs
kind/                     # Kubernetes dev environment
tests/                    # Test suites

8.9 Testing Strategy

Layer Framework Location Run Command
Backend unit pytest tests/unit/ poetry run pytest tests/unit/
Backend integration pytest tests/integration/ poetry run pytest tests/integration/
Frontend unit Vitest frontend/src/**/*.test.ts cd frontend && npm test
E2E Playwright tests/e2e/ npx playwright test

Key testing patterns:

  • InMemoryFileStore for storage tests (no filesystem side effects)
  • Mock LLM responses for agent behavior tests
  • Docker-based integration tests for runtime validation
  • Playwright for full browser-to-sandbox E2E flows

8.10 Build System

Backend (Poetry):

poetry install              # Install dependencies
poetry run python -m openhands.core.main  # Run server
poetry run pytest           # Run tests

Frontend (npm):

cd frontend
npm install                 # Install dependencies
npm run dev                 # Development server (Vite HMR)
npm run build               # Production build
npm test                    # Run Vitest suite

Docker images:

# Build runtime sandbox image
docker build -f containers/runtime/Dockerfile -t openhands-runtime .

# Build app server image
docker build -f containers/app/Dockerfile -t openhands-app .

9. Integration Points

9.1 API Endpoint Reference

Core App Server

Method Path Auth Description
GET /search Session Search events in event stream
GET /count Session Count events matching filter
GET (batch) Session Batch retrieve events
GET /settings Session Load user settings
POST /settings Session Store user settings
GET /agent-schema Session Load agent settings JSON schema
GET /conversation-schema Session Load conversation settings schema

Runtime Action Execution Server

Method Path Auth Description
GET /server_info API Key Runtime server metadata
POST /execute_action API Key Execute an action in the sandbox
POST /update_mcp_server API Key Update MCP server configuration
POST /upload_file API Key Upload file to sandbox workspace
GET /download_files API Key Download files from sandbox
GET /alive None Liveness probe
GET /vscode/connection_token API Key Get VS Code server connection token
POST /list_files API Key List files in sandbox workspace
GET / None File viewer root
GET /view None File viewer interface

Enterprise: Authentication

Method Path Auth Description
GET /keycloak/callback None Keycloak OAuth callback
POST /authenticate None Authenticate user
POST /accept_tos Session Accept terms of service
POST /complete_onboarding Session Complete user onboarding
POST /logout Session End session
GET /refresh-tokens Session Refresh auth tokens

Enterprise: Billing

Method Path Auth Description
GET /credits Session Get credit balance
GET /subscription-access Session Check subscription status
POST /create-checkout-session Session Create Stripe checkout
GET /success Session Payment success callback
GET /cancel Session Payment cancel callback

Enterprise: API Keys

Method Path Auth Description
POST (create) Session Create new API key
GET (list) Session List API keys
DELETE /{key_id} Session Delete API key
GET /current API Key Get current key info
GET /llm/byor Session Get BYOR LLM config
POST /llm/byor/refresh Session Refresh BYOR LLM config

Enterprise: Organizations

Method Path Auth Description
GET (list) Session List user's organizations
POST (create) Session Create organization
GET /{org_id} Session Get organization details
DELETE /{org_id} Session Delete organization
PATCH /{org_id} Session Update organization
GET /{org_id}/members Session List org members
POST /{org_id}/switch Session Switch active organization

Enterprise: Email

Method Path Auth Description
POST (update) Session Update email address
PUT /resend Session Resend verification email
GET /verified Session Check verification status

Enterprise: Feedback

Method Path Auth Description
POST /conversation Session Submit conversation feedback
GET /conversation/{id}/batch Session Get batch feedback

Enterprise: OAuth Device Flow

Method Path Auth Description
POST /authorize None Start device authorization
POST /token None Poll for token
POST /verify-authenticated Session Verify device auth

Enterprise: Invitations

Method Path Auth Description
POST /invite Session Send organization invitation
GET /accept Session View invitation
POST /accept Session Accept invitation

Enterprise: Service

Method Path Auth Description
GET /health None Health check
GET /ready None Readiness probe

9.2 LLM Provider Integration

OpenHands supports 100+ LLM providers through litellm. Configuration per provider:

# OpenAI
[llm]
model = "gpt-4o"
api_key = "sk-..."

# Anthropic
[llm]
model = "claude-sonnet-4-20250514"
api_key = "sk-ant-..."

# AWS Bedrock
[llm]
model = "bedrock/anthropic.claude-3-sonnet"
aws_access_key_id = "AKIA..."
aws_secret_access_key = "..."
aws_region_name = "us-east-1"

# Azure OpenAI
[llm]
model = "azure/gpt-4o"
api_key = "..."
base_url = "https://your-resource.openai.azure.com/"
api_version = "2024-02-15-preview"

# Local (Ollama)
[llm]
model = "ollama/llama3"
base_url = "http://localhost:11434"
api_key = "dummy"

9.3 Git Provider Integration (Enterprise)

Provider Config Variables Features
GitHub GITHUB_APP_* Issue resolution, PR creation, webhooks
GitLab GITLAB_APP_* Issue resolution, MR creation
Bitbucket Cloud BITBUCKET_APP_* Repository integration
Bitbucket Data Center BITBUCKET_DATA_CENTER_* On-premise Bitbucket

Important

Issue #14096 documents that the Integrations page renders blank when GITHUB_APP_SLUG is not configured. Ensure all required GitHub App variables are set if enabling the GitHub integration.

9.4 External Service Integration

Service Variables Purpose
Slack SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_SIGNING_SECRET Chat-based agent interaction
Jira ENABLE_JIRA Issue tracking integration
Linear ENABLE_LINEAR Issue tracking integration
PostHog POSTHOG_CLIENT_KEY Product analytics
Resend RESEND_API_KEY, RESEND_FROM_EMAIL Transactional email
Automation Service AUTOMATION_SERVICE_URL, AUTOMATION_WEBHOOK_SECRET External workflow automation

9.5 Automation Event Forwarding

When AUTOMATION_EVENT_FORWARDING_ENABLED=true, OpenHands forwards agent events to an external automation service:

export AUTOMATION_SERVICE_URL="https://automation.example.com"
export AUTOMATION_WEBHOOK_SECRET="hmac-secret"
export AUTOMATION_EVENT_FORWARDING_ENABLED=true
export AUTOMATION_SERVICE_TIMEOUT=30

10. Troubleshooting

10.1 Agent Errors

Error Cause Resolution
AgentAlreadyRegisteredError Duplicate agent registration Check for duplicate @Agent.register decorators
AgentNotRegisteredError Unknown agent name in config Verify agent name matches a registered agent class
AgentStuckInLoopError Agent repeating the same action Reduce max_iterations, check prompt quality, try a different model

10.2 LLM Errors

Error Cause Resolution
LLMMalformedActionError LLM output didn't match expected format Check function call format, try lowering temperature
LLMNoActionError LLM response contained no tool call Model may need stronger system prompt; check token limits
LLMNoResponseError Empty response from LLM API Check API key validity, network connectivity, rate limits
LLMContextWindowExceedError Input exceeded model's context window Enable a condenser (e.g., recent_events or llm_summarizing)
UserCancelledError User cancelled the operation Normal flow — no action needed

10.3 Function Call Errors

Error Cause Resolution
FunctionCallConversionError Failed to convert between tool-calling formats Check model supports function calling; verify tool schemas
FunctionCallValidationError Tool call arguments failed validation Review agent prompt for correct parameter types
FunctionCallNotExistsError Agent called a non-existent tool Check enabled tools match agent configuration

10.4 Runtime Errors

Error Cause Resolution
AgentRuntimeBuildError Failed to build sandbox container Check Docker daemon, Dockerfile syntax, network access
AgentRuntimeTimeoutError Sandbox didn't start in time Increase SANDBOX_STARTUP_GRACE_SECONDS, check resource availability
AgentRuntimeUnavailableError No runtime available Verify Docker/K8s is running and accessible
AgentRuntimeNotReadyError Runtime started but not initialized Increase INIT_PLUGIN_TIMEOUT, check plugin dependencies
AgentRuntimeDisconnectedError Lost connection to sandbox Check network stability, container health, resource limits
AgentRuntimeNotFoundError Sandbox container was deleted/evicted Check K8s pod eviction policies, Docker cleanup crons

10.5 Browser Errors

Error Cause Resolution
BrowserInitException Failed to start browser in sandbox Ensure chromium is installed in sandbox image
BrowserUnavailableException Browser crashed or is unresponsive Increase sandbox memory, check codeact_enable_browsing

10.6 Storage Errors

Error Cause Resolution
FileNotFoundError (S3) Object doesn't exist in S3 bucket Verify bucket name, prefix, and IAM permissions
FileNotFoundError (GCS) Object doesn't exist in GCS bucket Verify bucket and service account permissions
FileNotFoundError (Memory) Key not in in-memory store Only persists for session lifetime; expected after restart

10.7 MCP Errors

Error Cause Resolution
RuntimeError (MCPClient) Failed to connect to MCP server Verify server URL/command, check network/process
ValueError (missing tool) MCP tool not found on server Ensure tool name matches server's published tools
ValueError (missing session) MCP session not established Check MCP server is running and connection succeeded

10.8 Common Operational Issues

Web UI stuck at "Starting" for old conversations (Issue #12684, CLOSED): Old sessions may fail to reconnect. Clear browser storage or start a new conversation.

Gemini models returning 404 (Issue #14028, OPEN): The base_url gets doubled when using Google Gemini models. Remove any /v1 suffix from your configured base URL.

LLM cost tracking stops after session resume (Issue #13843, CLOSED): Costs accumulated before resume are preserved, but the counter resets for the resumed session portion.

Accumulated token usage returns empty dict (Issue #14039, OPEN): The accumulated_token_usage field in the events API may return {}. This is tracked for fix.

Integrations page blank (Issue #14096, OPEN): The integrations page renders blank without GITHUB_APP_SLUG being set. Ensure all GitHub App configuration is complete.

Auto-titling broken for webhook conversations (Issue #14100, CLOSED): Conversations initiated via webhooks were not receiving auto-generated titles. Fixed in recent releases.

Tip

For full event history loading in long-running sessions, see Issue #12616 (OPEN) which tracks improvements to how events are loaded and retained across session boundaries.

10.9 Docker-Specific Issues

"Cannot connect to Docker daemon":

# Verify Docker is running
docker info

# Check socket permissions
ls -la /var/run/docker.sock

# If using rootless Docker, ensure DOCKER_HOST is set
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

Sandbox container won't start:

# Check available disk space
df -h

# Check Docker resource limits
docker system info | grep -i memory

# Increase startup timeout
export SANDBOX_STARTUP_GRACE_SECONDS=60

10.10 Kubernetes-Specific Issues

Pod eviction: Sandbox pods may be evicted under memory pressure. Increase resource_memory_limit or add dedicated node pools with appropriate taints/tolerations.

PVC provisioning failures: Verify the pvc_storage_class exists in your cluster:

kubectl get storageclasses

Image pull errors: Configure image_pull_secret if using private registries:

kubectl create secret docker-registry ghcr-pull-secret \
  --docker-server=ghcr.io \
  --docker-username=USERNAME \
  --docker-password=TOKEN

10.11 Performance Debugging

Slow agent responses:

  1. Enable DEBUG_LLM=true to measure LLM latency
  2. Check if context condensation is needed (large event histories)
  3. Verify network latency to LLM provider
  4. Consider a faster model for iterative tasks

High memory usage:

  1. Enable RUNTIME_MEMORY_MONITOR=true
  2. Set RUNTIME_MAX_MEMORY_GB to cap per-session usage
  3. Check for memory leaks in long-running sessions
  4. Review condenser settings to prevent unbounded event accumulation

10.12 Enterprise Database Issues

Connection pool exhaustion:

# Increase pool size
export DB_POOL_SIZE=50
export DB_MAX_OVERFLOW=20

# Monitor active connections
psql -c "SELECT count(*) FROM pg_stat_activity WHERE datname='openhands';"

Migration failures:

# Check current migration state
alembic current

# Run pending migrations
alembic upgrade head

# Rollback one step
alembic downgrade -1

11. Error Classes Reference

11.1 Agent Errors

Error Class Description
AgentAlreadyRegisteredError Attempted to register an agent name that already exists
AgentNotRegisteredError Requested agent name not found in registry
AgentStuckInLoopError Agent detected repeating the same action pattern

11.2 LLM Errors

Error Class Description
LLMMalformedActionError LLM response doesn't conform to expected action format
LLMNoActionError LLM response contained no actionable tool call
LLMNoResponseError LLM API returned empty or null response
LLMContextWindowExceedError Input tokens exceed model's maximum context length
UserCancelledError User explicitly cancelled the running operation

11.3 Function Call Errors

Error Class Description
FunctionCallConversionError Failed to convert between tool-calling formats
FunctionCallValidationError Tool call arguments failed schema validation
FunctionCallNotExistsError Agent invoked a tool that doesn't exist

11.4 Runtime Errors

Error Class Description
AgentRuntimeBuildError Sandbox container image build failed
AgentRuntimeTimeoutError Sandbox failed to become ready within timeout
AgentRuntimeUnavailableError No runtime backend available
AgentRuntimeNotReadyError Runtime exists but hasn't finished initialization
AgentRuntimeDisconnectedError Lost connection to running sandbox
AgentRuntimeNotFoundError Referenced sandbox container no longer exists

11.5 Browser Errors

Error Class Description
BrowserInitException Browser process failed to start
BrowserUnavailableException Browser is not accessible or has crashed

12. Module Reference

12.1 openhands/core/config/ (14 files, 106K chars)

The configuration subsystem parses TOML files, environment variables, and CLI arguments into validated Pydantic models. All config classes support serialization to/from TOML and JSON.

Key files:

  • app_config.py — Top-level AppConfig aggregating all sub-configs
  • llm_config.py — LLMConfig with model, API key, retry, and token settings
  • agent_config.py — AgentConfig with tool enables and behavior flags
  • sandbox_config.py — SandboxConfig for container runtime settings
  • security_config.py — SecurityConfig for confirmation mode and analyzers
  • mcp_config.py — MCPConfig for MCP server definitions
  • condenser_config.py — Condenser strategy configs (6 types)

12.2 openhands/controller/

The AgentController manages the agent execution lifecycle:

  • Initializes runtime and event stream
  • Runs the agent loop (action → execute → observe → repeat)
  • Enforces iteration and budget limits
  • Handles delegation to sub-agents
  • Manages state transitions (RUNNING, PAUSED, STOPPED, ERROR)

12.3 openhands/runtime/

Runtime implementations share a common interface for:

  • Container/process lifecycle management
  • Action execution (commands, file ops, browser, Jupyter)
  • File upload/download
  • Plugin initialization
  • Health checking

The DockerRuntime and KubernetesRuntime provide full production isolation. LocalRuntime runs directly on the host (development only). CLIRuntime enables headless scripted usage.

12.4 openhands/llm/

  • llm.py — Synchronous LLM class wrapping litellm
  • async_llm.py — AsyncLLM for concurrent operations
  • streaming_llm.py — StreamingLLM for real-time token streaming
  • llm_registry.py — LLMRegistry managing named LLM instances
  • fn_call_converter.py — Cross-format function call conversion
  • model_features.py — Auto-detect model capabilities
  • metrics.py — Metrics class for cost/token tracking
  • bedrock.py — AWS Bedrock-specific integration

12.5 openhands/events/

  • event.py — Base Event class with metadata
  • stream.py — EventStream append-only store with subscribers
  • filter.py — EventFilter for querying events
  • action/ — All action types
  • observation/ — All observation types
  • serialization/ — Event serialization/deserialization

12.6 openhands/storage/

  • local.py — LocalFileStore (filesystem-backed)
  • s3.py — S3FileStore (AWS S3/MinIO)
  • google_cloud.py — GoogleCloudFileStore
  • memory.py — InMemoryFileStore (testing)
  • webhook.py — WebHookFileStore, BatchedWebHookFileStore

12.7 openhands/mcp/

  • client.py — MCPClient connecting via Stdio or HTTP/SSE
  • tool.py — MCPClientTool wrapping MCP tools for agent consumption
  • error_collector.py — MCPErrorCollector for diagnostic aggregation

12.8 enterprise/

Enterprise service layer:

Module Classes Purpose
Auth AuthTokenStore Token management, Keycloak integration
Organizations OrgService, OrgStore Multi-tenant org management
Users UserStore User profiles, preferences
API Keys ApiKeyStore API key CRUD and validation
Billing Stripe integration Subscriptions, credits, checkout
LLM Proxy LiteLlmManager Centralized LLM access control
Analysis SolvabilityClassifier Issue solvability scoring
Maintenance MaintenanceTaskProcessor Background maintenance tasks

13. Known Issues & Limitations

13.1 Open Issues

Issue Summary Impact
#14039 accumulated_token_usage returns empty dict in events API Token tracking incomplete
#14028 Gemini models 404 — base_url doubled Google Gemini models unusable without workaround
#14096 Integrations page blank without GITHUB_APP_SLUG Enterprise UI broken without full config
#14095 Bitbucket Data Center webhook handler Bitbucket DC integration incomplete
#12616 Full-history events loading/retention Long sessions may lose early events

13.2 Closed Issues (Recent, Noteworthy)

Issue Summary Resolution
#14107 GitlabV1CallbackProcessor deserialization Fixed deserialization logic
#14100 Auto-titling broken for webhook conversations Fixed title generation
#12684 Web UI stuck at "Starting" for old conversations Fixed session reconnection
#13843 LLM cost tracking stops after session resume Partial fix — counter resets on resume
#13412 MyPy doesn't check SQLAlchemy type stubs Added type stub configuration
#13150 Authority separation for autonomous agents Security model documented

Note

MyPy type checking for SQLAlchemy models was not fully configured (Issue #13412). If contributing to the enterprise layer, install sqlalchemy-stubs or sqlalchemy[mypy] to get proper type checking in your editor.


14. Contributing

14.1 Development Setup

# Clone the repository
git clone https://github.com/All-Hands-AI/OpenHands.git
cd OpenHands

# Backend setup
poetry install

# Frontend setup
cd frontend && npm install && cd ..

# Run development server
poetry run python -m openhands.core.main

# Run frontend dev server (separate terminal)
cd frontend && npm run dev

14.2 Code Patterns

Registry pattern (agents, runtimes):

# Register a new agent
@Agent.register("my_agent")
class MyAgent(Agent):
    def step(self, state: State) -> Action:
        ...

Event-driven communication:

# Subscribe to events
event_stream.subscribe(EventStreamSubscriber.AGENT_CONTROLLER, callback)

# Append events
event_stream.add_event(CmdRunAction(command="ls -la"), EventSource.AGENT)

Pydantic config models:

class MyConfig(BaseModel):
    setting_a: str = "default"
    setting_b: int = 42
    model_config = ConfigDict(extra="forbid")

14.3 Testing Conventions

  • Unit tests: pure logic, mock external dependencies
  • Integration tests: require Docker, test runtime interactions
  • E2E tests: require full stack (backend + frontend + Docker)
  • Use InMemoryFileStore in unit tests to avoid filesystem side effects
  • Mock LLM responses for deterministic agent behavior tests

14.4 Frontend Architecture

The frontend uses a modern React stack:

Library Version Purpose
React 19 UI framework
React Router 7 Client-side routing
Zustand 5 State management
TailwindCSS 4 Styling
i18next — Internationalization
Vite 7 Build tool and dev server

The openhands-ui/ package provides shared components published as @openhands/ui, used by both the main frontend and enterprise UI.


For basic usage, quick start guides, and user-facing feature documentation, see the OpenHands User Guide.