diff --git a/README.md b/README.md index 5b12ea0203..5b81f177ef 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ async def main(): async with EchoEnv(base_url="https://openenv-echo-env.hf.space") as client: # Reset the environment result = await client.reset() - print(result.observation.echoed_message) # "Echo environment ready!" + print(result.observation.metadata["message"]) # "Echo environment ready!" # Send messages result = await client.step( @@ -174,9 +174,8 @@ Base class for environment communication: #### 4. Container Providers Manage container deployment: - `LocalDockerProvider`: Run containers on local Docker daemon -- `DockerSwarmProvider`: Deploy to Docker Swarm clusters -- `UVProvider`, `DaytonaProvider`, `ACASandboxProvider`: Additional runtime providers -- `KubernetesProvider`: Deploy to Kubernetes clusters (planned) +- `UVProvider`: Run an environment's server from its project directory, without Docker +- Docker Swarm, Daytona, Modal, Novita, Azure Container Apps and Hugging Face sandboxes: see [Runtime Providers](https://huggingface.co/docs/openenv/guides/runtime-providers) #### 5. Models Type-safe data structures: @@ -270,10 +269,13 @@ The OpenEnv CLI provides commands to manage environments: - **`openenv init `** - Initialize a new environment from template - **`openenv import --name --output-dir `** - Wrap a supported third-party source environment, including ORS/OpenReward and Verifiers, as OpenEnv - **`openenv push [--repo-id ] [--private]`** - Deploy environment to Hugging Face Spaces -- **`openenv serve`** - Serve an environment locally with optional auto-reload - **`openenv build`** - Build the Docker image for an environment - **`openenv fork `** - Fork a Space from HF Hub to your account - **`openenv validate`** - Validate an environment configuration +- **`openenv harbor`** - Serve and run Harbor tasks with token-level capture (`info`, `rollout`, `serve`, `push`) +- **`openenv collect`** - Collect rollouts from a deployed environment +- **`openenv catalog`** / **`openenv discover`** - Build and search environment catalogs +- **`openenv skills`** - Manage OpenEnv skills for AI assistants ### Quick Start @@ -318,7 +320,7 @@ uv pip install pytest PYTHONPATH=src:envs uv run pytest tests/ -v --tb=short # Run a specific test file -PYTHONPATH=src:envs uv run pytest tests/envs/test_echo_environment.py -v +PYTHONPATH=src:envs uv run pytest tests/envs/test_chess_environment.py -v ``` **To run environment-specific tests**, install that environment's dependencies: diff --git a/docs/source/contributing.md b/docs/source/contributing.md index db4b5be551..0e5f59b38a 100644 --- a/docs/source/contributing.md +++ b/docs/source/contributing.md @@ -53,7 +53,7 @@ Documentation improvements are always welcome: 1. Fork the repository 2. Create a feature branch: `git checkout -b feature/my-feature` 3. Make your changes -4. Run tests: `pytest` +4. Run tests: `PYTHONPATH=src:envs uv run pytest tests/` 5. Submit a pull request ## Development Setup @@ -63,14 +63,15 @@ Documentation improvements are always welcome: git clone https://github.com/YOUR_USERNAME/OpenEnv.git cd OpenEnv -# Install in development mode -pip install -e ".[dev]" +# Install in development mode, with the dev tools +uv sync # Run tests -pytest +PYTHONPATH=src:envs uv run pytest tests/ # Run linting -ruff check . +uv run ruff check src/ tests/ +uv run ruff format --check src/ tests/ ``` ## Code Style diff --git a/docs/source/environments/browsergym.md b/docs/source/environments/browsergym.md index d232520357..22621cf339 100644 --- a/docs/source/environments/browsergym.md +++ b/docs/source/environments/browsergym.md @@ -32,7 +32,7 @@ from browsergym_env import BrowserGymEnv, BrowserGymAction # Create environment for MiniWoB training task env = BrowserGymEnv.from_docker_image( - "ghcr.io/openenv/browsergym-env:latest", + "ghcr.io/huggingface/openenv-browsergym-env:latest", environment={ "BROWSERGYM_BENCHMARK": "miniwob", "BROWSERGYM_TASK_NAME": "click-test", # or "click-button", "click-dialog", etc. @@ -261,7 +261,7 @@ from envs.browsergym_env import BrowserGymEnv, BrowserGymAction # Create environment for WebArena evaluation env = BrowserGymEnv.from_docker_image( - "ghcr.io/openenv/browsergym-env:latest", + "ghcr.io/huggingface/openenv-browsergym-env:latest", environment={ "BROWSERGYM_BENCHMARK": "webarena", "BROWSERGYM_TASK_NAME": "0", # Task ID @@ -560,7 +560,7 @@ python app.py browsergym_env/ ├── __init__.py # Module exports ├── models.py # Action, Observation, State dataclasses -├── client.py # HTTPEnvClient implementation +├── client.py # BrowserGymEnv client (EnvClient) ├── README.md # This file └── server/ ├── __init__.py diff --git a/docs/source/environments/connect4.md b/docs/source/environments/connect4.md index 6accc891b6..dce6ad0bda 100644 --- a/docs/source/environments/connect4.md +++ b/docs/source/environments/connect4.md @@ -11,9 +11,9 @@ from connect4_env import Connect4Action, Connect4Env async def main(): async with Connect4Env(base_url="http://localhost:8000") as client: - obs = await client.reset() - print(f"Board: {obs.board}") - print(f"Legal moves: {obs.legal_actions}") + result = await client.reset() + print(f"Board: {result.observation.board}") + print(f"Legal moves: {result.observation.legal_actions}") # Drop a piece in column 3 result = await client.step(Connect4Action(column=3)) diff --git a/docs/source/environments/finqa.md b/docs/source/environments/finqa.md index fdd5e5d3db..ba06ac7289 100644 --- a/docs/source/environments/finqa.md +++ b/docs/source/environments/finqa.md @@ -56,9 +56,9 @@ from envs.finqa_env import FinQAEnv, CallToolAction async def main(): async with FinQAEnv(base_url="http://localhost:8000") as env: # Reset to get a question - obs = await env.reset() - question = obs.metadata["question"] - company = obs.metadata["company"] + result = await env.reset() + question = result.observation.metadata["question"] + company = result.observation.metadata["company"] print(f"Question: {question}") print(f"Company: {company}") @@ -76,7 +76,8 @@ async def main(): arguments={ "company_name": "alphabet", "table_name": "us_gaap_ScheduleOfIncomeBeforeIncomeTaxDomesticAndForeignTableTextBlock", - "query": "SELECT * FROM data WHERE year = '2022'" + # Name the columns you need (see get_table_info); SELECT * is rejected + "query": "SELECT FROM data WHERE year = '2022'" } )) print(f"Done: {step_result.done}, Reward: {step_result.reward}") diff --git a/docs/source/environments/finrl.md b/docs/source/environments/finrl.md index d4ec065bcf..b16a1750c3 100644 --- a/docs/source/environments/finrl.md +++ b/docs/source/environments/finrl.md @@ -122,7 +122,7 @@ client.close() │ │ │ │ ┌────────▼────────┐ │ │ │ FinRLEnv │ ← HTTP Client │ -│ │ (HTTPEnvClient) │ │ +│ │ (EnvClient) │ │ │ └────────┬────────┘ │ └────────────────────────────┼─────────────────────────────────┘ │ HTTP (JSON) diff --git a/docs/source/environments/kernrl.md b/docs/source/environments/kernrl.md index 7d3b5f395d..22bb9b2a5c 100644 --- a/docs/source/environments/kernrl.md +++ b/docs/source/environments/kernrl.md @@ -34,8 +34,8 @@ from kernrl import KernelAction, kernrl_env env = kernrl_env(base_url="http://localhost:8000") # Start episode -obs = env.reset(problem_id="L1_23_Softmax") -print(obs.problem_description) +result = env.reset(problem_id="L1_23_Softmax") +print(result.observation.problem_description) # Submit a kernel action = KernelAction(code=''' diff --git a/docs/source/environments/maze.md b/docs/source/environments/maze.md index 99fc89011b..458df42382 100644 --- a/docs/source/environments/maze.md +++ b/docs/source/environments/maze.md @@ -71,7 +71,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -85,7 +85,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) diff --git a/docs/source/environments/openapp.md b/docs/source/environments/openapp.md index 632087d233..442208efa0 100644 --- a/docs/source/environments/openapp.md +++ b/docs/source/environments/openapp.md @@ -68,7 +68,7 @@ openapp_env/ **Key Components:** -- **client.py**: `OpenAppEnv` class that extends `HTTPEnvClient` for remote environment interaction +- **client.py**: `OpenAppEnv` class that extends `EnvClient` for remote environment interaction - **models.py**: `OpenAppAction` and `OpenAppObservation` dataclasses with validation - **server/openapp_environment.py**: `OpenAppEnvironment` class that wraps BrowserGym and OpenApps - **server/app.py**: FastAPI server that exposes the environment via HTTP endpoints diff --git a/docs/source/environments/openspiel.md b/docs/source/environments/openspiel.md index ffda1f2541..4881adc067 100644 --- a/docs/source/environments/openspiel.md +++ b/docs/source/environments/openspiel.md @@ -88,7 +88,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -102,7 +102,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) diff --git a/docs/source/environments/reasoning_gym.md b/docs/source/environments/reasoning_gym.md index 529e29ff1b..8f4000273d 100644 --- a/docs/source/environments/reasoning_gym.md +++ b/docs/source/environments/reasoning_gym.md @@ -71,7 +71,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -86,7 +86,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) diff --git a/docs/source/environments/snake.md b/docs/source/environments/snake.md index 5727623740..c37bfea447 100644 --- a/docs/source/environments/snake.md +++ b/docs/source/environments/snake.md @@ -60,7 +60,7 @@ client.close() ```bash # Install dependencies -cd src/envs/snake_env +cd envs/snake_env pip install -e . # Run server @@ -240,7 +240,7 @@ pip install git+https://github.com/kc-ml2/marlenv.git Make sure you're in the correct directory when running the server: ```bash -cd src/envs/snake_env +cd envs/snake_env uv run --project . server ``` diff --git a/docs/source/environments/textarena.md b/docs/source/environments/textarena.md index 1f496867cd..43ea0cc36f 100644 --- a/docs/source/environments/textarena.md +++ b/docs/source/environments/textarena.md @@ -98,7 +98,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -112,7 +112,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) diff --git a/docs/source/environments/websearch.md b/docs/source/environments/websearch.md index bd232205ff..b9c6e7a30d 100644 --- a/docs/source/environments/websearch.md +++ b/docs/source/environments/websearch.md @@ -39,15 +39,14 @@ This environment requires a Serper.dev API key to function. ## Quick Start -The simplest way to use the Web Search environment is through the `WebSearchEnvironment` class: +The simplest way to use the Web Search environment is through the `WebSearchEnv` client: ```python -from envs.websearch_env.server.websearch_env_environment import WebSearchEnvironment -from envs.websearch_env import WebSearchAction +from envs.websearch_env import WebSearchAction, WebSearchEnv try: # Create environment from Docker image - web_search_env = WebSearchEnvironment.from_docker_image("web_search-env:latest") + web_search_env = WebSearchEnv.from_docker_image("web_search-env:latest") # Reset result = web_search_env.reset() @@ -65,7 +64,7 @@ finally: web_search_env.close() ``` -That's it! The `WebSearchEnvironment.from_docker_image()` method handles: +That's it! The `WebSearchEnv.from_docker_image()` method handles: - Starting the Docker container - Waiting for the server to be ready - Connecting to the environment @@ -90,7 +89,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -104,7 +103,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) @@ -164,10 +163,10 @@ The reward is undefined here. If you already have a Web Search environment server running, you can connect directly: ```python -from envs.websearch_env import WebSearchEnvironment +from envs.websearch_env import WebSearchAction, WebSearchEnv # Connect to existing server -web_search_env = WebSearchEnvironment(base_url="") +web_search_env = WebSearchEnv(base_url="") # Use as normal result = web_search_env.reset() diff --git a/docs/source/environments/wildfire.md b/docs/source/environments/wildfire.md index b46b47690e..554a6781d3 100644 --- a/docs/source/environments/wildfire.md +++ b/docs/source/environments/wildfire.md @@ -447,7 +447,7 @@ This approach: docker build -t openenv-base:latest -f src/openenv/core/containers/images/Dockerfile . # Build wildfire environment using the script -cd src/envs/wildfire_env/server +cd envs/wildfire_env/server ./build_docker.sh # Run container @@ -1014,7 +1014,7 @@ docker build -t wildfire-env:latest -f envs/wildfire_env/server/Dockerfile . - **Main Repository**: [OpenEnv GitHub](https://github.com/openenv) - **Documentation**: See `rfcs/` directory for design documents -- **Other Environments**: See `src/envs/` for more environment examples +- **Other Environments**: See `envs/` for more environment examples ### Related Tools diff --git a/docs/source/getting_started/contributing-envs.md b/docs/source/getting_started/contributing-envs.md index 35db2b1d97..1e57d35830 100644 --- a/docs/source/getting_started/contributing-envs.md +++ b/docs/source/getting_started/contributing-envs.md @@ -4,7 +4,7 @@ OpenEnv environments are designed to be shared. The `openenv` CLI provides first commands for publishing, forking, and contributing to environments hosted as [Hugging Face Spaces](https://huggingface.co/spaces). -Envs are deployed as Hugging Face Spaces which are; Git repositories, Docker images, Python packages, and Gradio apps +Envs are deployed as Hugging Face Spaces, which are Git repositories, Docker images, Python packages and Gradio apps at once. This guide covers three workflows: @@ -33,7 +33,7 @@ The `openenv` CLI will also prompt you to log in automatically if you haven't al Once you've [built an environment](environment-builder.md), publishing it to a Hugging Face Space is a single command. ```bash -# Push the env at '.' to the hub with config in env.yaml +# Push the env at '.' to the hub with config in openenv.yaml openenv push # Push the env to a specific repo @@ -61,7 +61,7 @@ Forking creates a copy of a Hugging Face Space under your own account. This is the fastest way to start experimenting with an existing environment. ```bash -# Fork the openenv/wordle-env environment to your account +# Fork the openenv/wordle environment to your account openenv fork owner/space-name ``` @@ -69,15 +69,15 @@ This duplicates the Space to `/space-name` using the same name an ```bash # Fork to a specific repo name -openenv fork openenv/wordle-env --repo-id my-username/my-wordle +openenv fork openenv/wordle --repo-id my-username/my-wordle -# Fork the openenv/coding-env environment to your account with environment variables and secrets -openenv fork openenv/coding-env \ +# Fork the openenv/coding_env environment to your account with environment variables and secrets +openenv fork openenv/coding_env \ --set-env MODEL_ID=meta-llama/Llama-3-8B \ --set-secret HF_TOKEN=hf_xxxxxxxxxxxxx -# Fork the openenv/coding-env environment to your account with a GPU -openenv fork openenv/coding-env --hardware t4-medium +# Fork the openenv/coding_env environment to your account with a GPU +openenv fork openenv/coding_env --hardware t4-medium ``` @@ -167,7 +167,7 @@ submitting a PR. ```bash # Fork the echo environment -openenv fork openenv/echo-env --repo-id my-username/echo-env-improved +openenv fork openenv/echo_env --repo-id my-username/echo-env-improved # Clone your fork git clone https://huggingface.co/spaces/my-username/echo-env-improved @@ -180,7 +180,7 @@ cd echo-env-improved openenv validate --level static --skip-build # Push your improvement as a PR to the original -openenv push --repo-id openenv/echo-env --create-pr +openenv push --repo-id openenv/echo_env --create-pr ``` ## Next Steps diff --git a/docs/source/getting_started/environment-builder.md b/docs/source/getting_started/environment-builder.md index 73348e2493..266e180272 100644 --- a/docs/source/getting_started/environment-builder.md +++ b/docs/source/getting_started/environment-builder.md @@ -15,7 +15,7 @@ Already familiar with OpenEnv? Here's the 8-step process at a glance: |------|------------------|-------------| | 1 | `openenv init my_env` | Scaffold new environment | | 2 | Edit `models.py` | Define Action & Observation dataclasses | -| 3 | Edit `server/my_environment.py` | Implement `reset()` and `step()` methods | +| 3 | Edit `server/my_env_environment.py` | Implement `reset()` and `step()` methods | | 4 | Edit `client.py` | Implement `_step_payload()`, `_parse_result()`, `_parse_state()` | | 5 | `uv run --project . server` | Start local dev server for testing | | 6 | `openenv validate --level static --skip-build` | Validate the declared manifest contract | @@ -58,7 +58,7 @@ A typical workflow looks like: - Python 3.11+ and [`uv`](https://github.com/astral-sh/uv) for dependency locking - Docker Desktop / Docker Engine -- The OpenEnv library installed: `pip install https://github.com/huggingface/OpenEnv.git` +- The OpenEnv library installed: `pip install openenv` ## Step-by-Step Guide @@ -90,7 +90,7 @@ my_env/ └── server/ ├── __init__.py ├── app.py - ├── my_environment.py + ├── my_env_environment.py ├── requirements.txt └── Dockerfile ``` @@ -127,10 +127,10 @@ class MyObservation(Observation): ### 3. Implement Environment Logic -Customize `server/my_environment.py` by extending `Environment`: +Customize `server/my_env_environment.py` by extending `Environment`: ```python -# server/my_environment.py +# server/my_env_environment.py from uuid import uuid4 from openenv.core.env_server.interfaces import Environment from openenv.core.env_server.types import State @@ -165,7 +165,7 @@ class MyEnvironment(Environment): # server/app.py from openenv.core.env_server import create_app from ..models import MyAction, MyObservation -from .my_environment import MyEnvironment +from .my_env_environment import MyEnvironment # Pass the class (factory) - each WebSocket session gets its own instance app = create_app(MyEnvironment, MyAction, MyObservation, env_name="my_env") @@ -178,7 +178,7 @@ For environments with constructor arguments, create a factory function: import os from openenv.core.env_server import create_app from ..models import MyAction, MyObservation -from .my_environment import MyEnvironment +from .my_env_environment import MyEnvironment # Read config from environment variables api_key = os.getenv("MY_API_KEY") @@ -236,78 +236,7 @@ The CLI template ships with `pyproject.toml` and `server/Dockerfile`. You should Keep building from the `openenv-base` image so shared tooling stays available: -
-Dockerfile - -```dockerfile -# SPDX-License-Identifier: BSD-3-Clause - -# Multi-stage build using openenv-base -# This Dockerfile is flexible and works for both: -# - In-repo environments (with local src/core) -# - Standalone environments (with openenv from pip) -# The build script (openenv build) handles context detection and sets appropriate build args. - -ARG BASE_IMAGE=openenv-base:latest -FROM ${BASE_IMAGE} AS builder - -WORKDIR /app - -# Build argument to control whether we're building standalone or in-repo -ARG BUILD_MODE=in-repo -ARG ENV_NAME=__ENV_NAME__ - -# Copy environment code (always at root of build context) -COPY . /app/env - -# For in-repo builds, openenv is already in the pyproject.toml dependencies -# For standalone builds, openenv will be installed from pip via pyproject.toml -WORKDIR /app/env - -# Install dependencies using uv sync -# If uv.lock exists, use it; otherwise resolve on the fly -RUN --mount=type=cache,target=/root/.cache/uv \ - if [ -f uv.lock ]; then \ - uv sync --frozen --no-install-project --no-editable; \ - else \ - uv sync --no-install-project --no-editable; \ - fi - -RUN --mount=type=cache,target=/root/.cache/uv \ - if [ -f uv.lock ]; then \ - uv sync --frozen --no-editable; \ - else \ - uv sync --no-editable; \ - fi - -# Final runtime stage -FROM ${BASE_IMAGE} - -WORKDIR /app - -# Copy the virtual environment from builder -COPY --from=builder /app/env/.venv /app/.venv - -# Copy the environment code -COPY --from=builder /app/env /app/env - -# Set PATH to use the virtual environment -ENV PATH="/app/.venv/bin:$PATH" - -# Set PYTHONPATH so imports work correctly -ENV PYTHONPATH="/app/env:$PYTHONPATH" - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:8000/health || exit 1 - -# Run the FastAPI server -# The module path is constructed to work with the /app/env structure -CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"] - -``` - -
+The template's [`server/Dockerfile`](https://github.com/huggingface/OpenEnv/blob/main/src/openenv/cli/templates/openenv_env/server/Dockerfile) is a multi-stage build on `ghcr.io/huggingface/openenv-base` that installs your dependencies with `uv sync` and serves `server.app:app` on port 8000. `openenv build` works out whether you're building in the repo or standalone and sets its build arguments. If you introduced extra dependencies in the Dockerfile, you should install them in the Dockerfile before removing temp files. @@ -347,7 +276,7 @@ openenv push openenv push --repo-id my-org/my-env # Push to Docker/ghcr (interface disabled by default) -openenv push --registry ghcr.io/my-org --tag my-env:latest +openenv push --registry ghcr.io/my-org # Customize image base or visibility openenv push --base-image ghcr.io/huggingface/openenv-base:latest --private @@ -358,7 +287,7 @@ openenv push -e OPENSPIEL_GAME=tic_tac_toe --secret OPENAI_API_KEY=sk-... Key options: -- `--directory`: path to the environment (defaults to `cwd`) +- `DIRECTORY` (positional): path to the environment (defaults to the current directory) - `--repo-id`: explicit Hugging Face space name - `--registry`: push to Docker Hub, GHCR, etc. - `--interface/--no-interface`: toggle the optional web UI @@ -366,6 +295,7 @@ Key options: - `--private`: mark the space as private - `--env-var/-e KEY=VALUE`: set a public Space variable (repeatable); overrides matching keys from `variables:` in `openenv.yaml` - `--secret KEY=VALUE`: set a private Space secret (repeatable); value is never logged +- `--hardware/-H`, `--count/-n`, `--create-pr`, `--exclude`: Space hardware, number of Space instances, open a PR instead of pushing, and an ignore file with globs to leave out of the upload The command validates your `openenv.yaml`, injects Hugging Face frontmatter when needed, and uploads the prepared bundle. Space variables and secrets are only applied on direct Hugging Face Space pushes; @@ -409,7 +339,7 @@ strategy: Here is a simple example of using your environment: ```python -from envs.my_env import MyAction, MyEnv +from my_env import MyAction, MyEnv # Create environment from Docker image (starts a container) client = MyEnv.from_docker_image("my-env:latest").sync() @@ -454,7 +384,7 @@ The equivalent async usage is: ```python import asyncio -from envs.my_env import MyAction, MyEnv +from my_env import MyAction, MyEnv async def main(): diff --git a/docs/source/guides/auto-discovery.md b/docs/source/guides/auto-discovery.md index 1ab6744080..50afad1ebd 100644 --- a/docs/source/guides/auto-discovery.md +++ b/docs/source/guides/auto-discovery.md @@ -11,7 +11,7 @@ The auto-discovery system provides two main classes: Both classes work with: -- **Local packages**: Installed via `pip install openenv-` +- **Local packages**: Environment packages installed from the repo (`pip install -e envs/`) or from their Space (`pip install git+https://huggingface.co/spaces//`) - **HuggingFace Hub**: Environments hosted on HuggingFace Spaces ## Quick Start @@ -166,7 +166,7 @@ Alias for `from_env()` for backward compatibility. ```python from openenv import AutoAction -CodeAction = AutoAction.from_env("coding") +CodeAction = AutoAction.from_hub("coding") action = CodeAction(code="x = 5 + 3") ``` @@ -382,8 +382,8 @@ The auto-discovery system works by: Environments are distributed as installable Python packages: ```bash -# Install an environment -pip install openenv-coding-env +# Install an environment from the repo +pip install -e envs/coding_env # Now it's automatically discoverable python -c "from openenv import AutoEnv; AutoEnv.list_environments()" @@ -398,27 +398,14 @@ Each environment package includes: ### Manifest Format -Each environment includes an `openenv.yaml` file: +Each environment includes an `openenv.yaml` file. The client, action and observation classes are found by naming convention (`coding_env` → `CodingEnv`, `CodingAction`, `CodingObservation`). When an environment's classes don't follow it, the manifest names them: ```yaml name: coding_env -version: 0.1.0 -description: Coding environment for OpenEnv - -client: - class_name: CodingEnv - module: coding_env.client - -action: - class_name: CodeAction - module: coding_env.client - -observation: - class_name: CodeObservation - module: coding_env.client - -default_image: coding-env:latest -spec_version: 1 +version: "0.1.0" +description: "Coding environment for OpenEnv" +action: CodeAction +observation: CodeObservation ``` ## Benefits diff --git a/docs/source/guides/concepts.md b/docs/source/guides/concepts.md index bb6f373b0a..5406224211 100644 --- a/docs/source/guides/concepts.md +++ b/docs/source/guides/concepts.md @@ -65,13 +65,13 @@ provides both async and sync clients. ```python from openenv import AutoEnv -env = AutoEnv.from_env("coding") - -async with env as client: +# Async +async with AutoEnv.from_env("coding") as client: result = await client.reset() result = await client.step(action) -with env.sync() as client: +# Sync, with its own client (a client is locked to the mode it is first used in) +with AutoEnv.from_env("coding").sync() as client: result = client.reset() result = client.step(action) ``` @@ -106,40 +106,40 @@ Every OpenEnv environment consists of: ``` my_env/ -├── openenv.yaml # Manifest file -├── my_env/ -│ ├── __init__.py -│ ├── client.py # Client classes -│ ├── server.py # Server/Environment -│ └── models.py # Pydantic models -├── Dockerfile # Container definition -├── pyproject.toml # Package metadata -└── README.md # Documentation +├── openenv.yaml # Manifest file +├── __init__.py # Exports the client and models +├── client.py # Client class +├── models.py # Action and Observation types +├── pyproject.toml # Package metadata +├── README.md # Documentation (and the Space card) +└── server/ + ├── app.py # FastAPI app built with create_app + ├── my_env_environment.py # The Environment: reset() and step() + └── Dockerfile # Container definition ``` +`openenv init my_env` generates this layout. + ### The Manifest (openenv.yaml) ```yaml +spec_version: 1 name: my_env version: 0.1.0 -description: My custom environment - -client: - class_name: MyEnvClient - module: my_env.client - -action: - class_name: MyAction - module: my_env.models - -observation: - class_name: MyObservation - module: my_env.models - -default_image: my-env:latest -spec_version: 1 +type: space +runtime: fastapi +app: server.app:app +port: 8000 +validation: + reward: + range: [0.0, 1.0] + capabilities: + verifier: + kind: reward_channel ``` +`openenv init` writes the full manifest, including the resource limits checked by `openenv validate`. The client, action and observation classes are found by naming convention (`MyEnv`, `MyAction`, `MyObservation`), so the manifest doesn't list them. + ### Models (Pydantic) Custom `Action`, `Observation`, and `State` types subclass the base classes from `openenv.core.env_server.types` — not `pydantic.BaseModel` directly. The base `Observation` already carries `done` and `reward` fields, which `step()` populates; `Action` and `State` add metadata plumbing used by the server. diff --git a/docs/source/guides/customizing-web-ui.md b/docs/source/guides/customizing-web-ui.md index 51b0af8b11..1e6dd20fe7 100644 --- a/docs/source/guides/customizing-web-ui.md +++ b/docs/source/guides/customizing-web-ui.md @@ -1,10 +1,10 @@ # Custom Web UI -When `ENABLE_WEB_INTERFACE=true`, the server serves a default Gradio app at `/web` with Reset/Step/Get state, Quick Start, and README. Environment authors can **add** a custom tab by providing a custom Gradio builder. +The web UI is off by default. When `ENABLE_WEB_INTERFACE=true` (which `openenv push` sets for Spaces), the server serves a default Gradio app at `/web` with Reset/Step/Get state, Quick Start, and README. Environment authors can **add** a custom tab by providing a custom Gradio builder. ## Extension point: `gradio_builder` -`create_app()` accepts an optional **`gradio_builder`** callable. When set, the UI at `/web` is built with [Gradio’s TabbedInterface](https://www.gradio.app/4.44.1/docs/gradio/tabbedinterface): the **first tab (“Playground”)** is the default OpenEnv UI, and the **second tab (“Custom”)** is the `gr.Blocks` returned by your builder. Users can switch between the default Playground and your custom interface without losing either. The same `/web/reset`, `/web/step`, `/web/state`, and `/web/metadata` API routes remain available; your custom tab can use the provided `web_manager` in-process or call those endpoints. +`create_app()` accepts an optional **`gradio_builder`** callable. When set, the UI at `/web` is built with [Gradio’s TabbedInterface](https://www.gradio.app/docs/gradio/tabbedinterface): by default the **first tab (“Playground”)** is the default OpenEnv UI, and the **second tab (“Custom”)** is the `gr.Blocks` returned by your builder (see [Naming and ordering the tabs](#naming-and-ordering-the-tabs) to change this). Users can switch between the default Playground and your custom interface without losing either. The same `/web/reset`, `/web/step`, `/web/state`, and `/web/metadata` API routes remain available; your custom tab can use the provided `web_manager` in-process or call those endpoints. ### Builder signature @@ -20,7 +20,18 @@ def my_gradio_builder( ... ``` -Return a `gr.Blocks` instance. It is shown in the **“Custom”** tab of a tabbed interface; the **“Playground”** tab always shows the default OpenEnv UI. Core applies the same theme/css when mounting. +Return a `gr.Blocks` instance. By default it is shown in the **“Custom”** tab, next to the **“Playground”** tab with the default OpenEnv UI. Core applies the same theme/css when mounting. + +### Naming and ordering the tabs + +`create_app()` takes a few options for the custom UI: + +| Option | Default | Effect | +|---|---|---| +| `custom_tab_name` | `"Custom"` | Label of your tab | +| `custom_tab_primary` | `False` | Show your tab first, before Playground | +| `show_default_tab` | `True` | When `False`, mount only your builder's UI, with no Playground and no tabs | +| `title_override` | `None` | App and browser-tab title, instead of `"OpenEnv Agentic Environment: {name}"` | --- @@ -69,7 +80,7 @@ So you can influence the default UI by ensuring `metadata` and README are correc Environments that currently override `/web` with custom HTML (e.g. by removing the default route and adding a GET `/web` that returns HTML) should migrate to a **gradio_builder** that returns a `gr.Blocks` app. The custom UI then appears in the **“Custom”** tab alongside the default **“Playground”** tab. Benefits: -- Single, supported extension point using [TabbedInterface](https://www.gradio.app/4.44.1/docs/gradio/tabbedinterface). +- Single, supported extension point using [TabbedInterface](https://www.gradio.app/docs/gradio/tabbedinterface). - No need to remove or override routes; the default UI stays in the first tab. - Same `/web` path; both tabs can use `web_manager` or `/web/reset`, `/web/step`, `/web/state`. diff --git a/docs/source/guides/mcp-environment-lifecycle.md b/docs/source/guides/mcp-environment-lifecycle.md index a31315ae5b..bc93a3fd98 100644 --- a/docs/source/guides/mcp-environment-lifecycle.md +++ b/docs/source/guides/mcp-environment-lifecycle.md @@ -91,7 +91,8 @@ These are helpers, not a separate environment lifecycle. `MCPToolClient` and its base `MCPClientBase` only support `mode="production"`; construction raises `ValueError` for other modes. For direct in-process training or eval code, instantiate the environment class and call `env.step(CallToolAction(...))` instead of using `MCPToolClient`. -- `list_tools()` wraps `step(ListToolsAction())` and returns the `list[Tool]` directly. +- `list_tools()` and `call_tool()` go to the HTTP `/mcp` JSON-RPC endpoint, so they don't run through `step()`: no reward, no step count, no `done`. +- `list_tools()` returns a `list[Tool]`, and an empty list if the request fails. - `call_tool(name, **kwargs)` returns the **unwrapped tool return value** directly — not the `CallToolObservation`, and not the runtime result object you would get from `obs.result`. - `call_tool()` raises `RuntimeError` on any tool error. Use `step(CallToolAction(...))` when you need to inspect `ToolError.error_type` or continue after a failed tool call. @@ -99,9 +100,9 @@ Remote `step(CallToolAction(...))` calls return a `StepResult`; the full observa ### Direct MCP behavior -When production MCP access is explicitly enabled on the client, the same convenience methods use the HTTP `/mcp` JSON-RPC endpoint directly. +The `/mcp` path is for tool-serving behavior, not the training loop. It bypasses reward computation, step counts, trajectory tracking, and `done` handling. For training, send tool calls as `step(CallToolAction(...))`. -That path is for tool-serving behavior, not the training loop. It bypasses reward computation, step counts, trajectory tracking, and `done` handling. +Tool calls sent through `step()` time out after 30 seconds by default (`MCP_TOOL_CALL_TIMEOUT`). `MCPEnvironment.step()` takes a `timeout_s`, so an environment whose tools wait on slow work, such as an LLM call, can pass a larger default from its own `step()`. ## Which Pattern Should You Use? @@ -113,12 +114,12 @@ Use `step(CallToolAction(...))` when you need the full `CallToolObservation`: - `obs.result`, a runtime result object typed as `Any`; FastMCP commonly returns `fastmcp.client.client.CallToolResult` with `.data`, `.content`, and `.structured_content`, but serialized clients or custom envs may surface a dict or plain value - trajectory-compatible behavior -Use `await env.call_tool(name, **kwargs)` when you only want the tool's raw return value and do not need to inspect the full observation. It is async and unwraps the result for you. +Use `await env.call_tool(name, **kwargs)` when you only want the tool's raw return value and do not need rewards or the full observation. It is async, unwraps the result for you, and goes through `/mcp` rather than `step()`. In other words: - `step(...)` is the canonical simulation pattern -- `call_tool()` is an async convenience wrapper that returns the unwrapped tool output +- `call_tool()` is an async convenience wrapper over `/mcp` that returns the unwrapped tool output ## Concrete Examples @@ -151,7 +152,7 @@ If an MCP environment "doesn't call step", check these first: 1. Are you using an async client path that triggers `step_async()`? 2. Did you instrument both `step()` and `step_async()`? -3. Are you using `call_tool()` and assuming it bypasses the step loop? +3. Are you using `call_tool()`? It goes through `/mcp`, so it never reaches `step()`. Use `step(CallToolAction(...))` instead. 4. Are you expecting the MCP tool layer to behave like a separate environment lifecycle? Usually the action is flowing correctly, but through the async WebSocket path rather than the synchronous method you were watching. diff --git a/docs/source/guides/runtime-providers.md b/docs/source/guides/runtime-providers.md index ff061c3ed7..8ca4de1fae 100644 --- a/docs/source/guides/runtime-providers.md +++ b/docs/source/guides/runtime-providers.md @@ -16,7 +16,8 @@ is a one-line change. | `ACASandboxProvider` | Azure Container Apps Sandboxes | `pip install openenv[aca]` | ✅ | | `ModalProvider` | Modal sandboxes | `pip install openenv[modal]` | ✅ | | `NovitaSandboxProvider` | Novita AI sandboxes | `pip install openenv[novita]` | ✅ | -| `KubernetesProvider` | Kubernetes cluster | core | 🚧 planned | + +A `KubernetesProvider` is planned but not available yet. Cloud-provider SDKs are optional extras, imported lazily, so installing core OpenEnv pulls in no cloud SDK. The core providers (`LocalDockerProvider`, @@ -92,14 +93,14 @@ concurrently with `asyncio.gather`, wrapping the blocking provider calls in `asyncio.to_thread` since most cloud SDKs are synchronous: ```python -async def run_one(env_id: int, image) -> str: +async def run_one(env_id: int, image) -> dict: provider = DaytonaProvider() base_url = await asyncio.to_thread(provider.start_container, image) try: await asyncio.to_thread(provider.wait_for_ready, base_url, 300) async with MyEnv(base_url=base_url, provider=provider) as env: result = await env.reset() - return result.observation.text + return result.observation.metadata finally: await asyncio.to_thread(provider.stop_container) diff --git a/docs/source/guides/task-api.md b/docs/source/guides/task-api.md index fe7557a95f..82b4580655 100644 --- a/docs/source/guides/task-api.md +++ b/docs/source/guides/task-api.md @@ -203,20 +203,24 @@ The core clients (`EnvClient`, `SyncEnvClient`) do not ship Task API methods, be ```python import requests -from urllib.parse import urljoin ENV_NAME = "latex_ocr_env" class LatexOCREnv(EnvClient[LatexOCRAction, LatexOCRObservation, State]): + def _task_url(self, path: str) -> str: + # `base_url` may be ws(s)://, and the Task API is plain HTTP. + base = self.base_url.replace("ws://", "http://", 1).replace("wss://", "https://", 1) + return f"{base}/{ENV_NAME}/{path}" + def list_splits(self) -> list[str]: - resp = requests.get(urljoin(self._http_base(), f"{ENV_NAME}/splits"), timeout=30) + resp = requests.get(self._task_url("splits"), timeout=30) resp.raise_for_status() return [s["name"] for s in resp.json()] def num_tasks(self, split: str) -> int: resp = requests.post( - urljoin(self._http_base(), f"{ENV_NAME}/num_tasks"), + self._task_url("num_tasks"), json={"split": split}, timeout=60, ) diff --git a/docs/source/tutorials/end-to-end-walkthrough.md b/docs/source/tutorials/end-to-end-walkthrough.md index 63bbc1e69e..42dcbd65cd 100644 --- a/docs/source/tutorials/end-to-end-walkthrough.md +++ b/docs/source/tutorials/end-to-end-walkthrough.md @@ -264,7 +264,7 @@ Open the Trackio Space linked in the trainer logs to follow the run live. A heal - **`reward`** climbs from your baseline toward `1.0` over the first ~100 steps. A flat line near 0 means the task is too hard for the base model; a flat line near 1 means it's too easy — adjust `DATASET_CONFIG` in either case. - **`reward_std`** starts moderate and *drops* as the policy converges (most rollouts succeed). Persistent zero means every rollout in the group gives the same score → no advantage signal → no learning. Bump `num_generations` or task difficulty. - **`frac_reward_zero_std`** is the fraction of groups where every rollout has the same reward — when it climbs toward 1.0 you've saturated. -- **`entropy`** stays low while the model is learning. Once `reward` saturates, `entropy` typically rises again because the policy gradient is zero and only the KL penalty against the reference model is active — at that point further training is net-negative. Stop with a kernel interrupt or trust `max_steps`. +- **`entropy`** stays low while the model is learning. Once `reward` saturates, `entropy` typically rises again: most groups have zero advantage, so the remaining updates come from the few groups that still differ and from optimizer momentum — at that point further training is net-negative. Stop with a kernel interrupt or trust `max_steps`. - **`grad_norm`** decays toward zero as gradients become uninformative; same saturation signal. Once training finishes, the model in the running process has been fine-tuned in place. @@ -306,7 +306,7 @@ A delta of **+10 to +30 pp** is what you should expect at this difficulty; outsi - **Δ ≈ 0 pp, initial already high (≥90%)** — `DATASET_CONFIG` is too easy; the model already solves it before training. Bump `min_terms` / `min_digits`. - **Δ ≈ 0 pp, initial very low (≤20%)** — task is too hard for the base model to ever stumble onto a correct answer, so GRPO has no positive rollouts to learn from. Lower `min_terms` / `min_digits`. If the reward stays near zero even at minimum difficulty, the bottleneck is likely **format compliance** rather than task difficulty — the model never produces a valid `` so the env cannot score it. See the [SFT warm-up tutorial](sft-warmup) for how to fix this before returning to GRPO. -- **Δ negative** — you trained past saturation: once `reward` plateaus, the KL penalty starts pulling the policy back toward the reference. Reduce `max_steps` so training stops while it's still net-improving. +- **Δ negative** — you trained past saturation: once `reward` plateaus, the few groups that still carry signal move the policy without improving it. Reduce `max_steps` so training stops while it's still net-improving. > [!NOTE] > This delta is measured *during training* — same prompt format, same env, same procedural distribution that produced each rollout. It's the most direct way to ask "did the policy improve over the run?". A more rigorous protocol — generating completions on a held-out split with a separate evaluation harness — is what frameworks like [Inspect AI](https://inspect.aisi.org.uk/) are designed for; that's a follow-up rather than part of this walkthrough. diff --git a/docs/source/tutorials/mcp-environment.md b/docs/source/tutorials/mcp-environment.md index 4e79fa710c..64f9ac3a3d 100644 --- a/docs/source/tutorials/mcp-environment.md +++ b/docs/source/tutorials/mcp-environment.md @@ -43,7 +43,7 @@ That is the only MCP-specific piece. Everything around it — how the trainer ge ### Framework-agnostic rollout loop -If you drive the rollout yourself (a custom loop, [torchforge](https://github.com/pytorch-labs/torchforge), an external agent server), you own the full generation path and call `env.step()` directly: +If you drive the rollout yourself (a custom loop, [torchforge](https://github.com/meta-pytorch/torchforge), an external agent server), you own the full generation path and call `env.step()` directly: ```python obs = env.reset() diff --git a/docs/source/tutorials/openenv-tutorial.md b/docs/source/tutorials/openenv-tutorial.md index 7762a133b6..9703daf489 100644 --- a/docs/source/tutorials/openenv-tutorial.md +++ b/docs/source/tutorials/openenv-tutorial.md @@ -106,13 +106,13 @@ Focus on experiments, use OpenEnvironments, and build agents that go beyond Cart ### Hands-On Demo -- [Part 6: Interactive Demo 🎮](#part-6-using-real-openspiel) +- [Part 6: Using Real OpenSpiel 🎮](#part-6-using-real-openspiel) - [Part 7: Four Policies 🤖](#part-7-four-policies) - [Part 8: Policy Competition! 🏆](#part-8-policy-competition) ### Advanced -- [Part 9: Using Real OpenSpiel 🎮](#part-9-switching-to-other-games) +- [Part 9: Switching to Other Games 🎮](#part-9-switching-to-other-games) - [Part 10: Create Your Own Integration 🛠️](#part-10-create-your-own-integration) ### Wrap Up @@ -1212,7 +1212,8 @@ OpenEnv includes 3 complete examples: --- -(summary-your-journey)= + + ## 🎓 Summary: Your Journey ### What You Learned diff --git a/docs/source/tutorials/sft-warmup.md b/docs/source/tutorials/sft-warmup.md index 279837bb23..82873231ab 100644 --- a/docs/source/tutorials/sft-warmup.md +++ b/docs/source/tutorials/sft-warmup.md @@ -427,7 +427,7 @@ MODEL_NAME = "Qwen/Qwen3-1.7B" MODEL_NAME = f"{YOUR_HF_USERNAME}/reasoning-gym-chain-sum-Qwen3-1.7B-sft" ``` -With format compliance already near 100%, GRPO's `reward_std` will be non-zero from the very first +With most completions already in the right format, GRPO's `reward_std` will be non-zero from the very first batch and the reward curve will climb immediately — no cold-start stall. **Other directions:** diff --git a/docs/source/tutorials/wordle-grpo.md b/docs/source/tutorials/wordle-grpo.md index d9388ffdb5..6d31272a88 100644 --- a/docs/source/tutorials/wordle-grpo.md +++ b/docs/source/tutorials/wordle-grpo.md @@ -283,7 +283,7 @@ from transformers import AutoModelForCausalLM, AutoTokenizer model_name = "sergiopaniego/wordle-grpo-Qwen3-1.7B" # Replace with your HF username or organization -fine_tuned_model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype="float32", device_map="auto") +fine_tuned_model = AutoModelForCausalLM.from_pretrained(model_name, dtype="float32", device_map="auto") tokenizer = AutoTokenizer.from_pretrained(model_name) ``` diff --git a/envs/browsergym_env/README.md b/envs/browsergym_env/README.md index 6e6b3aa5fd..f8c96aae8c 100644 --- a/envs/browsergym_env/README.md +++ b/envs/browsergym_env/README.md @@ -47,7 +47,7 @@ from browsergym_env import BrowserGymEnv, BrowserGymAction # Create environment for MiniWoB training task env = BrowserGymEnv.from_docker_image( - "ghcr.io/openenv/browsergym-env:latest", + "ghcr.io/huggingface/openenv-browsergym-env:latest", environment={ "BROWSERGYM_BENCHMARK": "miniwob", "BROWSERGYM_TASK_NAME": "click-test", # or "click-button", "click-dialog", etc. @@ -276,7 +276,7 @@ from envs.browsergym_env import BrowserGymEnv, BrowserGymAction # Create environment for WebArena evaluation env = BrowserGymEnv.from_docker_image( - "ghcr.io/openenv/browsergym-env:latest", + "ghcr.io/huggingface/openenv-browsergym-env:latest", environment={ "BROWSERGYM_BENCHMARK": "webarena", "BROWSERGYM_TASK_NAME": "0", # Task ID @@ -575,7 +575,7 @@ python app.py browsergym_env/ ├── __init__.py # Module exports ├── models.py # Action, Observation, State dataclasses -├── client.py # HTTPEnvClient implementation +├── client.py # BrowserGymEnv client (EnvClient) ├── README.md # This file └── server/ ├── __init__.py diff --git a/envs/connect4_env/README.md b/envs/connect4_env/README.md index ca128074fa..6e77b21ebe 100644 --- a/envs/connect4_env/README.md +++ b/envs/connect4_env/README.md @@ -23,9 +23,9 @@ from connect4_env import Connect4Action, Connect4Env async def main(): async with Connect4Env(base_url="http://localhost:8000") as client: - obs = await client.reset() - print(f"Board: {obs.board}") - print(f"Legal moves: {obs.legal_actions}") + result = await client.reset() + print(f"Board: {result.observation.board}") + print(f"Legal moves: {result.observation.legal_actions}") # Drop a piece in column 3 result = await client.step(Connect4Action(column=3)) diff --git a/envs/finqa_env/README.md b/envs/finqa_env/README.md index 7ed74b7ab8..9f0681f9d0 100644 --- a/envs/finqa_env/README.md +++ b/envs/finqa_env/README.md @@ -70,9 +70,9 @@ from envs.finqa_env import FinQAEnv, CallToolAction async def main(): async with FinQAEnv(base_url="http://localhost:8000") as env: # Reset to get a question - obs = await env.reset() - question = obs.metadata["question"] - company = obs.metadata["company"] + result = await env.reset() + question = result.observation.metadata["question"] + company = result.observation.metadata["company"] print(f"Question: {question}") print(f"Company: {company}") @@ -90,7 +90,8 @@ async def main(): arguments={ "company_name": "alphabet", "table_name": "us_gaap_ScheduleOfIncomeBeforeIncomeTaxDomesticAndForeignTableTextBlock", - "query": "SELECT * FROM data WHERE year = '2022'" + # Name the columns you need (see get_table_info); SELECT * is rejected + "query": "SELECT FROM data WHERE year = '2022'" } )) print(f"Done: {step_result.done}, Reward: {step_result.reward}") diff --git a/envs/finrl_env/README.md b/envs/finrl_env/README.md index ca4ce61723..bbf24b34b5 100644 --- a/envs/finrl_env/README.md +++ b/envs/finrl_env/README.md @@ -121,7 +121,7 @@ client.close() │ │ │ │ ┌────────▼────────┐ │ │ │ FinRLEnv │ ← HTTP Client │ -│ │ (HTTPEnvClient) │ │ +│ │ (EnvClient) │ │ │ └────────┬────────┘ │ └────────────────────────────┼─────────────────────────────────┘ │ HTTP (JSON) diff --git a/envs/kernrl/README.md b/envs/kernrl/README.md index fb17cfe05b..8eede54841 100644 --- a/envs/kernrl/README.md +++ b/envs/kernrl/README.md @@ -33,8 +33,8 @@ from kernrl import KernelAction, kernrl_env env = kernrl_env(base_url="http://localhost:8000") # Start episode -obs = env.reset(problem_id="L1_23_Softmax") -print(obs.problem_description) +result = env.reset(problem_id="L1_23_Softmax") +print(result.observation.problem_description) # Submit a kernel action = KernelAction(code=''' diff --git a/envs/maze_env/README.md b/envs/maze_env/README.md index caee729aca..c87daf350f 100644 --- a/envs/maze_env/README.md +++ b/envs/maze_env/README.md @@ -83,7 +83,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -97,7 +97,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) diff --git a/envs/openapp_env/README.md b/envs/openapp_env/README.md index 393f84ccef..33099b6ce5 100644 --- a/envs/openapp_env/README.md +++ b/envs/openapp_env/README.md @@ -84,7 +84,7 @@ openapp_env/ **Key Components:** -- **client.py**: `OpenAppEnv` class that extends `HTTPEnvClient` for remote environment interaction +- **client.py**: `OpenAppEnv` class that extends `EnvClient` for remote environment interaction - **models.py**: `OpenAppAction` and `OpenAppObservation` dataclasses with validation - **server/openapp_environment.py**: `OpenAppEnvironment` class that wraps BrowserGym and OpenApps - **server/app.py**: FastAPI server that exposes the environment via HTTP endpoints diff --git a/envs/openspiel_env/README.md b/envs/openspiel_env/README.md index c64f9a5b87..fd1fb26612 100644 --- a/envs/openspiel_env/README.md +++ b/envs/openspiel_env/README.md @@ -100,7 +100,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -114,7 +114,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) diff --git a/envs/reasoning_gym_env/README.md b/envs/reasoning_gym_env/README.md index a26e3581b6..06deccbd6e 100644 --- a/envs/reasoning_gym_env/README.md +++ b/envs/reasoning_gym_env/README.md @@ -85,7 +85,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -100,7 +100,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) diff --git a/envs/snake_env/README.md b/envs/snake_env/README.md index b1a013163d..ef8589d338 100644 --- a/envs/snake_env/README.md +++ b/envs/snake_env/README.md @@ -72,7 +72,7 @@ client.close() ```bash # Install dependencies -cd src/envs/snake_env +cd envs/snake_env pip install -e . # Run server @@ -252,7 +252,7 @@ pip install git+https://github.com/kc-ml2/marlenv.git Make sure you're in the correct directory when running the server: ```bash -cd src/envs/snake_env +cd envs/snake_env uv run --project . server ``` diff --git a/envs/textarena_env/README.md b/envs/textarena_env/README.md index 1a15016b07..ded4f1cb6f 100644 --- a/envs/textarena_env/README.md +++ b/envs/textarena_env/README.md @@ -110,7 +110,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -124,7 +124,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) diff --git a/envs/websearch_env/README.md b/envs/websearch_env/README.md index 7f7de84c3f..bb0018a881 100644 --- a/envs/websearch_env/README.md +++ b/envs/websearch_env/README.md @@ -51,15 +51,14 @@ This environment requires a Serper.dev API key to function. ## Quick Start -The simplest way to use the Web Search environment is through the `WebSearchEnvironment` class: +The simplest way to use the Web Search environment is through the `WebSearchEnv` client: ```python -from envs.websearch_env.server.websearch_env_environment import WebSearchEnvironment -from envs.websearch_env import WebSearchAction +from envs.websearch_env import WebSearchAction, WebSearchEnv try: # Create environment from Docker image - web_search_env = WebSearchEnvironment.from_docker_image("web_search-env:latest") + web_search_env = WebSearchEnv.from_docker_image("web_search-env:latest") # Reset result = web_search_env.reset() @@ -77,7 +76,7 @@ finally: web_search_env.close() ``` -That's it! The `WebSearchEnvironment.from_docker_image()` method handles: +That's it! The `WebSearchEnv.from_docker_image()` method handles: - Starting the Docker container - Waiting for the server to be ready - Connecting to the environment @@ -102,7 +101,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -116,7 +115,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public) @@ -176,10 +175,10 @@ The reward is undefined here. If you already have a Web Search environment server running, you can connect directly: ```python -from envs.websearch_env import WebSearchEnvironment +from envs.websearch_env import WebSearchAction, WebSearchEnv # Connect to existing server -web_search_env = WebSearchEnvironment(base_url="") +web_search_env = WebSearchEnv(base_url="") # Use as normal result = web_search_env.reset() diff --git a/envs/wildfire_env/README.md b/envs/wildfire_env/README.md index 7da1cfd07c..a941556401 100644 --- a/envs/wildfire_env/README.md +++ b/envs/wildfire_env/README.md @@ -462,7 +462,7 @@ This approach: docker build -t openenv-base:latest -f src/openenv/core/containers/images/Dockerfile . # Build wildfire environment using the script -cd src/envs/wildfire_env/server +cd envs/wildfire_env/server ./build_docker.sh # Run container @@ -1029,7 +1029,7 @@ docker build -t wildfire-env:latest -f envs/wildfire_env/server/Dockerfile . - **Main Repository**: [OpenEnv GitHub](https://github.com/openenv) - **Documentation**: See `rfcs/` directory for design documents -- **Other Environments**: See `src/envs/` for more environment examples +- **Other Environments**: See `envs/` for more environment examples ### Related Tools diff --git a/examples/sft_warmup.ipynb b/examples/sft_warmup.ipynb index d6f326270e..fc7afcaf14 100644 --- a/examples/sft_warmup.ipynb +++ b/examples/sft_warmup.ipynb @@ -160,7 +160,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "---\n\n## 11. Where to go next: GRPO\n\nThe SFT checkpoint is ready to use as the warm-start for GRPO. In the\n[end-to-end walkthrough](https://huggingface.co/docs/openenv/tutorials/end-to-end-walkthrough),\nchange one line in the trainer setup:\n\n```python\n# Before (cold-start from the base model):\nMODEL_NAME = \"Qwen/Qwen3-1.7B\"\n\n# After (warm-start from your SFT checkpoint):\nMODEL_NAME = f\"{YOUR_HF_USERNAME}/reasoning-gym-chain-sum-Qwen3-1.7B-sft\"\n```\n\nWith format compliance already near 100%, GRPO's `reward_std` will be non-zero from the first batch\nand the reward curve will climb immediately." + "source": "---\n\n## 11. Where to go next: GRPO\n\nThe SFT checkpoint is ready to use as the warm-start for GRPO. In the\n[end-to-end walkthrough](https://huggingface.co/docs/openenv/tutorials/end-to-end-walkthrough),\nchange one line in the trainer setup:\n\n```python\n# Before (cold-start from the base model):\nMODEL_NAME = \"Qwen/Qwen3-1.7B\"\n\n# After (warm-start from your SFT checkpoint):\nMODEL_NAME = f\"{YOUR_HF_USERNAME}/reasoning-gym-chain-sum-Qwen3-1.7B-sft\"\n```\n\nWith most completions already in the right format, GRPO's `reward_std` will be non-zero from the first batch\nand the reward curve will climb immediately." } ], "metadata": { diff --git a/src/openenv/cli/templates/openenv_env/README.md b/src/openenv/cli/templates/openenv_env/README.md index c4e12b5e09..446610a7d9 100644 --- a/src/openenv/cli/templates/openenv_env/README.md +++ b/src/openenv/cli/templates/openenv_env/README.md @@ -71,7 +71,7 @@ You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push # Or specify options -openenv push --namespace my-org --private +openenv push --repo-id my-org/my-env --private ``` The `openenv push` command will: @@ -85,7 +85,7 @@ The `openenv push` command will: ### Options -- `--directory`, `-d`: Directory containing the OpenEnv environment (defaults to current directory) +- `DIRECTORY` (positional): Directory containing the OpenEnv environment (defaults to current directory) - `--repo-id`, `-r`: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml) - `--base-image`, `-b`: Base Docker image to use (overrides Dockerfile FROM) - `--private`: Deploy the space as private (default: public)