Skip to content

strands robots v0.5.3

Latest

Choose a tag to compare

@cagataycali cagataycali released this 07 Oct 22:17
· 81 commits to main since this release
e26d92a

strands-robots v0.5.3

Release date: 2026-10-05 Previous release: v0.5.2 (2026-09-17)

621 pull requests in eighteen days - the robots this library can drive doubled, and where a
fleet can live did too. A robot anywhere on the internet is one Mesh.send away over AWS IoT
Core Direct Messaging
; an Isaac Lab run trains on thousands of GPU worlds, exports, and
walks on MuJoCo through create_policy("rl"); mjlab steps 4,096 MuJoCo worlds in one Warp
launch; the native drivers are the hardware default and nine more robots have one; the
registry grows 75 → 157 with every robot_descriptions URDF; holosoma, wbc_latent and
π0.5-DROID join the policy providers; any robot streams to Foxglove. 431 fixes keep the
theme of the last release: refuse, don't guess. groot (the hand-rolled provider),
lerobot_async and the Dynamixel driver stub leave.

strands-robots is the robot-control library for Strands Agents:
one Robot object for a simulated or physical arm, humanoid, quadruped or mobile base; policy
inference, teleoperation, recording, training and fleets behind one agent-callable tool surface;
and a real robot that does not move until an operator says yes.


