> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openhands.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenHands Cloud Workspace

> Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance.

> A ready-to-run example is available [here](#ready-to-run-example)!

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.

## Key Concepts

### OpenHandsCloudWorkspace

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

```python icon="python" focus={1-2} 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 icon="python" focus={4} 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 icon="python" focus={1-3} 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.

### Inheriting SaaS Credentials

Instead of providing your own `LLM_API_KEY`, you can inherit the LLM configuration and secrets from your OpenHands Cloud account. This means you only need `OPENHANDS_CLOUD_API_KEY` — no separate LLM key required.

#### `get_llm()`

Fetches your account's LLM settings (model, API key, base URL) and returns a ready-to-use `LLM` instance:

```python icon="python" focus={2-3} theme={null}
with OpenHandsCloudWorkspace(...) as workspace:
    llm = workspace.get_llm()
    agent = Agent(llm=llm, tools=get_default_tools())
```

You can override any parameter:

```python icon="python" theme={null}
llm = workspace.get_llm(model="gpt-4o", temperature=0.5)
```

Under the hood, `get_llm()` calls `GET /api/v1/users/me?expose_secrets=true`, sending your Cloud API key in the `Authorization` header plus the sandbox's `X-Session-API-Key`. That session key is issued by OpenHands Cloud for the running sandbox, so it scopes the request to that sandbox rather than acting like a separately provisioned second credential.

#### `get_secrets()`

Builds `LookupSecret` references for your SaaS-configured secrets. Raw values **never transit through the SDK client** — they are resolved lazily by the agent-server inside the sandbox:

```python icon="python" focus={2-3} theme={null}
with OpenHandsCloudWorkspace(...) as workspace:
    secrets = workspace.get_secrets()
    conversation.update_secrets(secrets)
```

You can also filter to specific secrets:

```python icon="python" theme={null}
gh_secrets = workspace.get_secrets(names=["GITHUB_TOKEN"])
```

<Tip>
  See the [SaaS Credentials example](#saas-credentials-example) below for a complete working example.
</Tip>

## 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          |

## Ready-to-run 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
```

## SaaS Credentials Example

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

This example demonstrates the simplified flow where your OpenHands Cloud account's LLM configuration and secrets are inherited automatically — no need to provide `LLM_API_KEY` separately:

```python icon="python" expandable examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py theme={null}
"""Example: Inherit SaaS credentials via OpenHandsCloudWorkspace.

This example shows the simplified flow where your OpenHands Cloud account's
LLM configuration and secrets are inherited automatically — no need to
provide LLM_API_KEY separately.

Compared to 07_convo_with_cloud_workspace.py (which requires a separate
LLM_API_KEY), this approach uses:
  - workspace.get_llm()     → fetches LLM config from your SaaS account
  - workspace.get_secrets()  → builds lazy LookupSecret references for your secrets

Raw secret values never transit through the SDK client. The agent-server
inside the sandbox resolves them on demand.

Usage:
  uv run examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py

Requirements:
  - OPENHANDS_CLOUD_API_KEY: API key for OpenHands Cloud (the only credential needed)

Optional:
  - OPENHANDS_CLOUD_API_URL: Override the Cloud API URL (default: https://app.all-hands.dev)
  - LLM_MODEL: Override the model from your SaaS settings
"""

import os
import time

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


logger = get_logger(__name__)


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:
    # --- LLM from SaaS account settings ---
    # get_llm() calls GET /users/me?expose_secrets=true,
    # sending your Cloud API key plus the sandbox session
    # key that OpenHands Cloud issued for this workspace.
    # It returns a fully configured LLM instance.
    # Override any parameter: workspace.get_llm(model="gpt-4o")
    llm = workspace.get_llm()
    logger.info(f"LLM configured: model={llm.model}")

    # --- Secrets from SaaS account ---
    # get_secrets() fetches secret *names* (not values) and builds LookupSecret
    # references. Values are resolved lazily inside the sandbox.
    secrets = workspace.get_secrets()
    logger.info(f"Available secrets: {list(secrets.keys())}")

    # Build agent and conversation
    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()

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

    # Inject SaaS secrets into the conversation
    if secrets:
        conversation.update_secrets(secrets)
        logger.info(f"Injected {len(secrets)} secrets into conversation")

    # Build a prompt that exercises the injected secrets by asking the agent to
    # print the last 50% of each token — proves values resolved without leaking
    # full secrets in logs.
    secret_names = list(secrets.keys()) if secrets else []
    if secret_names:
        names_str = ", ".join(f"${name}" for name in secret_names)
        prompt = (
            f"For each of these environment variables: {names_str} — "
            "print the variable name and the LAST 50% of its value "
            "(i.e. the second half of the string). "
            "Then write a short summary into SECRETS_CHECK.txt."
        )
    else:
        # No secret was configured on OpenHands Cloud
        prompt = "Tell me, is there any secret configured for you?"

    try:
        conversation.send_message(prompt)
        conversation.run()

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

        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 SaaS Credentials Example theme={null}
export OPENHANDS_CLOUD_API_KEY="your-cloud-api-key"
# Optional: override LLM model from your SaaS settings
# export LLM_MODEL="gpt-4o"
cd agent-sdk
uv run python examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py
```

## Settings and Secrets API Examples

The remote agent-server examples also include end-to-end scripts for settings-backed secrets and authenticated LLM configuration:

* [examples/02\_remote\_agent\_server/12\_settings\_and\_secrets\_api.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/12_settings_and_secrets_api.py) demonstrates storing secrets through the Settings and Secrets API, referencing them with `LookupSecret`, and cleaning them up after use.
* [examples/02\_remote\_agent\_server/13\_workspace\_get\_llm.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/13_workspace_get_llm.py) demonstrates configuring LLM settings on an authenticated agent-server and retrieving them through `RemoteWorkspace.get_llm()`.

<RunExampleCode path_to_script="examples/02_remote_agent_server/12_settings_and_secrets_api.py" />

<RunExampleCode path_to_script="examples/02_remote_agent_server/13_workspace_get_llm.py" />

## 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
