Skip to main content
Early tutorial (stub). The flow and node configuration below are complete enough to build and test end-to-end. Screenshots and a shareable template link will be added as the template is published.
By the end of this tutorial you’ll have an SO-101 arm that listens to a spoken command, has a model turn it into a short sequence of poses, and executes that sequence — built entirely in the visual Workflow editor, no scripts and no VLA training. Prefer to build it in Python? See the SDK version: Control an SO-101 arm with your voice. Want a learned policy instead of a pose vocabulary? See the VLA version: Voice-controlled pick and place.

The idea

A trained VLA generalises, but needs a dataset. This template takes the opposite trade: you pre-teach the arm a small set of named poses for a fixed workspace, and a model just sequences them. No training, no dataset — the fixed pose set is your deterministic contract.

Prerequisites

  • An SO-101 follower twin, paired and calibrated (see Get started with the SO-101).
  • A teleop/pose controller on the arm that can reach your saved poses and open/close the gripper (see Twin saved poses).
  • A microphone twin that streams audio.
  • The saved poses below defined for your workspace: home, over_object, grasp, lift, over_target, release, plus gripper open_gripper / close_gripper.

Step 1: Create the workflow

Create a workflow (SO-101 Voice Agent) and add both twins: the microphone and the SO-101. You’ll wire nine nodes left to right; inputs are set in the inspector — fixed values on the # tab, references to another node with the </> expression tab using {node-name.output} syntax.

Step 2: Capture the voice — Audio Track → Audio Assistant

Audio Track (trigger) listens to the mic; Audio Assistant trims it to actual speech.
Speak and open Executions — a run fires, and Audio Assistant shows is_speaking: true with a captured speech segment.

Step 3: Transcribe — Call Model (speech-to-text)

Add a Call Model node, pick a speech-to-text model (e.g. Faster Whisper Small EN). Output: result = the transcript.

Step 4 (optional): See the workspace — Data Source

If you use a VLM planner, give it eyes: add a Data Source node. Output: image_url. Skip this node for a plain LLM on a fixed workspace.

Step 5: Plan the poses — Call Model (planner)

Add a second Call Model and pick an LLM or VLM. Set the Prompt to </> (expression) and paste the pose planner below; the last line inlines the transcript.
If using a VLM, also wire image_url{data-source.image_url}. Leave image_url empty for a plain LLM. Output: result = the JSON plan.

Step 6: Read the plan — JSON Parser


Step 7: The arm’s vocabulary — Twin

Add a Twin node pointing at the SO-101 → it reports the valid step names for matching. Output: control_actuations.

Step 8: Run each step — Loop

Point array_data at the steps list, not the whole object. A loop needs an array to iterate.
Exposes {loop.item} (the current step) and {loop.index}.

Step 9: Guardrail — Fuzzy Matcher

Wire Loop → Fuzzy Matcher. It snaps each planned step to a real pose name and returns empty if nothing matches. Outputs: matched, match, score.

Step 10: Confidence gate — Conditional

Wire the true port → the dispatch node so an unrecognised step never reaches the arm.

Step 11: Move the arm — Virtual Controller

Wire Conditional (true) → Virtual Controller. Use Virtual Controller (not Send Controller Command) because the command changes every step.

Step 12: Test in simulation

Switch to SIMULATE — voice is real, the arm runs as a 3D twin.
  1. Confirm the arm is at zero pose and (if using a VLM) the camera streams.
  2. Say “put the block in the cup.”
  3. Walk Executions:
Then test an impossible request (“make me a coffee”) → the plan degrades to home, the arm does nothing unsafe.
Step completion / pacing. Poses take real time. Confirm each virtual_controller command finishes before the next fires — otherwise grasp could be sent mid-move. If your controller doesn’t queue/complete commands in order, add a short wait step between moves. Simulation is where you catch this.
Zero-pose on attach. When a non-teleop controller attaches, the arm first moves to zero pose with collision detection on, and queues commands during that transition. Expect a brief pause before the first move.

Step 13: Go live

Once simulation is clean, switch to LIVE to drive the physical arm. The graph is unchanged — only the target flips. Keep an eye on it and be ready to stop.

The one idea to take away

The model never sets joint angles and never commands the arm directly. It only sequences pre-taught poses, and every step is validated against the arm’s real vocabulary before it moves. The model reasons; a fixed contract acts.

Next steps

The SDK version

Build the same agent in Python with the Cyberwave SDK.

The VLA version

Train a policy instead of pre-teaching poses.

Workflow nodes

Every node used here, with inputs, outputs, and where it runs.

UGV voice agent

The same template on a mobile rover.