Skip to content

Robots

Strands Robots gives a Strands Agent hands. One Robot() call returns a MuJoCo simulation (the default - no GPU, no hardware) or a real robot - same code, same natural-language control, both auto-joined to a peer-to-peer mesh.

from strands import Agent
from strands_robots import Robot
robot = Robot("so100") # MuJoCo sim by default; mode="real" for hardware
Agent(tools=[robot])("pick up the red cube")

The full documentation - guides, the robot catalog, policy references, and the hardware setup walkthroughs - lives at strands-labs.github.io/robots.

Examples use uv; plain pip works too. The base install is light (numpy, opencv-headless, Pillow) - pull in only the extras you need.

Terminal window
# Most users start here (simulation, no GPU, no hardware):
uv pip install "strands-robots[sim-mujoco]"
# Real hardware + local policies:
uv pip install "strands-robots[sim-mujoco,lerobot]"
# Everything (except the GPU-only extras):
uv pip install "strands-robots[all]"
ExtraUse for
sim-mujocoMuJoCo simulation (recommended starting point)
sim-newton / sim-isaac / sim-gsGPU-native, photoreal, and Gaussian-splatting rendering backends
lerobotReal hardware, local VLA inference, dataset recording
groot-service / cosmos3-serviceNVIDIA GR00T / Cosmos 3 inference clients
mesh / mesh-iotPeer-to-peer robot mesh (+ AWS IoT Core for fleets)
allKitchen sink (except GPU-only sim-isaac / sim-gs)

See the full extras matrix for the complete list (molmoact2, curobo, wbc, motionbricks, benchmark-libero, and more).

from strands import Agent
from strands_robots import Robot
robot = Robot("so100") # MuJoCo simulation
agent = Agent(tools=[robot])
agent("Wave the arm using the mock policy for 200 steps, then render a top-down view")

Robot("so100") returns a Simulation instance - the full simulation AgentTool. Drive it in natural language through an Agent, call its methods directly (robot.render(camera_name="topdown")), or dispatch an action by calling it (robot(action="render", camera_name="topdown")).

from strands import Agent
from strands_robots import Robot, gr00t_inference
robot = Robot(
"so101",
mode="real",
cameras={
"front": {"type": "opencv", "index_or_path": "/dev/video0", "fps": 30},
"wrist": {"type": "opencv", "index_or_path": "/dev/video2", "fps": 30},
},
port="/dev/ttyACM0",
data_config="so100_dualcam",
)
agent = Agent(tools=[robot, gr00t_inference])
# Start the GR00T inference service (Docker, Jetson/x86 GPU)
agent.tool.gr00t_inference(
action="start",
checkpoint_path="/data/checkpoints/model",
port=8000,
data_config="so100_dualcam",
)
agent("Use so101 to pick up the red block with the GR00T policy on port 8000")

Teleoperate a real arm to collect demos, fine-tune a policy on them, run it in sim and on hardware, hand work to a fleet peer, and expose it all on ROS 2 - one library, one mental model.

from strands import Agent
from strands_robots import Robot
from strands_robots.tools import train_policy
# 1. TELEOPERATE a real SO-101 with its leader arm and RECORD demos as a
# LeRobotDataset (one prompt drives cameras + teleop + recording).
follower = Robot("so101", mode="real", port="/dev/ttyACM0",
cameras={"front": {"type": "opencv", "index_or_path": "/dev/video0"}})
follower.attach_teleop("so101_leader", port="/dev/ttyACM1", id="leader")
Agent(tools=[follower])(
"start_recording(repo_id='me/pick', root='/tmp/pick', fps=30, "
"task='pick up the cube'); teleoperate for 60s; stop_recording"
)
# 2. POST-TUNE a policy on those demos (LoRA fine-tune; GPU box).
train_policy(action="train", provider="lerobot_local",
dataset_root="/tmp/pick", base_model="lerobot/smolvla_base",
output_dir="/tmp/pick_ckpt", method="lora", steps=20000)
# 3. RUN the tuned checkpoint - same policy on a MuJoCo twin AND the real arm.
twin = Robot("so101") # sim twin, no hardware
twin.run_policy(robot_name="so101", policy_provider="lerobot_local",
policy_config={"pretrained_name_or_path": "/tmp/pick_ckpt"}, duration=10.0)
follower.start_task("pick up the cube", policy_provider="lerobot_local",
policy_port=None, duration=10.0) # real arm, in-process
# 4. COORDINATE a fleet - tell a mesh peer to assist, in natural language.
follower.mesh.tell(follower.mesh.peers[0]["peer_id"], "hold the tray steady")
# 5. EXPOSE the running sim on ROS 2 - rviz / nav2 / any ros2 node can subscribe.
from strands_robots.simulation import Simulation
sim = Simulation(ros2_bridge=True); sim.create_world(); sim.add_robot("so101")
sim.step(100) # publishes /so101/joint_states + camera image_raw on the ROS 2 graph
StepCapabilitySurface
1Teleop + dataset recordingRobot(mode="real"), attach_teleop, start_recording
2Policy post-tuningtrain_policy (LeRobot / GR00T trainers)
3Sim + hardware policy rolloutrun_policy (sim), start_task (hardware)
4Fleet coordinationrobot.mesh.tell / robot_mesh tool
5ROS 2 interopSimulation(ros2_bridge=True), use_ros

