Microduck software architecture: daemons, 50 Hz control and policies

How Microduck works under the hood: the RK3566 board, robotd 50 Hz control loop, Unix-socket daemons, ONNX policy runtime and the open-source RL training loop.
Oct 19, 2026

Microduck robot on a light plinth with abstract system paths and sensor nodes

Short answer: Microduck is not a ROS 2 robot. Its published stack is a Rust workspace of small services that speak JSON-RPC over Unix sockets. robotd owns the 50 Hz control loop and the only motor-bus writer; ONNX policies run inside that loop, while training happens off the robot in mjlab and MuJoCo Warp.

That design is the interesting part. The duck looks like a toy, but the software boundary between a client intent and a motor command is deliberate, documented and testable. This page is a map of the published architecture, not a claim that every design page is final. Where the official docs label a section as a draft, treat it as the current public design rather than a permanent product promise.

The system in one view

gamepad / browser / Python / robotctl
                  |
          JSON-RPC over Unix socket
                  |
   robotd ---- safety ---- 50 Hz control loop ---- motor bus
      |            |
      |            +---- camera, LiDAR, two IMUs, ToF
      |
      +---- ONNX policy runtime (walk, stand, skills)

configd  updaterd  btd  padd  mediad  tofd
   |         |      |     |       |      |
 identity  releases BLE gamepad WebRTC depth

The client sends a request such as “walk forward”, “look there” or “run this skill”. It does not send a stream of raw joint positions. robotd applies the safety limits, runs the selected policy at 50 Hz and writes the resulting targets to the servo bus.

Hardware under the software

ComponentPublished detailWhy it shapes the software
Main boardRockchip RK3566-class boardRuns the Linux services and the policy runtime on the robot
Motors15 servosThe mechanism has many controlled joints, so bus and safety handling matter
SensingCamera, LiDAR, two IMUsPerception and balance inputs arrive from more than one service
DepthToF sensor served by tofdGives the robot a separate depth path from the colour camera
Control rate50 HzThe loop has 20 ms to read state, apply intent and write a command
ConnectivityWi-Fi and BluetoothAdds remote clients without putting control on the network path

The control loop runs locally because a network round trip is a terrible way to keep a biped upright. Remote clients are allowed to ask for an intent; they are not allowed to become the balance controller.

The seven daemons

ServiceOwnsWhat it does
robotdControl loop and motor busReads the robot, applies safety, runs the selected policy and writes the servos
updaterdSigned releasesInstalls, health-checks, rolls back and pins software; it cannot command a motor
configdIdentity and Wi-FiStores the robot name and pairing data, and handles network provisioning
btdBluetooth front doorGives a phone or laptop a BLE control path
paddGamepadTurns controller reports into intents
mediadCamera and remote mediaRuns the WebRTC/streaming side and relays the control lane
tofdDepth sensorServes time-of-flight depth frames to the rest of the stack

The split is by ownership, not by fashion. Wi-Fi provisioning has to work when robotd is dead, so it cannot live in robotd. A policy load must not be able to rewrite the update system, so updaterd has no motor authority. btd is a transport, not a second source of configuration.

One JSON-RPC contract

Every local client uses the same call vocabulary: the browser console, robotctl, a Python session and the gamepad path all ask the owning service for the same operations. A state subscription, a policy load and a robot.do request do not each invent a private protocol.

That gives the stack a useful property: a new client can be written against the documented calls without patching the daemons. It also gives the system a clear authority model. Only robotd writes motors; everything around it asks for intents or reads state.

The 50 Hz control chain

The loop is one process, one serial bus and one 50 Hz cycle. A control tick roughly does five things:

  1. Read the latest sensor and servo state.
  2. Take the newest client intent and apply the safety policy.
  3. Build the policy observation: 61 values in the current contract.
  4. Run the selected ONNX policy and receive 14 action values.
  5. Convert those actions into motor targets, apply limits and write the bus.

