Configuration Guide#

EmbodiChain uses a declarative configuration system built on Python dataclasses. This guide explains the key patterns: @configclass, FunctorCfg, and JSON/YAML configuration files.


The @configclass Decorator#

All configuration objects use the @configclass decorator, which is similar to Python’s @dataclass with additional validation and serialization support.

from embodichain.utils import configclass
from dataclasses import MISSING


@configclass
class MyManagerCfg:
    param_a: float = 1.0
    param_b: str = MISSING  # Required — must be set by caller
    param_c: int = 10
  • Optional parameters have default values.

  • Required parameters use MISSING as the default — callers must provide them.

  • All parameters are typed for IDE auto-completion and static analysis.


Configuration Hierarchy#

EmbodiChain configs form a nested hierarchy:

EmbodiedEnvCfg
├── sim_cfg: SimulationManagerCfg
│   ├── render_cfg: RenderCfg
│   ├── physics_cfg: DefaultPhysicsCfg | NewtonPhysicsCfg
│   │   └── gpu_memory: GPUMemoryCfg  # Default backend only
│   └── visualization: VisualizationCfg
├── robot: RobotCfg
│   ├── urdf_cfg: URDFCfg
│   ├── joint_drive_props: JointDrivePropertiesCfg
│   └── solver_cfg: Dict[str, SolverCfg]
├── sensor: List[SensorCfg]
├── events: EventCfg
├── observations: ObservationCfg
├── rewards: RewardCfg
├── actions: ActionTermCfg
├── dataset: DatasetFunctorCfg
└── extensions: Dict[str, Any]

Each sub-config can be set independently, allowing fine-grained control over the environment.


Functor Configuration#

Functors are configured through specialized config classes that inherit from FunctorCfg. The base class has three fields:

@configclass
class FunctorCfg:
    func: Callable | Functor = MISSING   # The function or class to call
    params: dict[str, Any] = dict()      # Keyword arguments
    extra: dict[str, Any] = dict()       # Optional metadata

Specialized Config Classes#

Config Class

Extra Fields

Used By

ObservationCfg

mode, name

ObservationManager

EventCfg

mode, interval_step, is_global

EventManager

RewardCfg

weight, mode

RewardManager

ActionTermCfg

(no extra fields)

ActionManager

DatasetFunctorCfg

mode

DatasetManager

Python Config Example#

from embodichain.utils import configclass
from embodichain.lab.gym.envs.managers.cfg import (
    ObservationCfg,
    RewardCfg,
    EventCfg,
    SceneEntityCfg,
)
from embodichain.lab.gym.envs.managers.observations import get_object_pose


@configclass
class MyObsCfg:
    object_pose: ObservationCfg = ObservationCfg(
        func=get_object_pose,
        mode="add",
        name="object/pose",
        params={"entity_cfg": SceneEntityCfg(uid="my_cube")},
    )


@configclass
class MyRewardCfg:
    distance: RewardCfg = RewardCfg(
        func="distance_between_objects",
        weight=0.5,
        params={
            "source_entity_cfg": SceneEntityCfg(uid="cube"),
            "target_entity_cfg": SceneEntityCfg(uid="target"),
        },
    )


@configclass
class MyEventCfg:
    randomize_light: EventCfg = EventCfg(
        func="randomize_light",
        mode="interval",
        interval_step=5,
        params={"light_uid": "main_light"},
    )

JSON and YAML Configuration#

For RL training and data expansion, EmbodiChain uses file-based configs (.json, .yaml, or .yml). The file format mirrors the Python config structure but uses string names instead of direct function references.

Configs are loaded with embodichain.utils.utility.load_config, which selects the parser from the file extension. Both formats produce the same in-memory dictionary and are passed to config_to_cfg() for environment setup.

For offline expert expansion, collection.target_episodes counts persisted environment rows rather than vector batches. Thus num_envs: 4 and target_episodes: 10 produce two full four-row commits plus a final two-row commit. The legacy max_episodes field and CLI option map to the same target. Failed rows count only when the relevant DatasetFunctorCfg sets save_failed_episodes: true.

Example paths in the repository:

Use case

JSON example

YAML example

Runnable Gym deployment

