LeRobot documentation

Loading Environments from the Hub

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Loading Environments from the Hub

The EnvHub feature allows you to load simulation environments directly from the Hugging Face Hub with a single line of code. This unlocks a powerful new model for collaboration: instead of environments being locked away inside monolithic libraries, anyone can publish custom environments and share them with the community.

What is EnvHub?

EnvHub lets you create custom robotics simulation environments with your own robot models and scenarios, and make them easily usable by anyone through the LeRobot framework.

EnvHub packages are stored on the Hugging Face Hub, and can be seamlessly pulled and used in your AI robotics projects through LeRobot with a single line of code.

Thanks to EnvHub, you can:

  1. Create and publish environments to the Hugging Face Hub as Git repositories, and distribute complex physics simulations without packaging hassles
  2. Load environments dynamically, without installing them as packages
  3. Version and track environment changes using Git semantics
  4. Discover new simulation tasks shared by the community

This design means you can go from discovering an interesting environment on the Hub to running experiments in seconds, or create your own custom robot and environment without worrying about dependency conflicts or complex installation procedures.

When you create an EnvHub package, you can build anything you want inside it and use any simulation tool you like: this is your own space to play with. The only requirement is that the package contains an env.py file that defines the environment and allows LeRobot to load and use your EnvHub package.

This env.py file needs to expose a small API so LeRobot can load and run it. In particular, you must provide a make_env(n_envs: int = 1, use_async_envs: bool = False) function, which is the main entry point for LeRobot. LeRobot calls it without a config, locally and when served. It should return one of:

  • A gym.vector.VectorEnv (most common)
  • A single gym.Env (will be automatically wrapped)
  • A dict mapping {suite_name: {task_id: VectorEnv}} (for multi-task benchmarks)

By default, a returned vector env must be an unwrapped SyncVectorEnv or AsyncVectorEnv: LeRobot takes its env_fns and builds each environment itself. Custom vector wrappers and subclasses are rejected rather than silently removed.

For provider-owned vector wrappers or GPU-native batches, use --env.use_native_batch=true. This keeps the repository’s vector environment intact for local batched evaluation, including its preprocessing requirements. It cannot be combined with an endpoint or SimRobot. The string API (make_env("user/repo")) also keeps the provider’s environment intact.

env.py may also define an optional LEROBOT_SIM dictionary describing the environment (fps, control_mode, component names; see EnvHub and the simulator server). It applies locally and when served.

Quick Start

Loading an environment from the Hub is as simple as:

from lerobot.envs import make_env

# Load a hub environment (requires explicit consent to run remote code)
env = make_env("lerobot/cartpole-env", trust_remote_code=True)
**Security Notice**: Loading environments from the Hub executes Python code from third-party repositories. Only use `trust_remote_code=True` with repositories you trust. We strongly recommend pinning to a specific commit hash for reproducibility and security.

Repository Structure

To make your environment loadable from the Hub, your repository must contain at minimum:

Required Files

env.py (or custom Python file)

  • Must expose a make_env(n_envs: int, use_async_envs: bool) function
  • Extra keyword arguments of make_env are the environment’s own options: --env.make_env_kwargs='{"episode_length": 900}' passes them (--robot.env.make_env_kwargs for a sim robot), and a server takes them with --sim.kwargs='{make_env_kwargs: {episode_length: 900}}'
  • This function should return one of:
    • A gym.vector.VectorEnv (most common)
    • A single gym.Env (will be automatically wrapped)
    • A dict mapping {suite_name: {task_id: VectorEnv}} (for multi-task benchmarks)

Optional Files

requirements.txt

  • List any additional dependencies your environment needs
  • Users will need to install these manually before loading your environment

LEROBOT_SIM in env.py

  • A module-level dictionary that describes the environment when it is served by the simulator server, so it starts with a single command
  • Every key is optional; repositories without LEROBOT_SIM keep working with the defaults
  • robot_type is the type the real robot records (so_follower, omx_follower) when lerobot has a class for the body, so simulated and real datasets match
KeyMeaningDefault
fpsControl rate of one step()30
control_modeAction convention: position, delta or eef_pose"position"
semanticsName of the observation/action conventions"envhub-v1"
state_namesNames of the observation.state components, in ordergeneric state_0, state_1, …
action_namesNames of the action components, in ordergeneric command_0, command_1, …
robot_typeSimulated body, recorded as a sim robot’s dataset robot_typenone (datasets record sim_robot)

README.md

  • Document your environment: what task it implements, observation/action spaces, rewards, etc.
  • Include usage examples and any special setup instructions

