File size: 12,391 Bytes
c8e832f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# 2. Deploying an OpenEnv environment

This section covers deploying OpenEnv environments locally, on clusters, and on Hugging Face Spaces.

**Contents:**
- [Local Development with Uvicorn](#local-development-with-uvicorn)
- [Docker Deployment](#docker-deployment)
- [Hugging Face Spaces](#hugging-face-spaces)
- [Best Practices](#best-practices)

## HF Spaces are the infrastructure for OpenEnv environments

Every HF Space provides three things that OpenEnv environments need:

| Component | What it provides | How to access | Used as |
|-----------|------------------|---------------|-----------|
| **Server** | Running environment endpoint | `https://<username>-<space-name>.hf.space` | Agent and Public API |
| **Repository** | Installable Python package | `pip install git+https://huggingface.co/spaces/<username>-<space-name>` | Code and client |
| **Registry** | Docker container image | `docker pull registry.hf.space/<username>-<space-name>:latest` | Deployment |

This means a single Space deployment gives you all the components you need to use an environment in training.

### 1. Server: A running environment endpoint

When you deploy to HF Spaces, your environment runs as a server. The client connects via **WebSocket** (`/ws`) for a persistent session:

```python

from echo_env import EchoEnv, EchoAction



# Connect directly to the running Space (WebSocket under the hood)

# Async (recommended):

async with EchoEnv(base_url="https://openenv-echo-env.hf.space") as client:

    result = await client.reset()

    result = await client.step(EchoAction(message="Hello"))



# Sync (using .sync() wrapper):

with EchoEnv(base_url="https://openenv-echo-env.hf.space").sync() as client:

    result = client.reset()

    result = client.step(EchoAction(message="Hello"))

```

**Endpoints available:**

| Endpoint | Protocol | Description |
|----------|----------|-------------|
| `/ws` | **WebSocket** | Persistent session (used by client) |
| `/health` | HTTP GET | Health check |
| `/reset` | HTTP POST | Reset environment (stateless) |
| `/step` | HTTP POST | Execute action (stateless) |
| `/state` | HTTP GET | Get current state |
| `/docs` | HTTP GET | OpenAPI documentation |
| `/web` | HTTP GET | Interactive web UI |

> **Note:** The Python client uses the `/ws` WebSocket endpoint by default. HTTP endpoints are available for debugging or stateless use cases.

**Example: Check if a Space is running**

```bash

curl https://openenv-echo-env.hf.space/health

# {"status": "healthy"}

```

### 2. Repository: Installable Python package

Every Space is a Git repository. OpenEnv environments include a `pyproject.toml`, making them pip-installable directly from the Space URL.

```bash

# Install client package from Space

pip install git+https://huggingface.co/spaces/openenv/echo-env

```

This installs:
- **Client class** (`EchoEnv`) โ€” Handles HTTP/WebSocket communication
- **Models** (`EchoAction`, `EchoObservation`) โ€” Typed action and observation classes
- **Utilities** โ€” Any helper functions the environment provides

**After installation:**

```python

from envs.echo_env import EchoEnv, EchoAction, EchoObservation



# Now you have typed classes for the environment

action = EchoAction(message="Hello")

```

### 3. Registry: Docker container image

Every Docker-based Space has a container registry. You can pull and run the environment locally.

```bash

# Pull the image

docker pull registry.hf.space/openenv-echo-env:latest



# Run locally on port 8001

docker run -d -p 8001:8000 registry.hf.space/openenv-echo-env:latest

```

**Find the registry URL for any Space:**

1. Go to the Space page (e.g., [openenv/echo-env](https://huggingface.co/spaces/openenv/echo-env))
2. Click **โ‹ฎ** (three dots) โ†’ **"Run locally"**
3. Copy the `docker run` command

### Choosing an access method

| Method | Use when | Pros | Cons |
|--------|----------|------|------|
| **Server** | Quick testing, low volume | Zero setup | Network latency, rate limits |
| **Repository** | Need typed classes | Type safety, IDE support | Still need a server |
| **Docker** | Local dev, high throughput | Full control, no network | Requires Docker |

**Typical workflow:**

```python

import asyncio

from echo_env import EchoEnv, EchoAction



async def main():

    # Development: connect to remote Space

    async with EchoEnv(base_url="https://openenv-echo-env.hf.space") as client:

        result = await client.reset()



    # Production: run locally for speed

    # docker run -d -p 8001:8000 registry.hf.space/openenv-echo-env:latest

    async with EchoEnv(base_url="http://localhost:8001") as client:

        result = await client.reset()



    # Or let the client manage Docker for you

    client = await EchoEnv.from_env("openenv/echo-env")  # Auto-pulls and runs

    async with client:

        result = await client.reset()



asyncio.run(main())



# For sync usage, use the .sync() wrapper:

with EchoEnv(base_url="http://localhost:8001").sync() as client:

    result = client.reset()

```

> **Reference:** [HF Spaces Documentation](https://huggingface.co/docs/hub/spaces) | [Environment Hub Collection](https://huggingface.co/collections/openenv/environment-hub)


## Local Development with Uvicorn

The fastest way to iterate on environment logic is running directly with Uvicorn.

## Clone and run the environment locally

```bash

# Clone from HF Space

git clone https://huggingface.co/spaces/burtenshaw/openenv-benchmark

cd openenv-benchmark



# Install in editable mode

uv sync



# Start server

uv run server



# Run isolated from remote Space

uv run --isolated --project https://huggingface.co/spaces/burtenshaw/openenv-benchmark server

```

## Uvicorn directly in python

```bash

# Full control over uvicorn options

uvicorn benchmark.server.app:app --host "$HOST" --port "$PORT" --workers "$WORKERS"



# With reload for development

uvicorn benchmark.server.app:app --host 0.0.0.0 --port 8000 --reload



# Multi-Worker Mode For better concurrency:

uvicorn benchmark.server.app:app --host 0.0.0.0 --port 8000 --workers 4

```

| Flag | Purpose |
|------|---------|
| `--reload` | Auto-restart on code changes |
| `--workers N` | Run N worker processes |
| `--log-level debug` | Verbose logging |

## Docker Deployment

Docker provides isolation and reproducibility for production use.

### Run the environment locally from the space

```bash

# Run the environment locally from the space

docker run -d -p 8000:8000 registry.hf.space/openenv-echo-env:latest

```

### Build Image

```bash

# Clone from HF Space

git clone https://huggingface.co/spaces/burtenshaw/openenv-benchmark

cd openenv-benchmark



# Using OpenEnv CLI (recommended)

openenv build -t openenv-benchmark:latest



# Or with Docker directly

docker build -t openenv-benchmark:latest -f server/Dockerfile .

```

### Run Container

```bash

# Basic run

docker run -d -p 8000:8000 my-env:latest



# With environment variables

docker run -d -p 8000:8000 \

    -e WORKERS=4 \

    -e MAX_CONCURRENT_ENVS=100 \

    my-env:latest



# Named container for easy management

docker run -d --name my-env -p 8000:8000 my-env:latest

```

### Connect from Python

```python

import asyncio

from echo_env import EchoEnv, EchoAction



async def main():

    # Async usage (recommended)

    async with EchoEnv(base_url="http://localhost:8000") as client:

        result = await client.reset()

        result = await client.step(EchoAction(message="Hello"))

        print(result.observation)



    # From Docker image

    client = await EchoEnv.from_docker_image("<local_docker_image>")

    async with client:

        result = await client.reset()

        print(result.observation)



asyncio.run(main())



# Sync usage (using .sync() wrapper)

with EchoEnv(base_url="http://localhost:8000").sync() as client:

    result = client.reset()

    result = client.step(EchoAction(message="Hello"))

    print(result.observation)

```

### Container Lifecycle

| Method | Container | WebSocket | On `close()` |
|--------|-----------|-----------|--------------|
| `from_hub(repo_id)` | Starts | Connects | Stops container |
| `from_hub(repo_id, use_docker=False)` | None (UV) | Connects | Stops UV server |
| `from_docker_image(image)` | Starts | Connects | Stops container |
| `MyEnv(base_url=...)` | None | Connects | Disconnects only |

Find Docker Commands for Any Space

1. Open the Space on HuggingFace Hub
2. Click **โ‹ฎ (three dots)** menu
3. Select **"Run locally"**
4. Copy the provided `docker run` command

## Deploy with CLI

```bash

cd my_env



# Deploy to your namespace

openenv push



# Deploy to specific repo

openenv push --repo-id username/my-env



# Deploy as private

openenv push --repo-id username/my-env --private

```

### Space Configuration

The `openenv.yaml` manifest controls Space settings:

```yaml

# openenv.yaml

name: my_env

version: "1.0.0"

description: My custom environment

```

Hardware Options:

| Tier | vCPU | RAM | Cost |
|------|------|-----|------|
| CPU Basic (Free) | 2 | 16GB | Free |
| CPU Upgrade | 8 | 32GB | $0.03/hr |

OpenEnv environments support configuration via environment variables.

| Variable | Default | Description |
|----------|---------|-------------|
| `WORKERS` | 4 | Uvicorn worker processes |
| `PORT` | 8000 | Server port |
| `HOST` | 0.0.0.0 | Bind address |
| `MAX_CONCURRENT_ENVS` | 100 | Max WebSocket sessions |
| `ENABLE_WEB_INTERFACE` | Auto | Enable web UI |

### Environment-Specific Variables

Some environments have custom variables:

**TextArena:**
```bash

TEXTARENA_ENV_ID=Wordle-v0

TEXTARENA_NUM_PLAYERS=1

TEXTARENA_MAX_TURNS=6

```

**Coding Environment:**
```bash

SANDBOX_TIMEOUT=30

MAX_OUTPUT_LENGTH=10000

```

# DEMO: Deploying to Hugging Face Spaces

This demo walks through the full workflow: create an environment, test locally, deploy to HF Spaces, and use it.

## Step 1: Initialize a new environment

```bash

openenv init my_env

cd my_env

```

This creates the standard OpenEnv structure:

```

my_env/

โ”œโ”€โ”€ server/

โ”‚   โ”œโ”€โ”€ app.py           # FastAPI server

โ”‚   โ”œโ”€โ”€ environment.py   # Your environment logic

โ”‚   โ””โ”€โ”€ Dockerfile

โ”œโ”€โ”€ models.py            # Action/Observation types

โ”œโ”€โ”€ client.py            # HTTP client

โ”œโ”€โ”€ openenv.yaml         # Manifest

โ””โ”€โ”€ pyproject.toml

```

## Step 2: Run locally

```bash

# Start the server

uv run server



# Or with uvicorn directly

uvicorn server.app:app --host 0.0.0.0 --port 8000 --reload

```

Test the health endpoint:

```bash

curl http://localhost:8000/health

# {"status": "healthy"}

```

## Step 3: Deploy to HF Spaces

```bash

openenv push --repo-id username/my-env

```

Your environment is now live at:
- Web UI: https://username-my-env.hf.space/web
- API Docs: https://username-my-env.hf.space/docs
- Health: https://username-my-env.hf.space/health

```bash

curl https://openenv-echo-env.hf.space/health

# {"status": "healthy"}

```

## Step 4: install the environment

```bash

uv pip install git+https://huggingface.co/spaces/openenv/echo_env

```

## Step 5: Run locally via Docker (optional)

Pull and run the container from the HF registry, or open the [browser](https://huggingface.co/spaces/openenv/echo_env?docker=true):

```bash

# Pull from HF Spaces registry

docker pull registry.hf.space/openenv-echo-env:latest



# Run locally

docker run -it -p 7860:7860 --platform=linux/amd64 \

	registry.hf.space/openenv-echo-env:latest

```

Now connect to your local instance:

```python

import asyncio

from echo_env import EchoEnv, EchoAction



# Async (recommended)

async def main():

    async with EchoEnv(base_url="http://localhost:8000") as env:

        result = await env.reset()

        print(result.observation)

        result = await env.step(EchoAction(message="Hello"))

        print(result.observation)



asyncio.run(main())



# Sync (using .sync() wrapper)

with EchoEnv(base_url="http://localhost:8000").sync() as env:

    result = env.reset()

    print(result.observation)

    result = env.step(EchoAction(message="Hello"))

    print(result.observation)

```