cyberwave-rl/tasks/<task_id>/, where the existing registry discovers it and make run_cfg runs it like any other task.
Cyberwave currently supports basic mjlab: exported tasks are mjlab-compatible (scene config, action and observation terms, skrl training). We’re planning to release a richer Cyberwave mjlab extension with useful helpers in the future.
Why a separate entity?
RL tasks are deliberately kept independent of Environments. Deleting an environment detaches any linked RL tasks (setsenvironment to none) instead of cascading the delete, so a task keeps its source tree even after the world it was originally trained against is gone.
This matches how researchers actually iterate: the SceneCfg is a moving target, while the reward, action, and observation specs are the long-lived artefacts.
Lifecycle
You can also describe a robot task from the dashboard or the environment editor’s Agent tab. Type@ (or choose Tag asset or scene object) to select an
existing scene object or a catalog asset by name/full slug. Tagging adds context;
it does not create an instance or start training. The assistant can look for a
compatible policy or prepare a training proposal when the task is supported.
Review and confirm training separately from binding or running its result.
The same training card appears in chat and Policies, showing reported
epochs, episodes or transitions as they advance. These counts are progress,
not a success score. Scene built on export means the configured scene
entities will generate the scene when you export; you do not need to save a
generated scene file first. Scene source saved identifies a saved source,
not a compilation or evaluation result. If the editor reports Scene setup
needed, add scene entities or use Scene entities → Regenerate generated
files, then prepare a fresh training request when its inputs change.
Use Cancel training on that card to review and cancel only its active
attempt. Cancellation prevents a new result from registering; worker shutdown
can still be in progress. Existing policies and recordings remain unchanged.
After failure or cancellation, Prepare a new attempt opens a fresh proposal
using the previous settings. Review and confirm it before training starts.
If confirmation reports changed inputs, or the proposal has expired, choose
Prepare a fresh proposal. Review the current task and settings again;
the rejected or expired proposal does not start training.
In Live and Simulation, open Process from the strip below the scene
to follow workflow runs and their recorded named steps in the central workspace.
The right panel stays available for the agent. Process is read-only, works in
Monitor mode, and does not verify physical task success. It is hidden in Edit
and Replay.
1
Create
Create an RL task from
/rl-tasks, or from File → Export → Create RL task from this environment in the environment editor.2
Import or author source
Import an existing task ZIP (for example an in-tree task from
cyberwave-rl/tasks/) to seed the source tree, or write files directly in the Files tab.3
Configure entrypoints
Set the entrypoint paths —
scene_cfg_path, env_cfg_path / env_cfg_factory, rl_cfg_path / rl_cfg_factory, and an optional registry_path. Cyberwave never forces a particular from … import … statement; the import wiring is yours.4
Regenerate SceneCfg
Regenerate the Cyberwave-generated SceneCfg module from the linked environment. This also rewrites the spec-derived files (
scene_bindings.py, actions.py, observations.py, task_export/, and the strict task spec).5
Export and run
Download the ZIP and drop the folder into
cyberwave-rl/tasks/. Run it with make run_cfg arg_overrides="--task=<id>" exactly like the in-tree examples.6
Round-trip
Edit locally, then re-import the same folder back into Cyberwave to keep the editor in sync. The editor stays authoritative for Cyberwave-generated files.
Editor tabs
The RL task editor is organized into tabs that map onto the export contract:
For a task configured for CPU, scene export preserves authored mesh
collisions supported by native MuJoCo. GPU exports still require primitive
colliders or an appropriate simplified collision variant. Selecting CPU does
not make arbitrary training code CPU-compatible; the task must also provide a
compatible training implementation.
Reviewing policy evidence
Training requests shows the current training phase and reported progress in both the task and assistant conversation. When a task reports curriculum stages, the same card shows its current stage and recent training-stage success rate. That rate is not the final held-out evaluation; missing results remain unreported. In Policies, select a checkpoint and open Evaluation evidence to inspect episode outcomes and available Replay links. Deployment reports separately show reported checks from completed simulation deployments, including failures. Tasks with confirmed deployment checks can report these automatically; the report identifies whether it came from the controller or a task writer. Missing recordings or checks are not a successful result. Check Final task outcome maintained as well as intermediate stages: reaching the goal briefly is not sustained success. Not reported means that outcome is unavailable, not that it passed. These reports are not held-out evaluations or hardware approval. Neither viewing evidence nor finishing training automatically binds or publishes a policy. An evaluated teaching result offers Use on this robot directly. It creates a private controller if needed and assigns it without starting motion; replacing an existing controller requires confirmation. Publish to catalog is separate and does not require assigning the policy to a robot first. Replay displays only recorded channels: object motion appears when the episode includes it; older joint-only recordings do not demonstrate object movement. Policy evidence Replay links frame the recorded robot on entry. You can still orbit, zoom, play and scrub the timeline; this does not start a controller.Author from Python (SDK)
Everything the editor tabs produce can also be authored from the Python SDK — useful for reproducible setup scripts. TheRLTaskClient covers every tab via its set_*_spec methods, so a script and the dashboard produce the same specs. The Actions, Observations, and RL config tabs also ship make_* builder helpers; the command tabs are authored as plain dicts passed to set_training_command_spec / set_inference_command_spec (see step 4b below).
The example below is the OpenArm pick-up task: a fixed-base arm, a table, a free-floating cube, and a wrist depth camera. It is the same scene used in the full RL task walkthrough, trimmed to the essentials.
Actions
Task input is not robot output
A goal or requested walking speed is an input to a policy. The policy’s actions are its output to the robot or another controller. These are separate contracts: a walking policy can accept speed and produce joint targets; a navigation policy can produce body velocities for an onboard controller whose actuators are not accessible. Match both contracts to the selected runtime. Simulation actuators in a robot model do not prove that its physical driver exposes them. Cyberwave’s shared adapter has a simulation-only body-velocity output port. For an explicitly authored aerial policy, controller startup checks its frozen interface against the lower controller observed in the running simulator. Unsupported or mismatched interfaces are rejected. Do not treat an evaluated checkpoint or a URDF as hardware readiness. The Velocity preset below means joint velocity, not base/drone velocity. Keep incompatible outputs explicit instead of converting them silently. Authored command-level flight tasks reuse custom action/observation terms and the existing asynchronous training adapter. Their lower controller, output units, frame, limits and cadence must match the training version. Changing that controller requires a fresh task version and evaluation. This adapter support does not yet establish a verified end-to-end drone deployment in the dashboard; inspect actual deployment acceptance and Replay separately from training results. Flight checks use measured robot pose and velocity, without requiring a fake joint-state stream. They require the goal to remain achieved, not merely be crossed once. A missing root feed, a different flight controller or unexplained applied forces makes the result fail. These checks are implementation support; end-to-end drone dashboard verification is still pending.Joint action presets
Action terms are authored from five control-mode presets:position, velocity, and effort mirror the Isaac Lab / mjlab JointPositionActionCfg / JointVelocityActionCfg / JointEffortActionCfg math one-to-one — exactly the target = action × scale + offset that mjlab implements in BaseAction.process_actions. position_delta is Cyberwave’s own preset: baseline_delta is an explicit per-step bias baked into the target, so an action of zero can drift the joints toward a goal pose. smoothed_position_delta is the same delta, but spread evenly across the policy step’s physics substeps for a smoother velocity profile.
A single Use default joint offset checkbox mirrors mjlab’s use_default_offset: when on, mjlab replaces offset with each joint’s default_joint_pos (or default_joint_vel) at runtime so action zero returns the joint to its home pose. Every numeric field (scale, offset, baseline_delta) has a Per joint toggle that switches it between a uniform scalar and a {joint_name: value} mapping. The backend rejects per-joint keys that aren’t in the action’s selected joints, so typos surface at save time. Legacy symbolic offsets ("default", "zero", "none") keep round-tripping unchanged.
The Scene tab is where you enable simulation actuators for an entity (which Xml*ActuatorCfg wrapper drives each joint); the Actions tab is where you pick the policy-controlled subset of those actuators. Each action row renders a schema-backed joint table (Joint, Control type, Position min/max, Max velocity, Max torque/force, KP, KD, Selected) with Select all / Clear. Passive joints, spring-only actuators, and joints driven by an incompatible wrapper appear greyed out with a hint. Incompatible presets are hidden rather than shown disabled. Concrete joint names are stored in target_names_expr; a collapsed Advanced — custom regex disclosure lets imported or legacy specs keep regex patterns.
Observations
Observation terms are organized into named groups (policy, critic, …) and drawn from a catalog:
Link terms pick bodies from a scene-derived checkbox list and expose Components toggles so you only export the axes the policy needs. Joint observation terms reuse the shared Joint target picker and — unlike actions — may include both actuated and passive joints, with “All actuated”, “All joints”, “Passive joints”, and “Pick specific joints” exposed directly; “Custom regex” sits behind a small Advanced disclosure.
- Separate policy and critic
RL config
The RL config tab persists the skrl backend, the algorithm (PPO or SAC), a structured graph-based Network builder, the trainer parameters (timesteps, intervals, seed), and the algorithm-config knobs (learning rate, discount factor, ratio clip, batch size, …). Switching algorithm seeds matching defaults. Experiment paths (experiment.name / experiment.directory) default to values derived from the RL task slug and are shown read-only; toggle Override experiment paths to write custom values.
Network builder
The network builder is graph-first: it renders the network as a DAG of typed nodes on an inline canvas, with one canvas per role for the active topology.shareduses the same architecture for both heads. For PPO this yields a true shared-trunk model (SharedRLModelCfg); for SAC it instantiates two independent modules (policy + Q) with the same block spec, because SAC heads can’t share weights.separatedlets you author one graph per role (policy/valuefor PPO,policy/qfor SAC).
observation_input), dense layers (mlp, residual_mlp), transforms (activation, layer_norm), recurrent layers (rnn, gru, lstm), vision (cnn_backbone with NatureCNN / IMPALA presets), merge nodes (concat, residual_add), and a role/algorithm-aware Heads section that surfaces actor_head + value_head for PPO and actor_head + q_head for SAC.
Drag the canvas background to pan, drag a node to reposition it (positions persist in graphs[role].layout), and click an edge to delete it. Clicking a node opens an inline inspector below the canvas with the outputs picker (features_node_id / head_node_id) and the per-node editor. An Open graph builder button pops the same graph into a fullscreen dialog for serious authoring.
observation_input nodes pick a group and a subset of term names from the Observations tab. Each term shows read-only tensor metadata — an image-like / flat badge and a short kind label — so the builder can route image-like terms into a cnn_backbone and reject flat terms feeding one. Gaussian log-std controls (initial_log_std, min_log_std, max_log_std) live in the actor head inspector and apply only to the stochastic Gaussian policy.
Legacy
{kind, hidden_units, activation} and architecture: "mlp" / "residual_mlp" / "custom" specs are transparently promoted to a graph on load, so older tasks just render. At runtime, from runners.skrl.network_builder import build_skrl_model_cfg maps the persisted spec onto rlmodule.MlpCfg / ResidualMlpCfg / SharedRLModelCfg / SeparatedRLModelCfg for the skrl runner.Runtime: target, accelerator, and bundle
Open the Runtime tab to choose how the exported plugin runs:- Pick a
runtime_target—cyberwave-rl-experimental(plain local Python, no Docker) orcyberwave-rl(the dockerized stack). - Pick a
runtime_accelerator—gpuis the default;cpuis the smoke-friendly opt-in. - Pin a single
runtime_bundleversion (defaultlatest). The bundle shipsmjlab+ the chosen runtime +skrltogether, so callers only ever pin one version. - Configure the entrypoints (
scene_cfg_path,env_cfg_path/env_cfg_factory,rl_cfg_path/rl_cfg_factory, optionalregistry_path) inline. - Copy the matching Train locally snippet for the selected target and accelerator.
RLTask and forwarded to checkpoints and inference dispatch with a task → checkpoint → launch merge order.
- cyberwave-rl (Docker)
- cyberwave-rl-experimental (local Python)
Policies and inference
The Policies tab is the post-training counterpart to the Runtime tab’s “Train locally” snippets: upload a trained checkpoint, publish it as a controller, and run inference. Inference deliberately does not run MuJoCo — the worker loops Cyberwave joint state through the policy and publishes commands over MQTT. See the dedicated Uploading policies page for the full checkpoint upload, publish, and inference flow, and Online controllers for serving a published policy.Managed training: training_options.json
A task’s async adapter declares which training strategies cw_request_skill_teaching (or POST /api/v1/skill-teaching-requests) can dispatch by shipping training_options.json next to training.py. This file is the contract between your adapter and the platform: it names each supported strategy, the JSON-Schema-constrained hyperparameters it accepts, and the defaults applied when a caller doesn’t override them.
Fetch the resolved options directly instead of guessing the shape:
GET /api/v1/rl-tasks/{uuid}/training-options, cw_inspect_rl_tasks(operation="get"), or the proposal.training.options field on a teaching-request proposal all return the current, validated training_options.json for a task.Shape
The whole file is capped at 32 KiB and never executed — it’s parsed as data, so authoring it requires no backend source access.
Worked example
This is the realtraining_options.json for the imitation:behavior_cloning strategy shipped with the in-tree cyberwave-rl/examples/kinova_gen3_pick_place example task:
strategy_config, for example {"epochs": 10}; the backend merges it onto defaults (nested objects like augmentation are replaced as a whole, not deep-merged) and validates the result against parameters before freezing it into the job.
Validation errors are field-specific
Every check names the offending path instead of stopping at the first top-level shape mismatch, so you can iterate directly against the error:training_options.json.strategies must be an object keyed by strategy key (e.g. {"rl:ppo": {...}}), got array— a common first guess is authoringstrategiesas a list of strategy objects instead of a dict keyed by strategy key.strategies['rl:ppo'] is missing required field(s): defaults— a strategy entry must declare exactlyparametersanddefaults, nothing else.strategies['rl:ppo'].parameters.properties.epochs.type must be one of [...]— nested schema problems point at the exact property.strategies['rl:ppo'].defaults.epochs: 0 is less than the minimum of 1— invalid defaults report the dotted field path and the underlying JSON Schema message.
missing_training_input requirement reported by cw_request_skill_teaching or a teaching-request proposal carries this same message, so the specific field is visible without backend source access.
What’s in the exported ZIP?
The export is a minimal Python package that drops intocyberwave-rl/tasks/<task_id>/:
__init__.py— generated header that imports your registry (or the default one).- Your source tree — exactly as you authored it, with arbitrary nesting and free file structure.
scene_cfg.py(at the configuredscene_cfg_path) — the Cyberwave-generated SceneCfg, regenerated from the linked environment.scene_bindings.py— semantic role / twin / sensor aliases plusACTION_SPEC,OBSERVATION_SPEC, andRL_CONFIG_SPEC.actions.py(make_actions()) andobservations.py(make_observations()) — the named wrappers.task_export/— the runtime adapters those wrappers import.cyberwave_task_spec.py— the strict, canonical Python interchange for scene entities.cyberwave_export.json— the export manifest (records runtime target, accelerator, and version pins).- MuJoCo assets — one merged
models/mujoco_scene.xml, one per-entitymodels/<name>.xmlcarved out of it at build time (so eachEntityCfg.spec_fncanMjSpec.from_fileits own slice), and the sharedmodels/assets/meshes. registry.py— a fallback registry wiringenv_cfg/rl_cfgfactories intoregister_cyberwave_task, used only when you don’t ship your ownregistry_path.
Cyberwave-generated vs user-written
scene_bindings.py is the stable interface between Cyberwave-generated scene metadata and user-written training code. It exposes role aliases (ROBOT_ENTITY, OBJECT_ENTITY, SUPPORT_ENTITY), twin namespace prefixes, namespaced joint-position regex keys, actuator target regexes, camera attach paths, contact sensor names, and a body_pattern(entity, suffix) helper. Consume these constants from env_cfg.py so renaming a twin or moving a camera in the composer regenerates the bindings without touching MDP code.
The editor owns Scene entities, Actions, Observations, RL config, and all generated artefacts. Task-local Python — reward functions, reset / termination logic, metrics, command shaping, and custom observation functions — stays user-written under task_sources/.
Three execution-only modules in cyberwave-rl consume the persisted specs so user-written files only have to provide rewards, resets, terminations, commands, and custom observation functions:
API surface
All endpoints are under/api/v1/rl-tasks and require authentication.
The
RLTask response surfaces the action, observation, and RL-config specs as action_spec, observation_spec, and rl_config_spec JSON fields. It also surfaces the two command specs split by lifecycle — training_command_spec and inference_command_spec — each paired with an explicit lifecycle switch (training_command_setup_enabled / inference_command_setup_enabled). Unlike the actions/observations/rl-config specs (which have dedicated GET/PUT endpoints), the command specs and their switches are read from the RLTask response and written through POST/PUT /api/v1/rl-tasks/{uuid} (mirrored by the SDK’s set_training_command_spec / set_inference_command_spec).
The spec GET, the regenerate endpoints, and the export ZIP all produce byte-identical output for the same scene-entity rows, so re-reading and re-exporting are always consistent. If a body can’t be resolved while splitting the MuJoCo scene, the request fails with HTTP 400 naming the failing entity and the selectors that were tried — check the twin selectors and re-export.
Inference Commands
RL tasks can declare inference commands that define a live control surface for deployed policies. Each command declares:- A payload schema (typed fields like
target_position: vec3,object_position: vec3) - A source binding (transport + topic, e.g. MQTT)
- Position controllers: Set a target EE pose for reach tasks
- Waypoint controllers: Set the detected object center for inspection trajectory generation
CommandTermCfg subclass (module.symbol) implemented in the task’s env_cfg.py. Add and edit terms in the editor’s Commands tab (term name, module.symbol, lifecycle fields, kwargs) or from the SDK (set_training_command_spec / set_inference_command_spec); the term’s Python implementation lives in the task’s source files.
The Commands tab has two explicit lifecycle switches — Training command setup and Inference command setup. Turning one off clears its spec by design (a disabled lifecycle is “deliberately off”, not a forgotten upload); turning it on requires at least one code-backed term. From the SDK, pass enabled= to set_training_command_spec / set_inference_command_spec to set a switch explicitly, or omit it and the switch is derived from whether the spec has commands.
Where to go next
RL Task Walkthrough
Build the OpenArm pick-up task end-to-end in Python, from empty workspace to deployed policy.
Uploading Policies
Upload trained checkpoints, publish them as controllers, and run inference.
Online controllers
Serve a published policy as a live controller.
RL Task Scene Sensors
Configure cameras and contact sensors on scene entities.
Simulation
Build the MuJoCo environment that backs your RL task’s SceneCfg.