Running Environments with run-env#

embodichain run-env is the common entry point for loading a configured Gymnasium environment. It supports three primary workflows: inspecting an environment interactively, running an expert rollout while recording data, and replaying a previously recorded trajectory.

Execution modes#

Mode

Command switch

What it does

Typical use

Rollout

No mode switch

Generates task actions, steps the environment, and lets configured dataset or video recorders save each episode.

Expert demonstration generation and task smoke tests.

Preview

--preview

Resets the environment and opens an interactive IPython session on request. Dataset saving is disabled automatically.

Inspecting scene state, stepping manually, and checking observations.

Replay

--replay --replay_trajectory <file>

Restores recorded states, re-applies recorded actions, or provides an interactive trajectory scrubber, depending on --replay_mode.

Reproducing a rollout and diagnosing dynamics or task behavior.

--preview and --replay are mutually exclusive. Browser visualization with --viser is a display backend rather than a fourth execution mode, so it can be used while running or replaying an environment.

Inputs and startup behavior#

Every invocation requires a gym config:

embodichain run-env --gym_config path/to/gym_config.yaml

JSON, YAML, and YML files are supported. The input must be a runnable config with a non-empty id. It may declare simulation, robot, sensors, managers, and recorders inline, or select reusable components. A pure physical env.yaml component uses environment_id instead of id and is not runnable by itself.

A configuration-defined Task Program deployment conventionally uses task.<embodiment>.yaml. It selects environment.component, all three task_program paths (program, integration, and execution_policy), and embodiment.component:

embodichain run-env \
    --gym_config embodichain_tasks/configs/tasks/manipulation/tableware/pour_water/task.cobotmagic.yaml

At startup, run-env:

  1. discovers installed task packages through the embodichain.tasks entry point and executes their initialization hooks;

  2. loads the runnable config, expands its selected physical components, and composes any declared Task Program components;

  3. applies CLI overrides such as --num_envs, --device, --renderer, and --max_episodes;

  4. creates the environment selected by the gym config’s id; and

  5. enters rollout, preview, or replay mode.

Use embodichain run-env --help for the complete option list. The CLI Reference also lists defaults and visualization arguments.

Preview an environment#

Preview mode is intended for inspection before an expensive data-generation run:

embodichain run-env \
    --gym_config path/to/gym_config.yaml \
    --preview

After constructing and resetting the environment, the terminal accepts:

  • p: enter an IPython session with env in scope;

  • q: close the preview.

IPython is required only when entering the embedded session. Install it with pip install ipython if the p command reports that it is unavailable.

Inside the session, use the regular Gymnasium interface and EmbodiChain helpers to inspect state or advance the simulation. For example:

# Inspect the current robot configuration.
env.unwrapped.robot.get_qpos()

# Advance with an action that is valid for this task.
obs, reward, terminated, truncated, info = env.step(action)

# Display an RGB sensor observation.
env.unwrapped.preview_sensor_data("camera")

Preview mode does not execute the task’s expert policy. It also sets filter_dataset_saving=True, so configured structured dataset recorders do not write debugging episodes. Visual randomization remains enabled unless --filter_visual_rand is supplied.

Native window and Viser#

--preview controls the interactive terminal workflow. --viser instead selects the browser visualization backend and makes the simulation headless:

embodichain run-env \
    --gym_config path/to/gym_config.yaml \
    --viser \
    --viser-host 127.0.0.1 \
    --viser-port 8080

Use Viser to inspect selected environments, camera frustums, and all camera RGB previews locally or remotely. The expanded preview panel separates cameras created by record_camera_data under Record cameras from configured observation sensors under Sensor cameras. Limit the published batch with --viser-env-ids, and tune scene, image, or soft-body publication rates with the corresponding --viser-*-fps options. See Browser visualization with Viser for browser controls and remote-access details.

Run and record#

Without --preview or --replay, run-env enters offline rollout mode. For each vector batch, it asks the task for its demonstration segments, applies every action through env.step(), and commits selected environment rows with an explicit reset. max_episodes is the exact number of persisted per-environment episodes, not the number of vector batches. For example, max_episodes=10 with num_envs=4 runs three batches and commits only two rows from the final batch. --max_episodes overrides the value in the gym config:

embodichain run-env \
    --gym_config path/to/gym_config.yaml \
    --action_config path/to/action_config.yaml \
    --headless \
    --device cuda \
    --max_episodes 10

Headless execution is normally preferred for throughput. Use --filter_dataset_saving for a rollout smoke test that should not create a structured dataset.

