-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathpyproject.toml
More file actions
724 lines (693 loc) · 35.8 KB
/
Copy pathpyproject.toml
File metadata and controls
724 lines (693 loc) · 35.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[project]
name = "strands-robots"
dynamic = ["version"]
description = "AI-powered robot control, simulation, and training for Strands Agents - integrates with MuJoCo, Isaac Sim, Newton, and many more"
readme = "README.md"
requires-python = ">=3.12"
license = {text = "Apache-2.0"}
authors = [
{name = "AWS", email = "opensource@amazon.com"},
]
keywords = [
"robots", "agents", "strands", "sdk",
"mujoco", "simulation", "lerobot", "reinforcement-learning",
"gr00t", "isaac-sim", "vla",
"robot-learning", "imitation-learning",
"teleoperation", "manipulation", "strands-agents",
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Physics",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"strands-agents>=1.0.0,<2.0.0",
"numpy>=1.21.0,<3.0.0",
"opencv-python-headless>=4.5.0,<5.0.0",
"Pillow>=8.0.0,<13.0.0",
]
[project.optional-dependencies]
groot-service = [
"pyzmq>=27.0.0,<28.0.0",
"msgpack>=1.0.0,<2.0.0",
]
cosmos3-service = [
# Self-contained msgpack+NumPy WebSocket client for the Cosmos 3 RoboLab
# policy server (cosmos_framework.scripts.action_policy_server_robolab).
#
# No openpi-client dependency: the wire protocol is implemented directly
# via `websockets` + `msgpack` plus a vendored numpy packer
# (strands_robots/policies/cosmos3/_msgpack_numpy.py). This keeps the
# extra numpy-version agnostic so it composes cleanly with `lerobot`
# (numpy>=2) for dataset recording in the same env.
"msgpack>=1.0.0,<2.0.0",
"websockets>=12.0",
]
# In-process Cosmos 3 backend (Cosmos3Policy(backend="diffusers")). Loads the
# Cosmos3OmniPipeline directly via native Hugging Face `diffusers` (no RoboLab
# policy server, no GPU isolation) - one forward pass returns the predicted
# world video + sound + action chunk. This is a HEAVY GPU stack (diffusers +
# torch + transformers); the default backend stays "service", so a plain
# `pip install strands-robots[cosmos3-service]` (msgpack + websockets only) is
# unaffected. diffusers composes with numpy>=2, so this extra is co-installable
# with cosmos3-service and lerobot.
#
# The floor is the CAPABILITY floor, not a nominal lower bound: measured against
# the released wheels, Cosmos3OmniPipeline + CosmosActionCondition first ship in
# diffusers 0.39.0 (0.36.0/0.37.1/0.38.0 carry neither symbol), so any floor below
# it resolves to a diffusers that cannot build the pipeline at all. A checkpoint
# newer than the floor can still need a newer diffusers than it (Cosmos3-Edge is
# built against 0.40.0.dev0); that is a per-checkpoint fact a version range cannot
# express, so Cosmos3DiffusersBackend reports it at load time instead.
cosmos3-diffusers = [
"diffusers>=0.39",
"torch>=2.0",
"transformers>=4.40",
"accelerate>=0.26",
]
# Cosmos 3 -> MuJoCo sim-loop bridge. The diffusers backend emits the model's
# raw unified action ([-1, 1] quantile-normalized relative end-effector pose,
# not joint radians); turning that into joint targets a MuJoCo arm can track
# needs de-normalization (bundled per-domain q01/q99 stats, pure NumPy) plus an
# inverse-kinematics solve. This extra pulls `mink` (differential IK on the same
# mujoco.MjModel) + `mujoco`. Install it to drive a sim arm with
# Cosmos3Policy(backend="diffusers") via decode_cosmos_chunk_to_targets /
# MinkIKBridge. numpy>=2 compatible (co-installable with cosmos3-diffusers,
# cosmos3-service, sim-mujoco, and lerobot).
cosmos3-sim = [
"mink>=0.0.4",
"mujoco>=3.2.0,<4.0.0",
# qpsolvers QP backend for mink.solve_ik. quadprog is the dependency-light
# default that is always available; the bridge auto-selects whatever
# qpsolvers reports (preferring daqp when present).
"qpsolvers[quadprog]>=4.0.0",
]
# MoveIt2 sidecar client (ROS 2 / moveit_py runs out-of-process; the
# client is a lightweight ZMQ + msgpack peer so users without ROS 2
# sourced are unaffected). See policies/moveit2/server/ for the
# sidecar reference implementation.
moveit2 = [
"pyzmq>=27.0.0,<28.0.0",
"msgpack>=1.0.0,<2.0.0",
]
# cuRobo collision-aware motion planning. Runs in-process as a CUDA
# library (no sidecar, unlike MoveIt2). Users without a CUDA-capable GPU
# should not install this extra. See policies/curobo/ for the policy
# wrapper.
#
# IMPORTANT: cuRobo is NOT distributed on PyPI. The package named
# ``nvidia-curobo`` on PyPI is an unrelated v0.1 squatter — installing
# it does not get you a working cuRobo. The real cuRobo is published
# only as source on GitHub at https://github.com/NVlabs/curobo. Users
# must install it from source before using this policy:
#
# git clone https://github.com/NVlabs/curobo.git
# pip install -e ./curobo
#
# Once cuRobo's API surface stabilises and a real PyPI release exists,
# this extra will pin to it. Until then, keep the extra empty so
# ``pip install strands-robots[curobo]`` is a no-op rather than a
# silently-broken install of the squatter package. Tracked in the
# follow-up issue linked from PR #306.
curobo = []
# GR00T Whole-Body-Control (SONIC) locomotion policy. Runs the ONNX
# controllers in-process via onnxruntime (no torch, no sidecar) - a light
# extra, like the client side of moveit2. Model weights are NOT bundled;
# they are fetched at runtime under the NVIDIA Open Model License
# (e.g. nvidia/GEAR-SONIC). See policies/wbc/ and issue #466.
wbc = [
"onnxruntime>=1.17.0,<2.0.0",
# The upstream GR00T-WBC checkpoint config (g1_gear_wbc.yaml) is YAML;
# WBCConfig.from_file reads it via pyyaml. JSON configs work without it.
"pyyaml>=5.1,<7.0.0",
# Resolving a HuggingFace model id checkpoint (e.g. the default
# nvidia/GEAR-SONIC, or any "org/repo") downloads it via huggingface_hub.
# Required so create_policy("wbc") with no local checkpoint works out of the
# box - WBCPolicy._maybe_download_checkpoint raises RuntimeError naming the
# [wbc] extra when it is absent.
# Floor at >=1.5 (cap the MAJOR only, <2.0.0): the docs' bucket-sync
# guidance (README streamed-training section + dataset_recorder.sync_to_bucket)
# instructs `pip install -U "huggingface_hub>=1.5"` because the `hf buckets`/
# `hf sync` CLI subcommands first ship in huggingface_hub 1.5.0
# (huggingface_hub/cli/buckets.py, registered by cli/hf.py). Every earlier
# release installs an `hf` entry point WITHOUT them - 0.36.x, but also
# 1.0-1.4.x, which answer `hf buckets create` with
# "Error: No such command 'buckets'". A floor below 1.5 therefore lets a
# fresh resolve land on a CLI that cannot run a bucket sync while satisfying
# the documented minimum. lerobot floors it higher still
# (lerobot 0.6.1 requires huggingface-hub>=1.6.0) and [wbc] is pulled into
# [all] alongside lerobot, so the two co-resolve on the stricter of the two.
"huggingface_hub>=1.5,<2.0.0",
]
motionbricks = [
# MotionBricks (NVlabs/GR00T-WholeBodyControl `motionbricks/`) generative
# kinematic motion for the G1. The `motionbricks` package itself is NOT on
# PyPI - install it editable from the upstream checkout AFTER this extra:
# git clone https://github.com/NVlabs/GR00T-WholeBodyControl
# git -C GR00T-WholeBodyControl lfs pull --include="motionbricks/out/**" --exclude=""
# pip install -e GR00T-WholeBodyControl/motionbricks
# This extra pulls the PyPI support libraries its model code imports so the
# editable install resolves cleanly. Model weights are NOT bundled; they are
# fetched with git-LFS under the NVIDIA Open Model License. See
# policies/motionbricks/ and docs/policies/motionbricks.md.
"torch>=2.0.0",
"vector-quantize-pytorch>=1.14.0",
"adam-atan2-pytorch>=0.0.12",
"hydra-core>=1.3.0,<2.0.0",
"omegaconf>=2.3.0,<3.0.0",
"pytorch-lightning>=2.0.0,<3.0.0",
"scipy>=1.10.0",
]
lerobot = [
# [dataset] bundles the read-back stack the streaming data loop needs:
# datasets + pandas + pyarrow (parquet streaming) + av + torchcodec
# (remote video decode for StreamingLeRobotDataset / StreamingDatasetReader).
# Declaring it here makes `sim.stream_dataset(...)` work out of the box
# instead of pushing torchcodec/av plumbing onto user code. See
# reports/STREAMING_DATA_LOOP_DEEP_DIVE.md Appendix C.
#
# lerobot 0.6 ships mature, platform-correct torch/torchvision/torchcodec
# dependency markers, so the codec/decoder stack resolves ABI-consistently
# on every platform (torch 2.11 + torchcodec 0.11.x + torchvision 0.26 on
# linux x86_64/aarch64 and macOS arm64) WITHOUT the per-platform torchcodec
# overrides the 0.5.1 range needed. The aarch64 torch>=2.11 requirement --
# which fixes the NVIDIA Thor/Jetson sm_110 cuBLAS bug present in torch 2.10
# -- now falls out of lerobot 0.6's own `torchcodec>=0.11,<0.12; aarch64`
# marker. NOTE: torch/torchcodec ship as +cuXXX wheels resolved by
# UV_TORCH_BACKEND; a plain `pip install` on Thor may need the matching CUDA
# index -- see the README Installation section for the Thor/Jetson caveat.
#
# The floor is 0.6.1 rather than 0.6.0 because bucket streaming
# (`stream_dataset(repo_type="bucket")`) needs a StreamingLeRobotDataset
# that accepts `repo_type`, and 0.6.1 is the first release that does
# (0.6.0's constructor has no such parameter). Flooring here makes the
# flagship streaming path resolver-guaranteed instead of docs-guaranteed;
# `strands_robots.streaming_dataset.BUCKET_STREAMING_MIN_LEROBOT` carries
# the same version for the runtime guard that protects an environment with
# a pre-existing older lerobot.
"lerobot[feetech,dataset]>=0.6.1,<0.7.0",
]
# Remote/offloaded inference via lerobot's native async-inference gRPC
# transport (the `lerobot_async` policy provider, LerobotAsyncPolicy). Layers
# grpcio on top of the base lerobot stack so a light robot host can drive a
# policy running on a separate GPU server (lerobot.async_inference.policy_server).
# Mirrors lerobot's own [async] extra dependency (grpcio) rather than pinning
# it here, so the gRPC/protobuf floor tracks lerobot.
lerobot-async = [
"strands-robots[lerobot]",
"lerobot[async]>=0.6.1,<0.7.0",
]
# MolmoAct2 transformers-native VLA (e.g. allenai/MolmoAct2-SO100_101). The
# MolmoAct2Policy ships in lerobot >= 0.6 (landed via lerobot PR #3604). Rather
# than hand-mirror lerobot's aux-dep pins here (transformers/peft/scipy) -- which
# silently drifts whenever lerobot bumps them -- we defer entirely to lerobot's
# own ``[molmoact2]`` extra. That extra pulls lerobot[transformers-dep],
# lerobot[peft-dep], lerobot[scipy-dep] at exactly the versions the MolmoAct2
# modeling/processor code was built against, so the two stay in lock-step by
# construction. The base ``strands-robots[lerobot]`` pin (>=0.6.1,<0.7.0) already
# fixes the lerobot floor; ``lerobot[molmoact2]`` just adds the aux deps.
molmoact2 = [
"strands-robots[lerobot]",
"lerobot[molmoact2]>=0.6.1,<0.7.0",
]
sim = [
"robot_descriptions>=1.11.0,<2.0.0",
]
sim-mujoco = [
"strands-robots[sim]",
"mujoco>=3.2.0,<4.0.0",
"imageio>=2.28.0,<3.0.0",
"imageio-ffmpeg>=0.4.0,<1.0.0",
# Differential-IK solver stack. `move_to`, the Cartesian transport
# primitive in this backend's agent-callable action enum, solves IK on the
# same mujoco.MjModel via strands_robots.simulation.ik.MinkIKBridge, so the
# solver is part of what this extra ships rather than an add-on the caller
# has to name themselves. `qpsolvers` is declared here because
# simulation/ik.py imports it directly, and the `[daqp]` backend extra
# because qpsolvers on its own solves nothing: with no backend installed
# `mink.solve_ik` cannot run and `move_to` returns "IK bridge unavailable:
# No qpsolvers backend is installed", whose remedy names THIS extra. daqp is
# both the bridge's first preference (ik._PREFERRED_QP_SOLVERS) and the
# backend `mink` pulls for itself, so this changes nothing about what
# resolves - it stops the guarantee resting on a transitive of `mink` that
# this extra does not control. `[cosmos3-sim]`, which drives the same
# mink + qpsolvers stack, declares its backend the same way.
"mink>=0.0.4",
"qpsolvers[daqp]>=4.0.0",
]
# Newton GPU-native backend (newton-physics/newton on NVIDIA Warp +
# MuJoCo-Warp). GPU-batched parallel envs, headless ray-traced rendering, and
# the same MJCF assets as the MuJoCo backend. Requires an NVIDIA GPU (Maxwell+,
# driver 545+, CUDA 12); on CPU-only hosts Warp falls back to the CPU device.
# trimesh loads the collision/visual meshes referenced by the MJCF assets;
# mujoco-warp provides the GPU MuJoCo solver used by the default `solver`.
sim-newton = [
"strands-robots[sim-mujoco]",
"newton>=1.3.0,<2.0.0",
"warp-lang>=1.14.0,<2.0.0",
"mujoco-warp>=3.8.0",
"trimesh>=4.0.0,<5.0.0",
]
# Isaac Sim GPU-native backend (NVIDIA Isaac Sim / Omniverse). Selected via
# create_simulation("isaac") / Robot(..., backend="isaac"); the vendored
# in-tree backend lives at strands_robots.simulation.isaac (parent epic #1144).
# Provides photorealistic RTX rendering, synthetic-data generation, and
# GPU-batched sensor simulation. GPU/heavy - kept OUT of `[all]` (like
# sim-newton's spirit but even more so: Isaac Sim needs an RTX GPU + an
# out-of-band Kit runtime).
#
# IMPORTANT: Isaac Sim itself is NOT installed by this extra. It is an Omniverse
# Kit application provisioned separately - via the runtime's own pip wheels
# (`pip install 'isaacsim[all,extscache]==6.0.*' --extra-index-url
# https://pypi.nvidia.com`, viable on Python 3.12; the `extscache` extra is
# required, and see docs/simulation/isaac.md for the coverage-downgrade /
# EULA / torch-bump caveats, #1803), the NVIDIA Omniverse Launcher,
# Isaac Lab (`./isaaclab.sh -i`), or the NGC docker image
# (`nvcr.io/nvidia/isaac-sim:6.0`; see
# strands_robots/simulation/isaac/_install.py for the pinned image tag). Those
# routes ship a complete, bootable Kit (Python 3.12 interpreter + the
# full `isaacsim-*` / `isaacsim-extscache-*` extension set that `SimulationApp`
# needs to boot). We still do NOT pin `isaacsim` here: the bare `isaacsim[all]`
# metapackage is incomplete on its own (it omits the `isaacsim-extscache-*`
# packages, so `SimulationApp` aborts resolving its extension graph), it needs
# the NVIDIA extra index, and its transitive pins (coverage==7.4.4, a
# torch/torchvision bump) would degrade every non-Isaac install of this extra.
#
# This extra pulls ONLY the pip-installable Python helpers the backend uses
# out-of-band from an Isaac Sim install:
# * `usd-core` - pure-Python USD runtime used by the procedural scene
# builders / loaders (loaders.load_usd) even when Kit isn't booted. usd-core
# uses CalVer (25.x/26.x); per AGENTS.md `>=1.0` deps cap the major.
# * `imageio` / `imageio-ffmpeg` - rollout-video encoding.
# All heavy omni/isaacsim imports stay lazy - installing this extra never
# imports Isaac Sim.
sim-isaac = [
"strands-robots[sim]",
"usd-core>=25.5,<27.0.0",
"imageio>=2.28.0,<3.0.0",
"imageio-ffmpeg>=0.4.0,<1.0.0",
]
# 3D Gaussian Splatting hybrid rendering (strands_robots.rendering:
# GsplatBackground + HybridCompositor). GPU/heavy - kept OUT of `[all]`
# (sim-isaac precedent).
#
# IMPORTANT: `gsplat` ships to PyPI as a source distribution that JIT-compiles
# its CUDA kernels with `nvcc` on FIRST USE (same supply-chain class as cuRobo
# above). On a box with a CUDA *runtime* but no CUDA *toolkit* - e.g. the
# Isaac Sim container - `pip install gsplat` succeeds and imports fine, yet the
# first rasterization call fails ("No CUDA toolkit found"). Call
# `strands_robots.rendering.gsplat_rasterizer_available()` to probe an install
# before relying on it, or install a pre-built wheel from the gsplat wheel
# index (ships compiled kernels, no nvcc needed):
# https://docs.gsplat.studio/main/installation.html
# `PanoramaBackground` is the zero-GPU-dep fallback; torch is pinned without a
# CUDA constraint (the probe fails loud on CPU-only builds instead of dying
# deep inside the first frame).
sim-gs = [
"gsplat>=1.4.0,<2.0.0",
"plyfile>=1.0.0,<2.0.0",
"torch>=2.2.0,<3.0.0",
]
benchmark-libero = [
"libero>=0.1.0,<1.0.0",
]
mesh = [
"eclipse-zenoh>=1.0.0,<2.0.0",
"json5>=0.9.0,<1.0.0",
]
# AWS IoT Core integration for the mesh - MQTT5/mTLS transport, Device
# Shadow mirror, S3 camera offload, account-wide bootstrap (Rules /
# Lambda / DynamoDB / Fleet Provisioning template). Requires:
# * awsiotsdk (high-level MQTT5 + mTLS client builder)
# * awscrt (low-level CRT runtime; pulled in by awsiotsdk but pinned
# here for transitive predictability)
# * boto3 (used for IoT control-plane provisioning + S3 frame upload)
mesh-iot = [
"strands-robots[mesh]",
"awsiotsdk>=1.21.0,<2.0.0",
"awscrt>=0.20.0,<1.0.0",
"boto3>=1.34.0,<2.0.0",
]
# Device Connect - device-aware networking layer (discovery, RPC, events,
# safety). The primary transport in server mode; when this extra is not
# installed, robot_mesh() falls back to the built-in Zenoh mesh.
device-connect = [
"device-connect-edge>=0.2.0",
"device-connect-agent-tools>=0.1.0",
]
# ROS 2 bridge for the use_ros tool (strands_robots/tools/use_ros.py). The tool
# runs entirely in-process through rclpy - no `ros2` CLI shelling, no docker. It
# needs `rclpy` + `rosidl_runtime_py` importable in the interpreter, which means
# a sourced system ROS 2 distro (e.g. `source /opt/ros/jazzy/setup.bash`).
# Those core client libraries are NOT distributed on PyPI - they ship with the
# system ROS 2 install (apt / RoboStack / conda) and cannot be `pip install`ed.
# So, like the [curobo] extra, this stays minimal: it pulls only the
# pip-installable cyclonedds Python binding (a DDS RMW rclpy can use). Installing
# this extra does NOT by itself provision ROS 2 - source a real distro first.
ros2 = [
"cyclonedds>=0.10.2,<1.0.0",
]
# NOTE: There is intentionally no `vera` extra. VERA is only distributed as a
# git repository (https://github.com/sizhe-li/VERA) and PyPI rejects any package
# whose metadata contains a direct URL/VCS reference (HTTP 400 on upload). The
# VERA provider launches the git-installed `vera` package as a subprocess
# (`python -m vera.server...`), so install it directly alongside strands-robots:
# pip install "strands-robots[vera-sim]" websockets msgpack "numpy>=1.24"
# pip install "vera @ git+https://github.com/sizhe-li/VERA.git"
# See README (VERA row) and strands_robots/policies/vera/__init__.py.
vera-sim = [
"gymnasium==0.29.1",
"gym-pusht==0.1.5",
"robosuite==1.4.1",
# robomimic v0.5.0 is NOT published to PyPI -- the highest PyPI release is
# 0.3.0, and v0.5.0 exists only as an ARISE-Initiative GitHub tag. Pinning
# `robomimic==0.5.0` here is therefore both unresolvable (uv lock fails) and
# a dependency-confusion risk (anyone publishing 0.5.0 to PyPI would be
# installed). robomimic is not imported anywhere in the source; VERA pulls
# it in itself. If you need robomimic v0.5.0, install it from source:
# pip install "robomimic @ git+https://github.com/ARISE-Initiative/robomimic.git@v0.5.0"
# MimicGen (NVlabs) is NOT published to PyPI -- the `mimicgen` name on PyPI
# is an unaffiliated third-party package, so pinning it here is a
# dependency-confusion risk and it is not imported anywhere in the source.
# The `mimicgen` VERA *embodiment* is just a config string and does not need
# the package. If you genuinely need NVlabs MimicGen for data generation,
# install it from source:
# pip install "mimicgen @ git+https://github.com/NVlabs/mimicgen.git"
"mujoco>=3.5.0",
"imageio",
]
# Local-LLM agent path (no API key / no AWS -- the natural choice on an edge
# device): strands.models.ollama.OllamaModel does a top-level `import ollama`.
# Delegate to strands-agents' own [ollama] extra (bounded pin, ollama>=0.4.8,<1.0.0)
# so the client version stays in sync with the code that imports it.
ollama = [
"strands-agents[ollama]>=1.0.0,<2.0.0",
]
# Remote policy inference (client/server split): stream observations from a
# CPU/edge robot host to a remote GPU PolicyServer and receive action chunks
# back over a portable WS-JSON WebSocket protocol. Pulls only `websockets`
# (numpy-version agnostic, so it composes with lerobot's numpy>=2). See
# strands_robots/inference/ and docs/inference/remote.md.
inference = [
"websockets>=12.0",
]
# rosbridge WebSocket transport: drive ROS1 (or remote ROS 2) robots from a host
# with no ROS install at all. Pure-pip `roslibpy` speaks the rosbridge JSON
# protocol over a WebSocket. See strands_robots/tools/use_rosbridge.py and
# docs/rosbridge-integration.md.
rosbridge = [
"roslibpy>=1.7.0,<2.0.0",
]
all = [
"strands-robots[groot-service]",
"strands-robots[moveit2]",
"strands-robots[lerobot]",
"strands-robots[lerobot-async]",
"strands-robots[sim-mujoco]",
"strands-robots[mesh]",
"strands-robots[mesh-iot]",
"strands-robots[device-connect]",
"strands-robots[wbc]",
"strands-robots[motionbricks]",
"strands-robots[molmoact2]",
"strands-robots[ollama]",
"strands-robots[inference]",
"strands-robots[rosbridge]",
]
dev = [
"pytest>=6.0,<10.0.0",
"pytest-cov>=4.0.0,<6.0.0",
"ruff>=0.15.12,<0.16.0",
"mypy>=1.0.0,<2.0.0",
"pytest-timeout>=2.0.0,<3.0.0",
]
[project.scripts]
strands-robots = "strands_robots.__main__:main"
[project.urls]
Homepage = "https://github.com/strands-labs/robots"
Documentation = "https://github.com/strands-labs/robots#readme"
Repository = "https://github.com/strands-labs/robots.git"
Issues = "https://github.com/strands-labs/robots/issues"
Changelog = "https://github.com/strands-labs/robots/releases"
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.targets.wheel]
packages = ["strands_robots"]
[tool.hatch.envs.default]
installer = "uv"
features = ["all"]
dependencies = [
"pytest>=6.0,<10.0.0",
"pytest-cov>=4.0.0,<6.0.0",
"pytest-timeout>=2.0.0,<3.0.0",
"ruff>=0.15.12,<0.16.0",
"mypy>=1.0.0,<2.0.0",
"Pillow>=8.0.0,<13.0.0",
"requests>=2.28.0,<3.0.0",
"msgpack>=1.0.0,<2.0.0",
"pyzmq>=27.0.0,<28.0.0",
]
# ---------------------------------------------------------------------------
# uv resolution overrides.
#
# lerobot 0.6 declares platform-correct torch/torchvision/torchcodec markers
# (torch>=2.7,<2.12; on linux aarch64 its `torchcodec>=0.11,<0.12` marker pulls
# torch 2.11 -- the build that fixes the NVIDIA Thor/Jetson sm_110 cuBLAS bug
# that afflicts torch 2.10), so the whole torch/torchvision/torchcodec stack
# resolves ABI-consistently on every platform (torch 2.11 + torchcodec 0.11.x +
# torchvision 0.26) with NO strands torch override. Paired with
# UV_TORCH_BACKEND=auto below, a Thor `hatch env` resolves the +cu130 wheels and
# inference runs on GPU. The sole remaining override is the diffusers security
# floor.
[tool.uv]
# The legacy VERA sim stack ([vera-sim]) pins gymnasium==0.29.1, which is
# mutually exclusive with lerobot>=0.6.0 (requires gymnasium>=1.1.1). uv
# resolves all extras jointly by default, so without a fork declaration this
# single pin drags the WHOLE resolution below lerobot 0.6 (previously froze the
# lock at lerobot 0.5.1, making vla_jepa/molmoact2/eo1/lerobot.rl unreachable).
# Declaring the conflict makes uv fork the resolution: install [vera-sim] OR the
# lerobot-0.6 extras, never both in one environment.
conflicts = [
[{ extra = "vera-sim" }, { extra = "lerobot" }],
[{ extra = "vera-sim" }, { extra = "lerobot-async" }],
[{ extra = "vera-sim" }, { extra = "molmoact2" }],
[{ extra = "vera-sim" }, { extra = "all" }],
[{ extra = "vera-sim" }, { extra = "benchmark-libero" }],
]
# SECURITY floors for transitive packages. Each of these arrives only through
# another dependency (cbor2/ujson under autobahn, twisted under roslibpy,
# pyopenssl under twisted), so nothing in [project] declares a version for
# them and the resolver is free to pick any release the graph allows --
# including one inside a HIGH advisory range. A constraint bounds a transitive
# without claiming it as a direct dependency, and unlike an override it fails
# the resolution loudly if something genuinely requires less, which is the
# wanted behaviour for a security floor.
#
# Each floor is the first version that clears every HIGH/CRITICAL advisory for
# that package, not the version currently resolved: the floor states the
# requirement, so it stays correct as the resolution moves.
constraint-dependencies = [
# GHSA-3c37-wwvx-h642 (HIGH): uncontrolled recursion DoS in cbor2.loads, <= 5.8.0.
"cbor2>=5.9.0",
# GHSA-grgv-6hw6-v9g4 (HIGH): twisted.names DNS compression-pointer DoS,
# <= 25.5.0. First patched in 26.4.0rc2; 26.4.0 is the first stable release
# that clears it, and a floor at the rc would admit a prerelease.
"twisted>=26.4.0",
# GHSA-c38f-wx89-p2xg (HIGH): memory leak in ujson.dump() on write failure, <= 5.12.0.
"ujson>=5.12.1",
# GHSA-5pwr-322w-8jr4 (HIGH): DTLS cookie-callback buffer overflow, >= 22.0.0 < 26.0.0.
"pyopenssl>=26.0.0",
]
override-dependencies = [
# SECURITY + CAPABILITY: diffusers <0.36 is vulnerable to a GHSA
# trust_remote_code bypass + a TOCTOU RCE (fixed in 0.38.0). uv *overrides*
# replace a requirement rather than intersecting with it, so this pin is the
# effective floor for the whole resolution and must not fall below the
# cosmos3-diffusers extra's own floor - at 0.38.0 it silently discarded that
# floor and locked a diffusers carrying no Cosmos3OmniPipeline. 0.39 clears
# both bounds (the CVE fix landed in 0.38.0; the pipeline first ships in
# 0.39.0). diffusers is only exercised by the cosmos3-diffusers extra
# (Cosmos3Policy backend="diffusers"); lerobot core does not import it.
"diffusers>=0.39",
]
# Auto-select the right PyTorch wheel for the host accelerator. On NVIDIA
# Thor / Jetson (linux + aarch64) the plain-PyPI torch wheel is CPU-ONLY, so
# torch.cuda.is_available() is False and all inference silently runs on CPU.
# "auto" makes uv probe the local driver/SoC and resolve the matching build
# (e.g. +cu130 on Thor) WITHOUT changing the pinned version.
#
# IMPORTANT: uv's "auto" detection is HOST-based, not target-platform based.
# If "auto" is hardcoded here it contaminates EVERY resolution -- including
# x86 CI -- with the build host's CUDA variant. On a Thor that means x86 CI
# resolves +cu130 wheels (proven: `uv pip compile --python-platform
# x86_64-unknown-linux-gnu` yields torchcodec==0.10.0+cu130), which cannot
# import on the GPU-less runner, so tests/conftest.py silently falls back to
# the numpy torch mock and torch-dependent tests break.
#
# So we INHERIT the value from the process env and only DEFAULT to "auto":
# - Thor/Jetson dev: nothing set -> "auto" -> GPU wheels.
# - x86 CI: workflow exports UV_TORCH_BACKEND=cpu -> CPU wheels, importable.
# A hatch pyproject env-var normally OVERRIDES the process env; the
# {env:VAR:default} form is what lets the process value win.
[tool.hatch.envs.default.env-vars]
UV_TORCH_BACKEND = "{env:UV_TORCH_BACKEND:auto}"
[tool.hatch.envs.default.scripts]
test = "pytest {args:tests}"
test-integ = "pytest tests_integ/ -v --timeout=300 {args}"
lint = ["ruff check strands_robots tests tests_integ", "ruff format --check strands_robots tests tests_integ", "mypy strands_robots tests tests_integ"]
format = ["ruff check --fix strands_robots tests tests_integ", "ruff format strands_robots tests tests_integ"]
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
# Two flake8-bugbear codes, selected individually rather than as "B".
# They carry the no-op-statement capability that CodeQL's
# py/ineffectual-statement used to nominally provide before
# .github/codeql/codeql-config.yml filtered it (#1810): every one of that
# rule's 27 alerts was a `...` typing-construct body, which ruff exempts,
# so the rule reported only unavoidable idioms while the real bug class
# went unenforced by any merge-blocking check. Ruff is that check here.
"B015", # useless comparison - `x == 1` as a statement, the real bug class
"B018", # useless expression - a value computed and discarded
]
ignore = [
"E501", # line too long - handled by formatter
]
[tool.ruff.lint.isort]
known-first-party = ["strands_robots"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
[tool.mypy]
python_version = "3.12"
# The VERA docker/ scripts run INSIDE the strands-vera-server container
# (they import vera / torchmetrics / omegaconf, present only in that image),
# so exclude them from the host-side mypy run.
exclude = "strands_robots/policies/vera/docker/"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
ignore_missing_imports = false
# Third-party libs without type stubs
[[tool.mypy.overrides]]
module = ["lerobot.*", "gr00t.*", "tyro", "draccus.*", "msgpack.*", "websockets", "websockets.*", "zmq.*", "huggingface_hub.*", "serial.*", "psutil.*", "torch.*", "torchvision.*", "transformers.*", "einops.*", "robot_descriptions.*", "mujoco.*", "imageio.*", "pyarrow", "pyarrow.*", "libero.*", "zenoh.*", "boto3", "boto3.*", "awscrt", "awscrt.*", "awsiot", "awsiot.*", "botocore.*", "strands_robots.policies.cosmos3._msgpack_numpy", "diffusers", "diffusers.*", "accelerate", "accelerate.*", "openpi_client", "openpi_client.*", "openpi_server", "openpi_server.*", "openpi", "openpi.*", "device_connect_edge", "device_connect_edge.*", "device_connect_agent_tools", "device_connect_agent_tools.*", "rclpy", "rclpy.*", "cyclonedds", "cyclonedds.*", "roslibpy", "roslibpy.*", "rosidl_runtime_py", "rosidl_runtime_py.*", "moveit", "moveit.*", "moveit_configs_utils", "moveit_configs_utils.*", "geometry_msgs", "geometry_msgs.*", "mink", "mink.*", "qpsolvers", "qpsolvers.*", "onnxruntime", "onnxruntime.*", "motionbricks", "motionbricks.*", "trimesh", "trimesh.*", "stable_baselines3", "stable_baselines3.*", "strands_robots_sim", "strands_robots_sim.*", "isaacsim", "isaacsim.*", "omni", "omni.*", "gsplat", "gsplat.*", "plyfile", "plyfile.*"]
ignore_missing_imports = true
# Device Connect drivers - thin wrappers over the untyped device_connect_edge
# SDK; their RPC handlers return dicts that bubble Any (same posture as tools.*).
# disallow_untyped_defs stays relaxed because the SDK's @rpc/@emit/@on
# decorators are themselves untyped (they make decorated methods "untyped" to
# mypy, which the global config cannot see through). disallow_any_generics is
# enabled to keep the public dict/list/Callable surfaces fully parameterized
# rather than silently bare (which a developer's IDE/strict mypy would flag).
[[tool.mypy.overrides]]
module = ["strands_robots.device_connect.*"]
disallow_untyped_defs = false
disallow_any_generics = true
warn_return_any = false
# @tool decorator injects runtime signatures mypy cannot check
[[tool.mypy.overrides]]
module = ["strands_robots.tools.*"]
disallow_untyped_defs = false
warn_return_any = false
ignore_missing_imports = true
# RL trainers (training.rl.*) build torch modules with dynamic tensor-dict
# returns; torch's untyped surface makes warn_return_any noise here, same as the
# policies posture. Public signatures are still annotated.
[[tool.mypy.overrides]]
module = ["strands_robots.training.rl.*"]
warn_return_any = false
disallow_untyped_defs = false
# LeRobot local policy uses dynamic attr access via draccus PreTrainedConfig
[[tool.mypy.overrides]]
module = ["strands_robots.policies.*"]
warn_return_any = false
disallow_untyped_defs = false
# Robot core - complex dynamic assembly, optional deps
[[tool.mypy.overrides]]
module = ["strands_robots.robot"]
disallow_untyped_defs = false
warn_return_any = false
# RTPS IDL bundle - cyclonedds IdlStruct uses metaclass keyword args (typename=)
# that mypy reads as __init_subclass__ kwargs on object. The classes are dynamic
# by construction (DDS type registration), so relax call-arg checking here.
[[tool.mypy.overrides]]
module = ["strands_robots.rtps.idl"]
disable_error_code = ["call-arg"]
disallow_untyped_defs = false
# __init__.py has lazy import helpers
[[tool.mypy.overrides]]
module = ["strands_robots.__init__"]
disallow_untyped_defs = false
# AWS IoT bootstrap modules - boto3 always returns Any from API calls.
# We type the public surface explicitly (ProvisionedThing, BootstrappedAccount)
# but the internal helpers naturally bubble Any from boto3.
[[tool.mypy.overrides]]
module = ["strands_robots.mesh.iot.*", "strands_robots.mesh.transport.iot_transport"]
warn_return_any = false
# Registry modules - dynamic JSON loading returns Any
[[tool.mypy.overrides]]
module = ["strands_robots.registry.*"]
warn_return_any = false
disallow_untyped_defs = false
# MuJoCo simulation - mixins use cooperative self._world patterns
# attr-defined: Mixins access self._world/self._lock/etc. from Simulation (cooperative pattern)
# assignment: PEP 484 implicit Optional (= None on typed params)
# override: Subclass signatures extend base with extra params (orientation, mesh_path)
# misc: Multiple inheritance method resolution conflicts between mixin + ABC
[[tool.mypy.overrides]]
module = ["strands_robots.simulation.mujoco.*"]
disallow_untyped_defs = false
warn_return_any = false
# Isaac Sim backend - same override-signature / cooperative-mixin patterns as
# the MuJoCo backend. The subclass extends the SimEngine ABC with extra,
# backend-specific params on add_robot (mjcf_path / usd_path) and add_object
# (**kwargs escape hatch), and narrows send_action's action type to the
# dict|ndarray the Isaac articulation API consumes -- all deliberate,
# documented extensions. Heavy omni/isaacsim imports are untyped (no stubs).
# override: add_robot / add_object / send_action extend the base signature.
[[tool.mypy.overrides]]
module = ["strands_robots.simulation.isaac.*"]
disallow_untyped_defs = false
warn_return_any = false
disable_error_code = ["override"]
# Async utils and dataset recorder - thin wrappers with dynamic types
[[tool.mypy.overrides]]
module = ["strands_robots._async_utils", "strands_robots.dataset_recorder"]
disallow_untyped_defs = false
warn_return_any = false
# Test files - relaxed type checking for mocks, fixtures, and test utilities
[[tool.mypy.overrides]]
module = ["tests.*", "tests_integ.*"]
disallow_untyped_defs = false
warn_return_any = false
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --cov=strands_robots --cov-report=term-missing --cov-fail-under=80 --strict-markers --timeout=120"
markers = [
"gpu: requires CUDA GPU and real model weights (run with: pytest -m gpu)",
"slow: long-running test (deselect with: pytest -m 'not slow')",
"integration: requires live Device Connect infra (Zenoh router + etcd via Docker); skipped unless DEVICE_CONNECT_ALLOW_INSECURE is set (deselect with: pytest -m 'not integration')",
"moveit2: requires a running MoveIt2 sidecar (ROS 2 / moveit_py). Run with: pytest -m moveit2",
"curobo: requires NVIDIA cuRobo + a CUDA-capable GPU. Run with: pytest -m curobo (set CUROBO_LIVE=1)",
"wbc: requires onnxruntime + a downloaded GR00T-WBC (SONIC) checkpoint + mujoco. Run with: pytest -m wbc (set WBC_LIVE=1)",
"motionbricks: requires the [motionbricks] extra + the upstream `motionbricks` package + git-LFS checkpoints + mujoco. Run with: pytest -m motionbricks (set MOTIONBRICKS_CKPT=<out dir>)",
"molmoact2: requires lerobot >= 0.5.2 (from source) + the [molmoact2] extra (transformers, peft, scipy). Run with: pytest -m molmoact2",
"rtps: requires cyclonedds ([ros2] extra) + a live ROS 2 peer (e.g. turtlesim) on the DDS domain. Run with: RTPS_LIVE=1 pytest -m rtps",
]