# SO-101 Robot

In this lesson, we’ll add the SO-101 robot to the scene and inspect the results.
We’ll begin with the [SO-101](https://github.com/TheRobotStudio/SO-ARM100) on an
empty floor so its shape and joints are easy to inspect. The SO-101 is a
five-joint robot arm with a two-finger gripper. The arm is mounted on a fixed
base, and the gripper can open and close to grasp objects.

This module uses an SO-101 asset with a wrist-mounted camera. The teacher first
uses simulation state, and the distilled student later uses that camera image
and robot joint state.

![Labeled diagram of the SO-101 arm showing its six joints: shoulder pan, shoulder lift, elbow flex, wrist flex, wrist roll, and gripper.](images/so101-assembly.webp)

The SO-101 is a popular choice for education because it is affordable, easy to
assemble, simple to control, and small. The arm is designed to be lightweight
and compact, making it suitable for desktop use. The SO-101 design is
open source, but you can find commercial versions from various vendors. See
[LeRobot’s SO-101 guide](https://huggingface.co/docs/lerobot/so101) for the
physical arm and assembly details.

## Describing the Action Space

The robot has six actuators: five for the arm and one for the gripper. Unlike
cartpole’s force command, these actuators use joint-position targets: angular
positions for the arm joints and the gripper. Joint controllers then move the
robot toward those targets.

#### HINT
As you look at the robot, think about what a policy could observe and control.
What inputs and outputs would you choose for a learned policy?

Looking at the arm from its base to its fingers, a pan joint near the base moves
the gripper through a large arc. Shoulder and elbow joints let the robot reach
out, a wrist joint changes how the fingers approach an object, and the gripper
opens and closes to grasp. Coordinating those motions will matter
when the robot needs to turn a horizontal vial upright.

## SO-101 Robot Configuration

#### NOTE
Stay on the `learning-path` branch with the preceding lesson’s changes in place.
**Replace** only the marked regions named below. You can compare your working copy
with the completed reference at any time with:

```bash
git diff main -- src/
```

We’ll next look at the important aspects of the SO-101 scene. The physics
configuration below is already supplied in `env_cfg.py` (it does **not** need to
be copied into the project).

```python
# src/isaaclab_tutorial/tasks/place_vial/config/so101/env_cfg.py
@configclass
class PhysicsCfg(PresetCfg):
    newton_mjwarp = NewtonCfg(
        solver_cfg=MJWarpSolverCfg(...),
        collision_cfg=NewtonCollisionPipelineCfg(),
        num_substeps=2,
        debug_mode=False,
    )
    default = newton_mjwarp
```

Notice the parallels with [the cartpole physics
configuration](https://docs.nvidia.com/learning/physical-ai/getting-started-with-isaac-lab/2026-10.0/isaac-lab-basics/05-manager-based-workflows.md#default-configuration-and-overrides).
Return to that lesson if you need a refresher on Isaac Lab’s physics
configuration. Or see the [Isaac Lab
docs](https://isaac-sim.github.io/IsaacLab/release/3.0.0/) for more details.

Now let’s look at the interactive scene configuration, which defines the robot’s
initial state, including its position, orientation, and joint positions. The
configuration also includes a light source for the scene.

In the case of the SO-101, the tutorial starts with Isaac Lab’s `SO101_CFG` and supplies a custom
configuration with calibrated geometry, contact settings, and selected colliders.
The scene below uses that supplied configuration. If you are curious, you can find the full implementation in the
[Isaac Lab source
code](https://github.com/isaac-sim/IsaacLab/blob/release/3.0.0/source/isaaclab_assets/isaaclab_assets/robots/so101.py).
If you want to define a custom robot configuration, that file is a good example
to follow.

**Replace the contents**

- File: `src/isaaclab_tutorial/tasks/place_vial/config/so101/env_cfg.py`
- Markers: `# BEGIN lesson-02-robot` and `# END lesson-02-robot`

Paste the code below between the markers. Keep both marker comments so that you can check your work against the reference solution.

```python
    robot = SO101_CFG.replace(
        prim_path="{ENV_REGEX_NS}/Robot",
        init_state=SO101_CFG.init_state.replace(
            pos=preset(default=(-0.05, 0.0, 0.0), sim2real=(-0.05, 0.0, BASE_HEIGHT_CORRECTION)),
            # +90 degrees about world Z (XYZW).
            rot=(0.0, 0.0, 0.7071068, 0.7071068),
            joint_pos=dict(zip(JOINTS, WORKSHOP_INITIAL_JOINT_POSITION, strict=True)),
        ),
        soft_joint_pos_limit_factor=0.98,
    )
```

`SO101_CFG.replace(...)` creates a configuration with the requested changes
based on the SO-101 configuration built-in to Isaac Lab. The nested
`init_state.replace(...)` changes the initial pose without rebuilding the other
settings. `dict(zip(JOINTS, WORKSHOP_INITIAL_JOINT_POSITION, strict=True))`
pairs each joint name with its starting angle; `strict=True` raises an error if
the two sequences have different lengths.

At this stage, the only **event** we need is a reset to the default scene state.
This is already supplied in `lesson_scaffold.py`.

```python
# src/isaaclab_tutorial/tasks/place_vial/config/so101/lesson_scaffold.py
@configclass
class LessonEventsCfg:
    reset_scene = EventTerm(
        func=mdp.reset_scene_to_default,
        mode="reset",
        params={"reset_joint_targets": True}
    )
```

The function `mdp.reset_scene_to_default` is provided by Isaac Lab. You can find
the description in the [Isaac Lab
documentation](https://isaac-sim.github.io/IsaacLab/release/3.0.0/source/api/lab/isaaclab.envs.mdp.html#isaaclab.envs.mdp.events.reset_scene_to_default).
The reset helper writes the configured default poses and velocities back to
the scene assets for the environments being reset. With
`reset_joint_targets=True`, it also resets the joint-position targets. Resetting
the measured pose without its controller target could make a joint immediately
move toward a target left over from the previous episode.

The Isaac Lab API provides many useful functions for interacting with the scene,
including resetting the scene, getting observations, and applying actions.
Before writing a utility from scratch, take a look at the Isaac Lab API—you
may find a function that already does what you need.

The supplied `JointObservationsCfg` in `lesson_scaffold.py` defines an observation
space that includes the robot’s joint positions and velocities. The action space stays empty until the
[simple-agent lesson](https://docs.nvidia.com/learning/physical-ai/getting-started-with-isaac-lab/2026-10.0/train-a-robot-arm/04-simple-agents.md).

```python
# src/isaaclab_tutorial/tasks/place_vial/config/so101/lesson_scaffold.py
@configclass
class JointObservationsCfg(ObsGroup):
    joint_pos = ObsTerm(func=mdp.joint_pos, params={"asset_cfg": SceneEntityCfg("robot")})
    joint_vel = ObsTerm(func=mdp.joint_vel, params={"asset_cfg": SceneEntityCfg("robot")})

    def __post_init__(self):
        self.enable_corruption = False
        self.concatenate_terms = True
```

The robot configuration completes the scene used by the supplied
`SO101InspectionEnvCfg(ManagerBasedRLEnvCfg)` for
inspecting the scene without agent control. The [Isaac Lab manager-based
environment
tutorial](https://isaac-sim.github.io/IsaacLab/release/3.0.0/source/how-to/create_manager_base_env.html#tutorial-create-manager-base-env)
explains the underlying configuration pattern.

At this stage, the scene contains the robot and light, with no vial or rack.

![The SO-101 arm standing alone on an empty ground plane.](images/so101-initial.png)

In the next lesson, we’ll add a vial, a rack, and a desk to the scene. In the
lesson after that, we’ll add the actions and observations needed for simple
agents.

## Inspect the Robot

Run the scene from the tutorial repository root:

```bash
uv run --extra video                 \
  isaaclab zero_agent                \
  --task IsaacTutorial-Inspect-SO101 \
  --num_envs 1                       \
  --max_steps 120                    \
  --visualizer newton                \
  presets=newton_mjwarp,record_video
```

The inspection task has zero action values and 12 joint observations. It advances
physics for 120 steps without a learned controller. Inspect the newest recording
in `videos/inspection`: the robot should be present, with no vial or rack.
If validation reports a missing robot, check the `lesson-02-robot` region.

Keep `--extra video` so you can save the video.

Running on a remote server with no monitor? You might see `No display found`.
That’s fine: Newton simply renders *headlessly* (without opening a window), and
you can watch the saved video instead. If you’d like an interactive window,
you’ll need a real graphical session. Just setting the `DISPLAY` variable
won’t create one.

## Review

### Check Your Understanding

Does the robot’s actuator count define the policy’s action space?

### Answer

No. The action configuration defines how policy outputs map to selected joint commands, including their order and scaling. The robot asset defines the joints and actuators.

### Apply What You Learned

How would the robot’s initial pose and actuator settings affect vial placement? Choose one initial joint position or actuator setting, predict an effect, and explain which limits or action mappings matter.

### <svg version="1.1" width="1.0em" height="1.0em" class="sd-octicon sd-octicon-dependabot" viewBox="0 0 16 16" aria-hidden="true"><path d="M5.75 7.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5a.75.75 0 0 1 .75-.75Zm5.25.75a.75.75 0 0 0-1.5 0v1.5a.75.75 0 0 0 1.5 0v-1.5Z"></path><path d="M6.25 0h2A.75.75 0 0 1 9 .75V3.5h3.25a2.25 2.25 0 0 1 2.25 2.25V8h.75a.75.75 0 0 1 0 1.5h-.75v2.75a2.25 2.25 0 0 1-2.25 2.25h-8.5a2.25 2.25 0 0 1-2.25-2.25V9.5H.75a.75.75 0 0 1 0-1.5h.75V5.75A2.25 2.25 0 0 1 3.75 3.5H7.5v-2H6.25a.75.75 0 0 1 0-1.5ZM3 5.75v6.5c0 .414.336.75.75.75h8.5a.75.75 0 0 0 .75-.75v-6.5a.75.75 0 0 0-.75-.75h-8.5a.75.75 0 0 0-.75.75Z"></path></svg> Agent Tutor Prompt

Copy this prompt and include your answer:

> Review my answer to this question: How would the robot’s initial pose and actuator settings affect vial placement? Choose one initial joint position or actuator setting, predict an effect, and explain which limits or action mappings matter.

> My answer: [paste your answer here].

> Check my reasoning about joint names, initial state, limits, gains, and distinction between asset mechanics and action mapping. Ask for missing context rather than assuming it. Identify what I explained well, point out one gap or misconception, and ask a follow-up question that helps me improve my answer. Let me revise it before supplying a complete answer.

### Key Takeaways

- The SO-101 has five arm joints and a gripper actuator.
- The action mapping and the robot mechanics are separate parts of the configuration.
- The module asset includes the wrist camera used later by the student policy.
