Environment Definition#

An Arena environment is three parts — scene, embodiment, and task (see the section overview). This page covers the two ways to define that composition, and where they diverge.

Both produce the same object: an IsaacLabArenaEnvironment. Environment Builder turns that into a runnable Isaac Lab environment:

Python factory and Graph YAML both produce an IsaacLabArenaEnvironment, which ArenaEnvBuilder compiles into a ManagerBasedRLEnv.
  • Python — a registered ArenaEnvironmentCfg. build() constructs the scene, embodiment, and task in code.

  • YAML — an ArenaEnvGraphSpec: a declarative environment graph. Nodes are assets; edges are spatial relations and task parameters. from_yaml() validates on load; to_arena_env() returns the same IsaacLabArenaEnvironment.

The same environment, side by side#

Same pick-and-place setup in both columns: a DROID arm picks a Rubik’s cube off a table and places it in a bowl. Full examples, same order, matching # markers — read across to see the correspondence.

YAML

Python

# Environment name
env_name: pick_and_place_maple_table

# Embodiment
embodiment:
  id: droid_abs_joint_pos
  registry_name: droid_abs_joint_pos

# Scene (lighting injected automatically)
background:
  id: maple_table
  registry_name: maple_table_robolab
objects:
- id: cube
  registry_name: rubiks_cube_hot3d_robolab
- id: bowl
  registry_name: bowl_ycb_robolab

# Sub-asset prims
object_references:
- id: table
  parent_id: maple_table
  prim_path: table
  object_type: rigid

# Layout
relations:
- kind: is_anchor
  subject: table
- kind: 'on'
  subject: cube
  reference: table
- kind: 'on'
  subject: bowl
  reference: table

# Task
task:
  composition: atomic
  description: Pick up the Rubik's cube and place it in the bowl.
  subtasks:
  - kind: PickAndPlaceTask
    params:
      pick_up_object: cube
      destination_location: bowl
      background_scene: maple_table
      episode_length_s: 20.0
@register_environment
class PickAndPlaceMapleTable(ArenaEnvironmentFactory):
    # Identity
    name = "pick_and_place_maple_table"

    def build(self, cfg):
        get = self.asset_registry.get_asset_by_name

        # Embodiment
        embodiment = get("droid_abs_joint_pos")(
            enable_cameras=cfg.enable_cameras,
        )

        # Scene (light must be added explicitly)
        maple_table = get("maple_table_robolab")()
        cube = get("rubiks_cube_hot3d_robolab")()
        bowl = get("bowl_ycb_robolab")()
        light = get("light")()

        # Sub-asset prims
        table = ObjectReference(
            name="table",
            prim_path="{ENV_REGEX_NS}"
                      "/maple_table_robolab/table",
            parent_asset=maple_table,
            object_type=ObjectType.RIGID,
        )

        # Layout
        table.add_relation(IsAnchor())
        table.set_initial_pose(Pose.identity())
        cube.add_relation(On(table))
        bowl.add_relation(On(table))

        # Task
        task = PickAndPlaceTask(
            pick_up_object=cube,
            destination_location=bowl,
            background_scene=maple_table,
            episode_length_s=20.0,
        )

        return IsaacLabArenaEnvironment(
            name=self.name,
            embodiment=embodiment,
            scene=Scene(assets=[
                maple_table, light, cube, bowl, table,
            ]),
            task=task,
        )

Where the two formats differ#

Three buckets:

  • Only Python

  • Only YAML

  • Same key, different behavior

Only in Python#

IsaacLabArenaEnvironment takes ten constructor arguments. build_arena_env_from_graph_spec() fills five: name, scene, embodiment, task, and a partial placer_params. Everything else has no YAML key.

Teleoperation device. YAML never sets env_cfg.teleop_devices. Python can pass a device that drives the embodiment:

teleop_device = self.device_registry.get_device_by_name(cfg.teleop_device)()
return IsaacLabArenaEnvironment(..., teleop_device=teleop_device)

RL interoperability. Gym kwargs for RL frameworks are Python-only. You cannot define an RL-training environment in YAML:

return IsaacLabArenaEnvironment(
    ...,
    rl_framework_entry_point="rsl_rl_cfg_entry_point",
    rl_policy_cfg="my_module:RLPolicyCfg",
)

Patching the compiled config. env_cfg_callback runs after Arena builds the ManagerBasedRLEnvCfg. Use it for viewport, decimation, physics, or anything else on that config:

def set_viewport(env_cfg):
    env_cfg.viewer.eye = (1.5, 1.5, 1.0)
    env_cfg.viewer.lookat = (0.0, 0.0, 0.5)

return IsaacLabArenaEnvironment(..., env_cfg_callback=set_viewport)

Extra episode recorder terms. Per-episode signals beyond the built-ins:

return IsaacLabArenaEnvironment(
    ...,
    episode_recorder_terms={"cube_pose": EpisodeRecorderTermCfg(func=record_cube_pose)},
)

Most placement tuning. YAML placement_validators can set only four ObjectPlacerParams fields: enabled_checks, required_checks, debug_visualize, and debug_visualize_output_path. Seeds, random yaw, pool size, resolve-on-reset, and IK reachability need Python:

placer_params = ObjectPlacerParams(
    placement_seed=42,          # reproducible layouts
    random_yaw_init=True,       # random yaw per object
    resolve_on_reset=False,     # solve once, reuse across resets
    min_unique_layouts_per_env=20,
    reachability_config=ReachabilityConfig(...),
)
return IsaacLabArenaEnvironment(..., placer_params=placer_params)

Extra variations. Both formats get the defaults an asset attaches in its constructor (a light brings intensity, color, and HDR). Anything the asset class does not declare needs Python:

pick_up_object.add_variation(MyCustomVariation(pick_up_object))

Calling methods on assets. YAML params go to constructors only. Method calls on a live instance have no YAML form:

directional_light.set_dome_light(dome_light)

Only in YAML#

Load-time validation. The spec is Pydantic. Registry names, node ids, relation arity, task params, and check subsets fail when the file loads — before Isaac Sim starts. Python has no equivalent; the same mistakes show up later as an AttributeError or a broken scene:

AssertionError: Unknown asset registry_name 'rubiks_cube_hot3d'
AssertionError: Relation kind 'is_anchor' must not define relation.reference
AssertionError: composition 'atomic' requires exactly one atomic task

Automatic default lighting. No light-tagged asset and no light in the background USD? The loader adds a dome light and a directional light (off until a lighting-direction variation turns it on). In Python, no light means no light.

Scene reuse across task files. external_yaml merges one scene into many task files. Pattern used in isaaclab_arena_environments/robolab/: scenes/*.yaml for layout, tasks/*.yaml for the task block only:

external_yaml: ../scenes/bagel_plate_banana_bowl.yaml
env_name: banana_on_plate
task:
  composition: atomic
  ...

Declared CLI overrides. cli_override_specs turns a node into a CLI flag with no code change. Below, --object replaces that node’s registry_name:

cli_override_specs:
- arg: object
  target_node_id: rubiks_cube_hot3d_robolab

Machine authoring. Graph YAML is the output of Agentic Environment Generation: natural language in, validated ArenaEnvGraphSpec out. Pydantic checks registry names, node ids, relation arity, and task params before the simulator sees anything.

Same key, different behavior#

Skip these when porting and you will not get the same environment.

Duplicate instances need unique names. YAML sets instance_name to the node id. Asset classes default it to the registry name, so two Python instances of the same asset share one name unless you set instance_name. Prim paths, event terms, and observation keys all key off that name:

# Two objects, one name. Pass instance_name yourself.
banana_a = get("banana_ycb_robolab")(instance_name="banana_a")
banana_b = get("banana_ycb_robolab")(instance_name="banana_b")

An anchor without a pose. YAML gives an is_anchor subject with no pose Pose.identity() — the layout origin for the relation solver. Python leaves the anchor with no pose until you set one:

table.add_relation(IsAnchor())
table.set_initial_pose(Pose.identity())

Relative prim paths. An object-reference prim_path that does not start with {ENV_REGEX_NS}/ expands to {ENV_REGEX_NS}/<background registry_name>/<prim_path>. Always the background registry name — even if parent_id points elsewhere. Python takes the path verbatim; it must be the full runtime path.

Cameras. YAML forwards enable_cameras into the embodiment params. A Python factory that ignores cfg.enable_cameras builds a camera-less env even when the flag is set. Policies that expect images will not be amused:

embodiment = get("droid_abs_joint_pos")(enable_cameras=cfg.enable_cameras)

Openable references. YAML picks the class from the fields: openable_joint_name in paramsOpenableObjectReference (and object_type: articulation); otherwise → ObjectReference. Python: you pick. Use the plain class for a door and you get a door with nothing to open.

Object set members. YAML constructs set members with no constructor args, so they cannot carry a usd_path. SimReady assets fail at load time and cannot be set members. Python RigidObjectSet takes live instances — SimReady is fine:

RigidObjectSet(name="bottles", objects=[simready_bottle, ycb_bottle])

How to spawn an environment#

YAML

Python

python isaaclab_arena/scripts/environment_runner.py \
  --env_spec \
  isaaclab_arena_environments/robolab/tasks/banana_on_plate.yaml
python isaaclab_arena/scripts/environment_runner.py \
  pick_and_place_maple_table \
  --embodiment droid_rel_joint_pos \
  --pick_up_object banana_ycb_robolab \
  --destination_location bowl_ycb_robolab

Or load a graph YAML directly:

spec = ArenaEnvGraphSpec.from_yaml("path/to/env_graph.yaml")
environment = spec.to_arena_env()
env = ArenaEnvBuilder(environment, ArenaEnvBuilderCfg()).make_registered()

Choosing between them#

Many assets and relations? Start with YAML as it is validated, and machine-generatable so you can focus on the scene layout and task definition. Reach for Python when the YAML cannot express what you need: RL registration, teleop, ManagerBasedRLEnvCfg patches, placement parameters, and so on.

Next Steps#

Environment Builder shows how either definition becomes a runnable Isaac Lab environment.