Failed attempts are discarded and retried by default, up to demo_max_attempts (default: 3). Set save_failed_episodes: true on a dataset functor to keep a failed or truncated attempt that contains recorded frames. Such a commit counts toward max_episodes and is not retried. Empty plans and exceptions have no complete dataset transaction and are still discarded.

Multi-segment episodes#

An episode is the complete task; a segment is one semantic subtask inside it. For example, moving three objects is one episode containing three pick/place segments, even if each segment has its own motion trajectory. The task owns the number, order, and targets of those segments. The runner only manages the episode lifecycle, termination checks, retry, and commit/discard boundary.

Existing tasks implementing create_demo_action_list() remain compatible and are recorded as a single legacy segment. A multi-object task can instead implement a lazy segment planner:

from embodichain.lab.gym.envs import DemoSegment


def create_demo_segments(self):
    for object_uid in self.object_order:
        # This runs after the preceding segment, so planning sees the latest
        # scene state.
        actions = self.plan_pick_and_place(object_uid)
        yield DemoSegment(
            actions=actions,
            name="pick_and_place",
            target_uid=object_uid,
            instruction=f"Place {object_uid} in its target bin",
            # Segment validation is separate from Gym episode termination.
            validator=lambda uid=object_uid: self.is_object_placed(uid),
        )

Direct callers of generate_function() must no longer use num_traj as a sub-trajectory count. Only None and 1 are accepted; larger values raise ValueError. Move the repeated subtasks into create_demo_segments() so the task, rather than the runner, owns their order and validation.

Gym terminated and truncated always describe the whole episode, never an individual segment. A segment normally ends when its action iterable is exhausted; its optional zero-argument validator then returns one boolean per parallel environment. A failed validator aborts the batch. Episode-level success termination stops the remaining lazy plan without requesting another segment.

The executor checks terminal signals after every action and temporarily disables Gym auto-reset. Dataset recording is transactional: an explicit reset commits selected rows, while reset(options={"save_data": False}) discards them. Successful final validation always commits. A failed result commits only when a configured dataset functor enables save_failed_episodes; exceptions, interrupts, empty plans, and closing an environment with a live rollout abort pending structured data, videos, and trajectories.

Segment actions pass through the same action-dimension normalization used by legacy create_demo_action_list() tasks. A time-limit truncation is always an unsuccessful expert result, including when it occurs on the planner’s final action. It is discarded by default or retained as failed data when configured, so a task’s max_episode_steps must be greater than the longest valid expert plan when collecting successful demonstrations.

In a vectorized environment, segments and actions remain on one shared planner clock, but completion is tracked independently. When one environment reports success, its terminal result and recording cursor become sticky; subsequent shared actions use a safe hold/no-op command for that row while unfinished rows continue. Consequently, rollout and trajectory lengths may differ by row. The executor’s result remains batch-atomic: without failed-data saving every row must eventually succeed, while any failure or truncation invalidates the batch. With save_failed_episodes, selected failed rows are committed with their per-row failure metadata. Rows not needed to reach max_episodes are explicitly discarded, so parallel collection never overshoots the requested episode count.

save_failed_episodes belongs beside func and mode, not inside params:

{
  "func": "LeRobotRecorder",
  "mode": "save",
  "save_failed_episodes": true,
  "params": {
    "robot_meta": {"robot_type": "UR5"},
    "instruction": {"lang": "Pick and place the cube"}
  }
}

Run the built-in three-cycle example#

The shipped embodichain_tasks/configs/tasks/manipulation/repeated_pick_place/task.ur5.yaml deployment combines the reusable env.yaml with a UR5 parallel-gripper embodiment to pick up and freely place the same cube three times. Each cycle is a separate lazy segment. The next pickup is planned only after the previous placement has fallen and become stable, so the planner starts from the cube’s measured pose rather than its requested release pose.

No action-bank config is needed:

embodichain run-env \
    --gym_config embodichain_tasks/configs/tasks/manipulation/repeated_pick_place/task.ur5.yaml \
    --headless \
    --device cuda \
    --max_episodes 1

The reference environment leaves env.dataset empty, so this command is a rollout smoke test and does not persist a dataset. Add a LeRobotRecorder to the reusable env.yaml (or a copied inline deployment) to record one overall task plus three per-frame subtask/segment annotations. See Expert Data Generation for recorder setup and Inspect Recorded LeRobot Data for validation and preview.

Choose the recording output you need#

EmbodiChain uses “recording” for three related but distinct outputs:

Recording outputs#

Output

How it is enabled

Contents

Intended consumer

Structured dataset

A dataset manager in the gym config.

Observations, actions, episode/segment annotations, task metadata, and optionally sensor videos.

Imitation-learning or data-processing pipelines.

Debug or demo video

