Skip to content

Agents

A robot and the @tool functions in a Strands Agent, and what happens when the model asks a real robot to move.

By the end of this page a Strands Agent holds a robot as a tool, you know which of the 73 @tool functions to mount beside it, and what happens when the model asks a real robot to move: the gate, the variable that pre-approves it, the audit row.

from strands import Agent
from strands_robots import Robot
from strands_robots import pose_tool, robot_mesh   # pose_tool needs strands-robots[serial]

arm = Robot("so101")                       # sim, the default
agent = Agent(tools=[arm, pose_tool, robot_mesh])
print(sorted(agent.tool_names))   # ['pose_tool', 'robot_mesh', 'so101_sim']

A robot is a tool

Left, you, typing a sentence to a Strands Agent; a wire, a sentence, into the agent and a dashed wire, words back. Middle, the Strands Agent: the model reasons, then emits tool calls; it never writes a servo command. Right, a dashed layer, the robot tool: so101_sim, Robot("so101"), 77 actions in simulation, never gated, with chips add_object, run_policy and render; so101, Robot("so101", mode="real"), the same verbs, where execute and run_policy wait; under it the one green element, the operator gate, n declined, y dispatched. Wires from the agent into the layer carry tool_use: add_object and tool_use: execute. Under the layer a card, the status envelope, status and content with text, json and image blocks; a wire tool_result reaches it from the layer and a dashed wire returns to the agent, the model reads it and answers. Footnote: the model never touches an actuator.Left, you, typing a sentence to a Strands Agent; a wire, a sentence, into the agent and a dashed wire, words back. Middle, the Strands Agent: the model reasons, then emits tool calls; it never writes a servo command. Right, a dashed layer, the robot tool: so101_sim, Robot("so101"), 77 actions in simulation, never gated, with chips add_object, run_policy and render; so101, Robot("so101", mode="real"), the same verbs, where execute and run_policy wait; under it the one green element, the operator gate, n declined, y dispatched. Wires from the agent into the layer carry tool_use: add_object and tool_use: execute. Under the layer a card, the status envelope, status and content with text, json and image blocks; a wire tool_result reaches it from the layer and a dashed wire returns to the agent, the model reads it and answers. Footnote: the model never touches an actuator.

Robot(...) returns an object with the Strands AgentTool surface (tool_name, tool_type, tool_spec, stream), so it goes into Agent(tools=[...]) like any tool; the model sees one tool per robot with an action field:

mode tool name actions the model sees
mode="sim" (default) <name>_sim all 77 in tool_spec["description"], e.g. get_robot_state, move_to, set_gripper, rotate_wrist, run_policy, render, start_recording
mode="real" the robot's name get_state, get_robot_state, list_cameras, render, execute, start, status, stop

execute runs one rollout to completion, start runs it in the background. Two Robot("so101") in one agent collide on so101_sim; name them with tool_name=.

The tools around the robot

strands_robots.tools lazy-loads shared tools; the most mounted:

tool what it does gated?
use_lerobot record, replay, train, inspect datasets (record) no
run_policy, train_policy build a policy from any provider, run or train it no
pose_tool named poses and joint moves on a Feetech arm motion verbs
serial_tool raw servo bus reads and writes writes
robot_mesh the fleet (fleet): read with peers, status, inbox; act with the six verbs in the gate table those six
use_ros, use_rosbridge, use_rtps a ROS 2 graph, three transports (ROS 2) blocklisted surfaces
use_unitree, g1_* from strands_robots.tools.g1 Unitree G1 locomotion and arm verbs (unitree) motion RPCs
reachy_* from strands_robots.tools.reachy Reachy Mini head, antennas, sound (reachy) no
load_episode, sample_frames, write_label judge episodes (label and judge) no

The operator gate

