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
MISSINGas 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 |
|---|---|---|
|
|
ObservationManager |
|
|
EventManager |
|
|
RewardManager |
|
(no extra fields) |
ActionManager |
|
|
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 |
|
|
RL training |
|
|
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 |
Concrete |
Fields of |
Inline runnable Gym file |
Required |
Flat |
Componentized deployment |
Selected environment component’s |
The same environment component owns |
Launcher |
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 toembodichain.lab.gym.envs.managers.rewards.distance_between_objects"RelativeJointPositionAction"resolves toembodichain.lab.gym.envs.managers.actions.RelativeJointPositionAction"get_object_pose"resolves toembodichain.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#
Start from an existing config. Copy a task config from
embodichain_tasks/configs/tasks/<domain>/<task>/, then modify it for your task.Use Python configs for development. They provide IDE auto-completion and type checking.
Use JSON or YAML configs for experiments. YAML is often easier to read for nested structures; JSON remains fully supported.
Validate configs early. Run your environment with a short episode count to catch config errors before long training runs.
Keep ownership explicit. Put each reusable physical backend in
envs/<backend>.yaml, runnable choices intask.<embodiment>.yaml, and Task Program intent plus semantic integration intask_program/{program,integration}.yaml. Keep shared embodiments and execution policies underconfigs/components/, and keep task-facing expansion overrides underexpansion/.
See Also#
Custom Functors Guide — How to write observation, reward, event, and action functors
Embodied Environments — Full environment configuration reference
Browser Visualization — Viser configuration and runtime behavior
Tutorial: Modular Environment — Complete example using config-driven setup
Tutorial: RL Training — RL training configuration walkthrough