Skip to content

LIBERO

Eden runs the 130 tasks of the LIBERO benchmark through one registered task, libero, which builds any catalog task from its upstream BDDL file. Read this page to evaluate a policy on LIBERO in Eden, including a generalist policy served by a remote server. The code is in eden/tasks/libero/.

LIBERO, robosuite, SIMPLER and PolaRiS scenes in Eden

What is ported

  • The catalog. All five suites, by their upstream names: libero_spatial, libero_object, libero_goal and libero_10 (10 tasks each) and libero_90 (90 tasks), plus the derived libero_100 (libero_90 followed by libero_10). The 10-task suites also carry LIBERO's official continual-learning task orders.
  • The tasks. Each task's scene, fixtures, movable objects, language instruction and goal come from its BDDL file, which is fetched on first use from the Kashu7100/eden_libero dataset along with the objects.
  • The robot and controller. LIBERO's mounted Panda (eden.options.robots.libero.LiberoPanda), driven by an OperationalSpaceController in robosuite's OSC_POSE convention (0.05 m and 0.5 rad per unit action) and a stateful LiberoPandaGripperController (+1 closes, -1 opens, 0 holds).
  • The goal check. LiberoGoalMetric evaluates the task's BDDL goal predicates (On, In, Open, ...) with the upstream semantics.
  • The protocol. 20 Hz control (a 2 ms step, decimation=25), five zero-action settling steps (LIBERO_RESET_WARMUP_STEPS) and then 600 policy actions (LIBERO_POLICY_HORIZON_STEPS). The episode ends on goal success or after those 605 steps.

The task defines no rewards: it is an evaluation task, not an RL training task.

Fixed-state bundles

LIBERO evaluates every task from a fixed set of initial states. Eden resets from a fixed-state bundle per task, which holds the exact poses, joint positions and velocities of each init ID. Eden does not ship, generate or download these bundles: you provide them.

The layout is:

<bundle_root>/<suite>/<task>/state-bundle/task.json
<bundle_root>/<suite>/<task>/state-bundle/states.npz

task.json names the task, its init IDs and its entities; states.npz stores the arrays under semantic keys such as entity/robot/root_pos_world and entity/robot/joint/joint1/qpos, so each array is bound to the entity or joint it restores rather than to a position in a list. Both are validated when the env builds (eden.tasks.libero.state_bundle.load_state_bundle).

If you omit bundle_root, the task looks under libero/fixed_states in the asset directory. Nothing populates that path for you, so in practice you pass bundle_root.

BDDL :init is not the reset state

The BDDL :init predicates are kept as symbolic catalog facts (LiberoTaskSpec.initial_state) for inspection and tooling. They are not used to reconstruct simulator state; the bundle is the only reset source.

Selecting a task and its init states

libero takes three TaskMod flags (LiberoTaskMod):

Flag Meaning Default
--task_name A catalog task name pick_up_the_alphabet_soup_and_place_it_in_the_basket (the first libero_object task)
--bundle_root Root of the <suite>/<task>/state-bundle directories libero/fixed_states in the asset directory
--init_ids Comma-separated init IDs, one per env empty: the first num_envs states of the bundle
eden smoke --task libero \
    --task_name pick_up_the_alphabet_soup_and_place_it_in_the_basket \
    --bundle_root /path/to/libero-fixed-states

--init_ids may name any nonempty subset of the bundle's init IDs. Because it assigns one state per env, it sets num_envs.

--init_ids and --num_envs must agree

Every num_envs override is applied after the task's modifiers run, so a later override can contradict the count --init_ids set. eden smoke --num_envs defaults to 2 and always applies, so a single-state smoke run needs both flags. A mismatch is rejected when the env builds, naming both values.

eden smoke --task libero --init_ids 7 --num_envs 1   # one env, fixed state 7
eden smoke --task libero                              # num_envs envs, the first num_envs states

To list tasks, use the catalog functions:

from eden.tasks.libero import get_suite_names, get_suite_task_names, get_task_spec

get_suite_names()                       # ('libero_spatial', ..., 'libero_90', 'libero_100')
get_suite_task_names("libero_goal")     # in task order 0; pass task_order_index= for another
spec = get_task_spec("turn_on_the_stove")
spec.language_instruction, spec.goal_state