.gitignore

  • Exclude unnecessary files from your repository

Example Repository Structure

my-environment-repo/
├── env.py                 # Main environment definition (required)
├── requirements.txt       # Dependencies (optional)
├── README.md             # Documentation (recommended)
├── assets/               # Images, videos, etc. (optional)
│   └── demo.gif
└── configs/              # Config files if needed (optional)
    └── task_config.yaml

Creating Your Environment Repository

Step 1: Define Your Environment

Create an env.py file with a make_env function:

# env.py
import gymnasium as gym

# Optional: describes the environment to the simulator server (see "Optional Files").
LEROBOT_SIM = {"fps": 50, "control_mode": "position"}

def make_env(n_envs: int = 1, use_async_envs: bool = False):
    """
    Create vectorized environments for your custom task.

    Args:
        n_envs: Number of parallel environments
        use_async_envs: Whether to use AsyncVectorEnv or SyncVectorEnv

    Returns:
        gym.vector.VectorEnv or dict mapping suite names to vectorized envs
    """
    def _make_single_env():
        # Create your custom environment
        return gym.make("CartPole-v1")

    # Choose vector environment type
    env_cls = gym.vector.AsyncVectorEnv if use_async_envs else gym.vector.SyncVectorEnv

    # Create vectorized environment
    vec_env = env_cls([_make_single_env for _ in range(n_envs)])

    return vec_env

Step 2: Test Locally

Before uploading, test your environment locally:

import numpy as np

from lerobot.envs.configs import HubEnvConfig

# Build the envs from the local folder that holds env.py, as lerobot-eval and the env server do
envs = HubEnvConfig(hub_path="./my-env", trust_remote_code=True).create_envs(n_envs=2)

# Verify it works
suite_name = next(iter(envs))
env = envs[suite_name][0]
obs, info = env.reset(seed=0)
print({key: value.shape for key, value in obs.items()})  # LeRobot keys, e.g. observation.state
env.step(np.zeros(env.action_space.shape, np.float32))
env.close()

Step 3: Upload to the Hub

Upload your repository to Hugging Face:

# Install huggingface_hub if needed
pip install huggingface_hub

# Login to Hugging Face
hf auth login

# Create a new repository
hf repos create my-org/my-custom-env

# Initialize git and push
git init
git add .
git commit -m "Initial environment implementation"
git remote add origin https://huggingface.co/my-org/my-custom-env
git push -u origin main

Alternatively, use the huggingface_hub Python API:

from huggingface_hub import HfApi

api = HfApi()

# Create repository
api.create_repo("my-custom-env", repo_type="space")

# Upload files
api.upload_folder(
    folder_path="./my-env-folder",
    repo_id="username/my-custom-env",
    repo_type="space",
)

Loading Environments from the Hub

Basic Usage

from lerobot.envs import make_env

# Load from the hub
envs_dict = make_env(
    "username/my-custom-env",
    n_envs=4,
    trust_remote_code=True
)

# Access the environment
suite_name = next(iter(envs_dict))
env = envs_dict[suite_name][0]

# Use it like any gym environment
obs, info = env.reset()
action = env.action_space.sample()
obs, reward, terminated, truncated, info = env.step(action)

Advanced: Pinning to Specific Versions

For reproducibility and security, pin to a specific Git revision:

# Pin to a specific branch
env = make_env("username/my-env@main", trust_remote_code=True)

# Pin to a specific commit (recommended for papers/experiments)
env = make_env("username/my-env@abc123def456", trust_remote_code=True)

# Pin to a tag
env = make_env("username/my-env@v1.0.0", trust_remote_code=True)

Custom File Paths

If your environment definition is not in env.py:

# Load from a custom file
env = make_env("username/my-env:custom_env.py", trust_remote_code=True)

# Combine with version pinning
env = make_env("username/my-env@v1.0:envs/task_a.py", trust_remote_code=True)

Async Environments

For better performance with multiple environments:

envs_dict = make_env(
    "username/my-env",
    n_envs=8,
    use_async_envs=True,  # Use AsyncVectorEnv for parallel execution
    trust_remote_code=True
)

URL Format Reference

The hub URL format supports several patterns:

PatternDescriptionExample
user/repoLoad env.py from main branchmake_env("lerobot/cartpole-env")
user/repo@revisionLoad from specific revisionmake_env("lerobot/cartpole-env@main")
user/repo:pathLoad custom filemake_env("lerobot/envs:pusht.py")
user/repo@rev:pathRevision + custom filemake_env("lerobot/envs@v1:pusht.py")