Left, the motion commands that enter: execute and start on the robot tool, the ros, serial, pose and unitree tools. Middle, gate_motion and under it a dashed layer with four steps decided in order: the allowlist variable names the command, allow silently; BYPASS_TOOL_CONSENT=true, allow with a warning; nobody to ask, refuse naming the variable; ask the operator, the one green element, an interrupt where y dispatches and anything else declines. Each step's outcome is written to the right and every outcome flows to the audit log under the layer. Bottom left, the one path around the gate: the native drivers' move_to is not gated today; reading and stopping are never gated. Footnote: the operator's reply goes to the audit log, never to the model.Left, the motion commands that enter: execute and start on the robot tool, the ros, serial, pose and unitree tools. Middle, gate_motion and under it a dashed layer with four steps decided in order: the allowlist variable names the command, allow silently; BYPASS_TOOL_CONSENT=true, allow with a warning; nobody to ask, refuse naming the variable; ask the operator, the one green element, an interrupt where y dispatches and anything else declines. Each step's outcome is written to the right and every outcome flows to the audit log under the layer. Bottom left, the one path around the gate: the native drivers' move_to is not gated today; reading and stopping are never gated. Footnote: the operator's reply goes to the audit log, never to the model.

Every policy rollout, ROS command, serial write and pose move the model asks for goes through strands_robots._command_gate.gate_motion, which decides in order:

  1. The tool's allowlist variable names the command: allow silently.
  2. BYPASS_TOOL_CONSENT=true: allow, logging a WARNING.
  3. No tool_context (outside an agent, or the host cannot interrupt): refuse, naming the variable and value pre-approving the call.
  4. Otherwise raise a Strands interrupt named <tool>-command-approval. The operator answers out of band: y, yes, approve or approved proceeds, anything else declines; the model never sees the reply.

Reading is never gated; robot_mesh gates stop.

caller gated verbs allowlist variable
Robot(mode="real") tool, mesh and Device Connect commands execute, start; mesh also teleop_receive, reset, step STRANDS_ROBOT_COMMAND_ALLOW
pose_tool move_motor, move_multiple, incremental_move, load_pose, reset_to_home STRANDS_POSE_COMMAND_ALLOW
serial_tool bus writes STRANDS_SERIAL_COMMAND_ALLOW
use_unitree motion RPCs (loco.SetVelocity, ...) STRANDS_UNITREE_COMMAND_ALLOW
use_ros, use_rosbridge, use_rtps publish, service_call, action_send_goal on a blocklisted name (/cmd_vel, /e_stop, ...) STRANDS_ROS2_COMMAND_ALLOW (by base name: /cmd_vel covers every namespace)
robot_mesh emergency_stop, broadcast, tell, send, stop, rpc STRANDS_MESH_HITL_ACTIONS selects the set

Values are the verbs or targets the tool matches, comma-separated; * pre-approves every command where the tool honours it; =1 or =true pre-approve nothing.

What a refusal looks like

No hardware needed: the gate precedes the port.

from strands_robots.tools.pose_tool import pose_tool

result = pose_tool(action="move_motor", motor_name="shoulder_pan", position=10.0, port="/dev/ttyACM0")
print(result["content"][0]["text"])
pose_tool: 'move_motor' moves the arm on '/dev/ttyACM0' (motor_name=shoulder_pan position=10.0); it needs operator approval before any goal position is sent. No tool_context available for operator approval. Set STRANDS_POSE_COMMAND_ALLOW=move_motor (or STRANDS_POSE_COMMAND_ALLOW=* for every pose_tool command; comma-separated) or BYPASS_TOOL_CONSENT=true to allow in headless mode.

Inside an agent the same call pauses the turn; the dashboard's MotionInterruptHook asks the operator in the browser and deposits a grant keyed on the exact tool input, which the gate spends rather than ask twice.

Audit

Every operator verdict is one JSONL row in ~/.strands_robots/mesh_audit.jsonl (STRANDS_MESH_AUDIT_DIR moves it): event llm_tool_action, source <tool>_tool, action, target, success, detail: "operator approved: 'y'" or "operator declined: ..."; with STRANDS_MESH_AUDIT_PSK set, an HMAC (security).

Posture

There is no dry_run flag; the dry run is mode="sim", the same tool surface over a MuJoCo arm. On hardware keep the gate on and pre-approve only verbs you have watched run; BYPASS_TOOL_CONSENT=true is for a CI box with no robot.

Edit page