record_camera_data or record_camera_data_async as an interval event functor in the gym config.

Human-viewable RGB video from a configured camera pose.

Visual debugging, reports, and demonstrations.

Replay trajectory

--record_trajectory.

Per-step robot and object kinematic state, raw task action, and replay metadata in a PyTorch .pt file.

run-env --replay and ReplayWrapper.

Dataset video is still structured training data; it is not interchangeable with a replay trajectory. Conversely, --record_trajectory does not configure a LeRobot dataset or export an MP4.

For structured datasets, see Expert Data Generation and Dataset Functors. For human-viewable video, see Event Functors.

Record replayable trajectories#

Add --record_trajectory to a normal rollout:

embodichain run-env \
    --gym_config path/to/gym_config.yaml \
    --action_config path/to/action_config.yaml \
    --record_trajectory \
    --trajectory_save_dir outputs/trajectories

The recorder stores the robot root pose and complete joint position, the raw action before ActionManager preprocessing, and the pose or joint state of scene rigid objects and articulations. Each environment in a vectorized rollout is tracked independently. A trajectory is saved only at an explicit commit reset; this is normally a successful episode, or a recorded failure when save_failed_episodes is enabled on a dataset functor. close() is a durability barrier for already committed writes, not an implicit commit, so an unfinished trajectory is discarded.

Files are named like traj_env0_000000.pt. When --trajectory_save_dir is omitted, they are written below:

${EMBODICHAIN_DATA_ROOT:-~/.cache/embodichain_data}/trajectories/<run_id>/

Each file contains states, actions, and meta. Metadata includes the actual per-environment lengths, segment ranges and targets, timestep, robot identity and DOF, active joint IDs, recorded object IDs, and original environment IDs. LeRobot exports also contain per-frame annotation.segment_* fields and a meta/embodichain_episodes.jsonl sidecar with the complete segment records. The overall episode instruction stays in LeRobot’s task field; each semantic segment is exposed through a per-frame subtask_index resolved by meta/subtasks.parquet. The final directory is also printed when the run finishes.

Replay a trajectory#

Replay requires the trajectory file and the gym config used to record it:

embodichain run-env \
    --gym_config path/to/gym_config.yaml \
    --replay \
    --replay_trajectory outputs/trajectories/traj_env0_000000.pt

Keep the robot, active joint selection, scene object IDs, action processing, and episode horizon compatible with the recording run. Robot DOF or active joint mismatches are rejected. Missing or additional scene objects produce warnings. A single-environment trajectory can be broadcast with --num_envs; a multi-environment trajectory requires the replay environment count to match. Recorded lengths that exceed the current episode horizon are clamped.

If the task needs an action config to construct its environment or ActionManager, pass the same --action_config during replay as during recording.

If the gym config contains dataset recorders, add --filter_dataset_saving when the replay is for inspection only. Control replay applies this filter automatically.

Kinematic replay#

Kinematic replay is the default and the best first check:

embodichain run-env \
    --gym_config path/to/gym_config.yaml \
    --replay \
    --replay_trajectory path/to/trajectory.pt \
    --replay_mode kinematic

Physics is disabled and recorded poses and joint positions are written into the scene at every step. This gives an exact visual/state reproduction and renders new observations, but it does not recompute meaningful task rewards or successes from the recorded actions. At completion, run-env reports the maximum robot joint-state error against the recording.

Dynamic replay#

Dynamic replay sends each recorded raw action back through env.step():

embodichain run-env \
    --gym_config path/to/gym_config.yaml \
    --replay \
    --replay_trajectory path/to/trajectory.pt \
    --replay_mode dynamic

Physics, ActionManager preprocessing, observations, rewards, and termination logic all run normally. Use this mode to test whether the behavior can be re-simulated rather than merely restored. Results may diverge if the replay configuration changes physics, control, randomization, assets, or timestep settings that are not stored in the trajectory file.

Interactive control replay#

Control mode uses kinematic state restoration and lets you scrub the trajectory from the terminal or Viser:

embodichain run-env \
    --gym_config path/to/gym_config.yaml \
    --replay \
    --replay_trajectory path/to/trajectory.pt \
    --replay_mode control

The commands are:

  • n: next step;

  • p or b: previous step;

  • <N> followed by Enter: jump to step N;

  • a: auto-play, then press any key to pause;

  • r: return to step 0;

  • q: quit.

Run control mode with a native render window. --headless leaves no window in which to see the scrubbed state. Alternatively, add --viser to open an expanded Replay control panel in the browser. Its integer Frame slider jumps directly to any recorded frame, stays synchronized with terminal and auto-play commands, and pauses auto-play when dragged.