Skip to content

Add generic Gymnasium environment - #168

Closed
alialamiidrissi wants to merge 9 commits into
huggingface:mainfrom
alialamiidrissi:gym_environment
Closed

Add generic Gymnasium environment#168
alialamiidrissi wants to merge 9 commits into
huggingface:mainfrom
alialamiidrissi:gym_environment

Conversation

@alialamiidrissi

Copy link
Copy Markdown

This PR introduces a generic Gymnasium (OpenAI Gym/Gymnasium) environment server and client integration to OpenEnv.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 8, 2025
@github-actions

github-actions Bot commented Nov 8, 2025

Copy link
Copy Markdown
Contributor

✅ Deployment succeeded for gym_env

Nice work! Wait for a code review and we're ready to go.

You can iterate locally or validate fixes by running scripts/deploy_to_hf.sh --env "gym_env".

1 similar comment
@github-actions

github-actions Bot commented Nov 8, 2025

Copy link
Copy Markdown
Contributor

✅ Deployment succeeded for gym_env

Nice work! Wait for a code review and we're ready to go.

You can iterate locally or validate fixes by running scripts/deploy_to_hf.sh --env "gym_env".

@@ -0,0 +1,326 @@
"""Tests for the generic Gymnasium environment integration."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest skip generic Prefix. We can say OpenAI Gymnasium, or just Gymnasium is also cool probably..

@jspisak jspisak added the enhancement New feature or request label Nov 11, 2025
Darktex
Darktex previously approved these changes Jan 13, 2026

@Darktex Darktex left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This is an automated review by Claude Code (alignment-reviewer agent), not a human review. The account posting this is shared with the human maintainer.


Perfect! Now I have all the context I need. Let me compile my review.


PR #168 Review: Add generic Gymnasium environment

Previous Review Summary

Review by @shaginhekvs: Details not provided in the PR context, but review was marked as COMMENTED (not APPROVED).

Automated checks: Deployment to HuggingFace succeeded for the gym_env environment.


TIER 1: Bugs and Critical Issues

🔴 CRITICAL: Using deprecated HTTPEnvClient instead of WebSocket-based EnvClient

Location: src/envs/gym_env/client.py:14

from core.http_env_client import HTTPEnvClient

class GymEnvironment(HTTPEnvClient[GymAction, GymObservation]):

Issue: The codebase has migrated to WebSocket-based communication (commit 85c47c6). According to INVARIANTS.md:74, HTTP is being deprecated in favor of WebSocket-only:

Note: We are in the process of deprecating HTTP (see PR #252) in favor of WebSocket-only, but we are still transitioning and both protocols are currently available.

Impact:

  • This environment uses a communication pattern that is being phased out
  • Inconsistent with reference implementation (echo_env uses EnvClient)
  • Will need to be rewritten when HTTP support is removed

Fix Required: Change to use EnvClient from core.env_client with WebSocket support, following the pattern in envs/echo_env/client.py:31.


🔴 BUG: Import path violations

Location: Multiple files

Issue 1: src/envs/gym_env/client.py:12-14

from core.client_types import StepResult
from core.http_env_client import HTTPEnvClient
from .models import GymAction, GymObservation, GymState

Issue 2: src/envs/gym_env/server/app.py:10

from core.env_server import create_app

Problem: Import paths are incorrect. Should use openenv.core instead of core for in-repo imports, or support both in-repo and standalone patterns like echo_env does.

Reference: envs/echo_env/client.py:16-28 shows the correct dual-import pattern:

try:
    # In-repo imports (when running from OpenEnv repository)
    from openenv.core.client_types import StepResult
    ...
except ImportError:
    # Standalone imports (when environment is standalone with openenv from pip)
    from openenv.core.client_types import StepResult
    ...

Fix Required: Add try/except import pattern for both in-repo and standalone usage.


🟡 Lint failures (cannot verify without uv installed)

The lint check hook failed because uv is not installed:

Error: 'uv' is not installed or not in PATH

Action Required: Run uv run ruff format src/envs/gym_env/ tests/envs/test_gym_environment.py --check locally to verify formatting compliance.


🟢 Debug code check: PASSED

No problematic debug code found in the new files.


TIER 2: Alignment with Principles and Invariants

⚠️ ALIGNMENT CONCERN: Deviation from canonical environment structure

Reference: PATTERNS.md:5-20 defines canonical structure

Issue: The environment is placed in src/envs/gym_env/ instead of envs/gym_env/.

Observed structure:

src/envs/gym_env/
├── __init__.py
├── client.py
├── models.py
├── README.md
└── server/
    ├── __init__.py
    ├── app.py
    ├── gymnasium_environment.py
    ├── requirements.txt
    ├── Dockerfile
    └── additional_env_parameters.yaml

Expected structure (per PATTERNS.md and reference implementation):

envs/gym_env/
├── __init__.py
├── client.py
├── models.py
├── openenv.yaml       # Missing!
├── pyproject.toml     # Missing!
└── server/

Missing files:

  • openenv.yaml - Environment manifest
  • pyproject.toml - Dependencies for standalone usage

Question for maintainer: Is the src/envs/ location intentional, or should this follow the standard envs/ pattern used by echo_env?


⚠️ Type safety patterns not fully followed

Issue: The GymState class extends the base State but adds additional fields:

@dataclass
class GymState(State):
    env_id: str = "Unknown"
    render_mode: Optional[str] = None
    max_steps: Optional[int] = None
    seed: Optional[int] = None
    episode_length: int = 0
    total_reward: float = 0.0

Observation: The reference echo_env uses the base State class directly without extension:

# echo_env/client.py:98-111
def _parse_state(self, payload: Dict) -> State:
    return State(
        episode_id=payload.get("episode_id"),
        step_count=payload.get("step_count", 0),
    )

Analysis: This is not necessarily wrong - adding domain-specific state is reasonable for Gym environments. However, it's inconsistent with the simpler reference pattern.

Suggestion: Consider whether all the additional state fields are necessary, or if some could be moved to metadata in the observation.


📋 MINOR: Dockerfile comments reference wrong environment

Location: src/envs/gym_env/server/Dockerfile:1-2

# Dockerfile for Atari Environment
# This image provides Atari 2600 games via the Arcade Learning Environment (ALE)

Issue: Copy-paste error - comments mention "Atari" but this is the generic Gym environment.

Fix: Update comments to reference the generic Gymnasium environment.


📋 MINOR: Inconsistent YAML file usage

Location: src/envs/gym_env/server/additional_env_parameters.yaml

Issue: File exists but is empty. The app.py loads it if present:

yaml_param_file_path = os.getenv("ADDITIONAL_PARAMETERS_YAML_FILE")
additional_params = {}

if yaml_param_file_path and os.path.exists(yaml_param_file_path):
    with open(yaml_param_file_path, "r") as f:
        additional_params = yaml.safe_load(f)

Suggestion: Either:

  1. Add example content/documentation to the YAML file
  2. Remove it if not needed
  3. Add a comment explaining its purpose

✅ POSITIVE: Excellent serialization handling

The _to_serializable() method in gymnasium_environment.py:346-368 handles numpy types, nested structures, and edge cases comprehensively. This is well-designed for dealing with diverse Gymnasium observation/action spaces.


✅ POSITIVE: Comprehensive action space support

The environment handles multiple Gymnasium action space types (Discrete, MultiDiscrete, MultiBinary, Box, Tuple, Dict, Text) with proper conversion logic. This makes it truly generic.


✅ POSITIVE: Good test coverage

The test file includes tests for:

  • Discrete action spaces (CartPole, Taxi)
  • Continuous action spaces (BipedalWalker, Pendulum, MountainCar, LunarLander)
  • Client parser validation
  • Both Box and Discrete action space metadata

Summary

Critical Issues (Must Fix)

  1. Replace HTTPEnvClient with WebSocket-based EnvClient - The codebase is migrating away from HTTP
  2. Fix import paths - Use openenv.core and add dual-import pattern for standalone support
  3. Run lint checks - Verify ruff format compliance

Alignment Concerns (Review Needed)

  1. Directory structure - Clarify if src/envs/ vs envs/ is intentional
  2. Missing manifest files - Add openenv.yaml and pyproject.toml per canonical pattern
  3. Dockerfile comments - Fix copy-paste error from Atari environment

Recommendations

  1. Follow the echo_env reference implementation more closely for consistency
  2. Consider simplifying GymState or documenting why extensions are needed
  3. Document or remove the empty additional_env_parameters.yaml file

Overall Assessment: This is a valuable addition that brings generic Gymnasium support to OpenEnv. However, it uses deprecated patterns (HTTP client) and deviates from the canonical structure. The core implementation is solid, but architectural alignment is needed before merge.

Recommended Action: Request changes to address Tier 1 issues and clarify Tier 2 alignment concerns with maintainers.


Automated review by Claude Code | Learn more about OpenEnv's agentic workflow

@Darktex
Darktex dismissed their stale review January 13, 2026 05:51

Dismissing automated approval due to bug in review bot. The original review either had blank content or approved despite finding blocking issues. Please disregard this approval.

@zkwentz

zkwentz commented Jan 21, 2026

Copy link
Copy Markdown
Collaborator

@greptile

@greptile-apps

greptile-apps Bot commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds generic Gymnasium (OpenAI Gym) environment integration to OpenEnv, enabling support for numerous RL environments through a client-server architecture.

Key Changes:

  • Client implementation (GymEnvironment) extending HTTPEnvClient for remote interaction
  • Server wrapper (GymnasiumEnvironment) handling action/observation space conversion and serialization
  • Comprehensive test coverage for discrete and continuous action spaces
  • Docker deployment with configurable environment parameters

Issues Found:

  • Critical: Environment variable name mismatch in Dockerfile will prevent YAML parameters from loading
  • Critical: YAML loading logic crashes when additional_env_parameters.yaml is empty (returns None)
  • Copy-paste artifacts from Atari environment in Dockerfile comments

Confidence Score: 3/5

  • This PR has runtime bugs that will cause failures in Docker deployments
  • Two critical logic errors will cause the server to fail: (1) Dockerfile sets wrong ENV variable name, breaking YAML parameter loading, and (2) empty YAML file causes TypeError when unpacking None. Code quality is otherwise good with proper abstractions and comprehensive tests.
  • Pay close attention to src/envs/gym_env/server/Dockerfile and src/envs/gym_env/server/app.py - both contain critical bugs

Important Files Changed

Filename Overview
src/envs/gym_env/server/Dockerfile Copy-paste artifacts from Atari; ENV variable name mismatch will cause runtime issues
src/envs/gym_env/server/app.py FastAPI server setup with YAML loading bug that crashes on empty files
src/envs/gym_env/server/gymnasium_environment.py Comprehensive Gymnasium wrapper with robust action/observation space handling

Sequence Diagram

sequenceDiagram
    participant Client as GymEnvironment<br/>(Client)
    participant HTTP as HTTP/FastAPI
    participant Server as app.py
    participant Wrapper as GymnasiumEnvironment
    participant Gym as Gymnasium Env

    Note over Client,Gym: Initialization
    Server->>Wrapper: Create GymnasiumEnvironment(env_id, render_mode, max_steps, seed)
    Wrapper->>Gym: gym.make(env_id, render_mode)
    Gym-->>Wrapper: env instance
    Wrapper->>Wrapper: Initialize action/observation space metadata
    
    Note over Client,Gym: Reset Episode
    Client->>HTTP: POST /reset
    HTTP->>Wrapper: reset()
    Wrapper->>Wrapper: _consume_seed()
    Wrapper->>Gym: env.reset(seed)
    Gym-->>Wrapper: observation, info
    Wrapper->>Wrapper: _make_observation()
    Wrapper-->>HTTP: GymObservation
    HTTP-->>Client: StepResult with observation

    Note over Client,Gym: Execute Action
    Client->>HTTP: POST /step {action}
    HTTP->>Wrapper: step(GymAction)
    Wrapper->>Wrapper: _convert_action(action)
    Wrapper->>Gym: env.step(converted_action)
    Gym-->>Wrapper: obs, reward, terminated, truncated, info
    Wrapper->>Wrapper: Update state (step_count, total_reward)
    Wrapper->>Wrapper: _make_observation()
    Wrapper-->>HTTP: GymObservation
    HTTP-->>Client: StepResult with reward, done

    Note over Client,Gym: Query State
    Client->>HTTP: GET /state
    HTTP->>Wrapper: state property
    Wrapper-->>HTTP: GymState
    HTTP-->>Client: Current environment state

    Note over Client,Gym: Cleanup
    Client->>HTTP: POST /close
    HTTP->>Wrapper: close()
    Wrapper->>Gym: env.close()
    Gym-->>Wrapper: closed
    Wrapper-->>HTTP: success
    HTTP-->>Client: success
Loading

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +1 to +2
# Dockerfile for Atari Environment
# This image provides Atari 2600 games via the Arcade Learning Environment (ALE)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Header comments still reference Atari instead of Gym/Gymnasium.

Suggested change
# Dockerfile for Atari Environment
# This image provides Atari 2600 games via the Arcade Learning Environment (ALE)
# Dockerfile for Gym Environment
# This image provides generic Gymnasium (OpenAI Gym) environment support
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/envs/gym_env/server/Dockerfile
Line: 1:2

Comment:
**style:** Header comments still reference Atari instead of Gym/Gymnasium.

```suggestion
# Dockerfile for Gym Environment
# This image provides generic Gymnasium (OpenAI Gym) environment support
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +7 to +11
# Local build: docker build -t envtorch-base:latest -f src/core/containers/images/Dockerfile .
# docker build -f src/envs/atari_env/server/Dockerfile -t atari-env:latest .
#
# CI/CD build: docker build --build-arg BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest \
# -f src/envs/atari_env/server/Dockerfile -t atari-env:latest .

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Example commands in comments still reference atari_env instead of gym_env.

Suggested change
# Local build: docker build -t envtorch-base:latest -f src/core/containers/images/Dockerfile .
# docker build -f src/envs/atari_env/server/Dockerfile -t atari-env:latest .
#
# CI/CD build: docker build --build-arg BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest \
# -f src/envs/atari_env/server/Dockerfile -t atari-env:latest .
# Local build: docker build -t openenv-base:latest -f src/core/containers/images/Dockerfile .
# docker build -f src/envs/gym_env/server/Dockerfile -t gym-env:latest .
#
# CI/CD build: docker build --build-arg BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest \
# -f src/envs/gym_env/server/Dockerfile -t gym-env:latest .
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/envs/gym_env/server/Dockerfile
Line: 7:11

Comment:
**style:** Example commands in comments still reference `atari_env` instead of `gym_env`.

```suggestion
# Local build: docker build -t openenv-base:latest -f src/core/containers/images/Dockerfile .
#              docker build -f src/envs/gym_env/server/Dockerfile -t gym-env:latest .
#
# CI/CD build: docker build --build-arg BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest \
#              -f src/envs/gym_env/server/Dockerfile -t gym-env:latest .
```

How can I resolve this? If you propose a fix, please make it concise.

# --- Runtime environment with defaults ---
# These ENV lines set defaults but still allow runtime overrides
ENV GYM_ENVIRONMENT_ID=${GYM_ENVIRONMENT_ID}
ENV ADDITIONAL_PARAMETERS=${ADDITIONAL_PARAMETERS_YAML_FILE}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: ENV variable name mismatch - setting ADDITIONAL_PARAMETERS but the code expects ADDITIONAL_PARAMETERS_YAML_FILE.

Suggested change
ENV ADDITIONAL_PARAMETERS=${ADDITIONAL_PARAMETERS_YAML_FILE}
ENV ADDITIONAL_PARAMETERS_YAML_FILE=${ADDITIONAL_PARAMETERS_YAML_FILE}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/envs/gym_env/server/Dockerfile
Line: 41:41

Comment:
**logic:** ENV variable name mismatch - setting `ADDITIONAL_PARAMETERS` but the code expects `ADDITIONAL_PARAMETERS_YAML_FILE`.

```suggestion
ENV ADDITIONAL_PARAMETERS_YAML_FILE=${ADDITIONAL_PARAMETERS_YAML_FILE}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +30 to +32
if yaml_param_file_path and os.path.exists(yaml_param_file_path):
with open(yaml_param_file_path, "r") as f:
additional_params = yaml.safe_load(f)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: yaml.safe_load() returns None for empty YAML files, which will cause **additional_params to fail on line 40.

Suggested change
if yaml_param_file_path and os.path.exists(yaml_param_file_path):
with open(yaml_param_file_path, "r") as f:
additional_params = yaml.safe_load(f)
if yaml_param_file_path and os.path.exists(yaml_param_file_path):
with open(yaml_param_file_path, "r") as f:
loaded_params = yaml.safe_load(f)
additional_params = loaded_params if loaded_params else {}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/envs/gym_env/server/app.py
Line: 30:32

Comment:
**logic:** `yaml.safe_load()` returns `None` for empty YAML files, which will cause `**additional_params` to fail on line 40.

```suggestion
if yaml_param_file_path and os.path.exists(yaml_param_file_path):
    with open(yaml_param_file_path, "r") as f:
        loaded_params = yaml.safe_load(f)
        additional_params = loaded_params if loaded_params else {}
```

How can I resolve this? If you propose a fix, please make it concise.

@burtenshaw

Copy link
Copy Markdown
Collaborator

Closing as a stale, currently unmergeable PR. Reopen if someone wants to revive it on top of current main.

@burtenshaw burtenshaw closed this Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants