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 gripperopen_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.
Pose planner prompt
Pose planner prompt
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
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.- Confirm the arm is at zero pose and (if using a VLM) the camera streams.
- Say “put the block in the cup.”
- Walk Executions:
Then test an impossible request (“make me a coffee”) → the plan degrades to
home, the arm does nothing unsafe.
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.