> **Pinned roadmap** -- releases, merged PRs, active work, what's next. > > **Current tag**: [`v0.5.1`](https://github.com/strands-labs/robots/releases/tag/v0.5.1) (shipped 2026-08-06) -- the latest published tag. Previous: [`v0.5.0`](https://github.com/strands-labs/robots/releases/tag/v0.5.0) (2026-08-04), [`v0.4.1`](https://github.com/strands-labs/robots/releases/tag/v0.4.1) (2026-07-01). > **State**: **`v0.5.0` (2026-08-04) and `v0.5.1` (2026-08-06) are both out**; the planned v0.4.2 patch was never cut, so the post-v0.4.1 wave shipped as a minor tag. `v0.5.0` published the Isaac Sim backend absorption (#1156, closing epic #1144), the remote policy inference client/server split (#1142), the floating-base state lane, the locomotion reward-term DSL composed into runnable go2/g1/t1 benchmarks, the terrain curriculum, the `describe()` discovery surface and the `lerobot>=0.6.0` floor (#1114). `v0.5.1` is a correctness-only patch over three days, headlined by the `lerobot>=0.6.1` floor that makes `stream_dataset(repo_type="bucket")` resolver-guaranteed rather than lucky (#1930). Since the **`v0.5.1`** tag a further **1,451 PRs** (#1987 -> #3750) have accrued on a fresh `[Unreleased]` wave -- **981 fixes + 158 test pins against 151 features**, 78.5% correctness. Its capability surfaces are the **native hardware-driver programme** (twelve buses across 25 registry robots, #2734 seam onward), the **agent-facing Unitree G1 verb surface** ported from `neon-the-g1` and then consolidated 79 modules -> 22 (#3037/#3070), two **humanoid policy providers** (`KimodoPolicy` #2270, `ProtoMotionsPolicy` #2287), **Isaac backend parity**, **photoreal 3DGS + GS-derived lighting**, the **VLM-judge** batch, **one drive contract for every mobile base** (#2689/#2696), the **dashboard** -- which in the newest band finally got a **server** (#3695), taking that package to fifteen modules plus a served `static/` bundle -- **FastTD3** (#3206), the **rollout observer lane** (#3351) and a **deployable RL checkpoint** through the policy factory (#3414). Roughly **116,000 lines** have come out across thirteen net-negative refactors, and **the newest band ends the featureless run at nine bands**: 73 PRs, **277 files, +18,945 / -682 -- net +18,263 lines**, the largest net-positive band this page has recorded, carrying **seven `feat`** commits after nine bands with none. The removals ledger is nonetheless unchanged for a fifth band (`vera`, `lerobot_calibrate`, `motionbricks`, `strands_robots.transforms`, the MolmoAct2 normalization port), the net-negative refactor table holds at thirteen, and for a fifth band the absence of a `!` or a `BREAKING CHANGE` trailer is correct rather than a gap -- nothing public left `main`. **It also closed the oldest small item on this page: the `0000-` fragment placeholder defect is fixed** (#3715/#3721/#3722), so `changelog.d/` holds none for the first time since they appeared and sequencing item 6 is retired. Its recurring fix idioms each have a section below: a stop reports its outcome rather than asserting one; a published budget is measured on a clock that cannot step; a shared value domain replaces a hand-rolled bound; a posture flag is read as a value, not by truthiness; a refusal names a remedy that works; the whole-tree graders grade the whole tree; a span that resolves to no work is refused; a cache is served only while its key still names what the entry is about; a run that stopped early states how much never ran; one owner answers one question; a verdict names the thing it was written for; a store is written whole, or the previous one is still there; a telemetry field the frame does not carry is not a reading; an operator is asked before a command reaches a real actuator (#3418/#3420/#3430/#3431, widened to every unknown verb by #3508); a docs page states each contract once; a value that can never be honored is refused at the entry point (thirteen fixes, #3502 the sharpest); a resource a failed bring-up adopted is released (#3519/#3522/#3526/#3530/#3532); an action naming a key the surface cannot carry is refused (#3534/#3537/#3539); a name is held to the alphabet its consumers can carry (#3557/#3558/#3559/#3565/#3570); a run that commanded nothing is refused rather than scored (#3584/#3608/#3612/#3613/#3620, and now at the rollout entry point itself by #3680); a shutdown that cannot complete does not keep the resource (#3590/#3596/#3602/#3603); an offline snapshot names what the live registry registers (#3614/#3616/#3618/#3623); a criterion already satisfied at reset is reported, not read as a result (#3629/#3633/#3637/#3639/#3642); a plan that commands nothing is a failure, not a plan (#3643/#3652/#3655/#3657); a ROS 2 name must be able to name a topic (#3628/#3635/#3636/#3638/#3648/#3651); a reader grades the size its own payload declares (#3627/#3650/#3658); a refusal applies nothing from its own call (#3622/#3625/#3645); and now the band's own four: **an unreadable reply names the peer, not the codec** (#3670/#3672/#3676/#3677), **a configuration entry that is not a string is refused** (#3666/#3667/#3674/#3679), **a motion that did not reach names what stopped it** (#3703/#3705/#3727/#3733) and **a camera name answers for the camera it names** (#3683/#3691/#3693/#3730). **The tracker moved again after a band in which nothing moved at all**: two issues were filed and closed inside the band against merged code (#3664 via #3666, #3684 via #3686) and two more were filed and are still open (#3734, #3744), so open issues went 3 -> 5. The review queue went **11 -> 21**, the largest this page has recorded, and the extra-approval remedy is now the board's normal path rather than a novelty: **seven heads read `APPROVED`** (#3729, #3743, #3746, #3749, #3751, #3752, #3753). What still waits on a second account is #3205, #3343, #3427, #3496, #3515, #3577, #3615, #3714, #3726 and #3737 -- each green, armed, every thread resolved, and therefore still `REVIEW_REQUIRED` under `require_extra_approval_for_unattributed_changes`. > **Status**: **CLOSED as completed 2026-08-14** -- this roadmap is retired. It stays pinned as the historical record of the v0.3.9 -> v0.4.0 -> v0.4.1 -> v0.5.0 -> v0.5.1 arc; live tracking is the `0.5.0` milestone and the open issues, not this document. Reconciled once more below so a pinned page does not read as current plan. > **Last synced**: 2026-09-16 against `main` @ #3750 -- **73 PRs** have merged since the previous marker, taking the wave to **1,451 PRs** (#1987 -> #3750) since the `v0.5.1` tag: 1,378 + 73 = 1,451. The band spans **#3661 -> #3750** with **two out-of-order merges** (#3661 and #3662, both rows on the previous Open-PRs table). Caveats for anyone recounting. **The counting streak broke again, and in the direction that under-reports**: the tip commit is **#3740** while **#3750** is the highest merged number, so a marker read off `git log`'s newest subject is ten short. Last sync's marker was also its tip, so a range taken from the #3660 commit yields exactly **73** commits for 73 new pull requests -- no trap this time. **The clean sweep broke after two bands, and it broke on the shape #3169 measured**: seventeen numbers inside #3661 -> #3750 are absent from `main` -- nine open pull requests (#3673, #3714, #3726, #3729, #3737, #3739, #3743, #3746, #3749), two open issues (#3734, #3744), two issues closed in band against merged code (#3664, #3684), and **four pull requests closed unmerged, every one a second implementation** (#3665 against merged #3666, #3671 against merged #3672, #3736 against merged #3731, and #3732 descoped into the fix #3733 plus the tracker item #3734) -- instances **sixteen to nineteen** of that shape, after two bands at zero. **The `0000-` placeholder defect is closed**: `changelog.d/` holds **none**, where it held four for three syncs, and the guard now refuses the number at every surface (#3715/#3721). One bookkeeping stray remains: #3702's body carries `Closes #767`, which names a pull request merged in the v0.4.1 wave rather than an issue, so it closed nothing. The two older caveats still stand: one wave commit carries no PR reference at all (`cff130dc`, a direct `test(isaac)` push), so `git log` shows **1,452** commits for 1,451 PRs -- measured exactly at this marker -- and #2801's squash title carries a shell-quoting artifact (`(#'2801')`), so a naive `(#NNNN)` grep returns 1,450. ## Where we are (2026-09-16 reconciliation) **No new tag since `v0.5.1` (2026-08-06)** -- it is 41 days old and still the latest published release, with **1,451 PRs** (#1987 -> #3750) accrued behind it. The day since the last sync added **73 PRs**, the largest band since 2026-09-11. Milestone `0.5.0` held at **11 closed / 0 open (100%)** for a nineteenth sync. **Open issues 3 -> 5.** **Open PRs 11 -> 21**, the largest review queue this page has recorded. **The featureless run is over at nine bands, and it ended on volume rather than on one feature.** 73 PRs: **58 `fix` + 7 `feat` + 4 `test` + 3 `docs` + 1 `refactor`**, **277 files, +18,945 / -682 -- net +18,263 lines**, the largest net-positive band here by a factor of nearly two. `feat` moves **144 -> 151**, `fix` **923 -> 981**, `test` **154 -> 158**, `docs` **109 -> 112**, `refactor` **24 -> 25**, and correctness still climbs, 78.2% -> **78.5%**, for a fourteenth consecutive sync -- because the fix count grew faster than the feature count even in the band that broke the streak. The one `refactor` is `examples`-scoped (#3709), so the net-negative refactor table holds at thirteen and **the removals ledger is untouched for a fifth band**: `vera`, `lerobot_calibrate`, `motionbricks`, `strands_robots.transforms` and the MolmoAct2 normalization port are still gone from `main` and still present in the last published tag. No commit carries a `!` or a `BREAKING CHANGE` trailer, which is again correct rather than a gap. `_SHIPPED_DRIVERS` holds at twelve buses across 25 registry robot names and `strands_robots/tools/g1` at 22 modules; **`strands_robots/dashboard/` moves eleven -> fifteen** and gains a served `static/` bundle. **The seven features are the news, and six of the seven are one theme: the library says more about the thing it just handed you.** **#3695** is the largest -- **the dashboard finally has a server** (19 files, +1,609 / -74), the core wired to the rails the previous nine bands landed (`auth`, `access`, `consent`, `safety_state`, `settings`, `log_redaction`, `validate_scope`, `agent_hitl`) and **failing closed**, which is what the #2848 decomposition argument was for. Beside it **#3728** (`tool_frame` declares the tool point a site-less model lacks), **#3727** (`get_robot_state` says which way the arm extends from its base), **#3694** (SO-arm joints can be addressed by label rather than by index), **#3724** (`step` feeds an open dataset recording, so a hand-stepped scene records like a rollout), **#3731** (`Robot(tool_name=)` names the tool, so two robots of one type can share one Agent) and **#3697** (`render_dir` on the constructor moves the render sandbox per `Simulation`). **Its dominant fix shape is a refusal telling the truth about which side failed: an unreadable reply names the peer, not the codec.** Four clients decoded a peer's response inside a `try` that reported a codec error, so an inference server, a Cosmos3 world model, a MoveIt2 planner and a GR00T service each answered a malformed or truncated reply by blaming the local decoder -- which sends the caller to the wrong side of the wire. #3670, #3672, #3676 and #3677 each name the peer and what it sent. **Second, a configuration entry that is not a string is refused.** The mirror of the posture-flag lane, on the value side rather than the boolean side: a credential, a name-list entry or a body arrives as `None`, a number or a nested object, is stored, and is then dropped by whatever consumes it. **#3666** is the one with a tracker item behind it -- a DDS Security credential that is not a string was accepted and the participant QoS then dropped it, so a run believed it was authenticated (closes **#3664**) -- with #3667 (a dashboard settings list entry), #3674 (a mesh ACL `cert_common_names` entry) and #3679 (a Reachy daemon body that is not an object). **Third, a motion that did not reach names what stopped it.** The verified-halt lane asked whether a stop had stopped; this asks the same question of a *move*. **#3733** makes a `move_to` that fell short name the contact or the joint limit that stopped the servo, rather than reporting the pose it was asked for; **#3705** makes `set_gripper` say what the fingers closed on; **#3703** makes `attach_bodies` report whether the bodies actually touch; and the feature **#3727** answers the question underneath all three, which is which way the arm is pointing. **#3732 is the instructive part of this lane**: it was the `feat` version of the same idea, and it was closed unmerged in favour of the narrower fix #3733 plus a tracker item, **#3734**, for the `rotate_wrist` half -- a descope recorded rather than dropped. **Fourth, a camera name answers for the camera it names.** Four fixes on the surface that has produced more naming defects than any other: **#3683** (every camera name the sim lists can be rendered through it), **#3693** (a camera enumeration offers each camera once, not once per name), **#3691** (`add_camera`/`remove_camera` accept `camera_name` where every sibling does) and **#3730** (`start_recording` names the cameras by a name that answers for them). - **The featureless run ends at nine bands**: 73 PRs, 58 `fix` + 7 `feat` + 4 `test` + 3 `docs` + 1 `refactor`, **+18,945 / -682, net +18,263**; `feat` 144 -> **151**, correctness 78.2% -> **78.5%**. - **An unreadable reply names the peer, not the codec (4 fixes, new lane)**: #3670, #3672, #3676, #3677. - **A configuration entry that is not a string is refused (4 fixes, new lane)**: #3666 (closes #3664), #3667, #3674, #3679. - **A motion that did not reach names what stopped it (4, new lane)**: #3703, #3705, #3727, #3733. - **A camera name answers for the camera it names (4)**: #3683, #3691, #3693, #3730. - **Seven features, the dashboard server among them**: #3694, #3695, #3697, #3724, #3727, #3728, #3731. - **The `0000-` fragment defect is closed, and sequencing item 6 with it**: #3715 refuses the placeholder number, #3721 gives that rule one owner so every surface refuses it, #3722 renames the one fragment that named no PR. `changelog.d/` holds **zero** placeholders for the first time since they appeared. - **A rollout is exclusive, and one that commanded nothing is an error**: #3741 refuses `run_policy`/`eval_policy` while another rollout drives the robot; **#3680** carries the #3584 -> #3620 lane to the rollout entry point itself. - **A surface offers a next step the thing it just made can take**: #3686 (closes **#3684** -- `add_robot` had advertised `run_policy` for a model with zero actuators), #3687 (the sim tool description describes the world the agent is joining), #3719 (a paused real-robot call tells the script how to resume it), #3742 (an unreachable GR00T server fails fast and names the remedy). - **The dashboard's own correctness pass, same band as its server**: #3710 (a caller-supplied value cannot forge a log entry), #3738 (a credential store that parses but cannot serve as one is recovered, not answered with a 500). - **Four test pins**: #3662, #3669, #3675, #3745. - **The rest**: #3663, #3681, #3682, #3685, #3688, #3689, #3690, #3692, #3696, #3698, #3699, #3700, #3701, #3702, #3704, #3706, #3707, #3708, #3711, #3712, #3713, #3716, #3717, #3718, #3720, #3723, #3725, #3735, #3740, #3747, #3748, #3750. - **The earlier bands are unchanged and still the bulk of the wave**: the 81-PR g1 verb port (consolidated to 22 modules), the twelve-bus native-driver programme, the Isaac asset-reader run, the shared-value-domain sweep, Microduck hardening, the `doctor` lane, the whole-tree grader audit, the cache-validity lane, the mobile-base drive contract, FastTD3, the observer lane, the operator-consent lane, the docs diet, and the refuse-what-cannot-be-honored, release-what-a-failed-bring-up-adopted, checked-posture-flag, refused-payload-key, name-alphabet, commanded-nothing, failed-shutdown, offline-snapshot, criterion-already-satisfied, plan-that-commands-nothing, ROS-2-name, declared-size and refusal-applies-nothing lanes. Their sections below are unedited. **Release hygiene is still the only standing gap, and it is now 1,451 pull requests wide.** There is no `## [0.5.0]` or `## [0.5.1]` heading -- `CHANGELOG.md` has not been edited since #1690 (2026-07-28) -- `[Unreleased]` is still frozen at 168 entries, and the real accrual is still not in it: `changelog.d/` now holds **1,601** `.md` files, of which **1,600** are fragments (`README.md` is not one). The band added **70** and deleted none: 1,530 + 70 = 1,600 reconciles exactly, and `scripts/assemble_changelog.py --check` reports `OK (1600 pending)` -- collapsing both shipped tags remains a release action rather than a repair. **73 PRs produced 70 fragments**, the four `test` commits owing none and one PR carrying two, which is the convention working as intended. **What did change is the naming**: the four `0000-` placeholders are gone and the guard now rejects the number at every surface, so sequencing item 6 -- unactioned for three syncs -- is **closed**. **The gap between the fragments and the tag remains the page's oldest unaddressed item** -- 50 days since the last CHANGELOG edit, and every one of those 1,600 fragments is a release note nobody has read. ```mermaid flowchart LR A["v0.3.8<br>Feb 20"] --> B["v0.3.9 mesh security<br>folded into v0.4.0"] B --> C["v0.4.0 SHIPPED<br>Jun 17"] C --> D["v0.4.1 SHIPPED<br>Jul 1<br>ROS2 / training+RL / VERA / RTC / Newton / verify-dataset / security / EGL-GPU"] D --> E["v0.5.0 SHIPPED<br>Aug 4 -- 805 PRs<br>Isaac backend absorbed + remote inference + floating-base state + loco reward DSL + go2 walk/strafe/turn + g1/t1 benchmarks + terrain curriculum + describe() surface + lerobot 0.6"] E --> F["v0.5.1 SHIPPED<br>Aug 6 -- 51 PRs<br>latest tag; correctness-only patch (lerobot >=0.6.1 floor, hf_hub >=1.5, Isaac reset, LIBERO deadlock)"] F --> H["post-v0.5.1 wave (Unreleased)<br>1,451 PRs #1987-#3750<br>981 fixes + 158 test pins vs 151 features<br>+ twelve native drivers across 25 robots (Dynamixel, Feetech, Franka, G1, Go2, Reachy Mini, Microduck, Robotiq, Booster, UR, Crazyflie, EarthRover)<br>+ agent-facing G1 verb surface ported from neon-the-g1, then consolidated 79 modules -> 22<br>+ net-negative refactors: -34,302 (#3037) -33,213 (#3056) -15,204 (#3389 vera provider) -8,571 (#3070) -6,064 (#3426 augmentation transforms) -4,583 (#3419 motionbricks provider) -4,425 (#3388) -2,798 (#3391) -2,313 (#3386 lerobot_calibrate) -2,179 (#3411); ~116,000 lines out<br>+ verified-halt lane reaches drivers, sessions, the inference server and every backend's rollout release<br>+ whole-tree graders audited: roster derived from the tree, walk roots resolved; then one linter rule replaces fourteen copies (#3417)<br>+ refuse a span that resolves to no work + dashboard slices land, the 481-file draft #2848 closed<br>+ a cache is served only while its key still names what the entry is about<br>+ a run that stopped early states how much never ran; coverage on the PEP 669 core (+8.2% vs +76.6%)<br>+ the dashboard becomes the feature lane: HITL gate over hardware-moving tool calls, e-stop Lockout type, preflight scope, consent built from a refusal code (6 modules -> 11)<br>+ one owner answers one question; a key names the entry it is about; a verdict names the thing it was written for<br>+ FastTD3 lands as a native off-policy RL provider (#3206); an RL checkpoint becomes deployable through the factory (#3414)<br>+ a shared value domain reaches both owners of one value, and both halves of a dialled address<br>+ a value is graded before it is converted; a packaging declaration reaches the install that needs it<br>+ a store is written whole, or the previous one is still there; a dataset root resolves to the directory its repo_id recorded to<br>+ the rollout observer lane lands (#3351) and #3212's peer retention lands (#3352), both attributed<br>+ a posture flag is checked, so a word for no cannot select the other branch (closes #3356)<br>+ a telemetry field the frame does not carry is not a reading (Unitree + Booster decoders)<br>+ an operator is asked before a command reaches a real actuator (#3418 #3420 #3430 #3431)<br>+ the docs state each contract once: README to 120 lines, seven pages, -3,575<br>+ a value that can never be honored is refused at the entry point, not scored 0% (#3502 and twelve siblings)<br>+ a resource a failed bring-up adopted is released (#3519 #3522 #3526 #3530 #3532)<br>+ an evaluation reports how close it came and how often it holds (#3521, both routes by #3528)<br>+ first band that removed nothing: no refactor, +10,234 / -483<br>+ a posture flag is checked, not read by truthiness, on nine more surfaces including every policy provider<br>+ an action naming a key the surface cannot carry is refused (#3534 #3537 #3539)<br>+ a name is held to the alphabet its consumers can carry (#3557 #3558 #3559 #3565 #3570)<br>+ a stop grades the verdict it was given, on every robot_mesh route (#3560 #3566 #3571)<br>+ one camera-routing precedence honoured on every router (#3545 #3550 #3555)<br>+ a private name must have a reader (#3580 #3583)<br>+ a run that commanded nothing is refused, not scored (#3584 #3608 #3612 #3613 #3620)<br>+ a shutdown that cannot complete does not keep the resource (#3590 #3596 #3602 #3603)<br>+ an offline snapshot names what the live registry registers (#3614 #3616 #3618)<br>+ a door refuses a collaborator it cannot use (#3617 #3619)<br>+ a criterion already satisfied at reset is reported, not read as a result (#3629 #3633 #3637 #3639 #3642)<br>+ a plan that commands nothing is a failure, not a plan (#3643 #3652 #3655 #3657)<br>+ a ROS 2 name must name a topic; a refused call leaves no node behind (#3628 #3635 #3636 #3638 #3648 #3651)<br>+ a reader grades the size its own payload declares (#3627 #3650 #3658)<br>+ a refusal applies nothing from its own call (#3622 #3625 #3645)<br>+ eleven test pins in one band (#3621 #3626 #3632 #3634 #3641 #3644 #3646 #3647 #3656 #3659 #3660)<br>+ an unreadable reply names the peer, not the codec (#3670 #3672 #3676 #3677)<br>+ a configuration entry that is not a string is refused (#3666 #3667 #3674 #3679)<br>+ a motion that did not reach names what stopped it (#3703 #3705 #3727 #3733)<br>+ a camera name answers for the camera it names (#3683 #3691 #3693 #3730)<br>+ the dashboard gets a server, wired to the landed rails and failing closed (#3695)<br>+ the featureless run ends at nine bands: 7 feat (#3694 #3695 #3697 #3724 #3727 #3728 #3731), newest band +18,945 / -682"] H --> I["Next tag<br>uncut; no [0.5.0] or [0.5.1] heading, 1,600 changelog.d fragments pending, CHANGELOG untouched 50 days<br>the only standing gap -- open PRs 11 -> 21, the largest queue recorded; seven heads read APPROVED (#3729 #3743 #3746 #3749 #3751 #3752 #3753)<br>#3205 #3343 #3427 #3496 #3515 #3577 #3615 #3714 #3726 #3737 are green + armed + threads resolved and still REVIEW_REQUIRED on require_extra_approval_for_unattributed_changes<br>provider direction being executed (#3075): GR00T + MolmoAct stay; vera and motionbricks removed, lerobot_async / curobo / moveit2 still to go<br>removals section must still be assembled by hand -- five bands running have removed nothing<br>the 0000- fragment placeholder defect is FIXED (#3715 #3721 #3722): zero placeholders, guard refuses the number at every surface<br>the clean sweep broke: 4 PRs closed unmerged in #3661-#3750, each a second implementation (#3665 #3671 #3732 #3736)<br>plus a 108-file Isaac contribution ready for review (#3343), unchanged for seven syncs"] C --> G["0.5.0 milestone DRAINED<br>11 closed / 0 open (100%) -- held 15 syncs<br>#469 #472 #473 #474 closed 2026-08-24<br>substantiation issue #2720 and epic #858 both closed NOT_PLANNED 2026-09-10"] style A fill:#2ea44f,color:#fff style B fill:#2ea44f,color:#fff style C fill:#2ea44f,color:#fff style D fill:#2ea44f,color:#fff style E fill:#2ea44f,color:#fff style F fill:#2ea44f,color:#fff style H fill:#d29922,color:#fff style I fill:#0969da,color:#fff style G fill:#2ea44f,color:#fff ``` ## Published releases | Tag | Date | Notes | |-----|------|-------| | [`v0.3.8`](https://github.com/strands-labs/robots/releases/tag/v0.3.8) | 2026-02-20 | prior baseline | | [`v0.4.0`](https://github.com/strands-labs/robots/releases/tag/v0.4.0) | 2026-06-17 | mesh security (v0.3.9 scope) + docs/policies/motion-planners/HW-compat, collapsed into one tag. MoveIt2 #305 + hardware_robot coverage #453 landed before the cut. | | [`v0.4.1`](https://github.com/strands-labs/robots/releases/tag/v0.4.1) | 2026-07-01 | the large post-v0.4.0 wave: ROS 2/RTPS interop, end-to-end training + from-scratch RL (PPO/FastSAC, `VecSimEnv`, `BaseRLAlgo.evaluate()`), VERA, RTC/chunking correctness, recording + `verify-dataset` integrity, sim discovery/validation, EGL-GPU render reliability, filesystem/dependency security hardening, Newton GPU-backend parity. Tag at #947; release-enabled by #950 (PyPI vera-extra fix). CHANGELOG `[Unreleased]` collapsed into the dated `0.4.1` heading in #977. | | [`v0.5.0`](https://github.com/strands-labs/robots/releases/tag/v0.5.0) | 2026-08-04 | the post-v0.4.1 wave as a **minor** tag (the planned v0.4.2 patch was never cut): Isaac Sim backend absorbed (#1156, closing epic #1144), remote policy inference client/server split (#1142), floating-base state lane, locomotion reward-term DSL composed into runnable go2/g1/t1 benchmarks, terrain curriculum, `describe()` discovery surface, `lerobot>=0.6.0` floor (#1114). Published 2026-08-04T01:32:07Z. | | [`v0.5.1`](https://github.com/strands-labs/robots/releases/tag/v0.5.1) | 2026-08-06 | **latest published tag** (published 2026-08-06T20:23:39Z, tag commit `00cccb06`). A correctness-only patch, 51 commits over three days, no API changes: raises the `lerobot` floor to `>=0.6.1,<0.7.0` across `[lerobot]`/`[lerobot-async]`/`[molmoact2]` so `stream_dataset(repo_type="bucket")` is resolver-guaranteed rather than lucky (#1930), raises the `[wbc]` `huggingface_hub` floor to `>=1.5.0` for the `hf buckets`/`hf sync` CLI (#1957), fixes Isaac 6.0.x `reset()` articulation-handle revival (#1901, closes #1895) and the LIBERO eval Kit/agent thread deadlock (#1899, closes #1896), and continues the numeric-input hardening pass across training, tools, inference, hardware and the sim backends. | ## Post-v0.5.1 -- the current `[Unreleased]` wave (1,451 PRs, #1987 -> #3750) Merged to `main` after the `v0.5.1` tag (2026-08-06). **981 `fix` + 151 `feat` + 158 `test` + 112 `docs` + 25 `refactor` + 17 `ci` + 4 `security` + 1 `perf` + 1 `examples` + 1 `chore`** -- **78.5%** correctness and contract-pinning, climbing for a fourteenth sync. The newest band is **58 `fix` + 7 `feat` + 4 `test` + 3 `docs` + 1 `refactor`**, so `fix` moved **923 -> 981**, `feat` **144 -> 151**, `test` **154 -> 158**, `docs` **109 -> 112** and `refactor` **24 -> 25** while `ci` held at **17**. **`feat` moving at all is the band's defining fact**: it ends a nine-band featureless run, and at **277 files, +18,945 / -682 -- net +18,263 lines** it is the largest net-positive band on this page, with **none of its 73 commits carrying a `!` or a `BREAKING CHANGE` trailer** -- correct here, because nothing public was removed. Its one `refactor` is `examples`-scoped (#3709), so the net-negative refactor table holds at thirteen. No new vendor bus, so `_SHIPPED_DRIVERS` holds at **twelve** across 25 registry robot names and `strands_robots/tools/g1` at **22** modules; **`strands_robots/dashboard/` moves eleven -> fifteen** on #3695's `server.py`, `access.py`, `routes_auth.py` and `cli.py`, plus a served `static/` bundle. `strands_robots/transforms/` no longer exists, nor does `strands_robots/policies/motionbricks/`. `perf` remains a scope with exactly one member (#3149). Heaviest scopes across the wave: `simulation` (111), `tools/g1` (86), `mesh` (86), `simulation/mujoco` (55), `tools` (48), `training` (37), `drivers` (33) plus 32 more under a `drivers/*` sub-scope (65 in the family), `policies` (30), `isaac` (29), `simulation/isaac` (24), `ci` (21), `sim` (20), `rendering` (19), `policies/microduck` (17), `hardware` (17), `drivers/g1` (17), `policies/vera` (17, a removed surface), `device_connect` (15), `registry` (13), `tests` (12), `scripts` (12), `policies/wbc` (12), `policies/cosmos3` (12), `examples/fleet` (12), `dashboard` (12), `transforms` (11, a removed surface), `g1` (11), `agents` (11), `training/rl` (10), `policies/protomotions` (10), `dataset` (9), `assets` (8). ### Isaac backend parity (the largest feature lane) | PR | What | |----|------| | #2164 | promote `run_multi_policy` to the `Simulation` base with shared validation (the parity groundwork) | | #2175 | extract a backend-agnostic `MotionPrimitivesCore` from the MuJoCo mixin | | #2178 | synchronized multi-robot `run_multi_policy` control loop on Isaac (no recording) | | #2200 | Isaac joint-space motion primitives -- `set_gripper` / `rotate_wrist` | | #2212 | Isaac merged multi-robot recording for `run_multi_policy` | | #2218 | Isaac IK-backed `move_to` via the shared `MinkIKBridge` | The stated scope of both parity issues has landed -- #2122 (synchronized multi-robot rollout) via #2178/#2212 and #2123 (`move_to`/`set_gripper`/`rotate_wrist`) via #2200/#2218 -- but neither issue auto-closed, so both still read as not-started. Both parity issues have since been closed (#2156 with the parity suite in #2233; #2221, the body-mounted `add_camera(parent_body=...)`, on 2026-08-24), so nothing on this lane is open. The backend's own work continued in a different direction -- a 22-PR **asset-reader** run, tabled below. ### Off-policy RL -- FastTD3 lands (epic #858, #2720) | PR | What | |----|------| | #3206 | **FastTD3** -- twin delayed DDPG as a native off-policy RL provider, with vectorized collection and the trainer env-leak fix (+2,199 / -95 across 29 files) | The first of epic **#858**'s off-policy deliverables to reach `main`, and the first code answer to **#2720**'s claim that three of them are absent from the tree. It joins the from-scratch PPO (#877) and FastSAC (#886) backends shipped in v0.4.1 and the `VecSimEnv` / `BaseRLAlgo.evaluate()` groundwork (#918, #920). **It closed neither issue** -- the pull request declares no closing reference -- so #858 is still the last unscheduled epic and #2720 is still open against the milestone; the bookkeeping is owed. Its two changelog fragments are named for the issues they answer (`862-fast-td3-trainer.md`, `2720-rl-trainers-close-their-env.md`) rather than for the pull request, the first in the wave to do that. ### Humanoid policy providers | PR | What | |----|------| | #2270 | **`KimodoPolicy`** -- text-to-motion diffusion for the Unitree G1 | | #2279 | Kimodo action-key bridge for lerobot's Unitree G1 driver | | #2287 | **`ProtoMotionsPolicy`** -- native ONNX GTP whole-body tracker for the Unitree G1 | | #2271 | `Policy.required_bodies` -- a policy declares the body poses the runtime must supply (the seam both trackers need) | Milestone issue #469 was closed `COMPLETED` on 2026-08-24. That close is one of the 57 #2720 flags as carrying no linked PR, and the reservation this document recorded for four syncs still stands on the code: the provider merged, but the issue's namesake **kinematic-constraints** API did not -- there is no constraint module under `strands_robots/policies/kimodo/`, no multi-prompt period splitting, and the registry `config_keys` enumerate sampling knobs only. ### Fleet-orchestration examples (epic #2179) | PR | What | |----|------| | #2197 | `01_skill_dispatch_multi_vendor` -- capability-based dispatch across heterogeneous robots | | #2191 | `03_failover_and_degraded_ops` -- peer-loss reassignment + dispatcher-down safety | | #2207 | `04_emergency_evacuation` -- three-phase evacuate protocol, benchmark-scored | | #2188 | `05_work_order_dispatch` -- work-order ingress onto capability manifests | All five landed (`02_cross_zone_transport` via #2198), and the epic **#2179 closed** 2026-08-24. #2506 then added a `--serve-web` live Rerun viewer for headless hosts, and #2643 corrected the docs to describe a dashboard that is named rather than promised. ### Merge gates / CI / supply chain | PR | What | |----|------| | #2039 | refuse a lockfile that does not resolve the manifest | | #2043 | compare the whole recorded requirement set, not two properties of it | | #2019 | report whether an open pull request already claims an issue | | #2005 | last-push-approval reports a finding without failing the job | | #2241 | bound every workflow job so a hung step cannot hold a runner silently | | #2244 | sweep the open pull-request set for untested compositions | | #2044 | declare the advisory floors that keep four transitive packages patched | | #2769 #2797 #2830 | collide duplicate work on the identity a created path declares; then on a module one of the branches' tests names; then on the change a branch describes rather than the file it creates | ### Dependency floors (new lane) Every one of these was measured against the capability the code actually imports, rather than bumped speculatively. | PR | What | |----|------| | #2335 | raise the `robot_descriptions` floor to a release that ships every registry robot | | #2342 | raise the `websockets` floor to the release that ships the name the server imports | | #2345 | raise the `eclipse-zenoh` floor to the release that ships wire source attribution | | #2349 | bound the `vera-sim` `imageio` floor at the release that reads the clip duration in milliseconds | | #2340 | the same lane inverted -- `rclpy` is not on PyPI, so the refusal now names a remedy that can supply it | | #3236 | an **abbreviated** written version floor is not below the floor `pyproject` declares (extends #3188) | | #3234 | the lane read from the other end -- a test pinned an `np.cross` behaviour removed in **numpy 2.5**, so the upper half of the declared numpy range was red; the premise now holds across it (closes #3229) | | #3601 | the lane read one layer further: a pin whose whole admitted range has **no wheel** for the `requires-python` the project declares. `[ros2]` pinned `cyclonedds<1.0.0`, whose wheels stop at cp310, so the one transport documented as a self-contained wheel was a source build on every supported Python (closes #3599) | ### Agent-facing discovery + actionable refusals The dominant theme of the #2308 -> #2387 window. Two shapes: a capability the schema documents but never publishes, and a refusal that dead-ends. | PR | What | |----|------| | #2311 | publish `set_geom_properties`' payload contract in the tool schema | | #2320 | publish the `ellipsoid` shape the agent schema already documents | | #2308 | advertise `set_timestep` under the parameter name it accepts (Newton) | | #2329 | advertise every base-contract method the Newton backend delivers | | #2372 | report the provider aliases `create_policy` accepts | | #2317 | diagnose a missing asset instead of suggesting the name it refused | | #2364 | route the `create_world` refusal by the request instead of offering `reset` | | #2369 | name the hardware route when a registered robot has no sim model | | #2379 | make a relative `output_path` refusal name a remedy that works | | #2382 | offer a joint-discovery action an agent can call | | #2377 | name the action a misspelled action probably meant | | #2327 | point the WBC missing-session refusal at the seam that installs one | ### Security + gating | PR | What | |----|------| | #2313 | `use_ros` gates every verb that can command a robot, not just `publish` -- supersedes the long-open #1087; #2315 tracks the halt-vs-drive contract it exposed | | #2358 | mesh security admits every policy provider the registry can build | | #2381 | gate every spelling of a remote-code provider, not just its canonical name | | #2365 | let the caller decide whether a Kimodo checkpoint runs its own code | | #2855 #2859 | escape a line break where an observation-derived value enters a log record, and read the escape's shape off the value the caller receives | ### Scene-rebuild state + lifecycle (MuJoCo) A coherent run: a rebuild or a teardown must not silently drop state the caller established. | PR | What | |----|------| | #2360 | carry `ctrl`, `act`, an unnamed freejoint and the clock across a robot eject | | #2370 | keep a latched `apply_force` wrench latched across a scene rebuild | | #2380 | carry a latched joint force across a scene grow | | #2384 | apply the actuator command a keyframe pairs with its pose | | #2386 | refuse to double-actuate a joint a tendon already drives | | #2371 | report the placement `add_robot` compiled, not the one it was asked for | | #2387 | export a scene whose asset references still resolve | | #2355 | refuse `remove_robot` when the cooperative stop lapses instead of un-tracking a live worker | | #2333 | a `PolicyServer` stop must not kill the thread serving it | | #2362 | release the robot on Ctrl+C instead of exiting straight out of `run()` | ### Monotonic clocks (new lane) One root cause, six sites: a **published** time budget was measured by subtracting two `time.time()` readings, so an NTP correction or a manual clock change mid-budget silently truncated or extended the budget the caller asked for. | PR | What | |----|------| | #2404 | measure a published wait budget on a clock that cannot step (agent-callable tools) | | #2406 | measure a hardware control-loop time budget on a clock that cannot step | | #2408 | measure mesh peer liveness + publish intervals on a clock that cannot step | | #2412 | measure a teleop session's duration budget from the end of setup, not including it | | #2413 | measure a sim rollout's frame pacing + RTC latency on a clock that cannot step | | #2425 | pace the MuJoCo rendering loops on a clock that cannot step | The lane filed its own remainder rather than leaving it implicit, and has since closed it: **#2414** named the three pacers still deciding on `time.time()` -- the MJPEG stream, the multi-camera recorder and Isaac's idle-render gate -- and was closed on 2026-08-18 (no closing PR recorded; #2432 moved Isaac's preview cadence and its reported recording duration onto a clock that cannot step). ### `ProtoMotionsPolicy` hardening The ONNX GTP whole-body tracker merged in #2287 one wave earlier; this is its post-merge correctness pass. No public-surface change. | PR | What | |----|------| | #2416 | read a quaternion as the rotation it encodes | | #2417 | fill the reference-motion cache's body rows in the order the tracker reads them | | #2418 | use the ground an MJCF already declares instead of adding a second one | | #2411 | name the frame the reference-motion cache's velocity channels are in (docs) | ### Photoreal 3DGS + GS-derived lighting (new lane) The five-PR batch tracked as #2321/#2322/#2323/#2325 landed in one window, taking the hybrid Gaussian-splat render path from silently-optional to a declared contract. Four of the five tracking issues closed with it. | PR | What | |----|------| | #2336 | fail loud when the photoreal 3DGS background cannot initialize, instead of demoting to the procedural gradient (closes #2321) | | #2332 | composite premultiplied splat RGB once, alpha-normalize gsplat depth, honor the SPZ antialiased flag (closes #2322) | | #2331 | light the robot with the scene it stands in -- GS-derived IBL, shadow catcher, colour-pipeline contract (closes #2323) | | #2326 | evaluate SH view-dependence and swap the GS presets to fully-trained captures (closes #2325) | | #2328 | forward `render_mode` to `SimulationApp`; delete the abandoned renderer helpers (closes #2324) | Its correction pass followed inside the same wave -- the shape every new surface here takes: | PR | What | |----|------| | #2441 | state the scale of an sRGB decode input instead of guessing it | | #2443 | check `derive_key_light`'s search-region flag instead of reading it by truthiness | | #2446 | check the environment-map resolution knobs before baking with them | | #2452 | check the panorama bake's resolutions before it names a file or loads splats | Both remaining items -- **#2337** (regenerate the shipped gallery) and the parent **#1537** (lift the GS layer out of `examples`) -- were closed 2026-08-24, so the lane is fully closed out. `rendering` carries 12 PRs across the wave. ### Posture flags read as values (new lane) A flag is a posture the caller chose, so reading it by truthiness silently inverts the choice: `"false"`, `"no"` and `0` are all truthy strings or a falsy int, and the surface acts on the opposite of what was asked. Checked rather than coerced across four surfaces in one window, continuing the sub-shape #2436/#2443/#2448 opened. | PR | What | |----|------| | #2473 | check the ROS 2 command-surface posture flags instead of reading them by truthiness | | #2487 | check `randomize()`'s per-axis flags instead of reading them by truthiness | | #2475 | check the dataset-recorder creation API's posture flags instead of reading them by truthiness | | #2485 | check `GsplatBackground`'s alignment flags instead of reading them by truthiness | | #2489 | its resolution-knob sibling: check the value that sizes a heightfield before generating it | ### Native hardware drivers (a programme, 18 feature PRs) -- twelve buses shipped Every real robot used to be built by lerobot. The seam that made that optional landed in the #2734/#2739 pair; the #2750 -> #2836 band built three vendor buses and a complete G1 write path behind it, the #2837 -> #2879 band added a second robot, the #2880 -> #3057 band added a sixth driver plus a coverage report that derives the remaining gap -- and the #3037 -> #3091 band **doubled the set in a day**, landing every driver that had been queued for review. **`_SHIPPED_DRIVERS` now registers twelve buses across 25 registry robot names**: Dynamixel, Feetech, Franka, G1, Go2, Reachy Mini, Microduck, Robotiq, Booster, UR, Crazyflie and EarthRover. This is the wave's one genuinely new *capability* surface and it is now its largest feature lane. | PR | What | |----|------| | #2734 | **choose which driver builds a real robot** -- the seam itself | | #2739 | **native CycloneDDS driver for the Unitree G1** -- first driver that does not route through lerobot | | #2750 | **native Dynamixel Protocol 2.0 codec + driver skeleton** | | #2786 | **native Feetech STS/SCS Protocol 1 codec** | | #2762 | **native daemon driver for the Pollen Reachy Mini** | | #2757 | **`DDSPublisher`** -- the G1's write path | | #2767 | **`send_action` publishes `LowCmd_` on `rt/lowcmd`** | | #2779 | **a 500 Hz control loop** behind `run_policy` / `stop_task` / `get_task_status` | | #2856 | **register Pollen Microduck**, the 14-DOF open biped | | #2862 | **Microduck end to end** -- policy provider, native `robotd` driver, sim + real | | #3039 | **native CycloneDDS driver for the Unitree Go2** quadruped -- the sixth shipped bus | | #3042 | **`list_driver_coverage`** joins the robot registry and the driver registry, so a driver gap is *derived* rather than asserted | | #3040 | **native Franka FCI driver** for `panda`, `fr3` and `fr3_v2` | | #3053 | **native Modbus TCP driver for the Robotiq 2F-85** gripper (`robotiq_2f85`, `robotiq_2f85_v4`) | | #3045 | **native Booster Robotics T1 driver** (`booster_t1`) | | #3058 | **native RTDE driver for the UR5e and UR10e** | | #3046 | **native Crazyflie 2.x CRTP driver** | | #3081 | **native EarthRover Mini+ driver** over the earth-rovers-sdk HTTP API | | #3043 | **wire the Feetech SCS bus** so an SO-arm moves and reports joints | | #2947 #2949 | cache `rt/mainboardstate` and `rt/pressuresensorstate` on the G1, each with its own agent verb | | #2745 | survive a teardown after the device bring-up failed | | #2719 | hold the ROS 2 bridge QoS depth to a domain the transport can carry | Its own hardening arrived in the same band: #2802 stops the Dynamixel codec citing a vendor unstuffer that loses a byte; #2780 applies the vendor's **per-joint** gains on `rt/lowcmd` rather than one scalar; #2804 holds the battery floor to a numeric domain before it is stored; #2773 records the DDS interface `ensure_dds` actually bound rather than the one it was asked for; #2755 verifies a Feetech reply instead of reading it at fixed offsets and #2820 refuses the Feetech broadcast where `serial_tool` is about to read one reply; #2752 and #2756 stop reporting a LiDAR point cap the summary never applies and read the fields `LidarState_` actually declares; #2817 names a robot's native driver instead of lerobot's vocabulary. The newest band is almost entirely this lane's own accounting: #2865 makes the write refusal cite a resolvable issue and documents the soft-stop helper's real call site, #2863 and #2866 make the module docstring and the FSM-set docstrings name the verbs and the topic `send_action` actually writes, and #2872/#2877 stop two deferrals citing changes this repository has already landed. Four test pins hold the un-wired half honest -- #2847 and #2850 grade the `rt/lowcmd` wire-frame contract and the per-joint gain cells **on CI** rather than only where the vendor SDK is installed, #2841 pins the battery floor as unreachable behind the un-wired FSM, and #2861/#2874 pin the acceptance criterion as an XPASS trigger whose marker cites the refusal the driver actually returns. A five-PR **DDS endpoint-lifecycle** sub-run closes what it opens: #2768 closes the endpoints instead of only forgetting them, #2783 releases one whose construction failed part way, #2811 one a concurrent teardown left unreachable, #2816 reads whether the control loop actually halted before releasing the wire, and #2828 makes the stop verb report the halt outcome instead of asserting it. **Three of the four items this lane filed against itself have now closed against merged code**: #2749 via #2763 (which reads joints from a driver that owns its bus, pinned by #2754), **#2765 via #2888/#2916** (the motion-switcher decoder plus the FSM-producer wiring answered its five `rt/lowcmd` wire-format decisions), and **#3062 via #3064** -- the sport-mode gate now opens its RPC client under the shared `_DDS_INIT_LOCK`, with #3067/#3071/#3072/#3073 closing the same class across the T1 and the Go2. What the programme is still carrying is **#2753** (Dynamixel model-name resolution, against a live servo) and **#2812** (the Feetech position field bounded and reported at STS full scale, 4x the SCS-series servo the registry declares) -- both need hardware, and #3074 has since recorded the word order as an STS/SMS property so #2812 is a scale question alone -- plus two decisions the g1 port filed fresh: **#2891** (the FSM read cadence, still the thing blocking the `send_action` wire step) and **#2928** (the tools-bundle port has no seam decided, now partly answered in practice by #3037's `use_unitree` dispatcher). ### A stop reports its outcome instead of asserting one (new lane -- the band's dominant fix shape) One root cause at eight sites: a teardown or halt verb returned success because it had *issued* the stop, not because the thing had stopped. A loop could still be writing, or a device could be closed underneath one that was. | PR | What | |----|------| | #2809 #2815 | report whether `stop_teleoperate` / `InputPublisher.stop` actually stopped the loop | | #2816 #2828 | read whether the G1 control loop halted before releasing the wire; the stop verb reports the outcome | | #2821 #2826 | do not close the devices under a teleop loop that is still writing; join the loop before `detach_teleop` disconnects what it reads | | #2831 | report an emergency stop the robot refused instead of discarding it | | #2825 #2832 | report the joints an interpolated move actually commanded; answer for every joint a whole-arm reading was asked for | | #2834 #2835 | abandon a rollout whose bring-up a shutdown already cancelled; halt teleoperation as well as policies on a simulation emergency stop | | #2839 | report a timed drive whose trailing stop was refused | The lane filed its own remainder as **#2833** (a cooperative stop issued before a rollout's first frame is discarded, and every stop path still reports the halt), which is still open -- its first two answers, #2834 and #2835, have since merged. ### One drive contract for every mobile base (new lane) Both of the long-stalled external PRs this document tracked for months landed here -- re-landed as fresh PRs rather than rebased, after which the originals were closed unmerged. | PR | What | |----|------| | #2689 | **`AckermannRosRobot`** -- Ackermann ROS 2 cars (AWS DeepRacer) as strands robots (the #1035 scope) | | #2696 | **`MobileBaseRobot` + `Transport`** -- one drive contract for every transport (the #1722 scope) | | #2700 | name every class that carries a scoped drive limit | | #2705 | let a `RosbridgeRobot` command carry an operator decision | | #2693 | gate every ROS 2 transport's commands, not just `use_ros` (security; broadens #2313) | ### Isaac asset readers read what the compiler reads (the largest single theme, 22 PRs) One shape throughout: an MJCF or URDF attribute was read literally at the site that declares it, where the compiler resolves it through defaults, inheritance and alternative spellings. The backend loaded a model that was subtly not the model on disk. | PR | What | |----|------| | #2669 #2672 #2674 #2675 #2691 | read a geom's shape, a joint's every attribute and a mesh asset's scale through the `<default>` class they inherit; read the root class under both spellings; merge several top-level `<default>` elements | | #2680 #2686 #2692 | read every MJCF orientation spelling rather than `quat` alone; read a link's rotation from it; report a quat normalized, as the compiler does | | #2660 #2665 | a `fromto` collision geom carries its length *and* its offset; read a `fromto` link's extent from its endpoints | | #2656 #2667 | read an MJCF's mesh registry and its bodies from the whole model, not just the top file | | #2712 | read both MJCF spellings of a free joint | | #2701 #2694 | read a URDF joint's axis default from URDF rather than MJCF; read a link's pose from the joint that reaches it | | #2671 #2662 #2659 | refuse a body reached by more than one joint; refuse an ASCII-STL facet with no `endfacet`; read an OBJ keyword off the split | | #2498 #2575 | LIBERO objects render as real meshes instead of box proxies; refuse a render whose `camera_name` no camera carries | | #2800 #2813 #2827 | keep only the joint types URDF declares in the movable set; refuse a non-finite geom, and a non-finite body placement, instead of measuring a body around them | | #2838 #2845 | refuse a non-finite mesh scale instead of measuring through it -- then refuse it wherever it is *carried*, not only where it is measured | | #3264 #3267 | a declaration the format leaves implicit: a URDF joint that states no type is refused rather than welded, and a typeless MJCF geom takes the format's documented default | ### Shared value domains (the wave's dominant fix idiom) A parameter validated ad hoc, beside siblings that already share a domain, gets held to that domain -- so one spelling of "what is a legal value here" covers the whole surface. | PR | What | |----|------| | #2547 | hold a patch op's claimed name to the shared entity-name domain | | #2555 #2559 | hold `gr00t_inference`'s selectors and `lerobot_camera`'s documented vocabularies to a domain, like the numerics beside them | | #2560 #2563 | hold both trainers' `device` to torch's own device-string domain | | #2690 | hold every orientation parameter to one quaternion domain | | #2706 #2708 | hold `lerobot_train`'s `save_freq` to an int lerobot can decode, and `TrainSpec.save_freq` to one shared step-cadence domain | | #2715 #2731 | hold the terrain seed to the shared stream-name domain, and the RL run-size factors to the shared count domain | | #2722 | grade **every** field-scoped domain guard, not the five named by one helper spelling | | #2751 #2778 #2804 #2810 #2824 | hold FastSAC's replay counts, a holdout failure-mode tag, the G1 battery floor, `history_length` and a background's alignment numbers to the domain each already had beside it | | #2837 | hold a stored pose to the travel its joints are driven within | | #2849 #2860 | hold `num_envs` to the shared count domain on both RL backends, and `learning_starts` to the domain its relation's other operand already uses | | #2858 | hold **every** WBC observation dimension to the shared count domain | | #3242 #3244 | an infinite `max_tokens` escapes the dashboard-settings domain that refuses an infinite `temperature`; a per-ip challenge cap at or above the global cap loses the fairness it exists for | | #3238 #3239 #3241 | a hidden layer of width zero is not an architecture; a served policy's advertised `required_bodies` are held to a domain; a skeleton the caller named is not one the builder derived | | #3247 #3249 #3251 | a dataset identity the provenance record could not hold; a VERA readiness budget with no domain (and the variable its own timeout tells you to raise); an empty `cmd_scale` resolves from its owner table, not a bare `1.0` | | #3243 | a non-finite wire timestamp is neither stale nor future, so **four** mesh freshness gates accepted it | | #3266 #3275 #3277 | **two owners of one value**: a configured gravity, a configured camera resolution and the parallel environment count each take the domain their call-time argument already had | | #3284 | the same shape on the RL side -- the counts a vectorized env is built from take the shared count domain | | #3256 #3258 #3260 #3270 | an RL run's checkpoint interval, FastSAC's target temperature, the rate a target network tracks its online one, and the device every backend places its tensors on | | #3263 #3253 | the period a reference motion is resampled onto; a Kimodo seed the sampler and the buffered-motion key disagreed about | | #3269 #3259 | an Isaac stage path is a USD prim path, not free text; an unknown VERA embodiment is refused rather than resolved as `mimicgen` | | #3261 #3262 | three VERA sampler knobs that reached the server as unchecked text -- one of them in only one of the two launch modes | | #3257 | a narrowed dashboard session window is honoured or refused, never widened | | #3303 #3304 #3319 | **graded before it is converted**: only the integer `0` means a port was not supplied; a session record's pid is not converted to a nearby process id; its start time is graded rather than subtracted from the clock | | #3301 #3294 #3290 | a dataset's declared task count is not converted into the absent case; a per-call Kimodo sampler knob; the legacy Isaac timestep shortcut, graded before the conversion | | #3302 #3295 #3292 | a de-normalization quantile and a facing goal are graded in whatever spelling they arrive; the speed a serial bus is opened at takes the shared count domain | | #3347 #3350 | `stream_dataset` refuses a flag that is not a boolean rather than inverting it; a turn goal outside the measurable heading range is refused rather than compiled | ### A payload that cannot be encoded is reported, not dropped | PR | What | |----|------| | #2638 | report a payload that can never reach the wire, at a level an operator sees | | #2730 #2741 | coerce a sensor topic's and a safety event's readings so they reach the wire at all | | #2744 | a sensor record's identity is the publisher's, not the provider's | | #2621 #2733 | a degraded state probe names itself in the published snapshot; survey each sensor provider under its own guard | | #2729 #2735 | report which robot is running a policy on the state topic; report a refused rollout when a fleet stop aggregates | | #2842 | count a teleop frame the follower refused instead of reporting it applied | ### Dataset transforms, labeling + continuable refusals (new lane) The forward-looking batch filed on 2026-08-19 shipped in full, and took its own correction pass inside the same wave. | PR | What | |----|------| | #2480 | **episode augmentation as a dataset-transform surface** -- `LeRobotDataset` in/out, provenance-marked, verdict-gated | | #2486 | **VLM judge agent** for recorded-episode labeling on top of deterministic verdicts | | #2508 | `07_data_augmentation.ipynb` -- three shipped mechanisms, CPU-only | | #2610 #2612 #2616 #2617 #2618 | the correction pass: resolve a runtime-registered transform through the public resolver, record every read a verdict can make, refuse a provenance record that cannot answer, hold every determinism-key input to its domain, report a failed probe instead of raising it | | #2623 | **a continuable refusal carries a stable code and its subject** (graded by #2634/#2636/#2641) | ### Refuse a value that cannot be honored (new lane -- the band's dominant shape) The #2837 -> #2879 band's own idiom, at seven sites: a non-finite or out-of-domain number was accepted, carried through arithmetic that cannot express it, and reported as a result. An all-`NaN` configuration returned as a successful solve is the clearest case -- nothing raised, and the caller could not tell. | PR | What | |----|------| | #2869 | refuse an IK knob that cannot be honored instead of solving with it | | #2875 #2879 | refuse a non-finite pose or seed instead of solving with it; refuse a non-finite configuration instead of reporting a pose for it (closes #2873) | | #2876 | read the solve premise off the returned configuration rather than the fake's own storage | | #2846 | refuse a non-finite direction goal **and name the key it came from** | | #2838 #2845 | refuse a non-finite mesh scale where it is measured, then wherever it is carried (also in the asset-reader table above) | Its own remainder is open as PRs #2881 (a wrong-*shaped* pose or seed, which is a different refusal from a non-finite one) and #2882/#2883 on the Microduck provider. ### Docs name what the module actually reads (new lane) Five PRs with one shape: a page documented the extra that installs a feature, or the credential a link carries, without naming the variable that selects it -- so an operator could set everything the docs mention and still not turn the thing on. | PR | What | |----|------| | #2864 #2867 | name the transport **selector** `STRANDS_MESH_BACKEND`, not only the extra that installs it | | #2870 #2871 | name the IoT credentials the transport reads, and the audit-log environment variables the module reads | | #2857 | the policy catalogue named an install extra that cannot exist | ### An asset path resolves without fetching the corpus (new lane) | PR | What | |----|------| | #2851 | let a caller resolve an asset path without fetching the corpus | | #2868 | let the directory resolver decline the fetch it cannot use | ### A remote-inference connection is discarded when its exchange did not complete (new lane) | PR | What | |----|------| | #2843 | hold the wire lock across the connect replay | | #2854 | discard a connection whose exchange did not complete, rather than reusing it | ### Reference SO-101 pick | PR | What | |----|------| | #2685 | **a reference SO-101 pick that lifts the cube** -- closes the long-open #2145, which held that grasp quality has three conditions and the library exposed none of them | ### An agent-facing Unitree G1 verb surface (81 PRs -- the largest single theme in the wave, since consolidated) The `neon-the-g1` deployment's operator verbs were ported into `strands_robots/tools/g1` across 81 PRs, peaking at **79 modules**. Three shapes, one contract: an agent gets the vocabulary, the envelope that bounds it, and a verb that refuses a value outside it. **The port has since been consolidated to 22 modules** -- #3037 folded the 92 lookup verbs into one `use_unitree` dispatcher and #3070 folded 13 per-verb execution modules into a table-driven `g1_actions` -- so the PR table below records how each verb arrived, not the file that carries it today. | PR | What | |----|------| | #2888 #2916 | **the motion-switcher decoder for the FSM id producer**, then wiring that producer to the arm-SDK gate -- together these answered #2765's five `rt/lowcmd` wire-format decisions, and it closed against them | | #2932 #2933 | surface the driver's joint map, and its motion-gate admission sets, to agents | | #2934 #2938 #2939 #2941 #2943 | live-handle read verbs -- `g1_get_state`, `g1_battery`, `g1_imu`, `g1_lidar_state`, `g1_lidar_summary` | | #2947 #2949 | cache `rt/mainboardstate` + `g1_mainboard`; cache `rt/pressuresensorstate` + `g1_pressure` | | #2955 #2956 #3016 #3004 #3005 | the task and command verbs -- `g1_get_task_status`, `g1_stop_task`, `g1_start_task`, `g1_send_action`, `g1_run_policy` | | #3025 #3031 #3032 #3033 #3034 #3035 #3041 #3055 | posture and locomotion verbs -- `g1_set_fsm`, `g1_set_stand_height`, `g1_set_swing_height`, `g1_balance_stand`, `g1_release_arm`, `g1_stop_move`, `g1_wave_hand_loco`, `g1_move_velocity` | | #3044 #3048 #3052 | the safe posture transitions -- squat-to-stand, lie-to-stand, stand-to-squat | | #2959 #2960 #2961 #2962 | lookup pairs for arm actions, FSM targets, loco tasks and error codes | | #2965 #2966 #2970 #2972 #2973 #2974 | envelope pairs -- velocity, stand height, swing height, locomotion duration, walk-forward, turn | | #2967 #2971 #2975 #2976 #2978 #2980 #2987 | balance modes, arm-ready mode machines, damp transitions, shake-hand stages, wave-hand turn flags, joint-variant notes, safe-posture FSM gates | | #2969 #2982 #2984 #2986 #3002 #3020 #3021 | the DDS families -- topics and roles, dangerous publish topics, categories, IDL types, max-buffer envelope, topic descriptions, BMS state topics | | #2968 #2983 #2988 #2989 #2991 #2995 #2996 #2998 #2999 #3015 #3019 #3023 #3036 | the audio families -- volume envelope, voice providers, bidi-audio dependencies / mic keywords / sample rates / stream delay / frame size, capture-rate candidates, speak VAD and silence-duration envelopes, ASR payload keys, audio call API ids, speak actions | | #2992 #2994 #3001 | loco call API ids, LiDAR switch states, LiDAR max-points envelope | | #3000 #3006 #3008 #3010 #3011 #3013 #3014 #3026 #3027 #3029 | the SLAM family -- map-name rules, relocalize, ICP fitness, cloud range, map liveness, save, voxel dedup, pose history, frame queue, map-chunk compaction | | #2946 #2948 #2950 #2953 | the refusal run -- a short-form role names a member rather than a call expression; a live-handle verb, `g1_get_state`, and `g1_battery`/`g1_mainboard` each refuse a wrong driver handle | | #2997 #3028 #2951 | fail the live-handle sweep when its own discovery narrows; drop a stale deferral once its blocker landed; release the subscribers a failed bring-up already built | The port filed its own remainder rather than leaving it implicit: **#2891** (the FSM *read cadence* is undecided, and that is what now blocks the `send_action` wire step -- not the write-frame decisions #2765 carried) and **#2928** (the tools-bundle port has no seam decided). Both have since been narrowed in practice by the consolidation: **#3037 merged**, folding 92 of the lookup verbs into one `use_unitree` dispatcher (-34,302 LOC), and **#3070** folded 13 per-verb execution modules into a table-driven `g1_actions` (-8,571). ### Net-negative refactors (13 merged, 1 refused) -- and now they reach public surface Every band before #2880 -> #3057 added surface. Seven reductions have now merged -- roughly **101,000 lines removed** -- and the one that was stopped was stopped by an argument about public surface rather than by a conflict, which is why the argument mattered: it produced a ruling, and the ruling is what licensed the two removals in the #3352 -> #3406 band. | PR | What | |----|------| | #3056 | **remove the vendored LIBERO benchmark adapter, suite and drivers** -- 96 files, **+336 / -33,213**; `strands_robots/benchmarks/` no longer exists | | #3037 | **consolidate 92 g1 lookup verbs into the `use_unitree` dispatcher** -- 108 files, **+1,303 / -34,302** | | #3070 | **consolidate 13 per-verb g1 execution modules into one table-driven `g1_actions`** -- 30 files, **+761 / -8,571** | | #3389 | **`refactor(policies)!` -- drop the `vera` provider**, git-only and the last reason for the gymnasium resolution fork -- 132 files, **+424 / -15,204**; `uv.lock` 258 -> 237 packages, the five `[tool.uv] conflicts` rows gone, extras 34 -> 33 | | #3388 | **move the pull-request triage tools to `.github/scripts/`** and their replay suites out of `tests/` -- 16 files, **+110 / -4,425** | | #3391 | **the training core states the domain it checks**, not the history of the check -- 5 files, **+483 / -2,798** | | #3386 | **`refactor(tools)!` -- drop `lerobot_calibrate`**; calibrating is LeRobot's own CLI -- 22 files, **+227 / -2,313** | | #3426 | **remove the augmentation-transforms package** -- `strands_robots.transforms` (1,943 lines) had no importer anywhere in the package; `LeRobotDataset(image_transforms=...)` is the supported path -- 32 files, **+29 / -6,064** | | #3419 | **drop the `motionbricks` provider** -- the NVlabs model is not on PyPI, so the extra installed only support libraries while the model had to be cloned and `pip install -e`'d, and nothing imported it -- 58 files, **+163 / -4,583** | | #3411 | **stop porting MolmoAct2's normalization** -- lerobot owns it -- 12 files, **+232 / -2,179** | | #3443 | **the Training page reads in one sitting**; the reward wrappers go -- 10 files, **+243 / -960** | | #3446 | **the episode reader joins its verifier**; streaming trusts its floor -- 27 files, **+537 / -848** | | #3459 | **the two mesh safety subscribers share one envelope path and one audit wrapper** -- 7 files, **+615 / -844** | | #3065 #3089 #3384 | drop the docstring roles citing the removed lookup modules; a failed `use_unitree` call still reports what it was; resolve the body name a spec wrote rather than a synthesized `<name>_main` | | #3047 | remove the LIBERO adapter *and* the GR00T provider (276 files, -50,835) -- **closed unmerged 2026-08-31T19:10Z** | **Why #3047 was refused, and how that was settled.** #3075 was filed one minute before it closed: the "lerobot ships it upstream" rationale is true of GR00T's *local* mode (lerobot ships `policies/groot`, so `lerobot_local` covers `model_path=`), but **service mode -- the default, which dials Isaac-GR00T's own ZMQ server -- has no upstream replacement**, `lerobot_async` speaks a different transport, and neither the Docker-lifecycle tool nor the 27 `data_configs.json` embodiments have one either. Removing a documented provider whose modes have unequal upstream coverage is a breaking public-surface decision, not a cleanup. **#3075 closed `NOT_PLANNED` on 2026-09-10T04:21:21Z with the decision recorded in words: "GR00T stays."** The same comment states the direction the removals belong to -- keep GR00T and MolmoAct, drop `lerobot_async`, `curobo`, `moveit2` and `motionbricks` -- so a provider's fate is now a written ruling rather than an open question, and #3389 is the first of the four to land. ### A removed surface takes its own history with it (new lane) Two removals in one band, both `!`, and both answer the objection #3047 could not: the surface being dropped has an upstream owner that ships the capability, and the removal states which one. | PR | What | |----|------| | #3389 | **the `vera` provider goes** -- distributed only as a git repository, so it could never declare a dependency on the package it launched as a subprocess (PyPI rejects VCS metadata); its `install` column read `_(none - git-only)_`, and it was the sole reason for `gymnasium==0.29.1`, `gym-pusht`, `robosuite` and a five-row resolution fork. `discover_ee_frame` stays in `simulation.ik` with its three-rung ladder pin moved to the shared home; three shared guards were re-anchored on the surviving providers rather than deleted; `create_policy("vera")` now raises the registry's ordinary unknown-provider error with no migration shim | | #3386 | **`lerobot_calibrate` goes** -- 859 LOC that listed, viewed, backed up and "analyzed" the JSON LeRobot writes, while recording a calibration means moving a physical arm by hand, which LeRobot ships as `lerobot-find-port` / `lerobot-setup-motors` / `lerobot-calibrate` / `lerobot-find-joint-limits`, all four present in the version `[lerobot]` pins. It also removed `logging.basicConfig()` from all four `lerobot_*` tool modules -- reaching a tool is an import, so that call took over the root logger of whatever process imported it -- and pinned the two remaining import-time `mkdir()` calls by name | | #3419 | **the `motionbricks` provider goes** -- NVlabs' GR00T-WholeBodyControl `motionbricks/` is distributed only as a source checkout, so the `[motionbricks]` extra installed the PyPI support libraries its model code imports while the model itself had to be cloned, `pip install -e`'d and have its checkpoints pulled through git-LFS. Nothing in the package imported it. `create_policy("motionbricks")` raises the registry's ordinary unknown-provider error, with no shim -- and it is the first of the four providers #3075's ruling named as going | | #3426 | **the augmentation-transforms package goes** -- 1,943 lines with no importer anywhere in `strands_robots`; only its own tests, one docs page and one notebook reached it, and `LeRobotDataset(image_transforms=...)` is LeRobot's supported augmentation path. Deleted with `tests/transforms`, `tests_integ/transforms`, `docs/data/transforms.md`, its nav entry, the `07_data_augmentation` notebook and four cross-references in other tests | **What the second pair did not do is mark itself.** #3389 and #3386 both carried `!`; #3419 and #3426 carry neither `!` nor a `BREAKING CHANGE` trailer, and no commit in #3407 -> #3488 does. Two documented public surfaces therefore left `main` with nothing in the commit metadata to say so, which pushes the work onto whoever assembles the tag's removals section by hand. **Both of the first pair's removals deleted their own `changelog.d` fragments**, 23 of them for vera, matching how `3056-remove-vendored-libero-benchmark.md` handled the LIBERO ones -- which is why the fragment count moved by less than the PR count for the first time in the wave. ### A posture flag is checked, so a word for "no" cannot select the other branch The lane #2473/#2487/#2475/#2485 opened, finished across the surfaces an agent actually calls. `"false"`, `"no"` and `"off"` are all truthy strings, so a flag read by truthiness silently selects the opposite of the posture the caller asked for -- and on one of these the opposite posture destroys data. | PR | What | |----|------| | #3355 | `lerobot_train` refuses a posture that is not a boolean instead of reading it as its opposite | | #3360 | a calibration restore refuses an overwrite posture it can only misread | | #3364 | `lerobot_camera` refuses a camera posture it cannot read | | #3365 | `pose_tool`'s `smooth` is checked, so a word for "no" cannot change the trajectory | | #3367 | `resume` and `streaming` are checked, so a word for "no" cannot select the other posture | | #3368 | the rollout facades check their posture flags (closes #3356) | | #3369 | the field-scoped guard sweep discovers a guard that calls the shared read rule directly | **#3356** enumerated seven such tools and named `lerobot_calibrate`'s `overwrite` as the destructive one. That row resolved by deletion rather than by a guard: #3386 removed the tool, so **#3361** -- the pull request adding its check -- closed unmerged with its subject gone. ### An operator is asked before a command reaches a real actuator (new lane) The dashboard's HITL gate (#3184) established that an agent tool call which would move real hardware pauses for a human. Four agent-callable surfaces were still dispatching straight through, and each of them writes to a physical device. | PR | What | |----|------| | #3418 | `use_unitree` asks an operator before a mutative RPC reaches the robot | | #3420 | `serial_tool` asks an operator before a write reaches the servo bus | | #3430 | `pose_tool` asks an operator before a goal position reaches the arm | | #3431 | the hardware `Robot` tool asks an operator before `execute` / `start` reach real actuators | | #3403 | **the other end of the same contract**: a consent request is built from a refusal's *code*, never its prose, so what the operator is asked cannot drift from what the surface refused | | #3508 | the lane widened from a named list to a default: `use_unitree` gates **every** verb it does not know to be a read, so a verb added later is gated until it is declared safe | ### A value that can never be honored is refused at the entry point (new lane -- the band's dominant fix shape) Thirteen fixes, one shape, and it is the next question after the shared-value-domain sweep. Those values were out of domain; these are *in* domain -- finite, in range, correctly typed, past every existing guard -- and still cannot produce what the caller asked for. So the surface accepted them, did nothing, and reported a result. | PR | What | |----|------| | #3502 | **the sharpest of them**: a benchmark `success`, `failure` or `dense_reward` clause naming an entity that cannot exist compiles, never fires, and `evaluate_benchmark` publishes `success_rate: 0.0` **beside `success_measured: true`** -- a number certifying a success it could never observe, and on a `failure` clause the same defect reads as 100% success | | #3497 | a `stop_when` base predicate on a fixed-base robot can never fire -- 29 of the 61 constructible sim robots report no floating base, so `base_tipped` on an SO-100 is permanently False | | #3514 | a predicate-DSL entity name that cannot name anything is refused rather than resolving to a constant | | #3501 | an `on_frame` hook that is not callable is refused before the rollout starts, not at the first frame | | #3511 | a `policy_object` the rollout cannot drive is refused at the entry point | | #3494 | `start_recording` refuses a camera it cannot render, rather than opening a dataset it will not fill | | #3510 | a terrain resolution the requested kind cannot draw is refused | | #3516 | a pose too large to be a joint coordinate is refused instead of being written | | #3492 | a policy provider `start_task` cannot build is refused in an envelope, not raised past the tool boundary | | #3493 | an unknown Reachy move library is refused instead of collapsing to `dances` | | #3504 #3506 | a tuning strategy the backend cannot forward is refused -- Cosmos3, then GR00T, where `expert_only` was accepted and reached no flag, silently full-finetuning the backbone while reporting success; pinned by #3507 | | #3517 | a posture flag is checked, not coerced with `bool()`, so `"false"` cannot select the permissive branch and move a joint | ### A resource a failed bring-up adopted is released (new lane) The counterpart of the verified-halt lane, and the question it did not ask. That lane asked whether a *stop* had stopped; this asks what frees a thing whose *start* never finished -- because the success path is usually the only place the resource is adopted, so a failed bring-up leaves it running with nothing holding a reference to it. Every one of the five is a leak that no later verb can reach. | PR | What | |----|------| | #3530 #3532 | both routes out of `init_device_connect_sync`: a bring-up that raises, and one that returns no runtime, each left an event loop parked in `run_forever` on an unreachable thread -- and the second route reported `None` against a non-optional return, so the caller's own status line sent an operator to a warning that was never written | | #3526 | Reachy teardown joins the loop thread and **closes** the loop it previously only stopped -- `loop.stop()` is not the counterpart of `new_event_loop()`, and `close()` on a running loop raises, so the join is what makes the close possible | | #3522 | a link handshake the driver abandons is released, rather than left subscribed for the life of the process | | #3519 | leaving the mesh waits for the loops that publish on it -- the roster of loop threads had four writers and no reader, so `stop()` returned while nine loops were still live and one published after the session was released | ### A posture flag is checked, not read by truthiness -- the lane reaches every policy provider The sweep #2473/#2487/#2475/#2485 opened and #3355 -> #3369 carried through the agent-callable tools, finished across the providers, the trainers, the recorder and the sim facade. `"false"`, `"no"` and `"off"` are all truthy strings and `0` is a falsy int, so a flag read by truthiness acts on the opposite of the posture the caller chose -- and on two of these the truthy branch is the permissive one. | PR | What | |----|------| | #3538 | `lerobot_local`'s `strict_keys` and `cache_model` are checked | | #3543 | GR00T's `strict` and `strict_keys` are checked (closes #3540) | | #3546 | Kimodo's `trust_remote_code` is checked -- the truthy branch is the one that executes checkpoint code | | #3548 | the three RL posture flags are checked | | #3549 | `dataset_recorder`'s `strict` is checked | | #3553 | `create_world`'s `ground_plane` is checked | | #3554 | `add_object`'s `is_static` is checked | | #3574 | WBC's `allow_missing_models` is checked -- the truthy branch proceeds with models it could not find | | #3575 | `gr00t_inference`'s posture flags are checked | | #3582 | the read-side twin: a rover's sensor summary read a headlamp by truthiness, so a field the frame did not carry and a value the write door had already refused both rendered as `off` | ### An action naming a key the surface cannot carry is refused (new lane) One level below #3213. That fix refused an `action` **verb** a driver does not declare; these refuse a **key inside the payload of a declared verb**. Each key had been silently dropped, so a command naming a joint, axis or intent the robot does not have was answered as though it had been applied. | PR | What | |----|------| | #3534 | a Crazyflie action naming a key no setpoint carries is refused | | #3537 | a Reachy action naming a key no axis answers is refused | | #3539 | a Microduck action naming a key no intent carries is refused | ### A name is held to the alphabet its consumers can carry (new lane) The containment question #3296/#3298 asked of a filesystem join, asked of a *name*. A camera name is interpolated into an MJCF attribute, a dataset column and an MQTT topic, each with its own grammar, and none of the doors that accept one graded it. | PR | What | |----|------| | #3559 | a camera's name is a bare token at every door on the hardware `Robot` that accepts one | | #3565 | `add_camera` holds a camera's name to the alphabet its consumers can carry (closes #3562) | | #3570 | the recording docs name that alphabet, and every documented name is graded against it | | #3557 | a shadow-topic segment must be a single MQTT topic level | | #3558 | only a pointer topic gets the camera-frame drop exemption, so a real frame topic cannot inherit it | ### A stop grades the verdict it was given (the verified-halt lane reaches `robot_mesh`) The lane has asked since the #2750 -> #2836 band whether a stop had actually stopped. These three are the case where the caller *held* the answer and did not read it: every route reported success and audited `ok=True` over a delivered refusal, a timeout and a lockout alike. | PR | What | |----|------| | #3560 | Device Connect `emergency_stop` grades each target's stop verdict | | #3566 | the single-target Device Connect stop grades the target's verdict (closes #3561) | | #3571 | the mesh-path stop grades what the envelope send returned (closes #3568) | ### One camera-routing precedence, honoured on every router that implements it The lerobot-local page states a three-rung ladder -- `camera_key_map` when given, then the embodiment's `obs_rename`, then a name heuristic. Four functions implement rung one, and three of them did not. | PR | What | |----|------| | #3545 | an explicit `camera_key_map` outranks another camera's exact-name match | | #3550 | the sim's implicit free view does not take a declared image slot -- it had been seated first and a real camera dropped to make room | | #3555 | `camera_key_map` routes cameras on the declarative embodiment path, where an `embodiment=` caller's map was discarded outright and the model received no image input at all | ### A private name must have a reader (new lane) Two cross-cutting invariant fixes, each deleting state that is written and never read, and adding the grader that keeps the next one out. Both are net-positive, so the net-negative refactor table above is unchanged. | PR | What | |----|------| | #3580 | private instance state must have a reader -- six write-only attributes across seven files, with a ledger naming the two legitimate exceptions | | #3583 | the same rule for a private module-level name | ### A run that commanded nothing is refused, not scored (new lane -- the band's dominant shape) The outcome form of the #3492 -> #3517 lane. That one refused a *value* that could never be honored at the entry point; these five refuse the *result*. Every argument passed every door, the run then applied nothing or the dataset recorded nothing, and a number was published over it -- so a caller could not tell a working rollout from an empty one. | PR | What | |----|------| | #3620 | **the sharpest**: `evaluate` and `_evaluate_with_spec` tolerated an empty action chunk on every step and still published `success_rate`, `avg_reward` and `pass_hat_k` under `status="success"` for a rollout in which `send_action` was never called. `steps_advanced` and `actions_applied` are now separate numbers, and a run that commanded nothing is an error naming its own cause -- the rule two sibling consumers of the same condition already applied | | #3612 | `replay` refuses an episode whose frames carry no recorded action -- one frame with no `action` is tolerated so it keeps its time slice, but an episode where *no* frame carried one returned text byte-identical to a real replay; the payload now reports `frames_with_action` beside `frames_applied` | | #3613 | `stop_recording` reports the frames a `strict=False` recorder dropped. Partial loss is reported, total loss is refused **by its real cause** -- an all-frames-dropped episode had hit the empty-dataset refusal, which prescribed exactly the `start_recording` -> `run_policy` -> `stop_recording` recipe the caller had just followed | | #3608 | a declared image column that no frame fills is refused **by name**. The recorder stripped an undeclared camera to avoid LeRobot's `Extra features` error and assumed the symmetric `Missing features` branch away -- so a near miss on one of three camera names recorded zero frames, silently, and a total mismatch warned about a consequence that was not the one that happened | | #3584 | an action column present as `None` is refused, not recorded as `0.0` -- a placeholder is a reading nobody took | ### A shutdown that cannot complete does not keep the resource (new lane) The #3519 -> #3532 lane asked what frees a thing whose *start* never finished. This asks the other half: what happens when the *stop* raises. The resource is released either way, because the caller who asked for the teardown has no second verb to reach it with. | PR | What | |----|------| | #3603 | a node that will not be destroyed no longer holds the rclpy context -- the shutdown is reported, and the context released, rather than left initialized for the life of the process | | #3602 | a ROS 2 bridge that will not shut down cannot hold the devices, so a hardware `Robot` teardown completes even when the bridge's own stop fails | | #3590 | the `ros2_bridge` flag is graded **before** the rclpy probe, so an unusable posture is refused by the posture rather than diagnosed as a missing distro | | #3596 | the test half: four cells asserting an rclpy `ImportError` *assumed* rclpy absent instead of establishing it, so on any host with a distro sourced they ran past the probe into `rclpy.init()` and `create_node()` and left the context live for the rest of the session | ### An offline snapshot names what the live registry registers (new lane) The dependency-floor lane's twin. That one asked whether a declared bound was high enough; this asks whether a *copy* of the library's own vocabulary is still true. `training/lerobot` keeps `_FALLBACK` snapshots for hosts where lerobot is absent, and a snapshot cannot tell you it has gone stale. | PR | What | |----|------| | #3616 | the offline policy-type snapshot names every type lerobot registers -- it was nine types behind, **and its own test graded the constant against itself**, so no drift could ever fail it; the pin is now snapshot-against-live plus a per-capability subset invariant that needs no lerobot installed | | #3614 | `sample_weighting` accepts the fields its config declares -- one field list written down three times, whose consumers disagreed: `build_config` refused an unlisted key and `build_command` silently dropped it, so a typo either raised past the preflight or trained with the default under `status="success"` | | #3618 | a reward type that does not resolve gets no claim about its fields -- the gate refused a non-native `type` and then graded the caller's keys against the offline fallback for the type it had just said does not exist, naming three fields where the real one has 27 | | #3623 | the third member of the lane, and the same misdirection shape one file over: a **policy type** that does not resolve gets no claim about its capabilities | ### A door refuses a collaborator it cannot use (new lane) | PR | What | |----|------| | #3617 | a `robot_name` the host cannot route to is refused at the door -- the loop read it every tick while the session had already reported itself started, and the refusal sits ahead of the teardown of any stream already registered under that key | | #3619 | every door that accepts a teleoperator refuses one it cannot poll -- one surface already checked `get_action`, two did not, so an unpollable device got a topic, a `running` status and a loop burning `AttributeError` at 200 Hz | ### A criterion already satisfied at reset is reported, not read as a result (new lane -- the band's dominant fix shape) The third and sharpest reading of one question. #3492 -> #3517 refused a *value* that could never be honored at the door; #3584 -> #3620 refused a *result* the run never produced. These five ask it one step earlier: what does a surface publish when the **scene itself** already satisfies the thing being measured, before the policy has acted? Every one of them is well-formed input, past every guard, producing a number the actor did not earn. | PR | What | |----|------| | #3629 | **the sharpest, and the one with the widest consequence**: `measure_agreement`'s two fractions are accuracies over a class-imbalanced single-label column, so a judge that answered a constant scored the holdout's own class balance -- 0.95 with one episode tagged in twenty, 1.00 with none -- and nothing in the payload distinguished it from a judge that had looked. The figure that decides whether a judge may filter training data was unreadable off the figure; it now reports the baseline its fractions must beat, computed over the compared population rather than the whole input | | #3633 | an episode already successful at reset is counted, not silently averaged in -- a rollout that commanded nothing scored `success_rate: 1.0` with `avg_steps: 1`, the mirror of the `success_measured` guard that already warned about the hard `0.0` | | #3637 | a `stop_when` clause already true at reset is reported, not read as an achievement -- the rollout stopped on step 1 with nothing moved and reported `stopped_reason="predicate"`, indistinguishable from a goal reached | | #3642 | the same defect on a benchmark `failure` clause, which is the mirror case: a clause the spawn already trips marks every episode failed at step 1, and because the loop samples `is_failure` before `is_success` it discards a success the run had already earned | | #3639 | the RL side: `evaluate()` says whether its `success_rate` measured the policy at all, rather than publishing a figure over episodes whose outcome was never determined | ### A plan that commands nothing is a failure, not a plan The #3584 -> #3620 lane read at the producer rather than the rollout. A planner returned a trajectory, the runtime accepted it, and the arm was sent nothing. | PR | What | |----|------| | #3652 | a MoveIt2 plan that commands nothing is a planning failure -- a positionless waypoint row is refused where it had been unpacked into an empty command | | #3657 | a cuRobo plan whose waypoints do not all describe the same joints is refused. The joint-key list was resolved **once**, from the first waypoint, and then paired with every later one under a non-strict `zip`, so a narrower row downstream was filled from the wrong names -- and which behaviour you got depended on `action_horizon`, a streaming parameter that is no part of the plan. Graded at cache time, not at serve time, so the refusal lands before the first chunk runs | | #3655 | an evaluation whose every action commands nothing is refused rather than scored | | #3643 | `run_policy` stops on an episode the recorder could not flush instead of continuing over it | ### A ROS 2 name must be able to name a topic, and a refused call leaves no node behind (new lane) Two halves of one surface, and the oldest interop lane in the package. The naming half is the #3557 -> #3570 alphabet question asked of the ROS 2 graph; the lifecycle half is the #3519 -> #3532 release question asked of a node. | PR | What | |----|------| | #3636 | `command_robot_name` is graded against the topic grammar it is interpolated into, on both hardware bridges | | #3638 | a publisher is cached under its topic, not under the caller's spelling of it, so two spellings of one topic cannot hold two publishers | | #3648 | a published `JointState` names the joints its positions belong to | | #3628 | `joint_limits` is answered before the node exists, rather than requiring one to be built first | | #3635 | a refused `mesh=` leaves no ROS 2 node behind | | #3651 | `list_topics` reports every topic rosapi named, not the subset one call returned | ### A reader grades the size its own payload declares (new lane) The sizes-a-read question the Modbus and DDS codecs already answered, asked of a file format and a dataset file. | PR | What | |----|------| | #3658 | an `.spz` header count must account for its payload. `num_points` is a signed int32 that sizes all five body blocks and was graded nowhere, while the magic and version beside it were graded on the very next lines; a count short of the payload read each block's declared **prefix**, so every later block started at the wrong offset and the whole scene decoded to garbage and was reported as loaded. NumPy treats any negative `count` as "the rest of the buffer", so the under-declaring direction degraded silently rather than raising. The count now reconciles with the payload's exact byte length, validated against the shipped corpus scene the library itself references | | #3650 | a Robotiq reply is read by a length the codec has graded, not by the length the frame claims | | #3627 | a dataset metadata file that holds no JSON object is answered, not raised through the caller | ### An unreadable reply names the peer, not the codec (new lane -- the band's dominant fix shape) Four clients, one shape: the peer's response was decoded inside a `try` whose `except` reported a *codec* failure, so a malformed, truncated or empty reply from the other end of the wire was answered as a local decoding bug. The caller is then sent to the wrong side of the connection to debug it, which on a remote-inference path means reading your own msgpack while the server is the thing that is broken. | PR | What | |----|------| | #3670 | a frame the inference client cannot read names the peer, not the codec | | #3672 | the same on the Cosmos3 client | | #3676 | the same on the MoveIt2 client | | #3677 | the same on the GR00T service client | ### A configuration entry that is not a string is refused (new lane) The posture-flag lane read on the value side rather than the boolean side. A credential, a name-list entry or a response body arrives as `None`, a number or a nested object, passes every existing guard because only its *presence* was checked, is stored, and is then dropped by whatever finally consumes it -- so the surface reports a configuration it is not running. | PR | What | |----|------| | #3666 | a DDS Security credential must be a string the participant can carry -- the one with a tracker item behind it: `_validate_dds_security_config` accepted a `None` credential that the participant QoS then dropped, so a run believed it was authenticated when it was not (closes #3664) | | #3667 | a dashboard settings list entry is a string or the list is refused | | #3674 | a mesh ACL `cert_common_names` entry is a non-empty string or the ACL is refused | | #3679 | a Reachy daemon body that is not an object is refused, not read as one | ### A motion that did not reach names what stopped it (new lane) The verified-halt lane asked whether a stop had actually stopped. This asks the same question of a *move*: the servo was commanded, it did not arrive, and the surface reported the pose it was asked for rather than the one it reached. | PR | What | |----|------| | #3733 | a `move_to` that did not reach names the contact or the joint limit that stopped the servo | | #3705 | `set_gripper` says what the fingers closed on | | #3703 | `attach_bodies` reports whether the bodies actually touch | | #3727 | the feature underneath all three: `get_robot_state` says which way the arm extends from its base | **#3732 is the instructive part of this lane.** It was the `feat`-shaped version of the same idea -- "a not-reached `move_to` **or `rotate_wrist`** names what stopped the servo" -- and it was closed unmerged in favour of the narrower fix #3733 plus a tracker item, **#3734**, for the `rotate_wrist` half. That is a descope recorded rather than dropped, and it is why the `rotate_wrist` row is an open issue rather than a silent gap. ### A camera name answers for the camera it names The surface that has produced more naming defects than any other, four more in one band: a name the sim will *list* must be a name the sim will *accept*, once each, at every door. | PR | What | |----|------| | #3683 | every camera name the sim lists can be rendered through it | | #3693 | a camera enumeration offers each camera once, not once per name | | #3691 | `add_camera` / `remove_camera` accept `camera_name` for `name`, as every sibling door does | | #3730 | `start_recording` names the cameras by a name that answers for them | ### The `0000-` fragment placeholder defect is closed (sequencing item 6, retired) Recorded on this page for three syncs as unactioned, and fixed as a class rather than as four filenames. The old guard permitted the placeholder because it only rejected a number at or above 9000. | PR | What | |----|------| | #3715 | a `0000` fragment is a placeholder and is refused | | #3721 | the placeholder-number rule has one owner, so **every** surface refuses it -- the `## One owner answers one question` shape applied to the repository's own bookkeeping | | #3722 | the one fragment that named no pull request now names the one that carries it | `changelog.d/` holds **zero** `0000-` fragments for the first time since they appeared, and `--check` reports `OK (1600 pending)`. ### The dashboard gets a server `strands_robots/dashboard/` had spent nine bands accumulating rails -- `auth`, `access`, `consent`, `safety_state`, `settings`, `log_redaction`, `validate_scope`, `agent_hitl`, `task_timeout`, `ttl_cache`, `agent_motion` -- with nothing serving them. This is the slice the #2848 decomposition argument was for. | PR | What | |----|------| | #3695 | **the server core, wired to the landed rails, fails closed** (19 files, +1,609 / -74) -- `server.py`, `access.py`, `routes_auth.py`, `cli.py` and a served `static/` bundle, taking the package eleven -> fifteen modules | | #3710 | a caller-supplied value cannot forge a log entry | | #3738 | a credential store that parses but cannot serve as one is recovered, not answered with a 500 | **#3122 is still open and still names what the server slice owes**: both enrollment routes skipping the auth middleware, and two directory reads validating existence rather than containment. #3695 landing a server makes that issue sharper, not softer. ### Seven features end a nine-band featureless run Six of the seven are one theme -- the library says more about the thing it just handed you. | PR | What | |----|------| | #3695 | the dashboard server core (above) | | #3728 | `tool_frame` declares the tool point a site-less model lacks | | #3724 | `step` feeds an open dataset recording, so a hand-stepped scene records like a rollout | | #3694 | SO-arm joints can be addressed by label rather than by index | | #3727 | `get_robot_state` says which way the arm extends from its base | | #3731 | `Robot(tool_name=)` names the tool, so two robots of one type can share one Agent | | #3697 | `render_dir` on the constructor moves the render sandbox per `Simulation` | ### A surface offers a next step the thing it just made can take | PR | What | |----|------| | #3686 | `add_robot` offers a next step the model it added can take -- it had advertised `run_policy` for a model with **zero actuators** (closes #3684) | | #3687 | the sim tool description describes the world the agent is joining, not a generic one | | #3719 | a paused real-robot call tells the script how to resume it | | #3742 | an unreachable GR00T server fails fast and names the remedy | | #3741 | `run_policy` and `eval_policy` are refused while another rollout already drives the robot | | #3680 | the #3584 -> #3620 lane at the rollout entry point itself: a rollout that never commanded the robot is an error | ### A refusal applies nothing from its own call | PR | What | |----|------| | #3645 | a refused resize applies nothing from its own call. `set_geom_properties` persisted colour, friction and size up front, then re-derived the body inertial from a compile of the persisted spec; a shrink below the inertia floor does not compile, and the rollback restored only the property the refusal named -- so a call returning `status="error"` left a colour and a friction the caller never asked to keep, against a docstring promising the model is "left untouched". Fixed by ordering rather than by undoing: every model write now sits below the last thing that can refuse | | #3622 | removing a robot no longer drops a loaded scene | | #3625 | a `cameras=` no native driver will open is refused, not dropped | ### The largest test-pin band on this page (11 pins) Eleven of the band's 39 commits are `test`, the most any band here has carried, and each grades a door an existing suite reached only through a stand-in -- so the code under them was exercised without its answers being pinned. None owes a changelog fragment, which is why 39 PRs produced 28. | PR | What | |----|------| | #3641 #3647 #3632 | one wire field, every door that writes it: the Feetech domain, the Dynamixel two-byte frame-field boundaries from both sides, and every Modbus field the Robotiq codec refuses | | #3644 | the IDL-class resolver both Unitree drivers report an SDK drift through -- a function two sibling drivers carry identically, whose reason nobody had ever produced, with a cell asserting the twins answer the same | | #3660 | every door on the Franka shutdown path -- the teardown-absorb arms a fake that fails only on `close()` reaches | | #3646 #3659 | the MuJoCo rebuild state whose handle does not survive, and every reason a disagreeing spec is refused | | #3634 | every agent verb the Robotiq gripper schema declares | | #3621 #3626 | a raising UR controller is reported, never propagated; a Go2 frame that never reached the wire is named | | #3656 | the twin of every answer a Feetech cell already reads | ### An evaluation reports how close it came and how often it holds | PR | What | |----|------| | #3521 | **the band's one feature**: `pass_hat_k` and `max_step_reward` on the evaluate payload (+380 / -3 across 3 files) -- a run reports the probability that a k-attempt sample succeeds, by the subset form `C(c,k)/C(n,k)`, rather than only a mean that cannot separate a consistent policy from an erratic one | | #3528 | the same figure on the other route: `evaluate` early-returns to `_evaluate_with_spec` for a `spec` and runs the `success_fn` path itself, so only the spec payload carried the new keys and `eval_policy(success_fn=...)` reported a success rate with no reliability figure beside it | | #3520 | the docs half -- the notebook series can score the policy it just trained (+808 / -0) | ### The docs state each contract once (new lane) Seven `docs` PRs, every one net-negative, together **-3,575 lines**. Three of them are the same defect this page records everywhere else -- a page describing a surface the code does not have. | PR | What | |----|------| | #3469 | README is 120 lines; configuration, the numbers and the whole-loop example move into `docs/` (**-1,459**) | | #3472 | remove the dashboard pages that document a command the package does not ship (**-1,012**) | | #3463 | `validate_command` states its contract by refusal shape, not rule by rule (**-900**) | | #3471 | the lerobot-local and protomotions pages state each contract once (**-861**) | | #3440 | four policy pages keep their contract facts and lose the essays (**-668**) | | #3470 | the robot-family catalog is generated from `robots.json`, not typed | | #3473 | drop a documented `RuntimeError` `stream_dataset` cannot raise | ### One rule, one owner, across the test tree (new lane) The whole-tree grader audit (#3105 -> #3126) established that a rule sweeping the repository must resolve the whole tree. This band asked the next question: how many copies of the rule are there? | PR | What | |----|------| | #3417 | "a public module, class, method or function has a docstring" was asserted by **fourteen** copies of one AST walk under `tests/`, one per package; one linter rule replaces them (**-1,663**) | | #3468 | one grader enforces the module-not-filename citation rule across the package (**-709**) | | #3483 | the user-registry suite has one owner, and an inert alias pin is deleted (**-486**) | | #3444 #3455 #3457 #3460 #3464 #3465 #3475 #3482 | the import-hygiene run beside it -- a module that reaches an optional extra at import time skips rather than errors, a fixture that blocks a dependency restores what it displaced, `MUJOCO_GL` has one owner under `tests/`, and the camera pose domain is graded in CI rather than only where newton is installed | ### A telemetry field the frame does not carry is not a reading (new lane) A vendor IDL declares a field the wire may leave unset, and the decoder defaulted it to a typed zero. So an absent measurement and a real one read identically: a level IMU and a missing IMU are both `rpy=[0,0,0]`, a missing layout id is `0`, a missing lidar fault code is `0`. | PR | What | |----|------| | #3376 | the Unitree drivers agree on which telemetry fields are readings | | #3379 | a G1 IMU field the message does not carry is not a level reading | | #3380 | the lowstate layout id is a reading, not an `int()` cast | | #3382 | a lidar field the message does not carry is a `None`, not a fault code | | #3394 | `rt/lowstate.motor_state` is a reading, and the humanoid takes it | | #3381 #3383 | the Booster T1 snapshot reports an absent IMU field, and an absent frame field, as absent | ### A stop reports its outcome, and now so do the drivers, the sessions and the inference server The lane opened in the #2750 -> #2836 band (eight sites returning success because a stop had been *issued*) is the wave's longest-running fix shape, and it has now reached the driver programme, the rollout entry point, the training-session registry and the remote inference server. | PR | What | |----|------| | #3060 | a cooperative stop issued before a rollout's first frame halts it, instead of being discarded (closes #2833) | | #3091 | a UR halt reported as done is a halt the rollout thread has actually left | | #3092 | a camera recording reported as stopped is one whose recorder thread has exited | | #3100 | a remote stop reports which rollouts it halted | | #3104 #3110 | a session reported stopped is one whose process is confirmed gone; a session whose process cannot be inspected keeps its record rather than being dropped | | #3108 | a driver `stop()` that could not halt the robot says so | | #3114 | a stopped `PolicyServer` stops serving the clients it already has, not only refuses new ones | | #3224 | `stop_teleoperate` reached on the teleop loop's **own** thread still releases the devices -- the lane's original defect wearing re-entrancy as a disguise | | #3341 | a recorded Isaac or Newton rollout leaves the robot idle when it ends -- the release the MuJoCo backend already performed, now on the other two backends | | #3338 | a UR halt reaches a setpoint already in flight inside `send_action` | | #3326 | a Crazyflie flight command the radio would not carry is a refusal in an envelope, not a raise past the tool boundary | | #3374 | a caller-stopped G1 or Go2 rollout names what stopped it | | #3390 | a Robotiq halt that never reached the gripper is reported | | #3392 | a Reachy `motion_stopped` names the halt that still holds, not the last one asked for | | #3588 | the last Go2 sport-mode release is verified, not assumed to have failed | | #3609 | the Go2 write gate shuts on a mode reading that says the robot is not free | | #3597 | a silent policy server is reported as silent, not as absent | ### The whole-tree graders grade the whole tree (new lane) A guard that sweeps the repository is only as wide as the tree it resolves. Seven PRs in one day found four spellings of the same defect: the sweep read a narrower tree than the rule it enforces, so it reported a clean result over code it never opened. Each is a false *pass*, which is the direction that costs the most. | PR | What | |----|------| | #3107 | the whole-tree grader preflight derives its roster from the tree instead of a hand-maintained list (closes #3105) | | #3113 #3118 #3120 #3126 | the derivation resolves a grader's walk root held in a loop variable, passed as a parameter, bound inside the function, and named by a symbol or a method | | #3112 | the pacing sweep grades every ticker, not every ticker spelled `Ticker` (closes #3106) | | #3119 | a whole-tree guard locates the tree it grades rather than the working directory the run started in | | #3274 | a walk root derived from a symbol imported through a *re-exporting* module is refused instead of silently resolving to nothing (closes #3273) | | #3291 | the render-gating rule reads a camera image out of an observation, not only out of `render()` -- the one such assertion in the tree failed as a bare `KeyError` on a headless host (closes #3286) | | #3288 #3289 | the GL-free half of a construct-then-spend case keeps running; the GL gate on a render test has one owner | | #3313 #3293 | every environment variable the package reads is documented, and a grader says so; a shared numeric domain that counts its own families enumerates that many | | #3324 | that same env-var grader follows a **module constant** to the names it holds, and named three training variables it had missed | | #3316 | `check_merge_blockers` names whether auto-merge will perform the merge it is waiting on | ### Refuse a span that resolves to no work (new lane) Seven rate, period and horizon domains, one shape: a value that is positive, finite, inside every existing guard -- and still produces nothing. The sharpest case is a rollout that ran zero control steps and reported `success`. | PR | What | |----|------| | #3130 | a rollout `duration` that resolves to no control step is refused | | #3123 | `run_multi_policy` honours the step count it resolved instead of re-deriving it from the float | | #3116 | the Isaac preview cadence refuses an infinite period | | #3121 | a `lerobot_train` launch topology that cannot be honored is refused | | #3103 | a rollout runs for the wall clock it was asked for (a deadline, not a delay) | | #3124 #3129 | the rate guards judge every fps spelling their own domain accepts; an on-disk recording rate beyond the float range is unreadable, not an exception | | #3134 #3148 | a `lerobot_camera` capture span its rate records no frame in is refused; `stop_recording` accepts no destination it will not write | ### A cache is valid only while its source still holds its bytes (new lane) Four surfaces in four hours, one shape at each: a parse was memoised against something weaker than the bytes it was parsed from -- a directory's mtime, one file of several, or nothing but the process's lifetime -- so a file that changed underneath the entry kept being answered from the stale copy. Each is a false *hit*, which is the direction that costs the most. | PR | What | |----|------| | #3141 | the dashboard auth store's cache serves a hit only while the file still holds its bytes | | #3142 | a cached mesh ACL is served only while its file still holds its bytes | | #3145 | a cached robot registry is served only while **every** file it was parsed from holds its bytes | | #3147 | an asset mesh scan is memoised for one pass, not for the process (closes #3144) | | #3173 | a cached render backdrop belongs to the camera it was rendered for, not to four of its six fields | | #3177 | a cached qpos-address map belongs to the robot it was built for | | #3220 | the VERA IK bridge a rollout solves on is a bridge for the model that is bound -- the slot was keyed on nothing, and the simulation rebinds a freshly compiled model on every rollout | **#3144 named the mechanism precisely**, and it is worth quoting because it is the reason this lane is a defect class rather than a tidy-up: the mesh cache keyed on the top directory's mtime while answering for the whole subtree, so a download into the declared `meshdir` was discarded as "no meshes available" -- the cache reporting an absence it had itself created. **The shape has since moved on.** The first four were "the source changed underneath the entry"; #3173, #3177 and #3220 are "the key does not name the thing the entry is about" -- a backdrop keyed on four of `CameraParams`' six fields, an address map keyed past its robot, and a `MinkIKBridge` keyed on nothing at all while the engine hands the policy a newly compiled `MjModel` on every bind. Every one of the seven is a false *hit*, which is the direction that costs the most: #3220's unchanged-DOF case solves against a model that is no longer in the scene and reports an action dict shaped exactly like a good one. ### An undeclared agent verb is refused, not run as the halt (new lane) A driver's `tool_spec` declares the verbs an agent may plan against, and nothing between the model and the tool validated the `action` it sent against that schema. Eleven of the twelve shipped native drivers ended their dispatcher in a bare `else` whose comment named the halt, so every value the enum does not cover ran the halt and answered as though the caller's own verb had been dispatched. | PR | What | |----|------| | #3213 | an agent `action` a driver does not declare is refused instead of running the halt -- a typo (`"sensor"`), a verb borrowed from a sibling (`"sensors"` on a `URDriver`, whose read verb is spelled `state`; `"stop"` on a `CrazyflieDriver`, whose halt is spelled `land`) and a non-string all stopped the robot under a success-shaped answer. `drivers.base` gains `declared_verbs` + `undeclared_verb_error` as the single owner of that list and that refusal, read back off the schema the agent planned against; the refusal names every declared verb so an agent can correct itself in the same turn (17 files, +378 / -41) | The non-uniform vocabulary across the family is what made the confusion likely rather than hypothetical -- `land` where a sibling says `stop`, `state` where a sibling says `sensors`. `FeetechDriver` had already refused, and its own test docstring recorded the fallthrough as the defect; this closed the other eleven against that precedent rather than proposing a new rule. ### A verdict names the thing it was written for (new lane -- the band's dominant fix shape) Six PRs, one shape: a surface answered a question truthfully about some *other* subject. A pid the kernel had reissued, a stand-in the code under test never consults, a pull request that has already merged, a motion call adjacent to the one a human approved -- in each case the answer was well-formed and about the wrong thing, which is why none of them looked like a failure. | PR | What | |----|------| | #3219 | a training session's running verdict names the process the record was written for | | #3237 | its residue: the store's own test docstring and `_NotRunning` stub still described the pid-existence verdict #3110 replaced, so a finished run whose pid was reissued read as live (closes #3227) | | #3250 | a stand-in the prune never consults left the verdict to whichever host the suite ran on | | #3233 | an already-merged pull request is not a mergeability GitHub is still computing, so the report stops prescribing a re-read that can never settle (closes #3231) | | #3248 | an unreviewed pull request is owed by whoever can actually clear the rule, not by whoever is nearest | | #3254 | a one-shot HITL motion grant names the call the human approved, not a near neighbour of it | | #3255 | a redacted log record is still renderable by the formatter it was written for | | #3268 | a standing request for changes is a blocker owed by the account that made it, not by whoever is nearest | ### One owner answers one question (new lane) A question with a named owner acquires a second reader that answers it differently, and the two disagree in both directions. Each of these is the same repair: delete the second reading, not reconcile it. | PR | What | |----|------| | #3203 | "are this model's meshes on disk?" is answered by its owner -- `resolve_model_path` was a third reader walking the model directory for files with a mesh extension, where `_mjcf_missing_meshes` resolves each `file=` against the model's `<compiler meshdir>` the way MuJoCo does, across every `<include>`d fragment | | #3208 | a ROS 2 topic name is judged by one rule, and it is the mapping's own | | #3211 | the Device Connect state RPC reads the device that owns the bus, rather than whichever wrapper or driver was nearest | | #3216 | a Cosmos3 `action_mapping` is a rename, so two columns cannot become one -- the key side was validated and the target side was not, so a mapping could silently deliver one joint's value under another joint's name | | #3209 | each robot's `joint_states` reach that robot's own topic on the hardware RTPS bridge | | #3214 | Isaac's `remove_robot` prune is bounded at `/`, USD's path separator, so a robot whose name merely *extends* the removed one keeps its prim (with `arm` and `arm_left` both live, removing `arm` had dropped `/World/Robots/arm_left`, leaving a live robot with no prim for `destroy` to release) | | #3252 | the episode count `meta/info.json` declares gets one owner, so the reader and the verifier cannot disagree about it | | #3221 | a registry alias is keyed by the fold **every lookup applies to its query**, so the index and the lookup agree on what a name is | | #3230 | a VERA port means the same thing whichever spelling named it | | #3215 | the Device Connect allowlist advisory follows the transport, not one of its two sources | | #3265 | a recorded length of zero is a frame count, not a missing column -- so a reading is not reported as an absence | | #3300 | the parquet-truth counts a rollout reports are graded by their one owner | | #3323 | a failed dataset-stack import is diagnosed by one owner, so the refusal names the install that fixes it rather than the module that happened to raise | | #3308 | a `robot_mesh` command body is decoded by one owner, so every refusal reaches the tool envelope rather than escaping past it | | #3375 | every simulation backend reports the rollouts it is driving, so a peer's status and its state agree (closes #3359) | | #3378 | `list_policies_running` answers on every backend, not only the one that implemented it | | #3399 | `describe()` advertises the rollout reader its own entries name | | #3404 | every rollout loop acquires chunks through one pipeline and one query helper | | #3600 | a backend loader is a callable returning the backend class, graded once | | #3604 | one owner for the off-policy warmup batch relation | | #3606 | a composite joint group is exclusive whichever child declares it | | #3610 | `evaluate_benchmark` resolves `robot_name` the way its siblings do | | #3624 | `replay_episode` resolves `robot_name` the way its siblings do -- the same surface, the last entry point that did not | ### A packaging declaration reaches the install that needs it (new lane) A module imported a distribution that no extra declared, so the documented install resolved cleanly and then failed at import -- the same class of gap `v0.5.1` was cut for (#1930), read one layer down: not a floor too low, but a requirement never written. | PR | What | |----|------| | #3310 | the session tools' process dependency is declared | | #3315 | the rover driver's HTTP transport is declared where an install reaches it | | #3318 | the UR driver's RTDE client is declared where an install reaches it | ### A byte that cannot be decoded is reported, not raised (new lane) Four surfaces, one shape: text was decoded on whatever the process happened to be configured for, or strictly enough that one bad byte took the caller's call down instead of being reported. | PR | What | |----|------| | #3307 | on-disk text states `encoding="utf-8"` instead of taking the process locale -- 29 files, so a file this library wrote is a file it can read on a host configured differently | | #3311 | a child process's stream is decoded with substitution, not strictly | | #3306 | a dataset metadata file that cannot be decoded is reported, not raised | | #3308 | a `robot_mesh` command body is decoded by one owner, so every refusal reaches the envelope | ### A store is written whole, or the previous one is still there (new lane -- the band's dominant fix shape) Five surfaces, one defect: a document was serialized straight over the file that held the previous one, so a write that failed part way left neither the old state nor the new. The store is not merely stale afterwards -- it is gone, and on two of these five the thing lost cannot be recomputed. | PR | What | |----|------| | #3322 | a user-registry write stores the whole overlay or leaves it untouched | | #3328 | a pose library is stored whole, and the tool reports that it was not rather than claiming it was | | #3330 | an Isaac camera recording is encoded whole, or kept to be encoded -- a failed encode no longer discards the frames | | #3345 | a session store is written whole, or the previous one is still there -- the store is the only place a detached process's pid is written down | | #3348 | a calibration is written whole, or the previous measurement is still there -- and a calibration is a measurement of a physical robot, so a lost one has to be taken again | ### A dataset root resolves to the directory its `repo_id` recorded to (new lane) The `repo_id` a caller records under and the directory a reader opens had drifted apart, so a dataset written under one name was read back from another. Four entry points, closed in one band, plus the recipe that documented the wrong read order. | PR | What | |----|------| | #3332 | a recorded dataset lands in the directory `create` prepared for it | | #3339 | an append reopens the dataset its `repo_id` created | | #3342 | a read reopens the directory the `repo_id` recorded to | | #3346 | streaming a dataset back reopens the directory the `repo_id` recorded to | | #3344 | the streaming recipe documented as chronological read frames the reader shuffles (docs) | ### A seeded replay reads the forwarded seed, not the process-global RNG (new lane) A test-quality lane, and a false *pass* in the direction that costs the most: a cell measuring `random`'s process-global stream is graded by whatever else in the suite advanced it, not by the seed under test. | PR | What | |----|------| | #3331 | a seeded eval's replay is measured off a private fork of the forwarded seed (closes #3329) | | #3334 | the seeded-replay policy reads the forwarded seed | | #3337 | two more seeded-replay cells read the forwarded seed (closes #3333) | | #3336 | a re-imported module is put back on its **parent package**, not only in `sys.modules` -- otherwise a later import of the same name gets a different module than the one the suite patched (answers #3335) | ### A selector that names nothing is refused, not widened (new lane) The caller narrowed the request and the surface widened it -- the one direction a selector must never fail in. Two surfaces found the same defect in one band. | PR | What | |----|------| | #3305 | a Reachy Mini motor selector that names no motor is refused, not widened to every motor | | #3309 | a `lerobot_camera` batch selector that names no camera is refused, not widened to the default cameras | ### A path stays inside the thing that contains it The #3214 prim-prune shape asked of a filesystem join rather than a USD path, plus the log sink beside it. | PR | What | |----|------| | #3296 | a checkpoint's norm-stats filename stays inside the checkpoint | | #3298 | a stored model path is contained wherever it is joined | | #3297 | the seed a WBC reset renders into its log line is escaped at the sink | | #3325 | a symlink inside a downloaded robot tree is never followed into the asset cache | ### An external call states the bound its caller is waiting on | PR | What | |----|------| | #3312 | a docker query the VERA readiness wait makes states a bound | | #3314 | the shared docker query bound is one a healthy daemon can meet -- a guard against the fix becoming its own failure | | #3317 | a preset scene download bounds every socket operation, not only the connect | ### A dialled address has two halves (new lane) `ws://<host>:<port>` is one value in two parts, and every transport that dials one validated the port end to end while taking the host as free text -- so the half that decides *where* a connection goes had no domain on any of them. | PR | What | |----|------| | #3272 | the host is the other half of a dialled address, and takes the port's own domain (the policy providers) | | #3271 | the same for the VERA server URI | | #3279 | the rosbridge host half is graded *before* a pattern is offered it | | #3287 | the host half of the Reachy Mini daemon address is graded beside its port | | #3299 | the EarthRover SDK base URL addresses the host the caller wrote | Read as the "one owner answers one question" shape asked of a URI rather than of a registry: one owner for what a legal address is, rather than one per component. ### A recording keeps the dataset it was about to replace (new lane) | PR | What | |----|------| | #3196 | a refused `start_recording` keeps the dataset it was replacing -- `overwrite=True` is the one posture that deletes a real `LeRobotDataset` without asking, and it is what the `run_policy` agent tool records with. Every other camera-shaped refusal was already ahead of that deletion; a single unknown name in `cameras=` was refused from inside the schema-declaration block, roughly a hundred lines after the wipe, so the call reported an error having already removed the dataset | | #3198 | a `reset()` closes the open dataset episode on **every** backend, not only the one that already did | | #3245 | the same ordering question one directory over: a fresh `lerobot_train` start clears an **empty** `output_dir`, not a directory that holds a checkpoint -- so a refused run no longer costs the caller the checkpoints it was replacing | | #3200 | every span `lerobot_camera` reports is measured on the clock that cannot step (the #2404 -> #2425 monotonic lane, one more surface) | | #3199 #3207 #3210 #3218 | four more shared value domains -- an RL evaluation's episode count, a served policy's advertised metadata, every RTDE read held to the axis count the command path enforces, and a WBC substep count held to the count the caller named | | #3189 #3193 | a served policy's `required_bodies` reach the host that supplies them (the wire seam #3187 did not reach); a link written as raw HTML is graded like a Markdown one (the whole-tree-grader lane, one more false pass) | ### A run that stopped early states how much of it never ran (new lane) The required suite runs under `-x`, so a red check names one failure of an unknown number -- #3164 measured a run that aborted at 74% of 46,583 items and hid a second real failure four lines below the first. Three PRs answered it, and the third exists because the first two each derived the same number independently. | PR | What | |----|------| | #3167 | a session that stops early states how much of the suite never ran -- computed in process from the session's own item list | | #3166 | a red required check says how much of the suite it never reached -- parsed from the log into the job summary and an annotation (closes #3164) | | #3172 | how much of the suite ran is counted once, by the session that ran it -- one owner for the subtraction, text arithmetic as the fallback | | #3149 | **`perf`, the wave's only one**: collect coverage on the PEP 669 core rather than the C trace function -- +8.2% against the default's +76.6%, same reading per file, statement and exclusion | **#3169 is the process record of how the pair happened**, and it is the rare tracker item that files a negative result: widening the duplicate-claim key to simultaneous cross-references selects about 27 pairs across 9 issues for 2 genuine ones and fires on a third of the queue, so it is boilerplate rather than a gate. The documented spelling for a *deliberate* second implementation (`Refs #N`) is exactly the spelling that hides an unintended one. ### A measurement that could not be taken is reported as no reading | PR | What | |----|------| | #3128 | an unreadable episode count is reported as no reading, not as zero (closes #3125) | | #3109 | an evaluation that lost a recording episode does not report a success rate over it | ### A Reachy Mini verb surface | PR | What | |----|------| | #3082 | **14 Reachy Mini verbs in the consolidated shape** -- the arrangement #3037/#3070 arrived at, adopted on arrival | | #3094 -> #3097 | filed against it the same morning (the head-body yaw coupling limit was enforced only where one action carried both values, and two of the new verbs send one) and closed by #3097 | | #3095 | a verb states the coupling limit only where its own action can reach it | ### Every DDS endpoint is constructed under the shared lock (new lane) #3062 was filed against the Go2 driver the day it landed: a sport-mode gate built an RPC client outside `_DDS_INIT_LOCK`, racing one driver's own subscriber set into a segfault. The fix and its graders closed the whole class rather than the one site. | PR | What | |----|------| | #3064 | open the motion-switcher client under the shared DDS lock (closes #3062) | | #3067 | construct the Booster T1's DDS endpoints under the shared lock | | #3071 | derive the DDS-lock vocabulary from every lock-holder rather than a hand-written list | | #3072 | grade every endpoint construction against the shared lock | | #3073 | grade the DDS endpoint lifetime on both bring-up and teardown | ### The dashboard: from a decomposition argument to the wave's feature lane #2977 asked for the 481-file draft #2848 to be decomposed before another review round. Two slices merged, **the draft itself was closed 2026-09-02T02:24Z**, and the extracted package then became the place the wave spends its features. `strands_robots/dashboard/` now carries **ten modules** on `main` -- `agent_hitl`, `agent_motion`, `auth`, `log_redaction`, `safety_state`, `settings`, `task_timeout`, `ttl_cache`, `validate_scope` and the package `__init__` -- and the newest band's three features are all here. | PR | What | |----|------| | #3050 | **operator auth rail**, with the extra that supplies it | | #3051 | **settings, log redaction, task timeouts and a bounded cache** | | #3078 | a first enrollment is limited to the machine, not just the damaged-store case | | #3080 | a passkey ceremony is verified against the connection, not the caller's claim | | #3184 | **a HITL gate pauses an agent tool call that would move real hardware** (+1,058 / -31 across 13 files) -- and raises the `strands-agents` floor to `>=1.13.0` for the interrupt / `cancel_tool` hook members it calls | | #3185 | **a `Lockout` value type states what a dashboard may say about an e-stop** | | #3186 | **a policy preflight reports what it actually verified, not just `"ok"`** | | #3579 | a renewed session keeps its provenance marker | | #3585 | a lockout verdict is ordered on this process's own clock | | #3586 | a settings section given a scalar is reported, not dropped in silence | | #3594 | a WebAuthn expectation needs a `Host` header that actually arrived | **#3079** (`expected_origin` taken from the request's own `Origin` header, so the WebAuthn origin check cannot fail) **closed 2026-09-02T00:21Z with no closing PR**, exactly as the previous sync predicted once #3080 landed that remedy. #3115 then moved the challenge TTL onto a clock a correction cannot move, and **the 481-file draft #2848 was closed 2026-09-02T02:24Z** -- one minute after #3122 was filed naming what it still carried. Every module this section extracted has now taken at least one correctness pass -- #3131 made the log filter redact every part a formatter renders, #3141 held the auth store's cache to the bytes it was parsed from, and #3163 stopped a setting that is not a boolean opening the remote-code gate. **#3122 is still open**, and still names what the draft carried: both enrollment routes skipping the auth middleware, and two directory reads validating existence rather than containment. ### An EarthRover verb surface | PR | What | |----|------| | #3081 | **native EarthRover Mini+ driver** over the earth-rovers-sdk HTTP API | | #3084 | **six rover verbs in one table-driven module** (**+562 / -1**) -- the consolidated shape #3037/#3070 arrived at, adopted from the start rather than retrofitted | ### The conventions that catch a dead cross-reference #2940 recorded the concrete failure: two consecutive g1 verb ports shipped dead cross-reference roles behind a green local run, because the whole-tree graders are exactly the ones a diff-scoped selector cannot see. It closed against #3087. | PR | What | |----|------| | #3090 | report a docstring role naming a module the other side of a merge deletes | | #3087 | a qualified role must not name a module that lives in an open PR (closes #2940) | | #3083 | convention 7's rule is recoverability, and `name=` is one of three shapes | | #3076 | a test module name must state the behaviour, not the tracker item | | #3077 | an absent optional dependency names the module in `ImportError.name` | ### Mesh and Feetech correctness (the #3037 -> #3091 band) | PR | What | |----|------| | #3066 | **bound each teleop joint in the unit its follower declares** -- closes #2935, where a degree shoulder and a percent gripper shared one scalar bound | | #3086 | the teleop slew bound is sized on recorded teleop, and full scale does not size a speed | | #3085 | validate the command envelope's routing fields, which are key-expression segments | | #3074 | the two-byte word order is an STS/SMS property, so say so and decide it once | | #3063 | refuse a misspelled backend option instead of dropping it (closes #2895) | ### `doctor` reports a build that cannot run on this host (new lane) Each of these previously surfaced as a failure somewhere else -- a CUDA error inside a rollout, a GL error inside a render -- rather than as a diagnosis naming the build. | PR | What | |----|------| | #2893 | report a Warp build that cannot target this GPU's architecture | | #2908 | report a torch build that carries no code for this GPU | | #2911 | the `MUJOCO_GL` remedy names a backend this host can load | | #2896 | release the sim smoke test's renderer instead of leaving it to the finalizer | ### Microduck, the band after it landed The 14-DOF Pollen biped went end to end in #2856/#2862 last band. This one is its hardening pass, plus two real additions. | PR | What | |----|------| | #2906 | **episodic behaviors in `MicroduckPolicyBundle` auto-return on their own timer** | | #2909 | **route observation slot two through the ONNX `gravity_source` flag** (with #2913, `raw_accel` slot two is a gravity direction, not the reading) | | #2882 #2883 #2887 #2890 #2898 #2905 #2919 | refuse what cannot be honored -- an unchecked graph action, a scale or threshold that would silently change the command, an unreadable floating-base block, a non-finite observation component, a gate key naming no held skill, an override the method cannot honor, a goal the velocity gate cannot read | | #2902 #2903 | the velocity gate arbitrates between its pair instead of selecting into it; the command width follows the graph, not `command_names` | | #2884 #2920 | hold the transport knobs to the shared numeric domains; grade the degraded driver surface, not just the happy path | | #2900 #2926 #2927 #2922 | name the scene each shipped skill needs, the ball scene's actual contents, the stance every weight was trained in; `--scene` reaches the scene a skill was trained in | ### Environment variables are named where an operator looks (new lane) A page can document the extra that installs a feature and the credential a link carries, and still not name the variable that switches it on. Last band opened this shape on `STRANDS_MESH_BACKEND`; this band finished the mesh matrix. | PR | What | |----|------| | #2985 #2990 #3003 | `STRANDS_MESH_NAMESPACE`, `STRANDS_MESH_POLICY_TYPE_ALLOW` and `STRANDS_MESH_ACCEPT_PERMISSIVE_ACL` are named on the README matrix and the security page | | #2979 #2945 | name the audit-log family's tamper-evidence and rotation knobs; name the three TLS material paths the default posture requires | | #2952 #3038 | the pages describing `[all]` name the extras it leaves opt-in; the `driver="strands"` refusal example names a robot that has one | ### Other named surfaces | PR | What | |----|------| | #2095 | advertise `set_obs_noise` so an agent can configure sensor noise | | #2116 | apply the pose rule once in `add_camera`, not twice | | #2023 | delete the inert VERA `n_action_steps` knob | | #2258 | state the reason, not the review thread, in 43 docstring markers | | #2055 #2057 | document parameters with an enforced domain but no `Args:` entry; root the completeness guard at the package | | #2190 #2210 | mesh discovery prose + camera-offload docstring stop calling multicast the default | | #2844 #2840 | fill the undriven state columns of a synchronized rollout too; say that the MuJoCo grow path carries the joint-force buffer it carries | The remaining ~220 PRs are the sustained fix/test-pin sweep -- now including a `validate()` preflight trio that refuses a `val_episodes` whose episode count cannot be read (#2346), a dataset format version lerobot cannot read (#2351), and streaming alongside a validation split (#2352) rather than dropping one silently; and a policy/registry resolution run (GR00T `data_config` -> registry robot #2350, a provider's declared host default surviving `build_policy_kwargs` #2356, a non-lowercase policy URL scheme #2374, `processor_overrides` routed to the pipeline declaring the step #2375, an exclusive `CompositePolicy` joint group #2385) -- -- dominated by the sim facade refusing values it cannot honor (`add_object` mass/color/size vectors, `control_substeps`, rollout `duration` / `policy_config`, `create_world` timestep/gravity, plain-MP4 frame and pixel counts, MJPEG stream options, dataset-recording frame rates), MuJoCo version-drift resilience (the sparse-inertia mass matrix, camera intrinsics, the API floor), and matching contract pins. All of it lands in the same open `[Unreleased]` section. ## v0.4.1 -- shipped 2026-07-01 (published wave) Everything below merged to `main` after the v0.4.0 tag and shipped in **v0.4.1** (now under the dated `## [0.4.1]` heading in the CHANGELOG after #977). Grouped by theme. ### Security -- hardening the LLM-callable filesystem + dependency surface | PR | What | |----|------| | #929 | sandbox + harden `render(output_path=...)` against `..` traversal, backslash separators, shell metacharacters, symlinked targets, oversize, and partial-write corruption (`STRANDS_ROBOTS_RENDER_ROOT`, atomic write) | | #930 | extend the same output-path hardening to the sibling video sinks `run_policy(video={"path": ...})` and `start_cameras_recording` via a shared `simulation.safe_output` helper (`STRANDS_ROBOTS_VIDEO_ROOT`) | | #931 | remove the unregistered `mimicgen` dependency (dependency-confusion RCE, CVE-pending) | | #950 | remove the git-only `vera` extra that breaks PyPI upload (release-enablement) | ### ROS 2 / RTPS interop (new capability) | PR | What | |----|------| | #742 | `RosBridgedRobot` -- present a ROS 2 robot as a first-class strands `Robot` | | #744 | `use_rtps` + `RtpsRobot` -- act as a ROS 2 robot over pure RTPS (no rclpy) | | #747 | publish **simulation** telemetry on a ROS 2 domain (`ros2_bridge` flag) | | #755 | publish a **real** robot on a ROS 2 domain (`ros2_bridge` flag) | | #748 #750 | `use_ros` showcase + README/docs ROS 2 overview; hero-example-first README | | #767 | unify rclpy + RTPS hardware bridges under `RosTelemetryBase` (`HardwareRtpsBridge`) -- byte-identical topics by construction | | #875 | DDS Security gate + joint-limit bounds on the hardware bridges | ### Training -- SDK now trains AND scores end to end (incl. from-scratch RL) | PR | What | |----|------| | #667 | stream a Hub dataset into the LeRobot trainer (no full download) | | #734 | wire RA-BC sample weighting into the lerobot trainer | | #735 | relative-action training flag | | #736 | DAgger correction-collection action via lerobot-rollout | | #749 | SARM reward-model training + the RA-BC production loop (closes the produce half) | | #777 | dynamic `RewardModelConfig` parity with lerobot -- reaches every registered reward type (SARM-only 1/4 -> 4/4) | | #656 | `create_trainer` auto-discovery fallback | | #877 | **from-scratch RL trainers with a PPO backend** (on-policy) | | #886 | **off-policy FastSAC RL trainer** | | #915 | RL parity remainder -- `staged_reward`, gym adapter, **vectorized PPO**, render output_path + precise env union | | #918 | **`VecSimEnv`** -- N independent `SimEnv` presented as one `(N, D)`-batched env (parallel trajectory collection on the CPU/MuJoCo backend) | | #920 | **`BaseRLAlgo.evaluate()`** -- deterministic eval peer of `train()` (mean action, frozen normalizer, genuine-terminal `success_rate`) | ### New policy providers | PR | What | |----|------| | #663 | **VERA** video-to-action policy provider (closes #78) | | #466 (closed) | **WBCPolicy** (`wbc`/`sonic`) -- GR00T Whole-Body-Control (SONIC) for G1 | ### Humanoid-locomotion policies (v0.5.0-scoped, now landing on `main`) | PR | What | |----|------| | #847 | WBC: drop the wrong-family default checkpoint; actionable error + SONIC-stack detection | | #848 | auto-install WBC torque control on the `run_policy` default path | | #852 | `WBCGaitPolicy` gait-clock variant -- 95-dim obs + bipedal phase clock | | #854 | accept canonical GR00T-WholeBodyControl ONNX filenames without rename | | #856 | **MotionBricks** generative-motion provider for the Unitree G1 | | #876 | MotionBricks consumes `locomotion_style` to steer the gait | | #849 | **CompositePolicy** -- stack locomotion + manipulation on one robot | ### Policy execution correctness (RTC / chunking / eval) | PR | What | |----|------| | #703 | `async_rtc` chunk pipeline overlaps inference with execution in `run_policy` | | #707 | honor policy chunk length in the synchronized multi-robot loop (shared `ChunkedPolicy`) | | #715 | `execution_horizon` contract so RTC actually re-queries mid-chunk | | #714 #725 | RTC inference-delay uses real control frequency; made deterministic via caller step count | | #731 | honor ACT temporal ensembling instead of silently dropping it | | #728 | legacy `success_fn` path no longer drops the action chunk / checks success on stale obs | | #772 | auto-enable `async_rtc` for chunk-emitting policies + harden the seam | | #776 | honour the RTC contract in the **real-robot** (hardware) control loop | | #779 | re-anchor the RTC chunk-seam prefix for relative-action policies (was blended in a stale frame) | | #780 | forward `inference_delay` to lerobot's RTC denoiser (fixes a 2nd-chunk `TypeError`) | | #784 | pin the chunk re-query interval floor (`>= 1`) invariant | | #788 | RTC inference telemetry + opt-in async-RTC overlap in `eval_policy` | | #790 | detect image features by `FeatureType.VISUAL`, not a name substring | | #794 | policy resolution no longer shadows `lerobot.policies` with a partial stub | | #833 | fire `on_frame` on the `success_fn` eval path + expose it on `eval_policy` | | #857 | forward `policy_kwargs` through `eval_policy` / `evaluate_benchmark` | | #905 | `eval_policy` resolves `robot_name` like `run_policy` (auto-select the sole robot) | ### lerobot_local hardening (MolmoAct2 / SO-arm) | PR | What | |----|------| | #693 #737 | SO-arm degrees<->radians + gripper `RANGE_0_100`; mid-center conversion like `motors_bus` | | #753 | accept HWC/uint8 camera frames (canonicalize layout before preprocess) | | #704 | surface silent zero-fill / near-zero actions that freeze the arm | | #686 | cache loaded model across policy instances | | #773 | surface lerobot_local model-load telemetry end-to-end | | #653 #654 #680 | MolmoAct2 transformers pin + report all missing aux deps in one error | | #818 | honor `camera_key_map` on the preprocessor remap path | | #827 | fail fast on camera / `obs_rename` mismatch before the model download | | #834 | catch `ValueError` when resolving unknown LeRobot policy types (clean `ImportError`) | | #850 | pin HF Hub revision for lerobot_local checkpoints | | #883 | reconcile a device-pinned processor step onto the host device | | #892 | canonicalize bare camera frames on the declarative embodiment path | | #894 | drive a single-camera SO-101 ACT on the declarative path | | #895 | surface routing-degradation telemetry (`positional_fallback_used` / `generic_state_keys_used`) in `run_policy`/`eval_policy` | | #897 | make state-key mismatch loud instead of a silent open-loop rollout | | #898 | surface non-ImportError module failures as a clean `ImportError` in `resolve_policy_class_by_name` | | #900 | bind bare declared image keys by name on the VLA observation path | | #917 | batch declarative obs when the pipeline has no `AddBatchDimension` step | | #935 | empty instruction no longer crashes the language-VLA preprocessor with a cryptic `KeyError` | ### Performance | PR | What | |----|------| | #782 | `PersistentPolicy` + cache controls + `policy_resident_rss_mb` telemetry -- synchronous persistent worker eliminates per-episode model-reload in multi-rollout loops | ### Recording / dataset integrity | PR | What | |----|------| | #666 | `save_episode()` for explicit episode boundaries | | #691 | `reset()` during recording flushes the buffered rollout as an episode | | #716 | per-episode `recorder.save_episode` wired in `policy_runner` (#708) | | #681 | refuse to push an empty `LeRobotDataset` to the Hub | | #687 | surface + remap camera-key mismatch instead of silently dropping frames | | #684 | replay reads actions from the column store, never decodes discarded video | | #770 | episode-count contract for `run_policy` + `verify_dataset_episodes` | | #795 | `verify_dataset_episodes` cross-checks `meta/info.json` against the parquet | | #796 | `strands-robots verify-dataset` CLI for episode-count integrity | | #797 #805 | scope recorded cameras via `start_recording(cameras=...)` / `run_policy(dataset_cameras=...)` | | #798 | record a per-step observation across action chunks (no stale-obs frames) | | #800 | verify per-episode video files in `verify_dataset` | | #807 | don't negatively cache lerobot-availability probes (recording recovers when lerobot resolves) | | #813 | accept an existing empty root dir in `start_recording` | | #888 | default to OpenCV-decodable H.264; route `vcodec` onto the LeRobot 0.5.2 encoder config | | #906 | `verify-dataset` flags dead (all-zero) action/state columns | | #924 | warn when `start_recording` sweeps the implicit `default` camera into a dataset | ### GPU-render reliability (EGL) | PR | What | |----|------| | #909 | warn when MuJoCo EGL silently falls back to a CPU software rasterizer (was silent) | | #921 | auto-register the NVIDIA EGL vendor ICD so `MUJOCO_GL=egl` renders on the GPU | ### Simulation facade -- discovery, ergonomics + input validation | PR | What | |----|------| | #692 | first-class multi-episode `run_policy(n_episodes=N)` | | #665 | agent-consumable JSON block returned from `run_policy` | | #720 | `run_policy` `@tool` wrapper (closes #708 fabrication vector) | | #837 | forward video config through the `run_policy` tool | | #655 | `list_bodies` to discover camera mount points | | #758 | zero-config robot discovery from `robot_descriptions` (long-tail Menagerie robots) | | #761 | surface joint velocity + base IMU in observation; WBC-driven recording | | #764 | roll back the spec mutation when an object/camera injection recompile fails | | #771 | surface `[Benchmark]` actions in the tool_spec description + enum/description parity | | #809 | `render()` honors per-camera configured resolution | | #820 | return a viewable depth PNG from `render_depth` | | #891 | metric depth passthrough + capture the fd-2 depth warning + silence `export_xml` attach noise | | #823 | validate `add_camera` fov/width/height eagerly | | #829 | accept ordered action vectors in `send_action` | | #831 | validate `replay_episode` robot_name + reject a negative episode index | | #839 | reject non-positive `action_horizon` in `run_policy`/`eval_policy`/`start_policy` | | #844 | reject non-positive episode/step counts across `eval_policy` + `evaluate_benchmark` | | #845 | complete `control_frequency` validation across all entry points/paths | | #855 | source the MuJoCo "no world" guard message from one shared constant | | #879 | validate `replay()` speed is positive (no `ZeroDivisionError` / silent full-speed) | | #896 | fail-fast on 100% unresolved action keys + per-actuator resolution stats | | #903 | resolve vertical (top-down/up) camera look-at orientation | | #881 | route `Robot(backend=...)` through `create_simulation` for unified backend selection | | #841 #885 | `list_policy_types()` discovery surface for lerobot_local + re-export as a peer of `list_providers` | | #812 #815 #825 | list `render_depth`/`render_all`, `eval_policy`/`replay_episode`, and scene-construction methods in the `describe()` discovery surface | | #902 | key policies by **actuators**, not joints, so tendon-gripper / passive-joint robots actually move | | #926 | fail-fast on an action-vector length mismatch in `send_action` | | #927 | `randomize()` recolors unnamed + material-backed geoms and reports the true recolored count | | #933 | scale `body_inertia` with `body_mass` in MuJoCo `randomize_physics` (mass-only scaling left bodies physically inconsistent) | | #936 | correct the `MuJoCoSimEngine.__init__` `**kwargs` docstring to match runtime behavior | | #939 | `add_object` rejects unknown kwargs instead of silently dropping them | | #940 | `mj_forward` after `mj_resetData` so `reset()` leaves render-ready state | | #945 | `mj_forward` after `randomize()` so light-position jitter is not a silent render no-op | ### Newton GPU backend (post-tag maturation of #470, toward MuJoCo parity) | PR | What | |----|------| | #769 | honour configured gravity in the Newton backend; add `set_gravity` / `set_timestep` | | #774 | Newton scene-discovery + per-joint state parity with the MuJoCo backend | | #785 | wire `DatasetRecorder` into the Newton backend (recording parity) | | #786 | domain randomization + sensor-noise hooks | | #789 | named per-robot cameras (MuJoCo parity) | | #791 | live interactive viewer | | #792 | mesh-object support in `add_object` (MuJoCo parity) | | #793 | load `robot_descriptions` URDF robots | | #802 | `get_observation` renders cameras while recording (parity with MuJoCo) | ### Scenes / registry / assets | PR | What | |----|------| | #901 | `add_object` material/texture/reflectance for MuJoCo scenes | | #904 | make **LeKiwi** simulatable (`Robot("lekiwi", mode="sim")`) via a GitHub asset `source` | | #781 | re-export all `@tool`s at the package root | | #783 | re-export `list_providers` + `register_policy` at the package root | | #775 | conform registry hardware entries to the full LeRobot catalog | | #799 | add reBot B601-DM single + bimanual hardware entries | ### Humanoid / G1 + hygiene | PR | What | |----|------| | #662 | end-to-end VLA-on-G1 workflow (closes #471) | | #766 | embed the G1 walk rollout visual in the WBC policy docs | | #664 | harden `device_connect` + tools against auth bypass and path traversal | | #673 #730 #733 | strip emoji from runtime strings + widen no-emoji guard; ASCII-hyphen sweep | | #824 | reject numpy booleans in teleop input frames (mesh) | ### Added-then-removed in this window (net: neither ships) | PR | What | |----|------| | #853 -> #889 | `planning` package + `run_policy(planner=)` added (#853), then **removed** (#889) -- locomotion intent is expressed only through `policy_kwargs`; MotionBricks keeps a plain-string `locomotion_style` contract (no planner import) | | #878 -> #890 | `sim_managers` config-driven RL-env framework added (#878), then **removed** (#890) as off-mission with no consumers | Plus a large **test-coverage hardening sweep** (#787 #801 #803 #804 #806 #808 #810 #811 #814 #816 #817 #819 #821 #826 #828 #830 #832 #835 #838 #840 #842 #846 #851 #882 #893 #907 #908 #910 #911 #912 #913 #914 #916 #919 #922 #923 #925 #928 #932 #934 #937 #938 #941 #942 #943 #948 #951, with #822 fixing the molmoact2 sim-pickplace example embodiment) across policy_runner/RTC, verify-dataset, mesh ACL + safety-resume + audit-log + wire-validation + e-stop + Zenoh transport, VERA view-key/handshake, MotionBricks + CompositePolicy, Newton domain-randomization, gr00t dispatch, discovery, libero adapter, the EGL vendor-ICD scan, and the from-source lerobot conformance invariant. Examples/docs also grew a CPU-only getting-started notebook series (#843), a `use_lerobot` discovery example (#874), the `add_object` size-is-full-extent docs (#899), and a #944 CHANGELOG backfill of 12 missing wave entries. Release-enabled by #947 (`--cov-fail-under=80`) + #950 (PyPI vera-extra fix). ### v0.4.1 release status - [x] **Bump version + tag `v0.4.1` + GitHub release** -- published 2026-07-01 (tag at #947). - [x] **Collapse `[Unreleased]` into a dated `## [0.4.1]` heading** -- landed via #977. - [x] **Open a fresh `[Unreleased]` for the post-v0.4.1 wave** -- landed via #977. ## Post-v0.4.1 -- shipped as v0.5.0 (2026-08-04) / v0.5.1 (2026-08-06); no v0.4.2 was ever cut Merged to `main` after the v0.4.1 tag (#946, #949, #952 -> #1530; **+516 PRs**). Mostly consolidation -- the CHANGELOG heading collapse, a breaking-change CI gate, a sustained run of sim-facade correctness fixes, new sim capabilities, a lerobot 0.6 dependency bump, a remote policy inference client/server split (plus a lerobot gRPC async provider), a floating-base state lane, the absorbed Isaac Sim backend, the first locomotion reward-term DSL primitives now composed into a runnable `go2_walk_forward` benchmark, a terrain curriculum for locomotion (`create_world` heightfield kinds + a `difficulty` knob), a broad `describe()` discovery-surface expansion, ROS 2 action support, training fixes, lerobot_local normalization/embodiment-integrity fixes, example/packaging fixes, and a broad contract-pinning test sweep. Grouped by theme. ### CHANGELOG housekeeping | PR | What | |----|------| | #977 | collapse `[Unreleased]` into a dated `## [0.4.1]` heading + open a fresh `[Unreleased]` for the post-tag wave | | #1188 | normalize two off-convention `[Unreleased]` CHANGELOG entries | ### Public-API breaking-change CI gate | PR | What | |----|------| | #946 | informational breaking-change check for the public API | | #949 | detect breaking changes to the AgentTool action contract | | #963 | make the breaking-change check fork-safe | ### Dependencies | PR | What | |----|------| | #1114 | track lerobot 0.6 -- require `lerobot>=0.6.0` and drop the 0.5.1-era torch/torchcodec overrides | ### Isaac Sim backend (absorbed from `robots-sim`, epic #1144) | PR | What | |----|------| | #1156 | absorb the Isaac Sim backend from `robots-sim` into this repo (closes the #1144 epic) | | #1151 | builtin backend wins over a stale entry-point plugin of the same name (closes #1150) | | #1155 | add the `[isaac]` optional-dependency extra (closes #1147) | | #1154 | port the Isaac Sim GPU integration tests into `tests_integ/` (closes #1148) | | #1153 | document the Isaac Sim backend (closes #1149) | | #1205 | correct `create_simulation()` isaac-backend examples to the builtin backend (closes #1146) | | #1354 | Isaac `create_world` accepts `terrain`/`difficulty` and rejects `terrain` with an actionable error (no heightfield support; points at `backend="mujoco"`) | | #1357 | Isaac `add_object` accepts + loudly rejects the base `mesh_path`/`material` contract (was silently swallowing them via `**kwargs`) | | #1360 | Isaac `add_robot` accepts + loudly rejects the base `keyframe` contract (was a bare `TypeError`) | | #1372 | Newton / Isaac `create_world(difficulty=...)` rejects a non-default `difficulty` with no `terrain` (was silently ignored; points at `backend="mujoco"`) | ### Reward / predicate DSL + terrain curriculum -- locomotion groundwork now runnable (toward #873) | PR | What | |----|------| | #1198 | `base_velocity` velocity-tracking reward term for the predicate/reward DSL | | #1203 | `base_height` locomotion-regularizer reward term | | #1208 | `base_orientation` flat-orientation reward term | | #1215 | `base_lin_vel_z` / `base_ang_vel_xy` locomotion-regularizer reward terms | | #1239 | `base_velocity_tracking` exponential-kernel velocity-tracking reward term | | #1230 | `base_tipped` fall-over termination predicate (failure-clause counterpart of `base_orientation`) | | #1233 | `base_below_z` height-collapse termination predicate | | #1255 | `base_beyond_x` forward-progress success predicate | | #1259 | **`register_builtin_benchmarks()` + the canonical `go2_walk_forward` locomotion benchmark** -- first runnable benchmark composing the DSL primitives (Unitree Go2 velocity-tracking; discoverable via `list_benchmarks()`, runnable via `evaluate_benchmark()`) | | #1287 | **`g1_walk_forward`** humanoid locomotion benchmark (Unitree G1) -- the #873 G1 embodiment | | #1288 | **`t1_walk_forward`** humanoid locomotion benchmark (Booster T1) -- the #873 T1 embodiment | | #1321 | **`base_beyond_y`** lateral-progress success predicate + **`go2_strafe_left`** benchmark (vy command axis) | | #1324 | **`base_yaw_beyond`** turn-progress success predicate + **`go2_turn_left`** benchmark (wz command axis) -- completes the Go2 vx/vy/wz velocity-tracking vocabulary | | #1223 | reject a reward term used where a bool predicate is required in the benchmark DSL | | #1236 | registry name overrides the spec-internal name in `register_benchmark_from_file` | | #1336 | **`create_world(terrain="rough")`** -- rough-ground heightfield for locomotion (first terrain kind; #873 terrain-curriculum groundwork) | | #1338 | `create_world(terrain="stairs")` -- stepped-ground heightfield for locomotion | | #1339 | `create_world(terrain="pyramid")` -- concentric stepped pyramid for omnidirectional locomotion | | #1340 | `create_world(terrain="slope")` -- inclined-ramp heightfield for locomotion | | #1344 | **`create_world(terrain=..., difficulty=...)`** -- terrain elevation-curriculum knob (scales the heightfield peak so a curriculum ramps terrain magnitude across resets) | | #1350 | advertise `create_world(difficulty=...)` in the sim tool_spec + keep the terrain-kind hint in sync with `SUPPORTED_TERRAINS` | | #1346 | declarative benchmark spec accepts a natural-language `instruction` | | #1364 | `base_below_z` measures clearance above the **local terrain** surface, not absolute `z` (terrain-relative fall predicate) | | #1368 | `base_height` reward measures clearance above the **local terrain** surface, not absolute `z` | | #1386 | a floating-base robot spawns **seated on the terrain surface** (feet on the heightfield) at spawn/reset instead of buried below it | | #1388 | **`get_ground_height(x, y)`** -- public query for the local terrain surface height (facade + agent-dispatch action; place objects/cameras/goals on terrain correctly) | ### Remote policy inference (new capability) | PR | What | |----|------| | #1142 | remote policy inference client/server split (`strands_robots.inference`) -- `PolicyServer` wraps any `Policy`; `RemotePolicy` is a drop-in `Policy` forwarding observations over a WS-JSON protocol; wired as the `remote` provider (`create_policy("ws://gpu-box:8765")`), RTC contract preserved end to end | | #1152 | decode observations into writable arrays (remote path parity -- in-place VLA preprocessors + `torch.from_numpy` no longer hit read-only arrays) | | #1158 | lift the server WebSocket frame limit so camera-image observations are not rejected | | #1190 | warn on a mistyped `RemotePolicy` endpoint instead of silently defaulting to localhost | | #1212 | **lerobot gRPC async** inference provider -- remote inference over lerobot's async gRPC transport (a real provider, distinct from the WS-JSON `RemotePolicy`) | | #1250 | `PolicyServer` publishes its port before marking the server bound (no race reading the port on startup) | | #1398 | `lerobot_async` forwards a `rename_map` to the remote `PolicyServer` for observation-key remapping | | #1458 | `lerobot_async` sources its server-supported policy set live from lerobot instead of a hardcoded copy | ### Simulation facade -- correctness, discovery + backend parity | PR | What | |----|------| | #955 | `move_object` repositions static objects instead of silently no-op'ing (#966 pins the failure contract) | | #962 | bind `replay` / `send_action` action vectors to **actuators**, not joints | | #968 | flag `eval_policy` `success_rate` as unmeasured when no success criterion is given | | #970 | accept `cameras=` scope in Newton `start_recording` (`dataset_cameras` parity with MuJoCo) | | #981 | `render_depth` honors a camera's configured resolution | | #991 | enforce the Strands tool-result contract (move top-level extras into the `content` JSON) | | #992 | `set_obs_noise` on the MuJoCo backend (sensor-noise parity with Newton) | | #997 | `list_cameras()` on the MuJoCo backend (camera discovery at backend parity) | | #1002 | `inverse_dynamics` solves for zero desired acceleration -- gravity/bias compensation (was ~0 torques regardless of pose, stale `qacc`) | | #1003 | register mesh assets on a runtime `add_object(shape="mesh")` injection | | #1006 | `get_jacobian` / `get_body_state` reflect the current `qpos`/`qvel` (were computed against a stale configuration) | | #1012 | `replay` steps a full control period per recorded frame (was one physics dt -> under-integrated playback) | | #1015 | `set_body_properties` scales inertia with mass (mass-only left bodies physically inconsistent) | | #1032 | recompute `geom_rbound` / `geom_aabb` when `set_geom_properties` resizes a geom (stale broadphase bounds) | | #1038 | `raycast` / `multi_raycast` refresh geom poses and lock before `mj_ray` (was reading a stale configuration) | | #1044 | `eval_policy(video=)` records one rollout MP4 per episode | | #1050 | `get_energy` recomputes the forward pipeline before reading (was the energy of a stale pose after a direct `qpos`/`qvel` write) | | #1051 | `evaluate_benchmark` honors `policy_object` + `control_frequency`/`control_substeps` (parity with `run_policy`/`eval_policy`) | | #1056 | drop the `_set_eval_seed` back-compat alias for the public `set_eval_seed` | | #1059 | cap rollout-video fps at `control_frequency` for real-time playback | | #1062 | `describe()` advertises the physics-introspection / grounding surface (`get_body_state`, `get_contacts`, sensors, ...) | | #1065 | `grasped` predicate matches the LIBERO `<body>_g<idx>` geom-naming convention | | #1069 | honor `CooperativeStop` from an `on_frame` hook in `eval_policy` / `evaluate_benchmark` | | #1076 | `evaluate_benchmark` records one rollout MP4 per episode (`video=`) | | #1078 | `verify-dataset` flags truncated / partial-encode videos (a present, non-empty MP4 with fewer frames than recorded) | | #1083 | `move_object` places a dynamic object at rest at the new pose (drops retained velocity) | | #1090 | surface unresolvable body/joint names in the predicate & reward DSL (was a silent no-op returning a constant) | | #1100 | warn when an action value is clamped by a ctrl-limited actuator | | #1112 | `add_robot(keyframe=...)` spawns a robot in its canonical home pose | | #1134 | surface base state for a mobile base whose free joint is unnamed (was silently dropped for LeKiwi-style unnamed free joints) | | #1137 | `get_robot_state` surfaces a floating base as a structured base entry (not a flattened joint row) | | #1139 | Newton joint index maps no longer shift every joint after a floating base | | #1159 | make `NewtonSimEngine` teardown safe when `__init__` fails part-way | | #1164 | Newton `get_observation`/`get_robot_state` surface a floating base as a structured pose (MuJoCo parity) | | #1169 | warn when a floating base's orientation + angular velocity are dropped from a recorded dataset | | #1172 | preserve a floating base's orientation + angular velocity in recorded datasets | | #1176 | surface a floating base's full position + linear velocity in the observation | | #1179 | reject a non-scalar `send_action` dict value instead of crashing | | #1183 | preserve a floating base's state in Newton-recorded datasets | | #1187 | report a floating base's angular velocity in the body frame on Newton | | #1189 | reject robot-setup kwargs in backend constructors instead of silently dropping them | | #1191 | surface cached-XML refresh failures in scene mutations instead of swallowing them | | #1252 | warn on an out-of-ctrlrange clamp for joint-name-addressed actions (sibling of #1100) | | #1249 | `describe()` advertises `load_scene` + the object-manipulation siblings | | #1254 | advertise `get_features` in `describe()` (the method `run_policy` recommends) | | #1256 | `describe()` advertises the benchmark scoring family (`evaluate_benchmark` / `list_benchmarks` / `register_benchmark_from_file`) | | #1257 | `describe()` advertises the sim-state checkpoint + pose-setting family | | #1261 | `describe()` advertises the background-policy stop/list lifecycle | | #1262 | `describe()` advertises the physics-tuning / domain-perturbation family (`set_gravity` / `set_timestep` / `set_body_properties` / `set_geom_properties` / `apply_force`) | | #1264 | `describe()` advertises the plain-MP4 camera-recording family (`start_cameras_recording` / `stop_cameras_recording` / `get_cameras_recording_status`) | | #1266 | `describe()` advertises the robot-registry + `remove_robot` surface (`list_urdfs` / `register_urdf` / `remove_robot`) | | #1267 | `describe()` advertises the world-lifecycle + MJCF-editing family (`create_world` / `destroy` / `patch_scene_mjcf` / `replace_scene_mjcf` / `export_xml`) | | #1277 | `describe()` advertises the teleoperation family (`attach_teleop` / `teleoperate` / `stop_teleoperate` / ...) | | #1281 | `describe()` advertises the interactive-viewer family (`open_viewer` / `close_viewer`) | | #1286 | `describe()` advertises the multi-robot rollout family | | #1291 | `get_contact_forces` reflects the current pose (`mj_forward` before reading) | | #1298 | base `describe()` advertises `remove_robot` + the scene-variation facades | | #1299 | `add_robot` reports an actionable error for an unknown model (close-match + `list_urdfs`) | | #1303 | actionable `not found` errors for `move_object` / `remove_object` / `remove_camera` | | #1306 | actionable `Robot 'X' not found` across the facade (close-match + `list_robots`) | | #1308 | actionable `robot not found` on `run_policy` / `eval_policy` / `evaluate_benchmark` (sibling of #1306) | | #1318 | `get_observation` no longer emits a floating base's free joint as a degenerate scalar (drops the junk `floating_base_joint` column from recorded floating-base datasets; both backends) | | #1275 | point warp/mjwarp install hints at `strands-robots[sim-newton]` (closes #1268) | | #1348 | base `describe()` advertises `create_world` / `destroy` | | #1352 | unknown-camera error hints the canonical `list_cameras` action | | #1365 | `run_policy` single-robot policy binding is fail-soft like the multi-robot path | | #1370 | object/camera facade guards catch the partial-world state (empty world / no robot) | | #1383 | `add_robot(keyframe=...)` keeps earlier keyframe-spawned robots posed when adding more robots (was silently collapsed to the zero pose) | | #1405 | `DatasetRecorder.create(overwrite=...)` for honest re-recording into an existing `repo_id` | | #1407 | strip a stray leading space from the `render` / `render_depth` no-GL error messages | | #1416 | `get_ground_height(x, y)` accepts NumPy scalar coordinates | | #1417 | `add_camera(fov=...)` accepts NumPy scalar angles | | #1418 | `set_gravity` accepts a NumPy real scalar z-component | | #1419 | sim action router accepts NumPy scalar components in vector params | | #1420 | `run_policy`/`eval_policy`/`evaluate_benchmark` accept a NumPy scalar `control_frequency` (+ reject non-finite) | | #1423 | `replay_episode` accepts a NumPy scalar `speed` + rejects non-finite | | #1424 | `set_body_properties` rejects a non-finite mass (nan/+inf slipped the positivity guard) | | #1425 | `set_geom_properties` rejects non-finite/invalid color, friction, size | | #1426 | `set_joint_positions`/`set_joint_velocities` reject non-finite/non-numeric values | | #1427 | `move_object` validates the pose is finite numbers before mutating state | | #1429 | `add_camera` rejects non-finite/non-numeric position/target components | | #1430 | `add_object`/`add_camera` reject a non-finite/malformed numeric vector before baking it into the MJCF | | #1433 | `add_robot` validates its base pose (finite, numeric, correct length) before baking it into the scene | | #1435 | `raycast`/`multi_raycast` validate origin/direction are finite numbers | | #1436 | physics/introspection lookups give an actionable not-found for an unknown body/site/geom/sensor | ### Hardware | PR | What | |----|------| | #1026 | `run_policy(policy_object=...)` on the hardware `Robot` (sim parity for a pre-built in-process policy, no server on a port) | ### Training | PR | What | |----|------| | #1022 | honor `TrainSpec.learning_rate` in the `lerobot_local` trainer (was silently dropped -> `policy.optimizer_lr`) | | #1047 | discover LeRobot policy types from lerobot's live registry (drops the hardcoded allowlist) | | #1116 | `Gr00tTrainer.build_command` augmentation parity with `build_finetune_config` | | #1126 | `build_command` emits `--peft.lora_alpha` for LoRA parity with `build_config` | | #1085 | HIL consent gate blocking dangerous `lerobot_train` `extra_flags` passthrough (security; a long-open PR, merged out of order) | | #1438 | `build_command` emits `--reward_model.pretrained_path` for warm-started reward-model runs | | #1447 | forward `repo_type` through `StreamingDatasetReader.open` (stream a Hub dataset of a given repo type into training) | ### ROS 2 interop | PR | What | |----|------| | #995 | ROS 2 action support in `use_ros` (`list_actions`, `action_send_goal`) + goal-level `navigate_to` on `RosBridgedRobot` | ### lerobot_local / teleop / registry / mesh / tools fixes | PR | What | |----|------| | #979 | derive `teleoperate()` status from frame/error counters (a dead teleop reported "success") | | #980 | reject non-finite `STRANDS_MESH_CAMERA_HZ` (`inf` busy-spins the camera loop) | | #985 | warn when a checkpoint's normalization pipeline is present but inert | | #986 | registry hot-reload honors the user-overlay file's mtime | | #1057 | anchor mesh identifier allowlists with `\Z` so a trailing newline is rejected | | #1067 | `lerobot_teleoperate` / `lerobot_train` `status` returns telemetry in a `{"json": ...}` block instead of `**spread` top-level keys the runtime drops | | #1073 | surface an embodiment-config failure instead of silently discarding the pipeline behind a misleading diagnostic | | #1080 | `download_assets` `status` marks each robot available or missing (was ambiguous) | | #1082 | ZMQ REQ inference-client sets `LINGER=0` so teardown never blocks | | #1096 | reconstruct OLD-FORMAT in-model normalization dropped on a checkpoint load | | #1097 | degrade to passthrough on a corrupt `model.safetensors` instead of crashing | | #1099 | keep `observation.state` index-aligned on a partial `robot_state_keys` mismatch | | #1107 | zero-fill a missing embodiment `state_key` in place; use aloha's 14 actuator keys | | #1178 | resolve the `hf` CLI from the interpreter env in `sync_to_bucket` (recorder Hub upload) | | #1195 | decode cosmos3 msgpack arrays into writable, owning buffers | | #1202 | remove phantom `lerobot_async` / `dreamgen` provider references from the registry | | #1204 | raise on an unrecognized norm-stats mode instead of silent passthrough | | #1207 | run observation-history policies (Diffusion/VQBeT) via `select_action` | | #1210 | raise on an unknown `list_robots(mode=...)` instead of a silent unfiltered list | | #1422 | `is_molmoact2` respects an explicit `policy_type` without a Hub probe | | #1409 | `format_robot_table` renders every robot regardless of category | | #1446 | strip orphan whitespace from the `lerobot_calibrate` header lines | | #1450 | catch `zenoh.ZError` in the mesh best-effort lifecycle cleanup | | #1451 | reject a policy alias/shorthand that shadows another provider's canonical name (registry) | | #1457 | screen the `use_lerobot` method blocklist against the module path | ### Examples / packaging | PR | What | |----|------| | #973 | platform-aware `MUJOCO_GL` default (cgl is macOS-only; examples crashed on Linux) | | #975 | `[ollama]` extra so the local-LLM agent path installs out of the box | | #989 | `examples/train_ppo_reach.py` passes its own spec validation (`num_mini_batches` 4 -> 5) | | #1331 | add the streaming data-loop getting-started notebook (05) | | #586 | collect-train-run **MolmoAct2** example (`examples/lerobot/`) -- long-open PR, merged out of order | | #1390 | `10_evaluate_benchmark.py` -- benchmark-evaluation getting-started example | | #1391 | `11_author_a_benchmark.py` -- declarative-benchmark authoring example | | #1393 | `12_domain_randomization.py` -- domain-randomization example | | #1394 | `13_physics_introspection.py` -- physics-introspection example | | #1395 | `14_save_state_and_perturb.py` -- save-state + perturb example | | #1396 | `15_robot_catalog.py` -- robot-catalog discovery example | | #1402 | platform-aware `MUJOCO_GL` in the notebooks + lerobot e2e fixture (fixes #1017; supersedes #1018) | | #1434 | consolidate loose top-level examples into topic subfolders | ### Docs (README + docstrings) | PR | What | |----|------| | #957 | list the Cosmos 3 provider in the summary tables | | #958 | correct the simulation action count (64 -> 65) | | #960 | document the `create_trainer` training providers | | #1013 | correct stale lerobot reference paths in the `norm_stats` docstring | | #1016 | document `action_horizon` as a lower bound (clamped up to `execution_horizon`), not a maximum | | #1054 | correct the `async_rtc` seam-blending claim for non-RTC chunk policies | | #1066 | make the dataset IO docstrings self-contained | | #1071 | cross-reference sibling cosmos3 modules with `:mod:` roles, not source filenames | | #1079 | cross-reference MuJoCo backend siblings by module, not source filename | | #1120 | correct post-lerobot-0.6 VLA dependency guidance | | #1129 | correct stale from-source install hints for the `>=0.6.0` PyPI floor | | #1141 | document steerable dataset annotation with `lerobot-annotate` | | #1193 | correct stale `list_providers()` example output in api-reference + policies overview | | #1201 | list all 11 providers in the policies overview + a regression-test guard | | #1225 | enumerate every benchmark reward term in the predicate-registry docstring | | #1293 | `describe()` advertises the humanoid builtin benchmarks (docs) | | #1322 | cross-reference `MockPolicy` by module path, not source filename | | #1327 | cross-reference `MockTrainer` by module path, not source filename | | #1330 | cross-reference sibling mesh modules by module path, not source filename | | #1337 | cross-reference simulation modules by module path, not source filename | | #1374 | correct the README simulation action count (65 -> 67) | | #1375 | list notebook 05 in the notebook indexes | | #1376 | point the quickstart at the notebook series | | #1378 | mention procedural terrain in the README simulation-tool feature | | #1379 | add the remote policy provider to the README providers table | | #1387 | add the procedural terrain example (`examples/.../09_procedural_terrain.py`) | | #1401 | document the public RL trainer/env methods + properties | | #1410 | correct residual pre-0.6 lerobot install guidance (architecture / troubleshooting / molmoact2) | | #1441 | update the Isaac backend docs to the in-tree `strands-robots[sim-isaac]` (closes #1440) | | #1452 | document the concrete Trainer provider public methods | | #1456 | point the streamed-training instructions at the renamed `lerobot.scripts.lerobot_train` module | ### CI | PR | What | |----|------| | #1167 | retry `apt-get update` to survive transient third-party mirror failures | | #1240 | silence a pyarrow `import-untyped` mypy break after it dropped its `py.typed` marker (dependency drift) | | #1284 | remove a duplicate auto-generated `codeql-advanced` workflow | ### Coverage / contract-pinning sweep | PR | What | |----|------| | #952 | `dataset_recorder` `sync_to_bucket` hf-CLI failure + delete-flag contract | | #953 | mesh/audit seq-counter restore/lock fail-soft contract | | #954 | lerobot_local `ProcessorBridge` optional-dep degradation contracts | | #956 | mesh/bridge idempotent-teardown fail-soft contracts | | #959 | lerobot_local best-effort CUDA allocator release in `clear_model_cache` | | #961 | `render_all` namespaced camera short-name resolution | | #964 | bidirectional `name`/`robot_name` router alias + None-vector passthrough | | #965 | mesh CA trust hardening (operator-pin allowlist + download timeout) | | #967 | RL FastSAC truncation-bootstrap contract (time-out vs terminal) | | #969 | MotionBricks control-signal builder edge cases (invalid mode/range/direction) | | #971 | `LerobotTrainer` builder-side escape-hatch validation | Plus the v0.4.1-era **contract-pinning sweep**, which ran continuously from #976 to #1455 across sim, mesh, `lerobot_local`, `dataset_recorder`, gr00t, MoveIt2, `use_ros`, `CompositePolicy`, teleop, training, hardware, the RL backends, LIBERO, VERA, WBC, the Newton and Isaac backends, the recorders, the predicate and reward DSL, the inference server, the registry and the docstring-cross-reference guards. Its per-batch prose was condensed on 2026-09-13 -- every pull request it enumerated is kept here as a roster, 238 in all: #976 #982 #983 #984 #987 #990 #993 #996 #998 #999 #1001 #1004 #1005 #1007 #1008 #1009 #1010 #1011 #1014 #1019 #1023 #1024 #1027 #1028 #1029 #1030 #1031 #1033 #1036 #1037 #1039 #1040 #1041 #1042 #1043 #1045 #1046 #1048 #1049 #1052 #1053 #1055 #1058 #1060 #1061 #1063 #1064 #1068 #1070 #1072 #1074 #1075 #1077 #1081 #1088 #1089 #1091 #1092 #1093 #1094 #1095 #1098 #1101 #1102 #1103 #1104 #1105 #1106 #1108 #1111 #1113 #1115 #1117 #1118 #1119 #1121 #1122 #1123 #1124 #1125 #1127 #1128 #1130 #1131 #1132 #1133 #1134 #1135 #1136 #1138 #1140 #1143 #1157 #1160 #1161 #1162 #1163 #1165 #1166 #1168 #1170 #1171 #1173 #1174 #1175 #1177 #1180 #1181 #1182 #1184 #1185 #1186 #1192 #1194 #1196 #1197 #1199 #1200 #1206 #1209 #1211 #1213 #1214 #1216 #1217 #1218 #1219 #1220 #1221 #1222 #1224 #1226 #1227 #1228 #1229 #1231 #1232 #1234 #1235 #1237 #1238 #1241 #1242 #1243 #1244 #1245 #1246 #1247 #1248 #1253 #1258 #1260 #1263 #1283 #1285 #1289 #1290 #1292 #1294 #1295 #1296 #1297 #1300 #1301 #1302 #1304 #1305 #1307 #1309 #1310 #1311 #1312 #1313 #1314 #1315 #1316 #1317 #1319 #1320 #1323 #1325 #1326 #1328 #1329 #1332 #1333 #1334 #1335 #1341 #1342 #1343 #1345 #1349 #1351 #1353 #1355 #1356 #1358 #1359 #1361 #1362 #1363 #1366 #1367 #1369 #1371 #1373 #1377 #1380 #1381 #1382 #1384 #1385 #1389 #1392 #1397 #1399 #1400 #1403 #1408 #1411 #1413 #1414 #1415 #1428 #1431 #1432 #1437 #1439 #1442 #1443 #1444 #1445 #1448 #1449 #1453 #1454 #1455. ## v0.5.0 -- Humanoid locomotion + GPU sim + hardware (milestone drained, tag shipped) Milestone `0.5.0`: **11 closed / 0 open** issues, **100%** on the bar -- unchanged for a nineteenth sync, having moved all the way on 2026-08-24. **The two issues that contested its substantiation are now closed, both `NOT_PLANNED`, both on 2026-09-10 inside the same second range.** **#2720** -- the issue reporting that 57 of 60 issues closed `COMPLETED` in one 10-minute window with no linked pull request, and that three of epic #858's deliverables were absent from the tree -- closed at 04:21:37Z on the grounds that *"the board state it describes is history; closing with #858, whose absent deliverables are now out of scope."* **#858**, the last unscheduled epic, closed one second later. So the milestone's reading is unchanged and the contest is settled by decision rather than by delivery: **#3206** (FastTD3 as a native off-policy RL provider, +2,199 / -95 across 29 files, naming both issues in its title) is the one deliverable of the three that reached `main`, and it declared no closing reference, so the other two were dispositioned rather than shipped. That is a legitimate call and it is recorded as one -- `NOT_PLANNED` says "we are not doing this", which is what the previous twelve syncs asked for in place of a `COMPLETED` claim. It also means **no issue is open against this milestone's substantiation any more, and this page is the only remaining record of the reservation.** (The API's `closed_issues: 14` counts the three closed pull requests also assigned to the milestone -- #163, #317, #319 -- so 11 is the issue figure and both are correct at different scopes.) The final four (#469, #472, #473, #474) all closed on **2026-08-24T09:30Z**, within six seconds of each other, each with `stateReason: COMPLETED`. Together with #467/#468/#475 (closed 2026-08-14) and #466/#470/#471 (closed in June), every issue this milestone ever carried is closed, so the `v0.5.0` and `v0.5.1` tags -- both cut while it was still open -- are no longer ahead of it. **The code-level reservations this page recorded are unretired, and now unrepresented.** All four 2026-08-24 closes fall inside the window #2720 described, and none was retired by a merged pull request: #469's namesake kinematic-constraints API still has no constraint module under `strands_robots/policies/kimodo/`; #472's registry expansion and #474's mobile-base velocity contract were last recorded as unstarted, though #2689's `AckermannRosRobot` and #2696's `MobileBaseRobot` drive contract are the closest thing on `main` to #474's scope; #473's HW-in-the-loop lane has its sim prerequisite (LeKiwi #904) and the floating-base state lane behind it, but no eval lane. The milestone is drained; the tracker and the tree agree less than the bar suggests, and with #2720 closed there is no longer an issue saying so. ### Closed | Issue | What | Via | |-------|------|-----| | #466 | WBCPolicy -- GR00T Whole-Body-Control (SONIC) | merged (closed 2026-06-18) | | #470 | NewtonSimulation -- GPU-native backend (Warp / MuJoCo-Warp) | merged (closed 2026-06-19) | | #471 | End-to-end VLA-on-G1 hero example | #662 (closed 2026-06-25) | | #467 | MotionBricksPolicy -- generative G1 locomotion | provider #856/#876; issue closed 2026-08-14 | | #468 | CompositePolicy -- stack policies across DOF groups | #849, integration-tested #1009; issue closed 2026-08-14 | | #475 | QwenVLAPolicy -- Qwen-VL-based manipulation provider | closed 2026-08-14 | | #469 | KimodoPolicy -- text-to-motion + kinematic constraints | provider #2270/#2279/#2271 merged; closed `COMPLETED` 2026-08-24 with no linked PR -- the constraints API is not in the tree | | #472 | Expand `robots.json` (mobile-manip + expressive gaps) | partial on `main` (reBot #799, LeKiwi sim #904); closed `COMPLETED` 2026-08-24 with no linked PR | | #473 | HW-in-the-loop eval lane (LeKiwi, EarthRover) | prerequisites landed (#904, floating-base state); closed `COMPLETED` 2026-08-24 with no linked PR | | #474 | Mobile-base action contract (velocity cmds) for `lerobot_local` | nearest code on `main` is #2689/#2696; closed `COMPLETED` 2026-08-24 with no linked PR | | #94 | this roadmap | closed 2026-08-14 as completed | ### Open None -- the milestone is drained. Four of the eleven closes are unverified against a merged PR; see #2720. ### Suggested sequencing 1. **Cut the tag. Nothing is holding it, and the cost of waiting keeps growing.** `0.5.0` is drained, twelve driver buses are in, FastTD3 and the observer lane are in, an RL checkpoint is deployable, an evaluation reports a reliability figure, and **the dashboard now has a server** (#3695) -- and there is still no `## [0.5.0]` or `## [0.5.1]` heading, `[Unreleased]` is frozen at 168 entries, and **1,600 fragments** are pending in `changelog.d/` (through #3750) with `--check` clean. **1,451 unreleased pull requests** and **50 days since the CHANGELOG was last edited (#1690)** are both the largest this page has recorded. The concrete cost is unchanged and unaddressed: `vera`, `lerobot_calibrate`, `motionbricks` and `strands_robots.transforms` are gone from `main` and still present in the last published tag, so an installer following the published docs gets four surfaces `main` no longer has. Milestones `v0.4.0` (due 2026-07-31) and `v0.3.9` (due 2026-05-30) are also still OPEN and fully drained -- closing them is free. 2. **The extra-approval rule is no longer the story; the queue's size is.** `main` requires one approval **and** an extra approval for unattributed changes, so one account's approval can never carry a pull request -- and that remedy is now routine, with **seven heads reading `APPROVED`** (#3729, #3743, #3746, #3749, #3751, #3752, #3753). What has not kept pace is the rest: **ten heads are green, armed, thread-resolved and still `REVIEW_REQUIRED`** -- #3205, #3343, #3427, #3496, #3515, #3577, #3615, #3714, #3726, #3737. At **21 open** this is the largest queue this page has recorded, and it grew by ten in a day. 3. **Decide #3343.** A new outside contributor's Isaac backend: 108 files, green, all four review threads resolved, armed since 2026-09-09T13:42Z, no approving or change-requesting review ever submitted -- only comments -- and **unchanged for a seventh sync**. The author has done everything an author can do. It is a maintainer call on review scale, and it has been owed longer than anything else here. 4. **#3205 is the oldest head on the board at eleven days** -- `MERGEABLE`, green, armed, thread-resolved (+2,223 / -7 across 12 files), and still the `mesh_bridge` decomposition #2977 asked for. #3695 landing a dashboard server makes it more mergeable in principle and no more reviewed in practice; this page is the only remaining record of that request. 5. **Three heads are owed by their author, and one of them is red.** **#3400** (3 files, `CHANGES_REQUESTED`, unarmed, six days old), **#3640** (4 files, `CHANGES_REQUESTED`, one unresolved thread) and the new **#3673** (3 files, `CHANGES_REQUESTED`, one unresolved thread, **checks failing**) -- all `shipitfast`'s, all small. **#3739** is a fourth kind: 22 files, two unresolved threads and no review decision yet. 6. **Fixed -- the fragment naming defect is closed.** `changelog.d/` holds **zero** `0000-` placeholders, where it held four for three syncs, and the guard no longer permits the number at any surface: #3715 refuses it, #3721 gives that rule one owner so every surface refuses it, and #3722 renamed the one fragment that named no pull request. Naming a fragment for the **issue** it answers remains the established convention and needs no change. **This item is retired.** 7. **The duplicate-implementation rate went from zero to four, so the procedural mitigation is not holding.** Two bands took none; this one took four, every one a second implementation of a fix that merged: #3665 against #3666, #3671 against #3672, #3736 against #3731, and #3732 descoped into #3733 plus #3734 -- instances **sixteen to nineteen** of the shape #3169 measured. #3169's negative result stands (widening the duplicate-claim key fires on a third of the queue for two genuine pairs), so the remedy is still procedural: check the issue for an open head before opening a second one. At a 21-head queue that check matters more, not less. 8. **The five open issues are one safety gap, one vendor answer, and three things the tree can answer.** **#3122** (`dashboard`, **P2**) is still the highest-priority item on the board and #3695 sharpens it: both enrollment routes skip the auth middleware and two directory reads validate existence rather than containment, on a package that now *serves* and that gates hardware-moving tool calls (#3184). **#3744** (`policies`, **P2**, new) is the interesting one -- the relative-action roster cannot hold across the pinned lerobot range, which is the offline-snapshot lane (#3616/#3618/#3623) read as a *range* rather than a version. **#3099** (`drivers`, **P3**) still needs a vendor answer or a measurement on a physical pack. **#3734** (`simulation`, **P3**, new) is the `rotate_wrist` half of #3733, deliberately deferred. **#3578** (`simulation`, **P3**) overlaps #3343's diff and is best decided with that review. 9. **The tracker bookkeeping is holding -- keep it.** Both issues filed inside this band that could be closed from the tree were closed against the commit that answered them: #3664 via #3666 and #3684 via #3686, each inside two hours of filing. That is a fifth consecutive clean pass, after a band with no tracker movement at all. Set against 2026-08-24 (sixty closures, three reconciling) and 2026-09-10 (thirteen, one closer), it remains the standard this page asked for. 10. **The removals still need a hand-written section, and five bands running have added nothing to it.** The wave's five removed surfaces are `vera` and `lerobot_calibrate` (both marked `!`), and `motionbricks`, `strands_robots.transforms` and the MolmoAct2 normalization port (none marked). Any tooling that derives a release's breaking changes from commit metadata under-reports the wave by three. The tag is a **minor at least**, its note has to name all five plus the two providers #3075 still lists as going, and the forward fix is a convention: a removal that deletes a public surface carries the marker. ## Other open issues (no milestone) **Five open issues, 3 -> 5, after a band in which nothing moved in either direction.** Two were filed and closed inside the band against the commit that answered them (#3664 via #3666, #3684 via #3686), which extends the clean reconciliation run to a fifth pass; two more were filed and are still open. All five carry an area label and a priority. The two long survivors are now on this table for a fourteenth and fifteenth sync, and both remain blocked on something other than review. | Issue | Title | Note | |-------|-------|------| | #3122 | dashboard: path-containment and enrollment gates owed by the server slice | filed 2026-09-02T02:22Z, `dashboard` / **P2**, one minute before the #2848 draft was closed. Both enrollment routes skip the auth middleware and two directory reads validate existence rather than containment. **Still the highest-priority item on the board, and #3695 sharpens it**: the package it names now ships a server, on top of gating hardware-moving tool calls (#3184) and building its consent request from a refusal code (#3403). #3509, #3594, #3710 and #3738 have each hardened a neighbouring surface without touching either route this issue names -- the fifth band running that a fix has landed beside it | | #3744 | training: the relative-action roster cannot hold across the pinned lerobot range | filed 2026-09-16T05:04Z, `policies` / **P2**, the newest and the most structurally interesting item on the board. The offline-snapshot lane (#3616, #3618, #3623) asked whether a copy of lerobot's vocabulary is still true; this asks whether one copy *can* be true, when the pinned range admits releases that disagree about the roster. #3698 documented the roster the gate accepts one commit earlier, which is what made the contradiction visible | | #3099 | drivers/g1: `BmsState_.current` has no declared unit | filed 2026-09-01, `drivers` / **P3**. The vendor IDL declares no unit and the `g1_battery` verb asserted amps, so the verb published one the wire does not carry. Needs a vendor answer or a measurement on a physical pack -- unchanged for thirteen syncs, and still the only item on this page that cannot be closed from the tree | | #3734 | simulation: `rotate_wrist` that did not reach should name the obstruction | filed 2026-09-16T03:53Z, `simulation` / **P3**, in the same second that #3732 closed. It is the deliberately-deferred half of the motion-that-did-not-reach lane: #3733 shipped the `move_to` case as a fix and this carries the `rotate_wrist` case, so the descope is recorded rather than dropped | | #3578 | simulation/isaac: `SO101_RECORD_CONVERGE` is read into state nothing consults | filed 2026-09-13T02:20Z, `simulation` / **P3**. A documented operator environment knob is parsed into an attribute with no reader, so setting it changes nothing -- the write-only-private-state shape #3580 and #3583 swept the package for, caught at a surface that is also an operator-facing contract. Whether the fix is to wire it up or to delete it is a design call, and it lands inside the file #3343 rewrites, so it is best decided with that review | ## Open PRs **21 open** (11 at the previous sync) -- the largest review queue this page has recorded, and it grew by ten in a day. #3661 and #3662 merged off the previous table; twelve heads opened (#3673, #3714, #3726, #3729, #3737, #3739, #3743, #3746, #3749, #3751, #3752, #3753) and nine carried over. `shipitfast` has seven of the twenty-one. **The extra-approval remedy is now the board's normal path, and the bottleneck has moved from the rule to the volume.** `main` carries `require_extra_approval_for_unattributed_changes: true` beside `required_approving_review_count: 1` (and `require_last_push_approval: true`, `required_review_thread_resolution: true`, one required check, squash only), so one account's approval can never carry a pull request. **Seven heads now read `APPROVED`** -- #3729, #3743, #3746, #3749, #3751, #3752, #3753 -- where two did a day ago. What has not moved is the ten that satisfied every reviewable condition and wait on a second account: **#3205**, **#3343**, **#3427**, **#3496**, **#3515**, **#3577**, **#3615**, **#3714**, **#3726**, **#3737**, all `MERGEABLE`, green, armed, every thread resolved, all `REVIEW_REQUIRED`. Three are owed by their author (#3400, #3640, and the new #3673, whose checks are **failing**), and #3739 is its own case at 22 files with two unresolved threads and no review decision yet. | PR | Title | Status | |----|-------|--------| | #3205 | feat(dashboard): mesh_bridge slice of #2848 -- the signed rail honours the kill switch and an e-stop reports what the peers said | opened 2026-09-05T01:34Z by an outside contributor, `MERGEABLE`, checks **green**, armed 2026-09-05T11:04Z, `REVIEW_REQUIRED`, its one thread resolved (+2,223 / -7 across 12 files). **The oldest thing on the board at eleven days**, and the decomposition #2977 asked for -- waiting on a reviewer, not on its author | | #3343 | fix(simulation/isaac): Isaac Sim backend | opened 2026-09-08T22:24Z by a **new outside contributor**, `MERGEABLE`, checks **green**, armed 2026-09-09T13:42Z, `REVIEW_REQUIRED`, **all four review threads resolved**, and no approving or change-requesting review ever submitted -- only comments (+15,087 / -914 across 108 files). Claims MJCF-importer joint parity with MuJoCo, floating-base observation and dataset coverage, runtime-vs-requested status reporting, and new `get_contacts` / `apply_force` / `raycast` / `randomize` / `set_obs_noise` / `move_to` surfaces, verified 26/26 on an A10G. **Unchanged for a seventh sync and still the decision that is actually owed** | | #3400 | fix(drivers/booster): read_mode passes GetMode its out-parameter | opened 2026-09-10T05:05Z by `shipitfast`, `MERGEABLE`, checks **green**, **unarmed**, `CHANGES_REQUESTED` since 05:34Z, no open threads (+41 / -4 across 3 files) -- the smallest diff on the board, and it has owed its author a round for six days | | #3427 | fix(drivers/crazyflie): the telemetry block fits the packet cflib allows | opened 2026-09-10T10:34Z by `shipitfast`, `MERGEABLE`, checks **green**, its thread resolved, an approving review recorded and armed 2026-09-13T02:08Z (+53 / -4 across 4 files) -- waiting only on the extra-approval rule | | #3496 | fix(policies/lerobot_async): rename_map renames the camera it declares | opened 2026-09-11T10:45Z by `shipitfast`, `MERGEABLE`, checks **green**, armed 17:21Z, both threads resolved, **two approving reviews from one account** -- and still `REVIEW_REQUIRED` (+81 / -10 across 4 files). **The clearest thing on the board, and the one a second reviewer would close in a click** | | #3515 | fix(docs): sim teleop fence passes robot_name to teleoperate only | opened 2026-09-11T20:15Z by `shipitfast`, `MERGEABLE`, checks **green**, both threads resolved, armed 2026-09-13T04:09Z (+121 / -2 across 3 files) -- same state as #3427 | | #3577 | fix(policies/groot): groot_version is graded in service mode too | opened 2026-09-13T01:58Z by `shipitfast`, `MERGEABLE`, checks **green**, armed 2026-09-13T07:50Z, no open threads (+84 / -28 across 4 files) | | #3615 | fix(policies): lerobot_local names the constructor kwargs it ignores | opened 2026-09-14T02:20Z by `shipitfast`, `MERGEABLE`, checks **green**, **armed 2026-09-14T08:31Z**, its thread resolved (+56 / -1 across 3 files) -- **cleared its `CHANGES_REQUESTED` and joined the extra-approval queue** | | #3640 | fix(tools): download_assets grades unknown names and failed clones | opened 2026-09-14T17:26Z by `shipitfast`, `MERGEABLE`, checks **green**, **unarmed**, `CHANGES_REQUESTED` with **one unresolved thread** (+55 / -2 across 4 files) -- owed by its author for a second sync | | #3673 | fix(tools): lerobot_teleoperate action description names dagger | opened 2026-09-15T10:52Z by `shipitfast`, `MERGEABLE`, **checks FAILING**, **unarmed**, `CHANGES_REQUESTED` with one unresolved thread (+35 / -1 across 3 files) -- **the only red head on the board**, and owed by its author | | #3714 | feat(hardware_robot): a real arm can be read by an agent without running a policy on it | opened 2026-09-15T22:35Z, `MERGEABLE`, checks **green**, armed 22:36Z, both threads resolved, `REVIEW_REQUIRED` (+1,844 / -27 across 10 files) | | #3726 | feat(dashboard): the fleet, a simulated robot per thread, and an e-stop that reaches one still starting | opened 2026-09-16T01:45Z, `MERGEABLE`, checks **green**, armed 01:46Z, **all fourteen review threads resolved**, `REVIEW_REQUIRED` (+1,808 / -5 across 13 files) -- the follow-on to #3695's server | | #3729 | fix(docs): the Training data-loop fence opts into the remote-code gate | opened 2026-09-16T02:19Z by `shipitfast`, `MERGEABLE`, **`APPROVED`**, checks **green**, unarmed, no threads (+64 / -1 across 3 files) | | #3737 | fix(policies/lerobot_local): the camera pre-flight check reads the features the checkpoint declares | opened 2026-09-16T04:10Z, `MERGEABLE`, checks **green**, armed 04:11Z, its thread resolved, `REVIEW_REQUIRED` (+438 / -9 across 5 files) | | #3739 | feat(dashboard): a 3D twin drawn from the compiled model, with no physics in the browser | opened 2026-09-16T04:33Z, armed 04:35Z, checks **pending**, **two unresolved threads**, no review decision yet (+3,890 / -6 across 22 files) -- the largest head after #3343 | | #3743 | fix(simulation): stop_policy answers once the robot is free | opened 2026-09-16T04:58Z, **`APPROVED`**, armed 04:58Z, no threads, **checks FAILING** (+410 / -10 across 8 files) -- approved and red, so it is owed a push | | #3746 | fix(simulation): a policy the constructor refuses is reported, not raised or lost | opened 2026-09-16T05:55Z, **`APPROVED`**, armed 06:22Z, checks **pending**, no threads (+371 / -10 across 5 files) | | #3749 | fix(docs,hardware_robot): the quickstart records a real arm with the tool that records, and the refusal names where each verb lives | opened 2026-09-16T06:31Z, `MERGEABLE`, **`APPROVED`**, armed 06:31Z, checks **pending**, no threads (+309 / -13 across 4 files) | | #3751 | fix(docs): the quickstart says what steps 4 and 5 need, not "everything else runs in sim" | opened 2026-09-16T07:20Z, **`APPROVED`**, armed 07:21Z, checks **pending**, no threads (+190 / -1 across 3 files) | | #3752 | fix(hardware_robot): a checkpoint provider with no checkpoint is refused before the arm is energized | opened 2026-09-16T07:35Z, **`APPROVED`**, armed 07:35Z, checks **pending**, no threads (+248 / -2 across 5 files) | | #3753 | fix(hardware_robot): a command the dispatcher would refuse is refused before the operator is asked | opened 2026-09-16T07:50Z, **`APPROVED`**, armed 07:50Z, checks **pending**, no threads (+198 / -2 across 5 files) -- the newest head, and the fourth `hardware_robot` one in a day | **Seven of the twenty-one read `APPROVED`, ten wait on a second account, three are owed by their author and one (#3739) is owed a review decision.** The two large contributor features remain asymmetric: #3205 is unblocked and #3343 still needs a decision. The new shape this band is a **hardware-robot cluster** -- #3714, #3749, #3752, #3753 -- which is where the queue's growth came from. ## Closed / superseded notes - **The featureless run ends at nine bands, on volume rather than on one feature.** 73 PRs, **58 `fix` + 7 `feat` + 4 `test` + 3 `docs` + 1 `refactor`**, **277 files, +18,945 / -682 -- net +18,263 lines**, the largest net-positive band this page has recorded. `feat` moves 144 -> **151** and correctness still climbs to **78.5%**, because the fix count grew faster than the feature count even here. The one `refactor` is `examples`-scoped (#3709), so the net-negative refactor table holds at thirteen, the removals ledger (`vera`, `lerobot_calibrate`, `motionbricks`, `strands_robots.transforms`, the MolmoAct2 normalization port) is untouched for a fifth band, and the absence of a `!` or a `BREAKING CHANGE` trailer on all 73 commits is correct rather than a gap. - **The dashboard finally has a server, and it fails closed.** #3695 (19 files, +1,609 / -74) wires the core to the rails nine bands had accumulated -- `auth`, `access`, `consent`, `safety_state`, `settings`, `log_redaction`, `validate_scope`, `agent_hitl` -- taking `strands_robots/dashboard/` eleven -> **fifteen** modules plus a served `static/` bundle. It is the slice the #2848 decomposition argument was for, and #3726 and #3739 are already queued behind it. #3710 and #3738 are its correctness pass in the same band, and **#3122 is still open naming what the slice owes**. - **An unreadable reply names the peer, not the codec -- the band's dominant fix shape, and new in kind.** #3670, #3672, #3676, #3677. Four clients decoded a peer's response inside a `try` reporting a *codec* failure, so a malformed or truncated reply from the far end was answered as a local decoding bug and sent the caller to the wrong side of the wire. - **A configuration entry that is not a string is refused.** #3666 (closes **#3664**), #3667, #3674, #3679 -- the posture-flag lane read on the value side. #3666 is the consequential one: a DDS Security credential that was not a string was accepted and the participant QoS then dropped it, so a run believed it was authenticated. - **A motion that did not reach names what stopped it.** #3703, #3705, #3727, #3733 -- the verified-halt question asked of a *move* rather than a stop. **#3732 is the instructive part**: the `feat`-shaped version of the same idea was closed unmerged in favour of the narrower fix #3733 plus a tracker item, **#3734**, for the `rotate_wrist` half -- a descope recorded rather than dropped. - **A camera name answers for the camera it names.** #3683, #3691, #3693, #3730 -- a name the sim will list must be a name the sim will accept, once each, at every door. - **Seven features, six of them one theme: the library says more about the thing it just handed you.** #3695, #3728, #3724, #3694, #3727, #3731, #3697. - **Sequencing item 6 is retired -- the `0000-` fragment placeholder defect is fixed.** #3715 refuses the number, #3721 gives that rule one owner so every surface refuses it, #3722 renames the one fragment that named no pull request. `changelog.d/` holds **zero** placeholders, where it held four for three syncs, and `--check` reports `OK (1600 pending)`. - **The tracker moved again, in both directions.** #3664 and #3684 were filed and closed inside the band against the commits that answered them (#3666, #3686) -- a fifth clean pass -- while #3734 and #3744 were filed and remain open, so open issues went 3 -> 5. **#3744 is the structurally interesting one**: the offline-snapshot lane asked whether a copy of lerobot's vocabulary is still true; it asks whether one copy *can* be, across a pinned range whose releases disagree. - **The board grew by ten in a day to 21, its largest, and the constraint moved from the rule to the volume.** Seven heads read `APPROVED` (#3729, #3743, #3746, #3749, #3751, #3752, #3753) where two did a day ago; ten are green, armed and thread-resolved on `REVIEW_REQUIRED` (#3205, #3343, #3427, #3496, #3515, #3577, #3615, #3714, #3726, #3737); three are owed by their author (#3400, #3640, #3673, the last of them red). #3343 is unchanged for a seventh sync. - **The clean sweep broke after two bands, and it broke on the duplicate shape.** Four pull requests closed unmerged inside #3661 -> #3750, every one a second implementation of a fix that merged: #3665 against #3666, #3671 against #3672, #3736 against #3731, and #3732 descoped into #3733. Instances **sixteen to nineteen** of the shape #3169 measured, after two bands at zero. The counting streak also broke in the under-reporting direction: the tip is #3740 while #3750 is the highest merged number. - **The 2026-09-15 band bullets are now Finding/References rows below, and the 2026-08-26 -> 2026-09-14 ones were condensed on 2026-09-11, 09-13, 09-14 and 09-15.** Every pull request named is tabled in full under `## Post-v0.5.1` and every band has a dated row in the log; the claims and cross-references are kept, the per-bullet prose is not. | Finding | References | |---------|------------| | **A criterion already satisfied at reset is reported, not read as a result -- the third and sharpest reading of one question** (2026-09-15). #3492 -> #3517 refused a *value* that could never be honored and #3584 -> #3620 refused a *result* the run never produced; these five ask what a surface publishes when the **scene itself** already satisfies what is being measured. #3629 is the widest in consequence -- `measure_agreement`'s two fractions are accuracies over a class-imbalanced column, so a judge answering a constant scored the holdout's own class balance (0.95 at one-tagged-in-twenty, 1.00 at none), and the number deciding whether a judge may filter training data was unreadable off the number. #3637 and #3642 are the mirror pair: the same defect reads as an achievement on a `stop_when` clause and as a policy failure on a benchmark `failure` clause, both on step 1 with nothing moved | #3492, #3517, #3584, #3620, #3629, #3633, #3637, #3639, #3642 | | **Four more lanes, and the largest test-pin band on the page** (2026-09-15). *A plan that commands nothing is a failure, not a plan* (#3657's cuRobo joint-key list resolved once from the first waypoint, so the outcome depended on `action_horizon`); *a ROS 2 name must be able to name a topic, and a refused call leaves no node behind*; *a reader grades the size its own payload declares* (#3658's `.spz` `num_points` sized all five body blocks and was graded nowhere while the magic beside it was graded on the next line); *a refusal applies nothing from its own call* (#3645 rolled back only the property its refusal named). Eleven `test` commits, the most any band here has carried, each grading a door an existing suite reached only through a stand-in | #3621, #3622, #3625, #3626, #3627, #3628, #3632, #3634, #3635, #3636, #3638, #3641, #3643, #3644, #3645, #3646, #3647, #3648, #3650, #3651, #3652, #3655, #3656, #3657, #3658, #3659, #3660 | | **The cleanest sweep the page had recorded, and a tracker that did not move at all** (2026-09-15). Exactly one number inside #3621 -> #3660 was absent from `main` (the open #3640), none closed unmerged, and no issue was filed *or* closed -- a first for the wave. The extra-approval remedy entered routine use with #3661 and #3662 both `APPROVED`; rows also went to the offline-snapshot (#3623) and one-owner (#3624) tables, with #3630, #3631, #3649, #3653 and #3654 the rest | #3169, #3623, #3624, #3630, #3631, #3640, #3649, #3653, #3654, #3661, #3662 | | **A run that commanded nothing is refused, not scored -- the outcome form of the lane #3492 -> #3517 opened** (2026-09-14). Those refused a value that could never be honored *at the door*; these five refuse the *result*, where every argument passed and the run still applied nothing. #3620 is the sharpest -- `success_rate`, `avg_reward` and `pass_hat_k` published under `status="success"` for a rollout in which `send_action` was never called, with `steps_advanced` and `actions_applied` now separate numbers. #3608 is the one with an external premise: a source comment asserting that "LeRobot tolerates absent columns" where `validate_frame` refuses missing and extra features symmetrically, three lines apart | #3492, #3517, #3584, #3608, #3612, #3613, #3620 | | **A shutdown that cannot complete does not keep the resource, and an offline snapshot names what the live registry registers** (2026-09-14). The #3519 -> #3532 lane read from the teardown end -- that one asked what frees a thing whose *start* never finished, this asks what happens when the *stop* raises -- with #3596 the test half and the sharper finding: four cells asserting an rclpy `ImportError` *assumed* rclpy absent rather than establishing it, so on any host with a distro sourced they built a live node and left the context initialized. Beside it the dependency-floor lane's twin, where #3616's `_FALLBACK` policy-type snapshot was nine types behind **and its own test graded the constant against itself**, and #3618 is the misdirection case -- a gate refusing a non-native reward `type` and then describing the fields of the thing it had just said does not exist | #3519, #3532, #3590, #3596, #3602, #3603, #3614, #3616, #3618 | | **A door refuses a collaborator it cannot use, and the board's remedy was proven** (2026-09-14). #3617 (a `robot_name` the host cannot route to) and #3619 (a teleoperator the loop cannot poll, on the two surfaces that skipped the `get_action` check a third door already performed) each accepted, reported the session started, then failed every tick. And #3584 became the first head here to read `APPROVED` on a review from a second account and **merged**, so `require_extra_approval_for_unattributed_changes` is a queue rather than a deadlock | #3584, #3617, #3619 | | **A posture flag is checked, not read by truthiness -- the lane opened 2026-08-20 finished across nine surfaces at once, and reached every policy provider** (2026-09-13). On two of the nine the truthy branch is the permissive one: #3546 decides whether a Kimodo checkpoint executes its own code and #3574 whether WBC proceeds with models it could not find, so `"false"` had been selecting the unsafe posture. #3582 is the read-side twin -- a rover's sensor summary read a headlamp by truthiness, so a field the frame did not carry and a value the write door had already refused both rendered as `off` | #3538, #3543, #3546, #3548, #3549, #3553, #3554, #3574, #3575, #3582 | | **Three lanes opened, each the next question after one already here** (2026-09-13). *An action naming a key the surface cannot carry is refused* (#3534, #3537, #3539) sits one level below #3213: that fix refused an undeclared **verb**, these refuse an undeclared **key inside a declared verb's payload**, which had simply been dropped. *A name is held to the alphabet its consumers can carry* (#3557, #3558, #3559, #3565 closing #3562, #3570) is the containment question #3296/#3298 asked of a *name* -- a camera name reaches an MJCF attribute, a dataset column and an MQTT topic, each with its own grammar. *A private name must have a reader* (#3580, #3583) is a cross-cutting invariant with its own grader, and the shape #3578 was then filed against | #3213, #3296, #3298, #3534, #3537, #3539, #3557, #3558, #3559, #3562, #3565, #3570, #3578, #3580, #3583 | | **The verified-halt lane reached `robot_mesh` in the form where the caller held the answer and did not read it** (2026-09-13) -- every route reported success and audited `ok=True` over a delivered refusal, a timeout and a lockout alike. Beside it **one documented camera-routing precedence became honoured on every router that implements it**, #3555 the sharpest, an `embodiment=` caller's `camera_key_map` discarded outright so the model received no image input at all | #3545, #3550, #3555, #3560, #3561, #3566, #3568, #3571 | | **The duplicate-implementation shape stopped being occasional and took five instances in one band** (2026-09-13): #3536 against merged #3535, #3564 and #3569 both against merged #3565, #3567 against merged #3566, #3572 against merged #3571 -- instances eleven to fifteen of the shape #3169 measured, four inside 105 minutes on the evening of 2026-09-12, with issue #3562 drawing three implementations of one fix. #3169's negative result stands but was measured against roughly one instance per band. The same sync recorded the `0000-` fragment placeholders going 2 -> 4 (#3534, #3537) instead of being renamed | #3169, #3534, #3535, #3536, #3537, #3562, #3564, #3565, #3566, #3567, #3569, #3571, #3572 | | **A value that can never be honored is refused at the entry point (13 fixes, 2026-09-12's dominant shape)** -- in domain, finite, correctly typed, past every existing guard, and still unable to produce what the caller asked for. #3502 is the sharpest: a benchmark clause naming an entity that cannot exist compiles, never fires, and the evaluation publishes `success_rate: 0.0` beside `success_measured: true`; on a `failure` clause the same defect reads as 100% success. #3506's GR00T route accepted `expert_only`, reached no flag, and full-finetuned the backbone while reporting success | #3492, #3493, #3494, #3497, #3501, #3502, #3504, #3506, #3507, #3510, #3511, #3514, #3516, #3517 | | **A resource a failed bring-up adopted is released** -- the verified-halt lane's missing half, asking what frees a thing whose *start* never finished, because the success path is the only place it was adopted. Both `init_device_connect_sync` routes left a loop parked in `run_forever` on an unreachable thread; a Reachy teardown only stopped a loop it never closed; leaving the mesh returned while nine publishing loops were live. Every one is a leak no later verb can reach | #3519, #3522, #3526, #3530, #3532 | | **The queue's constraint was the ruleset, not review latency, and this page had it wrong for eight syncs** -- `require_extra_approval_for_unattributed_changes` beside `required_approving_review_count: 1`, so one account's approval could never carry a pull request; #3496 was green, armed, threads resolved, twice approved and still `BLOCKED`. Resolved in kind on 2026-09-13, when a second account approved #3584 | #3496 | | **The removals widened while the marking stopped (2026-09-11)** -- `motionbricks` (58 files, -4,583), the augmentation-transforms package (32 files, -6,064, 1,943 lines with no importer) and the MolmoAct2 normalization port (-2,179) all left `main` with no `!` and no `BREAKING CHANGE` trailer, where #3389/#3386 carried both one band earlier, so the tag's removals section has to be assembled by hand. `motionbricks` was the first of the four providers #3075's ruling named as going, with #3405 the counterpart -- the first GR00T change since the ruling. Whole band +24,826 / -26,659, net -1,833; #3485/#3488 were the same defect twice, `list_objects` and `list_robots` reporting where a thing was *put* rather than where it *is* | #3075, #3386, #3389, #3405, #3407, #3411, #3419, #3426, #3485, #3488 | | **An operator is asked before a command reaches a real actuator, and the docs and the test tree lost 6,433 lines saying each thing once (2026-09-11)** -- the HITL gate #3184 established was bypassed by four agent-callable surfaces that write to physical devices, and #3403 closes it from the other end by building the consent request from a refusal's *code* rather than its prose. Beside them #3417 replaced **fourteen** per-package copies of one docstring AST walk with a single linter rule (-1,663), #3469 cut README to 120 lines (-1,459), and seven docs pages plus three test consolidations went net-negative | #3184, #3403, #3417, #3418, #3420, #3430, #3431, #3440, #3463, #3468, #3469, #3470, #3471, #3472, #3483 | | **The tracker drained to two and every closure reconciled against merged code (2026-09-11)** -- #3398, the board's only P1 (a Feetech `set_torque` that never read the status packet a unicast WRITE returns, so a servo that had gone quiet read as one that complied) closed against merged #3407 about 27 hours after filing, and #3146 (break the loader/user_registry cycle or accept `py/cyclic-import`) against merged #3480, which took the third option and made the package import in a DAG. Survivors #3122 (P2) and #3099 (P3). The 2026-09-12 pass held it: #3531 filed 04:44Z, closed 06:18Z against merged #3532, with two fragments merged under the reserved `0000-` placeholder (#3526, #3529) the one new bookkeeping defect | #3099, #3122, #3146, #3398, #3407, #3480, #3526, #3529, #3531, #3532 | | FastTD3 reached `main` (+2,199 / -95, 29 files), the first of epic #858's off-policy deliverables and the first code answer to #2720 -- closing neither, its two fragments named for the issues rather than the PR | #858, #2720, #3206 | | **A verdict names the thing it was written for**, new in kind: a session verdict against the process its record was written for, its pid-reissue residue, a stand-in the prune never consults, an already-merged PR read as a pending mergeability, an unreviewed PR owed to whoever was nearest, a HITL grant naming a neighbouring call | #3110, #3219, #3227, #3231, #3233, #3237, #3248, #3250, #3254 | | Nine more shared value domains, two of them a value refused on one field and accepted on its sibling, one a non-finite wire timestamp four mesh freshness gates accepted | #3238, #3239, #3241, #3242, #3243, #3244, #3247, #3249, #3251 | | **A destructive step waits for the last refusal**: a refused `start_recording` had already wiped the `LeRobotDataset` it was replacing; `reset()` flushes the open episode on every backend; the same ordering asked of a trainer's `output_dir` | #3196, #3198, #3245 | | The floor lane read from the other end -- a test pinned an `np.cross` behaviour numpy 2.5 removed, so the upper half of the declared range was red | #2335, #2342, #2345, #2349, #3229, #3234 | | **Duplicate work, instances one to five**, all measured and none caught by a documented key: two implementations of one README line, of one script fix, and of one `-x` subtraction that both merged -- #3169 is the negative result (widening the key selects ~27 pairs across 9 issues for 2 genuine ones) | #3132, #3135, #3136, #3164, #3166, #3167, #3169, #3170, #3171, #3172, #3189, #3192, #3194, #3195, #3220, #3231, #3232, #3233 | | **The features moved off `main` into the queue** -- four `feat` heads from one outside contributor in ninety minutes, including the `mesh_bridge` slice #2977 asked for and the mesh peer-retention head | #858, #2720, #2848, #2977, #3189, #3205, #3206, #3212, #3220 | | **An undeclared agent verb ran the halt on eleven of the twelve shipped drivers** -- a typo, a verb borrowed from a sibling, or a non-string stopped the robot under a success-shaped answer; `drivers.base` now owns `declared_verbs` and the refusal that lists them | #3213 | | **The cache-validity lane, seven surfaces**, shape moving from *the source changed underneath the entry* to *the key does not name what the entry is about* -- the mesh cache reporting an absence it had created, and a `MinkIKBridge` keyed on nothing at all | #3141, #3142, #3144, #3145, #3147, #3173, #3177, #3214, #3220 | | **#2907's arithmetic**: the "Update branch" button is a push, so its sole approval was spent twice; #3197 measured the eight review rounds a base absorption costs and why the resulting `BLOCKED` reads as latency. An off switch that still published one step closed beside it | #2907, #3173, #3175, #3176, #3178, #3180, #3190, #3191, #3197 | | **The dashboard went from a decomposition argument to the wave's feature lane**: #2878 restated as #2977, both extracted slices merged and hardened, the 481-file draft #2848 closed with no check run in its life one minute after #3122 named what it carried, then a HITL gate over hardware-moving tool calls, a `Lockout` type, an honest preflight, `strands-agents>=1.13.0` and the written-bound guard | #2335, #2342, #2345, #2349, #2848, #2878, #2977, #3050, #3051, #3078, #3080, #3122, #3184, #3185, #3186, #3188 | | The wave's only `perf` commit: coverage on the PEP 669 core, +8.2% against +76.6%, identical reading per file, statement and exclusion -- buying room on #3143's bound rather than deciding it | #3143, #3149 | | **The whole-tree graders were audited and found under-reading**: a hand-maintained roster, four unlearned spellings of a walk root, a pacing sweep grading only tickers spelled `Ticker`, and guards grading the working directory -- every one a false *pass*. The band that followed had every PR number on `main`, its seven gaps all issues | #3092, #3094, #3095, #3099, #3105, #3106, #3107, #3111, #3112, #3113, #3118, #3119, #3120, #3122, #3125, #3126, #3128, #3130 | | Closures that reconciled -- five against a merged PR, and the two this page called closeable resolved as written | #2833, #2895, #2935, #2940, #2963, #2981, #3060, #3062, #3063, #3064, #3066, #3079, #3080, #3086, #3087 | | **The review queue drained 17 -> 5 in a day** and the native-driver programme doubled to twelve buses across 25 registry robot names, its verbs shipped in the consolidated shape from the start; what it kept carrying was two hardware items and two design ones | #2734, #2739, #2749, #2750, #2753, #2757, #2762, #2763, #2765, #2767, #2779, #2786, #2812, #2836, #2891, #2928, #3037, #3040, #3043, #3045, #3046, #3050, #3051, #3053, #3058, #3060, #3063, #3070, #3081, #3084 | | **Every DDS endpoint is constructed under the shared lock** -- filed against the Go2 driver the day it landed, then closed as a class rather than a site, with the lock vocabulary derived from every holder | #3039, #3062, #3064, #3067, #3071, #3072, #3073 | | **A stop reports its outcome instead of asserting one** -- the wave's longest-running idiom, opened at eight sites and reaching the drivers, the rollout entry point, the training sessions and the inference server | #2809, #2815, #2816, #2821, #2825, #2826, #2828, #2831, #2832, #2833, #2834, #2835, #3060, #3091, #3092, #3100, #3104, #3108, #3110, #3114 | | **#2765 closed against merged code** (the motion-switcher decoder plus the FSM-producer wiring answered its five `rt/lowcmd` decisions), promoting the read-cadence question; and the 81-PR `neon-the-g1` verb port peaked at 79 modules before consolidating to 22 | #2765, #2888, #2891, #2916, #2946, #2948, #2950, #2953, #3037, #3065, #3070, #3089 | | **Three net-negative refactors merged and a fourth was refused** -- the vendored LIBERO adapter gone, 92 g1 lookup verbs folded into one dispatcher, 13 execution modules into one table -- roughly 76,000 lines, with the GR00T removal stopped by an argument about public surface | #3037, #3047, #3056, #3070, #3075 | | **The 2026-08-24 tracker pass**: sixty issues closed in one 10-minute window, all `COMPLETED`, 57 with no linked PR -- the pass #2720 was filed against, which is why every count on this page is reported rather than endorsed. Three of the closes do reconcile against merged code | #858, #859, #873, #1537, #1905, #2062, #2145, #2179, #2221, #2315, #2330, #2337, #2459, #2460, #2507, #2685, #2720 | | Both long-stalled external PRs drained by re-landing rather than rebase, each original closed within minutes of its replacement | #1035, #1722, #1905, #2689, #2696 | | The duplicate/overlap merge gates narrowed three times in one band; the residue is the concrete failure that motivated them | #2762, #2769, #2774, #2791, #2797, #2830 | | **The Isaac asset-reader sweep** (22 PRs) -- MJCF and URDF attributes read the way the compiler resolves them, not literally at the site that declares them; LIBERO's box proxies replaced by real meshes; parity itself closed out | #2156, #2221, #2233, #2498, #2656, #2712 | | The dataset-transform / VLM-judge batch shipped in full within five days, plus its own correction pass and the continuable refusal that carries a code and its subject | #2480, #2486, #2508, #2610, #2612, #2616, #2617, #2618, #2623 | | #1087's scope shipped broadened -- a publish blocklist became a HITL gate over every verb that can command a robot -- and #1087 closed as superseded, leaving the halt-vs-drive contract tracked separately | #1087, #2313, #2315 | | **A dependency-floor audit closed five floors in one window**, each raised to the release that ships the imported capability, plus the one that cannot be floored because it is not on PyPI -- the #1930 class `v0.5.1` was cut for | #1930, #2335, #2340, #2342, #2345, #2349 | | **A monotonic-clock lane closed six published budgets** measured by subtracting two `time.time()` readings, and filed its own remainder | #2404, #2406, #2408, #2412, #2413, #2414, #2425 | | **The photoreal 3DGS + GS-lighting batch landed whole**, closing four of its five tracking issues plus one more, with its correction pass inside the same wave and the gallery and parent closed later | #1537, #2321, #2322, #2323, #2324, #2325, #2326, #2328, #2331, #2332, #2336, #2337, #2441, #2443, #2446, #2452 | | Two lanes closed their last subtask -- the Isaac motion-primitive parity suite, and the fleet example epic at 5/5 | #2156, #2179, #2181, #2198, #2233 | | `ProtoMotionsPolicy` took its post-merge hardening pass, no public-surface change | #2287, #2411, #2416, #2417, #2418 | | The six CodeQL `py/uninitialized-local-variable` alerts cleared, and the Newton `set_timestep` parameter-name fix landed | #2308, #2359 | | **The removal direction reached shipped public surface, on a ruling rather than an argument** (2026-09-10 band): two `refactor!` commits, `vera` deleted (132 files, +424 / -15,204; `uv.lock` 258 -> 237 packages, the five `[tool.uv] conflicts` rows gone, extras 34 -> 33) and `lerobot_calibrate` deleted (22 files, +227 / -2,313, 859 LOC that never calibrated anything -- LeRobot ships four console scripts that do). Neither amputated a shared piece: `discover_ee_frame` stayed in `simulation.ik` with its three-rung ladder pin moved to the shared home, three shared guards were re-anchored on the surviving providers, and both removals took their own `changelog.d` fragments with them as the LIBERO one had. What licensed them was #3075, closed `NOT_PLANNED` 2026-09-10T04:21:21Z with the decision in words -- "GR00T stays. Service mode and the container tool have no upstream replacement" -- keeping GR00T/MolmoAct and dropping `lerobot_async`, `curobo`, `moveit2`, `motionbricks`. So #3047's refusal produced a ruling rather than a stalemate. Whole band 327 files, +15,052 / -25,581, net -10,529; wave removals ~101,000 lines across seven refactors at that point | #3047, #3056, #3075, #3386, #3389, #3411 | | **The tracker emptied a second time, and #2720 was closed by the pattern it was filed about**: thirteen issues closed 04:21:02Z -> 05:16:27Z, **seven `NOT_PLANNED`**, five `COMPLETED` with no closer, one with a closer -- #2796 via merged #3395, the required check now evaluating the merge commit rather than the branch head, retiring the oldest CI item on the page. #2720 (*"57 of 60 issues closed COMPLETED with no linked PR inside one 10-minute window"*) closed `NOT_PLANNED`, with no linked pull request, inside a ten-minute window. The distinction that matters: `NOT_PLANNED` is a decision and 08-24's `COMPLETED` was a claim, so seven of the thirteen are a different act from the pass #2720 described -- and five are not. #858, the last unscheduled epic, closed one second after it, with FastTD3 (#3206) the one deliverable of the three that reached `main`. Both hardware-blocked items went (#2753, #2812), so nothing on the page waits on a physical robot. Open issues 16 -> 4, and the survivors carried **priority labels** for the first time, including the new P1 #3398, whose remedy was already approved as #3407 | #858, #2720, #2753, #2791, #2796, #2812, #2891, #2928, #2977, #3075, #3143, #3169, #3197, #3206, #3395, #3398, #3407 | | **The clean sweep broke after two bands, and the shape that broke it is the one whose record closed the same morning**: #3361 and #3370 closed unmerged inside #3352 -> #3406, each a second implementation of a fix that merged as #3360 and #3368 -- the seventh and eighth instances of the shape #3169 measured, on the day #3169 itself closed `NOT_PLANNED`. #3361 is the sharper: it added the boolean check to `lerobot_calibrate`'s `overwrite`, and #3386 deleted the tool, so its subject no longer exists. That is also how #3356 finished -- six PRs checked the posture flags on the surfaces an agent calls, and the seventh row resolved by deletion | #3169, #3352, #3355, #3356, #3360, #3361, #3364, #3365, #3367, #3368, #3370, #3386, #3406 | | **Both contributor features resolved exactly as the previous sync predicted**: #2907 closed unmerged 2026-09-09T07:08Z with its observer lane already on `main` as #3351, and #3212 closed unmerged at 07:24Z once #3352 landed its payload cherry-picked and attributed -- one pull request whose work had shipped and one decision on two heads, both bookkeeping rather than review rounds. The queue that replaced them was thirteen heads, **four `APPROVED` and armed**, with #3343 promoted out of draft at 106 files with all four threads resolved | #2907, #3212, #3343, #3351, #3352 | **Pre-`v0.5.0` release and cluster notes (condensed 2026-09-10, further 2026-09-14).** Every claim below is carried by the `## Published releases` table and the mermaid above, so what is kept here is the roster. Motion-planner epic **#415** closed (cuRobo #442, MoveIt2 #305, both in v0.4.0). The v0.3.9 mesh-security scope shipped folded into **v0.4.0**; its nine consolidation umbrellas (#386-#394) and their follow-ups stay closed. **v0.4.1** (2026-07-01) collapsed `[Unreleased]` into a dated heading and opened a fresh one via **#977** -- a collapse **never repeated**, which is why one `[Unreleased]` section now spans two shipped tags. **v0.5.0** (2026-08-04T01:32Z) shipped as a minor, the planned v0.4.2 patch never cut, while its milestone still carried 8 open issues, so the tag and milestone boundaries did not agree; **v0.5.1** (2026-08-06T20:23:39Z) was a correctness-only patch answering a resolution gap -- `stream_dataset(repo_type="bucket")` needs the `>=0.6.1` constructor that v0.5.0's `>=0.6.0` floor did not guarantee (#1930) -- plus the `[wbc]` `huggingface_hub>=1.5.0` floor (#1957). Inside those waves: remote policy inference as `strands_robots.inference` (#1142); the Isaac Sim migration epic **#1144** closed via #1156, with #1151/#1153/#1154/#1155/#1205 closing #1146-#1150 and the vendor subtask #1145; the first runnable locomotion benchmark #1259 over #1239 and #1255, joined by #1287/#1288 and completed on all three command axes by #1321/#1324; the terrain curriculum's four heightfield kinds #1336/#1338/#1339/#1340 plus the `difficulty` knob #1344 (tool_spec-discoverable #1350) against the #873 groundwork; the security HIL gate #1085 merged out of order; and #1360 -> #1389 adding public `get_ground_height` (#1388), terrain-relative predicates (#1364/#1368), floating-base robots seated on terrain (#1386), Isaac/Newton base-contract rejects (#1360/#1372), the multi-robot keyframe fix (#1383), fail-soft facade guards (#1365/#1370) and the README/notebook docs run (#1374, #1375, #1376, #1378, #1379, #1387). The short-lived `planning` (#853) and `sim_managers` (#878) packages were both removed (#889/#890) inside their own wave, so neither added net surface. **#586** merged 2026-07-15 and **#1018** was superseded by **#1402** (fixing #1017). **This roadmap closed as completed 2026-08-14T02:17Z**, in the grooming pass that also closed #467, #468 and #475 within 78 minutes and took `0.5.0` from 3 closed / 8 open to 7 / 4; the roadmap label carries no successor issue, so tracking moved to the milestone and the open-issue set. #1087 (superseded 2026-08-19) and #1035 (2026-08-24, after **#2689** landed its scope) were the last long-open pair, while the robots-sim examples-migration cluster landed (#1278, #1280, #1282 merged; #1276, #1279 closed), the mesh IoT-transport test #1347 closed 2026-07-23, #1110 merged 2026-07-31 and #1000 closed unmerged 2026-08-02. **2026-07-09 -> 2026-07-22 (thirteen daily reconciliations, condensed 2026-09-06, rostered 2026-09-14).** Thirteen runs against `main` while **v0.4.1 was still the latest tag**, folding the post-v0.4.1 `[Unreleased]` wave forward from **+234 to +516 PRs**, with milestone `0.5.0` reading **3 closed / 8 open** throughout. Every pull request they enumerated is tabled in full under `## Post-v0.4.1` above, and the arc they describe -- the Isaac backend migration, the reward/predicate DSL becoming runnable, the humanoid and omnidirectional benchmarks, the terrain curriculum, the `describe()` expansion, the NumPy-scalar and finite-value validation sweeps, and the getting-started example series -- is carried by that section's own theme tables. Markers, in order: 07-09 @ #1209 (+234); 07-10 @ #1238 (+263); 07-11 @ #1263 (+288); 07-12 @ #1307 (+319); 07-13 @ #1334 (+346); 07-14 @ #1359 (+370); 07-15 @ #1389 (+400); 07-16 @ #1418 (+427); 07-17 @ #1442 (+447); 07-18 @ #1458 (+465); 07-19 @ #1478 (+485); 07-21 @ #1497 (+504); 07-22 @ #1530 (+516). Every number those entries named, kept as a roster: #586 #1017 #1018 #1085 #1144 #1146 #1150 #1151 #1153 #1154 #1155 #1156 #1183 #1187 #1198 #1203 #1205 #1208 #1209 #1212 #1215 #1223 #1230 #1233 #1236 #1238 #1239 #1249 #1254 #1255 #1256 #1257 #1259 #1261 #1262 #1263 #1264 #1266 #1267 #1268 #1270 #1275 #1277 #1278 #1280 #1281 #1286 #1287 #1288 #1291 #1298 #1299 #1303 #1306 #1307 #1308 #1318 #1321 #1322 #1324 #1327 #1330 #1331 #1334 #1336 #1337 #1338 #1339 #1340 #1344 #1346 #1348 #1350 #1352 #1354 #1357 #1359 #1360 #1364 #1365 #1368 #1370 #1372 #1374 #1375 #1376 #1378 #1379 #1383 #1386 #1387 #1388 #1389 #1390 #1391 #1393 #1394 #1395 #1396 #1398 #1401 #1402 #1405 #1407 #1409 #1410 #1416 #1417 #1418 #1419 #1420 #1421 #1422 #1423 #1424 #1425 #1426 #1427 #1428 #1429 #1430 #1431 #1432 #1433 #1434 #1435 #1436 #1437 #1438 #1439 #1440 #1441 #1442 #1443 #1444 #1445 #1446 #1447 #1448 #1449 #1450 #1451 #1452 #1453 #1454 #1455 #1456 #1457 #1458 #1459 #1460 #1461 #1462 #1463 #1464 #1465 #1466 #1467 #1468 #1469 #1470 #1471 #1472 #1473 #1474 #1475 #1476 #1477 #1478 #1479 #1480 #1481 #1482 #1483 #1484 #1485 #1487 #1488 #1489 #1490 #1491 #1492 #1493 #1494 #1495 #1496 #1497 #1498 #1499 #1503 #1505 #1508 #1509 #1511 #1512 #1513 #1515 #1517 #1518 #1519 #1520 #1521 #1522 #1523 #1524 #1525 #1526 #1527 #1528 #1529 #1530 #1531. **2026-08-04 / 08-07 / 08-08 (three release-state correction passes, condensed 2026-09-06).** None was a full reconciliation, so `Last synced` stayed at `@ #1932` throughout. **08-04** recorded the new **`v0.5.0`** tag (published 2026-08-04T01:32Z, superseding the v0.4.2 patch that was never cut -- an earlier revision of that entry had said "no new tag", written ~9h after the release shipped), corrected the Open-PRs table to the three genuinely open (#1722, #1087, #1035) after #1596 and #1110 merged and #1559 / #1000 closed unmerged, and dropped #1145 (closed 2026-07-11) and #1109 from the no-milestone table. **08-07** found **`v0.5.1`** published 2026-08-06T20:23:39Z and unmentioned anywhere on the page -- the header had asserted `v0.5.0` was latest for ~14h -- so it corrected the header and added the missing v0.5.0 / v0.5.1 rows to the Published-releases table, which had stopped at v0.4.1. **08-08** fixed four statements that were false rather than stale: #1145 described as open in two places, nine of eleven items in a "still open" bullet already merged or closed, the robots-sim examples migration described as in flight, and the `## Post-v0.4.1` heading still targeting the uncut v0.4.2. Every defect drifted the same direction -- work was further along than the document admitted. All three deliberately left the dated blocks above untouched (true on their dates) and re-verified milestone `0.5.0`'s "3 closed / 8 open" against the bar's 6 (3 closed issues + 3 closed PRs = 6/14 = 42.857%, both correct at different scopes). Live drift they recorded for the next full sync: merged tip #2000, then #2034, i.e. 50 and 73 PRs beyond the marker (#2001 was open but unlisted at 08-07; #2028 and #2034 both opened and merged inside one morning, so neither was ever listable); and a known gap left unpatched -- the no-milestone table listed 3 issues while 32 were live. **2026-08-16 -> 2026-08-28 (eight daily reconciliations, condensed 2026-09-07, rostered 2026-09-14).** Eight runs against `main` while **`v0.5.1` was still the latest tag**, carrying the post-`v0.5.1` wave from **+226 to +690 PRs**. 08-16 was the **final reconciliation** taken after this roadmap closed on 2026-08-14; **08-26 is the one that mattered** -- milestone `0.5.0` went 7 closed / 4 open -> **11 / 0 (100%)**, open issues 78 -> 4, and **#2720** was filed against exactly that pass, which is why every count on this page is reported rather than endorsed. The theme sections those entries opened -- the native hardware-driver seam, one drive contract for every mobile base, the Isaac asset-reader run, the shared-value-domain sweep, the dataset-transform and VLM-judge batch, a stop reporting its outcome, refuse-what-cannot-be-honored, and the docs/asset/remote-inference lanes -- are all tabled in full under `## Post-v0.5.1` above. Markers, in order: 08-16 @ #2307 (+226); 08-17 @ #2387 (+282); 08-18 @ #2426 (+318); 08-19 @ #2456 (+350); 08-20 @ #2521 (+381); 08-26 @ #2748 (+581); 08-27 @ #2836 (+650); 08-28 @ #2879 (+690). Every number those entries named, kept as a roster: #467 #468 #475 #1035 #1087 #2122 #2123 #2145 #2198 #2233 #2268 #2269 #2307 #2310 #2363 #2367 #2368 #2378 #2387 #2388 #2390 #2391 #2392 #2393 #2396 #2401 #2402 #2403 #2407 #2410 #2414 #2415 #2420 #2421 #2422 #2423 #2426 #2427 #2433 #2439 #2440 #2451 #2453 #2454 #2456 #2457 #2458 #2461 #2463 #2464 #2465 #2466 #2467 #2468 #2469 #2473 #2474 #2475 #2476 #2478 #2479 #2481 #2483 #2485 #2487 #2488 #2489 #2491 #2492 #2493 #2494 #2497 #2499 #2500 #2501 #2503 #2504 #2510 #2511 #2515 #2516 #2517 #2521 #2522 #2685 #2720 #2746 #2748 #2749 #2763 #2765 #2771 #2791 #2792 #2793 #2794 #2795 #2796 #2798 #2801 #2812 #2823 #2829 #2830 #2833 #2836 #2848 #2856 #2862 #2874 #2879. **2026-08-31 -> 2026-09-02 (three daily reconciliations, condensed 2026-09-07, rostered 2026-09-14).** The three consecutive runs that took the wave from **+841 to +910 PRs** while `v0.5.1` stayed the latest tag, and between them they hold the wave's turning point in both directions. **08-31 was the largest band this page recorded, 151 PRs, and it inverted the wave's character** -- 84 `feat` against 48 `fix` took `feat` 36 -> 120 and the correctness share 81.7% -> 73.5%, but by concentration, since 81 of the 151 were one programme, the `neon-the-g1` operator verbs at their 79-module peak; it also opened the net-negative refactor lane, the `doctor` lane and the environment-variable lane, and left **open PRs at 17**, the largest queue recorded. **09-01 drained it to 5**, doubled the native-driver programme to twelve buses across 25 registry robot names, landed both g1 consolidations (79 modules -> 22) and refused #3047 one minute after #3075 was filed against its premise. **09-02 was the most correctness-dominated band to that point** and closed the 481-file dashboard draft #2848 one minute after #3122 was filed naming what it still carried; it also opened the whole-tree-grader, refuse-a-span-that-resolves-to-no-work and no-reading lanes, and was the first band in which **every pull-request number inside it was on `main`**. Every pull request they enumerated is tabled in full under `## Post-v0.5.1` above. Markers, in order: 08-31 @ #3057 (+841); 09-01 @ #3091 (+877); 09-02 @ #3130 (+910). Every number those entries named, kept as a roster: #2765 #2833 #2848 #2888 #2891 #2893 #2896 #2906 #2907 #2908 #2909 #2911 #2916 #2945 #2952 #2963 #2979 #2981 #2985 #2990 #3003 #3009 #3025 #3037 #3038 #3039 #3040 #3042 #3043 #3045 #3046 #3047 #3050 #3051 #3053 #3054 #3055 #3056 #3057 #3058 #3060 #3062 #3063 #3064 #3066 #3067 #3070 #3071 #3072 #3073 #3074 #3075 #3076 #3077 #3078 #3079 #3080 #3081 #3082 #3083 #3084 #3085 #3086 #3087 #3090 #3091 #3092 #3094 #3095 #3097 #3099 #3100 #3103 #3104 #3105 #3106 #3107 #3108 #3109 #3110 #3112 #3113 #3114 #3116 #3118 #3119 #3120 #3121 #3122 #3123 #3124 #3125 #3126 #3128 #3129 #3130 #3131 #3132 #3133 #3134. **2026-09-03 -> 2026-09-05 (three daily reconciliations, condensed 2026-09-08, rostered 2026-09-15).** The three runs that took the wave from **+944 to +979 PRs** while `v0.5.1` stayed the latest tag. **09-03** was the third featureless band (34 PRs, one out-of-order merge #3009 closing #3007; share 73.2% -> 73.5%) and the band whose structural event was duplicate work measured on itself -- #3164 named the `-x` subtraction, #3167 and #3166 answered it 50 minutes apart, and **#3169** filed the negative result that widening the claim key selects ~27 pairs across 9 issues for 2 genuine ones; it opened the cache-validity lane (#3144's mechanism) and the run-that-stopped-early lane (with #3149, the wave's only `perf` commit, +8.2% against +76.6%), and took open PRs to 2, the smallest queue recorded. **09-04** ended the featureless run with three dashboard features (#3184 the HITL gate, #3185 `Lockout`, #3186 the honest preflight, six modules -> ten), raised `strands-agents` to `>=1.13.0`, and held open issues at 16 in number *and* membership for the first time. **09-05** was the fourth featureless band and the one where the features moved off `main` into the review queue (open PRs 2 -> 9, four `feat`, three opened inside ninety minutes by one outside contributor), its most consequential merge the safety fix **#3213**, and it opened the undeclared-agent-verb, one-owner and keeps-the-dataset lanes. Every pull request they enumerated is tabled in full under `## Post-v0.5.1` above, and the themes those days opened each have a section there. Markers, in order: 09-03 @ #3172 (+944); 09-04 @ #3191 (+959); 09-05 @ #3220 (+979). Every number those entries named, kept as a roster: #858 #2335 #2342 #2345 #2349 #2720 #2848 #2907 #2977 #3007 #3009 #3132 #3134 #3135 #3141 #3142 #3143 #3145 #3147 #3148 #3164 #3166 #3167 #3170 #3171 #3173 #3175 #3176 #3177 #3178 #3180 #3183 #3187 #3188 #3189 #3190 #3192 #3193 #3194 #3195 #3196 #3197 #3198 #3199 #3200 #3202 #3203 #3204 #3205 #3206 #3207 #3208 #3209 #3210 #3211 #3212 #3214 #3216 #3218 #3220 #3222. **2026-09-06 -> 2026-09-09 (four daily reconciliations, condensed 2026-09-09/10, rostered 2026-09-15).** The three runs that took the wave from **+1,011 to +1,072 PRs** while `v0.5.1` stayed the latest tag. **09-06** folded in 32 PRs with four out-of-order merges and landed **#3206**, FastTD3 (+2,199 / -95 across 29 files) -- the first of epic #858's off-policy deliverables to reach `main` and the first of the three #2720 reported absent to become code rather than argument -- declaring no closing reference, so the bookkeeping stayed owed; it opened *a verdict names the thing it was written for*, and its two fragments were numbered for the issues they answer, a first. **09-07** was the fifth featureless band (26 PRs; correctness past three-quarters at 75.0%), almost end to end one idiom, whose sharpest form is *two owners of one value*, and it opened *a dialled address has two halves*. **09-08** was the sixth and largest featureless band (35 PRs; 75.7%), its idiom the shared domain read one step earlier -- *a value is graded before it is converted* -- and it opened three lanes including *a packaging declaration reaches the install that needs it*; its one non-reconciling closure (#2853, closed with no linked pull request 42 minutes before #3297 merged the nearest code to it) is recorded against #2720, and the clean sweep returned. Every pull request they enumerated is tabled in full under `## Post-v0.5.1` above, and the themes those days opened each have a section there. **09-09** followed, 27 PRs with no out-of-order merge, ending the featureless run at six bands on a single feature that landed **by rebase rather than by review** -- #3351 carrying #2907's observer lane byte-identically with authorship preserved, which made #2907 closeable as superseded and #3212 the same offer as #3352; it opened *a store is written whole*, *a dataset root resolves to the directory its `repo_id` recorded to* and *a seeded replay reads the forwarded seed*, and #3343 arrived as the largest head on the board since #2848 closed. Markers, in order: 09-06 @ #3254 (+1,011); 09-07 @ #3284 (+1,037); 09-08 @ #3319 (+1,072); 09-09 @ #3351 (+1,099). Every number those entries named, kept as a roster: #3320 #3321 #3327 #3340 #858 #1930 #2720 #2853 #2907 #3007 #3054 #3136 #3169 #3170 #3171 #3183 #3188 #3194 #3196 #3197 #3202 #3204 #3205 #3206 #3212 #3215 #3217 #3219 #3221 #3222 #3223 #3224 #3226 #3227 #3229 #3230 #3231 #3232 #3233 #3234 #3235 #3236 #3237 #3238 #3239 #3241 #3242 #3243 #3244 #3245 #3247 #3248 #3249 #3250 #3251 #3252 #3253 #3254 #3255 #3256 #3257 #3258 #3259 #3260 #3261 #3262 #3263 #3264 #3265 #3266 #3267 #3268 #3269 #3270 #3271 #3272 #3273 #3274 #3275 #3276 #3277 #3278 #3279 #3280 #3281 #3282 #3283 #3284 #3285 #3286 #3287 #3288 #3289 #3290 #3291 #3292 #3293 #3294 #3295 #3296 #3297 #3298 #3299 #3300 #3301 #3302 #3303 #3304 #3305 #3306 #3307 #3308 #3309 #3310 #3311 #3312 #3313 #3314 #3315 #3316 #3317 #3318 #3319. **2026-09-10 -> 2026-09-14 (five daily reconciliations, condensed 2026-09-11 to 2026-09-15, rostered 2026-09-16).** The five runs that took the wave from **+1,144 to +1,339 PRs** while `v0.5.1` stayed the latest tag. **09-10** is where the removal direction first reached shipped public surface behind a written ruling -- two `refactor!` commits, **#3389** dropping the `vera` provider (132 files, +424 / -15,204; `uv.lock` 258 -> 237 packages, extras 34 -> 33) and **#3386** dropping `lerobot_calibrate` (-2,313, 859 LOC that never calibrated anything), licensed by **#3075** closing `NOT_PLANNED` with the direction in words, "GR00T stays" -- and where the tracker drained **16 -> 4** in one 55-minute window: seven `NOT_PLANNED`, five `COMPLETED` with no closer, exactly one with a closer (#2796 via merged #3395), closing **#2720 by the pattern it was filed about** and **#858**, the last unscheduled epic, one second later. It also opened *a telemetry field the frame does not carry is not a reading* and *a removed surface takes its own history with it*. **09-11** was the largest band since 08-31 (80 PRs) and the one where **the removals widened while the marking stopped** -- `motionbricks` (#3419), the augmentation-transforms package (#3426, 1,943 lines with no importer) and the MolmoAct2 normalization port (#3411) all left `main` with no `!` and no `BREAKING CHANGE` trailer, so the tag's removals section must be assembled by hand -- while the docs and the test tree lost 6,433 lines saying each thing once (**#3417** replacing fourteen per-package copies of one AST walk, **#3469** taking README to 120 lines) and, for the first time in the wave, **every tracker closure reconciled against merged code** (#3398 via #3407, #3146 via #3480). It opened *an operator is asked before a command reaches a real actuator*. **09-12** was the first band here that **removed nothing** (+10,234 / -483) and refused *a value that can never be honored at the entry point* at thirteen sites, **#3502** the sharpest -- `success_rate: 0.0` published beside `success_measured: true`. **09-13** finished the posture-flag lane across nine surfaces including every policy provider, and took **the hardest clean-sweep break recorded**: five PRs closed unmerged, each a second implementation of a fix that merged, four inside 105 minutes. **09-14** was featureless and refactor-free, refusing *a run that commanded nothing* (**#3620**, `success_rate` under `status="success"` for a rollout in which `send_action` was never called) and *a shutdown that cannot complete* (**#3596** the test half). Every pull request they enumerated is tabled in full under `## Post-v0.5.1` above, and the lanes they opened each have a section there. Markers, in order: 09-10 @ #3406 (+1,144); 09-11 @ #3488 (+1,224); 09-12 @ #3533 (+1,264); 09-13 @ #3583 (+1,304); 09-14 @ #3620 (+1,339). Every number those entries named, kept as a roster: #858 #1048 #1690 #1987 #2720 #2753 #2791 #2796 #2801 #2812 #2853 #2891 #2907 #2928 #2977 #3075 #3099 #3122 #3126 #3143 #3146 #3169 #3197 #3205 #3212 #3213 #3296 #3298 #3343 #3349 #3352 #3353 #3355 #3356 #3359 #3360 #3361 #3362 #3364 #3365 #3367 #3368 #3369 #3370 #3371 #3373 #3374 #3375 #3376 #3378 #3379 #3380 #3381 #3382 #3383 #3386 #3387 #3389 #3390 #3392 #3393 #3394 #3395 #3396 #3397 #3398 #3399 #3400 #3401 #3403 #3404 #3405 #3406 #3407 #3408 #3409 #3410 #3411 #3412 #3414 #3417 #3418 #3419 #3420 #3423 #3426 #3427 #3429 #3430 #3431 #3432 #3433 #3434 #3435 #3436 #3438 #3439 #3440 #3441 #3442 #3443 #3444 #3445 #3446 #3449 #3450 #3451 #3452 #3453 #3455 #3457 #3459 #3460 #3461 #3462 #3463 #3464 #3465 #3466 #3467 #3468 #3469 #3470 #3471 #3472 #3473 #3475 #3476 #3478 #3479 #3480 #3482 #3483 #3485 #3488 #3489 #3490 #3491 #3492 #3493 #3494 #3495 #3496 #3497 #3498 #3499 #3500 #3501 #3502 #3503 #3504 #3505 #3506 #3507 #3508 #3509 #3510 #3511 #3512 #3513 #3514 #3515 #3516 #3517 #3518 #3519 #3520 #3521 #3522 #3523 #3524 #3525 #3526 #3527 #3528 #3529 #3530 #3531 #3532 #3533 #3534 #3535 #3536 #3537 #3538 #3539 #3540 #3541 #3542 #3543 #3544 #3545 #3546 #3547 #3548 #3549 #3550 #3551 #3552 #3553 #3554 #3555 #3556 #3557 #3558 #3559 #3560 #3561 #3562 #3563 #3564 #3565 #3566 #3567 #3568 #3569 #3570 #3571 #3572 #3573 #3574 #3575 #3576 #3577 #3578 #3579 #3580 #3581 #3582 #3583 #3584 #3585 #3586 #3587 #3588 #3589 #3590 #3591 #3592 #3593 #3594 #3595 #3596 #3597 #3598 #3599 #3600 #3601 #3602 #3603 #3604 #3605 #3606 #3607 #3608 #3609 #3610 #3611 #3612 #3613 #3614 #3615 #3616 #3617 #3618 #3619 #3620 #3621. | Date | Marker | Wave | What that day folded in | |------|--------|------|-------------------------| | 09-15 | @ #3660 | +1,378 | **39 PRs** in a clean contiguous run #3621 -> #3660 with **no out-of-order merge**; 1,339 + 39 = 1,378 exactly, tip commit the highest merged number again after a one-band break. **Featureless, the ninth here, and refactor-free for a second, so net-positive for a fourth**: 27 `fix` + 11 `test` + 1 `docs`, **125 files, +8,791 / -303**; `fix` 896 -> 923, `test` 143 -> 154, `docs` 108 -> 109, `feat` held at 144; correctness 77.6% -> **78.2%**. Refactor table held at thirteen, removals ledger untouched for a fourth band, no `!` or `BREAKING CHANGE` trailer -- correct rather than a gap. **Eleven test pins, the most any band here has carried** (#3621, #3626, #3632, #3634, #3641, #3644, #3646, #3647, #3656, #3659, #3660), each grading a door an existing suite reached only through a stand-in. Dominant fix shape new in kind and the third reading of one question -- *a criterion already satisfied at reset is reported, not read as a result*: **#3629** the widest in consequence (`measure_agreement`'s fractions are accuracies over a class-imbalanced column, so a constant answerer scored the holdout's own class balance), **#3637** and **#3642** the mirror pair (the same defect reading as an achievement on a `stop_when` clause and as a policy failure on a benchmark `failure` clause, both on step 1 with nothing moved), with #3633 and #3639. Four more lanes: *a plan that commands nothing is a failure, not a plan* (#3643, #3652, #3655, **#3657**, whose joint-key list was resolved once from the first waypoint); *a ROS 2 name must be able to name a topic, and a refused call leaves no node behind* (#3628, #3635, #3636, #3638, #3648, #3651); *a reader grades the size its own payload declares* (#3627, #3650, **#3658**, an `.spz` count sizing five body blocks graded nowhere while the magic beside it was graded on the next line); *a refusal applies nothing from its own call* (#3622, #3625, **#3645**). Rows also to the offline-snapshot (#3623) and one-owner (#3624) tables; the rest #3630, #3631, #3649, #3653, #3654. Milestone `0.5.0` unchanged at 11/0, eighteenth sync. **Open issues held at 3** (#3099, #3122, #3578) and **for the first time in the wave no issue was filed *or* closed**. **Open PRs 9 -> 11** (#3621 merged; #3640, #3661, #3662 opened), the extra-approval remedy in routine use with #3661 and #3662 both `APPROVED`, while #3205, #3343, #3427, #3496, #3515, #3577 and #3615 stayed green, armed, thread-resolved and `REVIEW_REQUIRED`; #3615 cleared its `CHANGES_REQUESTED`, #3640 took its place beside #3400, and #3343 was unchanged for a sixth sync. Fragments **1,530** (+28, none deleted -- the eleven `test` commits owe none), `--check` clean, CHANGELOG untouched 49 days (#1690), `0000-` placeholders at four with sequencing item 6 unactioned for a third sync. **The cleanest sweep the page had recorded**: exactly one number in range absent from `main` (the open #3640), none closed unmerged, so #3169's shape took no sixteenth instance for a second band. One correction to the previous marker: it read 1,501 fragments where the tree held **1,502**. Older caveats stood (`cff130dc` carries no PR reference; #2801's squash artifact) | **2026-09-16:** Reconciled against `main` @ **#3750**. Still no new tag -- `v0.5.1` (2026-08-06) is 41 days old, now with **1,451 PRs** (#1987 -> #3750) behind it. Folded in **73 PRs** spanning #3661 -> #3750, with **two out-of-order merges** (#3661, #3662, both rows on the previous Open-PRs table): 1,378 + 73 = 1,451 reconciles exactly. Band shape **58 `fix` + 7 `feat` + 4 `test` + 3 `docs` + 1 `refactor`**, so `fix` moved 923 -> **981**, `feat` 144 -> **151**, `test` 154 -> **158**, `docs` 109 -> **112** and `refactor` 24 -> **25** with `ci` held at 17; correctness 78.2% -> **78.5%**, a fourteenth sync, because the fix count grew faster than the feature count even in the band that broke the streak. **The nine-band featureless run is over, and it ended on volume**: **277 files, +18,945 / -682 -- net +18,263**, the largest net-positive band this page has recorded, nearly double the previous high. The one `refactor` is `examples`-scoped (#3709), so the net-negative refactor table holds at thirteen and the removals ledger is untouched for a fifth band; no `!` or `BREAKING CHANGE` trailer, correct rather than a gap. Drivers hold at twelve buses across 25 registry robot names and `tools/g1` at 22, while **`dashboard/` moves eleven -> fifteen**. **The seven features are the news, and #3695 is the one that matters most: the dashboard has a server** (19 files, +1,609 / -74), the core wired to the rails nine bands had accumulated and **failing closed** -- the slice the #2848 decomposition argument was for, with #3726 and #3739 already queued behind it. The other six say more about what the library just handed you: #3728 (`tool_frame`), #3727 (which way the arm extends), #3694 (SO-arm joints by label), #3724 (`step` feeds an open recording), #3731 (`Robot(tool_name=)`), #3697 (`render_dir` per `Simulation`). **The dominant fix shape is new in kind: an unreadable reply names the peer, not the codec** (#3670, #3672, #3676, #3677) -- four clients decoded a peer's response inside a `try` reporting a *codec* failure, so a malformed reply from the far end sent the caller to the wrong side of the wire. **Three more lanes**, each tabled above: *a configuration entry that is not a string is refused* (#3666 closing **#3664**, #3667, #3674, #3679 -- #3666 is the consequential one, a DDS Security credential the participant QoS then dropped, so a run believed it was authenticated); *a motion that did not reach names what stopped it* (#3703, #3705, #3727, **#3733**); *a camera name answers for the camera it names* (#3683, #3691, #3693, #3730). Rows also to the commanded-nothing lane at its entry point (#3680), a rollout made exclusive (#3741), and a surface offering a next step the thing it just made can take (#3686 closing **#3684**, #3687, #3719, #3742); the dashboard's own pass is #3710 and #3738; four test pins #3662, #3669, #3675, #3745. **Milestone `0.5.0` did not move**: 11 closed / 0 open (100%), nineteenth sync; `v0.4.0` and `v0.3.9` still OPEN and drained. **Open issues 3 -> 5.** Two were filed and closed in band against the commit that answered them (#3664 via #3666, #3684 via #3686), a **fifth clean pass**, and two remain open -- **#3734** (the `rotate_wrist` half #3733 deliberately deferred) and **#3744** (`policies` / P2, the offline-snapshot lane read as a *range*: the relative-action roster cannot hold across the pinned lerobot range, made visible by #3698 documenting it one commit earlier). **Open PRs 11 -> 21, the largest queue recorded, up ten in a day** -- and the constraint has moved from the ruleset to the volume: **seven heads read `APPROVED`** (#3729, #3743, #3746, #3749, #3751, #3752, #3753) where two did a day ago, while **ten** are green, armed, thread-resolved and still `REVIEW_REQUIRED` (#3205, #3343, #3427, #3496, #3515, #3577, #3615, #3714, #3726, #3737). Three are owed by their author (#3400, #3640, #3673 -- the last **red**, the only failing head on the board) and #3739 is owed a review decision. Sequencing item 3 (**#3343**) is unchanged for a **seventh** sync; #3205 is now the oldest head at eleven days. **Sequencing item 6 is retired**: the `0000-` fragment placeholder defect is fixed (#3715 refuses the number, #3721 gives that rule one owner so every surface refuses it, #3722 renames the fragment that named no PR), so `changelog.d/` holds **zero** placeholders where it held four for three syncs. Release hygiene is otherwise unchanged and still the only standing gap: no `## [0.5.0]` or `## [0.5.1]` heading (`CHANGELOG.md` untouched since #1690, now **50 days**), `[Unreleased]` frozen at 168, `changelog.d/` at **1,601** `.md` of which **1,600** are fragments, `--check` `OK (1600 pending)`; the band added **70** and deleted none -- 73 PRs produced 70 because the four `test` commits owe none and one PR carried two. Caveats -- **the counting streak broke again, in the direction that under-reports**: the tip is **#3740** while **#3750** is the highest merged number, so a marker read off `git log`'s newest subject is ten short. **The clean sweep broke after two bands, and on the duplicate shape**: seventeen numbers in #3661 -> #3750 are absent from `main` -- nine open PRs, two open issues, two issues closed in band, and **four PRs closed unmerged, every one a second implementation** (#3665 against #3666, #3671 against #3672, #3736 against #3731, #3732 descoped into #3733 plus #3734) -- instances **sixteen to nineteen** of #3169's shape after two bands at zero, which is why sequencing item 7 stands. One stray: #3702's body carries `Closes #767`, a pull request merged in the v0.4.1 wave rather than an issue, so it closed nothing. Older caveats stand (`cff130dc` carries no PR reference, so `git log` shows **1,452** commits for 1,451 PRs, measured exactly at this marker; #2801's squash artifact makes a naive `(#NNNN)` grep return 1,450). **On size**: 1.2 KB of headroom was nowhere near enough for the largest band on record, so three condensations were taken, each verified by cross-reference conservation *before* the edit -- the **2026-09-10 -> 2026-09-14** log rows **rostered** into one paragraph (the reduction the previous sync named as next, -18.3 KB), the **2026-09-15** entry folded into the log table, and its band bullets into three Finding/References rows. **0 of 1,833 cross-references lost. The next pass**: the 09-15 and 09-16 log rows take the same treatment, after which the only reductions left are the v0.4.1-era theme tables, which are the historical record this page exists to hold.