EnvHub and the simulator server

By default, EnvHub environments run inside the evaluation process: make_env downloads the repository’s env.py, and its dependencies are installed next to LeRobot. To keep an environment’s dependencies away from your policy’s, serve it with the simulator server instead. One command builds an image with the repository and its requirements.txt, and starts the server:

lerobot-env-server --envhub=username/my-env@<commit> --docker=true

Besides requirements.txt (installed with pip’s rules, so --extra-index-url lines work), the image installs the apt packages listed in an optional packages.txt, one per line, as in Hugging Face Spaces. The image is plain Ubuntu: the GPU driver comes from the host, and CUDA libraries come from the Python wheels that need them (torch, Isaac Sim).

Passing --envhub is the explicit consent to run the repository’s code, like trust_remote_code=True. Pin a commit. Then evaluate against it like any other environment, adding its endpoint:

lerobot-eval --policy.path=<checkpoint> \
  --env.type=envhub --env.hub_path=username/my-env@<commit> --env.server.endpoint=tcp/127.0.0.1:7448 \
  --rename_map='{"observation.images.front": "observation.images.camera1"}' \
  --eval.n_episodes=10

Without --env.server.endpoint, the same command runs the environment locally (with --trust_remote_code=true); a sim robot takes the same options under --robot.env.*.

The server reads the control rate, action convention and component names from the repository’s LEROBOT_SIM, and uses the defaults for anything it does not declare. Override a value on the command line, which wins over the repository, e.g. --sim.fps=15.

To be served, an environment must follow these conventions:

  • make_env returns vector environments built from per-environment factories (SyncVectorEnv/AsyncVectorEnv, as in the examples above) or a single gym.Env. The server builds its own environments from those factories and closes them when a session ends.
  • env.py is executed once per server process, so it can keep process-wide state, such as a simulator application that can only start once (Isaac Sim’s SimulationApp).
  • Observations are the dictionaries LeRobot reads locally: pixels (one HWC uint8 image or a dictionary of cameras), agent_pos and environment_state, as NumPy arrays.
  • Actions are a one-dimensional continuous Box.
  • Episodes end through terminated/truncated or _max_episode_steps (default 500), and info["is_success"] reports success. The task text comes from the environment’s task_description (or task) attribute.
  • LEROBOT_SIM is optional, but declare it whenever the defaults (30 fps, position control) do not match your environment.

For examples, see the EnvHub environments collection: lerobot/so101-tabletop-mujoco (MuJoCo) and lerobot/so101-tabletop-isaac (Isaac Sim, installed through requirements.txt and packages.txt) serve the same SO-101 scene with the same observations, actions and LEROBOT_SIM.

The repository is downloaded on your machine with your Hugging Face login (HF_TOKEN or hf auth login), so private repositories work too, and the image build never sees a token. --envhub also takes a local directory holding env.py, to serve an environment before pushing it: lerobot-env-server --envhub=./my-env --docker=true. --build=true rebuilds only what changed: the apt packages when packages.txt changes, the requirements when requirements.txt changes, and otherwise just the repository’s files. Test a new environment locally first (see Step 2: Test Locally).

Multi-Task Environments

For benchmarks with multiple tasks (like LIBERO), return a nested dictionary:

def make_env(n_envs: int = 1, use_async_envs: bool = False):
    env_cls = gym.vector.AsyncVectorEnv if use_async_envs else gym.vector.SyncVectorEnv

    # Return dict: {suite_name: {task_id: VectorEnv}}
    return {
        "suite_1": {
            0: env_cls([lambda: gym.make("Task1-v0") for _ in range(n_envs)]),
            1: env_cls([lambda: gym.make("Task2-v0") for _ in range(n_envs)]),
        },
        "suite_2": {
            0: env_cls([lambda: gym.make("Task3-v0") for _ in range(n_envs)]),
        }
    }

make_env builds every task when it is called. For a GPU simulator that steps a whole batch in one world (e.g. Isaac Lab), or a benchmark with many tasks, define vector_env_fns in env.py instead, as LeRobot’s own integrations do. It returns factories, and only the factories of the tasks that run are called:

from functools import partial


def vector_env_fns(task: str, n_envs: int, **make_env_kwargs):
    # {suite_name: {task_id: factory}}, each factory building one vector env of n_envs envs
    return {"suite_1": {0: partial(MyBatchedEnv, n_envs, "Task1"), 1: partial(MyBatchedEnv, n_envs, "Task2")}}

