Layer 0 — Core Schemas & Exceptions
Part of the OpenRAL public-symbol inventory. Hand-curated;
(LNN)markers are refreshed bytools/refresh_methods_linenos.py.
Authoritative Pydantic v2 contracts (CLAUDE.md §1.3). Anything imported
from openral_core.__init__ is API. All on-disk schemas
(RobotDescription, RSkillManifest, SceneGraph, …) are at
schema_version: "0.1" and evolve in place (CLAUDE.md §1.6).
python/core/src/openral_core/schemas.py
openral schema v0 — normative Pydantic v2 contracts for all layers.
Enums
class EmbodimentKind(str, Enum)— Top-level kinematic class. (L52)HUMANOID, MANIPULATOR, BIMANUAL, QUADRUPED, MOBILE_BASE, MOBILE_MANIPULATOR, DRONEclass JointType(str, Enum)— URDF joint type. (L64)REVOLUTE, PRISMATIC, CONTINUOUS, FIXED, FLOATING, PLANARclass ClockOrigin(str, Enum)— Authoritative source for OpenRALstamp_nsvalues./clockis a ROS projection, not an origin. (L75)HOST_WALL, SIMULATION, HARDWARE_SYNCEDclass ClockEpoch(str, Enum)— Epoch that astamp_nsvalue is measured from. (L87)UNIX, SIMULATION_ELAPSED, HARDWAREJointRole: TypeAlias = Literal[…]— Structural classification of aJointSpec. (L95)"arm", "base", "gripper", "torso", "leg", "head", "neck", "wheel", "unknown". Used by runner/safety/dataset-bridge to identify a channel without name-substring heuristics. Default"unknown"keeps legacy manifests loadable.class ControlMode(str, Enum)— Action space / control interface. (L199)JOINT_POSITION, JOINT_VELOCITY, JOINT_TORQUE, JOINT_TRAJECTORY, CARTESIAN_POSE, CARTESIAN_DELTA, CARTESIAN_TWIST, BODY_TWIST, FOOT_PLACEMENT, GRIPPER_BINARY, GRIPPER_POSITION, DEX_HAND_JOINT, COMPOSITE_MODEconst BODY_TWIST_DIM: int = 6— Width of a BODY_TWIST / CARTESIAN_* twist row(vx, vy, vz, wx, wy, wz). Single source for the HAL packers (openral_hal.panda_mobile,openral_hal.sim_attached) + the safety supervisor that validate 6-vec twist payloads; matchesAction.body_twist(a 6-tuple). (L252)class SensorModality(str, Enum)— Physical sensing modality. (L265)RGB, DEPTH, STEREO, IR, POINT_CLOUD, LIDAR_2D, IMU, FORCE_TORQUE, JOINT_STATE, TACTILE_VISION, TACTILE_ARRAY, AUDIO, GPS, BATTERYclass Hand(str, Enum)— End-effector laterality. (L284)LEFT, RIGHT, NAclass StateRepresentation(str, Enum)— State vector format. (L905)JOINT_POSITIONS, EEF_POS_AXISANGLE, EEF_POS_EULER, EEF_POS_QUAT, EEF_POS_AXISANGLE_GRIPPERclass ActionRepresentation(str, Enum)— Action vector format:JOINT_POSITIONS, JOINT_VELOCITIES, DELTA_EE_6D, DELTA_EE_6D_PLUS_GRIPPER, DELTA_EE_3D_PLUS_GRIPPER(3-D EE translation delta + gripper, e.g. MetaWorld),CARTESIAN_POSE. (L915)class JointUnits(str, Enum)— Angular convention a joint-position checkpoint was trained in:DEGREES, RADIANS(issue #135). openral'sJointState/Actioncontract is radians; the skill_runner converts deg↔rad at the policy boundary when a manifest declaresDEGREES. Declared onActionContract.joint_units; the runner reads it directly and no longer guesses units from normalizer-stats magnitude (the old fallback heuristic was removed with issue #135 — a wrong guess sent ~57× commands and slammed the arm's limits). A joint-position skill reaching the runner without a declaration is a hardROSConfigError, not a silent default. (L930)JOINT_POSITIONS, JOINT_VELOCITIES, DELTA_EE_6D_PLUS_GRIPPER, DELTA_EE_6D, CARTESIAN_POSEclass RSkillAction(str, Enum)— Closed vocabulary of high-level action verbs an rSkill can perform; declared onRSkillManifest.actionsand surfaced to the reasoner LLM tool palette so it can pick a skill by what it does. (L948) Manipulation primitives:PICK, PLACE, PICK_AND_PLACE, TRANSFER, GRASP, RELEASE; articulated / contact-rich:OPEN, CLOSE, PUSH, PULL, SLIDE, INSERT, POUR, WIPE, ROTATE; motion:REACH; mobile:NAVIGATE; social/expressive:WAVE, SHAKE; generalist marker (foundation / multi-task checkpoints):GENERALIST; perception producer:DETECT(forkind: "detector"rSkills); scene VLM:QUERY(forkind: "vlm"rSkills); reward monitor:MONITOR(forkind: "reward"rSkills); playbook decision procedure:PLAN(forkind: "playbook"rSkills). New entries are additive.class QuantizationDtype(str, Enum)— Weight numeric format. (L2634)FP32, FP16, BF16, INT8, INT4, FP4_NVFP4class QuantizationBackend(str, Enum)— Inference backend. (L2668)PYTORCH, ONNX, TENSORRT, GGUF, MLXclass RSkillState(str, Enum)— Skill lifecycle. (L2740)UNCONFIGURED, INACTIVE, ACTIVE, FINALIZED, ERRORclass RSkillLicensePosture(str, Enum)— License posture (CLAUDE §7.4). (L2820)APACHE_2_0, MIT, BSD, PERMISSIVE_RESEARCH, NVIDIA_NON_COMMERCIAL, NVIDIA_OPEN_MODEL, RLWRLD_NON_COMMERCIAL, PROPRIETARY, UNKNOWN(NVIDIA_OPEN_MODEL = GR00T N1.7+, commercial OK)class RSkillRuntime(str, Enum)— Manifest runtime hint. (L2835)PYTORCH, ONNX, TENSORRT, TRT_LLM, VLLM, GGUF, MLX, JAXclass PhysicsBackend(str, Enum)— Sim backend. (L6707)MUJOCO, MUJOCO_MJX, PYBULLET, SAPIEN, ISAACSIM, COPPELIASIM, GENESIS, MOCK(SAPIEN = ManiSkill3 / RoboTwin engine; RoboTwin uses it via a py3.10 sidecar;COPPELIASIM= CoppeliaSim/PyRep RLBench backend, out-of-process py3.10 sidecar)
Pydantic models — robot manifest hierarchy
class IntrinsicsPinhole(BaseModel)— Pinhole camera intrinsics. (L295) fields:width, height, fx, fy, cx, cy, distortion_model, distortion_coeffsscale_intrinsics_to(base, width, height) -> IntrinsicsPinhole— Linearly rescale pinhole intrinsics to a new render resolution (fx/fy/cx/cy scale by width/height ratios; FOV and distortion preserved). Deploy-sim renders the same MuJoCo camera atscene.observation_width/height, so the HAL scales the manifest's nominal intrinsics to the render resolution before the depth back-projection — keeping the published camera model matched to what was rendered. Returnsbaseunchanged when the target equals its resolution; raisesValueErroron non-positive dims. (L323)class ClockAuthority(BaseModel)— Named origin for OpenRAL timestamps across sim, rSkills, ROS, and hardware. Fields:origin,epoch,clock_id,publishes_ros_clock,timestep_s,notes. Constructors:host_wall()for real-deployment wall/ROS system time,simulation(clock_id, timestep_s=None, publishes_ros_clock=True)for simulator elapsed time projected to/clock, andhardware_synced(clock_id, epoch=UNIX)for future synchronized controller/PTP clocks. Validator rejects impossible origin/epoch pairs and competing hardware/clockpublishers. (L119)class CameraSimPlacement(BaseModel)— Where an RGB sensor's camera sits in the sim MJCF, so the generic HAL camera rig can splice it into a bare-arm MJCF (which ships no<camera>) fordeploy sim. Fields:parent_body: str | None(MJCF body the camera mounts to / tracks;None= world-fixed in<worldbody>),pos: (x,y,z)+target: (x,y,z)(look-at point, parent or world frame),fovy_deg: float | None(else derived from the sensor'sintrinsicsas2·atan(h/2·fy)).extra="forbid". Replaces per-robotscene_defaults.compositionfor camera-only deploy twins.class SensorSpec(BaseModel)— Generalizable sensor descriptor (all modalities).catalog_id: str | Nonerecords theopenral_sensorscatalog provenance for robot-mounted physical devices while keeping the manifest fully materialized; calibrated intrinsics, placement, feature keys, and serials stay explicit on the spec.sim_camera_name: str | None(issue #191 Phase 3b, mirrorsJointSpec.sim_joint_name) carries the MJCF camera name when it differs from the sensorname—MujocoArmHAL.read_imagesrenderssim_camera_name or name(used when a robot's canonical sensor name differs from the upstream MJCF camera name).sim_placement: CameraSimPlacement | Nonecarries the camera's sim pose; when set, the HAL camera rig splices the camera into a bare-arm MJCF so deploy-sim twins render their declared cameras without a scene composer.deploy_binding: SensorDeployBinding | Noneis the runtime counterpart — howdeploy runopens the physical device; host-specific, unset in committed manifests, filled byopenral detect. (L421) fields:name, modality, frame_id, parent_frame, static_transform_xyz_rpy, rate_hz, intrinsics, encoding, fov_h_deg, fov_v_deg, sim_camera_name, sim_placement, n_channels, range_min_m, range_max_m, accel_noise_density, gyro_noise_density, n_axes, tactile_grid, vla_feature_key, ros2_topic, ros2_msg_type, qos_profile, catalog_id, vendor, model, driver_pkg, metadataclass SensorBundle(BaseModel)— Multi-modal sensor group. (L517) fields:bundle_name, sensors, sync, sync_tolerance_msclass JointSpec(BaseModel)— URDF-derived joint spec. (L536) fields:name, joint_type, parent_link, child_link, axis_xyz, origin_xyz, origin_rpy, position_limits, velocity_limit, effort_limit, has_position_sensor, has_velocity_sensor, has_torque_sensor, backlash_estimate, actuator_kind, sim_joint_name, role.origin_xyz/origin_rpyare the fixed parent-link→joint transform (URDF<joint><origin>); withaxis_xyzthey let the kernel compute forward kinematics for self-collision. Default zeros; populated by the offline lowering tool only for robots that enable collision checking.roleis aJointRoleliteral that downstream code reads to identify gripper / base / arm DoFs structurally instead of substring-matching the joint name (default"unknown").sim_joint_namecarries the MJCF/MuJoCo joint name when it differs from the logicalname— used byopenral_sim.backends.robocasa.{synthesize_laser_scan_2d,read_panda_mobile_base_velocity},SimSensorBridge._compute_scan_ranges, andopenral_hal.sim_attached.SimAttachedHAL.read_stateto look upmj_name2idwithout hardcoding robosuite/robocasa naming.None= "MJCF name matchesname" (the common case for fixed-base manipulators). Population contract: a robot needssim_joint_namepopulated only when (a) its sim adapter doesmj_name2idon a joint name, AND (b) the loaded MJCF differs fromname. Today:panda_mobile(robocasa auto-prefixes withmobilebase0_*+robot0_*). LIBERO / ManiSkill3 / aloha / so100_robosuite / ur5e / widowx preserve URDF names — populatingsim_joint_namefor those is a no-op.openarm_robosuitedoes its own hardcodedmj_name2idlookups inenv.py:309-318(openarm_{side}_joint{i}) and is a candidate to refactor through this field.class EndEffectorSpec(BaseModel)— End-effector spec. (L618) fields:name, kind, hand, n_dof, max_grip_force_n, max_payload_kg, workspace_radius_m, tactile_sensors, actuated.actuateddefaults toTrue; setFalsefor passive tools (inert flanges, kinematic-only mounts) so the safety kernel can reject chunks addressed at them.class ComputeSpec(BaseModel)— Compute profile for one deployment tier (edge / local / cloud). Populated byopenral_detect._enrich_computefrom GPU probe results; attached toRobotDescription.compute_edge,compute_local, orcompute_cloud. (L678) fields:compute_tops, system_memory_gb, num_gpus, gpu_vram_gb, cuda_compute_capability, cuda_toolkit_version, tensorrt_version, gpu_supported_runtimes, gpu_supported_dtypes, nvmm_available, endpoint, network_latency_ms. Shared across all three tiers —endpointandnetwork_latency_msareNonefor edge/local.nvmm_availableis probed on all tiers (returnsFalsegracefully when absent).num_gpus > 1captures cloud multi-GPU pods. GPU/runtime fields moved here fromRobotCapabilitiesto separate physical robot capabilities from host compute properties.supports_cumotion() -> bool— True when the host meets the cuMotion (Isaac ROS) GPU floor: compute capability >= (8, 0), CUDA toolkit major >= 13, andgpu_vram_gb>=_CUMOTION_MIN_VRAM_GIB(7.5; nominal-8 GB cards report ~7.99 GiB). The MoveIt planner gate uses it to pick cuMotion vs OMPL. False on non-CUDA hosts or when the CUDA toolkit version is unknown.class ReasonerModel(BaseModel)— Frozen curated S2 model registry entry (ADR-0088). Fields:id,display_name,dialect (anthropic|openai),hosting (cloud|managed_local|byo_local),served_model_id,default_endpoint,auth_required,tool_choice,max_tokens_default,min_gpu_vram_gb,required_dtype,weights_license; propertyis_local.REASONER_MODELSis the curated map (claude-opus-4-8,gpt-5.5,gpt-5.6,cosmos3-edge); membership means the model passed the robotics tool-calling contract.REASONER_MANAGED_ENDPOINT="managed"is the managed-local sentinel. (L8270)class ReasonerEndpointPreset(NamedTuple)— everything a namedOPENRAL_REASONER_ENDPOINTimplies beyond its URL:url,dialect,auth_required,timeout_s(cold-start allowance),tool_choice.REASONER_ENDPOINT_PRESETS: dict[str, ReasonerEndpointPreset]maps the accepted names (anthropic/openrouter/gemini/xai/deepseek/huggingface/ollama/vllm); the base-URL constants (ANTHROPIC_BASE_URL,OPENROUTER_BASE_URL,OLLAMA_BASE_URL,VLLM_BASE_URL,GEMINI_BASE_URL,XAI_BASE_URL,DEEPSEEK_BASE_URL,HUGGINGFACE_BASE_URL) live alongside. Home moved here fromopenral_reasoner.tool_use(which re-exports for back-compat) so the factory andopenral doctorconsume ONE table — the doctor-side mirror drifted twice (2fe732a, 131a489). (L8419)class RobotCapabilities(BaseModel)— Physical capability flags for skill compatibility. (L771) fields:locomotion, can_lift_kg, has_dexterous_hands, has_tactile, has_force_control, has_vision, has_lidar, has_vision_slam, has_audio, bimanual, supported_control_modes, supported_vla_embodiments, embodiment_tags. GPU/compute fields moved toComputeSpec(attached atRobotDescription.compute).has_vision_slamgates the camera-based cuVSLAM+nvblox SLAM backend for lidar-less robots; independent ofhas_lidar(lidar backend wins when both set).class SafetyEnvelope(BaseModel)— Constraints enforced by C++ safety kernel. (L819) fields:workspace_box_min_xyz, workspace_box_max_xyz, no_go_zones, max_ee_speed_m_s, max_ee_accel_m_s2, max_joint_speed_factor, max_force_n, max_torque_nm, deadman_required, e_stop_topic, e_stop_qos, contact_force_threshold_n, cycle_time_violation_threshold_ms, human_in_loop_required+ per-mode bounds (max_cartesian_step_m/_rad,max_ee_angular_speed_rad_s,max_base_linear/angular_speed_rad_s) +self_collision_margin_m: float = 0.0— clearance margin (m) for the kernel's self-collision geometric check; a negative value tolerates the grazing contact of a compact arm's in-distribution envelope (e.g. so101 pen VLA) while gross folds still fire. Separate from the world/voxel margins; loosening is a safety-WG decision.class ObservationSpec(BaseModel)— VLA observation config. (L1006) fields:state_key, state_shape, state_representation, image_flip_180class ActionSpec(BaseModel)— VLA action config. (L1023) fields:dim, representation, control_freq_hz, chunk_sizeclass ActionSlot(BaseModel)— One contiguous slice of an rSkill's action vector. fields:range, control_mode, discard, ee, frame, joint_names, input_bounds. Per-mode field requirements enforced by@model_validator: cartesian needs ee+frame, body_twist needs frame only, gripper needs ee only, joint needs neither (joint_names optional, length must equal slot width when supplied).input_bounds=(min,max)clips controller-native policy inputs before safety/HAL dispatch;discard=Trueslots drop their slice silently.class ActionContract(BaseModel)— Per-rSkill action-vector contract. fields:dim, representation, slots, joint_units, cartesian_delta_scale. Whenslotsis set, every index in[0, dim)is covered by exactly oneActionSlot(@model_validatorrejects gaps + overlaps + over-range slots). Whenslots is None, the legacy single-Action JOINT_POSITION path applies (back-compat). Manifests carryingslotsare exempt from thedim <= len(robot.joints)invariant — the slot decoder gives a per-slice typed contract.joint_units: JointUnits | None(issue #135) governs the deg↔rad conversion the skill_runner applies to both the state fed to the policy and the action it emits.cartesian_delta_scale: tuple[float,...] | Nonecarries the per-axis controller-native→physical multiplier used only by predictive safety; raw action bytes remain unchanged for the HAL. Identity is the backward-compatible default.class TaskSpaceFamily(str, Enum)— Coarse classification of aControlModefor task-space views (DRAFT):JOINT, CARTESIAN, GRIPPER, BASE, DEX_HAND, COMPOSITE. Mapped exhaustively from everyControlModeby module-private_FAMILY_FOR_MODE(lockstep-tested).class TaskSpaceSegment(BaseModel)— One typed slice of an action vector, layer-neutral. fields:family, control_mode, width (>0), target.@model_validatorenforcesfamily == _FAMILY_FOR_MODE[control_mode].targetnames the EE for cartesian/gripper/dex modes,Noneotherwise. The gripper is an explicit 1-D segment — answers "is the gripper a dimension?" structurally.class TaskSpaceMatch(BaseModel)— Result oftask_space_compatible. fields:ok, reasons.reasonsis empty iffok.class TaskSpace(BaseModel)— Layer-neutral view of an action interface as orderedTaskSpaceSegments (DRAFT). fields:segments (non-empty), representation. Props:total_dim(sum of widths),control_modes(set).@classmethod from_action_contract(action, robot)expandsslots→ segments, else expandsrepresentationviacanonical_slots_for_representation, else falls back to one whole-vectorJOINT_POSITIONsegment. Derived, never a hand-authored manifest field — cannot drift from the primitives.class SceneTaskSpace(BaseModel)— Scene leg of the task-space contract. fields:modes (frozenset[ControlMode]), action_dim (int|None), runs_via_default_packers (bool). The control interface a scene-adapter family executes (a property of the adapter, not the coarsePhysicsBackendnor the individual scene). Declared once per family inSCENE_FAMILY_TASK_SPACE.runs_via_default_packers=Falsemarks dedicated-controller adapters (RLBench cartesian-pose planner, RoboCasa-GR1 composite) — the scene-leg analogue ofKNOWN_SIM_GAPS.class SphereShape(BaseModel)— Sphere collision primitive; discriminatorshape="sphere", fieldradius_m (>0). (L1371)class CapsuleShape(BaseModel)— Capsule collision primitive (segment along local +Z swept by a radius); discriminatorshape="capsule", fieldsradius_m (>0), length_m (>=0). (L1389)class BoxShape(BaseModel)— Oriented box (OBB) collision primitive for blocky links (e.g. the SO-ARM base housing); discriminatorshape="box", fieldhalf_extents_m: tuple[float,float,float] (all >0). Fits a near-cubic link far tighter than a capsule, whose circular section bulges past flat faces and over-reports clearance. (issue #84)CollisionShape: TypeAlias = CapsuleShape | SphereShape | BoxShape— Discriminated union of convex collision primitives (discriminatorshape); mesh shapes excluded so the allocation-free kernel checks only analytic convex volumes. (L1449)class LinkCollisionGeometry(BaseModel)— One convex collision volume attached to a robot link; fieldslink_name, shape: CollisionShape, origin_xyz_rpy. Lowered, kernel-facing form (hand-authored or emitted by the offline lowering tool from MJCF/URDF). (L1461)class RobotDescription(BaseModel)— Top-level robot manifest, one per robot. (L1570) fields:name, embodiment_kind, assets, base_frame, odom_frame, map_frame, joints, end_effectors, sensors, sensor_bundles, capabilities, safety, ros2_namespace, middleware, onboard_compute, sdk_kind, hal, observation_spec, action_spec, sim, scene_defaults, base_joints, footprint_radius, base_kinematics, collision_geometry, allowed_collision_pairs, footprint_polygon, compute_edge, compute_local, compute_cloud, schema_version.compute_edge: ComputeSpec | None— Jetson / embedded SoC profile; populated byopenral detectwhen a Jetson is found; falls back tocompute_localin skill checks.compute_local: ComputeSpec | None— workstation / tethered-laptop accelerator profile; populated for discrete NVIDIA / Apple Silicon / CPU-only hosts.compute_cloud: ComputeSpec | None— optional remote compute endpoint (SSH or HTTPS; set manually or viaopenral detect --target cloud).schema_version: Literal["0.1"]— on-disk schema version; default"0.1"(three-slot compute layout).assets: AssetRefsis the single URDF/MJCF/SRDF reference block (default empty) — it replaces the former scatteredurdf_path,urdf_root_frame,static_base_to_urdf_root_xyz_rpy, andsrdf_pathfields (andSimDescription.mjcf_uri); refs share theopenral_core.assets.resolve_assetgrammar and the URDF'srobot_state_publisherwiring (root_frame+base_to_root_xyz_rpy) lives onassets.urdf.collision_geometry: list[LinkCollisionGeometry]+allowed_collision_pairs: list[tuple[str, str]]carry the per-link collision primitives and the self-collision allowed-collision matrix the safety kernel consumes; all default empty/Noneandjointsstays normative for the kinematic chain (URDF/SRDF add geometry + ACM only — the SRDFdisable_collisionsblock named byassets.srdfis the canonical source forallowed_collision_pairson real robots).footprint_radius: float | None(>0) +base_kinematics: Literal["differential","holonomic","omni","ackermann"] | Nonedrive the generic Nav2 bringup (seenav2_param_overrides).footprint_polygon: list[tuple[float, float]] | None— optional base-frame XY polygon vertices (metres, CCW); when set, draws the true base outline on the SLAM occupancy grid instead of thefootprint_radiuscircle.scene_defaults: SceneDefaults | None = None— Optional scene-level defaults (top-camera POV, etc.) consumed by the MJCF composers as the fallback when an environment does not pin its own values.validate_for_e2e_pipeline(self) -> None— Assert this manifest carries every field the e2e ROS graph (openral deploy sim→ C++ safety kernel) needs: every actuated joint must haveposition_limits,velocity_limit, andeffort_limitset. RaisesROSConfigErrorlisting every missing field at once — used bysim_e2e.launch.pyso a misshapen manifest fails at launch-parse time, not later in the HAL's first actuation tick. Pure validation; for synthesis of the kernelEnvelopeIntersectionuseopenral_safety.envelope_loader.compute_intersection(robot, skill=None).lidar_sensor(self) -> SensorSpec | None[@property] — First declaredlidar_2dSensorSpec(beam countn_channels,range_min_m/range_max_m,rate_hz), or None. Single source of truth for the synthetic/scanenvelope:openral deploy sim(deploy_sim._scan_params_from_description) forwards it as HALscan_*ROS params andSimSensorBridge(which owns/scanfor the manifest-driven node) reads the envelope from them, so neither hardcodes a scan envelope.nav2_param_overrides(self) -> dict[str, str]— Nav2 param substitutions derived fromfootprint_radius(→robot_radiusand costmapinflation_radius=footprint_radius+NAV2_INFLATION_CLEARANCE_M, kept ≥ the inscribed/circumscribed radius Nav2 derives from the footprint) +base_kinematics(→ MPPImotion_model).{}for fixed-base arms.nav2.launch.pyRewrittenYaml-rewrites the shared base param file with these so one base file serves any mobile base — no hand-vendored per-robot Nav2 yaml.class GripperReadMode(str, Enum)— HowMujocoArmHALreports the gripper qpos. Values:SUM_OVER_SCALE(Franka parallel — normalised to[0,1]),AFFINE_LOW_HIGH(SO-100 revolute Jaw — normalised to[0,1]),PASSTHROUGH(Aloha prismatic / OpenArm revolute — raw qpos in MJCF units).class GripperWriteMode(str, Enum)— HowMujocoArmHALmaps an Action's gripper value toctrl. (bimanual amendment) Values:NORMALISED([0,1]→ctrl_range),PASSTHROUGH(raw →ctrl; MuJoCo clips).class SimGripperDescription(BaseModel)— Gripper wiring inside a MuJoCo MJCF. fields:joint, ctrl_range, qpos_addrs, qpos_scale, read_mode, write_mode, actuator_index, mirror_actuator_indexclass UrdfAsset(BaseModel)— A URDF asset reference plus itsrobot_state_publisherwiring. fields:ref: str(validated against theresolve_assetscheme grammar),root_frame: str | None(URDF root link when it differs frombase_frame),base_to_root_xyz_rpy: tuple[float×6] | None(staticbase_frame→root_frametransform [x,y,z,roll,pitch,yaw], metres+radians)class AssetRefs(BaseModel)— UnifiedRobotDescription.assetsblock: one URDF/MJCF/SRDF reference set replacing the former scattered asset fields. fields:urdf: UrdfAsset | None,mjcf: str | None,srdf: str | None(the last two are bare refs, validated against the same scheme grammar; all defaultNone)class SimDescription(BaseModel)— OptionalRobotDescription.simblock holding MuJoCo joint↔qpos/qvel/actuator wiring forMujocoArmHAL.from_description; the MJCF itself is named byRobotDescription.assets.mjcf. fields:floating_base, joint_qpos_addr, joint_qvel_addr, actuator_index, grippers, settle_steps_default, keyframe_index, seed_ctrl_from_qposclass HalEntrypoints(BaseModel)—RobotDescription.halblock: the robot's simulation + real-hardware HAL import strings, resolved byopenral_hal.build_hal. fields:sim: str | None(null → deriveMujocoArmHAL.from_descriptionwhen asim:block exists),real: str | None(null → simulation-only robot),parameters: HalParameters(per-robot HAL construction defaults)class HalParameters(BaseModel)—RobotDescription.hal.parametersblock: per-robot HAL construction defaults (serialport,robot_ip, …) merged into the constructor byopenral_hal.build_hal(explicittransportwins; unaccepted keys dropped), so a parameterised robot needs no bespoke lifecycle subclass. Empty by default. (issue #191) fields:defaults: dict[str, object]class TopCameraDefaults(BaseModel)— Default placement for the scene-level "top" / "base" camera consumed by sim backends that render an overview camera. (L1271) fields:pos: tuple[float, float, float], target: tuple[float, float, float], fovy: float (gt=0, lt=180)- Replaces the dataset-specific
_DEFAULT_TOP_CAMERA_*module-level constants previously hard-coded inopenral_sim.backends.openarm_robosuite._assets. Backend YAML overrides (scene.backend_options.top_camera_*) still win — this submodel is the default fed to the composer. class SceneDefaults(BaseModel)— Per-robot scene rendering defaults consulted when the scene YAML does not override them. Fields:top_camera: TopCameraDefaults | None,composition: SceneComposition | None. (L1336)class SceneComposition(BaseModel)— Declarative MJCF scene composition (issue #191 Phase 3b).composer: "module:fn"returning(xml, meshdir)+params: dict. The manifest-drivenManifestHALLifecycleNode._create_halcalls the composer and threads the composed MJCF in as the HAL'smjcf_path— replaced openarm's bespoke_create_haltabletop splicing. fields:composer: str,params: dict[str, object]fields:top_camera: TopCameraDefaults | None = None- First consumer is the
openarm_tabletop_pnpMJCF composer (openral_sim.backends.openarm_robosuite._assets.compose_openarm_tabletop_mjcf). Future scenes can extend this submodel as new defaults are pulled out of backend hardcodes.
Pydantic models — runtime snapshots
class JointState(BaseModel)— Real-time joint state snapshot. (L1988) fields:name, position, velocity, effort, stamp_nsclass Pose6D(BaseModel)— 6D pose (position + xyzw quaternion). (L2006) fields:xyz, quat_xyzw, frame_idclass DetectedObject(BaseModel)— Object detection. (L2020) fields:label, confidence, pose, bbox_3d, track_idclass WorldCollisionPrimitive(BaseModel)— A placed convex obstacle in the world (world-frame analogue ofLinkCollisionGeometry); fieldsshape: CollisionShape, pose: Pose6D, object_id: str | None. (L2038)class OccupancyGridRef(BaseModel)— Reference to a 2D occupancy grid for mobile-base world-collision (mirrorsnav_msgs/OccupancyGridmetadata); fieldsframe_id, resolution_m (>0), width (>=0), height (>=0), origin: Pose6D, data_topic. (L2062)class WorldState(BaseModel)— Snapshot consumed by Reasoner and Skills; optionalpolicy_statecarries a typed simulator-native checkpoint proprio vector when joint state alone cannot represent the trained observation. (L2207) fields:stamp_ns, joint_state, base_pose, base_twist, ee_poses, contact_forces, images, image_frames, point_clouds, tactile, detected_objects, battery_pct, diagnostics, collision_primitives, occupancy_grid- collision_primitives / occupancy_grid —
list[WorldCollisionPrimitive](default empty) +OccupancyGridRef | None(defaultNone): the bounded world surface the kernel's world-collision phase checks robot links against; an absent/stale world is treated as unavailable (fail-closed). - image_frames —
dict[str, SensorFrame] | None. Optional in-process frame carrier for no-ROS deployments; defaultNonekeeps the existingimages: dict[str, str]topic-ref path unchanged.
Persistent spatial memory — scene graph
Advisory, queryable Layer-2 world model the S2 Reasoner consults to recall where objects/places/agents are. Never a safety input (the kernel gates only on the geometric world). Poses anchored in the tf2 map frame.
class SpatialNodeKind(str, Enum)—OBJECT | PLACE | ROOM | AGENT.class SpatialRelationKind(str, Enum)—CONTAINS | AT_PLACE | TRAVERSABLE_TO | ON | NEAR.class SpatialNode(BaseModel)— A typed scene-graph node; superset ofDetectedObjectforkind=OBJECT. fieldsnode_id, kind, pose: Pose6D, label, confidence, bbox_3d, embedding_ref, is_container, occludes_contents, first_seen_ns, last_seen_ns, observation_count. Validators:last_seen_ns >= first_seen_ns;occludes_contentsrequiresis_container.class SpatialEdge(BaseModel)— Directed relation; fieldssrc, dst, kind: SpatialRelationKind.class SceneGraph(BaseModel)— Persistent scene-graph memory; fieldsschema_version="0.1", nodes: list[SpatialNode], edges: list[SpatialEdge]. Validators: uniquenode_id; every edge references an existing node.class RecallObjectQuery(BaseModel)— Read-only object recall; fieldstext, label, near: Pose6D | None, max_age_ns, limit. Validator: at least one oftext/labelnon-empty.class ApproachViewpoint(BaseModel)— Camera-facing standoff goal; fieldspose: Pose6D, standoff_m (>0), camera_frame_id.class RecallObjectMatch(BaseModel)— One ranked recall; fieldsnode_id, label, pose: Pose6D, score, last_seen_ns, approach: ApproachViewpoint | None, inside_container_id: str | None.class RecallObjectResult(BaseModel)—matches: list[RecallObjectMatch](empty = unknown → caller raisesROSObjectNotInMemory).class ResolvePlaceQuery(BaseModel)— Resolve a place/room/agent reference; fieldsreference, kind: SpatialNodeKind | None.class ResolvePlaceResult(BaseModel)— fieldsnode_id, goal: Pose6D, path_node_ids: list[str](atraversable_topath).class Action(BaseModel)— Action step or chunk produced by a Skill.tick_indexpreserves the shared inference-tick identity of multi-slot actions across the safety wire for atomic sim commit; optionalcartesian_delta_scalecarries normalized-controller physical ranges for predictive safety without changing the raw delta. (L551) fields:control_mode, horizon, joint_targets, joint_velocities, joint_torques, cartesian_pose, cartesian_delta, cartesian_delta_scale, cartesian_twist, body_twist, foot_placements, gripper, dex_hand_joints, confidence, stamp_ns, ee_name, frame_id, safety_overridesclass QuantizationConfig(BaseModel)— Quantization recipe. (L644) fields:dtype, backend, per_channel, calibration_dataset, extraclass DeviceInfo(BaseModel)— Host compute snapshot. (L668) fields:device_str, gpu_memory_bytes, cuda_compute_capability, cpu_count, archclass RSkillInfo(BaseModel)— Skill runtime state snapshot. (L720) fields:name, version, state, weights_loaded, quantized, warmed_up, embodiment_tags, role, latency_budget_ms, last_inference_ms, error_msg, stamp_ns
Pydantic models — skill packaging (rSkill)
class RSkillLatencyBudget(BaseModel)— Per-stage latency budget. (L799) fields:per_chunk_ms, warmup_ms, load_ms, max_execution_s(max_execution_s— total wall-clock budget for oneexecute_rskillgoal; the skill_runner resolves a dispatcheddeadline_s=0to it, else a global default, so a VLA — which never self-terminates — is bounded, CLAUDE.md §3)class SensorRequirement(BaseModel)— One sensor an rSkill needs the robot to provide. (L980) fields:modality, vla_feature_key, min_width, min_height, countclass ControlModeSemantics(BaseModel)— Action-space semantics on eachActuatorRequirement(rSkill self-containment audit, Gap 2). (L1180) fields:mode: Literal["absolute","delta"], gripper_convention, joint_order, reference_frame- Cross-validator on
ActuatorRequirement: gripper kinds REQUIREgripper_convention; cartesian kinds REQUIREreference_frame; other kinds forbid both. GripperConvention(TypeAlias = Literal[...]) — Closed gripper-action encoding set. Members:normalized_open_unit, normalized_open_symmetric, binary_close_one, raw_joint_rad, width_meters. (L1147)class ActuatorRequirement(BaseModel)— One actuator slot an rSkill emits actions for. (L1198) fields:kind, n_dof, vla_action_key, control_mode_semanticskindreusesControlMode;n_dof/vla_action_keyauto-fill from the robot YAML for canonical embodiments, REQUIRED on the manifest for the"custom"hatch.control_mode_semanticsis REQUIRED per the rSkill self-containment audit (Gap 2): declares absolute-vs-delta and (when applicable) gripper convention / reference frame.class EmbodimentExtra(BaseModel)— Sensor + actuator surface for the"custom"embodiment hatch. (L1304) fields:sensors: list[SensorRequirement] (≥1), actuators: list[ActuatorRequirement] (≥1)class RSkillProcessors(BaseModel)— Explicit lerobotPolicyProcessorPipelineartefact pointers (rSkill self-containment audit, Gap 1 + Gap 3). (L1402) fields:preprocessor_uri, postprocessor_uri- Per-file URI shape
hf://owner/repo[@rev]/path/to/file.ext(file tail REQUIRED — bare repo URIs are the implicit-snapshot shape we deliberately replaced). - Cross-validator rejects identical pre/post URIs.
class RosIntegration(BaseModel)— Wiring for a wrapped ROS 2 action / service. Required whenRSkillManifest.kind in {"ros_action", "ros_service"}; forbidden otherwise. (L2783) fields:package, interface_type, interface_name, result_trajectory_field, default_goal_json, ros_dependenciesresult_trajectory_field is None→ result-only mode (Nav2 shape); set → trajectory mode (MoveIt shape, adapter replays one waypoint perstep()).default_goal_jsonvalidator round-trips the literal throughjson.loadsand rejects non-dict payloads.class DetectorEngine(str, Enum)— Backend selector forkind: "detector"rSkills (2026-06-12 amendment):RTDETR_ONNX = "rtdetr_onnx",VLM_SIDECAR = "vlm_sidecar",ZEROSHOT_HF = "zeroshot_hf". Set onDetectorContract.engineto disambiguate backends that share aruntime(the VLM sidecar and the in-process Transformers zero-shot detector are bothruntime: pytorch);Nonekeeps the legacyruntime-keyed dispatch.class DetectorMode(str, Enum)— Invocation mode of akind: "detector"rSkill, orthogonal toDetectorEngine:CONTINUOUS = "continuous"(always-on background producer →WorldState.detected_objects; reasoner reads it passively, never prompts it; not ExecuteRskill-dispatchable) andON_DEMAND = "on_demand"(prompted open-vocab locator surfaced via thelocate_in_viewtool). Cleanly separates open-vocabulary from prompting: continuous detectors cover a fixed bank the reasoner reads for free; the on-demand locator handles the long tail.class DetectorContract(BaseModel)— Manifest contract forkind: "detector"rSkills. Required whenRSkillManifest.kind == "detector"; forbidden otherwise. Frozen,extra="forbid". Fields:labels,input_size,score_threshold,engine,mode, andmax_side: int | None = None— the VLM-sidecar (LocateAnything-3B) longest-edge resize cap before grounding; lower = fewer image tokens = lower activation VRAM peak (the lever for co-residency with a reward model on an 8 GB GPU),Nonekeeps the backend default (1024); ignored by ONNX/zero-shot detectors. (L2878) fields:labels: list[str](min_length=1; class-label list indexed by model class-id),input_size: tuple[int, int](width × height, both > 0; default (640, 640)),score_threshold: float(ge=0.0 le=1.0; default 0.5),engine: DetectorEngine | None(default None; explicit backend selector — 2026-06-12 amendment),mode: DetectorMode(defaultcontinuous; invocation mode).class RewardContract(BaseModel)— Manifest contract forkind: "reward"rSkills (Robometer-4B reward monitor). Required whenRSkillManifest.kind == "reward"; forbidden otherwise. Frozen,extra="forbid". fields:progress_range: tuple[float, float](default (0.0, 1.0); validated max > min),success_threshold: float(ge=0.0 le=1.0; default 0.5),preference: bool(default False),frame_window_s: float(> 0; rolling-buffer horizon),target_fps: float(> 0; sampling rate),num_bins: int(> 0; default 100; discrete-mode progress bins → normalized [0,1]),instruction_required: bool(default True),check_floor: float(ge=0.0 le=1.0; default 0.4; below this progress the attempt is clearly not done — skip VLM, go straight to the replanning ladder; validated ≤success_threshold),plateau_window_s: float(> 0; default 3.0; trailing window over which progress_trend ≈ 0 means "stopped getting closer"),plateau_tolerance: float(ge=0.0; default 0.05; ε band absorbing critic noise when deciding trend is flat),default_patience_s: float(> 0; default 30.0; baseline execution ceiling backstop). A later amendment adds the four calibration fields; a@model_validator(mode="after")enforcescheck_floor <= success_threshold. A reward monitor is a pure perception consumer: no actuators, no action/state contract; its progress/success signal is advisory-only.class PlaybookContract(BaseModel)— Manifest contract forkind: "playbook"rSkills (human-authored S2 decision procedure). Required whenRSkillManifest.kind == "playbook"; forbidden otherwise. Frozen,extra="forbid". fields:trigger: str(min_length=1, max_length=500; natural-language retrieval key for when the playbook applies),body_uri: str(min_length=1; repo-relative path to the Markdown SOP),composes_tools: list[str](min_length=1; advisory hint of theReasonerToolCalldiscriminators the SOP uses),done_predicate: str(min_length=1, max_length=500; natural-language acceptance test),max_steps: int(> 0; hard tool-call bound, no default). A playbook is content the reasoner reads (injected into the system prompt), never code it executes: no weights, actuators, ROS server, or action/state contract — advisory only.class RSkillManifest(BaseModel)—rskill.yamlmanifest (schema_version="0.1"; pre-publish surface — extended in place several times without bumping). (L2931) fields:schema_version, name, version, license, role, kind, model_family, embodiment_tags, embodiment_extra, capabilities_required, sensors_required, actuators_required, runtime, quantization, weights_uri, chunk_size, latency_budget, min_vram_gb, fallback_skill_id, benchmarks, evaluated_tasks, sim_env_control_mode, policy_extras, paper_url, dataset_uri, source_repo, description, default_prompt, actions, objects, scenes, processors, image_preprocessing, state_contract, action_contract, n_action_steps, ros_integration, detector, reward, reward_rskill_name, playbook. Amendment:evaluated_tasks: list[str](optional, default empty) declares the benchmark task ids / families the checkpoint was trained or validated for; the benchmark runner gates a scene'stask.idagainst it (openral_sim.benchmark.check_benchmark_task_compatibility) — a non-empty list that doesn't cover the scene raisesROSCapabilityMismatch, preventing a checkpoint from running on a task it was not trained for; empty is permissive (legacy).sim_env_control_mode: str | None(optional, default None) declares the simulator controller mode the policy expects when the scene pins none — consumed by the LIBERO backend (_resolve_control_mode: scenebackend_options.control_mode> manifestsim_env_control_mode>"relative"), letting an absolute-control policy (xVLA) run on the canonicallibero_spatial.yamlwithout a duplicate per-policy scene.policy_extras: dict[str, object]is copied intoVLASpec.extraduring CLI/test composition so adapter-owned knobs (e.g. OpenVLAgenerate_action_verl, torch seed, action-scale) are traceable in the manifest without growing top-level schema fields. Amendment:action_contract(mirrorsstate_contract) declares the per-checkpoint action dim consumed by the dataset bridge. Amendment:descriptionis now REQUIRED (was optional) and three new fields surface skill semantics to the reasoner LLM tool palette —actions: list[RSkillAction](closed-vocabulary, REQUIRED; min_length enforced per-kind in_check_kind_consistency),objects: list[str](free-form discriminative keywords),scenes: list[str](free-form). Amendment:kind: RSkillKindis REQUIRED (no default);model_familyandweights_uribecame optional and are gated onkind == "vla"; new optionalros_integration: RosIntegration | Noneblock. Amendment: newkind: "detector"value + optionaldetector: DetectorContract | Nonefield (required iffkind == "detector");actuators_requiredconstraint relaxed from global min_length=1 to per-kind enforcement in_check_kind_consistency(detectors have no actuators). Amendment: newkind: "vlm"value for video-language scene-understanding models (role: s2; no actuators, no action/state contract, no detector block);RSkillActiongainsQUERY = "query". Perception amendment:embodiment_tagsconstraint relaxed from globalmin_length=1to per-kind enforcement in_check_embodiment_tags_present— perception kinds (detector/vlm/reward,_PERCEPTION_KINDS) are embodiment-agnostic and ship emptyembodiment_tags(match-any); every other kind still requires ≥1 tag. Amendment: newkind: "reward"value for robotic reward/progress monitors (role: s2; no actuators, no action/state contract) + optionalreward: RewardContract | Nonefield (required iffkind == "reward");RSkillActiongainsMONITOR = "monitor". Amendment: newkind: "playbook"value for human-authored S2 decision procedures (Markdown SOP the reasoner reads, not code it executes) + optionalplaybook: PlaybookContract | Nonefield (required iffkind == "playbook");RSkillActiongainsPLAN = "plan". The_check_kind_consistencyplaybook branch requiresrole == "s2",RSkillAction.PLAN ∈ actions,chunk_size == 1, and emptyactuators_required; forbidsmodel_family/weights_uri/min_vram_gb/detector/reward/ros_integration/processors/image_preprocessing/action_contract/state_contract/n_action_steps/starting_pose. Amendment: new optionalreward_rskill_name: str | None(default None) — a VLA names the reward/progress-monitor rSkill it pairs with (a VLA emits no success signal of its own, so the reasoner needs a reward model resident alongside it); a top-level_check_kind_consistencyguard forbids it on any kind other than"vla".Nonedefers to the deployment default reward model (it does not mean "run without reward"). Amendment: new optionaldefault_prompt: str | None(default None, 1-500 chars) — the literal task string the checkpoint was conditioned on. A single-task finetune is trained on one exact phrase (upstream typos included) and degrades on a paraphrase, butExecuteRskill.promptwas previously the only source, so a hand-dispatched goal with no prompt fed the policy""._build_runtime_skill_from_manifestfalls back to this field when the goal prompt is empty (loggingrskill_runner.default_prompt_applied). Distinct fromdescription, which is prose for the LLM to choose the skill; this is the conditioning string fed to the policy. Generalist checkpoints leave it unset.from_yaml(cls, path: str) -> RSkillManifest[@classmethod] — Load and validate anrskill.yaml.active_min_vram_gb(self) -> float | None— Declared VRAM (GB) for this skill at its active quantization dtype (min_vram_gb[quantization.dtype]), orNonewhen undeclared. Consumed byassert_vla_reward_fits.is_commercial_use_allowed: bool[@property] — Derived fromlicense: True for apache-2.0/mit/bsd, False otherwise (incl. unknown). Replaces V0's free-fieldcommercial_use_allowed.is_scaffold_placeholder: bool[@property] — True whenname/weights_uri/source_repostill carry an unresolvedRSKILL_TEMPLATE_SENTINELSsentinel — i.e. this is therskills/template/scaffold therskills/*/rskill.yamlglob picks up. The reasoner palette gate (build_tool_palette) drops these so a weak LLM can't dispatch a non-existent skill; the publish gate (_rskill_doc_validator) rejects them. One predicate shared across both consumers.- Cross-validators:
"custom" ∈ embodiment_tags ↔ embodiment_extra is not None; when"custom"is present everyactuators_requiredentry must carry bothn_dofandvla_action_key. - rSkill self-containment audit cross-validator:
processorsREQUIRED whenmodel_family in {smolvla, pi05, xvla, diffusion, rldx}; onlyactmay omit it (legacy norm-stats-in-safetensors path). - Cross-validator (
_check_kind_consistency):kind == "vla"requiresmodel_family+weights_uri+ ≥1actuators_required, forbidsros_integration+detector.kind in {"ros_action","ros_service"}requiresros_integration+ ≥1actuators_required, forbidsmodel_family/weights_uri/processors/state_contract/action_contract/n_action_steps/image_preprocessing/starting_pose/detector, pinschunk_size == 1.kind == "detector"requiresdetector+weights_uri, forbidsmodel_family/ros_integration/action_contract/state_contract/processors/n_action_steps/starting_pose, requires emptyactuators_required.kind == "wam"validates schema-side; the loader rejects it at resolve time. - The historical
policy_idfield was removed in favour of dispatching onmodel_familydirectly. RSKILL_TEMPLATE_SENTINELS: tuple[str, ...] = ("TEMPLATE_ORG", "TEMPLATE_ID")— Canonical unresolved-scaffold sentinels theopenral rskill newscaffolder rewrites. One definition shared by the reasoner palette gate (RSkillManifest.is_scaffold_placeholder) and the publish gate (_rskill_doc_validator.PLACEHOLDER_SENTINELScomposes it; the manifest-name check iterates it) so the "is this a published skill?" rule can't drift across consumers.def contains_rskill_template_sentinel(text: str | None) -> bool— True whentextcarries anRSKILL_TEMPLATE_SENTINELSsubstring;None/empty → False. The text-level primitive behindis_scaffold_placeholder.def assert_vla_reward_fits(vla: RSkillManifest, reward: RSkillManifest, gpu_total_gb: float, *, margin_gb: float = 0.5) -> float— The pre-load gate that verifies a VLA + its paired reward model co-reside in GPU VRAM (a VLA has no success signal of its own, so the reward model must be resident alongside it). Reads each manifest'sactive_min_vram_gb(); raisesROSConfigErrorif either size is undeclared (a required co-residency can't be verified),ROSGPUMemoryErrorifvla + reward + margin_gb > gpu_total_gb(fail fast — never run a VLA blind), else returns the combined GB. Checks the model-pair footprint only (necessary, not sufficient — sim/ROS overhead is budgeted separately via eviction).- rSkill HF-repo naming (CLAUDE.md §3 rSkill packaging; enforced by
tools/rskill_publisher.py). Grammar: hyphens are ONLY the segment separators, every token uses underscores internally, so a name parses by a plainsplit("-")into one of three shapes —rskill-<model>-<robot>-<task>-<quant>(5 parts, weight-bearing kinds),rskill-<model>-<robot>-<task>(4 parts,ros_action/ros_service— no<quant>, they carry no weights), orrskill-playbook-<name>(3 parts,kind: playbook). Each segment shape^[a-z0-9][a-z0-9_]*$. CANONICAL_MODEL_TOKENS: frozenset[str]— versioned<model>checkpoint vocabulary (e.g.smolvla,pi05,xr1,gr00t_n17,rldx1_ft,3d_diffuser_actor,lingbot_vla/lingbot_vla2; non-VLA tool modelsomdet_turbo,rtdetr_coco_r18,robometer_4b,moveit,nav2). Distinct fromModelFamily(a runner dispatch key): several tokens can share one family (openvla/openvla_oft) and tool models have no family.CANONICAL_ROBOT_NAME_TOKENS: frozenset[str]— theEmbodimentTagvalues (which now includeanyand themultiaggregate for a skill declaring >1 concrete robot).multiis a member of theEmbodimentTagLiteral so the validator accepts it; manifests normally list the specific robots.CANONICAL_QUANT_TOKENS: frozenset[str]—{fp32, fp16, bf16, int8, nf4}(weight-bearing kinds;int4→nf4, bitsandbytes NF4). The ROS wrappers (ros_action/ros_service,_WEIGHTLESS_KINDS) carry no weights and OMIT the<quant>segment (a 4-part name)._MODEL_FAMILY_ALLOWED_TOKENS: dict[str, frozenset[str]]— family → allowed<model>tokens (a per-family allowlist;openvla→{openvla, openvla_oft}). The documented family-consistency map.def repo_name_is_canonical(name: str, *, kind: RSkillKind, model_family: str \| None = None) -> bool— The enforced validator: kind-selected shape (5/4/3 parts) + vocab (owner prefix ignored).<task>is checked by shape only — NEVER equality againstevaluated_tasks(that collapses e.g. so101penvspick_place_pen). Whenmodel_familyis given, the<model>token must be in_MODEL_FAMILY_ALLOWED_TOKENS[family](a smolvla checkpoint can't be labelledpi05). (L6150)def expected_repo_name(manifest: RSkillManifest) -> str— The canonical name suggestion (printed on a mismatch, written by--fix-name); always satisfiesrepo_name_is_canonicalfor the manifest's kind.<model>frommodel_family(via_MODEL_FAMILY_TO_TOKEN) or a name-prefix match for tool skills;<robot>=multifor >1 concrete tag else the tag elseany; ROS wrappers omit<quant>, else<quant>fromquantization.dtype(defaultfp32);<task>a default —evaluated_tasks[0]scene-family, else firstbenchmarks, else the name-tail author slug (strips model/robot prefixes + a trailing quant-like token, so it's idempotent and recoverslocator/pen), elsescenes[0], elsemain. Owner preserved fromname(defaultOpenRAL). RaisesValueErroronly when no canonical<model>token can be determined. (L6224)EmbodimentTag(TypeAlias = Literal[...]) — Closed canonical robot embodiments matchingrobots/*/robot.yaml, plus"custom"escape hatch,"mobile_base"class tag for any planar-base robot (so base-only rSkills like Nav2 can target the class without naming each specific mobile platform),"any"— the explicit embodiment-agnostic wildcard declared by perception/playbook kinds in lieu of an empty list (which_check_embodiment_tags_presentnow rejects for all kinds) — and"multi", the repo-name aggregate token for a skill declaring >1 concrete robot (member of the Literal so the name validator accepts it; manifests normally list the specific robots). (L2596)StateLayout(TypeAlias = Literal[...]) — Closed set of per-checkpoint proprioception layouts:smolvla_9d, human300_16d, gr1, rc365, simpler_widowx, simpler_google, libero_eef8d. Names the SHAPE the checkpoint was trained on (field order, frame convention, gripper encoding, quaternion handedness). Per-robot SOURCE bindings live onStateContractBindings.libero_eef8dis the LIBERO 8-D task-space proprio (eef_pos(3) ‖ eef_axisangle(3) ‖ gripper_qpos(2)in the world frame) the lerobot/smolvla_libero, pi05-libero and xvla-libero checkpoints train on — the deploy assembler reconstructs it from live TF + JointState so deploy state matches the benchmark (without it the runner feeds raw joint-space state to a task-space policy). (Thepi0_16d/eef_pose_7d/base_pose_7drobocasa sim-observation layouts were removed — no state-adapter assembler existed; recreate alongside an assembler when next needed.)WRAPPED_TASK_SPACE_LAYOUTS: frozenset[StateLayout]— Subset ofStateLayoutcovering Cartesian/FK-derived composites:{rc365, human300_16d, libero_eef8d}. These layouts REQUIREStateContract.bindings; the cross-validator onStateContractenforces this at manifest load (human300_16d/rc365requireeef_frame+base_frame;libero_eef8drequireseef_frame+gripper_qpos_joints— world-frame absolute EE pose, so nobase_frame). Joint-space layouts (smolvla_9d,gr1,simpler_*) are excluded — they're served verbatim from rawJointState.position.StateContractBindings(Pydantic model) — Per-robot source bindings for an rSkill'sstate_contract.layout. Fields:eef_frame: str | None,base_frame: str | None,world_frame: str | None = "map",gripper_qpos_joints: list[str],quaternion_convention: Literal["xyzw","wxyz"] = "xyzw". Symmetric toControlModeSemanticson the action side. Required whenStateContract.layoutis inWRAPPED_TASK_SPACE_LAYOUTS, forbidden otherwise.BenchmarkName(TypeAlias = Literal[...]) — Closed canonical benchmark ids matchingbenchmarks/*.yamlsuites (plus retainedaloha_insertion/aloha_transfer_cubetask-level ids cited by the act-aloha* manifests after the two suites were unified intoaloha.yaml). Members:aloha, aloha_insertion, aloha_transfer_cube, gr1_tabletop, libero_10, libero_goal, libero_object, libero_spatial, maniskill3_panda, metaworld_mt10, metaworld_mt50, pusht, rlbench, robocasa_pnp, robotwin, simpler_env_widowx. (L2633)ModelFamily(TypeAlias = Literal["smolvla","pi05","xvla","act","diffusion","rldx","molmoact2","gr00t","diffuser_actor","openvla","lingbot_vla2","lingbot_vla","internvla_n1"]) — Closed VLA/policy family used by the eval/runner adapter dispatch. Required only whenRSkillManifest.kind == "vla".gr00t(NVIDIA Isaac GR00T) runs out-of-process via a ZMQ sidecar, reusing therldxadapter.diffuser_actor(3D Diffuser Actor, RLBench keyframe policy) runs out-of-process via its own py3.10 sidecar.openvla(OpenVLA / OpenVLA-OFT) loads in-process as a transformers custom-code model and de-normalizes discrete action tokens via the checkpoint'sunnorm_key.lingbot_vla2(Robbyant LingBot-VLA 2.0, Qwen3-VL-4B + sparse-MoE flow-matching expert, Apache-2.0) runs out-of-process via its own auto-provisioned py3.12 + torch-2.9.1 sidecar (openral_sim.policies.lingbot_vla2);lingbot_vla(LingBot-VLA 1.0, 4B Qwen2.5-VL + dense Qwen2 flow-matching expert, the RoboTwin post-train) shares that sidecar via--variant v1(a separate V1 repo + transformers==4.51.3 / lerobot==0.4.2 venv, which stays on torch 2.8 and is x86_64-only —docs/reference/aarch64-support.md).internvla_n1(InternRobotics InternVLA-N1 / DualVLN, arXiv:2512.08186) is a dual-system vision-language navigation policy (Qwen2.5-VL-7B System-2 + NavDP DiT System-1); it runs out-of-process via a py3.11 ZMQ sidecar (upstream pins transformers 4.51) and emits a 6-DBODY_TWISTbase velocity from RGB-D + instruction. (L2655)RSkillKind(TypeAlias = Literal["vla","wam","ros_action","ros_service","detector","vlm","reward","playbook"]) — Discriminator selecting the loader / runner branch."vla"is today's learnable policy path;"ros_action"/"ros_service"route throughROSActionRskill;"wam"is reserved (loader rejects at resolve time);"detector"is a perception producer that runs an exported ONNX/TRT detection model and publishesObjectsMetadata— emits noAction;"vlm"is a video-language model answering natural-language scene queries from camera frames — emits text, no actions/boxes,role: s2(reached via the read-onlyquery_scenetool, notExecuteRskill);"reward"is a progress/reward monitor;"playbook"is a symbolic, human-authored S2 decision procedure (Markdown SOP) the reasoner reads into its system prompt — no weights, actuators, orAction,role: s2. (L2752)
Pydantic models — skill benchmark results (rskills/<id>/eval/<benchmark>.json)
class RSkillEvalSource(BaseModel)— Provenance of a benchmark block. (L985) fields:paper, arxiv, model_variant, evaluated_by, reproduced_locally, reproduction_planned, reproduction_cli, table, statusclass RSkillEvalBenchmark(BaseModel)— Suite identity for a benchmark block. (L1023) fields:name, dataset, protocol, robot, simulatorclass RSkillEvalResult(BaseModel)— On-disk shape ofrskills/<id>/eval/*.json. Carries an optionaltrace_id: str | None(32-hex) populated byopenral benchmark runfor offline cross-reference into the OTel trace tree. (L1042) fields:schema_version, source, benchmark, eval_config, results, baselinesfrom_json(cls, path: str) -> RSkillEvalResult[@classmethod] — Load and validate a single benchmark JSON. (L1083)
Pydantic models — sim eval
class SceneSpec(BaseModel)— Physics scene declaration. (L1283) fields:id, backend, assets_uri, observation_height, observation_width, cameras, backend_optionsclass RoboCasaBackendOptions(BaseModel)— Typed validator forSceneSpec.backend_optionsunder the RoboCasa backend. Prebuilt-vs-procedural XOR enforced by amodel_validator;state_layoutincludes XR-1's 8-D arm/gripper observation. (L1324) fields:mode, prebuilt_task, kitchen_style, layout_id, fixtures, spawn_objects, task_verb, robots, controller, horizon, state_layoutclass TaskSpec(BaseModel)— What the robot must achieve. (L4500) fields:id, scene_id, instruction, max_steps: int | None, success_key: str | None, metadataclass VLASpec(BaseModel)— Policy / brain declaration. (L1029) fields:id, weights_uri, device, runtime, quantization, deterministic, extraclass SimEnvironment(BaseModel)— Runtime (robot × scene × task × VLA) tuple. Composed at the CLI from aSimSceneorBenchmarkSceneYAML plus anRSkillManifest(--rskill); never loaded from YAML directly. (L2343) fields:robot_id, scene, task, vla, base_pose, seed, n_episodes, record_video, save_dir, metadatabase_pose: Pose6D | None = None— Per-rollout robot mounting pose in the scene's world frame; honoured by free-axis scenes only.model_post_init(_context: object) -> None— Cross-field validationtask.scene_id == scene.id. (L2389)class BenchmarkMetadata(BaseModel)— Provenance block required on everyBenchmarkScene; fields:paper: str,honest_scope: str, optionaldisplay_name: str | None, optionalsimulator: str | None. The two optional fields becomeRSkillEvalResult.benchmark.name/.simulatorwhen present; suite invariants treat the whole block as byte-identical across scenes. (L4687)class DeployRuntime(BaseModel)— Committed deploy-posture toggles for a workcell scene: every runtime legdeploy sim/deploy runcan bring up (enable_slam,enable_nav2,enable_octomap,enable_octomap_kernel_check,enable_object_detector+object_detector_onnx/manifest/query/locators,enable_reward_monitor+reward_monitor_manifest/task,enable_critic,spatial_memory_ingest,approach_skill_id), all tri-state (None= the CLI-documented auto). Field-by-field precedence inresolve_launch_invocation: explicit CLI flag > sceneruntime:> auto/built-in default. Host-operational knobs (dashboard, foxglove, ports, dataset recording,--initial-task) stay CLI-only. Relative*_manifest/*_onnxpaths that exist next to the scene YAML resolve against its directory. Two scene-only visual-SLAM keys (no CLI flag — workcell/host properties):slam_visual_impl: Literal["isaac_ros", "pycuvslam"] | Nonepicks the cuVSLAM engine thevisualbackend composes (None=isaac_ros, the composable Isaac ROS C++ node;pycuvslam= the in-process PyCuVSLAM wheel, rectified stereo) — orthogonal to the capability-derived backend choice;slam_stereo_cameras: tuple[str, str] | Nonenames the(left, right)rig cameras (each →/openral/cameras/<name>/image+/camera_info, forwarded to whichever impl is composed), validated distinct;slam_mono_camera: str | Nonenames ONE camera for the mono RGBD path (pycuvslamonly — cuVSLAMOdometryMode.RGBDfused with DA3 metric depth; the launch auto-composes the depth provider + nvblox), validated non-empty and mutually exclusive withslam_stereo_cameras;slam_depth_sidecar_autostart: bool = Truehas the launch spawntools/da3_depth_sidecar.py(ZMQ :5771) alongside the mono graph (False= operator-run/shared sidecar; only meaningful withslam_mono_camera).class DeployScene(BaseModel)— Unified deploy/workcell scene foropenral deploy simandopenral deploy run; carriesscene,robot_id,base_pose, deterministic startupseed, sim-onlycomposition: SceneComposition | None,safety: SafetyEnvelope | None(tighten-only against the robot manifest), additiveextra_allowed_collision_pairs: list[tuple[str, str]],sensors: list[SensorSpec],hal: HalParameters | None,memory_dir: str | None, andruntime: DeployRuntime | None. Notasksfield or evaluation cadence; deploy goals come from the operator via--initial-task//openral/prompt. Rejects legacyvla:blocks.from_yaml(cls, path: str) -> Self[@classmethod] — Load and validate a scene YAML from disk; inherited (not overridden) bySimSceneandBenchmarkScene, which validate against their own stricter schemas viacls. (L4734)class SimScene(DeployScene)— ExtendsDeployScenewithtask,seed,n_episodes,record_video,save_dir,metadata; cross-validatestask.scene_id == scene.id; accepted byopenral sim run. Loads via the inheritedDeployScene.from_yaml. (L4743)class BenchmarkScene(SimScene)— ExtendsSimScenewith requiredn_episodes,seed, andmetadata: BenchmarkMetadata; also requirestask.success_keyandtask.max_steps; consumed byopenral benchmark. Loads via the inheritedDeployScene.from_yaml(raisesValidationErrorif eval fields are missing). (L4779)class ProtocolSpec(BaseModel)— Standalone eval-protocol schema. Retained as a public surface for decision-record drafts and report tooling that quote a published protocol verbatim; never embedded in a benchmark suite (an earlier change flattened the per-scene fields ontoBenchmarkScene; a later change then deleted theBenchmarkSpecwrapper entirely so a suite is now a barelist[BenchmarkScene]). (L1386) fields:n_episodes, seeds, success_key, max_steps, min_repsmodel_post_init(_context: object) -> None— Cross-field validation:len(seeds) >= n_episodesandmin_reps <= n_episodes. (L1427)
Pydantic models — inference runner
On-disk + runtime contracts for the hardware inference runner (openral deploy --config R.yaml), sibling of SimEnvironment / openral sim run. Schemas are additive — SimEnvironment / RSkillEvalResult / BenchmarkScene are untouched.
class FrameEncoding(str, Enum)— HowSensorFramebytes are interpreted. (L557)BGR8, RGB8, MONO8, DEPTH16, JPEG, PNG, CUDA_NV12, CUDA_RGBA, RAW—CUDA_NV12is the Tegra NVMM handle layout,CUDA_RGBAthe x86 DeepStream oneclass SensorFrame(BaseModel)— Single sensor frame: metadata + optional inline / topic / handle payload. JSON-serializes the binary payload as base64. (L576) fields:sensor_id, stamp_monotonic_ns, stamp_wall_ns, encoding, width, height, channels, data, topic, handle, metadata_decode_data(cls, value: Any) -> bytes | None[@field_validator("data", mode="before")] — Accept rawbytesor a base64-encodedstron JSON parse. (L614)_encode_data(self, value: bytes | None) -> str | None[@field_serializer("data", when_used="json")] — JSON-serialize the binary payload as base64. (L632)model_post_init(_context: object) -> None— Cross-field validation: exactly one of(data, topic, handle)must be set. (L637)class SensorReaderBackend(str, Enum)— WhichSensorReaderimplementation a sensor uses. Includesgalaxea_a1_camera_bridge, which consumes the versioned paired raw-frame owner exposed by an external A1 Runtime process without importing Runtime or reopening either RealSense device. (L1691)OPENCV_THREAD, ROS2_IMAGE, GSTREAMERclass DeadlineOverrunPolicy(str, Enum)— Behaviour when a tick exceeds1 / rate_hz. (L1706)WARN, DROP, RAISEclass SensorReaderConfig(BaseModel)— Per-sensor reader backend + optional ROS-tee. (L1720)class SensorDeployBinding(BaseModel)— OptionalSensorSpec.deploy_bindingpayload — readerbackend+backend_params(device/fps) +max_age_msthat letsopenral deploy runopen the physical camera; the runtime counterpart ofsim_placement. fields:sensor_id, backend, backend_params, max_age_ms, publish_to_ros, publish_topic, publish_rate_hzmodel_post_init(_context: object) -> None— Cross-field validation:publish_to_ros ↔ publish_topic. (L1774)class HalConfig(BaseModel)— Which HAL adapter to instantiate + transport params (serial port / FCI URI / ROS namespace). (L1786) fields:adapter, transport, paramsclass TickResult(BaseModel)— One tick's record returned byInferenceRunner.tick. v2 (amendment 1) adds five optional sim-only fields (step_idx,episode_idx,reward,terminated,truncated) and an optionaltrace_context: str | None(full W3Ctraceparentfor the tick'srskill.tickspan). All optional fields default toNone; hardware ticks that don't carry sim metadata or a live trace context serialise byte-identically to v1 underexclude_none=True. (L1937) fields:stamp_ns, tick_idx, sensors_ms, world_state_ms, inference_ms, safety_ms, hal_ms, tick_ms, chunk_index, safety_violations, action_applied, step_idx, episode_idx, reward, terminated, truncatedclass RunResult(BaseModel)— Aggregated summary returned byInferenceRunner.run. (L1980) fields:n_ticks, success, budget_violations, avg_inference_ms, p99_inference_ms, avg_tick_ms, p99_tick_ms, trace_id, save_dir, metadata
Pydantic models — failure evidence
Discriminated union backing the evidence_json field of openral_msgs/msg/FailureTrigger. Discriminator is the kind field (a Literal[...] on each variant); decode via pydantic.TypeAdapter(FailureEvidence).validate_json(...). All variants are frozen=True and extra="forbid".
class _FailureEvidenceBase(BaseModel)— Private base,frozen=True,extra="forbid". (L3064)class TimeoutEvidence(L3075) —kind="timeout"; fieldsoperation, deadline_s, elapsed_s.class ForceEvidence(L3092) —kind="force"; fieldsjoint_or_ee, measured_n, limit_n.class WorkspaceEvidence(L3108) —kind="workspace"; fieldsee_name, measured_xyz, box_min, box_max.class PerceptionStaleEvidence(L3126) —kind="perception"; fieldssensor_id, staleness_ms, threshold_ms.class CriticEvidence(L3142) —kind="critic"; fieldscritic_id, score, threshold.class ControllerEvidence(L3158) —kind="controller"; fieldscontroller_name, state, detail.class SelfVerifyEvidence(L3174) —kind="selfverify"; fieldscheck, expected, observed.class HumanEvidence(L3190) —kind="human"; fieldsactor, reason.class WamEvidence(L3204) —kind="wam"; fieldshorizon, discrepancy, wam_id.class ReasonerTimeoutEvidence(L3220) —kind="reasoner_timeout"; fieldsmodel, deadline_s, elapsed_s.class CollisionEvidence(L4694) —kind="collision"; fieldscollision_kind: Literal["self"|"world"], link_a, link_b_or_object, horizon_step, min_distance_m. Maps toFailureTrigger.KIND_COLLISION = 10.class SuppressedSummaryEvidence(L3236) —kind="suppressed_summary"; fieldswindow_s, kinds: list[int], severities: list[int], counts: list[int]. Model-validator enforces parallel arrays (raisesROSConfigError).FailureEvidence: TypeAlias(L3270) — Discriminated union over the twelve variants above. Module docstring shows the encode/decode pattern.
Pydantic models — perception event metadata
Discriminated union backing the metadata_json field of openral_msgs/msg/PromptStamped when published on /openral/perception/<kind>. Discriminator is the kind field (a Literal[...] on each variant); decode via pydantic.TypeAdapter(PerceptionEventMetadata).validate_json(...). All variants are frozen=True and extra="forbid". New kinds = new topics, not a schema bump.
class _PerceptionEventBase(BaseModel)— Private base; carriessensor_id: str. (L3304)class ObjectDetection2D(BaseModel)(L3323) — single 2D detection insideObjectsMetadata; fieldslabel, confidence, bbox_xyxy, det_id: int = -1.det_idis a stable per-detector/per-camera identity assigned at detection time byDetectionTracker2D, so an object can be enumerated + de-duplicated without the 3D lift (-1= untracked). The lift propagates it intoDetectedObject.track_id→ one id across the 2Din_viewline and 3Dscene_objects.def aabb_iou_2d(a, b) -> float(openral_core.detection_tracker) — 2D axis-aligned bbox IoU in[0, 1]; boxes(x_min, y_min, x_max, y_max)(pixel space); 0.0 for disjoint/degenerate. The 2D analog ofaabb_iou_3d.class DetectionTracker2D(*, iou_threshold=0.3, max_misses=3)(openral_core.detection_tracker) — stateful camera-space 2D-IoU tracker (the 2D analog ofObjectMemory).assign(detections) -> list[ObjectDetection2D]stamps a stabledet_idper object: greedy highest-confidence-first same-label IoU association keeps a matched box's id (refreshing its box), an unmatched box mints a fresh monotonic id, and a track unseen formax_missesframes is retired. Pure, ROS-free; one instance per camera, run on the detector's continuous leg.class MotionMetadata(L3344) —kind="motion"; fieldsmagnitude, threshold, region_bbox.class ObjectsMetadata(L5070) —kind="objects"; fieldsdetections: list[ObjectDetection2D], model_id, frame_width: int (>0), frame_height: int (>0).frame_width/frame_heightwere added to make the pixel space ofbbox_xyxyexplicit so theVoxelFrustumLiftercan scale to the intrinsics resolution (CLAUDE.md §1.4). Producers (ObjectsDetector,NvmmObjectsDetector) populate both at detect-time.class OcrMetadata(L3386) —kind="ocr"; fieldstext, confidence, region_bbox.class SceneChangeMetadata(L3404) —kind="scene_change"; fieldsdistance, threshold, metric.PerceptionEventMetadata: TypeAlias(L3427) — Discriminated union over the four variants above. Module docstring shows the encode/decode pattern.
Pydantic models — reasoner tool calls
Discriminated union over the closed palette of typed tool calls the F4 reasoner can emit each tick. Discriminator is the tool field (a Literal[...] on each variant); decode via pydantic.TypeAdapter(ReasonerToolCall).validate_json(...). All variants are frozen=True and extra="forbid" so an LLM cannot smuggle ad-hoc fields onto the wire. The reasoner holds no authority over actuation — it never publishes ActionChunk; ExecuteRskillTool is indirect (action goal on the F1 server which gates through F5 safety).
class _ReasonerToolBase(BaseModel)— Private base; carries optionalrationale: str. (L3455)class ExecuteRskillTool(L7384) —tool="execute_rskill"; fieldsrskill_id: str(min_length=1),prompt: str(default ""),goal_params_json: str(default ""),deadline_s: float(ge=0.0; default 0.0; 0 = use manifest latency budget),patience_s: float | None(default None; gt=0.0; task-adaptive execution ceiling override — None uses the reward model'sdefault_patience_s),progress_tolerance: float | None(default None; ge=0.0; overrides the reward model'splateau_tolerancefor a noisy critic — None uses the model default).class ReloadGstPipelineTool(L3508) —tool="reload_gst_pipeline"; fieldssensor_id, pipeline_yaml.class LifecycleTransitionTool(L3532) —tool="lifecycle_transition"; fieldsnode, transition: Literal["configure"|"activate"|"deactivate"|"cleanup"].shutdownis intentionally absent — shutdown is the safety supervisor's authority per CLAUDE.md §6 Layer 6. This is the canonical primitive for managing long-lived background services (slam_toolbox, RTAB-Map, perception trees) — they are LifecycleNode peers, not rSkills.class EmitPromptTool(L3556) —tool="emit_prompt"; fieldstarget_topic(must start with/),text,metadata_json. The reasoner node publishes ontarget_topicitself (per-topic publisher cache;/openral/promptreuses the standing cascade publisher).class WaitTool— deliberate no-op;tool="wait"; no fields beyond the baserationale. The reasoner's tool choice is forced (tool_choice="any"/"required"), so without this variant the LLM must act every tick even when the correct decision is "observe and wait" (skill mid-execution and nominal; mission finished, no new goal). Dispatch: none — the node logs the rationale and returns. No actuation authority.class RecallObjectTool— read-only query;tool="recall_object"; fieldsquery(free-text/label),limit. Recalls an object from the scene-graph memory; no actuation authority. Dispatch + result-return is planned Phase 2 (not yet in the live provider palette).class ResolvePlaceTool— read-only query;tool="resolve_place"; fieldreference("the kitchen", "where I was standing"). Resolves a place/room/agent to a goal pose + path; no actuation authority. Dispatch is planned Phase 2.class LocateInViewTool— read-only query;tool="locate_in_view"; fieldsquery(concrete object noun(s) — a single noun or comma-separated list e.g."cup, bowl, basket"; the fast omdet locator matches each term as one class, so a collective phrase like"the objects on the table"matches nothing),camera(optional viewpoint id, default""= primary — camera-agnostic, not a hardcoded name),detector(optional on-demand locator selector / alias, default""= the deployment default; the reasoner routes to/openral/perception/<detector>/locate_in_view). Asks a live VLM detector if the object is in the CURRENT frame (vsrecall_object's remembered objects). No actuation authority — choosing a model does not grant it.class QuerySceneTool— read-only query;tool="query_scene"; fieldsquestion(open-ended scene-state question, min_length=1),camera(optional viewpoint id, default""). Asks a scene VLM (Qwen3.5-4B) an open-ended question about the CURRENT frame for task-progress / success verification; dispatched via/openral/perception/query_scene, answer fed back as a re-prompt. Distinct fromlocate_in_view(localization → boxes): returns free text. No actuation authority.class QueryTaskProgressTool— read-only query;tool="query_task_progress"; fieldswindow_s(seconds of recent frames to assess, > 0, default 8.0),task(optional instruction override, default""→ reuse the active goal). Asks the Robometer reward monitor for a quantitative windowed assessment of the CURRENT task; dispatched via/openral/perception/query_task_progress, the verdict (progress/success now + trends +stalled/succeeded) fed back as a re-prompt driving the replanning ladder. Distinct fromquery_scene(free text): returns normalized scalars. No actuation authority.MemorySection: TypeAlias = Literal[...]— the five fixed sections of the self-maintainedMEMORY.mdcore:home_map,preferences,lessons,object_locations,open_tasks.class MemoryWriteTool— write; the reasoner's first write-capable variant;tool="memory_write"; fieldsop(add/update/supersede/delete),section: MemorySection,content(required unlessdelete— validated),importance(0–1, default 0.5),target(required forupdate/supersede/delete— validated). Edits the advisoryMEMORY.mdvia an explicit op (Mem0 ADD/UPDATE/DELETE + Zep supersession); writes the memory file only — no actuation authority.class MemorySearchTool— read-only;tool="memory_search"; fieldsquery(min_length=1),section: MemorySection | None,limit(1–100, default 5). Pages archived entries (evicted from the bounded core) back in. No actuation.is_collective_target(text) -> bool— Module helper; true whentexttargets a set rather than one specific object: a quantifier (all/every/each/both/everything) or a bare generic plural (objects/items/things), via_COLLECTIVE_TARGET_RE. Narrow by design (a specific goal never trips it). Single source of truth shared byGroundedSubtask's validator and the reasoner node's runtime execute gate (openral_reasoner_ros.reasoner_node).class GroundedSubtask— One subtask bound to exactly ONE specific object;extra="forbid",frozen; fieldsobject_ref: str(min_length=1; the single concrete object/place) andtext: str(min_length=1; the instruction handed to the skill / VLA prompt). A@model_validator(mode="after")forbids a collectiveobject_refortext(is_collective_target) and requirestextto nameobject_ref— so "the first batch of objects" is not a representable value.render() -> strreturns the trimmedtext.class DecomposeMissionTool— task-ledger write (issue #123);tool="decompose_mission"; fieldssubtasks: list[GroundedSubtask](min_length=1 — each subtask grounded to one object, no free strings),target_task_id: str(default"").rendered_subtasks() -> list[str]renders each to itsMissionStatetask text. The typed path for thedecompose-missionplaybook to write the deterministicMissionState: emptytarget_task_id→ populate/replace the whole queue (refining the single-task seed); set → flat-splice that blocked task (subdivide_active, bounded byDEFAULT_MAX_SUBDIVIDE_DEPTH). Edits the S2 task ledger only — no actuation authority.ReasonerToolCall: TypeAlias— Discriminated union over the thirteen variants above (four actuation/effect + thewaitno-op + four read-only query + the two memory tools, one write + one read + theDecomposeMissionTooltask-ledger write (issue #123)). Module docstring shows the encode/decode pattern.
Module-level functions (Layer 0)
def control_modes_for_representation(rep: ActionRepresentation) -> set[ControlMode](L2376) — Maps a VLA's declaredActionRepresentationto the set ofControlModes it drives (JOINT_POSITIONS→{JOINT_POSITION},JOINT_VELOCITIES→{JOINT_VELOCITY},DELTA_EE_6D→{CARTESIAN_DELTA},DELTA_EE_6D_PLUS_GRIPPER/DELTA_EE_3D_PLUS_GRIPPER → {CARTESIAN_DELTA, GRIPPER_POSITION},CARTESIAN_POSE→{CARTESIAN_POSE}). Single source of truth for the reasoner's deploy-path palette gate (a skill is offered only when the target robot advertises every returned mode).SIM_EXECUTABLE_CONTROL_MODES: frozenset[ControlMode](L2492) — Canonical set ofControlModes the default sim HAL action-packers can execute (amended 2026-06-04), and the single source of truth for the reasoner'shal_mode == "sim"deploy-path palette gate:{JOINT_POSITION, JOINT_VELOCITY, CARTESIAN_DELTA, GRIPPER_POSITION, BODY_TWIST, COMPOSITE_MODE}. MirrorsCONTROL_MODE_TO_UINT8as a shared core constant (core is a dep of both reasoner and HAL). Pinned in both directions to the packers inpython/hal/src/openral_hal/sim_attached.py(pack_action_for_env,SimAttachedHAL._pack_with_composite_split, and theBODY_TWISTdirect-qpos path) bytests/unit/test_sim_executable_modes_match_packers.py. ExcludesJOINT_TORQUE/JOINT_TRAJECTORY/CARTESIAN_POSE/GRIPPER_BINARY(decoded but never pack-executed → would E-stop mid-run) andCARTESIAN_TWIST/FOOT_PLACEMENT/DEX_HAND_JOINT(no sim controller).def canonical_slots_for_representation(rep: ActionRepresentation, *, dim: int, description: RobotDescription) -> list[ActionSlot] | None(L2408) — Builds the canonicalActionSlotlayout the skill_runner dispatches a representation-onlyActionContractthrough. Joint representations →None(caller keeps the legacy whole-vectorJOINT_POSITIONpath).DELTA_EE_6D/CARTESIAN_POSE→ one cartesian slotrange=(0,5)addressed at the primary EE (description.end_effectors[0]);DELTA_EE_6D_PLUS_GRIPPERadds aGRIPPER_POSITIONslotrange=(6, dim-1).DELTA_EE_3D_PLUS_GRIPPERis the translation-only variant: a 3-wide cartesian-delta slotrange=(0,2)+ gripper slotrange=(3, dim-1).EndEffectorSpechas no explicit tf-frame field, so the EEnameis used as the slotframe. RaisesROSConfigErrorwhen the representation needs an EE butend_effectorsis empty, or whendimis too small (DELTA_EE_6D/CARTESIAN_POSEneeddim>=6;DELTA_EE_6D_PLUS_GRIPPERneedsdim>=7;DELTA_EE_3D_PLUS_GRIPPERneedsdim>=4).def task_space_compatible(skill_space: TaskSpace, robot: RobotDescription, *, hal_mode: Literal["sim", "real"] = "real") -> TaskSpaceMatch(L3592) — (DRAFT) The single cross-layer gate that subsumes today's implicit wiring (embodiment_tagsstring match + raw dim equality + adapter magic). Mirrors the reasoner deploy gate_action_executable: inhal_mode="sim"each segment'scontrol_modemust be inSIM_EXECUTABLE_CONTROL_MODES(robosuite OSC synthesises cartesian/gripper/base from joint commands); in"real"it must be advertised inrobot.capabilities.supported_control_modes. Both modes also require cartesian/gripper/dextargetEEs to exist on the robot and total joint-segment width ≤ robot joint count (physical facts). Returnsok+ a reason per incompatibility. Wired warn-only into the reasoner palette +rskill_publisher(Phase 2). The repo-wide sweep (tests/unit/test_task_space_sweep.py) pins the current state: actuating skill×robot pairs sim-executable except 2 recordedKNOWN_SIM_GAPS(rlbench cartesian-pose + gr1 29-DoF, both dedicated-controller paths).def scene_family(task_id: str) -> str— Reduce anevaluated_tasksentry to its scene-family key (leading token before any/):"rlbench/open_drawer" → "rlbench","metaworld" → "metaworld".SCENE_FAMILY_TASK_SPACE: dict[str, SceneTaskSpace]— Single source of truth for the control interface each scene-adapter family executes, keyed byscene_family(evaluated_task). Adding an rSkill that declares a new task family with no entry tripstest_scene_families_are_declared. Covers libero_/metaworld/simpler_env (cartesian_delta+gripper), aloha_/robotwin/maniskill3/pusht (joint_position), robocasa (cartesian_delta+gripper+joint_velocity+composite), rlbench (cartesian_pose+gripper, dedicated path).def scene_task_space_compatible(family: str, skill_space: TaskSpace) -> TaskSpaceMatch— Third leg of the cross-layer gate: everyControlModethe rSkill emits must be in the scene family's executed set, and (when the family fixes a width)total_dimmust match. Pairs withtask_space_compatible(rSkill × robot) to close the rSkill × robot × scene triangle. Unknown family → a single reason (not an exception), so a sweep collects every gap in one pass.
python/core/src/openral_core/loaders.py
Strict YAML loaders for the three scene tiers.
def load_scene_strict(path: str, expected: type[DeployScene | SimScene | BenchmarkScene]) -> DeployScene | SimScene | BenchmarkScene(L36) — Loadpathas exactlyexpected; reject other tiers withROSConfigErrorcarrying a redirect message that names the right CLI command.mypy --strictoverloads narrow the return to the requested concrete type. Centralises the rejection logic used by every scene-driven CLI loader (openral deploy sim→DeployScene,openral sim run→SimScene,openral benchmark scene→BenchmarkScene) so a YAML one tier too rich is not silently widened (e.g. a BenchmarkScene YAML passed toopenral sim runwould otherwise dropn_episodes/metadataon the floor). RaisesFileNotFoundErrorfor a missing path andROSConfigErrorfor a non-mapping YAML root, an extra-key mismatch, or a tier mismatch.def load_benchmark_suite(path: str) -> list[BenchmarkScene](L148) — Load a barelist[BenchmarkScene]frombenchmarks/<id>.yaml. The suite id is the filename stem; the YAML root MUST be a list. The legacy{id, tasks, metadata}dict shape is rejected with an explicit redirect message naming the migration. Per-scene Pydantic validation runs here; suite-level invariants (uniformity, uniqueness, non-empty) are NOT enforced — callraise_on_invalid_suiteseparately so tests can construct invalid in-memory suites without touching disk. RaisesFileNotFoundErrorfor missing paths andROSConfigErroron every shape / validation failure (never a bareValidationError).def raise_on_invalid_suite(scenes: list[BenchmarkScene], *, suite_id: str) -> None(L223) — Free-function replacement for the deletedBenchmarkSpec.model_post_init. Enforces the five suite invariants: non-empty list, everyscenes[i].robot_idnon-None, everytask.idunique within the suite, everyscenes[i].robot_id/n_episodes/seed/metadatabyte-identical across the list. Per-scenetask.success_keyandtask.max_stepsMAY differ (ManiSkill3 mixed-budget suite).suite_idis embedded in every error message so failures point back at the rightbenchmarks/<id>.yaml. First violation wins — no batched reporting. RaisesROSConfigErroron any invariant violation.
python/core/src/openral_core/assets.py
The single resolver for robot description assets — URDF / MJCF / SRDF.
class AssetRefError(ValueError)(L39) — A description-asset reference is malformed or cannot be resolved.def resolve_asset(ref: str, kind: AssetKind, *, manifest_dir: Path | None = None) -> Path | None(L43) — Resolve one assetrefto a concrete file path for the requestedkind(urdf/mjcf/srdf). One grammar replacingresolve_urdf_path,resolve_mjcf_uri, plain-path SRDF, andurdf_lowering._load_urdf_model. Schemes:rd:<module>(upstreamrobot_descriptions, downloads on first use; xacro-only URDF →AssetRefErrordirecting toopenral robot vendor-urdf),file:<relpath>(manifest dir then repo root),gym_aloha:<scene>/openarm:<variant>/menagerie:<model>(sim-only MJCF loaders, lazy-imported;openarm:variants:bimanual→ Enactic v2 viaensure_openarm_v2_mjcf,anvil_v2_bimanual→ Anvil OpenARM 2.0 viaensure_anvil_openarm_v2_mjcf; menagerie not yet wired),ros2://robot_description(URDF-only dynamic marker → returnsNone). RaisesAssetRefErrorfor every other unresolvable/malformed ref.
python/core/src/openral_core/geometry.py
Shared rotation geometry — look-at/camera gaze poses (relocated here from openral_world_state.geometry so every layer, incl. the layer-0 HAL camera rig, shares one source) plus the planar yaw↔quaternion helpers formerly duplicated across HAL/world-state/sim/runner. Import-on-demand (not re-exported by openral_core.__init__) so the schemas stay numpy-free on the fast CLI path. openral_world_state.geometry re-exports every symbol for back-compat.
ViewAxis(TypeAlias =Literal["-z", "+z", "+x"]) — Camera forward-axis conventions:"-z"MuJoCo cameras,"+z"ROS optical frames (REP-103),"+x"body-frame forward (the approach-viewpoint convention).look_at_quat_wxyz(eye, target, *, up=(0,0,1), view_axis="-z") -> tuple[float, float, float, float]— Unit(w, x, y, z)quaternion orienting a camera ateyesoview_axispoints attarget. Degenerate fallbacks (never raises):target == eye→ MuJoCo straight-down flip for"-z"/ identity otherwise; gaze near-parallel toup→ +Y alternate up. Used by the sim scene composers and the HAL camera rig.compute_gaze_pose(camera_xyz, target_xyz, *, frame_id="map", up=(0,0,1), view_axis="+z") -> Pose6D— Full 6-DOF camera pose whose view axis hitstarget_xyz; therskill-moveit-multi-look_atrSkill's goal (defaults to the optical-frame"+z"convention).rotation_to_quat_wxyz(rot: 3x3) -> tuple[w, x, y, z]— Public matrix→quaternion conversion (Shepperd's method); used byLookAtRskillto re-express a gaze pose for the camera's mount link.yaw_to_quat_xyzw(yaw: float) -> tuple[x, y, z, w]— Unit quaternion (ROS order) forRz(yaw). Consumers:MobileBaseBridge._publish_odom(odometry + TF), spatial-memory approach viewpoints.yaw_to_quat_wxyz(yaw: float) -> tuple[w, x, y, z]— Same rotation in MuJoCo order. Consumer: the so101_box MJCF composer's base re-anchor.quat_xyzw_to_yaw(x, y, z, w) -> float— Planar yaw (rad, CCW about +Z, in[-pi, pi]); ZYX extraction reduced to the yaw term, roll/pitch ignored. Consumers:OccupancyGridIndex.from_msg,robot_pose_from_transform(slam bridge).
python/core/src/openral_core/exceptions.py
openral exception hierarchy — use these, do not invent new base classes.
class ROSError(Exception)— Base class for all OpenRAL errors. (L41)class ROSConfigError(ROSError)— Bad manifest, missing weights, invalid YAML/URDF. (L48)class ROSCapabilityMismatch(ROSError)— Skill requires a capability the robot lacks. (L52)class ROSRuntimeError(ROSError)— General runtime failure. (L59)class ROSQuantizationError(ROSRuntimeError)— Quantization failed. (L63)class ROSGPUMemoryError(ROSRuntimeError)— Out of GPU memory. (L67)class ROSSafetyViolation(ROSError)— Safety constraint violated. Never silently caught. (L74)class ROSWorkspaceViolation(ROSSafetyViolation)— Action outside allowed workspace. (L82)class ROSForceLimitExceeded(ROSSafetyViolation)— Contact force exceeds limit. (L86)class ROSCollisionImminent(ROSSafetyViolation)— Proposed motion would self-collide or strike a world obstacle. (L90)class ROSEStopRequested(ROSSafetyViolation)— Emergency stop requested. (L101)class ROSPerceptionStale(ROSError)— Sensor reading exceeds staleness deadline. (L108)class ROSObjectNotInMemory(ROSPerceptionStale)— A scene-graph query (RecallObjectQuery/ResolvePlaceQuery) matched no node or only stale nodes; caller degrades to "unknown" (may trigger active search), never fabricates a pose.class ROSPlanningError(ROSError)— Reasoner failed to produce valid plan. (L125)class ROSReasonerInvalidPlan(ROSPlanningError)— LLM returned invalid plan. (L129)class ROSFleetError(ROSError)— Fleet-level / dispatch error. (L136)class ROSDispatchUnavailable(ROSFleetError)— No dispatcher available. (L140)class ROSRskillGoalSatisfied(ROSError)— Typed control-flow completion signal raised byROSActionRskill._step_implonce a wrapped-ROS rSkill (kind: ros_action / ros_service) has emitted its last waypoint (trajectory mode) or finished awaiting the wrapped action's result (result-only mode). Caught only at therskill_runner_nodeexecute-callback boundary; the runner closes the goal withsuccess=True. NOT an error — inheritsROSErroronly to stay inside the OpenRAL exception surface. (L151)class ROSDeadlineMissed(ROSFleetError)— Cloud RTT exceeded skill deadline. (L144)