Duplication & Reuse Watch
Part of the OpenRAL public-symbol inventory. Hand-curated;
(LNN)markers are refreshed bytools/refresh_methods_linenos.py.
This is the user-facing deliverable for the goal of "ensure there are no duplication or redundancy of methods". Each item is something a future contributor should look at before adding similar code.
Confirmed redundancy candidates
- Sensor
_spec()private factory helpers — seven structurally identical kwargs-only_spec()functions across: python/sensors/src/openral_sensors/force_torque.py:28python/sensors/src/openral_sensors/imu.py:27python/sensors/src/openral_sensors/livox.py:31python/sensors/src/openral_sensors/ouster.py:23python/sensors/src/openral_sensors/hokuyo.py:25python/sensors/src/openral_sensors/slamtec.py:27python/sensors/src/openral_sensors/usb_uvc.py:51(_uvc_spec)
Each just constructs a SensorSpec(...) from kwargs that map to the
same openral_core.SensorSpec field set. Reasonable
consolidation: a single _make_sensor_spec(modality, **fields)
helper in openral_sensors.catalog (or a new _factories.py
sibling). Low risk because the public *_spec() API is unchanged.
- Three parallel registries with the same lookup-by-string pattern:
python/rskill/src/openral_rskill/loader.py:114—rSkill+InstalledRSkillEntryJSON file registry.python/sensors/src/openral_sensors/catalog.py:85—SensorCatalogin-memory dict.python/sim/src/openral_sim/registry.py:43—_Registry[T]decorator-driven dict.
These are different in lifecycle (file-backed vs. in-memory) and
value type (skill vs. sensor entry vs. factory), so deep
consolidation is not warranted. Worth aligning method names
though — SensorCatalog.list_ids(), _Registry.names(), and
rSkill.list_installed() all answer the same question with
different verbs. A future ADR could standardise on one verb.
-
VLA adapter boundary helpers — resolved.
resolve_device,resolve_rskill_repo_id,run_inference,to_numpy_action,parse_hf_file_uri, andmaterialize_processor_dirnow live inpython/rskill/src/openral_rskill/_vla_core.py. All five eval adapters (smolvla,pi05,xvla,act,diffusion) and the skill-sideChunkedExecutorroute through it. Thediffusion/xvla/pi05adapters now go through the smallpython/sim/src/openral_sim/policies/_processors.py::resolve_processor_dirhelper, which delegates tomaterialize_processor_dirwhen the weights URI resolves to a manifest that declares aprocessorsblock, falling back tosnapshot_downloadfor legacyhf://shapes — the three sister TODOs on the audit closed 2026-05-18. When adding a new VLA family, do NOT re-implement device or rSkill resolution; do NOT wrappolicy.select_actionin your owninference_spanblock — callrun_inferenceso the OTel span fires uniformly. For loading the lerobotPolicyProcessorPipeline, call_processors.resolve_processor_dir(sim-layer) ormaterialize_processor_dir(manifest)(skill-layer) — do NOT callsnapshot_downloaddirectly. -
SmolVLA skill-side
SmolVLAAdaptervs eval-side_SmolVLAAdapter— not a duplication target. The two have incompatible input contracts on purpose: the skill takesWorldStateand emits anActioninside the ROS2 lifecycle (Layer 3, S1 runtime); the eval adapter takes a dictObservationand emits a flat numpy array (Layer 8, sim driver). Collapsing them would force either ceremonial Pydantic wrapping in the sim hot loop or wideningSkill.step()to accept dicts (breaks §6.1). With_vla_coreabsorbing the cross-cutting seams, residual overlap (checkpoint load + processor factory, ~30 LOC each side) is below the abstraction-cost threshold. Keep them separate. -
_build_libero_scene/_build_metaworld_scene/_build_mock_sceneinpython/sim/src/openral_sim/{policies,backends}/{libero,metaworld,mock}.pyshare the same structure: lazy-import a backend module, instantiate a_*Simwrapper, return it. Already correctly DRY through theSCENES.register(...)decorator pattern; do not consolidate further. -
Policy load-phase heartbeat — resolved. The original threaded heartbeat (
pi05._heartbeat) lived inline in the pi05 adapter and hard-coded thepi05_*event prefix, the daemon thread plumbing, and the GPU memory probe. It now lives once inpython/rskill/src/openral_rskill/_diagnostics.py::phase_timer(name, *, prefix, gpu_mb, **fields)and the pi05 / smolvla adapters apply it through one-line per-adapter shortcuts (_pi05_phase/_smolvla_phase). When adding a new VLA family, do NOT roll your own heartbeat thread — wrap every load phase (imports/from_pretrained/to_device/processor_dir/make_processors/ family-specific quant or compile phases) with a thin_<family>_phaseshortcut sotools/profile_policy_load.pyandopenral dashboardsee the same event shape across all adapters.
Already correctly DRY (do not flag)
-
SimSensorBridge (ADR-0034) — the single source for RGB camera publishing + MuJoCo viewer under
deploy sim. All manifest-driven arms route throughopenral_hal.sim_sensor_bridge.SimSensorBridgevia_ManifestHALLifecycleNode. Thepanda_mobilepackage retains its own wiring until Phase 2 of ADR-0034 (the planned dedup refactor). Do NOT add per-arm camera or viewer timers in lifecycle subclasses; extendSimSensorBridgeinstead. -
HAL adapters (sim) —
FrankaPandaHAL,UR5eHAL,UR10eHAL,SO100MujocoHAL,Rizon4MujocoHAL,G1MujocoHAL,H1MujocoHAL,AlohaMujocoHAL,OpenArmMujocoHALall extendMujocoArmHAL. Post-ADR-0023 (including the bimanual amendment + the 2026-05 cleanup that collapsed each subclass__init__into a single forward toMujocoArmHAL._init_from_description(<DESCRIPTION>, …)), each subclass is now one line of meaningful code — the typed__init__(*, mjcf_path, settle_steps, gravity_enabled, staleness_limit_s)signature is kept so IDEs surface the four user-tunable knobs, but every per-robot constant (MJCF URI, joint→qpos/actuator maps, gripper config, keyframe/seed-ctrl flags) lives entirely in<ROBOT>_DESCRIPTION.sim(SimDescription/SimGripperDescription). The seam isMujocoArmHAL._init_from_description(instance method) → which delegates toMujocoArmHAL._sim_kwargs_for(static method, returning a_MujocoArmInitKwargsTypedDict so the**kwargsunpack into__init__is typed-clean undermypy --strictwith no per-subclass# type: ignore). Per-robot_<robot>_mjcf_pathhelpers were also retired in the same cleanup — every MJCF ref resolves through the centralopenral_core.assets.resolve_assetgrammar (rd:/gym_aloha:/openarm:/menagerie:/file:schemes; ADR-0058). New MuJoCo HALs — single-arm, floating-base humanoid, or bimanual — should declare anassets.mjcfref (plus an optionalsim:joint-wiring block) inrobots/<id>/robot.yamland callMujocoArmHAL.from_description(desc). No per-robot Python file is required at all; the existing classes only exist so the explicithal.simstrings ("openral_hal.<robot>:<Class>") some manifests pin keep resolving.H1MujocoHALretains a real subclass body only for its_per_step_updatetorque hook (default no-op inMujocoArmHAL, overridden by H1 to recomputetau = kp*(target-q) - kv*dqevery step) — that PD behavior is H1-specific cerebellar substitute, not arm-data, and stays in code. - Policy adapter loader seams — resolved. The 2026-05 cleanup
pulled three parallel copies of
_load_manifest_for_spec(one each inpolicies/smolvla.py,policies/rldx.py,policies/pi05.py) and one copy of the lerobot lazy-import +ROSConfigErrorinstall hint into a newpython/sim/src/openral_sim/policies/_policy_loading.py—load_manifest_for_spec(spec)andlazy_import_lerobot(adapter_name, *, install_hint=...). Similarly, the four dtype helpers that used to live inpolicies/pi05.py(_manifest_dtype,_normalise_manifest_dtype,_torch_dtype_for,_default_dtype) were lifted intopython/sim/src/openral_sim/_quantization.pyas publicmanifest_dtype,normalise_manifest_dtype,torch_dtype_for,default_dtype_for_device. Theact.pyadapter still carries its own_load_manifest_for_specbecause the rest of its load path is structured around a snapshot of the policy weights; if a fifth adapter ever needs the same shape, route it through_policy_loading.load_manifest_for_spec. - Humanoid contract validators vs useful humanoid sims —
G1MujocoHALandH1MujocoHALare contract validators only. Both robots' floating bases fall without an S0 cerebellar balance controller (CLAUDE.md §6.2), so closed-loop sim tests run withgravity_enabled=False. This is the same situation a future GR1 HAL twin (currently still deferred — see below) will be in until the C++ S0 cerebellum lands. Do NOT promote these HALs to "useful humanoid sim" by bolting Python balance heuristics onto them — that path crosses the S0 layer boundary §6.1 reserves for C++. Note thatH1MujocoHAL's software PD position loop is not a balance controller — it's a per-joint Kp/Kd that converts the H1 menagerie's torque actuators into the position-target contract every otherMujocoArmHALsubclass implements, and mirrors whatunitree_sdk2does on real hardware. - Deliberate digital-twin gaps —
SawyerandGR1intentionally ship without a MuJoCo HAL twin: - Sawyer: Rethink Robotics is defunct; no real Sawyer hardware
will ever be plugged in. Sawyer remains only as a MetaWorld
VLA-eval robot (no
SawyerHAL, onlySawyerRealHALskeleton). Twin would be busywork. - GR1: still no Python HAL twin — Fourier GR1 is one humanoid
family along with Unitree G1, and once the C++ S0 cerebellum
lands (M2) it's the natural second consumer of the humanoid
HAL pattern that
G1MujocoHALset up. Currently only exists as anopenral_simrollout robot. These are documented absences, not missing work; do not add HAL twins for them speculatively. - Real-HW manifest derivation — every real-HW adapter publishes a
*_REAL_DESCRIPTIONconstant derived from a sim-side baseline viaopenral_hal._real_description.make_real_description(base, sdk_kind=...). The helper centralises themodel_copy+sdk_kindoverride pattern (thehalentrypoints are shared, ADR-0031), so kinematics + safety envelope + capabilities + HAL entrypoints never drift between the sim and real-HW siblings of the same robot. New real-HW adapters MUST go through this helper rather than re-typing the wholeRobotDescriptionconstructor. The UR real-HW module (ur_real.py) uses this helper to deriveUR5e_REAL_DESCRIPTION/UR10e_REAL_DESCRIPTIONfromUR{5,10}e_DESCRIPTION. - HAL adapters (real-HW) — three shapes coexist on purpose:
FrankaPandaRealHALandSawyerRealHALcomposeRosControlHAL(delegating wrapper) and add robot-specific structlog metadata + a vendor-specific recovery / halt topic publish inestop(). This is the intended pattern for any real-HW arm whose vendor stack exposes a singleros2_controljoint trajectory controller plus a separate recovery topic.UR5eRealHAL/UR10eRealHALsubclass a private_URRealHAL(RosControlHAL)base inur_real.pyto share theur_robot_drivercontroller / topic / deadman defaults. Pick subclassing when two adapters share enough defaults to warrant a base; pick composition when each adapter has distinct recovery / metadata semantics. Any future UR variant (UR3e, UR16e, …) is a one-line subclass that swaps theRobotDescription.AlohaHALinlines the publish/state machinery rather than wrappingRosControlHALbecause it splits a single 14-D action across four controllers (two arms + two grippers) — a contract that doesn't matchRosControlHAL's single-controller assumption. Adding a sixth composed-real-HW adapter is the trigger to hoistRosControlHAL-wrapping logic into a_RealHALMixin; adding a second multi-controller adapter is the trigger to hoist AlohaHAL's fan-out into aMultiRosControlHAL.- HIL transport bridges (real-HW HALs) — the single-controller
RosControlHILTransport(tests/hil/_ros_control_transport.py) is the source of truth for the trajectory wiring;AlohaHILTransport(tests/hil/_aloha_ros_transport.py) reuses the module-private_make_trajectory_publisherhelper rather than duplicating theJointTrajectory+ QoS setup four times. Both bridges share the joint-state caching shape (_latestdict,state()projection overjoint_names,wait_for_first_statehelper). Adding a third HIL bridge variant is the trigger to extract the shared subscriber half into a_JointStateCachemixin. - Kernel-twin sim tests — the four
tests/sim/safety/test_kernel_with_<robot>_*.pyfiles (so100_digital_twin,openarm_twin,rizon4_twin,h1_humanoid_twin) used to each open-code the subprocess + lifecycle - ROS-graph envelope around the C++ safety kernel. After the 2026-05
cleanup, all four route through
tests/sim/safety/_kernel_subprocess.py::{start_kernel, activate_kernel_node, build_kernel_envelope, terminate_kernel}and only declare their embodiment-specific joint-name lists + per-test action / state vectors. Adding a fifth robot's kernel-twin test means one new short test file that calls the same four helpers — do NOT re-roll the lifecycle ceremony. - rSkillBase subclasses —
GpuPassthroughSkill,SmolVLAAdapter,SO100SmolVLASkillall override the same five_*_implhooks. The duplicated method names are the contract fromSkillABC; this is inheritance, not redundancy.GpuPassthroughSkill(M8 PR I/10) is the canonical "this skill provably runs on GPU" reference — its_step_implis the right starting point when prototyping a torch.cuda-based Skill that consumes a CPUSensorFrame.data: bytesand needs to be explicit about device placement (raises on missing CUDA rather than silently falling back). - Runtime backends —
NullRuntime,PyTorchRuntime,ONNXRuntime,TensorRTRuntimeall implement theRuntimeProtocol surface (load/infer/quantize/warmup/unload). Same situation as Skill. backends/so100_robosuite/—_So100Liftextendsrobosuite.environments.manipulation.lift.Liftrather than reimplementing the arena / reward / observable / placement scaffolding, and the controller config is the shippedparts/osc_position.jsonwith three knobs overridden (output_max,kp,input_ref_frame) — NOT a custom controller class. The scripted policy is correspondingly tiny (~150 lines, just Cartesian deltas) because OSC owns the IK. The next new robosuite-integrated robot should follow the same pattern: register the robot model + gripper in robosuite's factories, build the env via robosuite's stock manipulation subclasses, pick a stock part controller (osc_position/osc_pose/joint_position) and tune only the gain / output ranges — do not write a JOINT_POSITION + custom-IK stack like the earlyso100_robosuitedrafts did.
Watch list (not yet a problem, but worth tracking)
_validate_action()appears in bothMujocoArmHAL(L296) andRosControlHAL(L250). They validate different invariants today (MuJoCo:joint_targetsrank; ros2_control: control mode). If a third HAL grows a third_validate_action, lift the common parts into a free function inopenral_hal.protocol._require_connected()appears inMujocoArmHAL(L289),SO100FollowerHAL(L386),RosControlHAL(L243), andAlohaHAL(L426). Four is over the threshold — the next HAL adapter that adds a fifth_require_connectedis the trigger to hoist this into a base mixin (openral_hal._lifecycle.RequireConnectedMixin).FrankaPandaRealHAL/SawyerRealHALdeliberately delegate the check to their innerRosControlHALrather than duplicating it.from_yaml(cls, path)classmethods appear inRSkillManifest(L883) andSimEnvironment(L1127). Both are "open file → parse YAML →model_validate(dict)". Acceptable as a copy because they live in the normative schemas module, but aopenral_core._yaml.pyhelper would remove the boilerplate.
Generated and curated 2026-05-08 from a single AST pass over
python/, packages/, and tools/. Re-run python3 -c "import ast"-based
extraction whenever a module is added or renamed; this file is hand-edited
afterwards. If a future contributor automates regeneration, mirror the
pattern in tools/schema_export.py.