AgentCanvas / Pages / Developer Guide / Nodesets / Env / LIBERO
2026-07-04

The LIBERO NodeSet (EnvLiberoNodeSet, workspace/nodesets/env/env_libero/) wraps robosuite + MuJoCo for LIBERO (23.06) — 130 manipulation tasks split across 5 suites (libero_spatial / object / goal / 10 / 90), each task with 50 init-state variations, executed on a Franka Panda arm. It implements the gym-like env interface natively: reset (metadata only) · step_continuous / step_ee_pose (control signals) · observe_egocentric / observe_objects (pull perception) · evaluate (metric sink).

Sibling of SIMPLER — the two are a matched pair for first-release VLA evaluation. LIBERO uses MuJoCo + robosuite + Franka Panda; SIMPLER uses SAPIEN + ManiSkill2 + WidowX or Google Robot. Same TEXT-JSON 7-vec step_continuous contract, same egocentric port surface.


1. Overview

Architecture

  1. LiberoEnvManager (singleton) — one OffScreenRenderEnv + LiberoWrapper (sidecar _wrapper.py) instance, rebuilt on every set_episode because each task is its own BDDL scene file. Pinned single-thread executor for MuJoCo GL/physics affinity. After every rebuild the wrapper applies the episode's init-state, then runs num_steps_wait = 10 zero-delta settle steps.
  2. Eight canvas tool nodes — the four gym verb families (§2) plus two EE-control debug extras.
  3. EnvLiberoNodeSetserver_python = conda_env_python("ac-libero", "LIBERO_PYTHON"); parallelism = "replicated" (robosuite scene state is per-worker); default_per_step_budget_sec = 30.0.

Reset semantics (ensure-live)

env_libero__reset forwards to LiberoEnvManager.ensure_live(): a live episode — the batch-eval path, where the runner has just placed a fresh one via set_episode — is read without disturbance (no rebuild); a done episode (canvas re-run) is re-armed in place by rebuilding the same (suite, task, episode). Reset never chooses an episode — placement is env-panel-owned, exactly per template §5.1. (Until 2026-07-03 reset rebuilt unconditionally — correct but one wasted MuJoCo build per eval episode.)

Step budget

