Skip to main content
This tutorial trains a reinforcement-learning (RL) policy for a robot arm entirely in MuJoCo on your laptop, then deploys it to the physical robot through its Cyberwave digital twin. You bring your own RL stack (here, Gymnasium + Stable-Baselines3); Cyberwave handles the scene export and the sim-to-real bridge.
Prefer a platform-managed workflow where you author actions, observations, and the reward graph in the dashboard and export a ready-to-run mjlab/skrl task? Use RL Tasks instead. This tutorial is the bring-your-own-stack path.

Why reinforcement learning?

Most robot learning on Cyberwave starts with imitation: teleoperate the robot, record demonstrations, and train a VLA policy. That works beautifully when you can show the robot what to do. Reinforcement learning is the tool when you can’t demonstrate the behaviour, or when a demonstration isn’t the point:
  • No demonstrations available. The motion is too precise, too fast, or too tedious to teleoperate (full-coverage inspection, dynamic balancing, contact-rich insertion).
  • You have an objective, not a trajectory. You can score the outcome (keep the part centred, visit every viewpoint, minimise energy) but you don’t know the exact joint path that achieves it. RL discovers the path from the reward.
  • You need many, cheap rollouts. RL needs millions of steps. Simulation gives you those for free; the real robot never could.
Cyberwave’s job is to make RL boring to operate:

One scene, everywhere

The environment you build in the editor exports to MuJoCo unchanged. No re-modeling meshes, joints, or cameras by hand.

One SDK, sim to real

The same code drives the simulated twin and the physical robot. cw.affect("simulation") while you validate, then flip to live.

Your stack, your GPU

Train locally with any RL library. Cyberwave never dictates your training loop.

Free rollouts

Millions of MuJoCo steps on a CPU cost nothing and never wear out a motor.

How it works

The pipeline is five steps. You only ever build the scene once, and everything downstream reuses it.
1

Set up the environment

Build the robot twin, parts, and sensors once in the editor or over the SDK. Jump to step
2

Export the scene to MuJoCo

One SDK call writes an MJCF scene with meshes, joints, and cameras intact. Jump to step
3

Write and train the RL pipeline locally

Your own Gymnasium env and Stable-Baselines3 loop, millions of steps on CPU. Jump to step
4

Mirror the policy into Cyberwave

Replay the trained policy against the twin with cw.affect("simulation"). Jump to step
5

Deploy to the real robot

Drop the affect() call. Same script, live hardware. Jump to step

Prerequisites

Store your credentials in a .env file (never commit it):
.env

Step 1: Set up the environment

Build the world your policy will train in: add the robot twin, any parts it interacts with, and the sensors the policy observes (e.g. a wrist camera). Do this in the environment editor or with the SDK:
Keep the scene minimal at first. Fewer bodies and a small camera resolution (e.g. 128×128) train dramatically faster. You can always enrich the scene once the task is learnable.
All twins in the environment must be MuJoCo-compatible before it can export. See simulation support.

Step 2: Export the scene to MuJoCo

Cyberwave exports your environment as a self-contained MuJoCo scene (an mujoco_scene.xml plus all meshes) that runs on your laptop, cluster, or CI runner. Download the ZIP from the environment export API:
You now have mujoco_scene.xml locally. Sanity-check it before writing any RL code:
See the environment MuJoCo export API for the full set of export endpoints (XML-only, URDF, and universal schema).

Step 3: Write and train the RL pipeline locally

This is your code, running on your machine, and Cyberwave isn’t in the loop yet. The three pieces of any RL task are the reward, the environment, and the training run.
1

Define the reward

Turn your objective into a scalar the policy maximises each step. Combine a few dense terms (that guide the arm every step) with a sparse bonus (that rewards the actual goal). For a wrist-camera inspection task:
2

Wrap the scene as a Gymnasium environment

Expose the MuJoCo scene through the standard gym.Env interface so any RL library can train on it.
Validate it with gymnasium.utils.env_checker.check_env before training.
3

Train with PPO

Start with a privileged baseline: feed the policy the part’s position from simulation ground truth (a small MLP, no camera) to confirm the task is learnable and your reward is sane. It trains in minutes on a CPU.
Once the baseline works, switch to the image policy (MultiInputPolicy with a CNN) that observes the raw wrist camera. This is the version you can actually deploy, since the real robot has pixels, not ground-truth part positions.
Privileged-first is the fastest way to fail cheaply. If a tiny MLP with perfect information can’t solve the task, no amount of CNN training will. Fix the reward first.

Step 4: Mirror the policy into Cyberwave

Your policy runs in MuJoCo, but you want to see it on the Cyberwave twin, in the same 3D viewport you’ll watch the real robot in. Flip the SDK into simulation mode and send each step’s joint targets to the twin:
Because affect("simulation") is active, nothing here touches physical hardware, so it’s safe to run while you iterate. Watch the twin move in the dashboard viewport to confirm the policy behaves the way it did in MuJoCo.
arm.joints.set({...}) publishes joint positions in radians by default (pass degrees=True for degrees). See the Python SDK reference for the full joint API.

Step 5: Deploy to the real robot

The payoff: the code that mirrored the policy into simulation is the code that drives the physical robot. Change one line.
Everything below it is unchanged: the same arm.joints.set({...}) calls now flow through Cyberwave’s live-control path to the real robot (via the Edge Core paired with your twin).
Test in affect("simulation") until you trust the policy. When you switch to affect("live"), start with a clear workspace, a hand on the e-stop, and conservative action limits.

Sim-to-real: closing the observation gap

If your deployable policy reads the wrist camera (the image policy), it transfers directly. The real robot has a real camera, and you read frames back from the twin:
If you deployed the privileged baseline, remember its observation included the part’s position from simulation ground truth, which doesn’t exist on the real robot. You have two ways to close that gap:

External perception

Supply the part’s position from your own perception module (a detector, AprilTags, a depth pipeline) and inject it into the observation each step in place of the sim value.

Student distillation

Train a CNN “student” that takes only the raw camera image + joints and imitates the privileged “teacher”. The student learns to infer the part’s position from pixels, with no external perception needed.

Next steps

RL Tasks (platform-managed)

Author actions, observations, and rewards in the dashboard and export a ready-to-run task.

Uploading policies

Store a trained checkpoint on Cyberwave and serve it as a controller.

Simulation

How MuJoCo, Playground, and affect() fit together.

Python SDK

Full SDK reference: twins, joints, cameras, and more.

Train a VLA

The imitation-learning counterpart to this tutorial.

Connect hardware

Pair the Edge Core so affect('live') reaches your robot.