Hydra Configuration System#

Isaac Lab supports the Hydra configuration system to modify the task’s configuration using command line arguments, which can be useful to automate experiments and perform hyperparameter tuning.

Any parameter of the environment can be modified by adding one or multiple elements of the form env.a.b.param1=value to the command line input, where a.b.param1 reflects the parameter’s hierarchy, for example env.actions.joint_effort.scale=10.0. Similarly, the agent’s parameters can be modified by using the agent prefix, for example agent.seed=2024.

The way these command line arguments are set follow the exact structure of the configuration files. Since the different RL frameworks use different conventions, there might be differences in the way the parameters are set. For example, with rl_games the seed will be set with agent.params.seed, while with rsl_rl, skrl and sb3 it will be set with agent.seed.

As a result, training with hydra arguments can be run with the following syntax:

python scripts/reinforcement_learning/rsl_rl/train.py --task=Isaac-Cartpole-v0 --headless env.actions.joint_effort.scale=10.0 agent.seed=2024
python scripts/reinforcement_learning/rl_games/train.py --task=Isaac-Cartpole-v0 --headless env.actions.joint_effort.scale=10.0 agent.params.seed=2024
python scripts/reinforcement_learning/skrl/train.py --task=Isaac-Cartpole-v0 --headless env.actions.joint_effort.scale=10.0 agent.seed=2024
python scripts/reinforcement_learning/sb3/train.py --task=Isaac-Cartpole-v0 --headless env.actions.joint_effort.scale=10.0 agent.seed=2024

The above command will run the training script with the task Isaac-Cartpole-v0 in headless mode, and set the env.actions.joint_effort.scale parameter to 10.0 and the agent.seed parameter to 2024.

Note

To keep backwards compatibility, and to provide a more user-friendly experience, we have kept the old cli arguments of the form --param, for example --num_envs, --seed, --max_iterations. These arguments have precedence over the hydra arguments, and will overwrite the values set by the hydra arguments.

Modifying advanced parameters#

Callables#

It is possible to modify functions and classes in the configuration files by using the syntax module:attribute_name. For example, in the Cartpole environment:

class ObservationsCfg:
    """Observation specifications for the MDP."""

    @configclass
    class PolicyCfg(ObsGroup):
        """Observations for policy group."""

        # observation terms (order preserved)
        joint_pos_rel = ObsTerm(func=mdp.joint_pos_rel)
        joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel)

        def __post_init__(self) -> None:
            self.enable_corruption = False
            self.concatenate_terms = True

    # observation groups
    policy: PolicyCfg = PolicyCfg()

we could modify joint_pos_rel to compute absolute positions instead of relative positions with env.observations.policy.joint_pos_rel.func=isaaclab.envs.mdp:joint_pos.

Setting parameters to None#

To set parameters to None, use the null keyword, which is a special keyword in Hydra that is automatically converted to None. In the above example, we could also disable the joint_pos_rel observation by setting it to None with env.observations.policy.joint_pos_rel=null.

Dictionaries#

Elements in dictionaries are handled as a parameters in the hierarchy. For example, in the Cartpole environment:

        spawn=sim_utils.GroundPlaneCfg(size=(100.0, 100.0)),
    )

    # cartpole
    robot: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")

    # lights
    dome_light = AssetBaseCfg(
        prim_path="/World/DomeLight",
        spawn=sim_utils.DomeLightCfg(color=(0.9, 0.9, 0.9), intensity=500.0),
    )


##
# MDP settings
##


@configclass
class ActionsCfg:
    """Action specifications for the MDP."""

    joint_effort = mdp.JointEffortActionCfg(asset_name="robot", joint_names=["slider_to_cart"], scale=100.0)


the position_range parameter can be modified with env.events.reset_cart_position.params.position_range="[-2.0, 2.0]". This example shows two noteworthy points:

  • The parameter we set has a space, so it must be enclosed in quotes.

  • The parameter is a list while it is a tuple in the config. This is due to the fact that Hydra does not support tuples.

Modifying inter-dependent parameters#

Particular care should be taken when modifying the parameters using command line arguments. Some of the configurations perform intermediate computations based on other parameters. These computations will not be updated when the parameters are modified.