Running an episode in Python

import torch

import eden
from eden.envs.base import RLEnvBase
from eden.tasks import TASK_REGISTRY
from eden.tasks.libero import LIBERO_RESET_WARMUP_STEPS

eden.init()
config = TASK_REGISTRY.build(
    "libero",
    task_name="pick_up_the_alphabet_soup_and_place_it_in_the_basket",
    bundle_root="/path/to/libero-fixed-states",
    init_ids="7",
)
env = RLEnvBase.from_config(config)
env.build()
observations, _ = env.reset()

# The benchmark protocol: five exact zero-action settling steps before the 600 policy actions.
zero_action = {
    "osc_pose": torch.zeros((env.num_envs, 6), device=env.device),
    "gripper": torch.zeros((env.num_envs, 1), device=env.device),
}
for _ in range(LIBERO_RESET_WARMUP_STEPS):
    observations, _, _, _, _ = env.step(zero_action)

The policy observation group is not concatenated. It holds agentview_rgb and eye_in_hand_rgb (128 × 128 by default), arm_joint_pos and gripper_joint_pos. The language instruction is config.language_instructions[0].

Evaluating a generalist policy

Two remote-policy clients ship a LIBERO setup that matches the upstream evaluation client end to end:

Client policy_setup Upstream client it matches
PiPolicy (eden.extensions.policies.pi.policy) libero OpenPI examples/libero/main.py
Gr00tPolicy (eden.extensions.policies.gr00t.policy) libero_sim Isaac-GR00T's LIBERO eval

Both drive the task's own osc_pose and gripper terms, mirror the images left-right as LIBERO's training images are, and step at 20 Hz. The clients need the policy-client extra (pip install 'eden-world[policy-client]', or uv sync --inexact --extra policy-client in a checkout).

Render at 256 × 256 for these checkpoints

The stock task renders 128 × 128, but OpenPI's and GR00T's LIBERO checkpoints were trained on 256 × 256 renders, and both clients refuse a camera at another resolution rather than upsample. Set both cameras before building:

config.cameras_options.agentview.cam_resolution = (256, 256)
config.cameras_options.eye_in_hand.cam_resolution = (256, 256)

Do not resize the frames in the client instead: an upsampled render is off the training distribution.

A minimal OpenPI loop:

import torch

import eden
from eden.envs.base import RLEnvBase
from eden.extensions.policies.pi.policy import PiPolicy
from eden.options.managers.observations import ObservationManagerOptions
from eden.tasks import TASK_REGISTRY
from eden.tasks.libero import LIBERO_RESET_WARMUP_STEPS

eden.init()
config = TASK_REGISTRY.build("libero", task_name="turn_on_the_stove", bundle_root="/path/to/libero-fixed-states")
config.env_options.num_envs = 1
config.cameras_options.agentview.cam_resolution = (256, 256)
config.cameras_options.eye_in_hand.cam_resolution = (256, 256)
config.observation_options = ObservationManagerOptions(**PiPolicy.observation_options(policy_setup="libero"))
env = RLEnvBase.from_config(config, eval_mode=True)
env.build()

policy = PiPolicy(env, PiPolicy.configure(host="localhost", port=8000, policy_setup="libero"))  # your server
policy.set_task(config.language_instructions[0])

observations, _ = env.reset()
policy.reset_server()
zero = {"osc_pose": torch.zeros((1, 6), device=env.device), "gripper": torch.zeros((1, 1), device=env.device)}
for _ in range(LIBERO_RESET_WARMUP_STEPS):
    observations, *_ = env.step(zero)
for _ in range(600):
    observations, _, terminated, _, _ = env.step(policy.step(observations))
    if terminated.any():
        break

PiPolicy's default port is 8000. See Remote policies for the client architecture, the gripper conventions and the construction checks that refuse a mis-wired env.

Scenes

The 130 benchmark tasks use one of five reusable LIBERO scene presets, chosen from the BDDL problem name (tabletop, floor, kitchen tabletop, living-room tabletop and study tabletop); a sixth, the coffee table, covers additional tasks. The presets are in eden.options.scenes.libero (see Scenes and terrains). A preset holds only the workspace and the robot; the task layer adds the declared fixtures and objects, the cameras, the reset term and the goal metric. Constructing a preset on its own does not give a runnable LIBERO task: use TASK_REGISTRY.build("libero", ...).

