Architecture¶
Which object Robot(...) returns in each mode, which module owns what, and the seven-layer rule a change must obey.
strands_robots is one factory over two lanes and seven import layers: this page says which object Robot(...) hands you in each mode, which module owns what, and the layer rule a change must obey.
Two lanes behind one factory¶
from strands_robots import Robot
sim = Robot("so101") # mode="sim": a MuJoCoSimEngine
arm = Robot("so101", mode="real", port="/dev/ttyACM0") # lerobot RobotConfig behind hardware_robot.Robot
arm = Robot("so101", mode="real", driver="strands", port="/dev/ttyACM0") # a native driver from strands_robots.drivers
Robot in strands_robots/robot.py is a function: it resolves the alias through the registry, then dispatches on mode:
| mode | returns | built by |
|---|---|---|
sim (default) |
a SimEngine from the backend registry (mujoco default, newton, plugin isaac) |
simulation.factory.create_simulation |
real, driver="lerobot" (what auto resolves to without a registry hardware.driver) |
hardware_robot.Robot, a lerobot RobotConfig under a Strands AgentTool |
hardware_robot.py |
real, driver="strands" |
a class satisfying the drivers.base.HardwareDriver protocol, no lerobot import |
drivers.registry |
auto |
STRANDS_ROBOT_MODE, else probes USB for a servo controller, else sim |
_auto_detect_mode |
Both lanes are AgentTools, so Agent(tools=[robot]) works the same on each, and both take a Policy from policies.create_policy for run_policy. Sim is the default so a script never moves hardware by accident.
The sim lane is the MuJoCoSimEngine class: SimEngine plus mixins for physics, rendering, recording, randomization, manipulation, motion primitives and teleop, exposed as one tool with an action vocabulary. The hardware lane is hardware_robot.Robot plus the driver layer; a task runs in a background thread with TaskStatus and a stop flag, and a policy dispatch passes the operator gate in _command_gate.py.
Layers¶
Seven layers, top to bottom; a module imports only from layers below its own:
scripts/check_import_layers.py grades this from the source with ast: no runtime import cycle, and no upward edge unless it is written in the script's KNOWN_DEFERRED_UPWARD_EDGES roster. The roster is a ratchet: removing an inversion deletes its line, adding one fails the check until it is listed. It is empty at this commit.
The table is generated from the grader's LAYERS declaration.
| layer | what it is for | members |
|---|---|---|
0 core |
leaves everything imports: gates, audit, dataset formats, refusal codes, rendering helpers | audit, bus_access, dataset_metadata, dataset_recorder, dataset_source, dataset_transfer, episode_labels, locomotion_envelope, recorder, recording_errors, refusal_codes, rendering, streaming_dataset, utils and 11 private helpers (_async_utils, _command_gate, _description_cache, _dyld, _hitl_audit, _mesh_switch, _motion_grants, _mujoco_gl, _pacing, _path_validation, _serial_discovery) |
1 registry |
the robot and policy rows, and the asset paths they declare | assets, registry |
2 drivers|mesh |
talk to hardware (native drivers, ROS, teleop) and to other peers (Zenoh mesh) | device_connect, drivers, foxglove, mesh, ros, ros_telemetry, rosbridge, rtps, teleop, teleop_mixin, teleoperator |
3 sim|policies |
the simulation backends, the policy providers, training and remote inference | inference, policies, simulation, training |
4 app |
what Robot(...) returns: the factory, the hardware lane, doctor and dataset verification |
doctor, hardware_observe, hardware_robot, hardware_ros_bridge, hardware_rtps_bridge, robot, verify_dataset |
5 tools |
the @tool surface an agent calls |
tools |
6 dashboard |
the operator UI, a mesh gateway over everything below | __main__, dashboard |
Placements that are a judgement: assets sits with registry because it resolves the paths the registry declares; the dataset modules (dataset_recorder, dataset_metadata, dataset_source, streaming_dataset, dataset_transfer) sit in core because a dataset is a contract two layers agree on, not a host.
Rules every module obeys¶
- Cheap import.
import strands_robotsleaves numpy, torch, mujoco and lerobot out ofsys.modules; every heavy name in__all__is behind the package__getattr__, and the import-time shims (_mujoco_gl,_dyld) are stdlib-only leaves. - Registry is the source of truth.
registry/robots.jsonholds the robots (156 at this commit, in 8 families) andpolicies.jsonthe providers. Code reads the row; it never hard-codes a robot. - Refuse, do not guess. A value the code cannot honour (a non-finite pose, an unknown joint name, a hardware kwarg on a sim robot) is refused with a message naming the valid set; continuable refusals carry a code.
- A policy does not reach hardware without an answer. Mutative verbs on
use_unitree,serial_tool,pose_tool, theRobottool'sexecuteandstart, and every ROS 2 transport raise the SDK interrupt;STRANDS_*_COMMAND_ALLOWpre-approves one command for an unattended run. Stop verbs are never gated; the native drivers'move_tois not gated yet. - Providers are plugins. Simulation backends register through
register_backendor thestrands_robots.backendsentry-point group; policies throughregister_policyorpolicies.json; native drivers throughregister_native_driver.
Extras¶
The package installs with no heavy dependency; each lane pulls its own extra: [sim-mujoco] for the sim lane, [lerobot] for the lerobot hardware lane, [mesh] for Zenoh, [dashboard] for the operator UI, and one extra per native driver or policy provider (19 drivers, 16 providers). pyproject.toml is the list; a door that needs an extra you lack refuses with the install line.
What changes in 1.0¶
1.0 keeps this layer DAG and changes what each layer holds (roadmap).
Edit page