For example, for the configuration of the Cartpole camera depth environment:

class CartpoleDepthCameraEnvCfg(CartpoleRGBCameraEnvCfg):
    # camera
    tiled_camera: CameraCfg = CameraCfg(
        prim_path="/World/envs/env_.*/Camera",
        offset=CameraCfg.OffsetCfg(pos=(-5.0, 0.0, 2.0), rot=(0.0, 0.0, 0.0, 1.0), convention="world"),
        data_types=["depth"],
        spawn=sim_utils.PinholeCameraCfg(
            focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 20.0)
        ),
        width=100,
        height=100,
    )

    # spaces
    observation_space = [tiled_camera.height, tiled_camera.width, 1]

If the user were to modify the width of the camera, i.e. env.tiled_camera.width=128, then the parameter env.observation_space=[80,128,1] must be updated and given as input as well.

Similarly, the __post_init__ method is not updated with the command line inputs. In the LocomotionVelocityRoughEnvCfg, for example, the post init update is as follows:

class LocomotionVelocityRoughEnvCfg(ManagerBasedRLEnvCfg):
    """Configuration for the locomotion velocity-tracking environment."""

    # Simulation settings — shared physics preset (PhysX + MJWarp) for all rough-terrain envs
    sim: SimulationCfg = SimulationCfg(physics=RoughPhysicsCfg())
    # Scene settings
    scene: MySceneCfg = MySceneCfg(num_envs=4096, env_spacing=2.5)
    # Basic settings
    observations: ObservationsCfg = ObservationsCfg()
    actions: ActionsCfg = ActionsCfg()
    commands: CommandsCfg = CommandsCfg()
    # MDP settings
    rewards: RewardsCfg = RewardsCfg()
    terminations: TerminationsCfg = TerminationsCfg()
    events: EventsCfg = EventsCfg()
    curriculum: CurriculumCfg = CurriculumCfg()

    def __post_init__(self):
        """Post initialization."""
        # general settings
        self.decimation = 4
        self.episode_length_s = 20.0
        # simulation settings
        self.sim.dt = 0.005
        self.sim.render_interval = self.decimation
        self.sim.physics_material = self.scene.terrain.physics_material
        # update sensor update periods
        # we tick all the sensors based on the smallest update period (physics update period)
        if self.scene.height_scanner is not None:
            self.scene.height_scanner.update_period = self.decimation * self.sim.dt
        if self.scene.contact_forces is not None:
            self.scene.contact_forces.update_period = self.sim.dt

        # check if terrain levels curriculum is enabled - if so, enable curriculum for terrain generator
        # this generates terrains with increasing difficulty and is useful for training
        if getattr(self.curriculum, "terrain_levels", None) is not None:
            if self.scene.terrain.terrain_generator is not None:
                self.scene.terrain.terrain_generator.curriculum = True
        else:
            if self.scene.terrain.terrain_generator is not None:
                self.scene.terrain.terrain_generator.curriculum = False

Here, when modifying env.decimation or env.sim.dt, the user needs to give the updated env.sim.render_interval, env.scene.height_scanner.update_period, and env.scene.contact_forces.update_period as input as well.

Custom Configuration Validation#

Configclass objects can define a validate_config() method to perform domain-specific validation after all fields have been resolved. This hook is called automatically after preset resolution and MISSING-field checks succeed, allowing you to catch invalid parameter combinations early with clear error messages.

Defining a validation hook:

from isaaclab.utils import configclass

@configclass
class MyEnvCfg:
    physics_backend: str = "physx"
    use_multi_asset: bool = False

    def validate_config(self):
        if self.physics_backend == "newton" and self.use_multi_asset:
            raise ValueError(
                "Newton physics does not support multi-asset spawning."
                " Use a single-geometry object preset instead."
            )

When it runs:

  1. All MISSING fields are checked first — if any remain, TypeError is raised.

  2. Only then is validate_config() called on the top-level config object.

  3. The hook should raise ValueError with a clear message and migration guidance.

Common validation patterns:

  • Physics backend compatibility (e.g., Newton does not support multi-asset spawning)

  • Renderer and camera data type compatibility (e.g., Newton Warp only supports rgb and depth)

  • Feature extractor compatibility with camera configuration