embodichain_tasks/configs/tasks/manipulation/push_cube/env.json

embodichain_tasks/configs/tasks/manipulation/tableware/pour_water/task.cobotmagic.yaml

RL training

embodichain_tasks/configs/tasks/classic_control/cart_pole/agents/ppo.json

embodichain_tasks/configs/tasks/classic_control/cart_pole/agents/ppo.yaml

When a training config references a gym config (via trainer.gym_config), the nested path may also use any supported extension.

Environment Config (gym_config.json / gym_config.yaml)#

{
    "id": "EmbodiedEnv-v1",
    "num_envs": 4,
    "collection": {
        "target_episodes": 100
    },
    "max_episode_steps": 600,
    "physics": "default",
    "device": "cpu",
    "physics_config": {
        "gravity": [0.0, 0.0, -9.81],
        "bounce_threshold": 2.0,
        "enable_ccd": false,
        "length_tolerance": 0.05,
        "speed_tolerance": 0.25
    },
    "render_cfg": {
        "renderer": "auto",
        "spp": 1,
        "tone_mapping_enabled": false,
        "tone_mapping_exposure": 1.0
    },
    "visualization": {
        "backend": "viser",
        "scene_fps": 15.0,
        "sensor_image_fps": 2.0,
        "soft_body_fps": 5.0,
        "env_ids": [0],
        "viser_server": {
            "host": "127.0.0.1",
            "port": 8080
        }
    },
    "robot": {
        "uid": "robot",
        "urdf_cfg": {
            "components": [
                {
                    "component_type": "arm",
                    "urdf_path": "robots/my_robot/my_robot.urdf"
                }
            ]
        }
    },
    "sensor": [
        {
            "uid": "cam_high",
            "type": "StereoCamera",
            "height": 540,
            "width": 960
        }
     ],
     "env": {
        "control_parts": ["arm"],
        "actions": {
            "delta_qpos": {
                "func": "RelativeJointPositionAction",
                "params": {"part_name": "arm", "scale": 0.1}
            }
        },
        "events": {
            "randomize_table": {
                "func": "randomize_visual_material",
                "mode": "interval",
                "interval_step": 10,
                "params": {"uid": "table"}
            }
        },
        "observations": {
            "obj_pose": {
                "func": "get_object_pose",
                "mode": "add",
                "name": "object/pose",
                "params": {"entity_cfg": {"uid": "cube"}}
            }
        },
        "rewards": {
            "distance": {
                "func": "distance_between_objects",
                "weight": 0.5,
                "params": {
                    "source_entity_cfg": {"uid": "cube"},
                    "target_entity_cfg": {"uid": "target"}
                }
            }
        },
        "dataset": {
            "lerobot": {
                "func": "LeRobotRecorder",
                "mode": "save",
                "params": {
                    "save_path": "/path/to/output",
                    "robot_meta": {"robot_type": "DexforceW1"},
                    "use_videos": true
                }
            }
        },
        "extensions": {
            "success_threshold": 0.1
        }
    }
}

Every runnable Gym configuration explicitly owns one physics backend: "default" or "newton". Its optional flat physics_config mapping is decoded strictly against that backend, so Default-only fields cannot silently leak into a Newton configuration (and vice versa). The launcher cannot switch a file-owned backend with --physics; select a different runnable configuration when the backend should change.

device is optional. If omitted, the backend config keeps its own default (cpu for Default and cuda:0 for Newton). An explicitly configured device, or an explicit run-task --device ... override, is applied to both the environment tensors and the selected physics backend. In particular, --device cpu is not ignored for Newton.

Paired physics configurations#

The following are physics fragments, not complete runnable task files. Apply each fragment to a separate copy of the same inline deployment, retaining its required id, env, robot/embodiment, and scene declarations. Retune any backend-specific asset fields and validate the final configuration on its selected backend.

Default fragment:

physics: default
device: cuda:0
physics_config:
  physics_dt: 0.01
  gravity: [0.0, 0.0, -9.81]
  bounce_threshold: 2.0
  enable_ccd: false

Newton fragment for a rigid articulated scene:

physics: newton
device: cuda:0
physics_config:
  physics_dt: 0.01
  gravity: [0.0, 0.0, -9.81]
  num_substeps: 10
  solver_cfg:
    solver_type: mujoco_warp
  use_cuda_graph: true

Omit solver_cfg to let Newton’s AutoSolver select from the complete scene at prepare(). For componentized tasks, place these backend declarations in envs/default.yaml and envs/newton.yaml, then point the deployment’s environment.default and environment.newton entries at those files. Do not repeat physics or physics_config in the deployment. Keep the control period equal when comparing the two backends.

Entry point

Backend selection

Backend parameters

Python SimulationManagerCfg

Concrete physics_cfg type

Fields of DefaultPhysicsCfg or NewtonPhysicsCfg.

Inline runnable Gym file

Required physics

Flat physics_config, decoded against that backend.

Componentized deployment

Selected environment component’s physics

The same environment component owns physics_config.

Launcher --physics

Selects a declared variant or confirms a single component

Cannot synthesize a backend that has no file.

The visualization section is optional and defaults to {"backend": "none"}. Setting "backend": "viser" starts browser visualization when the environment constructs its SimulationManager. The --viser* command-line options override these values for embodichain run-task.

Set sensor_image_fps to null to capture camera previews once per eligible simulation step instead of applying a wall-clock FPS limit. run-task --viser uses this step-synchronized mode by default when neither the configuration nor --viser-image-fps supplies a rate.

Keep viser_server.host on loopback for remote workers and use SSH port forwarding unless the service is behind an authenticated gateway. See Browser visualization with Viser for the full schema, supported scene content, and deformable-object behavior.

Reusable Physical Environments and Runnable Deployments#

Use componentized YAML when one physical task environment must support several embodiments, or one embodiment must run in several environments. The task-local layout separates a reusable environment from runnable deployment choices:

<task>/
├── envs/
│   ├── default.yaml
│   └── newton.yaml
├── task.franka.yaml
├── task.ur5.yaml
├── expansion/
│   └── <task-profile>.yaml
└── task_program/
    ├── integration.yaml
    └── program.yaml

Each pure environment variant under envs/ owns one physics backend, physical scene entities, and ordinary Gym values. It requires environment_id, physics, simulation, and env, may include physics_config and run controls such as max_episode_steps, and contains no runnable id, robot, sensor, or Task Program selection:

environment_id: repeated_pick_place
physics: default
max_episode_steps: 1200
simulation:
  rigid_object:
    - uid: cube
      # Shape, dynamics, and initial pose.
env:
  events: {}
  dataset: {}

A runnable deployment has id and selects its environment variant(s) and embodiment. A configuration-defined Task Program additionally selects its program, integration, and execution policy:

id: TaskProgramRepeatedPickPlace-v1
environment:
  default: envs/default.yaml
  newton: envs/newton.yaml
task_program:
  program: task_program/program.yaml
  integration: task_program/integration.yaml
  execution_policy: ../../../components/execution_policies/trajectory_open_loop.yaml
embodiment:
  component: ../../../components/embodiments/ur5_dh_pgi_140_80.yaml
expansion:
  config: expansion/<task-profile>.yaml

All references above resolve relative to the runnable deployment file. An embodiment component owns its simulation robot and sensor suite; it may also own a skill_profile when Task Program needs semantic resources. The task-local integration.yaml owns its nested scene_binding, including each canonical entity_id to physical simulation_uid mapping.

The optional expansion.config reference keeps task-facing augmentation settings in a separate file. That file may define runtime, policy, overrides, and collection; collection.target_episodes is the final committed row count, while collection.selection chooses sequential or explicit logical recipes. The runner deep-merges task-local fields, applies the runtime overlay after resolving the selected environment variant, and then constructs the common environment. --physics selects default or newton from the task mapping, while --expansion-profile and --expansion-recipe-indices override the referenced expansion values. The legacy candidate_indices spelling remains accepted as an explicit recipe selection alias. Relative policy and resource paths inside the expansion file resolve from that file’s directory.

Component ownership is exclusive. Do not combine environment.component with inline environment or scene fields, and do not combine embodiment.component with inline robot or sensor fields. The original inline format and a standalone physical scene.component remain supported when no environment component is selected. A registered handwritten-trajectory task may select the same environment and embodiment components while omitting task_program, so neither reusable component is coupled to an expert-authoring method. See the Task Program tutorial for a complete runnable composition.

