lerobot_env_so101
A standalone SO-101 (SO-ARM101) pick-cube MuJoCo environment, packaged as a
lerobot_env_* plugin (auto-detected by lerobot v0.6.0+ via
@EnvConfig.register_subclass, see
lerobot#3823) and usable
standalone as a plain gymnasium environment.
Installation
pip install lerobot-env-so101
Or from a checkout of the parent repo, for development:
pip install -e packages/lerobot_env_so101
Usage
Standalone (plain gymnasium)
Works without lerobot installed — only mujoco, gymnasium, and numpy:
import gymnasium as gym
import numpy as np
import lerobot_env_so101 # registers the env
env = gym.make("lerobot_env_so101/SO101PickCube-v0", render_mode="rgb_array")
obs, info = env.reset(seed=0)
for _ in range(100):
action = env.action_space.sample() * 0.1 # keep deltas small, see Known limitations
obs, reward, terminated, truncated, info = env.step(action)
if terminated or truncated:
obs, info = env.reset()
env.close()
gym.make kwargs: image_obs (add a pixels.front camera view to the
observation), reward_type ("sparse" or "dense"), and
random_block_position.
With lerobot (v0.6.0+)
Installing the package is all the wiring you need. lerobot's
register_third_party_plugins() discovers any installed lerobot_env_*
distribution at startup and imports it, which registers the env config under
the type name so101:
pip install lerobot lerobot-env-so101
lerobot-eval --policy.path=<your_policy> --env.type=so101
Config fields (--env.<field> on the CLI) are defined in
configuration_so101.py: task (default SO101PickCube-v0), fps (10,
matching the env's 0.1 s control step), episode_length (100), image_obs,
reward_type, random_block_position.
Driving it from 7-dim teleop tooling
import gymnasium as gym
import lerobot_env_so101
from lerobot_env_so101.wrappers import SevenDofToFourDofAdapter
env = SevenDofToFourDofAdapter(gym.make("lerobot_env_so101/SO101PickCube-v0"))
# env now accepts [dx, dy, dz, drx, dry, drz, grasp]; drx/dry/drz are
# explicitly discarded (documented in the class docstring, not silent).
Action space
The native action space is 4-dim: [dx, dy, dz, grasp].
SO-101's arm has 5 DOF (6 joints, one of which is the gripper), and the IK
controller in this package only solves for end-effector position. A 6-dim
Cartesian pose target (position + orientation) is not something this
robot/controller pair can track, so orientation deltas are not part of the
action space at all — there is no dead dimension to silently ignore. This was
verified with sim/check_dead_action_dims.py in the parent repo: all 4
dims move the state (0/4 dead).
Contrast with the upstream gym-hil design this was ported from, which kept
the Panda-derived 7-dim [dx,dy,dz,drx,dry,drz,grasp] action space and
silently dropped drx,dry,drz inside the IK solver — 3 of 7 dimensions were
dead, meaning a policy could learn to depend on inputs that had zero effect
on the simulation.
7-dim adapter
Teleop devices and HIL tooling written against the 7-dim Panda convention can
still be used via lerobot_env_so101.wrappers.SevenDofToFourDofAdapter,
which wraps a native env and accepts 7-dim actions, explicitly discarding
drx, dry, drz (see the class docstring — the discard is documented, not
silent).
What changed vs. the upstream port
Ported from
lohpaul9/gym-hil SO-101 branch
(Apache-2.0), submitted upstream as
huggingface/gym-hil#36 /
#37 and self-closed by the
author without review. Authored by Paul Loh (github.com/lohpaul9). See
the parent repo's README.md and docs/references.md for the full context
on why this became a standalone package instead of an upstream PR.
- Action space narrowed from 7-dim to native 4-dim (see above). The
7→4 adapter (
wrappers.py) is new. FrankaGymEnvand theRobotGymEnvabstract base were dropped.RobotGymEnvonly ever had one concrete subclass (SO101GymEnv), so it added an abstraction layer without abstracting anything;SO101GymEnvnow inherits directly fromMujocoGymEnv. No Panda/Franka code is present in this package.- Fixed: dead mocap body. Upstream's
SO101GymEnv.reset_robot()wrotemocap_pos/mocap_quat, butapply_action()never read them — the IK controller tracks its target in a plain Python attribute (self._target_ee_pos), not a MuJoCo mocap body. This package removes the mocap writes fromreset_robot()and removes the corresponding unusedmocap="true"targetbody frompick_scene.xml. - Not fixed, inherited as-is: gripper unit mismatch. LeRobot represents
the gripper as a linear joint (
0= closed,100= open). The MJCF/URDF gripper joint here still uses its native radian range (ctrlrange="-0.17453 1.74533") with no mapping layer. This was already an open issue in the upstream assets (documented in the originalassets/SO101/README.md); it is reproduced inlerobot_env_so101/assets/README.mdhere rather than silently dropped. - Not fixed, inherited as-is: unverified motor parameters.
damping/frictionloss/armaturefor the STS3215 servos are carried over unchanged from the upstream MJCF, which itself adapted them from the Open Duck Mini project rather than measuring the actual SO-101. See the parent repo'sdocs/references.mdfor the cross-check againstmujoco_menagerie/robotstudio_so101(same values, same unverified lineage). System identification is out of scope for this package (see the parent repo's Phase 2 notes). - Dropped: teleop/gamepad/keyboard wrappers,
PassiveViewerWrapper,GripperPenaltyWrapper,ResetDelayWrapper,EEActionWrapper,InputsControlWrapper, and thewrap_env/make_envfactory. These are HIL-teleop infrastructure orthogonal to the environment itself and out of scope for this package; only the base env, the pick-cube task, and the 7→4 action adapter were ported. - Dropped:
joints_properties.xml. Its contents are already inlined intoso101_new_calib.xmlupstream (see the comment there) and no MJCF file<include>s it separately — it was dead weight in the source repo. mujoco>=3.0.0,<4.0.0(same major-version bump the upstream fork made from vanillagym-hil's<3.0.0). Verified working against MuJoCo 3.10.0.- Fixed: sparse-mode success no longer ignores the gripper. Upstream gym-hil (both its Panda environments and the SO-101 port) computes a strict success check and then discards it in sparse mode, replacing it with a lift-only test — so a block knocked upward without ever being grasped terminates the episode as a success. Measured on this environment before the fix: gripper 0.092 m from the block (threshold 0.05 m) with the block lifted 0.20 m still reported success. Sparse mode now also requires the gripper to be near the block.
Known limitations
-
Action deltas default to unscaled metres. With the default
action_scale=1.0, an action component of magnitude 1.0 is added directly to the end-effector target as 1.0 metres, while the reachable workspace spans at most 0.6 m — over most of the declared [-1, 1] range the action behaves closer to bang-bang than to a proportional delta. This default is inherited from gym-hil, where actions come from human teleoperation and are naturally small. Since v0.1.1, passaction_scale(metres per unit action; position deltas only, the grasp increment is unaffected) to make the full [-1, 1] range meaningful for an RL policy:env = gym.make("lerobot_env_so101/SO101PickCube-v0", action_scale=0.025) # or on the lerobot CLI: --env.type=so101 --env.action_scale=0.025
-
SO101GymEnvis a base class, not a usable environment. It implements robot control but notstep()/reset(); instantiateSO101PickCubeGymEnv(orgym.make("lerobot_env_so101/SO101PickCube-v0")) instead.
Attribution
- SO-101 MJCF/URDF and control code: ported from lohpaul9/gym-hil (Apache-2.0), authored by Paul Loh (github.com/lohpaul9).
- Base
gym-hilproject structure and license headers: Copyright 2024 The HuggingFace Inc. team (Apache-2.0). - Asset provenance (CAD source, motor parameters): see
lerobot_env_so101/assets/README.md.
Licensed under Apache-2.0 — see LICENSE.
Testing
pip install -e "packages/lerobot_env_so101[test]"
pytest packages/lerobot_env_so101/tests -q
Metadata
Release files for lerobot-env-so101 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lerobot_env_so101-0.1.1.tar.gz | 6.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lerobot_env_so101-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 12.0 MB
Release files / lerobot_env_so101-0.1.1.tar.gz
| Download URL | lerobot_env_so101-0.1.1.tar.gz |
|---|---|
| Size | 6.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
54d7a4edd2bbc854d764ae6114f1941fe2de5a8debc4fa98c1acba936a038f57
|
|
BLAKE2b-256 checksum How to use checksums |
7fc1fbd678dee1e81f0f7936a4b9610f8428ec9cba17bea2406a6b3deeaa283c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 23, 2026.
Transparency logRelease files / lerobot_env_so101-0.1.1-py3-none-any.whl
| Download URL | lerobot_env_so101-0.1.1-py3-none-any.whl |
|---|---|
| Size | 6.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4707cf31a5cd60fc04f6b5912d74cd6a818d0a8f407eb0465304465df99d7550
|
|
BLAKE2b-256 checksum How to use checksums |
7f7e72416cb3cb2e477420aacd52d8ec53b8b997628ee0d2324cc11d1cd8a076
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 23, 2026.
Transparency log