Preset System#

The preset system lets you swap out entire config sections – or individual scalar values – with a single command line argument. Instead of overriding individual fields, you select a named preset that completely replaces the config section (no field merging).

Presets are declared by subclassing PresetCfg or by using the preset() convenience factory. The system recursively discovers all presets from nested configs automatically, including presets inside dict-valued fields (e.g. actuators).

Override Order#

Overrides are applied in sequence:

  1. Auto-default: Configs with a "default" field auto-apply without CLI args

  2. Global presets: presets=newton_mjwarp,inference applies to ALL matching configs

  3. Path presets: env.backend=newton_mjwarp replaces a specific section

  4. Scalar overrides: env.sim.dt=0.001 modifies individual fields

Defining Presets with PresetCfg#

Create a PresetCfg subclass where each field is a named alternative. The default field is the config used when no CLI override is given:

from isaaclab_tasks.utils import PresetCfg

@configclass
class PhysicsCfg(PresetCfg):
    default: PhysxCfg = PhysxCfg()
    newton_mjwarp: NewtonCfg = NewtonCfg()

@configclass
class MyEnvCfg:
    physics: PhysicsCfg = PhysicsCfg()
# Use Newton physics backend
python train.py --task=Isaac-Reach-Franka-v0 env.physics=newton_mjwarp

The default field can be set to None to make an optional feature that is disabled unless explicitly selected:

@configclass
class CameraPresetCfg(PresetCfg):
    default = None
    small: CameraCfg = CameraCfg(width=64, height=64)
    large: CameraCfg = CameraCfg(width=256, height=256)

@configclass
class SceneCfg:
    camera: CameraPresetCfg = CameraPresetCfg()
# camera is None -- no camera overhead
python train.py --task=Isaac-Reach-Franka-v0

# activate camera with the "large" preset
python train.py --task=Isaac-Reach-Franka-v0 env.scene.camera=large

Backend and Solver Presets#

Physics backend selection uses the same preset system. A task can define a PresetCfg whose entries replace the complete physics config:

from isaaclab.utils import configclass
from isaaclab_newton.physics import KaminoSolverCfg, MJWarpSolverCfg, NewtonCfg
from isaaclab_physx.physics import PhysxCfg
from isaaclab_tasks.utils import PresetCfg

@configclass
class CartpolePhysicsCfg(PresetCfg):
    default: PhysxCfg = PhysxCfg()
    physx: PhysxCfg = PhysxCfg()
    newton_mjwarp: NewtonCfg = NewtonCfg(
        solver_cfg=MJWarpSolverCfg(njmax=5, nconmax=3),
        num_substeps=1,
    )
    newton_kamino: NewtonCfg = NewtonCfg(
        solver_cfg=KaminoSolverCfg(
            integrator="moreau",
            use_collision_detector=True,
            sparse_jacobian=True,
            padmm_max_iterations=100,
        ),
        num_substeps=1,
        debug_mode=False,
        use_cuda_graph=True,
    )

The newton_mjwarp and newton_kamino entries both select the Newton physics backend because both entries are NewtonCfg objects. The difference is the solver configuration: newton_mjwarp uses MJWarpSolverCfg, while newton_kamino uses KaminoSolverCfg.

Kamino is therefore a solver preset, not a separate Isaac Lab backend. The same Newton assets, sensors, renderers, and visualizers are used after the preset is resolved. It is a Proximal Alternating Direction Method of Multipliers (P-ADMM) based solver for constrained rigid multi-body dynamics, and its Isaac Lab support is currently beta.

Note

Kamino support is experimental and currently depends on the asset being structured in a way that Kamino can consume. Assets that work with the MuJoCo-Warp or PhysX presets may still require model-structure updates before they work with presets=newton_kamino.

# Select the Kamino solver preset everywhere it is defined
python train.py --task=Isaac-Cartpole-v0 presets=newton_kamino

# Select the Kamino solver preset for a specific physics config path
python train.py --task=Isaac-Cartpole-v0 env.sim.physics=newton_kamino

The newton_kamino preset is currently defined for Isaac-Cartpole-Direct-v0, Isaac-Ant-Direct-v0, Isaac-Cartpole-v0, and Isaac-Ant-v0. Passing presets=newton_kamino to a task without a newton_kamino preset does not enable Kamino; add and validate a task-specific preset first.

