Skip to content
Open
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
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ dependencies = [
"kubernetes>=35.0.0",
"marimo>=0.23.6",
"moutils>=0.3.12",
"openai>=2.46.0",
"ray>=2.53.0",
"ruamel-yaml>=0.19.1",
"statistics>=1.0.3.5",
"torch>=2.10.0",
"transformers>=5.0.0",
"typing-extensions>=4.15.0",
"wandb>=0.24.2",
"wandb[sandbox]>=0.28.1",
"weave>=0.53.2",
]

[dependency-groups]
Expand Down
65 changes: 65 additions & 0 deletions sandboxes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# CoreWeave Sandboxes Examples

This directory contains marimo notebooks and scripts demonstrating different use cases for [CoreWeave Sandboxes](https://docs.coreweave.com/products/sandboxes), isolated, on-demand execution environments for agentic workloads.

## Notebooks

### 1. [`serverless-sandboxes-tutorial.py`](./serverless-sandboxes-tutorial.py)

An end-to-end code-evaluation workflow: a hosted model (via Serverless Inference's OpenAI-compatible API) generates Python for benchmark tasks, the generated code runs against deterministic tests inside a Serverless Sandbox, and every step is traced and scored in W&B Weave for side-by-side model comparison.

**Use Case:** Evaluating hosted code-generation models with safe, isolated code execution.

### 2. [`harness-evals.py`](./harness-evals.py)

Evaluates full coding-agent CLIs (Codex, Claude Code, OpenClaw, Nous Hermes) that each live inside their own sandbox. Solutions are scored in a separate, network-isolated sandbox against demo tasks or the HumanEval / MBPP benchmarks, with per-agent `weave.Evaluation` runs.

**Use Case:** Benchmarking and comparing agent harnesses on coding tasks at scale.

### 3. [`devin-outpost.py`](./devin-outpost.py)

Creates a Serverless Sandbox from Devin's official CLI image and connects it as
a single Linux worker for an existing Devin Outpost, with bounded resources and
explicit cleanup.

**Use Case:** Running Devin sessions inside an isolated, on-demand development
environment.

### 4. [`claude-remote-control-tutorial.py`](./claude-remote-control-tutorial.py)

A step-by-step, interactive tutorial for turning a Serverless Sandbox into the
remote machine your Claude Code session runs on. Walks through connecting W&B,
creating the sandbox (`Sandbox.run()`) with public ingress on port 8080,
installing Claude Code (`sandbox.exec()`), signing in and launching
[Remote Control](https://code.claude.com/docs/en/remote-control) over a PTY
(`sandbox.shell()`), then having Claude build and serve a live website reachable
at `sandbox.service_address`, and finally cleaning up (`sandbox.stop()`). The
OAuth login and the `Enable Remote Control?` prompt are handled inline in the
notebook.

**Use Case:** Steering Claude Code from [claude.ai/code](https://claude.ai/code)
or the Claude mobile app while execution stays in a cloud sandbox.

## Scripts

### 1. [`claude-remote-control-script.py`](./claude-remote-control-script.py)

The compact, no-frills version of the tutorial above: provisions a sandbox,
installs Claude Code, pre-trusts `/workspace`, and attaches your local terminal
to a PTY inside the sandbox so you can sign in and run `claude remote-control`.
Stops the sandbox on exit.

**Use Case:** Launching a remote Claude Code session from your terminal in one
command, without the notebook UI.

## Getting Started

1. From the repo root, install dependencies: `uv sync`
2. Open a notebook in the marimo editor: `uv run marimo edit sandboxes/serverless-sandboxes-tutorial.py`
3. When prompted about inlined package dependencies, answer `n` to use the project environment (or `Y` for an isolated venv built from the notebook's inline dependencies).

Scripts run directly in the project environment:

```bash
uv run python sandboxes/claude-remote-control-script.py
```
Binary file added sandboxes/assets/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
118 changes: 118 additions & 0 deletions sandboxes/claude-remote-control-script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Create a sandbox, sign in to Claude Code, and run `claude remote-control`.
"""

from __future__ import annotations

import fcntl
import os
import signal
import struct
import sys
import termios
import threading
import tty

os.environ.setdefault("WANDB_SILENT", "true")

from wandb.sandbox import NetworkOptions, ResourceOptions, Sandbox, SandboxDefaults # noqa: E402

# Pre-seeding ~/.claude.json marks /workspace as trusted and skips first-run onboarding
CLAUDE_JSON = '{"hasCompletedOnboarding": true, "projects": {"/workspace": {"hasTrustDialogAccepted": true}}}'
BOOTSTRAP = (
"npm install -g @anthropic-ai/claude-code --silent "
"&& mkdir -p /workspace "
f"&& printf '%s' '{CLAUDE_JSON}' > ~/.claude.json"
)
# Sign in first (full-scope claude.ai session, stored in the pod's ~/.claude),
# then serve Remote Control from /workspace.
RUN = "claude auth login && cd /workspace && exec claude remote-control"


def terminal_size() -> tuple[int, int]:
try:
rows, cols = struct.unpack("hh", fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, b"\0" * 4))
return cols or 100, rows or 30
except (OSError, struct.error):
return 100, 30


def attach(sandbox: Sandbox, command: list[str]) -> int:
"""Bridge the local terminal to a PTY inside the sandbox."""
cols, rows = terminal_size()
session = sandbox.shell(command, width=cols, height=rows)

def pump_output() -> None:
try:
for chunk in session.output:
sys.stdout.buffer.write(chunk)
sys.stdout.buffer.flush()
except Exception: # noqa: BLE001 - session closing is the normal exit path
pass

threading.Thread(target=pump_output, daemon=True).start()

def on_resize(*_: object) -> None:
new_cols, new_rows = terminal_size()
try:
session.resize(new_cols, new_rows)
except Exception: # noqa: BLE001 - resize is cosmetic
pass

signal.signal(signal.SIGWINCH, on_resize)

fd = sys.stdin.fileno()
saved = termios.tcgetattr(fd)
try:
tty.setraw(fd)
while True:
data = os.read(fd, 1024)
if not data:
break
try:
session.stdin.write(data).result()
except Exception: # noqa: BLE001 - remote session ended; stop forwarding
break
except (OSError, KeyboardInterrupt):
pass
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, saved)

try:
return session.wait(timeout=10)
except Exception: # noqa: BLE001
return 0


def main() -> int:
print("Creating sandbox...", flush=True)
sandbox = Sandbox.run(
defaults=SandboxDefaults(
container_image="node:22",
tags=("claude-code", "remote-control"),
resources=ResourceOptions(requests={"cpu": "2", "memory": "4Gi"}),
),
network=NetworkOptions(egress_mode="internet"),
max_lifetime_seconds=4 * 3600,
)
sandbox.wait()
print(f" {sandbox.sandbox_id} (expires in 4h)", flush=True)

print("Installing Claude Code...", flush=True)
setup = sandbox.exec(["bash", "-lc", BOOTSTRAP], timeout_seconds=900)
setup.wait(timeout=900)
if setup.returncode != 0:
sys.stderr.write(setup.result().stderr_bytes.decode(errors="replace"))
sandbox.stop(missing_ok=True).result()
return 1

print("Sign in when prompted, then Remote Control starts. Ctrl-C stops it.\n", flush=True)
code = attach(sandbox, ["bash", "-lc", RUN])

print("\nStopping sandbox...", flush=True)
sandbox.stop(missing_ok=True).result()
return code


if __name__ == "__main__":
sys.exit(main())
Loading