Steps 1 and 3-real need hardware; step 2 needs a GPU. Everything runs in sim with no hardware (Robot("so101")), so you can exercise the whole loop today.

  • Sim-first, safe by default. Robot("so100") spins up a MuJoCo world. You never accidentally drive real servos - mode="real" is an explicit opt-in.
  • 50+ robots, 8 categories. Arms, humanoids, quadrupeds, hands, drones, and bimanual rigs - resolved from a single registry with auto-download of assets.
  • Any policy. VLA models (NVIDIA GR00T, LeRobot ACT/Pi0/SmolVLA/Diffusion), plus classical motion planners, MPC, and scripted controllers behind one ABC.
  • Mesh networking built in. Every robot is a Zenoh peer. tell() another robot what to do; broadcast an E-STOP; bridge to AWS IoT Core for fleets.
  • ROS 2 interop. Observe + command any ROS 2 graph (use_ros), act as a robot with no rclpy (use_rtps), or expose a running sim as a ROS node.
  • One mental model. Sim and hardware share the same policy interface, the same mesh, and the same natural-language control surface.
graph LR
A[Natural Language<br/>'Pick up the red block'] --> B[Strands Agent]
B --> C[Robot<br/>sim or real]
C --> D[Policy Provider<br/>GR00T / Cosmos 3 / LeRobot / planner / mock]
D --> E[Action Chunk]
E --> F[MuJoCo Sim<br/>or Hardware]
F -->|observation| C
classDef input fill:#2ea44f,stroke:#1b7735,color:#fff
classDef agent fill:#0969da,stroke:#044289,color:#fff
classDef policy fill:#8250df,stroke:#5a32a3,color:#fff
classDef hardware fill:#bf8700,stroke:#875e00,color:#fff
class A input
class B,C agent
class D,E policy
class F hardware

Each control cycle, the robot captures observations (camera frames and joint states), sends them to the policy for inference, receives an action chunk, and executes those actions in the sim or on hardware.

Robot() is a factory, not a wrapper - you get the real backend instance back with all its methods.

Robot("so100") # mode="sim" (default, safe)
Robot("so100", mode="real") # explicit hardware opt-in
Robot("so100", mode="auto") # probe USB for servos, fall back to sim
Robot("my_arm", urdf_path="arm.xml") # bring your own MJCF/URDF
ParameterTypeDefaultDescription
namestrrequiredRobot name or alias
modestr"sim""sim", "real", or "auto" (case-insensitive)
backendstr"mujoco"Sim backend: "mujoco", "newton", or "isaac"
urdf_pathstrNoneExplicit MJCF/URDF path (skips registry lookup)
camerasdictNoneCamera config (mode="real" only)
data_configstrnameObservation/action schema name
meshboolTrueAuto-join the Zenoh mesh

Safety rules: defaults to sim; cameras= is rejected in sim mode (add sim cameras with the add_camera action); unknown names raise ValueError unless you pass urdf_path=; STRANDS_ROBOT_MODE overrides detection.

50+ robots across 8 categories, resolved from the registry. Assets (MJCF + meshes) auto-download from robot_descriptions / MuJoCo Menagerie on first use. List them at runtime with from strands_robots import list_robots; list_robots().

