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_config: PhysicsCfg
│   ├── gpu_memory_config: GPUMemoryCfg
│   └── visualization: VisualizationCfg
├── robot: RobotCfg
│   ├── urdf_cfg: URDFCfg
│   ├── drive_pros: 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

mode

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 generation, 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 generation, max_episodes counts persisted per-environment episodes rather than vector batches. Thus num_envs: 4 and max_episodes: 10 produce two full four-row commits plus a final two-row commit. 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/tableware/stack_blocks_two/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,
    "max_episodes": 100,
    "max_episode_steps": 600,
    "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": "DeltaQposTerm",
                "params": {"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
        }
    }
}

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-env.

Set sensor_image_fps to null to capture camera previews once per eligible simulation step instead of applying a wall-clock FPS limit. run-env --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>/
├── env.yaml
├── task.franka.yaml
├── task.ur5.yaml
└── task_program/
    ├── integration.yaml
    └── program.yaml

The pure env.yaml component owns physical scene entities and ordinary Gym values. It requires environment_id, simulation, and env, may include run controls such as max_episode_steps, and contains no runnable id, robot, sensor, or Task Program selection:

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

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

id: TaskProgramRepeatedPickPlace-v1
environment:
  component: env.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

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.

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.

Robot Preset Configs#

Use class_type to select a RobotCfg subclass from embodichain.lab.sim.robots. 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": "DeltaQposTerm",
                    "params": {"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

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

  • "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 the reusable physical environment in env.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/.


See Also#