BusyBox¶
BusyBox is a 3D-printable panel of everyday physical controls — buttons, switches, sliders, a knob, and pluggable wires. It was designed at Microsoft Research as a benchmark for affordance generalization: whether a robot foundation model that has learned to press one button can press a button it has never seen, on a panel it has never seen.
- BusyBox: Benchmarking Affordance Generalization — the CoRL 2025 Eval & Deploy workshop paper.
- microsoft/BusyBox on GitHub — CAD files, bill of materials, firmware, and the reference data-collection pipeline.
Armnet runs an instrumented BusyBox: the panel is wired to a Raspberry Pi that reports the live position of every control. That is what makes it an automated environment — the box knows whether the switch actually flipped, so it scores your rollout itself rather than asking a human what they saw, and the arm stages the next episode rather than waiting for someone to tidy up.
Why it is a good benchmark¶
The controls are deliberately generic. A toggle switch on a BusyBox looks and behaves like a toggle switch on a washing machine or a light fitting, so a policy that can work the panel is demonstrating a skill that transfers, not a scene it has memorised. The modules are also reconfigurable, so the same set of affordances can be presented in arrangements a policy has not been trained on.
For Armnet specifically the practical advantage is consistency: every episode is scored the same way by the same sensor, rather than by whichever person happened to be watching. Pair that with a reset the arm performs itself and a benchmark run needs no operator at all — it can go all night.
How scoring works¶
Install armnet-busybox in your image
Scoring and resets happen through the robot your container is driving, so the environment has to be installed alongside your job:
RUN pip install armnet-busybox
There is nothing to import — ctx.cell.instrument(robot) finds it through
the armnet.environments entry point, and everything below then happens on
its own. The managed CLIs (armnet-policy-eval and friends) already include
it. Leave it out and the job still runs, but a person has to judge and reset
every episode from the FMS.
Each task carries a goal — "the left toggle is up", "the knob reads 3", "the green button was pressed, then the yellow one". After every control tick the cell reads the panel state and compares it against that goal. When the goal is met the episode ends immediately and is recorded as a success.
Two details are worth knowing:
- Buttons are momentary. By the time an episode ends the button has sprung back up, so the box counts presses rather than sampling a held-down state.
- A rollout can become unwinnable. If an ordered task presses the buttons in the wrong order, the box can tell the goal is no longer reachable and ends the episode early as a failure instead of running down the clock.
How resets work¶
Resets are performed by the arm, not by a person. Between episodes the cell works out what the panel needs to look like for the task about to run and replays a stored motion plan to put it there. Controls already in a valid starting position are left alone — a slider task accepts any detent except the goal, so most episodes start without the arm staging anything, and the ones that do start from a different distance each time.
A motion plan may temporarily move a rail to the pose where its keypoints were taught. That pose is reset staging only: after the arm returns to rest, the runtime moves every rail directly back to the job's requested position before the next episode starts. It does not home again unless the edge detects that its recorded position is no longer safe to trust.
Each motion is taught once, by a person driving the arm through it, and the plans are being recorded task by task. Until a task has one, the cell asks an on-site operator to stage the panel, so that task runs at operator pace rather than unattended. Nothing about your job changes when a plan lands — the same submission simply stops waiting.
Buttons never wait either way: they are momentary and spring back on their own, so a button run is unattended today.
Tasks¶
The BusyBox cell is single-armed, so submit these with
--armnet.embodiment=lerobot/so-101. Naming the task is what routes your job
here; you never address the cell directly.
Every task listed below is scored by the panel itself. Where they differ today is how much staging they need between episodes, which is what the reset section above describes.
Start with the buttons
A button run needs no staging at all, so it is fully unattended and can go
all night. push_green_button is the one used throughout these docs, and it
has both a dataset and a trained policy to go with it.
Buttons¶
Momentary — the button springs back, so the panel counts the press.
| Task slug | Goal |
|---|---|
push_green_button |
Push the green button |
push_red_button |
Push the red button |
push_white_button |
Push the white button |
push_yellow_button |
Push the yellow button |
Switches¶
Two toggle switches, each with an up and a down target. busybox_left_toggle_up
is staged by the arm today, because the motion that flips that switch back down
is the first one to have been taught.
| Task slug | Goal |
|---|---|
busybox_left_toggle_up |
Turn the left toggle switch up to "On" |
busybox_left_toggle_down |
Turn the left toggle switch down to "Off" |
busybox_right_toggle_up |
Turn the right toggle switch up to "On" |
busybox_right_toggle_down |
Turn the right toggle switch down to "Off" |
Sliders¶
Two sliders with five detents each. An episode may start from any detent that is not the goal, so the arm covers a different distance each time.
| Task slug | Goal |
|---|---|
busybox_move_left_slider_to_1 … _to_5 |
Move the left slider to position 1–5 |
busybox_move_right_slider_to_1 … _to_5 |
Move the right slider to position 1–5 |
Knob¶
A six-position rotary knob.
| Task slug | Goal |
|---|---|
turn_knob_to_1 … turn_knob_to_6 |
Turn the knob to position 1–6 |
Wires¶
Four coloured wires that can be plugged in or pulled out.
| Task slug | Goal |
|---|---|
insert_blue_wire / pull_blue_wire |
Insert or pull the blue wire |
insert_green_wire / pull_green_wire |
Insert or pull the green wire |
insert_red_wire / pull_red_wire |
Insert or pull the red wire |
insert_yellow_wire / pull_yellow_wire |
Insert or pull the yellow wire |
Mixed¶
| Task slug | Goal |
|---|---|
busybox_mix |
Draw a different single-control task each episode |
Not yet available
busybox_mix is intended to spread one run across the whole panel — a
different button, toggle, slider or knob position each episode — so a result
says something about the box rather than about one spot on it. The slug is
reserved and accepted by the API, but the per-episode task selection behind
it is not deployed, so do not use it for a benchmark yet.
Bimanual¶
Not currently available
The BusyBox panel is attached to a single-arm cell today, so these two-arm tasks cannot be scheduled. The slugs are listed for reference and will become available again when a bimanual BusyBox cell comes online. Submitting one now will not find a matching cell.
Two-step tasks that use both arms in order.
| Task slug | Goal |
|---|---|
busybox_buttons_bimanual |
Press the green button with the left arm, then the yellow button with the right |
busybox_switches_bimanual |
Flip the top switch on with the left gripper, then the bottom switch on with the right |
busybox_button_slider_bimanual |
Push the green button with the left arm, then move the top slider to position 2 with the right |
busybox_slider_knob_bimanual |
Move the top slider to position 4 with the left arm, then turn the knob to position 3 with the right |
busybox_slider_switch_bimanual |
Move the bottom slider to position 3 with the left arm, then flip the top switch on with the right gripper |
busybox_switch_knob_bimanual |
Flip the bottom switch on with the left gripper, then turn the knob to position 5 with the right |
Reference datasets¶
Three teleoperated datasets are published on the Hugging Face Hub. All three are recorded on the Armnet BusyBox cell, so a policy fine-tuned on them sees the same camera angles and the same panel it will be evaluated against.
| Dataset | Use it to |
|---|---|
villekuosmanen/busybox_push_green_button |
Fine-tune a single-task policy for push_green_button. The smallest useful starting point, and the one to use if you just want to confirm your training recipe works end to end. |
villekuosmanen/busybox_flip_left_switch_off |
Fine-tune a single-task policy for busybox_left_toggle_down. A good second task for checking whether your recipe transfers between affordance types. |
villekuosmanen/busybox_multitask |
Train a language-conditioned multi-task policy. Covers every task above, so the instruction string is what selects the behaviour. |
Start from one of the single-task datasets to get a policy working end to end,
then move to busybox_multitask once you want one checkpoint to handle the whole
panel.
Two diffusion policies trained on the single-task datasets are published as
well, so you can run a real evaluation before training anything of your own:
busybox_push_green_button_diffusion
and
busybox_flip_left_switch_off_diffusion.
Run an evaluation¶
armnet-policy-eval \
--policy.path=villekuosmanen/busybox_push_green_button_diffusion \
--armnet.embodiment=lerobot/so-101 \
--armnet.task=push_green_button \
--eval.n_episodes=10 \
--armnet.episode_time_s=30
Because the panel scores itself, pc_success in the result comes straight from
the sensors — you do not need to watch the run or review the video afterwards.
See Also¶
- Environments and Tasks — how tasks and environments fit together, and the manual environment.
- Evaluate Policies — the full
armnet-policy-evalreference.