Multi-robot mesh¶
Every Robot() auto-joins a Zenoh mesh. Peers discover each other on the LAN and can query, command, and e-stop one another.
Device Connect is the recommended networking layer
What's described here is the built-in Zenoh mesh — the automatic fallback. When the device-connect extra is installed, Robot().run() and robot_mesh() use Device Connect (structured RPC, presence, registry, safety) and fall back to this mesh only when it's unavailable. Both ride on Zenoh.
# process A
from strands_robots import Robot
sim_a = Robot("so100")
print(sim_a.mesh.peers) # discovers sim_b within ~1 s
# process B
sim_b = Robot("aloha")
sim_a.mesh.tell(sim_b.mesh.peer_id, "pick up the cube",
policy_provider="mock", duration=10.0)
Key mesh calls¶
# Point-to-point status query
result = sim_a.mesh.send(target_peer_id, {"action": "status"}, timeout=5.0)
# Fan-out → list of responses collected within timeout
results = sim_a.mesh.broadcast({"action": "status"}, timeout=2.0)
# Safety primitive - writes a tamper-evident audit log
sim_a.mesh.emergency_stop() # STRANDS_MESH_AUDIT_DIR overrides log location
Published topics¶
| Topic | Rate | Content |
|---|---|---|
strands/{peer_id}/presence |
2 Hz | heartbeat / peer discovery |
strands/{peer_id}/state |
10 Hz | joints, sim time, task status |
strands/{peer_id}/cmd |
on demand | incoming RPC commands |
strands/{peer_id}/response/{id} |
on demand | RPC replies (turn_id correlated) |
strands/{peer_id}/stream |
on demand | VLA execution steps |
strands/{peer_id}/pose |
on demand | SE(3) from SLAM/odom/VIO |
strands/{peer_id}/imu |
on demand | orientation, gyro, accel |
strands/{peer_id}/health |
on demand | battery, CPU, memory |
strands/broadcast |
on demand | fan-out RPC |
Sensor topics only publish when the robot exposes the attribute. Zero cost when unused.
Agent-driven mesh¶
from strands import Agent
from strands_robots.tools import robot_mesh
agent = Agent(tools=[sim_a, robot_mesh])
agent("Find every robot on the mesh and ask each one to report its status")
agent("E-STOP all peers")
Mesh teleop¶
# Machine A - leader publishes at 50 Hz # requires hardware
leader = Robot("so100", mode="real")
leader.start_teleop_publish(teleoperator=leader.teleoperator,
device_name="leader", method="arm", hz=50)
# Machine B - follower applies incoming actions # requires hardware
follower = Robot("so100", mode="real")
follower.start_teleop_receive(source_peer_id=leader.mesh.peer_id,
device_name="leader", apply_fn=None)
leader.stop_teleop("leader")
follower.stop_teleop("leader")
get_teleop_status() on either side inspects current teleop state.
source_peer_id and device_name are single segments of the mesh key
expression strands/{peer_id}/input/{device_name}, so both must be plain
identifiers ([A-Za-z0-9_.-]+, at most 128 chars). A Zenoh wildcard (*,
**) or an embedded / is refused with a ValidationError rather than
silently widening the stream: source_peer_id="**" would subscribe to
strands/**/input/leader and apply joint commands from every publishing peer,
not just the configured leader.
Attach a mesh to a Simulation¶
Robot(name, mode="sim", mesh=True) is the normal path: it resolves the
STRANDS_MESH kill switch, starts a client, and stores it on the engine. To
attach one to a Simulation you built yourself, start the client and assign it:
from strands_robots.mesh import init_mesh
from strands_robots.simulation import create_simulation
sim = create_simulation("mujoco")
sim.mesh = init_mesh(sim, peer_id="bench-sim") # None when mesh is disabled
The Simulation(mesh=...) constructor argument takes that same started client -
it is not a boolean opt-in switch, and a truthy value with no .stop() (notably
mesh=True) is rejected at construction. cleanup() stops the client before it
tears down MuJoCo; a stop that fails is logged and stepped over, so the world,
renderers and executor are always released.
Disable¶
| Method | Scope |
|---|---|
STRANDS_MESH=false |
process-wide kill switch |
Robot("so100", mesh=False) |
per-robot opt-out |
Mesh failures are non-fatal - robot.mesh becomes None; the sim/hardware instance still works.
See also¶
- Device Connect - the recommended networking layer this mesh backs.
- AI agents - drive the mesh with natural language.
- Architecture - where the mesh sits in the module map.
- Mesh source -
core.py,session.py,audit.py,sensors.py,input.py.