All suites cap at max_steps = 2500 env ticks (raised from 1500 on 2026-05-16: VoxPoser's bounded-advance OSC at ~5 mm/tick needs ~2× more ticks than VLA per-step deltas). The underlying robosuite horizon is bumped to max_steps + 100 so the manager's own budget check fires first — robosuite's default horizon-1000 done would otherwise be mislabelled as success by the wrapper.


2. Canvas Nodes

Node TypeVerb familyInput PortsOutput Ports
env_libero__reset reset trigger (ANY, optional) instruction, episode_id ({suite}/{task}/{ep}), suite, task_id, max_stepsmetadata only, no observation
env_libero__step_continuous step action (TEXT, JSON 7-vec or K-chunk) reward, terminated, truncated, info + extras success, step_index
env_libero__step_ee_pose step action (TEXT, JSON 8-vec or {pose, hold_steps}) the four control ports + success, step_index, converged, final_pose, step_count, error
env_libero__observe_egocentric observe trigger (ANY, optional) rgb, agentview_image, wrist_image (all IMAGE, 180°-flipped), state (8-D proprio), observation (LIST[IMAGE]), pose (POSE, None), intrinsics (ANY, None)
env_libero__observe_objects observe trigger (ANY, optional) snapshot (ANY — one privileged GT dict), error (TEXT)
env_libero__evaluate evaluate trigger (ANY, optional) success (BOOL), metrics (METRICS: success, num_steps, suite, task_id, cumulative_reward)
env_libero__reset_to_home, env_libero__close_gripper extras Sim-mutating EE-control debug helpers outside the gym verbs — no shipped graph binds them (§7)

evaluate is typically declared with config.post_loop: true in the graph so it fires in the after-loop band and reads the final step count (see voxposer_libero_decomposed.json).


3. Action Contracts

step_continuous — 7-DoF delta chunk

"[ax, ay, az, arx, ary, arz, grip]"        # single step
"[[ax, ay, az, arx, ary, arz, grip], ...]" # K-step chunk (K runtime-variable)
IndexMeaningRange
0–2Delta end-effector position[-1, 1] (NaN/Inf clipped)
3–5Delta orientation (axis-angle)[-1, 1]
6Gripper+1 = close, −1 = open (§3 callout below)

The chunk loop early-breaks on success/terminal. The node's max_chunk_steps config truncates long policy chunks before stepping: 0 = run the full chunk; 8 matches Pi0's replan rate (50-step chunks executed whole drift off-policy).

step_ee_pose — absolute EE waypoint (closed-loop OSC)

"[x, y, z, qw, qx, qy, qz, grip]"                             # single waypoint (legacy 8-vec)
{"pose": [x, y, z, qw, qx, qy, qz, grip], "hold_steps": 60}   # gripper-settle hold

Drives the EE to an absolute world-frame pose via bounded-advance OSC deltas (~5 mm/tick), closed-loop on both position and rotation. Config: max_steps (default 100 env ticks per call), pos_tol_m (0.01), rot_tol_rad (0.10). hold_steps > 0 skips convergence and forces N zero-delta ticks with the requested gripper command — MuJoCo needs real ticks for fingers to actually close on an object. The waypoint's grip bit uses the downstream convention 1.0 = closed, 0.0 = open; the node maps it to robosuite's ±1 at the actuation boundary. Empty input ("[]") is a clean no-op tick for cursor-exhausted inner loops.

Gripper convention: robosuite action +1 = CLOSE, −1 = OPEN — verified empirically 2026-06-28 (10× action −1 → finger qpos 0.039 apart; 60× action +1 → qpos 0.0005 together). The opposite convention circulated in comments for weeks and inverted every VoxPoser grasp (roadmap #65). If a grasp "releases" at the object, check this first.

4. Observation Spaces

observe_egocentric

PortWire TypeMeaning
rgb / agentview_imageIMAGEThird-person agentview camera, 256×256 uint8 (config resolution), 180°-flipped to match VLA training data
wrist_imageIMAGErobot0_eye_in_hand camera, 180°-flipped
stateANY8-D float32 proprio: eef_pos(3) + axis_angle(3) + gripper_qpos(2)
observationLIST[IMAGE][agentview_image, wrist_image]
posePOSEAlways None (manipulation, no nav pose)
intrinsicsANYAlways None (no intrinsics surface exposed)

observe_objects — privileged GT snapshot

One atomic dict: BDDL object names, per-object AABB-sampled point clouds + normals (boxes, not true geometry), scene collision PC, EE pose + gripper state, workspace bounds. This is the read side GT planners (VoxPoser) consume — keeping it a visibly separate node is the platform's GT-dependency-visibility principle in action: whether an agent runs on ground truth or perception is graph topology, not a hidden implementation detail.


5. Env Panel Integration

Three cascaded select fields — LIBERO's benchmark hierarchy is one level deeper than the typical split → episode_index:

FieldDefaultOptions
suitelibero_spatial5 suites (task counts shown in the label)
task_id0per suite — 10 tasks each for spatial/object/goal/10, 90 for libero_90
episode_index0range(50) init-state variations per task

Field changes push set_episode and emit signal_name="episode_reset" (so lifetime="episode" state containers clear); play re-seats the selected episode then returns run_start. A split field-change naming a suite (e.g. eval harness split=libero_object) is accepted as an alias for the cascade head — that's how batch eval targets a suite without a stateful panel edit. step_budget reports 2500 per §1.


6. Environment Setup

Install

bash scripts/install/install_ac_libero.sh

Creates conda env ac-libero with robosuite + MuJoCo + the LIBERO source (nested inside the vla_workspace submodule). The nodeset always runs server-mode against it.

Data layout

PathContent
data/libero/datasets/symlink to the LIBERO HDF5 datasets
BDDL task filesauto-resolved from the installed package via libero.libero.get_libero_path("bddl_files") — nothing to fetch

Activation

export LIBERO_PYTHON=/home/$(whoami)/miniforge3/envs/ac-libero/bin/python

Optional — server_python resolves the ac-libero env by name and this var only overrides the location (conda_env_python("ac-libero", "LIBERO_PYTHON")).


7. Deviations from the Template


8. Verification Status

AgentCanvas docs