Highlights

  • AWS IoT Core direct messaging, and a fleet that is not on your LAN. On the iot and
    bridge mesh backends Mesh.send delivers a command to the one connected client it names as an
    AWS IoT Core direct message
    (QoS 1, the robot's PUBACK is the confirmation) and the robot answers on the responseTopic it
    was handed, after checking it is exactly strands/<sender>/response (#4242). Provisioning
    generates the private key on the robot and has AWS sign a CSR (CN=<thing>); the key never
    leaves the machine (#4242). A robot certificate obeys fleet stops and cannot originate
    one unless designated (allow_estop_publish, #4317); a retained safety message is never read as a
    live stop (#4326); a peer re-subscribes after every reconnect, so a robot that dropped its MQTT
    session no longer goes deaf to the fleet e-stop - measured 0/3 stops heard before the fix, 3/3
    after (#4370); child peers get their own policy (strands-robot-children) so a sim robot on
    iot no longer lives in a 150 ms connect/disconnect loop (#4346); operators receive their direct
    replies (first reply 450 ms, 226 ms steady, #4371); the pure iot backend starts without the
    permissive-ACL opt-in the docs forbid (#4351); e-stop and resume cross the bridge to the MQTT
    leg (#4098, #3965). The dashboard shows a robot reached over IoT as a full card next to the
    LAN peers - joints, cameras (S3 reference resolved server side, presigned URL never reaches the
    browser), safety state, run/stop/reset through the same gates (#4348).

    from strands_robots.mesh import init_mesh                 # STRANDS_MESH_BACKEND=iot on both ends
    mesh = init_mesh(robot, peer_id="operator-1", peer_type="operator")
    mesh.send("so101-arm-01", {"action": "status"})           # one direct message -> one client -> PUBACK

iot_direct_messaging

dashboard_iot_fleet

Left: pub/sub fan-out vs one direct message (the docs' own drawing, #4242). Right: IoT-reached
robots (IOT chip) beside a LAN sim peer on the Fleet tab (#4348).

  • Isaac Lab: train on thousands of worlds, export, deploy on MuJoCo, record a dataset.
    create_trainer("isaaclab") / train_policy(provider="isaaclab") launches python -m isaaclab train in the interpreter ISAACLAB_PYTHON names - Isaac Lab is never imported and adds no
    dependency - and answers status(job_id) from the run's log: latest iteration, first/latest/
    best reward, steps_per_s, the rsl_rl run directory (#4227 / fragment 866). The run can be
    played back on video and exported (#4287); export records the deploy contract - joint
    order, offset + scale * action, the run's PD gains - so a published Go2 rough-terrain policy
    no longer commands joints 1.73 rad from where Isaac Lab would (#4402, #4453); create_policy("rl")
    builds the observation from a strands robot's state and runs the run's PD on MuJoCo torque
    motors (#4402), and loads an rsl_rl run directory, one model_<n>.pt or a Hub repo id (#4440).
    rl_library="skrl" (AMP humanoids, MAPPO/IPPO, #4436) and "rl_games" (Factory, Forge,
    AutoMate, #4436) train beside rsl_rl; an operator's own task packages register through
    $STRANDS_ISAACLAB_TASK_PACKAGES (#4410); train_policy(action="record") rolls the newest
    checkpoint out in parallel environments and writes a LeRobotDataset (#4437); a job stops at its
    timeout_s with nobody polling (#4436); CNN and recurrent actors are refused at export instead
    of shipped without their encoder (#4373). Every RL trainer now leaves a curve, metrics.jsonl
    (#4427).

    train_policy(action="train", provider="isaaclab", steps=1500, output_dir="runs",
                 extra={"task": "Isaac-Velocity-Flat-G1", "num_envs": 4096})   # -> job_id
    train_policy(action="export", provider="isaaclab", job_id=JOB_ID, output_dir="g1_flat")
    Robot("unitree_g1").run_policy(policy_provider="rl", policy_config={"checkpoint_dir": "g1_flat"}, duration=10)

isaaclab_go2_rough

go2_isaaclab_policy_on_mujoco

Go2 rough-terrain training in Isaac Lab (4 of the worlds, #4227) · the exported actor standing
on strands' MuJoCo Go2 through create_policy("rl") (#4402).

  • Isaac Sim is an AgentTool, and a 41-PR parity pass. Agent(tools=[Robot("so101", backend="isaac")]) works: IsaacSimulation publishes the shared simulation schema narrowed to
    the 43 of 77 actions it implements, and run_agent drives an agent on it (#4361). IsaacConfig
    forwards Kit settings, caps Kit's task threads and bounds a hung start-up - 3 of 6 processes on
    one 32-core host were still booting after 240 s before (#4379). Then the user-visible fixes:
    cameras show the state the simulation is in and a recorded episode no longer opens on a black
    frame (#4409, #4358, #4353); render() returns the size it was asked (#4288); the Panda gripper
    works (#4341); MJCF robots spawn where asked, as fixed-base arms, without a second floor
    (#4359), keep their servo gains and joint names (#4271, #4272), and an MJCF motor is driven as a
    torque (#4345); replicate gives every environment its own grid cell (#4292); reset() leaves
    the scene at rest with the clock at zero (#4350); apply_force spins the right way (#4421);
    aerial robots fly (#4420); a robot that converts to several articulations loads every arm
    (#4382); the sim clock tracks physics (#4110); remove_robot deletes the prim (#4111); a floor
    renders clean instead of streak noise (#4424); Isaac Sim 6.1 robots load with their physics
    (#4268). The earlier Isaac audit closed 38 findings in one PR (#3343).

  • Native drivers are the default, and nine more robots have one. Robot(name, mode="real")
    with no driver= builds the native driver registered for the robot and falls back to lerobot
    only where this package has none; a registry hardware.driver declaration and driver="lerobot"
    still pin that path. An SO-101 on a fresh install connects without the [lerobot] extra and
    reads the calibration lerobot already wrote (#4460). New drivers: Boston Dynamics Spot
    over bosdyn-client (#4500), KUKA LBR iiwa 14 over FRI (#4525), Kinova Gen3 over
    kortex_api (#4519), UFactory xArm 7 (#4478), Hello Robot Stretch 3 over stretch_body
    (#4480), Rainbow RB-Y1 over rby1-sdk (#4479), Unitree H1, H1-2 and B2 on the
    Go2/G1 wires (#4502, #4510, #4531), Yahboom ROSMASTER M3 Pro over its ROS 2 graph (#3941),
    and a real Dynamixel bus so Robot("koch", driver="strands") reads, moves and runs a policy
    (#4122). URDriver serves every e-Series and UR-Series arm at each model's own joint speeds
    (#4513). The Feetech SO arms run a policy on the loop the UR already had (#3963), take a leader
    arm through attach_teleop (#4279), and drive their MuJoCo twins over the same bus (#3944).
    Reachy Mini and Microduck expose their whole operator vocabulary on the native driver (#3867,
    #3872). Robots a native driver builds: 25 → 40 (list_native_drivers()).

    arm = Robot("so101", mode="real", port="/dev/ttyACM0")   # FeetechDriver, no lerobot needed
    spot = Robot("spot", mode="real", port="192.168.80.3")        # SpotDriver over bosdyn-client

ur_family

The UR family in MuJoCo, one driver (#4513).

  • mjlab: GPU-vectorized MuJoCo (MuJoCo-Warp) as a first-party backend ([sim-mjlab], #4229).
    create_simulation("mjlab", num_envs=N) compiles the MuJoCo backend's MJCF once and steps N
    worlds in one Warp launch; world 0 speaks the single-world contract, so Robot("so101", backend="mjlab") behaves like mujoco, and get_observation_batch / send_action_batch /
    randomize(seed=) move (N, …) tensors on the GPU. train_policy(provider="rsl_rl") runs
    mjlab's PPO in-process and exports ONNX with a dynamic batch axis; rsl_rl_onnx loads it on
    mujoco, mjlab or hardware. Parity: so101 joints agree with the MuJoCo backend to 8e-5 rad
    over 2 s (#4229). Measured for these notes on one L40S, so101, torch.cuda.synchronize() before
    the clock stops: 3,520 env-steps/s at N=1 → 9.0 M at N=4096.

mjlab_throughput

  • Registry 75 → 157: every robot_descriptions URDF loads in MuJoCo (#4211, [sim-urdf]).
    strands_robots.assets.urdf compiles a URDF on first use into a Menagerie-shaped asset: mesh
    URIs resolved, Collada converted, the inertials and zero-size geoms MuJoCo refuses repaired, one
    position actuator per joint sized from the URDF effort, a free joint for floating bases, the root
    lifted onto the floor. 23 xacro-only descriptions render through xacrodoc. list_robots()
    reports them with source: "urdf". 79 of the 80 build - eve_r3 is listed with the reason its
    upstream repository no longer exists - and this is all 79, rendered from this release:

urdf_registry_mosaic

Robot(name, mode="sim").render() for each of the 79, headless EGL on an L40S.

  • Policies: holosoma, wbc_latent, π0.5-DROID, FLUX 3 Action. create_policy("holosoma")
    runs Amazon FAR's released G1 locomotion checkpoints (fastsac default or ppo) in process with
    ONNX Runtime - the 100-wide observation, the two-foot gait clock at 50 Hz, PD gains read from the
    checkpoint's own metadata; weights come from the Hub mirror at a pinned commit, sha256-checked,
    nothing bundled (#4249, [holosoma]). wbc_latent decodes a VLA's 64 SONIC motion tokens into
    29 G1 joint targets, the recipe of "Bringing Humanoids to LeRobot" (#4251). An embodiment can
    speak DROID - joint-velocity actions integrated at action_dt and a gripper fraction - so
    lerobot/pi05_droid runs on panda_droid without an adapter (#4428). flux3_action wraps
    Black Forest Labs' FLUX 3 Action with its SO-101 checkpoint, owning the unit frame between the
    model and the robot (#4199). lerobot_local keeps a pi0 / pi0.5 checkpoint's own processor
    pipeline, runs on the GPU, normalizes with supplied stats, and refuses an unknown embodiment=
    before any download (#4404, #4403, #4406, #4366).

g1_holosoma_walk

Unitree G1 under holosoma fastsac_g1_29dof.onnx at 0.5 m/s: 3.1 m in 8 s, pelvis at
0.79 m, no NaN (rendered on an L40S for these notes; mp4).

  • Foxglove live view and MCAP export for any robot ([foxglove], #4277). Robot(name, foxglove=True) serves a sim or real robot over foxglove.sdk.v1: /tf and the robot's own
    MuJoCo meshes for the 3D panel (no URDF), joint states, JPEG cameras, log and event channels.
    foxglove_mcap=path records the same channels; export_episode turns one LeRobot v3 episode
    into a seekable MCAP; mcap_info reads any MCAP back. A stopped bridge has released its port
    when shutdown() returns (#4461).

foxglove_so101_live

  • The dashboard is the fleet on the mesh, on a phone too. The agent console drives mesh peers
    only - in-process sim tools are gone (#4338) - and knows which policies each robot on the mesh
    can run (#4342); every card has a Reset button through the same gate as run and stop
    (#4344); a 3D twin drawn from the compiled model follows a real arm, reading the bus and never
    writing it (#3739, #3892); the Agent tab puts a consent card before anything that moves (#3879);
    the whole operator workflow - live fleet, devices, record, train, calibrate, voice - is wired
    (#4102), the voice console runs on strands.bidi (#4456), and the layout works on a phone and a
    tablet (#4459). It wears the docs' design tokens (#4218) and a Hub checkpoint type-ahead that
    knows the robot (#4226).

dashboard_fleet_reset

dashboard_phone

Fleet tab from this release: two sim SO-101s, Reset just pressed on the left card, the IoT
registry line below; the same page at 390 px.


Also new

  • Mesh. A peer advertises the tool it serves and call invokes it; sim_call carries the
    whole simulation tool over the mesh and set_joints is a simulation-only action (#4255, #4220);
    a second command inside 50 ms is answered, not dropped, and camera frames keep their colours
    (#4255). A mesh command that moves real hardware passes the operator gate on the robot host
    (#4313); a motion grant is bound to the calibration it was approved under and expires with its
    peer (#4320, #4325); a command key the peer does not read is refused (#4397); a peer's refusal
    crosses the wire with its code and grant (#4380); the embodiment travels with a wire policy
    command so a degrees-trained checkpoint is not applied in radians (#4368); a remote resume
    clears the whole fleet (#4364); an early e-stop still stops (#4377); replies are attributed to
    the Zenoh session that sent them (#4303). robot_mesh hands the agent the peer's whole reply
    (#4375) and its headless refusal names what pre-approves the call (#4386).
  • Simulation. SimEngine.capabilities() names what a backend supports (#4177) and
    actuator_ranges() what it can command (#4096); ManipulationOptional for backends without
    manipulation (#4270); register_robot registers a robot with no asset (#4232); a rollout that
    stalls against contact says so in saturated_step_rate (#4472); a diverged MuJoCo world is
    reported, not stepped through as a success (#4399); move_to says when what the fingers held
    was left behind (#4400); a teleoperated MuJoCo session keeps sim time and records a demonstration
    (#4446); Newton poses a robot with set_joint_positions and draws its floor (#4429).
  • Registry. yahboom_m3pro (sim + real, #3927, #3941), mujoco_humanoid, and gen3,
    iiwa14, iiwa, tiago, tiago_pp resolve by name (#4202); lekiwi_sim embodiment drives
    the simulated LeKiwi (#4491); lekiwi and spot are mobile manipulators (#4395); the robot
    catalog publishes the driver coverage join, generated at build (#4071).
  • Training. base_model is a Hub reference or a checkpoint inside a known home (#4310); a
    Hub dataset push is private unless you ask for public (#4521); create_trainer() names the
    closest trainer for a near miss (#4497).
  • Whole-body teleoperation seam and driver composition, designed and proved in simulation
    (#4253); a humanoid's G1 verbs are consolidated - one g1_sensor, one g1_task, four reference
    lookups (#3999, #4007, #4011).
  • Typo-tolerant refusals. create_policy, add_object, add_robot, keyframes, cameras,
    predicates, episode labels and Robot(**kwargs) now answer a near miss with "did you mean"
    (#4252, #4405, #4499, #4528, #4524, #4530, #4495, #4509, #4484, #4533, #4536).
  • Packaging. strands_robots.__version__ reports the installed version (#4244); the package
    ships py.typed (#4175); the test suite runs distributed and a PR's required check with it
    (#3992); [dev] gains pytest-xdist.

Breaking changes

  • Removed: the hand-rolled groot policy provider, its ZMQ tool gr00t_inference, the
    Isaac-GR00T trainer and the groot-service extra - about 6,600 lines of source; GR00T N1.7 is
    lerobot 0.6's native groot policy type behind the new [groot] extra, and a GPU box runs
    PolicyServer for the remote provider (#4256). The lerobot_async provider and
    [lerobot-async] extra - lerobot ships policy_server/robot_client itself (#4091). The
    Dynamixel driver stub, so driver="strands" refuses a Dynamixel robot it cannot move instead
    of building one that cannot read or move a joint; koch now has a real native bus (#4097,
    #4122), aloha/vx300s/wx250s/trossen_wxai/dynamixel_2r go through lerobot.
  • The native driver is the hardware default (#4460). Robot(name, mode="real") without
    driver= builds the strands driver where one exists. Pin driver="lerobot" to keep the old
    path; earthrover keeps lerobot by registry declaration.
  • The dashboard agent console drives mesh peers only; the in-process sim tools, the Sim tab's
    direct sessions and their routes are gone (#4338). Spawn a sim peer instead.
  • Moved: resolve_dataset_dir, local_dataset_dir, load_lerobot_episode →
    strands_robots.dataset_source (#3908); sync_dataset_to_bucket →
    strands_robots.dataset_transfer (#3916); the ROS 2 mobile bases strands_robots.mesh →
    strands_robots.drivers.ros (#4140); the Reachy daemon link → drivers.reachy_transport
    (#4117); loop pacing → strands_robots._pacing (#4114); BUCKET_STREAMING_MIN_LEROBOT →
    strands_robots.utils.
  • Deprecated, with a warning now: curobo, moveit2, kimodo, protomotions and in-process
    GR00T are removed in 0.7 (#4123); Device Connect (Robot(...).run(), strands_robots.device_connect,
    robot_mesh's rpc action, [device-connect]) is removed in 0.7 (#4132); a driver's
    start_task(policy_provider=) is removed in 0.8 - build the policy and pass policy_object=
    (#4127).
  • Floors: strands-agents>=1.57.2 (the dashboard voice console drives strands.bidi),
    Pillow>=12.3.0, websockets>=17.1, boto3/botocore>=1.43.17; uvicorn[standard];
    [dashboard] now pulls pyserial, opencv-python-headless, huggingface_hub. [voice]
    conflicts with [mesh-iot]/[all], [sim-mjlab] with [inference]/[cosmos3-service]/[all]
    (declared in tool.uv.conflicts; see Upgrade).
  • IsaacSimulation refuses reset(env_ids=...) it cannot perform, a joint write outside the
    joint's range, and a scene PhysX's tensor view no longer covers (#3343, #4343); send_action
    refuses a batch with an unknown key whole, before anything moves (#4486); Robot(mode="sim")
    refuses every hardware keyword (#4485); a pre-built policy_object beside policy_provider is
    refused (#4523).

New robots, drivers and policies

Registry 75 → 157 (list_robots()): +80 robot_descriptions URDF robots (#4211, 79 build),
yahboom_m3pro (#3927), mujoco_humanoid (#4202), lekiwi_sim (#4491). Categories now: arm 63 ·
humanoid 37 · mobile 18 · mobile_manip 16 · hand 11 · bimanual 9 · aerial 2 · expressive 1;
145 with a simulation asset.

Native drivers (strands_robots.drivers): 25 → 40 robots. New: spot (#4500) · kuka_iiwa
(#4525) · kinova_gen3 (#4519) · xarm7 (#4478) · stretch, stretch3 (#4480) · rby1 (#4479)
· unitree_h1, unitree_h1_2, b2 (#4502, #4510, #4531) · yahboom_m3pro (#3941) · koch on a
real Dynamixel bus (#4122) · ur3e ur7e ur8long ur12e ur15 ur16e ur18 ur20 ur30 beside ur5e/
ur10e (#4513). Gone: the Dynamixel stub's five (#4097). list_driver_coverage() is generated
into the docs catalog (#4071).

Policy providers (16; +4 −2):

Provider Extra What
holosoma [holosoma] Amazon FAR G1 locomotion, fastsac/ppo ONNX, weights from the Hub mirror (#4249)
wbc_latent [wbc] SONIC motion tokens from a VLA → 29 G1 joint targets (#4251)
rsl_rl_onnx [sim-mjlab] / core onnxruntime an rsl_rl actor exported from mjlab, on mujoco, mjlab or hardware (#4229)
flux3_action (f3a) [flux3] (empty; installs from GitHub) Black Forest Labs FLUX 3 Action, SO-101 checkpoint (#4199)

Removed groot (#4256), lerobot_async (#4091). rl loads rsl_rl runs and Hub ids (#4440);
lerobot_local gains the groot policy type through [groot] (#4256), the raw pi0/pi05 path
(#4203), panda_droid velocity actions (#4428).

Simulation backends: mjlab joins mujoco, newton, isaac (#4229); the Isaac backend is
an AgentTool (#4361) and imports MJCF (#3343).

Trainers: isaaclab (rsl_rl / skrl / rl_games) (#4227, #4436), rsl_rl on mjlab
(#4229); metrics.jsonl on every RL trainer (#4427).


Security

  • The lockfile clears 47 of the 49 open Dependabot advisories (#4093) and the last torch /
    transformers / setuptools three (#4185); urllib3>=2.8.0 closes GHSA-8988-9cw3-xx77,
    GHSA-vxq7-64xx-v4gw and GHSA-gh4c-6fx4-qh6g (#4447); Pillow>=12.3.0 clears eleven HIGH
    advisories (core floor).
  • CodeQL scans the package, not the test suites (#4135), and every alert it raised under
    dashboard (23 + 8 frontend advisories, #4136, #4208), mesh (29, #4137), simulation (#4139),
    dataset_recorder/assets/registry/policies (#4138, #4184) is closed.
  • Dashboard session and origin. The passkey session lives only in the HttpOnly cookie and
    every response carries a Content-Security-Policy (#4308); a ?token= in the URL is redeemed
    against the backend, never believed (#4302), and a ?backend= never carries the token to a new
    host (#4297); a WebSocket handshake is admitted by its Origin before any credential is read
    (#4311); removing a passkey ends its sessions (#4309); a settings write cannot plant the standing
    bearer (#4301); a fresh install's open posture needs the bootstrap proof (#4298); the env view
    masks by allowlist (#4307); thumbnails live in a private directory and the route never follows a
    link (#4306); a dashboard-spawned real arm never starts without wire auth (#4312).
  • Mesh. An ACL subject bound to interfaces: ["*"] reads as wide open (#3964); an ACL glob over
    the command or safety plane reads as permissive to the refuse-to-start gate (#4327); a retained
    command is refused, not executed at every boot (#4374); the resume proof is no longer an offline
    oracle for the override code (#4321); a peer id is a command target only if it passes the
    inbound charset rule (#4324); a mesh RPC reply is attributed to the session that sent it (#4303).
  • IoT. Robot certificates obey fleet stops and originate none unless designated (#4317);
    the private key is generated on the robot and never leaves it (#4242); child peers have their
    own policy (#4346).

Dependency floors

Dependency v0.5.2 v0.5.3 Reason
strands-agents >=1.13.0,<2 >=1.57.2,<2 strands.bidi (dashboard voice console, #4456)
Pillow >=8.0.0,<13 >=12.3.0,<13 eleven HIGH advisories cleared at 12.3.0
websockets ([inference], [cosmos3-service]) >=17.0 >=17.1 legacy=True on sync.client.connect (#4205)
boto3 / botocore >=1.34.0,<2 >=1.43.17,<2 IoT Direct Messaging API (#4242)
uvicorn ([dashboard]) >=0.27,<1 uvicorn[standard]>=0.27,<1 WebSocket speedups
new extras - [sim-mjlab] mjlab >=1.6,<2, mujoco/mujoco-warp >=3.11,<3.12, warp-lang >=1.14 · [sim-urdf] trimesh >=4,<6, pycollada, xacrodoc · [foxglove] foxglove-sdk >=0.25.1,<0.28, mcap >=1.5 · [holosoma] onnxruntime, huggingface_hub >=1.5 · [groot] lerobot[groot] · [voice] strands-agents[bidi] · [spot] bosdyn-client >=5,<6 · [rby1] rby1-sdk >=0.10,<0.11 · [xarm] xarm-python-sdk >=1.18,<2 · [flux3] (empty) #4229, #4211, #4277, #4249, #4256, #4456, #4500, #4479, #4478, #4199
[dashboard] adds - pyserial>=3.5, opencv-python-headless>=4.8, huggingface_hub>=1.5 devices tab, cameras, Hub search (#4426)
[dev] adds - pytest-xdist>=3,<4 distributed suite (#3992)
removed extras groot-service (pyzmq, msgpack), lerobot-async - #4256, #4091
constraint floors (tool.uv.constraint-dependencies) - urllib3>=2.8.0, cryptography>=50, aiohttp>=3.14.3, mcp>=1.28.1, setuptools>=83, torch>=2.13, transformers>=5.10, torchvision>=0.28, torchcodec>=0.13, anyio>=4.14.2, gitpython>=3.1.59, autobahn>=26.7.1 Dependabot relock (#4093, #4185, #4447)
build hatchling hatchling<1.32 1.32 emits Metadata-Version 2.5, which the PyPI publisher's twine rejected on the v0.5.2 publish

requires-python stays >=3.12. [sim-mjlab] stays out of [all] (mjlab needs torch>=2.14,
[lerobot] caps torch<2.12) and conflicts with [inference]/[cosmos3-service] (mjlab's viser
pins websockets<17); [voice] conflicts with [mesh-iot] and [all].


Fixed

431 Fixed fragments in changelog.d/ (381 fix: PRs). The theme holds - refuse, don't
guess
- and this release adds its corollary: when the library refuses, it names the spelling you
meant. The user-visible ones:

  • Isaac. Cameras show the state the simulation is in, a recorded episode is the rollout that
    ran and no episode opens on a black frame (#4409, #4358, #4353); render() honours its size
    (#4288); the Panda gripper works (#4341); MJCF robots spawn where asked without a second floor,
    keep gains, joint names and damping, and their motors are torques (#4359, #4271, #4272, #4345);
    replicate builds the fleet it reports, one grid cell each (#3343, #4292); reset() leaves the
    scene at rest, reset(env_ids=) is refused rather than resetting everything (#4350, #3343);
    after remove_robot/remove_object calls say "reset() first" instead of crashing (#4290,
    #4281); joint writes outside range are refused and success stops once physics diverged (#4343);
    apply_force spins the right way (#4421); cameras see objects closer than 1 m, mount on a link
    with parent_body, and warm up without logging errors (#4222, #4407, #4237); fov is vertical
    like MuJoCo (#4113); the floor is clean (#4424); aerial robots fly and multi-articulation robots
    load every arm (#4420, #4382); is_available() needs no torch, cuda:0 is not claimed while
    PhysX solves on the CPU, a failed create_world leaves no live World, a NaN delta-EEF never
    reaches PhysX (#3343); the unit suite never boots Kit (#4241); the sim-isaac extra installs the
    IK stack (#4245); two concurrent MJCF conversions get their own staging (#3973); headless render
    says its frames are blank and a refused recording names the reason (#4274, #4276).
  • Isaac Lab / RL. Export records the deploy contract or refuses (#4402, #4453, #4373); jobs
    stop at timeout_s, a relative jobs dir works, export takes the asked-for task (#4436); every
    TrainSpec field is forwarded or refused (#4436); a run says why it failed and whether it solved
    its task (#4287); rl policies no longer truncate an observation (#4373); FastSAC/FastTD3/PPO
    keep a curve (#4427).
  • Policies / inference. lerobot_local: pi0/pi0.5 keep their processor pipeline, run on the
    GPU with torch.compile off unless asked, normalize with supplied stats, and pi0-FAST loads with
    its own (#4404, #4403, #4406); the raw obs/action path for pi0/pi05 (#4203); a one-camera robot
    reaches a one-view checkpoint and a mixed observation routes the camera (#4065, #3920); an unknown
    embodiment= is refused before any download, a wider action head drives under dim_policy with
    the width judged first, actions far outside the checkpoint's range are flagged, a degree-trained
    SO-arm checkpoint no longer drives a sim arm into its limits, and state_units/action_units
    are refused as constructor keywords naming where they go (#4366, #4376, #4439, #4372);
    unit frames an embodiment cannot convert are refused and a child inherits its parent's (#3937,
    #3949, #3934, #3981). cosmos3 declares the chunk it serves, binds joint state from the
    observation, refuses before dialling a configuration no actuator would receive, and survives a
    dead socket (#4383, #3898, #4385, #4197). wbc refuses a wrong-family checkpoint where it is
    opened, a SONIC stack before downloading it, builds from a Hub id, and wbc_gait names the
    gait-clock weights it needs (#3888, #4355, #4369, #4260). remote reports an unusable remote
    instead of raising past its envelope and refuses a listening port that does not speak WebSocket
    (#4492, #3917). A provider's constructor verdict is a status=error envelope on every rollout
    surface (#4362), the run_policy tool judges the policy before it wipes dataset_root (#4360),
    a multi-episode run resets the policy every episode (#4199), a chunk policy without RTC runs
    synchronously by default (#4408), run_multi_policy steps one control period (#4398), and every
    "does not read the instruction" note describes your policy, not the mock (#3912, #3932, #3940,
    #4223). Planners plan from the robot's own observation (#3876, #3885); cuRobo refuses a pose goal
    beside a joint goal (#3901).
  • Drivers / hardware. Every hardware rollout entry point runs one preflight before it builds
    the policy (#4108, #3967, #3972); a stop pressed between start_task and the rollout is honoured
    (#4104); a serial arm with no usable port is refused at construction on both drivers (#4258); a
    native driver refuses a keyword it never read (#3957); status answers one envelope on every
    driver (#4230); the real-arm tool refuses an input outside its schema and takes policy_config
    (#4389, #4141); a Go2 partial action enables the joints it omits (#3887); an H1 refusal names the
    H1's mode (#4505); Feetech torque sweeps pair Lock with Torque_Enable, Present_Position
    carries its sign on the pose tool, and degree scale comes from the arm's calibration (#3858,
    #3863, #3868); the Crazyflie receives telemetry after connect (#3427); EarthRover's default SDK
    port and timed move (#3828, #3857); a Wireless Reachy Mini connects (#3852); a motion grant is
    bound to the calibration the operator approved it under (#4320); a lerobot arm's connect from
    the dashboard no longer fails "Port is in use!" and the spawn actually connects (#4457); the
    Unitree SDK refusal installs the DDS binding (#3844); a real arm's render saves the colour the
    camera saw (#3830).
  • Mesh / IoT. See Highlights for #4326 #4346 #4351 #4370 #4371 #4317 #4098 #3965. Also: a mesh
    command key the peer does not read is refused (#4397); a stop acknowledged by a locked peer does
    not paint its card clear (#4328); a safety envelope with a non-finite t does not break the
    fleet view (#4330); a connect-only peer joins under the default mTLS posture and the shipped ACL
    templates let operator and robot discover each other (#4462, #4463); Robot(mesh=True) without
    [mesh] names the dependency (#4239); robot_mesh refuses an unknown action before it touches
    the mesh (#3874); the dashboard files a sample under the peer that published it (#4304), reports
    the camera rate the mesh publishes (#3205), and no wire knob is writable from its env editor
    (#4305); doctor lists all sixteen probes including IoT Direct and does not fail it for a
    running operator (#4296, #4352); a Git LFS pointer is not a mesh and LFS meshes download without
    git-lfs (#3984, #4422).
  • Simulation (MuJoCo / Newton). A diverged world is reported (#4399); move_to says when the
    grasp was left behind and no longer spends the solve on a slide/hinge base (#4400, #3928);
    set_joint_positions writes back the pose the simulator settled at a limit (#4451); add_robot
    names a robot that starts inside the ground, is refused while a recording is open, and
    keyframes carry the robot's namespace (#4467, #4015, #4009); a floating base on terrain is seated
    by measurement (#3922); a rollout no longer reads a transposed observation.state and
    joint-keyed actions are unmeasured coverage, not an undriven robot (#3851, #3913); send_action
    takes a registry joint label and LeKiwi's arm takes SO-100 labels (#4283, #4535); frame
    timestamps follow sim time, a closed recorder refuses a frame, a refused start_recording points
    at no dataset, get_recording_status reports saved episodes (#4401, #4318, #4481, #3902);
    inverse_dynamics reports one force per DOF (#3958); the mock clips to the range that holds its
    command and the out-of-range warning reads that range (#3956, #3953); grasped's gripper_prefix
    names the gripper body (#4475); a colour channel outside 0..1 is refused (#4504); open_viewer()
    on macOS names mjpython (#4378); list_robots(mode="sim") lists only robots Robot(name) can
    spawn (#4468); the sim ROS 2 bridge publishes only the cameras a robot carries (#4473); Newton
    keeps its contact budget on a rendered rollout, carries joint velocity, keeps MJCF servo gains,
    and a floating base drives the joints its actions name (#3904, #3895, #3862, #4476); Go2
    benchmarks score a folded stance as the fall it is (#3918, #3970).
  • Dashboard. The tab the address names is the tab shown (#3909); one Fleet card per spawned
    robot and a confirmed resume clears the badge (#4142); the sim e-stop no longer claims it locked
    the fleet and the e-stop route is not reported dark (#4144, #4143); a recorder failure arrives as
    a reason, never exception text (#4208); USB auto-spawn proposes a remembered real arm instead of
    starting it (#4316); /api/collect forwards only a policy_config the wire accepts (#4314); a
    consent answer is the JSON boolean or a refusal (#4315); the tool factory and motion gate read one
    precedence, metal first (#4333); Hub search works on the documented install (#4426); the local
    address banner never offers an address the dashboard is not listening on (#4458); voice console
    on strands-agents 1.57.2 (#4456).
  • Registry / assets. Two concurrent fetches no longer corrupt one cache (#3995); a
    download_assets that fetched nothing is not a success (#3997); STRANDS_BASE_DIR takes the
    asset cache with it (#4073); register_robot refuses a built-in's name unless overwrite=True
    and names what it collides with (#4483, #4526); an unknown robot says when user_robots.json is
    broken and a key no lookup reaches is refused (#4511, #4176); a description short name loads the
    curated robot (#4334); open_duck_mini names the model file upstream has (#4417); a simulated
    robot's registry joints is the count get_robot_state reports (#4363).
  • Tools / packaging / doctor. Robot() refuses a non-string name with the promised
    ValueError and an unknown keyword names the one you meant (#4221, #4484); a positive-number
    refusal names the whole domain (#4347); verify-dataset tells a wrong path from an unfinished
    recording (#4507); stream_dataset(episodes=) streams what it was asked (#4027); the detached
    lerobot launchers run this interpreter (#4259); lerobot_train's dtype pin follows the installed
    lerobot (#4477); doctor fails a lerobot below 0.6.1, is not fooled by a directory named
    lerobot, and keeps Warp's banner out of its report (#4293, #4019, #4416); reading the pose
    library or importing a session tool creates no directory in your cwd (#4045, #4037); the PyPI
    publish can be re-run against a tag (#3832).

Docs & CI

  • The site is written again from the tree (#4112) and then teaches the architecture, not
    the API
    (#4435): eight nav sections and a landing page that shows the robot (#3833, #4003,
    #4092), one product look - a 199-line stylesheet, paper and true dark (#3836), a MuJoCo viewer on
    every robot page (#4112, #4209, #4207), the robot pages generated with chips, footer, action keys
    and a first fence that spawns the robot resting on the ground (#4125, #4130, #4178, #4517, #4518),
    the agent tool reference generated from the tool functions (#3835), the driver coverage matrix
    generated at build (#4071), ten runnable recipes with their output (#4094), learned policies on
    real robots said where readers look (#4234), the 10,542-word simulation reference split into five
    table-first pages (#3834), and a docs sweep that makes the Start, Learn and Reference pages say
    what the code does (#4286, #4289, #4291). The README redraws its three figures in the
    strandsagents.com design and its quickstart puts the red cube in the scene (#4186, #4498).
  • Docs graders. The word budget grades the whole site, zero slack (#4063); every python and
    bash fence under docs/ is checked statically (#4282) and fences the site does not run render as
    plain Python without a chip (#4455); Hub ids on pages resolve (#4367); content images are lazy in
    the HTML (#4079).
  • CI. The test suite runs distributed (pytest -n logical) and a PR's required check with it
    (#3992, #4006); whole-tree graders parse each package file once per process (#4332, #4425,
    #3884); CodeQL scans the package only (#4135); the publish workflow grants the reusable job what
    it asks and can re-run against a tag (#3832); hatchling<1.32 so the PyPI publisher's twine
    accepts the metadata; the test session leaves every STRANDS_* variable as it found it (#4225)
    and a passed test's tmp_path is removed at teardown (#4062).

Upgrade

uv venv --python 3.12 && source .venv/bin/activate
uv pip install -U "strands-robots==0.5.3"
uv pip install -U "strands-robots[sim-mujoco,sim-urdf]==0.5.3"        # 157 robots in MuJoCo
uv pip install -U "strands-robots[dashboard,mesh]==0.5.3"              # python -m strands_robots dashboard
uv pip install -U "strands-robots[mesh-iot]==0.5.3"                    # AWS IoT Core direct messaging
uv pip install -U "strands-robots[holosoma,wbc]==0.5.3"                # G1 locomotion providers
uv pip install -U "strands-robots[foxglove]==0.5.3"                    # Robot(name, foxglove=True)
uv pip install    "strands-robots[sim-mjlab]==0.5.3"                   # OWN venv: torch>=2.14, out of [all]

Before upgrading, check for: create_policy("groot", …) / gr00t_inference / Gr00tTrainer /
[groot-service] (use lerobot_local with policy_type="groot" behind [groot], or remote);
lerobot_async (lerobot's own policy_server); Robot(mode="real") calls that relied on the
lerobot default - they now build the native driver, add driver="lerobot" to keep the old path;
driver="strands" on aloha/vx300s/wx250s/trossen_wxai/dynamixel_2r (the stub is gone,
use lerobot); dashboard scripts that used the in-process Sim tab or its routes (spawn a sim peer);
imports from strands_robots.streaming_dataset, strands_robots.mesh (ROS 2 bases) or the old
Reachy transport path; and DeprecationWarnings from curobo/moveit2/kimodo/protomotions,
Device Connect and start_task(policy_provider=) - each names its replacement. One screen of this:
UPGRADE.md. A fork still builds as 0.1.dev… until git fetch upstream --tags (unchanged since v0.5.2).


Contributors

@cagataycali, @shipitfast, @Vivek0712, @rmncardoso, @dimwael, @tmoreton, @pgrayy - and
@yinsong1986, who reviewed every one of the 621.