Build task-specific LIBERO scenes from BDDL metadata.

Functions:

make_libero_scene_options

make_libero_scene_options(task_spec: LiberoTaskSpec, **extra_entities: BaseEntityOptions | CompositeEntityOptions) -> SceneOptions

Build a family preset with the fixtures and movable objects declared by BDDL.

Source code in eden/tasks/libero/scenes.py
def make_libero_scene_options(
    task_spec: LiberoTaskSpec,
    **extra_entities: BaseEntityOptions | CompositeEntityOptions,
) -> SceneOptions:
    """Build a family preset with the fixtures and movable objects declared by BDDL."""
    family_spec = _get_libero_family_spec(task_spec.problem_name)
    objects: dict[str, BaseEntityOptions] = {}
    staging_origin = (2.5, -0.8, 1.25)
    staging_step = 0.24
    staging_columns = 6
    staging_index = 0
    for entity_name, category_name in task_spec.entity_categories.items():
        if entity_name == family_spec.workspace_entity_name:
            continue
        entity_options = LIBERO_OBJECT_REGISTRY.get(category_name)(
            default_root_pos=(
                staging_origin[0] + (staging_index % staging_columns) * staging_step,
                staging_origin[1] + (staging_index // staging_columns) * staging_step,
                staging_origin[2],
            ),
            batch_fixed_verts=entity_name in task_spec.fixture_names,
        )
        if entity_name in task_spec.fixture_names and not entity_options.is_fixed_base:
            raise ValueError(f"LIBERO fixture {entity_name!r} ({category_name!r}) must use a fixed-base registry entry")
        objects[entity_name] = entity_options
        staging_index += 1
    return family_spec.scene_factory(**objects, **extra_entities)

Public API

Production LIBERO task construction and catalog discovery.

Attributes

LIBERO_POLICY_HORIZON_STEPS module-attribute

LIBERO_POLICY_HORIZON_STEPS = 600

LIBERO_RESET_WARMUP_STEPS module-attribute

LIBERO_RESET_WARMUP_STEPS = 5

Classes

LiberoConfig

LiberoConfig(**data)

Bases: EdenRLConfig

Shared production configuration for data-driven LIBERO tasks.

Source code in eden/options/options.py
def __init__(self, **data):
    # Seed type metadata as construction data so BaseModel validates it into
    # __pydantic_extra__ (no-op for extra="forbid" classes, which use
    # the serialization envelope instead).
    data.update(option_metadata(self))
    # NOTE: all of the option modifications/validations should be done in the __init__ method
    try:
        BaseModel.__init__(self, **data)
    except ValidationError as exc:
        # A misspelled field on an extra="forbid" class should teach the field set, not just
        # name the typo. Only rewritten when every error is extra_forbidden.
        enriched = _enrich_extra_forbidden(type(self), exc)
        if enriched is None:
            raise
        raise enriched from None

Attributes

env_options class-attribute instance-attribute
env_options: EnvOptions = Field(default_factory=_make_env_options)
renderer_options class-attribute instance-attribute
renderer_options: Rasterizer = Field(default_factory=Rasterizer)
action_options class-attribute instance-attribute
action_options: ActionManagerOptions = Field(default_factory=_make_action_options)
observation_options class-attribute instance-attribute
observation_options: ObservationManagerOptions = Field(default_factory=_make_observation_options)
termination_options class-attribute instance-attribute
termination_options: TerminationManagerOptions = Field(default_factory=_make_termination_options)

LiberoTaskMod

LiberoTaskMod(task_name: Annotated[str, 'LIBERO catalog task name.'] = get_suite_task_names('libero_object')[0], bundle_root: Annotated[str | None, 'Root containing <suite>/<task>/state-bundle directories; by default libero/fixed_states in the asset directory.'] = None, init_ids: Annotated[str, "Comma-separated fixed-state IDs, one per env; this SETS num_envs, so any later num_envs override (eden smoke's --num_envs defaults to 2 and always applies) must match the count. Empty selects the first num_envs states."] = '')

Bases: TaskMod

Select a catalog task and fixed-state initialization for the registered LIBERO task.

init_ids and num_envs are one selection: an explicit init_ids list assigns exactly one fixed state per environment and therefore sets env_options.num_envs. Leave init_ids empty to let num_envs lead and take the first num_envs states from the bundle.

Source code in eden/tasks/libero/config.py
def __init__(
    self,
    task_name: Annotated[str, "LIBERO catalog task name."] = get_suite_task_names("libero_object")[0],
    bundle_root: Annotated[
        str | None,
        "Root containing <suite>/<task>/state-bundle directories; by default libero/fixed_states in the asset "
        "directory.",
    ] = None,
    init_ids: Annotated[
        str,
        "Comma-separated fixed-state IDs, one per env; this SETS num_envs, so any later "
        "num_envs override (eden smoke's --num_envs defaults to 2 and always applies) must "
        "match the count. Empty selects the first num_envs states.",
    ] = "",
):
    self.task_name = task_name
    self.bundle_root = bundle_root
    self.init_ids = init_ids

Attributes

task_name instance-attribute
task_name = task_name
bundle_root instance-attribute
bundle_root = bundle_root
init_ids instance-attribute
init_ids = init_ids

Methods:

apply
apply(config: LiberoConfig) -> LiberoConfig
Source code in eden/tasks/libero/config.py
def apply(self, config: LiberoConfig) -> LiberoConfig:
    raw_init_ids = self.init_ids.strip()
    try:
        init_ids = tuple(int(value.strip()) for value in raw_init_ids.split(",")) if raw_init_ids else None
    except ValueError as exc:
        raise ValueError("init_ids must be a comma-separated list of integers") from exc

    task_spec = get_task_spec(self.task_name)
    if init_ids is not None:
        # An explicit selection is one state per env, so it *defines* num_envs rather than
        # being checked against it. Every CLI num_envs override lands after the mods run, so a
        # caller that passes both must keep them consistent; LiberoFixedStateReset.build()
        # raises and names both knobs when they disagree.
        config.env_options.num_envs = len(init_ids)
    indicators = {
        f"{entity_name}_burner_indicator": CylinderOptions(
            radius=0.0625,
            height=0.001,
            default_root_pos=(0.0, 0.0, LiberoStoveBurnerVisualization.hidden_z_offset),
            fixed=True,
            batch_fixed_verts=True,
            collision=False,
            surface=EmissionSurfaceOptions(color=(0.9, 0.05, 0.05, 1.0)),
        )
        for entity_name, category_name in task_spec.entity_categories.items()
        if category_name == LiberoFlatStove.category_name
    }
    config.scene_options = make_libero_scene_options(task_spec, **indicators)
    family_spec = _get_libero_family_spec(task_spec.problem_name)
    lights = [
        {
            "type": "point",
            "pos": (1.0, 1.0, 4.0),
            "color": (0.8, 0.8, 0.8),
            "intensity": 1.0,
        },
        {
            "type": "point",
            "pos": (-3.0, -3.0, 4.0),
            "color": (0.8, 0.8, 0.8),
            "intensity": 1.0,
        },
    ]
    if family_spec.has_directional_light:
        lights.insert(
            0,
            {
                "type": "directional",
                "dir": (0.0, 0.0, -1.0),
                "color": (0.8, 0.8, 0.8),
                "intensity": 1.0,
            },
        )
    camera_to_world = trans_quat_to_T(
        np.asarray(family_spec.agentview_pos_w),
        np.asarray(family_spec.agentview_quat_w),
    )
    image_resolution = (128, 128)
    config.cameras_options = CamerasOptions(
        agentview=CameraOptions(
            cam_extrinsic=np.linalg.inv(camera_to_world),
            cam_resolution=image_resolution,
            cam_fov=45.0,
            sensor_options=RasterizerCameraOptions(lights=lights),
        ),
        eye_in_hand=CameraOptions(
            cam_resolution=image_resolution,
            cam_fov=75.0,
            attach_entity_name=LIBERO_ROBOT_ENTITY_NAME,
            attach_link_name=LiberoPanda.ee_links_name[0],
            attach_offset=(0.05, 0.0, 0.0),
            attach_quat=(0.0, 0.7071067811865475, 0.7071067811865475, 0.0),
            sensor_options=RasterizerCameraOptions(),
        ),
    )
    bundle_root = self.bundle_root or get_assets_dir(LiberoObjectOptions.assets_subdir, "fixed_states")
    bundle_path = Path(bundle_root).expanduser() / task_spec.suite_name / task_spec.name / "state-bundle"
    event_terms = {
        "libero_fixed_state": LiberoFixedStateReset.configure(
            task_name=task_spec.name,
            bundle_path=str(bundle_path),
            init_ids=[] if init_ids is None else list(init_ids),
        ),
    }
    policy_dt = config.env_options.sim_dt * config.env_options.decimation
    for entity_name, category_name in task_spec.entity_categories.items():
        if category_name != LiberoFlatStove.category_name:
            continue
        indicator_name = f"{entity_name}_burner_indicator"
        event_terms[f"{indicator_name}_reset"] = LiberoStoveBurnerVisualization.configure(
            stove_entity_name=entity_name,
            indicator_entity_name=indicator_name,
        )
        event_terms[f"{indicator_name}_interval"] = LiberoStoveBurnerVisualization.configure(
            mode="interval",
            interval_range_s=(policy_dt, policy_dt),
            stove_entity_name=entity_name,
            indicator_entity_name=indicator_name,
        )
    config.event_options = EventManagerOptions(**event_terms)
    config.metric_options = MetricManagerOptions(goal=LiberoGoalMetric.configure(task_name=task_spec.name))
    config.language_instructions = [task_spec.language_instruction]
    return config

Functions:

get_suite_names

get_suite_names() -> tuple[str, ...]

Return the discoverable suite names, including the derived LIBERO-100 union.

Source code in eden/tasks/libero/catalog.py
def get_suite_names() -> tuple[str, ...]:
    """Return the discoverable suite names, including the derived LIBERO-100 union."""
    return (*_TASKS_BY_SUITE, "libero_100")

get_suite_task_names

get_suite_task_names(suite_name: str, task_order_index: int = 0) -> tuple[str, ...]
Source code in eden/tasks/libero/catalog.py
def get_suite_task_names(suite_name: str, task_order_index: int = 0) -> tuple[str, ...]:
    if suite_name == "libero_100":
        return get_suite_task_names("libero_90", task_order_index=0) + get_suite_task_names(
            "libero_10",
            task_order_index=task_order_index,
        )
    try:
        task_names = _TASKS_BY_SUITE[suite_name]
    except KeyError as exc:
        raise KeyError(f"Unknown LIBERO suite: {suite_name!r}") from exc
    if suite_name == "libero_90":
        if task_order_index != 0:
            raise IndexError("libero_90 supports only task_order_index=0")
        return task_names
    if not 0 <= task_order_index < len(_TASK_ORDERS):
        raise IndexError(
            f"task_order_index={task_order_index} out of range [0, {len(_TASK_ORDERS)}) for suite {suite_name!r}"
        )
    order = _TASK_ORDERS[task_order_index]
    return tuple(task_names[index] for index in order)

get_task_names

get_task_names() -> tuple[str, ...]

Return all 130 catalog task names in canonical suite order.

Source code in eden/tasks/libero/catalog.py
def get_task_names() -> tuple[str, ...]:
    """Return all 130 catalog task names in canonical suite order."""
    return tuple(task_name for task_names in _TASKS_BY_SUITE.values() for task_name in task_names)

get_task_spec cached

get_task_spec(task_name: str) -> LiberoTaskSpec

Resolve, parse, and memoize one task without loading unrelated BDDL files.

Source code in eden/tasks/libero/catalog.py
@functools.cache
def get_task_spec(task_name: str) -> LiberoTaskSpec:
    """Resolve, parse, and memoize one task without loading unrelated BDDL files."""
    suite_name = next((name for name, task_names in _TASKS_BY_SUITE.items() if task_name in task_names), None)
    if suite_name is None:
        raise KeyError(f"Unknown LIBERO task: {task_name!r}")
    bddl_path = _resolve_bddl_path(suite_name, task_name)
    return _parse_bddl_file(bddl_path, suite_name=suite_name)

See also