diff --git a/llms-full.txt b/llms-full.txt
index dc0149ea..f88f1c23 100644
--- a/llms-full.txt
+++ b/llms-full.txt
@@ -21747,13 +21747,13 @@ for usage_id, metrics in conversation.conversation_stats.usage_to_metrics.items(
### Observability & Tracing
Source: https://docs.openhands.dev/sdk/guides/observability.md
-> A full setup example is available [here](#example:-full-setup)!
+> A full setup example is available [below](#example-full-setup).
## Overview
-The OpenHands SDK provides built-in OpenTelemetry (OTEL) tracing support, allowing you to monitor and debug your agent's execution in real-time. You can send traces to any OTLP-compatible observability platform including:
+The OpenHands SDK provides built-in OpenTelemetry (OTEL) tracing support, allowing you to monitor and debug your agent's execution in real time. You can send traces to any OTLP-compatible observability platform including:
-- **[Laminar](https://laminar.sh/)** - AI-focused observability with browser session replay support
+- **[Laminar](https://laminar.sh/)** - AI-focused observability with trace inspection, signals, and browser session replay
- **[MLflow](https://mlflow.org/)** - Open-source AI platform with tracing, evaluation, and LLM governance
- **[Honeycomb](https://www.honeycomb.io/)** - High-performance distributed tracing
- **Any OTLP-compatible backend** - Including Jaeger, Datadog, New Relic, and more
@@ -21771,14 +21771,18 @@ Tracing is automatically enabled when you set the appropriate environment variab
### Using Laminar
-[Laminar](https://laminar.sh/) provides specialized AI observability features including browser session replays when using browser-use tools:
+[Laminar](https://laminar.sh/) provides specialized AI observability features for OpenHands, including full conversation traces, browser session replay, and higher-level analysis features like signals.
```bash icon="terminal" wrap
# Set your Laminar project API key
export LMNR_PROJECT_API_KEY="your-laminar-api-key"
```
-That's it! Run your agent code normally and traces will be sent to Laminar automatically.
+That's it. Run your agent code normally and traces will be sent to Laminar automatically.
+
+
+For Laminar-specific walkthroughs, see the official docs for [OpenHands SDK tracing](https://laminar.sh/docs/tracing/integrations/openhands-sdk), [session replay for browser agents](https://laminar.sh/docs/tracing/browser-agent-observability), [viewing traces](https://laminar.sh/docs/platform/viewing-traces), and [signals](https://laminar.sh/docs/signals/introduction).
+
For **self-hosted Laminar** deployments, you can also configure custom ports:
@@ -21788,6 +21792,17 @@ export LMNR_HTTP_PORT=8000
export LMNR_GRPC_PORT=8001
```
+If you need help deciding between Laminar Cloud and self-hosted Laminar, see Laminar's official [hosting options](https://laminar.sh/docs/hosting-options).
+
+### Why use Laminar with OpenHands?
+
+Laminar is especially useful when you want to understand how an agent behaved across one run or across many runs:
+
+- Inspect a single run in transcript, tree, or timeline views to see prompts, tool calls, outputs, and nested agent activity. See Laminar's guide to [viewing traces](https://laminar.sh/docs/platform/viewing-traces).
+- Watch browser automation alongside trace spans with [session replay for browser agents](https://laminar.sh/docs/tracing/browser-agent-observability).
+- Define [signals](https://laminar.sh/docs/signals/introduction) to classify failures, user friction, or success patterns across many traces.
+- Keep each OpenHands conversation grouped under a single session ID so multi-turn debugging is easier.
+
### Using OpenTelemetry (OTLP) Backends
For OpenTelemetry (OTLP) compatible backends, set the following environment variables:
@@ -21827,23 +21842,25 @@ export OTEL_EXPORTER="otlp_http" # or "otlp_grpc"
## How It Works
-The OpenHands SDK uses the [Laminar SDK](https://docs.lmnr.ai/) as its OpenTelemetry instrumentation layer. When you set the environment variables, the SDK:
+The OpenHands SDK uses Laminar as its OpenTelemetry instrumentation layer for built-in tracing support. When you set the environment variables, the SDK:
+
+1. **Detects configuration**: Checks for OTEL environment variables on startup
+2. **Initializes tracing**: Configures OpenTelemetry with the appropriate exporter
+3. **Instruments code**: Automatically wraps key functions with tracing decorators
+4. **Captures context**: Associates traces with conversation IDs for session grouping
+5. **Exports spans**: Sends trace data to your configured backend
-1. **Detects Configuration**: Checks for OTEL environment variables on startup
-2. **Initializes Tracing**: Configures OpenTelemetry with the appropriate exporter
-3. **Instruments Code**: Automatically wraps key functions with tracing decorators
-4. **Captures Context**: Associates traces with conversation IDs for session grouping
-5. **Exports Spans**: Sends trace data to your configured backend
+For Laminar-specific behavior and examples, see the official [OpenHands SDK integration guide](https://laminar.sh/docs/tracing/integrations/openhands-sdk).
### What Gets Traced
The SDK automatically instruments these components:
- **`agent.step`** - Each iteration of the agent's execution loop
-- **Tool Executions** - Individual tool calls with input/output capture
-- **LLM Calls** - API requests to language models via LiteLLM
-- **Conversation Lifecycle** - Message sending, conversation runs, and title generation
-- **Browser Sessions** - When using browser-use, captures session replays (Laminar only)
+- **Tool executions** - Individual tool calls with input/output capture
+- **LLM calls** - API requests to language models via LiteLLM
+- **Conversation lifecycle** - Message sending, conversation runs, and title generation
+- **Browser sessions** - When using browser-use, captures session replays (Laminar only)
### Trace Hierarchy
@@ -21863,10 +21880,9 @@ Traces are organized hierarchically:
-Each conversation gets its own session ID (the conversation UUID), allowing you to group all traces from a single
-conversation together in your observability platform.
+Each conversation gets its own session ID (the conversation UUID), allowing you to group all traces from a single conversation together in your observability platform.
-Note that in `tool.execute` the tool calls are traced, e.g., `bash`, `file_editor`.
+In `tool.execute`, the tool calls are traced individually, such as `bash`, `file_editor`, or `task_tracker`.
## Configuration Reference
@@ -21918,7 +21934,7 @@ The SDK supports both HTTP and gRPC protocols:
export LMNR_PROJECT_API_KEY="your-laminar-api-key"
```
-**Self-Hosted Laminar**: If you are running a self-hosted Laminar instance, you can configure the HTTP and gRPC ports via environment variables:
+**Self-hosted Laminar**: If you are running a self-hosted Laminar instance, you can configure the HTTP and gRPC ports via environment variables:
```bash icon="terminal" wrap
export LMNR_PROJECT_API_KEY="your-laminar-api-key"
@@ -21926,7 +21942,25 @@ export LMNR_HTTP_PORT=8000
export LMNR_GRPC_PORT=8001
```
-**Browser Session Replay**: When using Laminar with browser-use tools, session replays are automatically captured, allowing you to see exactly what the browser automation did.
+**Browser session replay**: When using Laminar with browser-use tools, session replays are automatically captured, allowing you to see exactly what the browser automation did.
+
+### OpenHands Enterprise Setup
+
+If you are running OpenHands Enterprise (OHE), you can use the same Laminar integration without changing application code:
+
+1. Complete the [OpenHands Enterprise quick start](/enterprise/quick-start).
+2. Enable analytics in the Admin Console.
+3. Deploy OHE and wait for the analytics service to become ready.
+4. Open the Laminar UI at `https://analytics.app.`.
+5. Create a Laminar project and an ingest-only API key.
+6. Save that key as the **Laminar Project API Key** in the Admin Console.
+7. Redeploy, then start a conversation in OpenHands.
+
+In OHE, environment variables with `LMNR_` and `LLM_` prefixes are automatically forwarded to the SDK runtime. That makes it possible to configure Laminar endpoint settings such as `LMNR_BASE_URL`, `LMNR_PROJECT_API_KEY`, and `LMNR_FORCE_HTTP`, as well as the LLM that powers Laminar's own AI features (chat-with-trace, SQL-with-AI, and [signals](https://laminar.sh/docs/signals/introduction)) via `LLM_PROVIDER`, `LLM_BASE_URL`, and `LLM_MODEL_SMALL|MEDIUM|LARGE`.
+
+`LLM_PROVIDER` accepts `gemini` (Laminar's default), `openai`, or `bedrock`. Set it to `openai` whenever you point `LLM_BASE_URL` at an OpenAI-compatible gateway (for example LiteLLM, OpenRouter, or vLLM), not just the public OpenAI API. For the full list of supported values, see Laminar's official [self-hosting configuration reference](https://laminar.sh/docs/self-hosting/configuration).
+
+For the full OHE flow with screenshots and configuration examples, see [Analytics in OpenHands Enterprise](/enterprise/analytics).
### MLflow Setup
@@ -21950,7 +21984,7 @@ export OTEL_EXPORTER_OTLP_HEADERS="x-mlflow-experiment-id=123" # Replace "123"
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="http/protobuf"
```
-Navigate to the MLflow UI (e.g., `http://localhost:5000`), select the experiment, and open the **Traces** tab to view the recorded traces.
+Navigate to the MLflow UI (for example, `http://localhost:5000`), select the experiment, and open the **Traces** tab to view the recorded traces.
### Honeycomb Setup
@@ -21980,7 +22014,7 @@ export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:4317"
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="grpc"
```
-Access the Jaeger UI at http://localhost:16686
+Access the Jaeger UI at `http://localhost:16686`.
### Generic OTLP Collector
@@ -22018,9 +22052,9 @@ The SDK automatically adds these attributes to spans:
### Debugging Tracing Issues
-If traces aren't appearing in your observability platform:
+If traces are not appearing in your observability platform:
-1. **Verify Environment Variables**:
+1. **Verify environment variables**:
```python icon="python" wrap
import os
@@ -22031,35 +22065,37 @@ If traces aren't appearing in your observability platform:
print(f"OTEL Headers: {otel_headers}")
```
-2. **Check SDK Logs**: The SDK logs observability initialization at debug level:
+2. **Check SDK logs**: The SDK logs observability initialization at debug level:
```python icon="python" wrap
import logging
logging.basicConfig(level=logging.DEBUG)
```
-3. **Test Connectivity**: Ensure your application can reach the OTLP endpoint:
+3. **Test connectivity**: Ensure your application can reach the OTLP endpoint:
```bash icon="terminal" wrap
curl -v https://api.honeycomb.io:443/v1/traces
```
-4. **Validate Headers**: Check that authentication headers are properly URL-encoded
+4. **Validate headers**: Check that authentication headers are properly URL-encoded.
+
+For Laminar-specific troubleshooting, see Laminar's official [tracing troubleshooting guide](https://laminar.sh/docs/tracing/troubleshooting).
## Troubleshooting
### Traces Not Appearing
-**Problem**: No traces showing up in observability platform
+**Problem**: No traces showing up in your observability platform.
**Solutions**:
- Verify environment variables are set correctly
-- Check network connectivity to OTLP endpoint
+- Check network connectivity to the OTLP endpoint
- Ensure authentication headers are valid
- Look for SDK initialization logs at debug level
### High Trace Volume
-**Problem**: Too many spans being generated
+**Problem**: Too many spans being generated.
**Solutions**:
- Configure sampling at the collector level
@@ -22068,7 +22104,7 @@ If traces aren't appearing in your observability platform:
### Performance Impact
-**Problem**: Concerned about tracing overhead
+**Problem**: Concerned about tracing overhead.
**Solutions**:
- Tracing has minimal overhead when properly configured
@@ -22135,6 +22171,7 @@ uv run python examples/01_standalone_sdk/27_observability_laminar.py
## Next Steps
+- **[Analytics in OpenHands Enterprise](/enterprise/analytics)** - Deploy Laminar inside OHE and send conversation traces automatically
- **[Metrics Tracking](/sdk/guides/metrics)** - Monitor token usage and costs alongside traces
- **[LLM Registry](/sdk/guides/llm-registry)** - Track multiple LLMs used in your application
- **[Security](/sdk/guides/security)** - Add security validation to your traced agent executions
@@ -22998,6 +23035,149 @@ if __name__ == "__main__":
+## Registered Marketplace Plugins
+
+Registered marketplaces let an agent context name one or more plugin catalogs once
+and then load plugins by marketplace-qualified names like
+`incident-bot@specialists`. Use `auto_load="all"` when every plugin in a
+marketplace should load at conversation startup, and call
+`conversation.load_plugin()` when you want to add a specific plugin later.
+
+The example below builds local marketplace catalogs so it can run without network
+access or credentials.
+
+
+This example is available on GitHub: [examples/05_skills_and_plugins/05_registered_marketplace_plugins/main.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/05_skills_and_plugins/05_registered_marketplace_plugins/main.py)
+
+
+```python icon="python" expandable examples/05_skills_and_plugins/05_registered_marketplace_plugins/main.py
+"""Example: Registered Marketplaces and Runtime Plugin Loading
+
+This example demonstrates the registered marketplace flow:
+
+1. Register multiple marketplace catalogs on AgentContext.
+2. Auto-load plugins from a marketplace with ``auto_load='all'``.
+3. Load an additional plugin at runtime by marketplace-qualified name.
+
+The example builds two temporary local marketplaces so it can run without network
+access or external credentials.
+"""
+
+import json
+import tempfile
+from pathlib import Path
+
+from openhands.sdk import Agent, AgentContext, Conversation
+from openhands.sdk.marketplace import MarketplaceRegistration
+from openhands.sdk.testing import TestLLM
+
+
+def write_plugin(plugin_dir: Path, plugin_name: str, skill_name: str) -> None:
+ manifest_dir = plugin_dir / ".plugin"
+ manifest_dir.mkdir(parents=True, exist_ok=True)
+ (manifest_dir / "plugin.json").write_text(
+ json.dumps(
+ {
+ "name": plugin_name,
+ "version": "1.0.0",
+ "description": f"Example plugin {plugin_name}",
+ }
+ )
+ )
+
+ skills_dir = plugin_dir / "skills"
+ skills_dir.mkdir()
+ (skills_dir / f"{skill_name}.md").write_text(
+ f"---\nname: {skill_name}\ndescription: Example skill\n---\n"
+ f"Use {skill_name} when demonstrating registered marketplace plugins."
+ )
+
+
+def write_marketplace(marketplace_dir: Path, plugin_name: str, skill_name: str) -> None:
+ write_plugin(marketplace_dir / "plugins" / plugin_name, plugin_name, skill_name)
+ manifest_dir = marketplace_dir / ".plugin"
+ manifest_dir.mkdir(parents=True, exist_ok=True)
+ (manifest_dir / "marketplace.json").write_text(
+ json.dumps(
+ {
+ "name": marketplace_dir.name,
+ "owner": {"name": "Example Team"},
+ "plugins": [
+ {
+ "name": plugin_name,
+ "source": f"./plugins/{plugin_name}",
+ "description": f"Example marketplace plugin {plugin_name}",
+ }
+ ],
+ }
+ )
+ )
+
+
+with tempfile.TemporaryDirectory() as tmpdir:
+ tmp_path = Path(tmpdir)
+ team_marketplace = tmp_path / "team-marketplace"
+ specialists_marketplace = tmp_path / "specialists-marketplace"
+ write_marketplace(team_marketplace, "review-bot", "review-checklist")
+ write_marketplace(specialists_marketplace, "incident-bot", "incident-brief")
+
+ agent = Agent(
+ llm=TestLLM.from_messages([]),
+ tools=[],
+ agent_context=AgentContext(
+ registered_marketplaces=[
+ MarketplaceRegistration(
+ name="team",
+ source=str(team_marketplace),
+ auto_load="all",
+ ),
+ MarketplaceRegistration(
+ name="specialists",
+ source=str(specialists_marketplace),
+ ),
+ ]
+ ),
+ )
+
+ conversation = Conversation(
+ agent=agent,
+ workspace=str(tmp_path / "workspace"),
+ )
+
+ conversation.load_plugin("incident-bot@specialists")
+
+ agent_context = conversation.agent.agent_context
+ assert agent_context is not None
+ skill_names = sorted(skill.name for skill in agent_context.skills or [])
+ resolved_sources = [plugin.source for plugin in conversation.resolved_plugins or []]
+
+ print("Registered marketplaces:")
+ for registration in agent_context.registered_marketplaces:
+ print(f" - {registration.name}: auto_load={registration.auto_load}")
+
+ print("Loaded skills:")
+ for skill_name in skill_names:
+ print(f" - {skill_name}")
+
+ print("Resolved plugins:")
+ for source in resolved_sources:
+ print(f" - {source}")
+
+ assert skill_names == ["incident-brief", "review-checklist"]
+ assert any(
+ source.endswith("team-marketplace/plugins/review-bot")
+ for source in resolved_sources
+ )
+ assert any(
+ source.endswith("specialists-marketplace/plugins/incident-bot")
+ for source in resolved_sources
+ )
+
+print("EXAMPLE_COST: 0")
+```
+
+
+
## Installing Plugins to Persistent Storage
The SDK provides utilities to install plugins to a local directory
@@ -23381,17 +23561,24 @@ The **LLMSecurityAnalyzer** is the default implementation provided in the agent-
Create an LLM-based security analyzer to review actions before execution:
-```python icon="python" focus={9}
-from openhands.sdk import LLM
+```python icon="python"
+from openhands.sdk import LLM, Agent, Conversation
+from openhands.sdk.security.confirmation_policy import ConfirmRisky
from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer
+
llm = LLM(
usage_id="security-analyzer",
model=model,
base_url=base_url,
api_key=SecretStr(api_key),
)
-security_analyzer = LLMSecurityAnalyzer(llm=security_llm)
-agent = Agent(llm=llm, tools=tools, security_analyzer=security_analyzer)
+security_analyzer = LLMSecurityAnalyzer(llm=llm)
+
+# Attach the analyzer on the conversation, not the Agent constructor.
+agent = Agent(llm=llm, tools=tools)
+conversation = Conversation(agent=agent, workspace=".")
+conversation.set_security_analyzer(security_analyzer)
+conversation.set_confirmation_policy(ConfirmRisky())
```
The security analyzer:
@@ -23586,7 +23773,7 @@ class CustomSecurityAnalyzer(SecurityAnalyzerBase):
# Use your custom analyzer
security_analyzer = CustomSecurityAnalyzer()
-agent = Agent(llm=llm, tools=tools, security_analyzer=security_analyzer)
+conversation.set_security_analyzer(security_analyzer)
```
@@ -28161,101 +28348,104 @@ Source: https://docs.openhands.dev/openhands/usage/advanced/custom-sandbox-guide
These settings are only available in [Local GUI](/openhands/usage/run-openhands/local-setup). OpenHands Cloud uses managed sandbox environments.
+
+ Looking for the legacy `SANDBOX_BASE_CONTAINER_IMAGE` / `base_container_image`
+ workflow? That only applies to OpenHands V0. See the
+ [V0 Custom Sandbox reference](/openhands/usage/v0/advanced/V0_custom-sandbox-guide).
+
+
The sandbox is where the agent performs its tasks. Instead of running commands directly on your computer
(which could be risky), the agent runs them inside a Docker container.
-The default OpenHands sandbox (`python-nodejs:python3.12-nodejs22`
-from [nikolaik/python-nodejs](https://hub.docker.com/r/nikolaik/python-nodejs)) comes with some packages installed such
-as python and Node.js but may need other software installed by default.
+## How the sandbox works in V1
-You have two options for customization:
+In OpenHands V1 the sandbox container **is** the OpenHands agent-server. By default OpenHands runs
+`ghcr.io/openhands/agent-server:-python`, which already includes Python and Node.js. The image is
+resolved from two environment variables:
-- Use an existing image with the required software.
-- Create your own custom Docker image.
+- `AGENT_SERVER_IMAGE_REPOSITORY` (default `ghcr.io/openhands/agent-server`)
+- `AGENT_SERVER_IMAGE_TAG` (default `-python`)
-If you choose the first option, you can skip the `Create Your Docker Image` section.
+Because the sandbox is the agent-server, you can't just swap in an arbitrary base image — the container has
+to keep running the agent-server. To add custom tooling you build a **custom agent-server image** on top of
+your chosen base image, then point OpenHands at it.
-## Create Your Docker Image
+## Building a custom agent-server image
-To create a custom Docker image, it must be Debian based.
+The agent-server is built from a [Dockerfile in the OpenHands SDK](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-agent-server/openhands/agent_server/docker/Dockerfile)
+that accepts a `BASE_IMAGE` build argument. Any Debian-based image works as the base.
-For example, if you want OpenHands to have `ruby` installed, you could create a `Dockerfile` with the following content:
+For example, to layer the agent-server onto an image that has `ruby` installed, first build (or pick) your base
+image. To build one:
```dockerfile
+# Dockerfile.base
FROM nikolaik/python-nodejs:python3.12-nodejs22
# Install required packages
RUN apt-get update && apt-get install -y ruby
```
-Or you could use a Ruby-specific base image:
-
-```dockerfile
-FROM ruby:latest
-```
-
-Save this file in a folder. Then, build your Docker image (e.g., named custom-image) by navigating to the folder in
-the terminal and running::
```bash
-docker build -t custom-image .
+docker build -t my-base:latest -f Dockerfile.base .
```
-This will produce a new image called `custom-image`, which will be available in Docker.
+Then build the agent-server image on top of it. Clone the
+[OpenHands SDK](https://github.com/OpenHands/software-agent-sdk) and, from the repository root, run:
-## Using the Docker Command
-
-When running OpenHands using [the docker command](/openhands/usage/run-openhands/local-setup#start-the-app), replace
-the `AGENT_SERVER_IMAGE_REPOSITORY` and `AGENT_SERVER_IMAGE_TAG` environment variables with `-e SANDBOX_BASE_CONTAINER_IMAGE=`:
-
-```commandline
-docker run -it --rm --pull=always \
- -e SANDBOX_BASE_CONTAINER_IMAGE=custom-image \
- ...
+```bash
+docker buildx build \
+ --build-arg BASE_IMAGE=my-base:latest \
+ --target binary \
+ -f openhands-agent-server/openhands/agent_server/docker/Dockerfile \
+ -t my-agent-server:custom \
+ --load \
+ .
```
-## Using the Development Workflow
-
-### Setup
+- `--build-arg BASE_IMAGE=` selects the base image to layer the agent-server onto.
+- `--target binary` matches how the default published `-python` image is built — it bundles a self-contained
+ agent-server binary (no Python virtual environment at runtime) and includes VSCode and VNC. Other targets
+ are available if you need them: `source` runs the agent-server from a Python virtual environment (handy for
+ development and debugging), and the `binary-minimal` / `source-minimal` targets drop VSCode and VNC for a
+ smaller image.
+- `--load` makes the resulting image available to your local Docker daemon.
-First, ensure you can run OpenHands by following the instructions in [Development.md](https://github.com/OpenHands/OpenHands/blob/main/Development.md).
+This produces a local image called `my-agent-server:custom`.
-### Specify the Base Sandbox Image
+## Pointing OpenHands at your image
-In the `config.toml` file within the OpenHands directory, set the `base_container_image` to the image you want to use.
-This can be an image you’ve already pulled or one you’ve built:
+Set both environment variables so OpenHands launches your image as the sandbox. They must be set together —
+if either is missing, OpenHands falls back to the default image:
```bash
-[core]
-...
-[sandbox]
-base_container_image="custom-image"
+docker run -it --rm --pull=always \
+ -e AGENT_SERVER_IMAGE_REPOSITORY=my-agent-server \
+ -e AGENT_SERVER_IMAGE_TAG=custom \
+ ...
```
-### Additional Configuration Options
-
-The `config.toml` file supports several other options for customizing your sandbox:
+If you start OpenHands with Docker Compose, set the same variables there:
-```toml
-[core]
-# Install additional dependencies when the runtime is built
-# Can contain any valid shell commands
-# If you need the path to the Python interpreter in any of these commands, you can use the $OH_INTERPRETER_PATH variable
-runtime_extra_deps = """
-pip install numpy pandas
-apt-get update && apt-get install -y ffmpeg
-"""
+```yaml
+environment:
+ - AGENT_SERVER_IMAGE_REPOSITORY=my-agent-server
+ - AGENT_SERVER_IMAGE_TAG=custom
+```
-# Set environment variables for the runtime
-# Useful for configuration that needs to be available at runtime
-runtime_startup_env_vars = { DATABASE_URL = "postgresql://user:pass@localhost/db" }
+When the sandbox starts, OpenHands launches your image on port `8000` and polls `/health` until the
+agent-server is ready.
-# Specify platform for multi-architecture builds (e.g., "linux/amd64" or "linux/arm64")
-platform = "linux/amd64"
-```
+
+ When you publish your image to a registry, set `AGENT_SERVER_IMAGE_REPOSITORY` to the fully qualified
+ repository (e.g. `ghcr.io/your-org/my-agent-server`) and make sure the host running OpenHands can pull it.
+
-### Run
+## Related
-Run OpenHands by running ```make run``` in the top level directory.
+- [Docker Sandbox](/openhands/usage/sandboxes/docker) — the default sandbox provider for Local GUI.
+- [Agent Server in Docker (SDK)](/sdk/guides/agent-server/docker-sandbox) — deeper details on the
+ agent-server image and how it is built.
### Search Engine Setup
Source: https://docs.openhands.dev/openhands/usage/advanced/search-engine-setup.md
@@ -29630,6 +29820,57 @@ Agent Canvas also shows model-switch events in the conversation timeline so you
- [Customize and Settings](/openhands/usage/agent-canvas/customize-and-settings)
- [LLM Settings](/openhands/usage/settings/llm-settings)
+### Phone & Tablet Access
+Source: https://docs.openhands.dev/openhands/usage/agent-canvas/mobile-access.md
+
+If Agent Canvas is running on your computer, you can access it from a phone, tablet, or another device without exposing it to the public internet.
+
+## Tailscale (Recommended)
+
+[Tailscale](https://tailscale.com/) creates a private network between your devices. No port forwarding, DNS, or firewall rules are required.
+
+### Setup
+
+1. Install [Tailscale](https://tailscale.com/download) on both your computer and your phone or tablet.
+2. Sign in with the same account on both devices.
+3. Find your computer's Tailscale IP in the Tailscale app. It usually looks like `100.x.y.z`.
+4. Start Agent Canvas on your computer:
+
+ ```bash
+ agent-canvas
+ ```
+
+5. Open `http://:8000/` in your phone or tablet browser.
+
+That's it. The connection is encrypted and only devices in your Tailscale network can reach it.
+
+
+ If the mobile browser still points at `127.0.0.1` or `localhost`, open `Manage Backends` and edit the local backend's `Host / Base URL` to `http://:8000`. If it keeps reverting, clear site data for the Agent Canvas URL in your browser settings and reload the page.
+
+
+## ngrok (Remote / Public Access)
+
+Use [ngrok](https://ngrok.com/) when Tailscale is not an option or when you need a temporary public URL. Because the URL is reachable from the internet, run Agent Canvas in public mode with a strong backend API key first:
+
+```bash
+export LOCAL_BACKEND_API_KEY=""
+agent-canvas --public
+```
+
+Then start ngrok in a second terminal:
+
+```bash
+ngrok http 8000
+```
+
+Open the ngrok forwarding URL in your phone or tablet browser.
+
+
+ Do not expose Agent Canvas over the internet without authentication. Public mode requires users to enter `LOCAL_BACKEND_API_KEY` before the backend can be used. Without authentication, anyone with the URL can use your instance and the LLM API keys configured in it. See [VM / Self-Hosted Backend](/openhands/usage/agent-canvas/backend-setup/vm) for the full remote-access setup.
+
+
+ngrok also supports OAuth, IP allowlists, and other access controls for additional protection.
+
### Agent Canvas Overview
Source: https://docs.openhands.dev/openhands/usage/agent-canvas/overview.md
@@ -30606,6 +30847,7 @@ When asking OpenHands to create an automation, include:
- **What it should do**: Describe the task clearly
- **When it should run**: Daily, weekly, every hour, etc.
- **Timezone** (optional): Defaults to UTC if not specified
+- **Run timeout** (optional): Defaults to 10 minutes; maximum 30 minutes
- **Name** (optional): The agent can suggest one based on your description
- **Plugins** (optional): Mention specific plugins if you need extended capabilities
@@ -30685,6 +30927,10 @@ The agent converts this to the appropriate cron schedule.
If you're familiar with cron expressions, you can specify them directly: "Run on cron schedule `0 9 * * 1-5`"
+## Run Timeouts
+
+Each run stops after its timeout. The default is 10 minutes; you can request up to 30 minutes, for example: "Use a 20-minute timeout."
+
## After Creation
Once your automation is created:
@@ -31020,6 +31266,14 @@ Change the "Daily Report" automation to run at 10 AM instead of 9 AM
Update the "Weekly Cleanup" automation to run on Sundays at 2 AM UTC
```
+## Changing the Run Timeout
+
+```
+Set the "Weekly Cleanup" automation timeout to 20 minutes
+```
+
+Timeouts can be up to 30 minutes. Runs that exceed their timeout fail automatically.
+
## Running Manually
Test an automation or run it outside its normal schedule:
@@ -41055,7 +41309,7 @@ The SDK is a composable Python library that contains all of our agentic tech. It
Define agents in code, then run them locally, or scale to 1000s of agents in the cloud.
-[Check out the docs](https://docs.openhands.dev/sdk) or [view the source](https://github.com/All-Hands-AI/agent-sdk/)
+[Check out the docs](https://docs.openhands.dev/sdk) or [view the source](https://github.com/OpenHands/software-agent-sdk)
## Legacy
@@ -43157,62 +43411,120 @@ Enterprise customers receive:
### Analytics
Source: https://docs.openhands.dev/enterprise/analytics.md
-This guide walks you through setting up and using Laminar for Analytics in OpenHands Enterprise.
-You'll opt into Analytics and configure conversations to automatically send traces to Laminar.
+This guide walks you through enabling Laminar in OpenHands Enterprise (OHE) so conversations automatically send traces for observability and analysis.
+
+For SDK-level tracing concepts, OTEL environment variables, and non-Laminar backends, see [Observability & Tracing](/sdk/guides/observability).
## Who This Is For
-This guide is for users who want to explore analytics on their OpenHands Enterprise conversations.
+This guide is for users who want to deploy Laminar alongside OpenHands Enterprise and inspect traces from Enterprise conversations.
-### Why Laminar?
+## Why Laminar in OHE?
-[Laminar](https://laminar.sh/) is an open source observability platform for AI agents like OpenHands.
+Laminar helps you understand what your OpenHands deployment is doing in production:
-Use Laminar to view your conversation traces including prompts, tool calls, and answers. A trace is a record of what your agent did.
+- Inspect prompts, tool calls, answers, and nested agent behavior in Laminar's [trace views](https://laminar.sh/docs/platform/viewing-traces).
+- Use [session replay for browser agents](https://laminar.sh/docs/tracing/browser-agent-observability) when conversations drive browser automation.
+- For Helm installs, define [signals](https://laminar.sh/docs/signals/introduction) to classify failures, measure outcomes, and monitor recurring patterns across many traces.
-Laminar can help you see where the agent went wrong. From traces, you can create signals. A signal is a natural language instruction to extract structured data from traces. Use signals to analyze recurring behavior across traces. You can then create better situations for prompting and measure them in Laminar. You can also analyze and improve your skills.
+For more information on evaluating skills, see [Evaluating Agent Skills](https://www.openhands.dev/blog/evaluating-agent-skills).
-For example, you can view all conversation traces related to a specific skill.
+## Prerequisites
-Laminar can help you answer the following questions:
-- On a trace, did the agent do a good job using the skill?
-- On another trace, did the agent do a bad job?
+Before you begin, complete the [Quick Start guide](/enterprise/quick-start).
-For more information on evaluating skills, see [Evaluating Agent Skills](https://www.openhands.dev/blog/evaluating-agent-skills).
+## Enable Analytics
-### Prerequisites
+
+
+
+ VM installs currently support trace collection, but do not support Laminar signals. The Admin Console configures the Laminar Project API Key only; the installer sets the remaining Laminar connection values automatically.
+
-Before you begin, make sure you completed the [Quick Start guide](/enterprise/quick-start).
+ You should see an **Analytics Configuration** section on the application configuration page.
-## Enable Analytics
+ Check the **Enable Analytics** box to have the installer set up and configure Laminar for analytics.
-You should see an **Analytics Configuration** section on the application configuration page.
+ 
+
+
+ If you deployed OpenHands Enterprise into your own Kubernetes cluster using Helm, enable Laminar in your `values.yaml` override file.
-Check the **Enable Analytics** box to have the installer set up and configure Laminar for analytics.
+ ```yaml
+ laminar:
+ enabled: true
+ global:
+ # Set to "aws" or "gcp" to match your cluster.
+ cloudProvider: "aws"
+
+ frontend:
+ ingress:
+ enabled: true
+ hostname: "analytics.app."
+ tls:
+ enabled: true
+ secretName: "laminar-frontend-tls"
+
+ appServer:
+ # Use an app-server ingress on GCP or other L7 ingress setups.
+ ingress:
+ enabled: true
+ hostname: "laminar-api."
+ tls:
+ enabled: true
+ secretName: "laminar-app-server-tls"
+
+ # On AWS, use a Network Load Balancer instead of appServer.ingress
+ # if your runtimes send traces directly over TCP.
+ loadBalancer:
+ enabled: false
+ ```
-
+ Keep `laminar.enabled: false` until your ingress, TLS, and storage class settings match your cluster.
+
+
## Deploy
-OpenHands will begin deploying. You can expect the deployment status to transition from
-**Missing** to **Unavailable** to **Ready**. This typically takes 10-15 minutes.
+
+
+ OpenHands will begin deploying. You can expect the deployment status to transition from **Missing** to **Unavailable** to **Ready**. This typically takes 10-15 minutes.
-
+ 
-Click **Details** next to the deployment status to monitor individual resources. Resources
-shown in orange are still deploying -- wait until all resources are ready.
+ Click **Details** next to the deployment status to monitor individual resources. Resources shown in orange are still deploying, so wait until all resources are ready.
-
+ 
+
+
+ Apply your updated `values.yaml` override file:
+
+ ```bash
+ helm upgrade openhands oci://ghcr.io/openhands/helm-charts/openhands \
+ --namespace openhands \
+ --values values.yaml
+ ```
-## Access Laminar UI
+ Wait for the Laminar workloads, ingress, and TLS resources to become ready.
+
+
-Once the deployment status shows **Ready**, navigate to `https://analytics.app.`.
+## Access the Laminar UI
+
+Once the deployment status shows **Ready**, navigate to the Laminar frontend URL:
+
+- VM install: `https://analytics.app.`
+- Kubernetes install: the hostname configured in `laminar.frontend.ingress.hostname`
Click the **Continue with Keycloak** button:

-## Create a Laminar project
+If you want more background on Laminar Cloud versus self-hosting outside OHE, see Laminar's official [hosting options](https://laminar.sh/docs/hosting-options).
+
+## Create a Laminar Project
+
+Create a project in the Laminar UI:

@@ -43220,60 +43532,285 @@ Once a project has been created, Laminar is ready to listen for traces.

-## Create an ingest only API Key
+## Create an Ingest-Only API Key
-Important: Always use ingest API keys when deploying.
+Always use ingest-only API keys when deploying OHE.
-Create a key with the right permissions. Ingest only keys are recommended as they only have write access to write traces. They cannot be used to read data.
+Ingest-only keys are recommended because OHE only needs permission to write traces. They cannot be used to read trace data.

-## Set Laminar Project API Key to enable automatic conversation traces
+## Set the Laminar Project API Key
+
+This is the same `LMNR_PROJECT_API_KEY` described in the [SDK observability guide](/sdk/guides/observability).
+
+
+
+ Set the ingest-only key as the **Laminar Project API Key** in the Admin Console configuration.
+
+ 
-Set the ingest only key as the Laminar Project API Key in the Admin Console configuration:
+ Click **Save config**.
+
+
+ Create a Kubernetes Secret for the ingest-only project key:
+
+ ```bash
+ kubectl create secret generic lmnr-project-api-key \
+ --namespace openhands \
+ --from-literal=LMNR_PROJECT_API_KEY=
+ ```
+
+ Create a Secret for the Laminar app-server base URL:
+
+ ```bash
+ kubectl create secret generic lmnr-base-url \
+ --namespace openhands \
+ --from-literal=LMNR_BASE_URL=https://laminar-api.
+ ```
+
+ Then reference those Secrets from your `values.yaml` override file:
+
+ ```yaml
+ laminar:
+ enabled: true
+ apiKeyFromSecret:
+ name: lmnr-project-api-key
+ key: LMNR_PROJECT_API_KEY
+ baseUrlFromSecret:
+ name: lmnr-base-url
+ key: LMNR_BASE_URL
+ forceHttp: true
+ ```
+
+ If your self-hosted Laminar app server exposes a non-default HTTP port, set `laminar.httpPort`.
+
+
+
+## Configure Runtime Environment Variables
+
+
+
+ VM installs configure analytics through the Admin Console. After analytics is enabled and the Laminar Project API Key is saved, the installer automatically configures:
+
+ ```yaml
+ LMNR_BASE_URL: "http://laminar-app-server-service"
+ LMNR_PROJECT_API_KEY: ""
+ LMNR_FORCE_HTTP: "true"
+ LMNR_HTTP_PORT: "8000"
+ ```
+
+ The Admin Console does not currently expose `LLM_*` settings for Laminar AI features. VM installs currently send traces to Laminar, but do not support Laminar signals.
+
+
+ In OHE, environment variables whose names start with `LMNR_` or `LLM_` are forwarded to the SDK runtime. This lets you configure Laminar ingestion settings and the LLM settings used for Laminar-backed workflows.
+
+ For example, you can point the runtime at the managed Laminar endpoint and use an ingest-only project key:
+
+ ```yaml
+ LMNR_BASE_URL: "https://laminar-api."
+ # Ingest-only API key, not a read-capable secret:
+ LMNR_PROJECT_API_KEY: ""
+ LMNR_FORCE_HTTP: "true"
+ ```
+
+ The chart sets `LMNR_PROJECT_API_KEY`, `LMNR_BASE_URL`, `LMNR_FORCE_HTTP`, and `LMNR_HTTP_PORT` from the `laminar` values above. If you need to override one of them directly, set it under the top-level `env` values in your `values.yaml`.
+
+ You can also control which LLM Laminar uses for its AI features — chat-with-trace, SQL-with-AI, and [signals](https://laminar.sh/docs/signals/introduction) — by forwarding the standard `LLM_*` variables. Add these values under the top-level `env` values:
+
+ ```yaml
+ env:
+ LLM_PROVIDER: "openai"
+ LLM_API_KEY: ""
+ LLM_BASE_URL: "https://llm-proxy."
+ LLM_MODEL_SMALL: "gpt-5.4-mini"
+ LLM_MODEL_MEDIUM: "gpt-5.4-mini"
+ LLM_MODEL_LARGE: "gpt-5.5"
+ ```
-
+ `LLM_PROVIDER` accepts `gemini` (Laminar's default), `openai`, or `bedrock`, and `LLM_MODEL_SMALL` / `LLM_MODEL_MEDIUM` / `LLM_MODEL_LARGE` are optional per-tier model overrides. Set `LLM_PROVIDER` to `openai` whenever you point `LLM_BASE_URL` at an OpenAI-compatible gateway (for example LiteLLM, OpenRouter, or vLLM), not just the public OpenAI API. Set `LLM_API_KEY` for `gemini`, `openai`, and OpenAI-compatible gateways; use AWS credentials instead for `bedrock`.
-Click **Save config**.
+ For the full set of supported values, see Laminar's official [self-hosting configuration reference](https://laminar.sh/docs/self-hosting/configuration).
+
+
## Deploy Updated Configuration
-Deploy the config change after setting the Laminar Project API Key in the Admin Console.
+Deploy the configuration change after setting the Laminar Project API Key.
+
+
+
+ Click **Deploy** in the Admin Console.
+
+ 
-
+ For a VM install walkthrough, watch the recap:
+
+
+
+
+ Apply your updated `values.yaml` override file:
+
+ ```bash
+ helm upgrade openhands oci://ghcr.io/openhands/helm-charts/openhands \
+ --namespace openhands \
+ --values values.yaml
+ ```
+
+
Wait for the deployment to complete.
-## Start a conversation
+## Start a Conversation
Navigate to the OpenHands UI at `https://app.`. Start a new conversation and try a prompt.

-Your conversations will now automatically send a trace to Laminar.
+Your conversations will now automatically send traces to Laminar.

-
+## What to Do Next in Laminar
+
+Once traces are flowing, use Laminar's official docs to go deeper:
+
+- [Viewing Traces](https://laminar.sh/docs/platform/viewing-traces) to inspect a single conversation in transcript, tree, or timeline views.
+- For Helm installs, [Signals](https://laminar.sh/docs/signals/introduction) to extract structured outcomes or failure modes across many traces.
+- [Session replay for browser agents](https://laminar.sh/docs/tracing/browser-agent-observability) to debug browser-based automations.
+- [Observability for OpenHands Software Agent SDK](https://laminar.sh/docs/tracing/integrations/openhands-sdk) for the OpenHands-specific tracing model.
## Next Steps
+
+ Learn the full OpenHands tracing model, OTEL configuration options, and non-Laminar backends.
+
- Get the most out of your AI coding agents with effective prompting techniques.
+ Get more reliable traces by improving the prompts you give your agents.
Reach out to the OpenHands team for deployment assistance or questions.
-
- Explore the full OpenHands documentation for usage guides and features.
-
+### Custom Sandbox Images
+Source: https://docs.openhands.dev/enterprise/custom-sandbox-image.md
+
+Custom sandbox images let you prebake the repository, dependencies, compiled output, and test harness
+your agents need. Instead of spending minutes provisioning a workspace on every run, your agents start
+on the actual task immediately.
+
+## Why Use a Custom Image
+
+Custom images eliminate cold-start setup work (clone, install, transpile, and bootstrap) so agents
+spend their time on the actual task. They also reduce setup variance and lower sandbox memory requirements
+by keeping only what the agent needs.
+
+## Build Your Own Custom Image
+
+The [OpenHands agent-server sandbox guide](https://docs.openhands.dev/sdk/guides/agent-server/docker-sandbox)
+provides full documentation on building custom sandbox images. The approach is the same for the Enterprise
+Replicated VM deployment.
+
+### Basic Pattern
+
+1. Start from the OpenHands agent-server base image.
+2. Keep the normal OpenHands entrypoint intact: extend the image, do not replace the entrypoint.
+3. Add your repo, docs, tools, and verification wrappers.
+4. Pre-run the expensive setup you do not want to repeat at task time.
+5. Publish the image to a registry and point the Replicated installer at it.
+
+
+ Do not override the entrypoint or replace the runtime contract of the base image. The installer
+ expects standard OpenHands agent-server behavior. Only extend, do not replace.
+
+
+### Base Image
+
+```dockerfile
+FROM ghcr.io/openhands/agent-server:1.23.0-python
+```
+
+Pin a specific version tag to ensure reproducible builds. Check
+[ghcr.io/openhands/agent-server](https://github.com/OpenHands/OpenHands/pkgs/container/agent-server)
+for the latest available tags.
+
+
+ To get the latest features of OpenHands Enterprise, rebuild your custom image before each upgrade. The agent server base image is updated with every OHE release.
+
+
+### Example: Build and Push
+
+```bash
+docker buildx build \
+ --platform linux/amd64 \
+ -f your-project/Dockerfile \
+ -t ghcr.io//openhands-custom-image: \
+ --push \
+ .
+```
+
+Use `--platform linux/amd64` because the Enterprise Replicated VM runs on `x86-64`.
+
+### What to Bake In
+
+Good candidates for prebaking:
+
+- Pinned repository checkouts
+- Package manager caches and installed dependencies (`node_modules`, Python virtualenvs, etc.)
+- Compiled or transpiled output
+- Native system packages (`xvfb`, `libkrb5-dev`, `pkg-config`, etc.)
+- Browser or Electron artifacts
+- Stable helper scripts such as `prepare-*` and `*-verify` wrappers
+
+### What to Keep Out
+
+
+ Do not bake the following into your image:
+
+ - Secrets, API keys, or personal credentials
+ - Machine-specific paths or environment assumptions
+ - Uncommitted source changes or task-specific fixes
+ - Rapidly changing dependencies (use a lightweight `prepare-*` helper instead)
+
+
+If the repository or dependencies change frequently, include a `prepare-*` script in the image
+so the agent can refresh only the parts that need updating without a full rebuild.
+
+## Configure the Replicated VM Installer
+
+Once your image is built and pushed to a registry, point the Replicated Admin Console at it.
+
+1. Open the **Admin Console** at `https://:30000`.
+2. Navigate to **Config** and find the **Sandbox Image** section.
+3. Set the following fields:
+
+| Field | Value |
+|---|---|
+| **Use a Custom Sandbox Image** | Enabled |
+| **Sandbox Image Repository** | Your image repository (e.g. `ghcr.io/your-org/openhands-custom-image`) |
+| **Sandbox Image Tag** | Your image tag (e.g. `v1.2.0`) |
+| **Registry Server** | If your registry requires authentication |
+| **Registry Username** | If your registry requires authentication |
+| **Registry Password or Credentials** | If your registry requires authentication |
+
+4. Click **Save config** and then **Deploy** to apply the change.
+
+
+ This setting applies to the **sandbox / agent-server image** only (the image that runs inside each
+ agent's isolated workspace). It does not replace the other OpenHands service images.
+
+
+## Reference
+
+- [OpenHands custom image example repo](https://github.com/OpenHands/openhands-custom-image): Dockerfile, benchmark scripts, and analysis tooling for the VS Code custom image example.
+- [Agent-server sandbox guide](https://docs.openhands.dev/sdk/guides/agent-server/docker-sandbox): full SDK documentation on building and configuring custom sandbox images.
+
### Enterprise vs. Open Source
Source: https://docs.openhands.dev/enterprise/enterprise-vs-oss.md
diff --git a/llms.txt b/llms.txt
index 73e74da8..f5956aef 100644
--- a/llms.txt
+++ b/llms.txt
@@ -129,7 +129,7 @@ from the OpenHands Software Agent SDK.
- [Creating Automations](https://docs.openhands.dev/openhands/usage/automations/creating-automations.md): Learn how to create scheduled automations using the Automation Skill.
- [Critic](https://docs.openhands.dev/openhands/usage/agent-canvas/critic.md): Configure critic evaluation and iterative refinement in Agent Canvas.
- [Custom LLM Configurations](https://docs.openhands.dev/openhands/usage/llms/custom-llm-configs.md): OpenHands supports defining multiple named LLM configurations in your `config.toml` file. This feature allows you to use different LLM configurations for different purposes, such as using a cheaper model for tasks that don't require high-quality responses, or using different models with different parameters for specific agents.
-- [Custom Sandbox](https://docs.openhands.dev/openhands/usage/advanced/custom-sandbox-guide.md): This guide is for users that would like to use their own custom Docker image for the runtime.
+- [Custom Sandbox](https://docs.openhands.dev/openhands/usage/advanced/custom-sandbox-guide.md): Build and use your own agent-server image when you need extra tools, system
- [Customize and Settings](https://docs.openhands.dev/openhands/usage/agent-canvas/customize-and-settings.md): Teach your agent with skills and MCP servers, and configure how it runs with settings.
- [Debugging](https://docs.openhands.dev/openhands/usage/developers/debugging.md)
- [Dependency Upgrades](https://docs.openhands.dev/openhands/usage/use-cases/dependency-upgrades.md): Automating dependency updates and upgrades with OpenHands
@@ -167,6 +167,7 @@ from the OpenHands Software Agent SDK.
- [OpenRouter](https://docs.openhands.dev/openhands/usage/llms/openrouter.md): OpenHands uses LiteLLM to make calls to chat models on OpenRouter. You can find their documentation on using OpenRouter as a provider [here](https://docs.litellm.ai/docs/providers/openrouter).
- [Overview](https://docs.openhands.dev/openhands/usage/llms/llms.md): OpenHands can connect to any LLM supported by LiteLLM. However, it requires a powerful model to work.
- [Overview](https://docs.openhands.dev/openhands/usage/sandboxes/overview.md): Where OpenHands runs code in V1: Docker sandbox, Process, or Remote.
+- [Phone & Tablet Access](https://docs.openhands.dev/openhands/usage/agent-canvas/mobile-access.md): Access Agent Canvas from a phone or tablet using Tailscale or ngrok.
- [Process Sandbox](https://docs.openhands.dev/openhands/usage/sandboxes/process.md): Run the agent server as a local process without container isolation.
- [Prompting Best Practices](https://docs.openhands.dev/openhands/usage/tips/prompting-best-practices.md): When working with OpenHands AI software developer, providing clear and effective prompts is key to getting accurate and useful responses. This guide outlines best practices for crafting effective prompts.
- [Remote Sandbox](https://docs.openhands.dev/openhands/usage/sandboxes/remote.md): Run conversations in a remote sandbox environment.
@@ -228,8 +229,9 @@ from the OpenHands Software Agent SDK.
## Other
-- [Analytics](https://docs.openhands.dev/enterprise/analytics.md): Get started with LLM observability and tracing in OpenHands Enterprise.
+- [Analytics](https://docs.openhands.dev/enterprise/analytics.md): Deploy Laminar for trace analysis in OpenHands Enterprise.
- [Bitbucket Data Center](https://docs.openhands.dev/enterprise/integrations/bitbucket-data-center.md): Configure Bitbucket Data Center authentication and repository webhooks for OpenHands Enterprise.
+- [Custom Sandbox Images](https://docs.openhands.dev/enterprise/custom-sandbox-image.md): Preload repos, dependencies, and tooling into a custom sandbox image to make your agents faster and more reliable.
- [Enterprise vs. Open Source](https://docs.openhands.dev/enterprise/enterprise-vs-oss.md): Compare OpenHands Enterprise and Open Source offerings to choose the right option for your team
- [External PostgreSQL](https://docs.openhands.dev/enterprise/external-postgres.md): Configure OpenHands Enterprise to use your own PostgreSQL database
- [Jira Data Center](https://docs.openhands.dev/enterprise/integrations/jira-data-center.md): Configure Jira Data Center for OpenHands Enterprise.