CategoryCountExamples
Arm22so100, so101, koch, panda, fr3, ur5e, xarm7, kinova_gen3, kuka_iiwa
Humanoid18unitree_g1, unitree_h1, apollo, talos, reachy2, booster_t1, cassie
Mobile13spot, go1, unitree_go2, anymal_c, stretch3, lekiwi, earthrover
Hand8shadow_hand, allegro_hand, leap_hand, ability_hand, robotiq_2f85
Bimanual3aloha, bi_openarm, trossen_wxai
Aerial2crazyflie, skydio_x2
Expressive1reachy_mini
Mobile manip1google_robot

Hardware-capable (drivable with mode="real" via LeRobot): so100, so101, koch, omx, hope_jr, aloha, bi_openarm, reachy2, unitree_g1, lekiwi, earthrover. All are simulatable. See the full robot catalog.

Import any of these and pass to Agent(tools=[...]). Each is a Strands AgentTool returning {"status", "content"}.

ToolPurpose
Robot(...)Universal robot - sim or hardware, natural-language + async control
run_policyMulti-episode policy rollout with per-episode eval + dataset recording
train_policyPost-tune (fine-tune) a policy on a recorded dataset
use_lerobotUniversal LeRobot bridge - call any lerobot module/class/config directly
robot_meshCoordinate robots over the Zenoh mesh (tell, broadcast, E-STOP)
use_ros / use_rtpsBridge to / join a ROS 2 graph (in-process rclpy, or pure DDS)
gr00t_inferenceManage NVIDIA GR00T inference services (Docker lifecycle)
lerobot_cameraOpenCV / RealSense camera discovery, capture, record
lerobot_calibrateList, view, back up, restore LeRobot calibrations
lerobot_teleoperateRecord demonstrations, replay episodes
pose_toolStore, recall, and execute named robot poses
serial_toolLow-level Feetech servo / raw serial communication
download_assetsPre-fetch robot MJCF + meshes into the asset cache

All policies implement one ABC - async get_actions(observation, instruction, **kwargs). The interface is deliberately agnostic about how actions are produced, so it fits both VLA models and classical controllers.

from strands_robots import create_policy
create_policy("mock") # sinusoidal test actions
create_policy("groot", port=5555) # NVIDIA GR00T via ZMQ
create_policy("cosmos3", embodiment="droid", port=8000) # NVIDIA Cosmos 3 via WebSocket
create_policy("lerobot/act_aloha_sim_transfer_cube") # local HuggingFace inference
ProviderBackendNotes
mocknoneSinusoidal trajectories; no images needed (~10x faster)
grootNVIDIA GR00T N1.5/N1.6/N1.7ZMQ service or local in-process
cosmos3NVIDIA Cosmos 3WebSocket to a Cosmos policy server
lerobot_localHuggingFaceDirect ACT / Pi0 / SmolVLA / Diffusion inference
lerobot_async / remoteHuggingFace / anyOffload to a remote PolicyServer (gRPC or WebSocket)

See the policies reference for the full provider list, plus motion-planning policies (moveit2, curobo), whole-body control (wbc), and motionbricks.

Drive any real robot - or a simulation - from one or more LeRobot teleoperators. Teleoperator() mirrors the Robot() factory; attach_teleop() + teleoperate() run the control loop.

from strands_robots import Robot
# Leader arm -> follower arm (both speak {motor}.pos -> zero config)
follower = Robot("so101", mode="real", port="/dev/ttyACM0")
follower.attach_teleop("so101_leader", port="/dev/ttyACM1", id="leader")
follower.teleoperate() # Ctrl+C or stop_teleoperate()

17 teleoperators drive 14 robots. Zero-config when action keys match; otherwise pass map_fn. Full matrix + recipes: Teleoperation docs.

The physical-AI data loop, end to end: record a LeRobotDataset from sim or hardware, stream it straight back for eval/training (no full download), and optionally dump it to a mutable Hugging Face Storage Bucket. Needs the lerobot extra.

from strands import Agent
from strands_robots import Robot
sim = Robot("so100", mesh=False)
agent = Agent(tools=[sim])
# COLLECT - one natural-language prompt drives scene + cameras + policy + record.
agent(
"Create a world with the so100 robot, add a red cube and a front camera, "
"start recording (repo_id='local/demo', root='/tmp/demo', fps=30, "
"overwrite=True, task='pick up the red cube'), run the mock policy for "
"60 steps, then stop recording."
)
# STREAM - read it back lazily; camera frames decode on the fly from the MP4
# shards, state/action from parquet. Nothing is re-materialized to disk.
reader = sim.stream_dataset("local/demo", root="/tmp/demo", shuffle=False)