ADR-0007: Separate physical robot manifests from simulator IO contracts
- Status: Accepted
- Date: 2026-05-24
- Amended: 2026-05-24 (see Amendments below)
Context
Five robots/<id>/robot.yaml manifests in tree describe what is supposed
to be a physical robot (CLAUDE.md §6.1, Layer 0 — HAL): kinematic chain,
end-effector, capabilities, safety envelope. In practice, three of them
have been co-tuned to a particular simulator's IO conventions rather
than the underlying hardware:
| YAML | Physical robot it claims to describe | Sim-specific bits leaking in |
|---|---|---|
robots/libero_franka/robot.yaml |
Franka Emika Panda (7-DoF) | LIBERO's 8-D eef_pos+axisangle+gripper_qpos state, 7-D delta-EEF action, image flip 180°. |
robots/metaworld_sawyer/robot.yaml |
Rethink Sawyer (7-DoF) | MetaWorld's 4-D agent_pos (XYZ + gripper) state, 4-D delta-XYZ-plus-gripper action. |
robots/pusht_2d/robot.yaml |
n/a — PushT is a synthetic 2-D benchmark | The whole manifest is a sim-only pseudo-robot. |
robots/aloha_bimanual/robot.yaml |
Trossen ALOHA (2 × 7-DoF) | None — gym-aloha already mirrors the real robot's joints. |
robots/so100_follower/robot.yaml |
LeRobot SO-100 follower | None — same description for sim and real hardware. |
Two consequences:
- Two
RobotDescriptions for one physical robot.libero_frankaand the in-codeFRANKA_PANDA_DESCRIPTION(python/hal/src/openral_hal/franka_panda.py:168) describe the same physical Panda but with different joint names, limits, andobservation_spec/action_spec. The drift was invisible until the manifest-vs-HAL regression test (PR 2 / issue #54) exposed it. - Robot identity is overloaded. "
robot_id: libero_franka" really means "Franka Panda running inside LIBERO". Same Panda checkpoint, different sim → differentrobot_id→ different manifest. That maps awkwardly onto the embodiment-tag compatibility check, the rSkill capability matcher, and the architectural seam between Layer 0 (HAL) and the eval scene (which is closer to Layer 5 / theopenral_simscene adapter).
The observation_spec / action_spec fields on RobotDescription
are also unused — rg "\.observation_spec|\.action_spec" returns zero
hits across python/, tests/, and examples/. They were declared in
YAML but never consumed at runtime. The sim-imposed IO contract is in
fact already encoded in the eval scene adapters
(python/sim/src/openral_sim/{policies,backends}/{libero,metaworld,pusht}.py)
and in each rSkill's RSkillManifest. So the cleanup is simpler than
the description above suggests: we delete the misleading specs from
the per-robot YAMLs, document the sim-imposed contract in the matching
scene adapter (where it already operationally lives), and rename the
YAMLs so the robot_id axis tracks physical embodiment.
Decision
robots/<id>/robot.yamldescribes a physical embodiment only. No sim-specificobservation_spec/action_spec. No "this is the Franka as LIBERO sees it" tuning. Joint limits, EE, capabilities, safety envelope — everything that survives the change of simulator.openral_sim.{policies,backends}.<scene>is the source of truth for sim-imposed IO contracts. The LIBERO scene adapter declares the 8-D EEF state, 7-D delta-EEF action, and 180° image flip; the MetaWorld adapter declares its 4-Dagent_posetc. These already exist in code; this ADR formalises that they are not duplicated into the per-robot YAML.- Rename to physical embodiments.
robots/libero_franka/→robots/franka_panda/.robots/metaworld_sawyer/→robots/sawyer/. The newrobots/franka_panda/robot.yamlmirrorsFRANKA_PANDA_DESCRIPTION(the existing manifest-vs-HAL drift-guard test catches divergence going forward). The newrobots/sawyer/describes the physical Rethink Sawyer; no real-HW HAL today (tracked by issue #57). pusht_2dstays. PushT genuinely has no real robot; the manifest stays as a documented "scene-pseudo-robot" — the schema needs somerobot_idto round-trip throughSimEnvironment, andpusht_2dis the canonical 2-D-tip embodiment of the PushT benchmark. Its README is updated to make the sim-only nature explicit.aloha_bimanualstays unchanged. It already describes the real Trossen ALOHA; the gym-aloha sim mirrors that one-to-one.- Hard rename, no shim. Pre-1.0 (
openral-coreis0.2.0, every other workspace package is0.1.0). No deprecation re-export under the oldrobot_ids. CLAUDE.md anti-fallback principle (§9 / §1). mock_robotstays in this PR; its removal is a separate PR (planned PR 9 — depends on this split for fixture migration) so the diff here stays focused.
Consequences
scenes/{smolvla,xvla,pi05}_libero_spatial.yamlmigraterobot_id: libero_franka→robot_id: franka_panda.scenes/benchmark/metaworld_push.yamlmigratesrobot_id: metaworld_sawyer→robot_id: sawyer.scenes/{act_aloha_transfer_cube, diffusion_pusht}.yamlunchanged.tests/unit/test_sim_environment_schemas.pytest fixture migrates tofranka_panda.robots/libero_franka/,robots/metaworld_sawyer/deleted.- New
robots/franka_panda/,robots/sawyer/added. - Auto-discovery (PR 3) means no
_BUILTIN_ROBOTSedit is required. docs/reference/vla_compatibility.mdalready listsfranka_pandain the Robot tag column for π0.5/xVLA/GR00T-LIBERO checkpoints; no churn there. The §3.1 LIBERO header now points atrobots/franka_panda/.openral_simrunner code is unchanged — it never readRobotDescription.observation_spec/action_spec. The LIBERO and MetaWorld scene adapter docstrings are extended to document the sim-imposed IO contract that previously lived (uselessly) in YAML.- The robot manifest-vs-HAL drift-guard (
tests/unit/test_robot_manifests_match_hal_constants.py, PR 2) extends torobots/franka_panda/robot.yaml↔FRANKA_PANDA_DESCRIPTION.
Migration
Single PR (PR 6). No multi-step deprecation. Anyone with an out-of-tree
SimEnvironment config hard-pinned to robot_id: libero_franka /
metaworld_sawyer updates the field in the same revision they bump
to this commit.
Why not other options
- Keep both YAMLs and document the conflation. Surfaces the
problem but doesn't fix it; the "two
RobotDescriptions for one physical Panda" trap stays. - Move
observation_spec/action_spectoSceneSpec/ a newEmbodimentProfilemodel. Tempting on principle, but the fields have no runtime consumer today; adding new typed plumbing for unused fields is the opposite of what CLAUDE.md operating principle 4 says ("explicit beats implicit; no hidden plumbing"). When a real consumer shows up — e.g. an action-space adapter inside the runner — that's the moment to add the typed surface. - Keep
pusht_2dbut move it under a newscenes/namespace. The schema currently requires arobot_id, and threading a "this is a synthetic embodiment, not a real robot" flag through theSimEnvironmentvalidator is more churn than the benefit warrants. AREADME.mdnote onrobots/pusht_2d/is the smaller answer.
Amendment 2026-06-08 — three-tier scene paths
ADR-0041 split scenes/ into three tiers
(scenes/deploy/, scenes/sim/, scenes/benchmark/) and stripped rSkill
names from filenames. The MetaWorld migration example above moves from
scenes/benchmarks/smolvla_metaworld_push.yaml to
scenes/benchmark/metaworld_push.yaml (singular benchmark/, rSkill name
removed; an rSkill is now passed at the CLI via --rskill). The Decision
text and schema are unchanged — only on-disk paths are renamed. See
ADR-0041 for the tier hierarchy and
scenes/README.md for the per-tier authoring
guide.