Skip to main content

lerobot_env_so101

PyPI Python License

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.

Documentation — getting started, action space, API reference.

SO-101 pick-cube environment

A hand-written controller driving a reach→gripper-close motion — not a successful pick-and-lift. A top-down grasp is not yet reachable with the position-only IK; see Known limitations.

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].

  • dx, dy, dz (bounds [-1, 1]): end-effector position deltas, scaled by action_scale (see Known limitations below).
  • grasp (bounds [0, 1]): an absolute gripper target, not a delta — 0 = fully closed, 1 = fully open, written directly to the gripper actuator each step (gripper.normalized_to_ctrl). Since v0.2.0; before that it was a normalized increment added to the current position each step.

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 notes/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.
  • FrankaGymEnv and the RobotGymEnv abstract base were dropped. RobotGymEnv only ever had one concrete subclass (SO101GymEnv), so it added an abstraction layer without abstracting anything; SO101GymEnv now inherits directly from MujocoGymEnv. No Panda/Franka code is present in this package.
  • Fixed: dead mocap body. Upstream's SO101GymEnv.reset_robot() wrote mocap_pos/mocap_quat, but apply_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 from reset_robot() and removes the corresponding unused mocap="true" target body from pick_scene.xml.
  • Fixed (v0.2.0): gripper is now an absolute, normalized command. The grasp action is a normalized [0, 1] (0=closed, 1=open) absolute target, mapped to the gripper actuator's native radian ctrlrange by gripper.normalized_to_ctrl — see Action space above. Breaking change: before v0.2.0, grasp was a normalized increment added to the current gripper position each step, so grasp=0 meant "no change"; as of v0.2.0 it means "fully closed". This does not by itself make LeRobot policy training pick up the new convention — see Known limitations below.
  • Not fixed, inherited as-is: unverified motor parameters. damping / frictionloss / armature for 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's notes/references.md for the cross-check against mujoco_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 the wrap_env/make_env factory. 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 into so101_new_calib.xml upstream (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 vanilla gym-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, pass action_scale (metres per unit action; position deltas only, grasp is absolute and 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
    
  • SO101GymEnv is a base class, not a usable environment. It implements robot control but not step()/reset(); instantiate SO101PickCubeGymEnv (or gym.make("lerobot_env_so101/SO101PickCube-v0")) instead.

  • Top-down grasp is not currently reachable. The gripper's fixed jaw (including the wrist_roll_follower mesh in the wrist servo bracket) hits the block's top face before the moving jaw can descend far enough to straddle it. A correct grasp pose — approaching vertically, gripper straddling the block — does exist kinematically, but the position-only IK in this package tracks a continuous Cartesian path from the HOME pose and converges monotonically onto a different solution branch (approach axis tilted roughly 36° from vertical) instead. Reaching the vertical-approach branch requires orientation-aware IK (see docs/ROADMAP.md); it is not implemented here.

  • LeRobot policy training does not automatically pick up the new grasp convention. LeRobot's policy eval does not read a gym env's action_space bounds; it scales actions using the training dataset's own normalization statistics. Making grasp absolute and [0, 1] (this section, above) is correct for this package as a plain gym/RL environment, but a LeRobot policy will only actually train against this convention if its dataset was collected/labeled that way.

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-hil project 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.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lerobot-env-so101 0.2.1
File Size Uploaded
lerobot_env_so101-0.2.1.tar.gz 6.0 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for lerobot-env-so101 0.2.1
File Interpreter ABI Platform
lerobot_env_so101-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 12.0 MB

Release files / lerobot_env_so101-0.2.1.tar.gz

Download URL lerobot_env_so101-0.2.1.tar.gz
Size 6.0 MB
Tags Source
SHA-256 checksum
How to use checksums
a5dd37a976d2eae4fe3c01d68e765a1dddf3471eb09664201626b43441cf43fe
BLAKE2b-256 checksum
How to use checksums
0c4de9847ac179ccc809d6326d6f6261c681cb1b6f067aa6b0872124703fd105
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.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 Aug 21, 2026.

Transparency log

Release files / lerobot_env_so101-0.2.1-py3-none-any.whl

Download URL lerobot_env_so101-0.2.1-py3-none-any.whl
Size 6.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
7e331146af2e4ebec75d94265de6cd2a511441ee1984f071a07a45319f3fc4ef
BLAKE2b-256 checksum
How to use checksums
290f96579f4054c2b4fbdd82e77b88c761d7bb43805f68bcb0e5bda45e1e11ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.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 Aug 21, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.0

2 release files

0.3.0

2 release files

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page