When environment.component is selected, that component exclusively owns physics and physics_config; do not repeat either field in the runnable deployment. This keeps backend choice next to the physical scene whose fields it validates.

Robot Preset Configs#

Use class_type to select a registered RobotCfg subclass through embodichain.robot_registry. Official concrete presets live in embodichain_robots; generic dual-arm composition remains in the core. Run embodichain list-robot to discover IDs and control summaries. Subclass-specific fields remain in the robot configuration and are passed to its from_dict() method. For example, this selects the canonical UR preset and then specifies the UR5 variant:

{
    "robot": {
        "class_type": "URRobot",
        "robot_type": "ur5",
        "uid": "Manipulator"
    }
}

For backward compatibility, existing configs may continue to use "robot_type": "CobotMagic" as the preset-class selector when the selected class has no separate variant field.

RL Training Config (train_config.json / train_config.yaml)#

{
    "trainer": {
        "exp_name": "push_cube",
        "seed": 42,
        "device": "cuda:0",
        "iterations": 500,
        "buffer_size": 1024
    },
    "env": {
        "id": "PushCubeRL",
        "cfg": {
            "num_envs": 4,
            "actions": {
                "delta_qpos": {
                    "func": "RelativeJointPositionAction",
                    "params": {"part_name": "arm", "scale": 0.1}
                }
            }
        }
    },
    "policy": {
        "name": "actor_critic",
        "actor": {
            "type": "mlp",
            "network_cfg": {"hidden_sizes": [256, 256], "activation": "relu"}
        },
        "critic": {
            "type": "mlp",
            "network_cfg": {"hidden_sizes": [256, 256], "activation": "relu"}
        }
    },
    "algorithm": {
        "name": "ppo",
        "cfg": {
            "learning_rate": 0.0001,
            "n_epochs": 10,
            "batch_size": 64,
            "gamma": 0.99,
            "gae_lambda": 0.95,
            "clip_coef": 0.2
        }
    }
}

The same structure in YAML:

trainer:
  exp_name: push_cube
  seed: 42
  device: cuda:0
  iterations: 500
  buffer_size: 1024
  gym_config: embodichain_tasks/configs/tasks/classic_control/cart_pole/env.yaml
policy:
  name: actor_critic
  actor:
    type: mlp
    network_cfg:
      hidden_sizes: [256, 256]
      activation: relu
algorithm:
  name: ppo
  cfg:
    learning_rate: 0.0001
    batch_size: 64
    gamma: 0.99

String-Based Function Resolution#

In JSON and YAML configs, functor functions are specified by name (string). EmbodiChain resolves these strings at runtime by searching registered modules. For example:

  • "distance_between_objects" resolves to embodichain.lab.gym.envs.managers.rewards.distance_between_objects

  • "RelativeJointPositionAction" resolves to embodichain.lab.gym.envs.managers.actions.RelativeJointPositionAction

  • "get_object_pose" resolves to embodichain.lab.gym.envs.managers.observations.get_object_pose

When writing custom functors, make sure they are imported in the module’s __init__.py so the resolver can find them.


SceneEntityCfg in Config Files#

When referencing scene entities in JSON or YAML, use a dictionary with a uid key:

{"uid": "my_cube"}

This is automatically converted to a SceneEntityCfg object at runtime.


Tips#

  1. Start from an existing config. Copy a task config from embodichain_tasks/configs/tasks/<domain>/<task>/, then modify it for your task.

  2. Use Python configs for development. They provide IDE auto-completion and type checking.

  3. Use JSON or YAML configs for experiments. YAML is often easier to read for nested structures; JSON remains fully supported.

  4. Validate configs early. Run your environment with a short episode count to catch config errors before long training runs.

  5. Keep ownership explicit. Put each reusable physical backend in envs/<backend>.yaml, runnable choices in task.<embodiment>.yaml, and Task Program intent plus semantic integration in task_program/{program,integration}.yaml. Keep shared embodiments and execution policies under configs/components/, and keep task-facing expansion overrides under expansion/.


See Also#