Inline Presets with preset()#

For simple values (scalars, lists) that don’t warrant a full subclass, use the preset() factory. It dynamically creates a PresetCfg instance from keyword arguments:

from isaaclab_tasks.utils.hydra import preset

# Scalar preset -- one line, no boilerplate class
self.scene.robot.actuators["legs"].armature = preset(default=0.0, newton_mjwarp=0.01, physx=0.0)

This is equivalent to defining a PresetCfg subclass with three float fields, but without the ceremony. The default keyword is required.

preset() works for any value type – scalars, lists, or even config instances:

# Resolution preset on a camera config field
width = preset(default=64, res128=128, res256=256)

# List preset for camera data types
@configclass
class DataTypeCfg(PresetCfg):
    default: list = ["rgb"]
    depth: list = ["depth"]
    albedo: list = ["albedo"]

Use preset() when the definition fits on a single line. Use a PresetCfg subclass when the options are verbose enough to benefit from type annotations and multiline formatting.

The preset system discovers preset() values anywhere in the config tree, including inside dict-valued fields such as actuators:

# Select MJWarp preset globally -- sets armature to 0.01
python train.py --task=Isaac-Velocity-Rough-Anymal-C-v0 presets=newton_mjwarp

Using Presets#

Path presets – select a specific preset for one config path:

python train.py --task=Isaac-Velocity-Rough-Anymal-C-v0 \
    env.events=newton_mjwarp

Global presets – apply the same preset name everywhere it exists:

# Apply "newton_mjwarp" preset to all configs that define it
python train.py --task=Isaac-Velocity-Rough-Anymal-C-v0 \
    presets=newton_mjwarp

Multiple global presets – apply several non-conflicting presets:

python train.py --task=Isaac-Velocity-Rough-Anymal-C-v0 \
    presets=newton_mjwarp,inference

Combined – global presets + scalar overrides:

python train.py --task=Isaac-Velocity-Rough-Anymal-C-v0 \
    presets=newton_mjwarp \
    env.sim.dt=0.002

Global Preset Conflict Detection#

If two global presets both match the same config path, an error is raised so the ambiguity is caught early:

ValueError: Conflicting global presets: 'foo' and 'bar'
            both define preset for 'env.events'

Real-World Example#

The ANYmal-C locomotion environment shows both PresetCfg and preset() working together:

        # post init of parent
        super().__post_init__()
        # switch robot to anymal-c
        self.scene.robot = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
        self.scene.robot.actuators["legs"].armature = preset(default=0.0, newton_mjwarp=0.01, physx=0.0)


@configclass
class AnymalCRoughEnvCfg_PLAY(AnymalCRoughEnvCfg):
    def __post_init__(self):
        # post init of parent
        super().__post_init__()

        # make a smaller scene for play
        self.scene.num_envs = 50
        self.scene.env_spacing = 2.5
        # spawn the robot randomly in the grid (instead of their terrain levels)
        self.scene.terrain.max_init_terrain_level = None
        # reduce the number of terrains to save memory
        if self.scene.terrain.terrain_generator is not None:
            self.scene.terrain.terrain_generator.num_rows = 5
            self.scene.terrain.terrain_generator.num_cols = 5

A single presets=newton_mjwarp on the command line resolves every PresetCfg and preset() that defines a newton_mjwarp field: the physics engine is swapped to Newton, AnymalCEventsCfg selects Newton-compatible events, and the actuator armature is set to 0.01.

# Default (PhysX events, armature=0.0)
python train.py --task=Isaac-Velocity-Rough-Anymal-C-v0

# MJWarp (Newton events, armature=0.01)
python train.py --task=Isaac-Velocity-Rough-Anymal-C-v0 presets=newton_mjwarp

Summary#

Override Type

Syntax

Effect

Scalar

env.sim.dt=0.001

Modify single field

Path preset

env.events=newton_mjwarp

Replace entire section

Global preset

presets=newton_mjwarp

Apply everywhere matching

Combined

presets=newton_mjwarp env.sim.dt=0.001

Global + scalar overrides