Each vector env must have num_envs == n_envs, must not autoreset (metadata["autoreset_mode"] = gym.vector.AutoresetMode.DISABLED), returns batched observations in the format above, takes one seed per env in reset(seed=[...]), and reports info["is_success"] per env. --env.make_env_kwargs are passed to it, and both lerobot-eval and the simulator server use it in place of make_env. Since one vector env already steps the whole batch, evaluate with --eval.use_async_envs=false: a worker process would only copy every observation batch between processes, and a server would build the simulator a second time, in the worker.

Security Considerations

**Important**: The `trust_remote_code=True` flag is required to execute environment code from the Hub. This is by design for security.

When loading environments from the Hub:

  1. Review the code first: Visit the repository and inspect env.py before loading
  2. Pin to commits: Use specific commit hashes for reproducibility
  3. Check dependencies: Review requirements.txt for suspicious packages
  4. Use trusted sources: Prefer official organizations or well-known researchers
  5. Sandbox if needed: Run untrusted code in isolated environments (containers, VMs)

Example of safe usage:

# ❌ BAD: Loading without inspection
env = make_env("random-user/untrusted-env", trust_remote_code=True)

# ✅ GOOD: Review code, then pin to specific commit
# 1. Visit https://huggingface.co/trusted-org/verified-env
# 2. Review the env.py file
# 3. Copy the commit hash
env = make_env("trusted-org/verified-env@a1b2c3d4", trust_remote_code=True)

Example: CartPole from the Hub

Here’s a complete example using the reference CartPole environment:

from lerobot.envs import make_env
import numpy as np

# Load the environment
envs_dict = make_env("lerobot/cartpole-env", n_envs=4, trust_remote_code=True)

# Get the vectorized environment
suite_name = next(iter(envs_dict))
env = envs_dict[suite_name][0]

# Run a simple episode
obs, info = env.reset()
done = np.zeros(env.num_envs, dtype=bool)
total_reward = np.zeros(env.num_envs)

while not done.all():
    # Random policy
    action = env.action_space.sample()
    obs, reward, terminated, truncated, info = env.step(action)
    total_reward += reward
    done = terminated | truncated

print(f"Average reward: {total_reward.mean():.2f}")
env.close()

Benefits of EnvHub

For Environment Authors

  • Easy distribution: No PyPI packaging required
  • Version control: Use Git for environment versioning
  • Rapid iteration: Push updates instantly
  • Documentation: Hub README renders beautifully
  • Community: Reach LeRobot users directly

For Researchers

  • Quick experiments: Load any environment in one line
  • Reproducibility: Pin to specific commits
  • Discovery: Browse environments on the Hub
  • No conflicts: No need to install conflicting packages

For the Community

  • Growing ecosystem: More diverse simulation tasks
  • Standardization: Common make_env API
  • Collaboration: Fork and improve existing environments
  • Accessibility: Lower barrier to sharing research

Troubleshooting

“Refusing to execute remote code”

You must explicitly pass trust_remote_code=True:

env = make_env("user/repo", trust_remote_code=True)

“Module X not found”

The hub environment has dependencies you need to install:

# Check the repo's requirements.txt and install dependencies
pip install gymnasium numpy

“make_env not found in module”

Your env.py must expose a make_env function:

def make_env(n_envs: int, use_async_envs: bool):
    # Your implementation
    pass

Environment returns wrong type

The make_env function must return:

  • A gym.vector.VectorEnv, or
  • A single gym.Env, or
  • A dict {suite_name: {task_id: VectorEnv}}

Best Practices

  1. Document your environment: Include observation/action space descriptions, reward structure, and termination conditions in your README
  2. Add requirements.txt: List all dependencies with versions
  3. Declare LEROBOT_SIM: Let the environment be served by the simulator server with one command
  4. Test thoroughly: Verify your environment works locally before pushing
  5. Use semantic versioning: Tag releases with version numbers
  6. Add examples: Include usage examples in your README
  7. Keep it simple: Minimize dependencies when possible
  8. License your work: Add a LICENSE file to clarify usage terms

Future Directions

The EnvHub ecosystem enables exciting possibilities:

  • GPU-accelerated physics: Share Isaac Gym or Brax environments
  • Photorealistic rendering: Distribute environments with advanced graphics
  • Multi-agent scenarios: Complex interaction tasks
  • Real-world simulators: Digital twins of physical setups
  • Procedural generation: Infinite task variations
  • Domain randomization: Pre-configured DR pipelines

As more researchers and developers contribute, the diversity and quality of available environments will grow, benefiting the entire robotics learning community.

See Also

Update on GitHub