Repository navigation
strands robots v0.5.2
strands-robots v0.5.2
Release date: 2026-09-16 Previous release: v0.5.1 (2026-08-06)
1500+ commits over six weeks - the biggest release since v0.5.0 and the first that
drives real robots without lerobot in the middle. A native driver layer (Unitree G1/Go2,
Booster T1, Franka, UR, Robotiq, Crazyflie, EarthRover, Reachy Mini, Pollen Microduck,
Feetech/Dynamixel buses), the Microduck as a sim + real robot with its own policy provider,
the operator-dashboard modules with a human-in-the-loop gate in front of every call that can
move hardware, the security pass (8 findings, 11 PRs, all merged), and a late pass
in which the tools stopped telling the agent things that were not true.vera,
motionbricks, the vendored LIBERO suite andlerobot_calibrateleave.
strands-robots is the robot-control library for Strands Agents:
policy inference, teleoperation, recording, and simulation (MuJoCo, Newton, Isaac) behind one
agent-callable tool surface - and, from this release, native drivers for the robots themselves.
Highlights
-
Native drivers.
Robot(name, mode="real", driver="strands")builds a driver the library
owns instead of a lerobotRobotConfig(#2734): Unitree G1 over CycloneDDS with a 500 Hz
run_policyloop (#2739, #2779) and Go2 (#3039), Booster T1 (#3045), Franka
panda/fr3 over FCI (#3040), UR5e/UR10e over RTDE (#3058), Robotiq 2F-85 (#3053),
Crazyflie (#3046), EarthRover Mini+ (#3081), Reachy Mini (#2762), and the
Feetech (#2786, #3043) / Dynamixel (#2750) bus codecs that move an SO-100/101.
list_driver_coverage()says which driver builds which robot (#3042).arm = Robot("so101", mode="real", port="/dev/ttyACM0", driver="strands")
-
Pollen Microduck, sim + real. The 14-DOF biped joins the registry (#2856) with a
microduckpolicy provider for Pollen's ONNX skills ([microduck], #2862), weights fetched
by bare name frompollen-robotics/microduck-policies(#3704), a nativerobotddriver for
the real duck, andexamples/microduck/render_video.py --scenefor the roller and ball scenes
(#2922) - the ball now sits where the kick was trained to find it (#3712) and the clip says
when the duck runs off the rendered floor (#3713).
alpha_walking.onnx at 0.3 m/s · roller.onnx on scene_rollers.xml, headless on an L40S.
-
The tool tells the agent the truth. Eight agent prompts replayed against the same sim arm
before and after this release's last fixes: 90 tool calls / 15 errors → 63 / 4. Nothing got
smarter; replies that were true-but-useless got honest. The README quickstart -
Robot("so100"), "pick up the red cube" - used to end:[tool←] 'red_cube' attached to 'so100/Fixed_Jaw' (mode=kinematic, relpos=[0.0098, -0.028, 0.0621]) [llm] 🎉 The red cube has been successfully picked up! ← it was 7 cm below the fingersand now ends:
[tool←] set_gripper: … Closed on 'red_cube' (6 contacts). [tool←] 'red_cube' attached to 'so100/Fixed_Jaw' … The bodies are touching. [llm] Attached the cube (gap = 0.0 m, touching); cube at z=0.106 m.set_grippernames what it closed on or says "Closed on nothing … move_to the object
first" (#3705);attach_bodiesmeasures the gap and calls a weld a weld (#3703); SO-arm
joints answer toshoulder_pan, not servo"2"(#3694);stepunder a recording records
(#3724); an evaluation with no success criterion prints no0%to quote (#3700); the sim
description says the world already exists (#3687);MockPolicystays insidectrlrange
(#3688); a registry entry can declare the tool point a Menagerie model lacks -so100now
lands the low targets it missed 0/3 (#3728). -
A paused real-robot call tells the script how to resume (#3719). Every call that would
move real hardware raises the SDK's interrupt; itsreason["how_to_answer"]gives the resume
form and the exact..._COMMAND_ALLOW=<value>that pre-approves that one command for an
unattended run.docs/security.md→ Answering a command gate from a script.result = agent("Rotate the wrist 5 degrees.") while result.stop_reason == "interrupt": for q in result.interrupts: print(q.reason["warning"], q.reason["how_to_answer"]) result = agent([{"interruptResponse": {"interruptId": q.id, "response": input("approve? [y/N] ")}} for q in result.interrupts])
-
G1 in simulation, end to end. The GR00T decoupled-WBC Balance+Walk pair drives the
Menagerie G1 throughrun_policy(policy_provider="wbc", …): 0 → 2.8 m in 8 s at 0.45 m/s.
scripted_g1.pykeeps every segment it records (#3708);docs/policies/wbc.mdsays to mount a
camera on the pelvis before recording - the fixed view loses a walking G1 after 1.5 m (#3716).
- The operator dashboard serves (
[dashboard]).python -m strands_robots dashboardis back
(#3695) on the rails that landed as atomic slices: passkey/WebAuthn auth (#3050, hardened
#3433/#3509, log-forgery-proof #3710), settings with redaction/timeouts/bounded cache (#3051,
#3242), aLockouttype for what a UI may say about an e-stop (#3185), a policy preflight that
reports what it verified (#3186), and the HITL gate - aBeforeToolCallEventhook that pauses
the agent on any call that would move a real robot (#3184). Stop verbs are never gated. It fails
closed without its auth store and refuses cross-origin or DNS-rebound callers. Fleet and Sim tabs are
live: one MuJoCo session per thread (max 4), MJPEG frames, telemetry over WebSocket, an e-stop
that answers 423 under lockout and is never refused (#3726).
- Security hardening (ROBOTS-52). Eight findings on the actuation path, eleven PRs, all merged:
nothing reaches a real actuator without an operator's answer or an explicit envelope. See below.
Also new
- Two robots of one type in one Agent -
Robot("so101", tool_name="left_arm")(#3731); a
bimanual pair or a real arm beside its sim twin no longer dies onTool name … already exists. - Dependencies say what to install. Every optional-dependency door refuses with the install
that fixes it, graded against what the extra really pulls in (#3777):unitree_sdk2pynames the
recipe at all 16 sites - PyPI's wheel does not import, so no extra can declare it (#3764);
Robot(mode="real")on a core install names[lerobot](#3769);serial_tool/pose_toolname
pyserial (#3770); the MoveIt 2 sidecar names the system ROS 2 (#3780). New[rl]extra for
create_trainer("ppo")(#3776);[ros2]stops promising a Linux-aarch64 wheel no cyclonedds
release ever shipped (#3782); the build reads its version from release tags only, so a fork's
own tags no longer breakpip install -e .(#3779). - G1 agent surface - 25 verbs behind one
use_unitreedispatcher instead of 120 names (#3037,
#3070, −7,825 LOC): posture transitions,move_velocity,set_fsm, gestures, SLAM/lidar/BMS/IMU;
every mutative verb asks an operator first (#3418, #3508). - Policy providers -
kimodotext-to-motion for the G1 (#2270),protomotionsONNX GTP tracker
(#2287),rldeploys a FastSAC/FastTD3 checkpoint (#3414) withFastTd3Trainer(#3206); a policy
can declare the body poses it needs (#2271). - Mesh fleets - one
MobileBaseRobot+Transportcontract (#2696),AckermannRosRobotfor ROS 2
cars like the DeepRacer (#2689), peer retention with freshness bounds (#3352), five fleet examples
with a read-only Rerun dashboard (#2197–#2506). - Isaac parity - IK
move_to(#2218),set_gripper/rotate_wrist(#2200), synchronized
run_multi_policywith merged recording (#2178, #2212, base-class contract #2164), LIBERO objects
as real meshes (#2498). - Evaluation that answers the question -
pass_hat_kestimated as C(c,k)/C(n,k), not
success_rate**k(#3521); a rollout that never commanded the robot is an error (#3680). - Works on Jetson Thor - JetPack R38.2.2 / torch 2.11+cu130:
doctorall green, SO-101 sim
rollout rendered in 9 s; the Jetson page drops thenumpy<2pre-pin lerobot ≥0.6 undid (#3717).
Breaking changes
- Removed:
lerobot_calibratetool - uselerobot-calibrate(#3386);veraandmotionbricks
providers (#3389, #3419); vendored LIBEROstrands_robots.benchmarkswith its
libero/robosuite/gymnasiumpins - usephysical-intelligence/libero+ the predicate DSL,
examples/libero/run.pystays (#3056, #2497);strands_robots.training.reward(#3443); the
episode-augmentation transforms added and withdrawn this cycle (#3426). - Mutative tools pause for an operator.
use_unitree,serial_tool,pose_tool, theRobot
tool'sexecute/start, and every ROS 2 command verb raise the SDK's interrupt before real
actuators move. Headless callers answer it (how_to_answer, #3719) or run in sim. device_connect: an unset RPC allowlist admits nobody (#3423); an unauthenticated transport is
refused (#3432). Set the allowlist explicitly.strands-agentsfloor>=1.13.0(BeforeToolCallEvent) · predicate DSL drops the<name>_main
fallback (#3384) ·move_todefaulttol0.01 → 0.015 m ·read_dataset_episode_indicesmoved to
strands_robots.verify_dataset(#3446) ·lerobot_localdrops thenorm_stats.jsonfallback (#3411).
New robots, drivers and policies
Registry 72 → 74: microduck (MuJoCo, 3 scenes; robotd real) (#2856, #2862) ·
bi_so_follower (aliases bi_so100/bi_so101; lerobot bimanual 2× SO follower) (#3470).
Native drivers (strands_robots.drivers, new): g1, go2 (CycloneDDS) · booster · franka
· ur · robotiq · crazyflie · earthrover · reachy (14 agent verbs, #3082) · microduck ·
feetech · dynamixel. The driver registry is joined against the robot registry so a coverage gap
is derived, not declared (#3042).
Policy providers (14; +4 −2):
| Provider | Extra | What |
|---|---|---|
microduck |
[microduck] |
Pollen ONNX skills; episodic behaviours auto-return (#2906) |
kimodo |
[kimodo] |
text-to-motion diffusion, G1 (#2270, #2279) |
protomotions |
[protomotions] |
ONNX GTP tracker, G1 (#2287) |
rl |
core | deploy a FastSac/FastTd3 checkpoint (#3414) |
Removed vera, motionbricks. cosmos3 gains OpenArm (#2492); a sagemaker trainer submits a
TrainSpec as a managed job (#2488).
Simulation: articulated-container pouring task (#2483); set_obs_noise in the tool schema, and an
unadvertised action is refused at the agent boundary (#2095); scene-lit compositing and view-dependent
GS backgrounds (#2331, #2326); VLM judge for episode labeling (#2486);
examples/18_so101_pick_and_lift.py - a reference pick that actually lifts (#2685).
Security hardening (ROBOTS-52)
Eight findings, eleven PRs, merged 2026-09-10/11. One rule: nothing reaches a real actuator
without an operator's answer or an explicit envelope.
| Finding | PR | Fix |
|---|---|---|
| F-001 | #3418, #3508 | use_unitree asks before a mutative RPC; reads are an allowlist, everything else gated; private _Call refused |
| F-003 | #3423, #3432 | device_connect: unset allowlist admits nobody; plaintext zenoh refused |
| F-004 | #3425 | G1Driver.send_action refuses (not clamps) a target outside the per-joint URDF envelope |
| F-005 | #3428 | target_velocity held to ±2 m/s, ±2 rad/s on the wire and in WBC |
| F-007 | #3433, #3509 | First passkey enrollment refuses proxied loopback; needs a 0600 bootstrap token on disk |
| F-009 / F-010 / F-011 | #3420 / #3430 / #3431 | serial_tool, pose_tool, and the Robot tool's execute/start ask an operator first |
Also: every ROS 2 transport's command verbs are gated (#2313, #2693); control characters refused in
wire instructions (#2403); use_rosbridge refuses a call missing type/topic before dialing
(#3682); every workflow uses: SHA-pinned (#2574); pose_tool scales the gripper off its range
like every other joint (#3735).
Dependency floors
| Dependency | v0.5.1 | v0.5.2 | Reason |
|---|---|---|---|
strands-agents |
>=1.0.0,<2 |
>=1.13.0,<2 |
BeforeToolCallEvent (HITL gate) |
eclipse-zenoh ([mesh]) |
>=1.0.0,<2 |
>=1.6.1,<2 |
|
cyclonedds |
>=0.10.2,<1 |
>=0.10.2,<12 |
native G1/Go2 drivers |
mujoco-warp / mujoco ([sim-newton]) |
>=3.8 / >=3.2 |
>=3.11,<3.12 both |
newton>=1.5,<1.6 ABI |
robot_descriptions · websockets |
>=1.11 · >=12 |
>=1.23 · >=17 |
|
| new | - | fastapi, uvicorn, PyJWT, webauthn, python-multipart |
[dashboard] |
| new | - | ur-rtde, cflib, cbor2, onnxruntime |
[ur], [crazyflie], [microduck] |
| new | - | torch>=2, gymnasium>=0.29,<2 |
[rl] (#3776), in [all] |
| removed | libero, robosuite, gymnasium, gym-pusht, hydra-core, omegaconf, pytorch-lightning, … |
- | LIBERO / vera / motionbricks |
requires-python stays >=3.12.
Fixed
1,011 fix: commits; one theme - refuse, don't guess: a non-finite pose, a misspelled flag, a
posture given as "no", a camera that cannot render, is an error at the door, not a silent wrong
answer downstream. The user-visible ones:
- Simulation. Record-then-rollout works first try - an omitted rate follows the one in force
(#3791);stop_policyreturns once the robot is free and a bare call stops the only rollout
(#3743, #3788);run_policy/eval_policyrefused while another rollout drives the robot (#3741);
replay_episodereads the dataset this session recorded (#3786); a hardware-only kwarg on a sim
robot is refused instead of building a sim understatus=successwhile the real arm sits still
(#3740); a provider the constructor refuses is astatus=errorenvelope, not "started" (#3746);
the JSON schema names every knob the runtime accepts (#3781); every listed camera renders (#3683,
#3693, #3691); a joint write says its unit and a range refusal names the degrees→radians
conversion (#3723);get_robot_statesays which way the arm extends (#3727); amove_toor
rotate_wristthe servo could not finish names the contact pair or joint limit that stopped it
(#3733, #3754); relative scene paths land in~/.strands_robots/scenes, not your checkout
(#3711);render_dir=on the constructor (#3697);stop_when/benchmark clauses true at reset are
reported, never scored, and a clause that cannot fire is refused (#3633–#3642, #3497, #3502);
out-of-range or non-finite poses/seeds/scales refused everywhere (#3429, #3462, #2740–#2881);
boolean posture flags checked as booleans (#3365–#3575);start_recordingnames its cameras and
refuses one it cannot render (#3730, #3494); EGL renderers closed before the display terminates
(#3706). - Isaac / Newton. A recorded rollout leaves the robot idle (#3341); undriven state columns filled
(#2844); IK solves on the bound model (#3220); stage paths are USD prim paths (#3269). - Policies / inference.
lerobot_localcamera pre-flight compares against the checkpoint's own
config.json(#3737);camera_key_mapon the declarative path (#3555); RTC on public flow-matching
checkpoints (#3474);create_policy("groot", hots=…)refuses the typo instead of dialing localhost
(#3747); GR00T's server fails fast with the URI, timeout and a TCP probe (#3742), mounts checkpoints
read-only (#3793) and acceptshf_local_dirunder the caller's home (#3755); a task envelope says
when the policy never read the instruction it echoes (#3763); cuRobo/ProtoMotions remedies name
installs that work (#3777);CompositePolicychildren never see an empty observation (#3372);
Microduck gravity slot, velocity gate and finite-observation checks (#3228, #2902, #2890). - Drivers / hardware. Feetech
Present_Positioncarries its sign (#3387), whole-armSYNC_READ
(#3393), torque confirmed by the servo (#3407), degrees against the arm's own calibration (#3397);
G1/Go2 telemetry fields the message lacks areNone, not zero (#3376–#3382), the write gate shuts
when the robot is not free (#3609); UR/Crazyflie/EarthRover/Reachy refuse what their wire cannot
carry (#3210, #3326, #3353, #3679, #3493). A doomed real-robot command is refused before the
operator is asked (#3753, #3756);lerobot_localwith no checkpoint is refused before the bus is
claimed (#3752); a bring-up that fails after connect releases the arm instead of leaving torque on
(#3778); a real robot'sstatusnames the device, its port, and each camera (#3748, #3790); a task
reports how long it actually ran (#3775). - Mesh / teleop. Posture warnings once per process (#3690); the discovery example reads the
typekey and waits a heartbeat (#3689);Mesh.tellsays anexecuteneeds a policy (#3750);
emergency_stophalts the issuer's own robot andstopsurvives lockout (#3450); non-finite wire
timestamps are neither stale nor future (#3243);JointStatenames its joints (#3648); DDS
credentials that are not strings refused (#3666, #3674). - Tools / training.
serial_tool/pose_toolname pyserial (#3770);lerobot_trainrefuses a
topology it cannot honour (#3121); session/calibration/pose files written whole or not at all
(#3328–#3348); a torchcodec wheel that cannot load costs one warning, not lerobot's 150-line wall
(#3792); recorder features are HWC andadd_framerefuses a missing column (#3451, #3449);
evaluate()says whethersuccess_ratemeasured the policy (#3639);[rl]extra (#3776). - Dashboard. A credential store that cannot serve is recovered, not a 500 (#3738); no
header can forge a log line (#3710); passkey ceremonies verified against the connection (#3080);
a one-shot motion grant names the call approved (#3254). - doctor / packaging / registry / rendering.
doctorreaches the runtime's verdict per row and
fits the torch remedy to the board (#3458, #3454, #2893); version from release tags only (#3779);
torchcodeclocked to 0.11.1 for torch 2.11 (#3424);[ros2]aarch64 story honest (#3601, #3782);
alohais Dynamixel ViperX, not Feetech (#3406); a symlink in a downloaded robot tree is never
followed into the cache (#3325); a composited frame is its own camera's size (#3140); an MP4 request
without a backend is refused as the missing dependency (#3320).
Docs & CI
- README is 120 lines (#3469); every environment variable is documented and a grader says so
(#3313). Install docs create the venv first (#3696). Robot pages are generated cards from
robots.json(#3470). Quickstart step 1 records a real arm withlerobot_teleoperate(#3749) and
the closing line names what steps 4–5 need (#3751). New:docs/policies/microduck.md(#2927), the
CA-pin rotation runbook (#2576), mesh security knobs (#2945–#3003), a Linux-aarch64 section in
docs/rtps-integration.md(#3782), "Installing the Unitree SDK" indocs/humanoids.md(#3764).
Removed: dashboard pages that launched a command the package lacked (#3472). - CI. The required check evaluates the merge commit (#3395); every
uses:is SHA-pinned
(#2574); every job is bounded (#2241);changelog.d/fragments are the only way a PR logs a change,
placeholders refused with one owner of the rule (#3715, #3721, #3722);CHANGELOG.mdis assembled at
tag time withpython scripts/assemble_changelog.py --apply.
Upgrade
uv venv --python 3.12 && source .venv/bin/activate # uv pip install needs an active venv
uv pip install -U "strands-robots==0.5.2"
uv pip install -U "strands-robots[sim-mujoco,microduck]==0.5.2" # sim + the duck (weights from the Hub on first use)
uv pip install -U "strands-robots[dashboard]==0.5.2" # then: python -m strands_robots dashboard
UV_TORCH_BACKEND=auto uv pip install "strands-robots[sim-mujoco,lerobot]==0.5.2" # Jetson / aarch64Before upgrading, check for: lerobot_calibrate, vera/motionbricks, strands_robots.benchmarks,
strands_robots.training.reward, and any headless caller of use_unitree / serial_tool /
pose_tool / the Robot tool on real hardware - those now raise an interrupt an operator must
answer, and the interrupt says how (reason["how_to_answer"]). One screen of this: UPGRADE.md.
From a clone or fork, the version comes from git describe --match 'v[0-9]*'; with no release tag
reachable it builds as 0.1.dev… - git fetch upstream --tags restores the real number (#3779).
Contributors
@cagataycali, @yinsong1986, @sundargthb, @rmncardoso, @Vivek0712.