Tools¶
The @tool functions re-exported from strands_robots, with their parameters as the agent sees them.
The @tool functions re-exported from strands_robots; hand any of them to a Strands Agent and the agent can call it. Below: each tool's parameters as the agent sees them. The catalog with every action value, including tools not re-exported at the top level, is Tools.
Hardware and simulation¶
strands_robots.tools.use_lerobot.use_lerobot ¶
use_lerobot(module: str = '__discovery__', method: str = 'list_modules', parameters: dict[str, Any] | None = None, label: str = '') -> dict[str, Any]
Universal LeRobot access - call any lerobot module, class, or function dynamically.
Like use_aws wraps boto3.client[service].operation(params), this wraps lerobot[module].method(params). The agent discovers available APIs and registered configs (robots/teleoperators/cameras/policies) directly from lerobot's own draccus registries - nothing is hardcoded.
Results are serialized at full fidelity (no lossy truncation), and any image
frames (numpy HxW / HxWxC arrays) returned by a call are emitted as proper
Strands image content blocks so the model can actually see them.
Calls under restricted namespaces (lerobot.scripts,
lerobot.common.datasets.push) and side-effecting methods
(push_to_hub, upload_folder, save_to_disk, ...) are refused
before invocation - whether named via method or reached through the
module path - so a prompt-injected call cannot push to the Hub or
spawn a training subprocess.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
str
|
Dotted path into lerobot (e.g. "cameras.opencv.OpenCVCamera",
"datasets.lerobot_dataset.LeRobotDataset", "policies.factory").
Special values:
"discovery" - explore modules + all registered configs.
"registry" - list a single registry; pass its name as
|
'__discovery__'
|
method
|
str
|
Method/function/attribute name to call or read. Special values: "list_modules" - discovery output (with module="discovery"). "describe" - inspect the object without calling it. |
'list_modules'
|
parameters
|
dict[str, Any] | None
|
Dict of kwargs to pass to the method. Omit for no-arg calls. |
None
|
label
|
str
|
Human-readable description of what this call does. |
''
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with status and content; content may include text + image blocks. |
Examples:
Discover everything (modules + registered choices)¶
use_lerobot(module="discovery", method="list_modules")
List just the robot choices (dynamic, from registry)¶
use_lerobot(module="registry", method="robots")
Describe a class¶
use_lerobot(module="cameras.opencv.OpenCVCamera", method="describe")
Find cameras¶
use_lerobot(module="cameras.opencv.OpenCVCamera", method="find_cameras")
Get a policy class¶
use_lerobot(module="policies.factory", method="get_policy_class", parameters={"name": "act"})
Read a calibration path constant¶
use_lerobot(module="utils.constants", method="HF_LEROBOT_CALIBRATION")
strands_robots.tools.lerobot_camera.lerobot_camera ¶
lerobot_camera(action: str = 'list', camera_type: str = 'opencv', camera_id: int | str | None = None, save_path: str = './lerobot_captures', filename: str | None = None, camera_ids: list[int | str] | None = None, width: int = 640, height: int = 480, fps: int = 30, color_mode: str = 'RGB', rotation: str = 'NO_ROTATION', format: str = 'jpg', capture_duration: float = 5.0, preview_duration: float = 10.0, async_mode: bool = False, timeout_ms: float = 1000, warmup: bool = True, save_config: bool = False) -> dict[str, Any]
Advanced LeRobot-based camera tool for professional camera management.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
Action to perform - "discover": Discover all available cameras (OpenCV + RealSense) - "list": List camera details and configurations - "capture": Capture single image from camera - "capture_batch": Capture from multiple cameras simultaneously - "record": Record video sequence from camera - "preview": Show live preview from camera - "test": Test camera functionality and performance - "configure": Configure camera settings and save |
'list'
|
camera_type
|
str
|
Camera type ("opencv" or "realsense"). "realsense" needs the Intel SDK installed on top of lerobot; without it the action is refused naming that install, rather than reported as unsupported. |
'opencv'
|
camera_id
|
int | str | None
|
Camera device ID (int for index, str for path like "/dev/video0") |
None
|
save_path
|
str
|
Directory to save captured images/videos |
'./lerobot_captures'
|
filename
|
str | None
|
Custom filename (without extension). Resolved inside save_path; a value naming a location outside it is refused rather than written there. |
None
|
camera_ids
|
list[int | str] | None
|
Cameras to capture from in one capture_batch call - a list of distinct ids, each an int index or a device path string. Omit it for the default robot cameras. An empty list selects no camera and is refused rather than widened to those defaults; a single id passed as a bare string is refused rather than read one camera per character. |
None
|
width
|
int
|
Frame width in pixels (a positive whole number) |
640
|
height
|
int
|
Frame height in pixels (a positive whole number) |
480
|
fps
|
int
|
Frames per second (a positive whole number) |
30
|
color_mode
|
str
|
Color mode; one of "RGB" or "BGR", compared case-insensitively. Any other value is refused rather than silently read as "BGR". |
'RGB'
|
rotation
|
str
|
Image rotation; one of "NO_ROTATION", "ROTATE_90", "ROTATE_180" or "ROTATE_270", compared case-insensitively. Any other value is refused rather than silently read as "NO_ROTATION". |
'NO_ROTATION'
|
format
|
str
|
Image format ("jpg", "png", "bmp"). Becomes the saved file's extension, so like filename it is resolved inside save_path and refused if it names a location outside it. The image returned alongside the file is encoded as JPEG only for a JPEG request; any other format is carried back losslessly as PNG, so the inline copy has the frame's own pixels. |
'jpg'
|
capture_duration
|
float
|
Duration for video recording (positive seconds) |
5.0
|
preview_duration
|
float
|
Duration for preview display (positive seconds) |
10.0
|
async_mode
|
bool
|
Use async reading for better performance. A boolean; it selects a read path rather than scaling one, so any other value is refused rather than read as its opposite. |
False
|
timeout_ms
|
float
|
Timeout for async operations (positive milliseconds; read only when async_mode is on) |
1000
|
warmup
|
bool
|
Enable camera warmup on connection. A boolean, refused rather than read by truthiness - it is also recorded in the saved configuration, so a non-boolean would persist there. |
True
|
save_config
|
bool
|
Save camera configuration to file. A boolean, refused rather than read by truthiness: it writes a file, so a truthy spelling of off would leave one behind. |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict containing status and detailed camera operation results |
strands_robots.tools.lerobot_teleoperate.lerobot_teleoperate ¶
lerobot_teleoperate(action: str = 'start', session_name: str | None = None, background: bool = True, robot_type: str = 'so101_follower', robot_port: str | None = '/dev/ttyACM0', robot_id: str | None = None, robot_cameras: dict[str, Any] | None = None, robot_left_arm_port: str | None = None, robot_right_arm_port: str | None = None, teleop_type: str | None = 'so101_leader', teleop_port: str | None = '/dev/ttyACM1', teleop_id: str | None = None, teleop_left_arm_port: str | None = None, teleop_right_arm_port: str | None = None, dataset_repo_id: str | None = None, dataset_single_task: str | None = None, dataset_num_episodes: int = 50, dataset_fps: int = 30, dataset_episode_time_s: int = 60, dataset_reset_time_s: int = 60, dataset_root: str | None = None, dataset_video: bool = True, dataset_push_to_hub: bool = False, record_resume: bool = False, replay_episode: int = 0, display_data: bool = False, fps: int = 60, teleop_time_s: float | None = None, play_sounds: bool = True, auto_accept_calibration: bool = True, policy_path: str | None = None, dagger_record_autonomous: bool = False, dagger_input_device: str = 'keyboard', dagger_num_episodes: int | None = None) -> dict[str, Any]
Advanced LeRobot teleoperation tool with recording capabilities for robot training data collection.
This tool integrates teleoperation and recording functionality from lerobot, allowing users to: - Control robots through teleoperation devices - Record demonstrations for training machine learning models - Replay recorded episodes - Manage multiple teleoperation sessions
Features: - Session Management: Start, stop, list, and monitor teleoperation sessions - Background Execution: Run teleoperation in background with logging - Recording Mode: Automatically record demonstrations when dataset configuration is provided - Multi-Robot Support: Support for single-arm and bimanual robots - Camera Integration: Multi-camera support with configurable settings - Replay Capability: Replay recorded episodes on physical robots - Safety Features: Graceful shutdown and process management
Actions
start: Start a new teleoperation session - Simple teleoperation (just teleop_type specified) - Recording mode (dataset_repo_id specified) - Background or foreground execution
stop: Stop a running session by name (SIGTERM, then SIGKILL), reported successful only once the process has left the process table
list: List all active teleoperation sessions
status: Get detailed status of a specific session - Process information, uptime, logs
replay: Replay a recorded episode on the robot - Requires dataset_repo_id and replay_episode
dagger: Human-in-the-loop correction (DAgger / teleop takeover) - A policy drives the follower; the leader can pre-empt to record corrections, appended to the dataset as new episodes. - Requires policy_path (policy to roll out) and dataset_repo_id. - Drives lerobot-rollout with --strategy.type=dagger. Toggle the correction window with the keyboard/pedal (dagger_input_device); set dagger_record_autonomous=True to also record the autonomous phase, dagger_num_episodes to cap collected corrections.
Robot Types
- so101_follower: Single-arm SO-101 robot
- bi_so100_follower: Dual-arm SO-100 robot
- koch_follower: Koch robot
- hope_jr: HOPE Jr robot
Teleoperator Types
- so101_leader: SO-101 leader device
- bi_so100_leader: Dual SO-100 leader devices
- koch_leader: Koch leader device
- gamepad: Gamepad controller
- homunculus: Homunculus teleoperator
Camera Configuration Format
{ "camera_name": { "type": "opencv", # a lerobot camera backend; "intelrealsense" for a RealSense "index_or_path": 0, # camera index or device path (opencv) "width": 640, "height": 480, "fps": 30 } }
"type" selects the backend from lerobot's camera registry, and the other options an entry may name are the fields that backend's config declares - an opencv camera is identified by "index_or_path", a RealSense by "serial_number_or_name" - the same vocabulary Robot(cameras=...) reads. An unstated "index_or_path" / "width" / "height" / "fps" is rendered as the default shown above. So a misspelled option is refused rather than dropped: "index" instead of "index_or_path" would otherwise record the default device under this camera's name. The name itself must be a bare token (letters, digits, "_", "-"); "width" / "height" / "fps" must be positive whole numbers, the same domain lerobot_camera reads them with; and "index_or_path" must be a non-negative index or a device path. A string value is quoted in the rendered argv, so a device path or a serial is read back exactly as given ("0123" stays "0123"). The map is rendered into the argv of a detached subprocess, so a value that would not be carried as given is reported here instead of in a session log.
Examples:
Simple teleoperation¶
lerobot_teleoperate( action="start", robot_type="so101_follower", robot_port="/dev/ttyACM0", teleop_type="so101_leader", teleop_port="/dev/ttyACM1" )
Recording demonstrations¶
lerobot_teleoperate( action="start", robot_type="so101_follower", robot_port="/dev/ttyACM0", teleop_type="so101_leader", teleop_port="/dev/ttyACM1", dataset_repo_id="my_user/cube_picking", dataset_single_task="Pick up the red cube and place it in the box", dataset_num_episodes=25, robot_cameras={ "front": {"type": "opencv", "index_or_path": 0, "width": 1920, "height": 1080, "fps": 30} } )
Bimanual robot teleoperation¶
lerobot_teleoperate( action="start", robot_type="bi_so100_follower", robot_left_arm_port="/dev/ttyACM0", robot_right_arm_port="/dev/ttyACM1", teleop_type="bi_so100_leader", teleop_left_arm_port="/dev/ttyACM2", teleop_right_arm_port="/dev/ttyACM3" )
List sessions¶
lerobot_teleoperate(action="list")
Stop session¶
lerobot_teleoperate(action="stop", session_name="teleop_1234567890")
Replay episode¶
lerobot_teleoperate( action="replay", robot_type="so101_follower", robot_port="/dev/ttyACM0", dataset_repo_id="my_user/cube_picking", replay_episode=5 )
Calibration
Calibrating an arm is LeRobot's own procedure, run from the shell -
lerobot-find-port to identify the bus, lerobot-setup-motors to
assign motor IDs, then lerobot-calibrate to record the homing
offsets and travel limits. The resulting JSON lives under
HF_LEROBOT_CALIBRATION and a session here reads it through LeRobot;
the interactive prompt LeRobot shows when a device has none is answered
by auto_accept_calibration below.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
Action to perform (start, stop, list, status, replay, dagger) |
'start'
|
session_name
|
str | None
|
Session identifier (auto-generated for start, required for stop/status) |
None
|
background
|
bool
|
Run session in background with logging (default: True).
Must be a boolean: it selects an execution posture rather than
scaling a quantity, so a truthy spelling of off such as
|
True
|
robot_type
|
str
|
Robot type identifier |
'so101_follower'
|
robot_port
|
str | None
|
Serial port for single-arm robots |
'/dev/ttyACM0'
|
robot_id
|
str | None
|
Robot instance identifier |
None
|
robot_cameras
|
dict[str, Any] | None
|
Camera configuration dictionary (see Camera Configuration Format above for the options and their domains) |
None
|
robot_left_arm_port
|
str | None
|
Left arm port for bimanual robots |
None
|
robot_right_arm_port
|
str | None
|
Right arm port for bimanual robots |
None
|
teleop_type
|
str | None
|
Teleoperator type identifier |
'so101_leader'
|
teleop_port
|
str | None
|
Serial port for single-arm teleoperators |
'/dev/ttyACM1'
|
teleop_id
|
str | None
|
Teleoperator instance identifier |
None
|
teleop_left_arm_port
|
str | None
|
Left arm port for bimanual teleoperators |
None
|
teleop_right_arm_port
|
str | None
|
Right arm port for bimanual teleoperators |
None
|
dataset_repo_id
|
str | None
|
HuggingFace dataset repository ID (enables recording mode) |
None
|
dataset_single_task
|
str | None
|
Task description for recordings |
None
|
dataset_num_episodes
|
int
|
Number of episodes to record |
50
|
dataset_fps
|
int
|
Recording frame rate |
30
|
dataset_episode_time_s
|
int
|
Episode duration in seconds |
60
|
dataset_reset_time_s
|
int
|
Reset time between episodes |
60
|
dataset_root
|
str | None
|
Local dataset storage directory. When omitted for a
recording, an explicit root is still pinned (resolved from
|
None
|
dataset_video
|
bool
|
Enable video encoding |
True
|
dataset_push_to_hub
|
bool
|
Upload dataset to HuggingFace Hub |
False
|
record_resume
|
bool
|
Append to an existing dataset at the resolved root
(lerobot-record |
False
|
replay_episode
|
int
|
Episode number to replay |
0
|
display_data
|
bool
|
Show live camera feeds and telemetry |
False
|
fps
|
int
|
Teleoperation control loop frequency |
60
|
teleop_time_s
|
float | None
|
Session duration limit |
None
|
play_sounds
|
bool
|
Enable lerobot's spoken event announcements ("Recording
episode 3", "Stop recording"). Effective for recording, replay and
dagger; plain teleoperation emits no audio and ignores it. Must be a
boolean - a string such as |
True
|
auto_accept_calibration
|
bool
|
Answer the calibration prompt on the session's
behalf, by writing two newlines into the process's stdin shortly
after it starts. Withhold it ( |
True
|
dagger_record_autonomous
|
bool
|
Record the autonomous rollout episodes into the corrections dataset as well, rather than only the teleoperated takeovers. Must be a boolean; a truthy spelling of off would otherwise land autonomous episodes in a corrections dataset. |
False
|
policy_path
|
str | None
|
Checkpoint the |
None
|
dagger_input_device
|
str
|
How the operator seizes control during a |
'keyboard'
|
dagger_num_episodes
|
int | None
|
Cap on the corrections collected in one |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with operation status and results: |
dict[str, Any]
|
{ "status": "success|error", "content": [{"text": "Description of operation"}], "session_name": "session_id", # for start action "pid": 12345, # process ID for background sessions "command": "full_command_executed", "log_file": "/tmp/session.log", # for background sessions "sessions": {...}, # for list action "uptime": 123.45, # session uptime in seconds; None when the # record states no usable start time "is_running": true # for status action |
dict[str, Any]
|
} |
strands_robots.tools.pose_tool.pose_tool ¶
pose_tool(action: str, robot_id: str = 'so101_follower', port: str | None = '/dev/ttyACM0', calibration: str | None = None, pose_name: str | None = None, motor_name: str | None = None, position: float | None = None, delta: float | None = None, positions: dict[str, float] | None = None, description: str | None = None, smooth: bool = True, steps: int = 20, step_delay: float = 0.05, tool_context: ToolContext | None = None) -> dict[str, Any]
Advanced robot pose management tool with fine motor control.
Actions
Pose Management: - "store_pose": Store current robot pose with a name - "load_pose": Move robot to a stored pose - "list_poses": List all stored poses - "delete_pose": Delete a stored pose - "show_pose": Display pose information
Motor Control: - "move_motor": Move single motor to position - "move_multiple": Move multiple motors simultaneously - "incremental_move": Small incremental motor movement - "read_position": Read current motor position - "read_all": Read all motor positions
System: - "connect": Test robot connection - "emergency_stop": De-energize every motor (Torque_Enable=0). The arm goes LIMP and falls under gravity -- it does not hold position, so anything it is grasping is dropped. Reports an error when any motor could not be released. - "reset_to_home": Move to safe home position
Calibration
This tool records no calibration - it reads the one on disk. Pass
calibration the JSON lerobot-calibrate wrote for this arm and
every degree and percent here is the number LeRobot quotes for the same
servo, and every target is bounded by the travel that run measured.
Omitting it reads and commands the servo's full rotation instead, which
is off by however far this arm's stops sit inside that rotation.
Recording one is LeRobot's own procedure, run from the shell with
lerobot-calibrate (after lerobot-find-port and
lerobot-setup-motors), which writes the JSON under
HF_LEROBOT_CALIBRATION; the interactive prompt LeRobot shows when a
device has none is answered by a lerobot_teleoperate session's
auto_accept_calibration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
Action to perform |
required |
robot_id
|
str
|
Robot identifier for pose storage. Becomes part of the pose file's name, so a value resolving outside the storage directory is refused rather than written there. |
'so101_follower'
|
port
|
str | None
|
Serial port for robot communication |
'/dev/ttyACM0'
|
calibration
|
str | None
|
Path of the calibration JSON |
None
|
pose_name
|
str | None
|
Name for pose operations |
None
|
motor_name
|
str | None
|
Motor name for single motor operations |
None
|
position
|
float | None
|
Target position in degrees (or 0-100% for gripper). A finite number within the motor's measured travel - a value outside it is refused rather than clamped to the mechanical limit, because the clamp cannot be told apart from a typo and the success text echoes the value asked for. |
None
|
delta
|
float | None
|
Incremental movement in degrees. A finite number whose magnitude is at most the motor's full travel, which no starting position could exceed. |
None
|
positions
|
dict[str, float] | None
|
Dictionary of motor positions {motor_name: degrees}. Every
value is held to the same domain as |
None
|
description
|
str | None
|
Description for stored poses |
None
|
smooth
|
bool
|
Interpolate towards the targets over |
True
|
steps
|
int
|
Number of increments for an interpolated move. A positive integer - it divides the travel and bounds the write loop. |
20
|
step_delay
|
float
|
Seconds between increments of an interpolated move. A
positive finite number - this pause is what makes the move smooth,
so |
0.05
|
tool_context
|
ToolContext | None
|
Supplied by the agent runtime; carries the operator interrupt the motion actions are approved through. Without it a motion is refused unless pre-approved via STRANDS_POSE_COMMAND_ALLOW or BYPASS_TOOL_CONSENT=true |
None
|
Operator approval
"move_motor", "move_multiple", "incremental_move", "load_pose" and "reset_to_home" move the arm, so each stops for a human before the motor controller is built; a declined or headless call sends no goal position. Pre-approve with STRANDS_POSE_COMMAND_ALLOW=move_motor,load_pose (or "*"). "connect", the reads, the pose library actions and "emergency_stop" are never gated.
Both interpolation options are read only by load_pose and
move_multiple (when smooth is left true) and by reset_to_home,
which always interpolates; any other action ignores them and is never
refused for them. smooth itself is read only by the first two -
reset_to_home supplies its own - so only those two are refused for it.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict containing status and response content, or an error dict when an |
dict[str, Any]
|
interpolation option or a joint target the requested action reads cannot |
dict[str, Any]
|
be honored. |
strands_robots.tools.serial_tool.serial_tool ¶
serial_tool(action: str, port: str | None = None, baudrate: int = 9600, timeout: float = 1.0, data: str | None = None, hex_data: str | None = None, motor_id: int | None = None, position: int | None = None, velocity: int | None = None, read_bytes: int = 1024, tool_context: ToolContext | None = None) -> dict[str, Any]
Advanced serial communication tool for robot control and device communication.
Actions
- "list_ports": Discover available serial ports
- "send": Send data to serial port
- "read": Read data from serial port
- "send_read": Send data and read response
- "feetech_position": Control STS/SMS servo position
- "feetech_velocity": Control STS/SMS servo velocity
- "feetech_ping": Ping a Feetech servo motor
- "monitor": Monitor serial port (continuous read)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
Action to perform |
required |
port
|
str | None
|
Serial port path (e.g., "/dev/ttyACM0", "COM3") |
None
|
baudrate
|
int
|
Communication speed in baud; a positive integer (default: 9600) |
9600
|
timeout
|
float
|
Read timeout in seconds; a finite number >= 0, where 0 is pyserial's non-blocking mode (return what is already buffered) |
1.0
|
data
|
str | None
|
String data to send |
None
|
hex_data
|
str | None
|
Hex string data to send (e.g., "FF FF 01 04 03 00 64 92") |
None
|
motor_id
|
int | None
|
Motor ID for Feetech commands; an integer in [1, 254], of which 254 (0xfe) is the broadcast every servo receives. An action that reads a reply back accepts only a single servo, [1, 253] |
None
|
position
|
int | None
|
Target position for STS/SMS-series motors; an integer in [0, 4095]. That full scale and the two-byte order this tool encodes into are both STS/SMS properties: the SCS series is 10-bit and reads the same two bytes in the opposite order, so an SCS-series servo is not addressed by this action at all |
None
|
velocity
|
int | None
|
Target velocity for STS/SMS-series motors; an integer in [0, 32767]. Goal_Velocity is sign-magnitude on that series, so a magnitude reaching bit 15 commands the opposite direction instead of a faster move |
None
|
read_bytes
|
int
|
Number of bytes to read; a positive integer |
1024
|
tool_context
|
ToolContext | None
|
Supplied by the agent runtime; carries the operator interrupt the write actions are approved through. Without it a write is refused unless pre-approved via STRANDS_SERIAL_COMMAND_ALLOW or BYPASS_TOOL_CONSENT=true |
None
|
Operator approval
"send", "send_read", "feetech_position" and "feetech_velocity" put bytes on the bus, so each stops for a human before the port is opened; a declined or headless call writes nothing. Pre-approve with STRANDS_SERIAL_COMMAND_ALLOW=feetech_position,feetech_velocity (or "*"). Reads, "monitor" and "feetech_ping" are never gated.
Validation
A numeric option the requested action consumes is checked before the port is opened, so a value that cannot be honored is reported instead of being masked into a different servo command, silently coerced by pyserial, or read as no wait at all. An option the action ignores is never checked.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict containing status and response content |
Policies and training¶
strands_robots.tools.run_policy.run_policy ¶
run_policy(simulation: Any, *, robot_name: str | None = None, policy_provider: str = 'mock', policy_config: dict[str, Any] | None = None, instruction: str = '', n_episodes: int = 1, n_steps: int = 60, control_frequency: float = 30.0, action_horizon: int = 8, fast_mode: bool = True, dataset_root: str | None = None, dataset_repo_id: str = 'local/run_policy_rollout', dataset_task: str = '', dataset_fps: int = 30, dataset_cameras: list[str] | None = None, seed: int | None = None, policy_kwargs: dict[str, Any] | None = None, video: dict[str, Any] | None = None, stop_when: dict[str, Any] | None = None) -> dict[str, Any]
Roll out a policy for n_episodes x n_steps with per-episode parquet boundaries.
Pass-through wrapper around :meth:Simulation.run_policy that owns the
multi-episode loop and the recording lifecycle, so an LLM agent never
has to improvise either. Closes the #708 fabrication vector by:
- Explicit
n_episodes- the loop iterates exactly N times, no narrated counts. - Per-episode
save_episode- each rollout lands in its own parquet row viaPolicyRunner._finalize_recorder_episode. - Parquet-truth return - final payload carries
total_episodes/total_framesread frommeta/info.jsonAFTERstop_recordingreturns, NOT self-reported by the loop. Mismatch withn_episodesis surfaced aswarnings=[...]for the verifier to act on. Both counts are graded bydeclared_count, so a header that declares something which is not a count reads-1and is reported as corrupt metadata rather than coerced into agreement with the request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
simulation
|
Any
|
Live |
required |
robot_name
|
str | None
|
Robot to control. Forwarded to |
None
|
policy_provider
|
str
|
Provider name passed to |
'mock'
|
policy_config
|
dict[str, Any] | None
|
Provider-specific kwargs forwarded verbatim. |
None
|
instruction
|
str
|
Natural-language instruction for the policy. |
''
|
n_episodes
|
int
|
Number of reset -> rollout episodes. MUST be a positive int. There is no "guess from duration" fallback. |
1
|
n_steps
|
int
|
Hard cap on control steps per episode. Forwarded to
|
60
|
control_frequency
|
float
|
Target Hz for policy queries. Must be a finite
number > 0; an unusable rate is reported before the rollout
starts instead of aborting every episode mid-flight. When a
recording is requested it must also EQUAL |
30.0
|
action_horizon
|
int
|
Lower bound on actions consumed per policy call
before re-querying; the effective interval is
|
8
|
fast_mode
|
bool
|
Skip real-time sleep between steps (default True for
rollouts - wall-clock pacing slows headless eval). Must be a
boolean; it selects a posture rather than scaling a quantity, so
any other type is reported before the rollout starts rather than
read by truthiness - a truthy |
True
|
dataset_root
|
str | None
|
When set, the tool drives the full recording
cycle: |
None
|
dataset_repo_id
|
str
|
Forwarded to |
'local/run_policy_rollout'
|
dataset_task
|
str
|
Task label forwarded to |
''
|
dataset_fps
|
int
|
Dataset FPS forwarded to |
30
|
dataset_cameras
|
list[str] | None
|
Camera names to record into the dataset.
When set, forwarded as |
None
|
seed
|
int | None
|
Master RNG seed. Each episode derives a deterministic offset so rollouts are reproducible within a process. |
None
|
policy_kwargs
|
dict[str, Any] | None
|
Optional per-call goal payload forwarded to
every |
None
|
video
|
dict[str, Any] | None
|
Optional rollout-video config forwarded to
:meth: |
None
|
stop_when
|
dict[str, Any] | None
|
Optional semantic early-return clause forwarded to every
per-episode :meth: |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Standard |
dict[str, Any]
|
also carries:: { "n_episodes_requested": int, "n_episodes_actual": int, # parquet-truth, -1 if unread "n_frames_actual": int, # parquet-truth, -1 if unread "dataset_root": str | None, "recording_save_error": str | None, # None on a healthy run "warnings": [str, ...], # mismatch flags "episodes_stop_when_true_at_reset": int, "stop_when_reset_warning": str | None, "episodes": [ {"index": int, "status": "success" | "error", ...}, ... ], } |
strands_robots.tools.train_policy.train_policy ¶
train_policy(action: str = 'train', provider: str = 'lerobot_local', dataset_root: str | None = None, dataset_repo_id: str | None = None, streaming: bool = False, base_model: str = '', output_dir: str | None = None, embodiment: str | None = None, steps: int = 10000, batch_size: int = 32, learning_rate: float | None = None, save_freq: int = 1000, num_gpus: int = 1, num_nodes: int = 1, resume: bool = False, seed: int | None = None, method: str = 'full', lora_r: int | None = None, lora_alpha: int | None = None, lora_target_modules: str | None = None, tune: dict[str, bool] | None = None, val_episodes: int | None = None, augmentation: dict[str, Any] | None = None, fps: int | None = None, extra: dict[str, Any] | None = None, job_id: str | None = None) -> dict[str, Any]
Post-tune (fine-tune) a robot policy on a recorded LeRobotDataset.
Provider-agnostic: provider picks the training backend and the same
arguments map onto its native pipeline. Closes the record -> train -> deploy
loop - the produced checkpoint loads back via create_policy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
One of:
- |
'train'
|
provider
|
str
|
Training backend / policy family - |
'lerobot_local'
|
dataset_root
|
str | None
|
Path to a LeRobotDataset v3 root (has |
None
|
dataset_repo_id
|
str | None
|
Hugging Face Hub dataset id ( |
None
|
streaming
|
bool
|
Stream frames instead of materializing the dataset (lerobot
|
False
|
base_model
|
str
|
HF id or local checkpoint to post-tune from. For GR00T N1.7
this is required ( |
''
|
output_dir
|
str | None
|
Where checkpoints + logs go. |
None
|
embodiment
|
str | None
|
Embodiment tag - which state/action projector head the run
trains. Read by any lerobot policy whose config declares
|
None
|
steps
|
int
|
Total optimizer steps. |
10000
|
batch_size
|
int
|
Global batch size (summed across GPUs). |
32
|
learning_rate
|
float | None
|
Optimizer learning rate. |
None
|
save_freq
|
int
|
Checkpoint cadence in steps. |
1000
|
num_gpus
|
int
|
GPUs on this node ( |
1
|
num_nodes
|
int
|
Nodes (Cosmos HSDP / torchrun |
1
|
resume
|
bool
|
Resume from the latest checkpoint under |
False
|
seed
|
int | None
|
Master seed. |
None
|
method
|
str
|
Tuning strategy - |
'full'
|
lora_r
|
int | None
|
LoRA adapter rank, read only when |
None
|
lora_alpha
|
int | None
|
Numerator of the LoRA |
None
|
lora_target_modules
|
str | None
|
Comma-separated module names the LoRA adapters are
attached to, read only when |
None
|
tune
|
dict[str, bool] | None
|
Fine-grained component toggles for GR00T
( |
None
|
val_episodes
|
int | None
|
Hold out the LAST N episodes for validation; the run
logs an eval loss over them at the checkpoint cadence. A positive
integer below the dataset's episode count, or None for no held-out
set - the split is a fraction lerobot takes the ceiling of, so a
fractional count would reserve a different number of episodes. The
count is read from the dataset's local |
None
|
augmentation
|
dict[str, Any] | None
|
Backend-specific augmentation dict. |
None
|
fps
|
int | None
|
Dataset control rate (when a backend needs it). |
None
|
extra
|
dict[str, Any] | None
|
Backend-specific passthrough. lerobot: |
None
|
job_id
|
str | None
|
Job identifier for |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Canonical Strands result |
dict[str, Any]
|
For |
dict[str, Any]
|
( |
dict[str, Any]
|
a |
dict[str, Any]
|
|
Dependencies (per provider - the base [lerobot] extra is not always
enough):
- lerobot_local + ACT/diffusion: pip install 'strands-robots[lerobot]'.
- lerobot_local + smolvla/pi0/pi05: add lerobot's
[smolvla]/[pi] extra on top of strands-robots[lerobot]
(which pins lerobot>=0.6.0). Those extras layer
transformers>=5.4.0,<5.6.0 (plus num2words / scipy); do NOT pin
transformers==5.3.0 - it conflicts with lerobot 0.6's transformers
floor.
- lerobot_local + groot (GR00T N1.7): add lerobot's [groot]
extra (pip install 'strands-robots[groot]').
- cosmos3: install the upstream package into THIS interpreter (the
trainer imports it and calls its library functions in-process - no
subprocess). Point extra['cosmos_root']/COSMOS_ROOT at the
checkout for runtime config/recipe resolution.
- torchcodec's .so must match the installed torch build exactly; a
torch nightly load-fails a stable torchcodec (undefined symbol)
and lerobot silently yields zero frames. See docs/reference/training/overview.md.
- isaaclab: nothing in THIS interpreter. Install Isaac Lab in its own
virtual environment and set ISAACLAB_PYTHON to its python and
OMNI_KIT_ACCEPT_EULA=YES; the trainer runs its CLI as a subprocess.
See docs/learn/training/isaaclab.md.
strands_robots.tools.lerobot_train.lerobot_train ¶
lerobot_train(dataset_root: str | None = None, tool_context: ToolContext | None = None, policy_type: str = 'act', pretrained_path: str | None = None, output_dir: str | None = None, job_name: str = 'strands_ft', steps: int = 20000, batch_size: int = 8, save_freq: int = 5000, device: str = 'cuda', dtype: str | None = None, gradient_checkpointing: bool = False, lora: bool = False, lora_r: int | None = None, lora_alpha: int | None = None, lora_target_modules: str | None = None, train_expert_only: bool = False, val_episodes: int | None = None, num_gpus: int = 1, push_to_hub: bool = False, resume: bool = False, action: str = 'start', session_name: str | None = None, extra_flags: dict[str, Any] | None = None) -> dict[str, Any]
Fine-tune a LeRobot policy on a local dataset by wrapping lerobot-train.
This closes the local record -> train -> deploy loop. After recording a
LeRobot v3 dataset, call this with dataset_root pointing at the dataset
directory (the one containing meta/info.json). On start it launches
python -m lerobot.scripts.lerobot_train (or accelerate launch for
num_gpus > 1) as a detached background process and tracks it in the same
on-disk session store used by lerobot_teleoperate.
Memory-fit levers
lora and train_expert_only both freeze the VLM and are mutually
exclusive; setting both fails fast. lora emits --peft.method_type=LORA
plus the supplied --peft.* overrides. train_expert_only applies
only to policies whose lerobot config exposes it (currently pi0/pi05/
smolvla; sourced live so it tracks lerobot).
Overfit guard
val_episodes=N reserves the LAST N episodes as a validation set by
emitting --dataset.eval_split (the fraction that makes lerobot hold
out exactly N) together with --eval_steps, so lerobot both keeps the
tail out of training AND logs an eval loss over it at the checkpoint
cadence. The episode and task counts are read from meta/info.json;
a dataset with several tasks is refused because lerobot applies the
split fraction per task, where a global count is not expressible.
Passing dataset.eval_split or eval_steps in extra_flags
overrides the derived value.
Resume
resume=True emits --config_path=<ckpt>/train_config.json --resume=true
only when a checkpoint exists under <output_dir>/checkpoints/last. If no
resumable checkpoint exists, a fresh run starts and a stale empty
output_dir is cleared so lerobot's "already exists" guard does not trip.
Postures
resume, lora, train_expert_only, gradient_checkpointing and
push_to_hub each select a posture rather than scaling a quantity, so each
is checked rather than parsed: a truthy spelling of off - "false", "no",
"0" - is refused before the run starts, instead of selecting the affirmative
posture it reads as the opposite of. Only start reads them, so no other
action is refused for one.
Actions
start: launch a new training run (default). status: report a run's PID, uptime, running flag, and recent log tail. stop: terminate a running session by name (SIGTERM then SIGKILL), reported successful only once the process has exited. list: list tracked training sessions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset_root
|
str | None
|
Local LeRobot v3 dataset directory (must contain meta/info.json).
Read by |
None
|
policy_type
|
str
|
Policy architecture (act, diffusion, vqbet, tdmpc, smolvla, pi0, pi05, pi0_fast, groot, xvla, ...). |
'act'
|
pretrained_path
|
str | None
|
HF id or local path to initialize weights from (gated checkpoints need HF_TOKEN in the environment). |
None
|
output_dir
|
str | None
|
Where to write run outputs; defaults to
|
None
|
job_name
|
str
|
Run name used in the default output_dir and lerobot logs. |
'strands_ft'
|
steps
|
int
|
Number of training steps. |
20000
|
batch_size
|
int
|
Training batch size. |
8
|
save_freq
|
int
|
Checkpoint save frequency in steps. A whole number; a
non-positive value disables periodic saving (only the final
checkpoint is written), and a fractional, non-finite, boolean or
non-numeric cadence is refused before the run starts because
lerobot decodes the flag into an |
5000
|
device
|
str
|
Torch device type, optionally with an index (cuda, cuda:0, cpu, mps). Refused before launch if torch cannot parse it; only the spelling is graded, so naming a device this machine does not have is allowed. |
'cuda'
|
dtype
|
str | None
|
Policy dtype (bfloat16, float32) for policies whose lerobot config declares a dtype field (e.g. the pi0 family, xvla). Default None lets lerobot pick; a policy whose installed config has no dtype field (ACT on lerobot 0.6.1) raises before launch. |
None
|
gradient_checkpointing
|
bool
|
Trade compute for memory on supported policies. |
False
|
lora
|
bool
|
Enable LoRA/PEFT fine-tuning (full-VLM fit on one GPU). |
False
|
lora_r
|
int | None
|
LoRA rank. |
None
|
lora_alpha
|
int | None
|
LoRA alpha (scaling = lora_alpha / r). |
None
|
lora_target_modules
|
str | None
|
PEFT target module spec (e.g. "all-linear"). |
None
|
train_expert_only
|
bool
|
Freeze the VLM, train only the action expert (policies exposing train_expert_only: pi0/pi05/smolvla). |
False
|
val_episodes
|
int | None
|
A positive integer below the dataset's episode count, or
None for no held-out set. Reserves the LAST N episodes as a held-out
validation split, evaluated every |
None
|
num_gpus
|
int
|
Number of GPUs; >1 launches via accelerate --multi_gpu. |
1
|
push_to_hub
|
bool
|
Push the trained checkpoint to the HF Hub at the end.
Publishing is an outward-facing action, so a true value requires
operator approval through |
False
|
resume
|
bool
|
Resume from the latest checkpoint under output_dir when present. |
False
|
action
|
str
|
One of start, status, stop, list. |
'start'
|
session_name
|
str | None
|
Session identifier (auto-generated on start; required for status/stop). |
None
|
extra_flags
|
dict[str, Any] | None
|
Passthrough dict of additional lerobot-train flags, e.g.
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with |
dict[str, Any]
|
|
dict[str, Any]
|
|
dict[str, Any]
|
|
dict[str, Any]
|
the session record states no usable start time - see |
dict[str, Any]
|
func: |
dict[str, Any]
|
the reported |
Assets and memory¶
strands_robots.tools.download_assets.download_assets ¶
download_assets(action: str = 'download', robots: str | None = None, category: str | None = None, force: bool = False) -> dict[str, Any]
Download and manage robot model assets (MJCF XML + meshes).
Assets are sourced from robot_descriptions (recommended by MuJoCo
Menagerie, requires pip install strands-robots[sim-mujoco]). When
robot_descriptions is unavailable, falls back to a shallow
git clone of the Menagerie repo. Robots with a custom GitHub
source in the registry are cloned from their respective repos.
Downloaded assets are cached in ~/.strands_robots/assets/
(override with STRANDS_ASSETS_DIR).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
|
'download'
|
robots
|
str | None
|
Comma-separated names (e.g. |
None
|
category
|
str | None
|
Filter: arm, bimanual, hand, humanoid, mobile, mobile_manip |
None
|
force
|
bool
|
Re-fetch a robot whose assets are already present, replacing
the cached directory. A posture, so a non-boolean is refused
rather than read by truthiness - |
False
|
strands_robots.tools.harness_memory.harness_memory ¶
harness_memory(action: str, task: str | None = None, trace: list[dict[str, Any]] | None = None, summary: dict[str, Any] | None = None, kind: str | None = None, text: str | None = None, backend: str | None = None, robot: str | None = None) -> dict[str, Any]
Persist and retrieve harness memory: task solution traces + global rules.
Task-Specific Memory stores HOW a task was solved (primitive ordering, where policy calls sit) - never WHERE objects happened to be. Loaded traces carry a re-grounding contract: reuse the procedural structure, but never replay literal coordinates; re-localize every object from the current observation. Global Memory stores cross-task success rules and failure models as plain text.
Actions
Task-Specific Memory: - "save_trace": Store (or replace) a task's solution trace + summary. Requires task, trace (list of action dicts - each entry must name a simulation action or registered tool in its "action" field), and summary (JSON object: strategy, what to avoid). Optional backend / robot are recorded as provenance alongside the library version. - "load_trace": Return a task's trace + summary with the re-grounding contract prepended. Requires task. - "list_tasks": List all stored task keys. - "delete_trace": Remove a task's trace + summary. Requires task.
Global Memory: - "append_rule": Append one plain-text rule. Requires kind ("success_rule" or "failure_model") and text (single line). - "load_rules": Return all success rules and failure models.
Storage: ~/.strands_robots/memory/ (override with
STRANDS_MEMORY_DIR). Plain JSONL / JSON / text files - auditable,
no embeddings; task keying is exact-name. The store assumes one agent
session per store (no concurrent-writer coordination), and everything
read back from disk is re-validated before it reaches the response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
Action to perform. |
required |
task
|
str | None
|
Task key, matched exactly on later runs. Must match ^[a-zA-Z0-9_-]+\Z (max 128 chars; no dots, so use "task_v2" rather than "task.v2"). |
None
|
trace
|
list[dict[str, Any]] | None
|
Solution skeleton: list of primitive-invocation dicts, one per step, e.g. {"action": "run_policy", "instruction": "grasp the bowl"}. Spatial values are reference bindings, not replay targets. |
None
|
summary
|
dict[str, Any] | None
|
Semantic summary: task description, strategy, pitfalls to avoid ("avoid" list), success flag. |
None
|
kind
|
str | None
|
Rule kind for append_rule: "success_rule" or "failure_model". |
None
|
text
|
str | None
|
Rule text for append_rule (single line, max 2000 chars). |
None
|
backend
|
str | None
|
Optional provenance: simulation backend the trace was collected on (e.g. "mujoco"). |
None
|
robot
|
str | None
|
Optional provenance: robot the trace was collected with (e.g. "so100"). |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict containing status and response content. |
ROS and mesh¶
strands_robots.tools.use_ros.use_ros ¶
use_ros(action: str, tool_context: ToolContext | None = None, topic: str | None = None, service: str | None = None, action_name: str | None = None, type: str | None = None, fields: dict[str, Any] | None = None, timeout: float = 5.0, count: int = 1, rate: float = 10.0) -> dict[str, Any]
Universal ROS 2 tool - in-process rclpy, dynamic types, no shelling out.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
One of |
required |
tool_context
|
ToolContext | None
|
Injected agent context, used to ask an operator before a
|
None
|
topic
|
str | None
|
Topic name ( |
None
|
service
|
str | None
|
Service name ( |
None
|
action_name
|
str | None
|
Action server name ( |
None
|
type
|
str | None
|
Fully-qualified interface type, e.g. |
None
|
fields
|
dict[str, Any] | None
|
JSON field dict applied with |
None
|
timeout
|
float
|
Seconds to wait for samples / a service / an action result.
For |
5.0
|
count
|
int
|
Number of messages to echo or publish. A positive integer;
it is consumed as a |
1
|
rate
|
float
|
Publish rate in Hz. A positive finite number - the inter-message
period is |
10.0
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A Strands tool result dict |
strands_robots.tools.use_rosbridge.use_rosbridge ¶
use_rosbridge(action: str, host: str = 'localhost', port: int = 9090, topic: str | None = None, service: str | None = None, type: str | None = None, fields: dict[str, Any] | None = None, timeout: float = 5.0, count: int = 1, rate: float = 10.0, tool_context: ToolContext | None = None) -> dict[str, Any]
Universal rosbridge tool - ROS over a WebSocket, no ROS install needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
One of |
required |
host
|
str
|
rosbridge server hostname or IP. Held to the shared domain every dialled host in this package shares, then to this transport's own narrower allowlist. |
'localhost'
|
port
|
int
|
rosbridge WebSocket port (default 9090). |
9090
|
topic
|
str | None
|
Topic name ( |
None
|
service
|
str | None
|
Service name ( |
None
|
type
|
str | None
|
ROS1 two-segment interface type, e.g. |
None
|
fields
|
dict[str, Any] | None
|
JSON field dict ( |
None
|
timeout
|
float
|
Seconds for the WebSocket dial, sample collection, or a service call. A positive finite number of seconds; every action dials the bridge, so every action reads it. |
5.0
|
count
|
int
|
Messages to echo or publish. A positive integer; it is consumed
as a |
1
|
rate
|
float
|
Publish rate in Hz. A positive finite number - the inter-message
period is |
10.0
|
tool_context
|
ToolContext | None
|
Injected agent context, used to ask an operator before a
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A Strands tool result dict |
strands_robots.tools.use_rtps.use_rtps ¶
use_rtps(action: str, topic: str | None = None, type: str | None = None, fields: dict[str, Any] | None = None, timeout: float = 5.0, count: int = 1, rate: float = 10.0, tool_context: ToolContext | None = None) -> dict[str, Any]
Pure-RTPS ROS 2 participant tool - no rclpy, all ROS 2 distros.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
One of |
required |
topic
|
str | None
|
ROS 2 topic name, absolute (e.g. |
None
|
type
|
str | None
|
ROS 2 interface type in the IDL bundle (e.g.
|
None
|
fields
|
dict[str, Any] | None
|
JSON field dict for |
None
|
timeout
|
float
|
Seconds to wait for samples ( |
5.0
|
count
|
int
|
Number of messages to publish or samples to echo. A positive
integer; it is consumed as a |
1
|
rate
|
float
|
Publish rate in Hz. A positive finite number - the inter-message
period is |
10.0
|
tool_context
|
ToolContext | None
|
Injected agent context, used to ask an operator before a
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A Strands tool result dict |
strands_robots.tools.robot_mesh.robot_mesh ¶
robot_mesh(action: str, tool_context: ToolContext | None = None, target: str = '', instruction: str = '', command: str = '', policy_provider: str = 'mock', policy_port: int = 0, duration: float = 30.0, timeout: float = 30.0, name: str = '', limit: int = 50, function: str = '') -> dict[str, Any]
Coordinate every robot, sim, and agent on the local Zenoh mesh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
One of |
required |
target
|
str
|
Peer id (for |
''
|
instruction
|
str
|
Natural-language instruction for |
''
|
command
|
str
|
JSON-encoded command body for |
''
|
policy_provider
|
str
|
Policy provider tag forwarded with |
'mock'
|
policy_port
|
int
|
Optional policy port forwarded with |
0
|
duration
|
float
|
Task duration (seconds) forwarded with |
30.0
|
timeout
|
float
|
Response timeout for RPC actions (seconds). A positive finite
number; read by |
30.0
|
name
|
str
|
Optional subscription name for |
''
|
limit
|
int
|
Max messages returned by |
50
|
function
|
str
|
Device-native function name for |
''
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A Strands tool response dict with status and a single text block. |
Examples::
robot_mesh(action="peers")
robot_mesh(action="tell", target="so100_sim-a1b2",
instruction="pick up the cube")
robot_mesh(action="send", target="peer-b",
command='{"action": "status"}')
robot_mesh(action="emergency_stop") # raises a HITL interrupt;
# runs only on operator approval
Safety controls
- Human-in-the-loop interrupts for
emergency_stopandbroadcast. The tool callstool_context.interrupt("robot_mesh-<action>-approval", reason=...)and only proceeds if the operator's response is an affirmative ("y" / "yes" / "approve"). The Strands SDK delivers the response out-of-band of the LLM's tool arguments, so prompt-injection that flips a boolean cannot bypass this gate. - Per-action sliding-window rate limit (e.g. emergency_stop is capped at 3 calls/min). Reject reason includes wait-time estimate.
send/broadcastpayloads are validated through :func:strands_robots.mesh.security.validate_commandbefore leaving the agent. The same validator runs on the receiver side, so a malformed or out-of-policy payload is rejected client-side before it hits the wire.- Every
tell/send/broadcast/stop/emergency_stop/rpcis audited.