Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ OpenSandbox provides rich examples demonstrating sandbox usage in different scen
- **[iflow-cli](examples/iflow-cli/README.md)** - Run iFLow CLI inside OpenSandbox.
- **[langgraph](examples/langgraph/README.md)** - LangGraph state-machine workflow that creates/runs a sandbox job with fallback retry.
- **[google-adk](examples/google-adk/README.md)** - Google ADK agent using OpenSandbox tools to write/read files and run commands.
- **[nullclaw](examples/nullclaw/README.md)** - Launch a [Nullclaw](https://github.com/nullclaw/nullclaw) Gateway inside a sandbox.
- **[openclaw](examples/openclaw/README.md)** - Launch an OpenClaw Gateway inside a sandbox.

#### 🌐 Browser and Desktop Environments
Expand Down
1 change: 1 addition & 0 deletions docs/README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ OpenSandbox 提供了丰富的示例来演示不同场景下的沙箱使用方
- **[iflow-cli](../examples/iflow-cli/README.md)** - 在 OpenSandbox 中运行 iFlow CLI。
- **[langgraph](../examples/langgraph/README.md)** - 基于 LangGraph 状态机编排沙箱任务与回退重试。
- **[google-adk](../examples/google-adk/README.md)** - 使用 Google ADK 通过 OpenSandbox 工具读写文件并执行命令。
- **[nullclaw](../examples/nullclaw/README.md)** - 在沙箱中启动 Nullclaw Gateway。
- **[openclaw](../examples/openclaw/README.md)** - 在沙箱中启动 OpenClaw Gateway。

#### 🌐 浏览器与桌面环境
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Examples for common OpenSandbox use cases. Each subdirectory contains runnable c
- <img src="https://www.kimi.com/favicon.ico" alt="Kimi" width="16" height="16" style="display:inline-block;width:16px;height:16px;vertical-align:middle;margin-right:4px;" /> [**kimi-cli**](kimi-cli): Call Kimi Code CLI (Moonshot AI) within the sandbox
- <img src="https://img.shields.io/badge/-%20-1C3C3C?logo=langgraph&logoColor=white&style=flat-square" alt="LangGraph" width="16" height="16" style="display:inline-block;width:16px;height:16px;vertical-align:middle;margin-right:4px;" /> [**langgraph**](langgraph): LangGraph agent orchestrating sandbox lifecycle + tools
- <img src="https://google.github.io/adk-docs/assets/agent-development-kit.png" alt="Google ADK" width="16" height="16" style="display:inline-block;width:16px;height:16px;vertical-align:middle;margin-right:4px;" /> [**google-adk**](google-adk): Google ADK agent calling OpenSandbox tools
- 🦞 [**nullclaw**](nullclaw): Launch a Nullclaw Gateway inside a sandbox
- 🦞 [**openclaw**](openclaw): Run an OpenClaw Gateway inside a sandbox
- 🖥️ [**desktop**](desktop): Launch VNC desktop (Xvfb + x11vnc) for VNC client connections
- <img src="https://playwright.dev/img/playwright-logo.svg" alt="Playwright" width="16" height="16" style="display:inline-block;width:16px;height:16px;vertical-align:middle;margin-right:4px;" /> [**playwright**](playwright): Launch headless browser (Playwright + Chromium) to scrape web content
Expand Down
71 changes: 71 additions & 0 deletions examples/nullclaw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Nullclaw Gateway Example

Launch a [Nullclaw](https://github.com/nullclaw/nullclaw) Gateway inside an OpenSandbox instance and expose its HTTP endpoint. The script polls the gateway health check until it returns HTTP 200, then prints the reachable endpoint.

## Start OpenSandbox server [local]

You can find the latest Nullclaw container image [here](https://github.com/nullclaw/nullclaw/pkgs/container/nullclaw).

### Notes (Docker runtime requirement)

The server uses `runtime.type = "docker"` by default, so it **must** be able to reach a running Docker daemon.

- **Docker Desktop**: ensure Docker Desktop is running, then verify with `docker version`.
- **Colima (macOS)**: start it first (`colima start`) and export the socket before starting the server:

```shell
export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock"
```

Pre-pull the Nullclaw image:

```shell
docker pull ghcr.io/nullclaw/nullclaw:latest
```

Start the OpenSandbox server (logs will stay in the terminal):

```shell
uv pip install opensandbox-server
opensandbox-server init-config ~/.sandbox.toml --example docker
opensandbox-server
```

If you see errors like `FileNotFoundError: [Errno 2] No such file or directory` from `docker/transport/unixconn.py`, it usually means the Docker unix socket is missing or Docker is not running.

## Create and Access the Nullclaw Sandbox

This example is hard-coded for a quick start:
- OpenSandbox server: `http://localhost:8080`
- Image: `ghcr.io/nullclaw/nullclaw:latest`
- Gateway port: `3000`
- Timeout: `3600s`

Install dependencies from the project root:

```shell
uv pip install opensandbox requests
```

Run the example:

```shell
uv run python examples/nullclaw/main.py
```

You should see output similar to:

```text
Creating nullclaw sandbox with image=ghcr.io/nullclaw/nullclaw:latest on OpenSandbox server http://localhost:8080...
[check] sandbox ready after 0.3s
Nullclaw gateway started. Please refer to 127.0.0.1:56234
```

The endpoint printed at the end (e.g., `127.0.0.1:56234`) is the Nullclaw Gateway address exposed from the sandbox.

By default, Nullclaw requires pairing before authenticated endpoints (for example, `/webhook`) can be used. The `/health` endpoint remains publicly accessible.

## References
- [Nullclaw](https://github.com/nullclaw/nullclaw) — Minimal AI assistant runtime (678 KB static Zig binary)
- [Nullclaw Documentation](https://nullclaw.github.io) — Full documentation
- [OpenSandbox Python SDK](https://pypi.org/project/opensandbox/)
76 changes: 76 additions & 0 deletions examples/nullclaw/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright 2025 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import time
from datetime import timedelta

import requests
from opensandbox import SandboxSync
from opensandbox.config import ConnectionConfigSync
from opensandbox.models.sandboxes import NetworkPolicy, NetworkRule


def check_nullclaw(sbx: SandboxSync) -> bool:
"""
Health check: poll nullclaw gateway until it returns 200.

Returns:
True when ready
False on timeout or any exception
"""
try:
endpoint = sbx.get_endpoint(3000)
start = time.perf_counter()
url = f"http://{endpoint.endpoint}/health"
for _ in range(150): # max for ~30s
try:
resp = requests.get(url, timeout=1)
if resp.status_code == 200:
elapsed = time.perf_counter() - start
print(f"[check] sandbox ready after {elapsed:.1f}s")
return True
except Exception:
pass
time.sleep(0.2)
return False
except Exception as exc:
print(f"[check] failed: {exc}")
return False


def main() -> None:
server = "http://localhost:8080"
image = "ghcr.io/nullclaw/nullclaw:latest"
timeout_seconds = 3600 # 1 hour

print(f"Creating nullclaw sandbox with image={image} on OpenSandbox server {server}...")
sandbox = SandboxSync.create(
image=image,
timeout=timedelta(seconds=timeout_seconds),
metadata={"example": "nullclaw"},
connection_config=ConnectionConfigSync(domain=server),
health_check=check_nullclaw,
# use network policy to limit nullclaw network accesses
network_policy=NetworkPolicy(
defaultAction="deny",
egress=[NetworkRule(action="allow", target="openrouter.ai")],
),
)

endpoint = sandbox.get_endpoint(3000)
print(f"Nullclaw gateway started. Please refer to {endpoint.endpoint}")


if __name__ == "__main__":
main()