The safety layer owns the only motor I/O. A deadman timeout, a fall verdict, a joint limit or a missing servo can therefore stop the robot without asking a client for permission. The architecture document is explicit that nothing above safety can write to a motor.

How ONNX policies fit in

The published training stack exports policies to ONNX at 50 Hz. At runtime the daemon holds named slots for default behaviours and a table of skills for requested one-shots. The 61-value observation contract is shared across the current policy family, which is what makes a hot swap possible without restarting the control loop.

The observation is 48 proprioceptive values plus three command groups: a three-value twist, a four-value head pose and a six-value body pose. A policy that does not use one group zero-pads it rather than changing the shape. The normalizer is baked into the ONNX graph, so a hand-converted checkpoint is not equivalent to an official export.

Slots and skills are described in the policy manifest documentation. The practical install and reset workflow is on the Microduck policies page.

Training, not runtime

The robot runs policies; it does not train them. Training happens in microduck_rl, where mjlab supplies the learning environment, MuJoCo Warp provides simulation and PPO updates the weights. The published recipe includes:

  • a BAM actuator model for the Dynamixel XL330, including voltage behaviour and friction;

  • domain randomisation over battery voltage, voltage sag, command delay and friction;

  • optional backlash variants with gear play modelled at each joint;

  • a shared observation layout across tasks;

  • export through the official script so the normaliser travels inside the ONNX

    .

The practical GPU requirement is for training, not for running a finished robot. A local CUDA machine or Hugging Face Jobs are both documented paths. The training guide covers the workflow, and the simulator guide covers the no-hardware entry point.

Updates and recovery

updaterd installs a signed release, restarts the relevant services and runs a health gate. If the new release fails the gate, the updater can return to the previous installed release. A release can also be pinned so an automatic check does not move it.

The design separates “software is healthy” from “a pack is low” or “a motor is hot”. Those values are reported, but they must not become the sole reason to roll back a release; otherwise a robot with a low battery would reject a good update and then judge the replacement on the same pack.

For a user, the safe update sequence is: let the connection drop, reconnect, run duckctl update status, and use duckctl update rollback if the robot does not become healthy. The troubleshooting guide has the full command sequence.

What the architecture is not

  • It is not a cloud robot. The control loop and policy runtime are on the robot.
  • It is not a ROS 2 graph. The published runtime is a Rust workspace with its own JSON-RPC contract.
  • It is not open hardware. The software and training code are Apache-2.0; the simulation STL meshes are CC BY-NC-SA 4.0; complete hardware design files are not published.
  • It is not a sealed appliance. The repositories and design notes are public, but changing the control path can remove the safety boundary that makes the stock behaviour predictable.

If you are writing an integration, start with the documentation index, then keep your code on the intent side of robotd. If you are writing a paper or a technical article, cite the official repository and design pages rather than treating this summary as the source of record.

Microduck architecture FAQ

Is Microduck based on ROS 2? No. The published robot stack is a Rust workspace whose services communicate through JSON-RPC over Unix sockets. It does not use ROS 2 as its runtime.

Can I run my own daemon on Microduck? The stack is open source and its IPC contract is documented, so additional clients and services can use the same calls. Only robotd should command the motor bus; bypassing that boundary removes the safety layer.

Why does Microduck use several daemons? Each process owns one failure domain. robotd controls motors, updaterd handles signed releases, configd owns identity and Wi-Fi, btd exposes Bluetooth, padd reads the gamepad, mediad streams media and tofd serves depth.

Can a Microduck update brick the robot? The updater installs signed releases, restarts into them, checks health and can roll back. The public design also keeps the update path unable to command motors. A power cut is still a physical failure case, but the update mechanism is built around recovery.

Where does policy training run? Training runs on a CUDA workstation or through Hugging Face Jobs using mjlab with MuJoCo Warp and PPO. The exported ONNX policy then runs on the robot at 50 Hz.

Where to go next

Microduck software architecture: daemons, 50 Hz control and policies