# OpenHands Cloud Workspace

> Connect to OpenHands Cloud for fully managed sandbox environments.

The `OpenHandsCloudWorkspace` demonstrates how to use the [OpenHands Cloud](https://app.all-hands.dev) to provision and manage sandboxed environments for agent execution. This provides a seamless experience with automatic sandbox provisioning, monitoring, and secure execution without managing your own infrastructure.

## Basic Example

<Note>
  This example is available on GitHub: [examples/02\_remote\_agent\_server/07\_convo\_with\_cloud\_workspace.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/07_convo_with_cloud_workspace.py)
</Note>

This example shows how to connect to OpenHands Cloud for fully managed agent execution:

```python icon="python" expandable examples/02_remote_agent_server/07_convo_with_cloud_workspace.py theme={null}
"""Example: OpenHandsCloudWorkspace for OpenHands Cloud API.

This example demonstrates using OpenHandsCloudWorkspace to provision a sandbox
via OpenHands Cloud (app.all-hands.dev) and run an agent conversation.

Usage:
  uv run examples/02_remote_agent_server/06_convo_with_cloud_workspace.py

Requirements:
  - LLM_API_KEY: API key for direct LLM provider access (e.g., Anthropic API key)
  - OPENHANDS_CLOUD_API_KEY: API key for OpenHands Cloud access

Note:
  The LLM configuration is sent to the cloud sandbox, so you need an API key
  that works directly with the LLM provider (not a local proxy). If using
  Anthropic, set LLM_API_KEY to your Anthropic API key.
"""

import os
import time

from pydantic import SecretStr

from openhands.sdk import (
    LLM,
    Conversation,
    RemoteConversation,
    get_logger,
)
from openhands.tools.preset.default import get_default_agent
from openhands.workspace import OpenHandsCloudWorkspace


logger = get_logger(__name__)


api_key = os.getenv("LLM_API_KEY")
assert api_key, "LLM_API_KEY required"

# Note: Don't use a local proxy URL here - the cloud sandbox needs direct access
# to the LLM provider. Use None for base_url to let LiteLLM use the default
# provider endpoint, or specify the provider's direct URL.
llm = LLM(
    usage_id="agent",
    model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"),
    base_url=os.getenv("LLM_BASE_URL") or None,
    api_key=SecretStr(api_key),
)

cloud_api_key = os.getenv("OPENHANDS_CLOUD_API_KEY")
if not cloud_api_key:
    logger.error("OPENHANDS_CLOUD_API_KEY required")
    exit(1)

cloud_api_url = os.getenv("OPENHANDS_CLOUD_API_URL", "https://app.all-hands.dev")
logger.info(f"Using OpenHands Cloud API: {cloud_api_url}")

with OpenHandsCloudWorkspace(
    cloud_api_url=cloud_api_url,
    cloud_api_key=cloud_api_key,
) as workspace:
    agent = get_default_agent(llm=llm, cli_mode=True)
    received_events: list = []
    last_event_time = {"ts": time.time()}

    def event_callback(event) -> None:
        received_events.append(event)
        last_event_time["ts"] = time.time()

    result = workspace.execute_command(
        "echo 'Hello from OpenHands Cloud sandbox!' && pwd"
    )
    logger.info(f"Command completed: {result.exit_code}, {result.stdout}")

    conversation = Conversation(
        agent=agent, workspace=workspace, callbacks=[event_callback]
    )
    assert isinstance(conversation, RemoteConversation)

    try:
        conversation.send_message(
            "Read the current repo and write 3 facts about the project into FACTS.txt."
        )
        conversation.run()

        while time.time() - last_event_time["ts"] < 2.0:
            time.sleep(0.1)

        conversation.send_message("Great! Now delete that file.")
        conversation.run()
        cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost
        print(f"EXAMPLE_COST: {cost}")
    finally:
        conversation.close()

    logger.info("✅ Conversation completed successfully.")
    logger.info(f"Total {len(received_events)} events received during conversation.")
```

```bash Running the Example theme={null}
export LLM_API_KEY="your-llm-api-key"
export OPENHANDS_CLOUD_API_KEY="your-cloud-api-key"
# Optional: specify a custom sandbox spec
# export OPENHANDS_SANDBOX_SPEC_ID="your-sandbox-spec-id"
cd agent-sdk
uv run python examples/02_remote_agent_server/07_convo_with_cloud_workspace.py
```

## Key Concepts

### OpenHandsCloudWorkspace

The `OpenHandsCloudWorkspace` connects to OpenHands Cloud to provision sandboxes:

```python highlight={1-4} theme={null}
with OpenHandsCloudWorkspace(
    cloud_api_url="https://app.all-hands.dev",
    cloud_api_key=cloud_api_key,
) as workspace:
```

This workspace type:

* Connects to OpenHands Cloud API
* Automatically provisions sandboxed environments
* Manages sandbox lifecycle (create, poll status, delete)
* Handles all infrastructure concerns

### Getting Your API Key

To use OpenHands Cloud, you need an API key:

1. Go to [app.all-hands.dev](https://app.all-hands.dev)
2. Sign in to your account
3. Navigate to Settings → API Keys
4. Create a new API key

Store this key securely and use it as the `OPENHANDS_CLOUD_API_KEY` environment variable.

### Configuration Options

The `OpenHandsCloudWorkspace` supports several configuration options:

| Parameter         | Type          | Default  | Description                                  |
| ----------------- | ------------- | -------- | -------------------------------------------- |
| `cloud_api_url`   | `str`         | Required | OpenHands Cloud API URL                      |
| `cloud_api_key`   | `str`         | Required | API key for authentication                   |
| `sandbox_spec_id` | `str \| None` | `None`   | Custom sandbox specification ID              |
| `init_timeout`    | `float`       | `300.0`  | Timeout for sandbox initialization (seconds) |
| `api_timeout`     | `float`       | `60.0`   | Timeout for API requests (seconds)           |
| `keep_alive`      | `bool`        | `False`  | Keep sandbox running after cleanup           |

### Keep Alive Mode

By default, the sandbox is deleted when the workspace is closed. To keep it running:

```python highlight={5} theme={null}
workspace = OpenHandsCloudWorkspace(
    cloud_api_url="https://app.all-hands.dev",
    cloud_api_key=cloud_api_key,
    keep_alive=True,
)
```

This is useful for debugging or when you want to inspect the sandbox state after execution.

### Workspace Testing

You can test the workspace before running the agent:

```python highlight={1-4} theme={null}
result = workspace.execute_command(
    "echo 'Hello from OpenHands Cloud sandbox!' && pwd"
)
logger.info(f"Command completed: {result.exit_code}, {result.stdout}")
```

This verifies connectivity to the cloud sandbox and ensures the environment is ready.

## Comparison with Other Workspace Types

| Feature        | OpenHandsCloudWorkspace | APIRemoteWorkspace          | DockerWorkspace            |
| -------------- | ----------------------- | --------------------------- | -------------------------- |
| Infrastructure | OpenHands Cloud         | Runtime API                 | Local Docker               |
| Authentication | API Key                 | API Key                     | None                       |
| Setup Required | None                    | Runtime API access          | Docker installed           |
| Custom Images  | Via sandbox specs       | Direct image specification  | Direct image specification |
| Best For       | Production use          | Custom runtime environments | Local development          |

## Next Steps

* **[API-based Sandbox](/sdk/guides/agent-server/api-sandbox)** - Connect to Runtime API service
* **[Docker Sandboxed Server](/sdk/guides/agent-server/docker-sandbox)** - Run locally with Docker
* **[Local Agent Server](/sdk/guides/agent-server/local-server)** - Development without containers
* **[Agent Server Overview](/sdk/guides/agent-server/overview)** - Architecture and implementation details


---

> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://docs.openhands.dev/llms.txt