Skip to content

Layer 0 — Core Schemas & Exceptions

Part of the OpenRAL public-symbol inventory. Hand-curated; (LNN) markers are refreshed by tools/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, DRONE
  • class JointType(str, Enum) — URDF joint type. (L64) REVOLUTE, PRISMATIC, CONTINUOUS, FIXED, FLOATING, PLANAR
  • class ClockOrigin(str, Enum) — Authoritative source for OpenRAL stamp_ns values. /clock is a ROS projection, not an origin. (L75) HOST_WALL, SIMULATION, HARDWARE_SYNCED
  • class ClockEpoch(str, Enum) — Epoch that a stamp_ns value is measured from. (L87) UNIX, SIMULATION_ELAPSED, HARDWARE
  • JointRole: TypeAlias = Literal[…] — Structural classification of a JointSpec. (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_MODE
  • const 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; matches Action.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, BATTERY
  • class Hand(str, Enum) — End-effector laterality. (L284) LEFT, RIGHT, NA
  • class StateRepresentation(str, Enum) — State vector format. (L905) JOINT_POSITIONS, EEF_POS_AXISANGLE, EEF_POS_EULER, EEF_POS_QUAT, EEF_POS_AXISANGLE_GRIPPER
  • class 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's JointState/Action contract is radians; the skill_runner converts deg↔rad at the policy boundary when a manifest declares DEGREES. Declared on ActionContract.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 hard ROSConfigError, not a silent default. (L930) JOINT_POSITIONS, JOINT_VELOCITIES, DELTA_EE_6D_PLUS_GRIPPER, DELTA_EE_6D, CARTESIAN_POSE
  • class RSkillAction(str, Enum) — Closed vocabulary of high-level action verbs an rSkill can perform; declared on RSkillManifest.actions and 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 (for kind: "detector" rSkills); scene VLM: QUERY (for kind: "vlm" rSkills); reward monitor: MONITOR (for kind: "reward" rSkills); playbook decision procedure: PLAN (for kind: "playbook" rSkills). New entries are additive.
  • class QuantizationDtype(str, Enum) — Weight numeric format. (L2634) FP32, FP16, BF16, INT8, INT4, FP4_NVFP4
  • class QuantizationBackend(str, Enum) — Inference backend. (L2668) PYTORCH, ONNX, TENSORRT, GGUF, MLX
  • class RSkillState(str, Enum) — Skill lifecycle. (L2740) UNCONFIGURED, INACTIVE, ACTIVE, FINALIZED, ERROR
  • class 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, JAX
  • class 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_coeffs
  • scale_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 at scene.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. Returns base unchanged when the target equals its resolution; raises ValueError on 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, and hardware_synced(clock_id, epoch=UNIX) for future synchronized controller/PTP clocks. Validator rejects impossible origin/epoch pairs and competing hardware /clock publishers. (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>) for deploy 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's intrinsics as 2·atan(h/2·fy)). extra="forbid". Replaces per-robot scene_defaults.composition for camera-only deploy twins.
  • class SensorSpec(BaseModel) — Generalizable sensor descriptor (all modalities). catalog_id: str | None records the openral_sensors catalog 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, mirrors JointSpec.sim_joint_name) carries the MJCF camera name when it differs from the sensor nameMujocoArmHAL.read_images renders sim_camera_name or name (used when a robot's canonical sensor name differs from the upstream MJCF camera name). sim_placement: CameraSimPlacement | None carries 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 | None is the runtime counterpart — how deploy run opens the physical device; host-specific, unset in committed manifests, filled by openral 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, metadata
  • class SensorBundle(BaseModel) — Multi-modal sensor group. (L517) fields: bundle_name, sensors, sync, sync_tolerance_ms
  • class 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_rpy are the fixed parent-link→joint transform (URDF <joint><origin>); with axis_xyz they let the kernel compute forward kinematics for self-collision. Default zeros; populated by the offline lowering tool only for robots that enable collision checking. role is a JointRole literal that downstream code reads to identify gripper / base / arm DoFs structurally instead of substring-matching the joint name (default "unknown"). sim_joint_name carries the MJCF/MuJoCo joint name when it differs from the logical name — used by openral_sim.backends.robocasa.{synthesize_laser_scan_2d,read_panda_mobile_base_velocity}, SimSensorBridge._compute_scan_ranges, and openral_hal.sim_attached.SimAttachedHAL.read_state to look up mj_name2id without hardcoding robosuite/robocasa naming. None = "MJCF name matches name" (the common case for fixed-base manipulators). Population contract: a robot needs sim_joint_name populated only when (a) its sim adapter does mj_name2id on a joint name, AND (b) the loaded MJCF differs from name. Today: panda_mobile (robocasa auto-prefixes with mobilebase0_* + robot0_*). LIBERO / ManiSkill3 / aloha / so100_robosuite / ur5e / widowx preserve URDF names — populating sim_joint_name for those is a no-op. openarm_robosuite does its own hardcoded mj_name2id lookups in env.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. actuated defaults to True; set False for 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 by openral_detect._enrich_compute from GPU probe results; attached to RobotDescription.compute_edge, compute_local, or compute_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 — endpoint and network_latency_ms are None for edge/local. nvmm_available is probed on all tiers (returns False gracefully when absent). num_gpus > 1 captures cloud multi-GPU pods. GPU/runtime fields moved here from RobotCapabilities to 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, and gpu_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; property is_local. REASONER_MODELS is 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 named OPENRAL_REASONER_ENDPOINT implies 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 from openral_reasoner.tool_use (which re-exports for back-compat) so the factory and openral doctor consume 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 to ComputeSpec (attached at RobotDescription.compute). has_vision_slam gates the camera-based cuVSLAM+nvblox SLAM backend for lidar-less robots; independent of has_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_180
  • class ActionSpec(BaseModel) — VLA action config. (L1023) fields: dim, representation, control_freq_hz, chunk_size
  • class 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=True slots drop their slice silently.
  • class ActionContract(BaseModel) — Per-rSkill action-vector contract. fields: dim, representation, slots, joint_units, cartesian_delta_scale. When slots is set, every index in [0, dim) is covered by exactly one ActionSlot (@model_validator rejects gaps + overlaps + over-range slots). When slots is None, the legacy single-Action JOINT_POSITION path applies (back-compat). Manifests carrying slots are exempt from the dim <= 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,...] | None carries 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 a ControlMode for task-space views (DRAFT): JOINT, CARTESIAN, GRIPPER, BASE, DEX_HAND, COMPOSITE. Mapped exhaustively from every ControlMode by 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_validator enforces family == _FAMILY_FOR_MODE[control_mode]. target names the EE for cartesian/gripper/dex modes, None otherwise. The gripper is an explicit 1-D segment — answers "is the gripper a dimension?" structurally.
  • class TaskSpaceMatch(BaseModel) — Result of task_space_compatible. fields: ok, reasons. reasons is empty iff ok.
  • class TaskSpace(BaseModel) — Layer-neutral view of an action interface as ordered TaskSpaceSegments (DRAFT). fields: segments (non-empty), representation. Props: total_dim (sum of widths), control_modes (set). @classmethod from_action_contract(action, robot) expands slots → segments, else expands representation via canonical_slots_for_representation, else falls back to one whole-vector JOINT_POSITION segment. 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 coarse PhysicsBackend nor the individual scene). Declared once per family in SCENE_FAMILY_TASK_SPACE. runs_via_default_packers=False marks dedicated-controller adapters (RLBench cartesian-pose planner, RoboCasa-GR1 composite) — the scene-leg analogue of KNOWN_SIM_GAPS.
  • class SphereShape(BaseModel) — Sphere collision primitive; discriminator shape="sphere", field radius_m (>0). (L1371)
  • class CapsuleShape(BaseModel) — Capsule collision primitive (segment along local +Z swept by a radius); discriminator shape="capsule", fields radius_m (>0), length_m (>=0). (L1389)
  • class BoxShape(BaseModel) — Oriented box (OBB) collision primitive for blocky links (e.g. the SO-ARM base housing); discriminator shape="box", field half_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 (discriminator shape); 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; fields link_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 by openral detect when a Jetson is found; falls back to compute_local in 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 via openral detect --target cloud). schema_version: Literal["0.1"] — on-disk schema version; default "0.1" (three-slot compute layout). assets: AssetRefs is the single URDF/MJCF/SRDF reference block (default empty) — it replaces the former scattered urdf_path, urdf_root_frame, static_base_to_urdf_root_xyz_rpy, and srdf_path fields (and SimDescription.mjcf_uri); refs share the openral_core.assets.resolve_asset grammar and the URDF's robot_state_publisher wiring (root_frame + base_to_root_xyz_rpy) lives on assets.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/None and joints stays normative for the kinematic chain (URDF/SRDF add geometry + ACM only — the SRDF disable_collisions block named by assets.srdf is the canonical source for allowed_collision_pairs on real robots). footprint_radius: float | None (>0) + base_kinematics: Literal["differential","holonomic","omni","ackermann"] | None drive the generic Nav2 bringup (see nav2_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 the footprint_radius circle.
  • 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 have position_limits, velocity_limit, and effort_limit set. Raises ROSConfigError listing every missing field at once — used by sim_e2e.launch.py so a misshapen manifest fails at launch-parse time, not later in the HAL's first actuation tick. Pure validation; for synthesis of the kernel EnvelopeIntersection use openral_safety.envelope_loader.compute_intersection(robot, skill=None).
  • lidar_sensor(self) -> SensorSpec | None [@property] — First declared lidar_2d SensorSpec (beam count n_channels, range_min_m/range_max_m, rate_hz), or None. Single source of truth for the synthetic /scan envelope: openral deploy sim (deploy_sim._scan_params_from_description) forwards it as HAL scan_* ROS params and SimSensorBridge (which owns /scan for 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 from footprint_radius (→ robot_radius and costmap inflation_radius = footprint_radius + NAV2_INFLATION_CLEARANCE_M, kept ≥ the inscribed/circumscribed radius Nav2 derives from the footprint) + base_kinematics (→ MPPI motion_model). {} for fixed-base arms. nav2.launch.py RewrittenYaml-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) — How MujocoArmHAL reports 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) — How MujocoArmHAL maps an Action's gripper value to ctrl. (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_index
  • class UrdfAsset(BaseModel) — A URDF asset reference plus its robot_state_publisher wiring. fields: ref: str (validated against the resolve_asset scheme grammar), root_frame: str | None (URDF root link when it differs from base_frame), base_to_root_xyz_rpy: tuple[float×6] | None (static base_frameroot_frame transform [x,y,z,roll,pitch,yaw], metres+radians)
  • class AssetRefs(BaseModel) — Unified RobotDescription.assets block: 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 default None)
  • class SimDescription(BaseModel) — Optional RobotDescription.sim block holding MuJoCo joint↔qpos/qvel/actuator wiring for MujocoArmHAL.from_description; the MJCF itself is named by RobotDescription.assets.mjcf. fields: floating_base, joint_qpos_addr, joint_qvel_addr, actuator_index, grippers, settle_steps_default, keyframe_index, seed_ctrl_from_qpos
  • class HalEntrypoints(BaseModel)RobotDescription.hal block: the robot's simulation + real-hardware HAL import strings, resolved by openral_hal.build_hal. fields: sim: str | None (null → derive MujocoArmHAL.from_description when a sim: block exists), real: str | None (null → simulation-only robot), parameters: HalParameters (per-robot HAL construction defaults)
  • class HalParameters(BaseModel)RobotDescription.hal.parameters block: per-robot HAL construction defaults (serial port, robot_ip, …) merged into the constructor by openral_hal.build_hal (explicit transport wins; 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 in openral_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-driven ManifestHALLifecycleNode._create_hal calls the composer and threads the composed MJCF in as the HAL's mjcf_path — replaced openarm's bespoke _create_hal tabletop splicing. fields: composer: str, params: dict[str, object] fields: top_camera: TopCameraDefaults | None = None
  • First consumer is the openarm_tabletop_pnp MJCF 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_ns
  • class Pose6D(BaseModel) — 6D pose (position + xyzw quaternion). (L2006) fields: xyz, quat_xyzw, frame_id
  • class DetectedObject(BaseModel) — Object detection. (L2020) fields: label, confidence, pose, bbox_3d, track_id
  • class WorldCollisionPrimitive(BaseModel) — A placed convex obstacle in the world (world-frame analogue of LinkCollisionGeometry); fields shape: CollisionShape, pose: Pose6D, object_id: str | None. (L2038)
  • class OccupancyGridRef(BaseModel) — Reference to a 2D occupancy grid for mobile-base world-collision (mirrors nav_msgs/OccupancyGrid metadata); fields frame_id, resolution_m (>0), width (>=0), height (>=0), origin: Pose6D, data_topic. (L2062)
  • class WorldState(BaseModel) — Snapshot consumed by Reasoner and Skills; optional policy_state carries 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_gridlist[WorldCollisionPrimitive] (default empty) + OccupancyGridRef | None (default None): 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_framesdict[str, SensorFrame] | None. Optional in-process frame carrier for no-ROS deployments; default None keeps the existing images: 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 of DetectedObject for kind=OBJECT. fields node_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_contents requires is_container.
  • class SpatialEdge(BaseModel) — Directed relation; fields src, dst, kind: SpatialRelationKind.
  • class SceneGraph(BaseModel) — Persistent scene-graph memory; fields schema_version="0.1", nodes: list[SpatialNode], edges: list[SpatialEdge]. Validators: unique node_id; every edge references an existing node.
  • class RecallObjectQuery(BaseModel) — Read-only object recall; fields text, label, near: Pose6D | None, max_age_ns, limit. Validator: at least one of text / label non-empty.
  • class ApproachViewpoint(BaseModel) — Camera-facing standoff goal; fields pose: Pose6D, standoff_m (>0), camera_frame_id.
  • class RecallObjectMatch(BaseModel) — One ranked recall; fields node_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 raises ROSObjectNotInMemory).
  • class ResolvePlaceQuery(BaseModel) — Resolve a place/room/agent reference; fields reference, kind: SpatialNodeKind | None.
  • class ResolvePlaceResult(BaseModel) — fields node_id, goal: Pose6D, path_node_ids: list[str] (a traversable_to path).
  • class Action(BaseModel) — Action step or chunk produced by a Skill. tick_index preserves the shared inference-tick identity of multi-slot actions across the safety wire for atomic sim commit; optional cartesian_delta_scale carries 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_overrides
  • class QuantizationConfig(BaseModel) — Quantization recipe. (L644) fields: dtype, backend, per_channel, calibration_dataset, extra
  • class DeviceInfo(BaseModel) — Host compute snapshot. (L668) fields: device_str, gpu_memory_bytes, cuda_compute_capability, cpu_count, arch
  • class 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 one execute_rskill goal; the skill_runner resolves a dispatched deadline_s=0 to 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, count
  • class ControlModeSemantics(BaseModel) — Action-space semantics on each ActuatorRequirement (rSkill self-containment audit, Gap 2). (L1180) fields: mode: Literal["absolute","delta"], gripper_convention, joint_order, reference_frame
  • Cross-validator on ActuatorRequirement: gripper kinds REQUIRE gripper_convention; cartesian kinds REQUIRE reference_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_semantics
  • kind reuses ControlMode; n_dof / vla_action_key auto-fill from the robot YAML for canonical embodiments, REQUIRED on the manifest for the "custom" hatch.
  • control_mode_semantics is 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 lerobot PolicyProcessorPipeline artefact 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 when RSkillManifest.kind in {"ros_action", "ros_service"}; forbidden otherwise. (L2783) fields: package, interface_type, interface_name, result_trajectory_field, default_goal_json, ros_dependencies
  • result_trajectory_field is None → result-only mode (Nav2 shape); set → trajectory mode (MoveIt shape, adapter replays one waypoint per step()).
  • default_goal_json validator round-trips the literal through json.loads and rejects non-dict payloads.
  • class DetectorEngine(str, Enum) — Backend selector for kind: "detector" rSkills (2026-06-12 amendment): RTDETR_ONNX = "rtdetr_onnx", VLM_SIDECAR = "vlm_sidecar", ZEROSHOT_HF = "zeroshot_hf". Set on DetectorContract.engine to disambiguate backends that share a runtime (the VLM sidecar and the in-process Transformers zero-shot detector are both runtime: pytorch); None keeps the legacy runtime-keyed dispatch.
  • class DetectorMode(str, Enum) — Invocation mode of a kind: "detector" rSkill, orthogonal to DetectorEngine: CONTINUOUS = "continuous" (always-on background producer → WorldState.detected_objects; reasoner reads it passively, never prompts it; not ExecuteRskill-dispatchable) and ON_DEMAND = "on_demand" (prompted open-vocab locator surfaced via the locate_in_view tool). 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 for kind: "detector" rSkills. Required when RSkillManifest.kind == "detector"; forbidden otherwise. Frozen, extra="forbid". Fields: labels, input_size, score_threshold, engine, mode, and max_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), None keeps 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 (default continuous; invocation mode).
  • class RewardContract(BaseModel) — Manifest contract for kind: "reward" rSkills (Robometer-4B reward monitor). Required when RSkillManifest.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") enforces check_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 for kind: "playbook" rSkills (human-authored S2 decision procedure). Required when RSkillManifest.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 the ReasonerToolCall discriminators 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.yaml manifest (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's task.id against it (openral_sim.benchmark.check_benchmark_task_compatibility) — a non-empty list that doesn't cover the scene raises ROSCapabilityMismatch, 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: scene backend_options.control_mode > manifest sim_env_control_mode > "relative"), letting an absolute-control policy (xVLA) run on the canonical libero_spatial.yaml without a duplicate per-policy scene. policy_extras: dict[str, object] is copied into VLASpec.extra during CLI/test composition so adapter-owned knobs (e.g. OpenVLA generate_action_verl, torch seed, action-scale) are traceable in the manifest without growing top-level schema fields. Amendment: action_contract (mirrors state_contract) declares the per-checkpoint action dim consumed by the dataset bridge. Amendment: description is 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: RSkillKind is REQUIRED (no default); model_family and weights_uri became optional and are gated on kind == "vla"; new optional ros_integration: RosIntegration | None block. Amendment: new kind: "detector" value + optional detector: DetectorContract | None field (required iff kind == "detector"); actuators_required constraint relaxed from global min_length=1 to per-kind enforcement in _check_kind_consistency (detectors have no actuators). Amendment: new kind: "vlm" value for video-language scene-understanding models (role: s2; no actuators, no action/state contract, no detector block); RSkillAction gains QUERY = "query". Perception amendment: embodiment_tags constraint relaxed from global min_length=1 to per-kind enforcement in _check_embodiment_tags_present — perception kinds (detector/vlm/reward, _PERCEPTION_KINDS) are embodiment-agnostic and ship empty embodiment_tags (match-any); every other kind still requires ≥1 tag. Amendment: new kind: "reward" value for robotic reward/progress monitors (role: s2; no actuators, no action/state contract) + optional reward: RewardContract | None field (required iff kind == "reward"); RSkillAction gains MONITOR = "monitor". Amendment: new kind: "playbook" value for human-authored S2 decision procedures (Markdown SOP the reasoner reads, not code it executes) + optional playbook: PlaybookContract | None field (required iff kind == "playbook"); RSkillAction gains PLAN = "plan". The _check_kind_consistency playbook branch requires role == "s2", RSkillAction.PLAN ∈ actions, chunk_size == 1, and empty actuators_required; forbids model_family/weights_uri/min_vram_gb/detector/reward/ros_integration/processors/image_preprocessing/action_contract/state_contract/n_action_steps/starting_pose. Amendment: new optional reward_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_consistency guard forbids it on any kind other than "vla". None defers to the deployment default reward model (it does not mean "run without reward"). Amendment: new optional default_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, but ExecuteRskill.prompt was previously the only source, so a hand-dispatched goal with no prompt fed the policy "". _build_runtime_skill_from_manifest falls back to this field when the goal prompt is empty (logging rskill_runner.default_prompt_applied). Distinct from description, 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 an rskill.yaml.
  • active_min_vram_gb(self) -> float | None — Declared VRAM (GB) for this skill at its active quantization dtype (min_vram_gb[quantization.dtype]), or None when undeclared. Consumed by assert_vla_reward_fits.
  • is_commercial_use_allowed: bool [@property] — Derived from license: True for apache-2.0/mit/bsd, False otherwise (incl. unknown). Replaces V0's free-field commercial_use_allowed.
  • is_scaffold_placeholder: bool [@property] — True when name/weights_uri/source_repo still carry an unresolved RSKILL_TEMPLATE_SENTINELS sentinel — i.e. this is the rskills/template/ scaffold the rskills/*/rskill.yaml glob 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 every actuators_required entry must carry both n_dof and vla_action_key.
  • rSkill self-containment audit cross-validator: processors REQUIRED when model_family in {smolvla, pi05, xvla, diffusion, rldx}; only act may omit it (legacy norm-stats-in-safetensors path).
  • Cross-validator (_check_kind_consistency): kind == "vla" requires model_family + weights_uri + ≥1 actuators_required, forbids ros_integration + detector. kind in {"ros_action","ros_service"} requires ros_integration + ≥1 actuators_required, forbids model_family/weights_uri/processors/state_contract/action_contract/n_action_steps/image_preprocessing/starting_pose/detector, pins chunk_size == 1. kind == "detector" requires detector + weights_uri, forbids model_family/ros_integration/action_contract/state_contract/processors/n_action_steps/starting_pose, requires empty actuators_required. kind == "wam" validates schema-side; the loader rejects it at resolve time.
  • The historical policy_id field was removed in favour of dispatching on model_family directly.
  • RSKILL_TEMPLATE_SENTINELS: tuple[str, ...] = ("TEMPLATE_ORG", "TEMPLATE_ID") — Canonical unresolved-scaffold sentinels the openral rskill new scaffolder rewrites. One definition shared by the reasoner palette gate (RSkillManifest.is_scaffold_placeholder) and the publish gate (_rskill_doc_validator.PLACEHOLDER_SENTINELS composes 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 when text carries an RSKILL_TEMPLATE_SENTINELS substring; None/empty → False. The text-level primitive behind is_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's active_min_vram_gb(); raises ROSConfigError if either size is undeclared (a required co-residency can't be verified), ROSGPUMemoryError if vla + 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 plain split("-") 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), or rskill-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 models omdet_turbo, rtdetr_coco_r18, robometer_4b, moveit, nav2). Distinct from ModelFamily (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] — the EmbodimentTag values (which now include any and the multi aggregate for a skill declaring >1 concrete robot). multi is a member of the EmbodimentTag Literal so the validator accepts it; manifests normally list the specific robots.
  • CANONICAL_QUANT_TOKENS: frozenset[str]{fp32, fp16, bf16, int8, nf4} (weight-bearing kinds; int4nf4, 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 against evaluated_tasks (that collapses e.g. so101 pen vs pick_place_pen). When model_family is given, the <model> token must be in _MODEL_FAMILY_ALLOWED_TOKENS[family] (a smolvla checkpoint can't be labelled pi05). (L6150)
  • def expected_repo_name(manifest: RSkillManifest) -> str — The canonical name suggestion (printed on a mismatch, written by --fix-name); always satisfies repo_name_is_canonical for the manifest's kind. <model> from model_family (via _MODEL_FAMILY_TO_TOKEN) or a name-prefix match for tool skills; <robot> = multi for >1 concrete tag else the tag else any; ROS wrappers omit <quant>, else <quant> from quantization.dtype (default fp32); <task> a defaultevaluated_tasks[0] scene-family, else first benchmarks, else the name-tail author slug (strips model/robot prefixes + a trailing quant-like token, so it's idempotent and recovers locator/pen), else scenes[0], else main. Owner preserved from name (default OpenRAL). Raises ValueError only when no canonical <model> token can be determined. (L6224)
  • EmbodimentTag (TypeAlias = Literal[...]) — Closed canonical robot embodiments matching robots/*/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_present now 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 on StateContractBindings. libero_eef8d is 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). (The pi0_16d / eef_pose_7d / base_pose_7d robocasa sim-observation layouts were removed — no state-adapter assembler existed; recreate alongside an assembler when next needed.)
  • WRAPPED_TASK_SPACE_LAYOUTS: frozenset[StateLayout] — Subset of StateLayout covering Cartesian/FK-derived composites: {rc365, human300_16d, libero_eef8d}. These layouts REQUIRE StateContract.bindings; the cross-validator on StateContract enforces this at manifest load (human300_16d/rc365 require eef_frame+base_frame; libero_eef8d requires eef_frame+gripper_qpos_joints — world-frame absolute EE pose, so no base_frame). Joint-space layouts (smolvla_9d, gr1, simpler_*) are excluded — they're served verbatim from raw JointState.position.
  • StateContractBindings (Pydantic model) — Per-robot source bindings for an rSkill's state_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 to ControlModeSemantics on the action side. Required when StateContract.layout is in WRAPPED_TASK_SPACE_LAYOUTS, forbidden otherwise.
  • BenchmarkName (TypeAlias = Literal[...]) — Closed canonical benchmark ids matching benchmarks/*.yaml suites (plus retained aloha_insertion/aloha_transfer_cube task-level ids cited by the act-aloha* manifests after the two suites were unified into aloha.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 when RSkillManifest.kind == "vla". gr00t (NVIDIA Isaac GR00T) runs out-of-process via a ZMQ sidecar, reusing the rldx adapter. 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's unnorm_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-D BODY_TWIST base 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 through ROSActionRskill; "wam" is reserved (loader rejects at resolve time); "detector" is a perception producer that runs an exported ONNX/TRT detection model and publishes ObjectsMetadata — emits no Action; "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-only query_scene tool, not ExecuteRskill); "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, or Action, 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, status
  • class RSkillEvalBenchmark(BaseModel) — Suite identity for a benchmark block. (L1023) fields: name, dataset, protocol, robot, simulator
  • class RSkillEvalResult(BaseModel) — On-disk shape of rskills/<id>/eval/*.json. Carries an optional trace_id: str | None (32-hex) populated by openral benchmark run for offline cross-reference into the OTel trace tree. (L1042) fields: schema_version, source, benchmark, eval_config, results, baselines
  • from_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_options
  • class RoboCasaBackendOptions(BaseModel) — Typed validator for SceneSpec.backend_options under the RoboCasa backend. Prebuilt-vs-procedural XOR enforced by a model_validator; state_layout includes 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_layout
  • class TaskSpec(BaseModel) — What the robot must achieve. (L4500) fields: id, scene_id, instruction, max_steps: int | None, success_key: str | None, metadata
  • class VLASpec(BaseModel) — Policy / brain declaration. (L1029) fields: id, weights_uri, device, runtime, quantization, deterministic, extra
  • class SimEnvironment(BaseModel)Runtime (robot × scene × task × VLA) tuple. Composed at the CLI from a SimScene or BenchmarkScene YAML plus an RSkillManifest (--rskill); never loaded from YAML directly. (L2343) fields: robot_id, scene, task, vla, base_pose, seed, n_episodes, record_video, save_dir, metadata
  • base_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 validation task.scene_id == scene.id. (L2389)
  • class BenchmarkMetadata(BaseModel) — Provenance block required on every BenchmarkScene; fields: paper: str, honest_scope: str, optional display_name: str | None, optional simulator: str | None. The two optional fields become RSkillEvalResult.benchmark.name / .simulator when 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 leg deploy sim / deploy run can 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 in resolve_launch_invocation: explicit CLI flag > scene runtime: > auto/built-in default. Host-operational knobs (dashboard, foxglove, ports, dataset recording, --initial-task) stay CLI-only. Relative *_manifest/*_onnx paths 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"] | None picks the cuVSLAM engine the visual backend 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] | None names the (left, right) rig cameras (each → /openral/cameras/<name>/image + /camera_info, forwarded to whichever impl is composed), validated distinct; slam_mono_camera: str | None names ONE camera for the mono RGBD path (pycuvslam only — cuVSLAM OdometryMode.RGBD fused with DA3 metric depth; the launch auto-composes the depth provider + nvblox), validated non-empty and mutually exclusive with slam_stereo_cameras; slam_depth_sidecar_autostart: bool = True has the launch spawn tools/da3_depth_sidecar.py (ZMQ :5771) alongside the mono graph (False = operator-run/shared sidecar; only meaningful with slam_mono_camera).
  • class DeployScene(BaseModel) — Unified deploy/workcell scene for openral deploy sim and openral deploy run; carries scene, robot_id, base_pose, deterministic startup seed, sim-only composition: SceneComposition | None, safety: SafetyEnvelope | None (tighten-only against the robot manifest), additive extra_allowed_collision_pairs: list[tuple[str, str]], sensors: list[SensorSpec], hal: HalParameters | None, memory_dir: str | None, and runtime: DeployRuntime | None. No tasks field or evaluation cadence; deploy goals come from the operator via --initial-task / /openral/prompt. Rejects legacy vla: blocks.
  • from_yaml(cls, path: str) -> Self [@classmethod] — Load and validate a scene YAML from disk; inherited (not overridden) by SimScene and BenchmarkScene, which validate against their own stricter schemas via cls. (L4734)
  • class SimScene(DeployScene) — Extends DeployScene with task, seed, n_episodes, record_video, save_dir, metadata; cross-validates task.scene_id == scene.id; accepted by openral sim run. Loads via the inherited DeployScene.from_yaml. (L4743)
  • class BenchmarkScene(SimScene) — Extends SimScene with required n_episodes, seed, and metadata: BenchmarkMetadata; also requires task.success_key and task.max_steps; consumed by openral benchmark. Loads via the inherited DeployScene.from_yaml (raises ValidationError if 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 onto BenchmarkScene; a later change then deleted the BenchmarkSpec wrapper entirely so a suite is now a bare list[BenchmarkScene]). (L1386) fields: n_episodes, seeds, success_key, max_steps, min_reps
  • model_post_init(_context: object) -> None — Cross-field validation: len(seeds) >= n_episodes and min_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) — How SensorFrame bytes are interpreted. (L557) BGR8, RGB8, MONO8, DEPTH16, JPEG, PNG, CUDA_NV12, CUDA_RGBA, RAWCUDA_NV12 is the Tegra NVMM handle layout, CUDA_RGBA the x86 DeepStream one
  • class 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 raw bytes or a base64-encoded str on 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) — Which SensorReader implementation a sensor uses. Includes galaxea_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, GSTREAMER
  • class DeadlineOverrunPolicy(str, Enum) — Behaviour when a tick exceeds 1 / rate_hz. (L1706) WARN, DROP, RAISE
  • class SensorReaderConfig(BaseModel) — Per-sensor reader backend + optional ROS-tee. (L1720)
  • class SensorDeployBinding(BaseModel) — Optional SensorSpec.deploy_binding payload — reader backend + backend_params (device/fps) + max_age_ms that lets openral deploy run open the physical camera; the runtime counterpart of sim_placement. fields: sensor_id, backend, backend_params, max_age_ms, publish_to_ros, publish_topic, publish_rate_hz
  • model_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, params
  • class TickResult(BaseModel) — One tick's record returned by InferenceRunner.tick. v2 (amendment 1) adds five optional sim-only fields (step_idx, episode_idx, reward, terminated, truncated) and an optional trace_context: str | None (full W3C traceparent for the tick's rskill.tick span). All optional fields default to None; hardware ticks that don't carry sim metadata or a live trace context serialise byte-identically to v1 under exclude_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, truncated
  • class RunResult(BaseModel) — Aggregated summary returned by InferenceRunner.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"; fields operation, deadline_s, elapsed_s.
  • class ForceEvidence (L3092) — kind="force"; fields joint_or_ee, measured_n, limit_n.
  • class WorkspaceEvidence (L3108) — kind="workspace"; fields ee_name, measured_xyz, box_min, box_max.
  • class PerceptionStaleEvidence (L3126) — kind="perception"; fields sensor_id, staleness_ms, threshold_ms.
  • class CriticEvidence (L3142) — kind="critic"; fields critic_id, score, threshold.
  • class ControllerEvidence (L3158) — kind="controller"; fields controller_name, state, detail.
  • class SelfVerifyEvidence (L3174) — kind="selfverify"; fields check, expected, observed.
  • class HumanEvidence (L3190) — kind="human"; fields actor, reason.
  • class WamEvidence (L3204) — kind="wam"; fields horizon, discrepancy, wam_id.
  • class ReasonerTimeoutEvidence (L3220) — kind="reasoner_timeout"; fields model, deadline_s, elapsed_s.
  • class CollisionEvidence (L4694) — kind="collision"; fields collision_kind: Literal["self"|"world"], link_a, link_b_or_object, horizon_step, min_distance_m. Maps to FailureTrigger.KIND_COLLISION = 10.
  • class SuppressedSummaryEvidence (L3236) — kind="suppressed_summary"; fields window_s, kinds: list[int], severities: list[int], counts: list[int]. Model-validator enforces parallel arrays (raises ROSConfigError).
  • 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; carries sensor_id: str. (L3304)
  • class ObjectDetection2D(BaseModel) (L3323) — single 2D detection inside ObjectsMetadata; fields label, confidence, bbox_xyxy, det_id: int = -1. det_id is a stable per-detector/per-camera identity assigned at detection time by DetectionTracker2D, so an object can be enumerated + de-duplicated without the 3D lift (-1 = untracked). The lift propagates it into DetectedObject.track_id → one id across the 2D in_view line and 3D scene_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 of aabb_iou_3d.
  • class DetectionTracker2D(*, iou_threshold=0.3, max_misses=3) (openral_core.detection_tracker) — stateful camera-space 2D-IoU tracker (the 2D analog of ObjectMemory). assign(detections) -> list[ObjectDetection2D] stamps a stable det_id per 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 for max_misses frames is retired. Pure, ROS-free; one instance per camera, run on the detector's continuous leg.
  • class MotionMetadata (L3344) — kind="motion"; fields magnitude, threshold, region_bbox.
  • class ObjectsMetadata (L5070) — kind="objects"; fields detections: list[ObjectDetection2D], model_id, frame_width: int (>0), frame_height: int (>0). frame_width/frame_height were added to make the pixel space of bbox_xyxy explicit so the VoxelFrustumLifter can scale to the intrinsics resolution (CLAUDE.md §1.4). Producers (ObjectsDetector, NvmmObjectsDetector) populate both at detect-time.
  • class OcrMetadata (L3386) — kind="ocr"; fields text, confidence, region_bbox.
  • class SceneChangeMetadata (L3404) — kind="scene_change"; fields distance, 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 optional rationale: str. (L3455)
  • class ExecuteRskillTool (L7384) — tool="execute_rskill"; fields rskill_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's default_patience_s), progress_tolerance: float | None (default None; ge=0.0; overrides the reward model's plateau_tolerance for a noisy critic — None uses the model default).
  • class ReloadGstPipelineTool (L3508) — tool="reload_gst_pipeline"; fields sensor_id, pipeline_yaml.
  • class LifecycleTransitionTool (L3532) — tool="lifecycle_transition"; fields node, transition: Literal["configure"|"activate"|"deactivate"|"cleanup"]. shutdown is 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"; fields target_topic (must start with /), text, metadata_json. The reasoner node publishes on target_topic itself (per-topic publisher cache; /openral/prompt reuses the standing cascade publisher).
  • class WaitTooldeliberate no-op; tool="wait"; no fields beyond the base rationale. 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 RecallObjectToolread-only query; tool="recall_object"; fields query (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 ResolvePlaceToolread-only query; tool="resolve_place"; field reference ("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 LocateInViewToolread-only query; tool="locate_in_view"; fields query (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 (vs recall_object's remembered objects). No actuation authority — choosing a model does not grant it.
  • class QuerySceneToolread-only query; tool="query_scene"; fields question (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 from locate_in_view (localization → boxes): returns free text. No actuation authority.
  • class QueryTaskProgressToolread-only query; tool="query_task_progress"; fields window_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 from query_scene (free text): returns normalized scalars. No actuation authority.
  • MemorySection: TypeAlias = Literal[...] — the five fixed sections of the self-maintained MEMORY.md core: home_map, preferences, lessons, object_locations, open_tasks.
  • class MemoryWriteToolwrite; the reasoner's first write-capable variant; tool="memory_write"; fields op (add/update/supersede/delete), section: MemorySection, content (required unless delete — validated), importance (0–1, default 0.5), target (required for update/supersede/delete — validated). Edits the advisory MEMORY.md via an explicit op (Mem0 ADD/UPDATE/DELETE + Zep supersession); writes the memory file only — no actuation authority.
  • class MemorySearchToolread-only; tool="memory_search"; fields query (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 when text targets 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 by GroundedSubtask'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; fields object_ref: str (min_length=1; the single concrete object/place) and text: str (min_length=1; the instruction handed to the skill / VLA prompt). A @model_validator(mode="after") forbids a collective object_ref or text (is_collective_target) and requires text to name object_ref — so "the first batch of objects" is not a representable value. render() -> str returns the trimmed text.
  • class DecomposeMissionTooltask-ledger write (issue #123); tool="decompose_mission"; fields subtasks: 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 its MissionState task text. The typed path for the decompose-mission playbook to write the deterministic MissionState: empty target_task_id → populate/replace the whole queue (refining the single-task seed); set → flat-splice that blocked task (subdivide_active, bounded by DEFAULT_MAX_SUBDIVIDE_DEPTH). Edits the S2 task ledger only — no actuation authority.
  • ReasonerToolCall: TypeAlias — Discriminated union over the thirteen variants above (four actuation/effect + the wait no-op + four read-only query + the two memory tools, one write + one read + the DecomposeMissionTool task-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 declared ActionRepresentation to the set of ControlModes 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 of ControlModes the default sim HAL action-packers can execute (amended 2026-06-04), and the single source of truth for the reasoner's hal_mode == "sim" deploy-path palette gate: {JOINT_POSITION, JOINT_VELOCITY, CARTESIAN_DELTA, GRIPPER_POSITION, BODY_TWIST, COMPOSITE_MODE}. Mirrors CONTROL_MODE_TO_UINT8 as a shared core constant (core is a dep of both reasoner and HAL). Pinned in both directions to the packers in python/hal/src/openral_hal/sim_attached.py (pack_action_for_env, SimAttachedHAL._pack_with_composite_split, and the BODY_TWIST direct-qpos path) by tests/unit/test_sim_executable_modes_match_packers.py. Excludes JOINT_TORQUE / JOINT_TRAJECTORY / CARTESIAN_POSE / GRIPPER_BINARY (decoded but never pack-executed → would E-stop mid-run) and CARTESIAN_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 canonical ActionSlot layout the skill_runner dispatches a representation-only ActionContract through. Joint representations → None (caller keeps the legacy whole-vector JOINT_POSITION path). DELTA_EE_6D/CARTESIAN_POSE → one cartesian slot range=(0,5) addressed at the primary EE (description.end_effectors[0]); DELTA_EE_6D_PLUS_GRIPPER adds a GRIPPER_POSITION slot range=(6, dim-1). DELTA_EE_3D_PLUS_GRIPPER is the translation-only variant: a 3-wide cartesian-delta slot range=(0,2) + gripper slot range=(3, dim-1). EndEffectorSpec has no explicit tf-frame field, so the EE name is used as the slot frame. Raises ROSConfigError when the representation needs an EE but end_effectors is empty, or when dim is too small (DELTA_EE_6D/CARTESIAN_POSE need dim>=6; DELTA_EE_6D_PLUS_GRIPPER needs dim>=7; DELTA_EE_3D_PLUS_GRIPPER needs dim>=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_tags string match + raw dim equality + adapter magic). Mirrors the reasoner deploy gate _action_executable: in hal_mode="sim" each segment's control_mode must be in SIM_EXECUTABLE_CONTROL_MODES (robosuite OSC synthesises cartesian/gripper/base from joint commands); in "real" it must be advertised in robot.capabilities.supported_control_modes. Both modes also require cartesian/gripper/dex target EEs to exist on the robot and total joint-segment width ≤ robot joint count (physical facts). Returns ok + 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 recorded KNOWN_SIM_GAPS (rlbench cartesian-pose + gr1 29-DoF, both dedicated-controller paths).
  • def scene_family(task_id: str) -> str — Reduce an evaluated_tasks entry 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 by scene_family(evaluated_task). Adding an rSkill that declares a new task family with no entry trips test_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: every ControlMode the rSkill emits must be in the scene family's executed set, and (when the family fixes a width) total_dim must match. Pairs with task_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) — Load path as exactly expected; reject other tiers with ROSConfigError carrying a redirect message that names the right CLI command. mypy --strict overloads narrow the return to the requested concrete type. Centralises the rejection logic used by every scene-driven CLI loader (openral deploy simDeployScene, openral sim runSimScene, openral benchmark sceneBenchmarkScene) so a YAML one tier too rich is not silently widened (e.g. a BenchmarkScene YAML passed to openral sim run would otherwise drop n_episodes/metadata on the floor). Raises FileNotFoundError for a missing path and ROSConfigError for a non-mapping YAML root, an extra-key mismatch, or a tier mismatch.
  • def load_benchmark_suite(path: str) -> list[BenchmarkScene] (L148) — Load a bare list[BenchmarkScene] from benchmarks/<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 — call raise_on_invalid_suite separately so tests can construct invalid in-memory suites without touching disk. Raises FileNotFoundError for missing paths and ROSConfigError on every shape / validation failure (never a bare ValidationError).
  • def raise_on_invalid_suite(scenes: list[BenchmarkScene], *, suite_id: str) -> None (L223) — Free-function replacement for the deleted BenchmarkSpec.model_post_init. Enforces the five suite invariants: non-empty list, every scenes[i].robot_id non-None, every task.id unique within the suite, every scenes[i].robot_id / n_episodes / seed / metadata byte-identical across the list. Per-scene task.success_key and task.max_steps MAY differ (ManiSkill3 mixed-budget suite). suite_id is embedded in every error message so failures point back at the right benchmarks/<id>.yaml. First violation wins — no batched reporting. Raises ROSConfigError on 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 asset ref to a concrete file path for the requested kind (urdf/mjcf/srdf). One grammar replacing resolve_urdf_path, resolve_mjcf_uri, plain-path SRDF, and urdf_lowering._load_urdf_model. Schemes: rd:<module> (upstream robot_descriptions, downloads on first use; xacro-only URDF → AssetRefError directing to openral 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 via ensure_openarm_v2_mjcf, anvil_v2_bimanual → Anvil OpenARM 2.0 via ensure_anvil_openarm_v2_mjcf; menagerie not yet wired), ros2://robot_description (URDF-only dynamic marker → returns None). Raises AssetRefError for 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 at eye so view_axis points at target. Degenerate fallbacks (never raises): target == eye → MuJoCo straight-down flip for "-z" / identity otherwise; gaze near-parallel to up → +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 hits target_xyz; the rskill-moveit-multi-look_at rSkill'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 by LookAtRskill to 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) for Rz(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 by ROSActionRskill._step_impl once 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 the rskill_runner_node execute-callback boundary; the runner closes the goal with success=True. NOT an error — inherits ROSError only to stay inside the OpenRAL exception surface. (L151)
  • class ROSDeadlineMissed(ROSFleetError) — Cloud RTT exceeded skill deadline. (L144)