LeRobot documentation
Loading Environments from the Hub
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:
- Create and publish environments to the Hugging Face Hub as Git repositories, and distribute complex physics simulations without packaging hassles
- Load environments dynamically, without installing them as packages
- Version and track environment changes using Git semantics
- 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_envare the environment’s own options:--env.make_env_kwargs='{"episode_length": 900}'passes them (--robot.env.make_env_kwargsfor 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)
- A
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_SIMkeep working with the defaults robot_typeis the type the real robot records (so_follower,omx_follower) when lerobot has a class for the body, so simulated and real datasets match
| Key | Meaning | Default |
|---|---|---|
fps | Control rate of one step() | 30 |
control_mode | Action convention: position, delta or eef_pose | "position" |
semantics | Name of the observation/action conventions | "envhub-v1" |
state_names | Names of the observation.state components, in order | generic state_0, state_1, … |
action_names | Names of the action components, in order | generic command_0, command_1, … |
robot_type | Simulated body, recorded as a sim robot’s dataset robot_type | none (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.yamlCreating 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_envStep 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 mainAlternatively, 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:
| Pattern | Description | Example |
|---|---|---|
user/repo | Load env.py from main branch | make_env("lerobot/cartpole-env") |
user/repo@revision | Load from specific revision | make_env("lerobot/cartpole-env@main") |
user/repo:path | Load custom file | make_env("lerobot/envs:pusht.py") |
user/repo@rev:path | Revision + custom file | make_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=trueBesides 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=10Without --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_envreturns vector environments built from per-environment factories (SyncVectorEnv/AsyncVectorEnv, as in the examples above) or a singlegym.Env. The server builds its own environments from those factories and closes them when a session ends.env.pyis executed once per server process, so it can keep process-wide state, such as a simulator application that can only start once (Isaac Sim’sSimulationApp).- Observations are the dictionaries LeRobot reads locally:
pixels(one HWC uint8 image or a dictionary of cameras),agent_posandenvironment_state, as NumPy arrays. - Actions are a one-dimensional continuous
Box. - Episodes end through
terminated/truncatedor_max_episode_steps(default 500), andinfo["is_success"]reports success. The task text comes from the environment’stask_description(ortask) attribute. LEROBOT_SIMis 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:
- Review the code first: Visit the repository and inspect
env.pybefore loading - Pin to commits: Use specific commit hashes for reproducibility
- Check dependencies: Review
requirements.txtfor suspicious packages - Use trusted sources: Prefer official organizations or well-known researchers
- 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_envAPI - 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
passEnvironment 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
- Document your environment: Include observation/action space descriptions, reward structure, and termination conditions in your README
- Add requirements.txt: List all dependencies with versions
- Declare
LEROBOT_SIM: Let the environment be served by the simulator server with one command - Test thoroughly: Verify your environment works locally before pushing
- Use semantic versioning: Tag releases with version numbers
- Add examples: Include usage examples in your README
- Keep it simple: Minimize dependencies when possible
- 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.