LIBERO NodeSet
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
LiberoEnvManager(singleton) — oneOffScreenRenderEnv+LiberoWrapper(sidecar_wrapper.py) instance, rebuilt on everyset_episodebecause 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 runsnum_steps_wait = 10zero-delta settle steps.- Eight canvas tool nodes — the four gym verb families (§2) plus two EE-control debug extras.
EnvLiberoNodeSet—server_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 Type | Verb family | Input Ports | Output Ports |
|---|---|---|---|
env_libero__reset |
reset | trigger (ANY, optional) |
instruction, episode_id ({suite}/{task}/{ep}), suite, task_id, max_steps — metadata 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)
| Index | Meaning | Range |
|---|---|---|
| 0–2 | Delta end-effector position | [-1, 1] (NaN/Inf clipped) |
| 3–5 | Delta orientation (axis-angle) | [-1, 1] |
| 6 | Gripper | +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.
+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
| Port | Wire Type | Meaning |
|---|---|---|
rgb / agentview_image | IMAGE | Third-person agentview camera, 256×256 uint8 (config resolution), 180°-flipped to match VLA training data |
wrist_image | IMAGE | robot0_eye_in_hand camera, 180°-flipped |
state | ANY | 8-D float32 proprio: eef_pos(3) + axis_angle(3) + gripper_qpos(2) |
observation | LIST[IMAGE] | [agentview_image, wrist_image] |
pose | POSE | Always None (manipulation, no nav pose) |
intrinsics | ANY | Always 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:
| Field | Default | Options |
|---|---|---|
suite | libero_spatial | 5 suites (task counts shown in the label) |
task_id | 0 | per suite — 10 tasks each for spatial/object/goal/10, 90 for libero_90 |
episode_index | 0 | range(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
| Path | Content |
|---|---|
data/libero/datasets/ | symlink to the LIBERO HDF5 datasets |
| BDDL task files | auto-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
ee_poseis a LIBERO-introduced action space (template §4.1, added 2026-07-03). The node shipped asstep_pose, but its shape — 8-vec with a gripper bit + optionalhold_steps,action:TEXT— never matched the navposerow (target:POSE, as onenv_habitat). Renamedstep_ee_poseand the vocabulary extended; the same rename sweptvoxposer_libero_decomposed.json+voxposer__waypoint_executor.json.- No
depthport inobserve_egocentric— the template's egocentric payload listsdepth:DEPTH, but neither manipulation env (LIBERO, SIMPLER) renders it today. Add the port (not a new space) if a consumer needs it. - EE-control extras outside the gym verbs —
reset_to_home/close_grippermutate the sim without beingstep_*nodes. Debug-tier: kept for canvas poking, bound by no graph;step_ee_pose'shold_stepssubsumedclose_gripper's original job. - Rebuild-on-
set_episodeis structural — each task is a separate BDDL scene, so episode placement means a fresh MuJoCo env (~1 s). The template's ensure-live reset (§1) keeps that cost out of the hot path.
8. Verification Status
- VoxPoser (decomposed) —
vla/verified/voxposer_libero_decomposed.json: libero_object SR 0.88 at 50-ep suite scale (2026-06-30); other suites grasp-geometry-limited. Canonical numbers: VLA support status. - Post-rename smoke (2026-07-04, run
20260703_235719): 2/2 libero_object episodes success 1.0 through the renamedstep_ee_posebinding + ensure-live reset. - Generic VLA framework (
policy_adapter_vla+env_adapterPi0/SmolVLA/DP adapters overstep_continuous) — re-eval pending since the gym-interface migration; see the support-status page.