axol serve and runs each operation in-process inside that server, so it’s a drop-in alternative to the per-command CLI workflow.
Start the server
With the one-command install, the server is already running on the robot machine (the computer wired to the robot, with the ZED cameras attached) as a root systemd service that starts at boot and restarts automatically — open axol.almond.bot and connect to the host. On a development install, start it yourself:/control. The layout is responsive, so you can run it from a phone or tablet on the same network, not just a desktop.
For a development install, the local UI must be built once (
cd web && npm install, then npm run build for the axol-vr-client and app workspaces) — or skip the build and use the hosted UI at axol.almond.bot, which connects to your axol serve directly. See Development install.Connections
The top of the panel has three tiles — Axol Host, Axol, and Mantis — each with a colored status dot. Axol and Mantis share the host’s idle telemetry connection: connecting one switches the live CAN health view to that hardware. The Mantis tile checks the two grippers oncan_mantis_l/r; switching back to Axol restores the arm-motor view. Click a tile’s title to open its settings: the Axol and Mantis tiles open their own device settings, and the host tile opens the general (shared) settings.
The Axol and Mantis tiles also carry the one system-wide choice of which hardware every operation runs on: the selected device’s tile shows a selected marker and the other offers Select. There is no per-operation Mantis toggle: teleop and data collection follow the selection, the idle link auto-connects to the selected device, and while Mantis is selected only Teleoperation and Collect data are listed — the Axol-only operations (gravity comp, waypoints, replay, run policy, DAgger) return when Axol is selected again. The selection is stored with the shared settings on the host, so every operator device sees the same choice; it is locked while an operation is running.
Axol Host
The Axol Host is the machine runningaxol serve — the computer wired to the robot, with the ZED cameras attached. Press Connect on the tile to open the host dialog and enter its address (just the IP — port 8001 is assumed); if the host can’t be reached the attempt gives up after a couple of seconds with a can’t reach the host message instead of leaving the tile stuck on Connecting…. When connected, the tile shows the host’s name, its installed release version (e.g. axol v0.1.2), and a green dot. Teleop and gravity comp need only this connection.
A connected tile carries three buttons:
- Restart reboots the robot host (
shutdown -r now) behind a confirmation dialog. The panel loses its connection while the host reboots — reconnect once it’s back up, typically under a minute. - Shut down powers the host off (
shutdown -h now), also behind a confirmation. Turning the robot back on requires physical access to it. - Disconnect (the unplug button) drops this browser only — a client-side disconnect that leaves the server, the robot link, and any running operation untouched, so another operator driving the same host isn’t affected.
axol serve escalates with sudo -n, so a host with no passwordless sudo reports that it couldn’t and stays up.)
Updates
When a newer release than the installed one exists, a banner appears above the tiles showing the version jump (e.g.v0.1.2 → v0.1.3) with a link to the release notes. Press Update to apply it: the host reinstalls at the new release, re-provisions its system dependencies, and restarts once idle — the button is disabled while an operation is running, since applying an update restarts the server. Nothing updates without that button press.
The same banner area also warns when the control panel UI and the host backend are running different code — the web bundle bakes in the git commit and release version it was built from, the host reports its own via /api/info, and a mismatch raises the warning (a released host is compared by version, a dev checkout by commit). This catches a stale hosted tab, or an unbuilt local web/app/dist, silently driving a newer or older backend; clear it by updating the host to the matching release or rebuilding the local UI.
Axol and Mantis
The panel inventories the host’s CAN interfaces when it comes online and while the link is idle. A new dual-channel hub may initially appear only as anonymouscan0/can1 interfaces; before connecting, the root server runs the same driver/setup identification path as axol can.setup, safely probes the attached shoulder motor and Mantis trigger, saves the proven role, and assigns the persistent interface names. The probe is bounded and shared across browser tabs, so an unpowered or ambiguous hub is never guessed or repeatedly reset. In that case, power the attached hardware and press Retry CAN identification; use interactive axol can.setup in a terminal only when the role still cannot be proven automatically. Unrelated arbitrary SocketCAN interfaces are never classified as Axol or Mantis automatically.
After discovery, if only Axol or only Mantis is present, the panel connects that hardware automatically; if both are present, the profile selected on its tile wins. A present-but-down interface still qualifies because Connect brings it up. On a previously configured host, the exact USB serial saved in that profile’s udev rules also qualifies before gs_usb has loaded or created the named interfaces; connecting then restores the driver and names. A hub that enumerates shortly after page load is picked up by the inventory poll. A failed automatic connection is retried twice with backoff; after that, the tile’s Connect button is the manual fallback.
A successful manual Disconnect pauses automatic reconnect for that exact hardware profile and saved CAN map across every open or newly opened panel. An explicit Connect, selecting a different detected profile on its tile, changing the saved map, or restarting axol serve starts a new automatic-connect intent. A successful manual Connect wins within the panel where it was clicked until that panel’s device selection changes; another control panel can still make a later selection. This is intentionally last-writer-wins between operators, not a browser lease.
- Enables the CAN interfaces — running
can.setupautomatically the first time when an Axol Hub adapter is used and the host has not been configured. Axol channel overrides live under Axol settings → Robot; Mantis channel mapping lives under Mantis settings → CAN. A non-hub SocketCAN adapter is brought up directly. - Pings the active profile once per second while idle: all 16 Axol motors, or the two Mantis grippers. Each square combines CAN reachability and motor error status; hover it for details and temperature.
motor power lost status instead of appearing to keep running, and the affected motors read unknown rather than a frozen pre-cut value (unknown motors are not counted as faults; unreachable ones still are). If the operation could not confirm the motors went torque-free, the panel keeps the robot reserved and shows a Re-check motors button: restore power and press it to re-probe the motors and release the reservation (it refuses while any motor still answers under torque). Host restart and shutdown are never blocked by this reservation.
The server runs as root under the one-command install’s systemd service, so CAN bring-up needs no password. While a task is running the 1 Hz polling pauses and the robot is handed to the task.
For Mantis, press Select on the Mantis tile — see Mantis Hardware for the rigs and hub. Teleop needs only CAN and never tracks: it drives the grippers straight from the rig triggers. Collection uses the pose source set up under Mantis settings → Tracking (Mantis Tracking covers the Quest / Lighthouse / Ultimate setup flows and the start gesture); the panel launches and owns the selected tracker bridge and its two trigger readers, and a missing binding, incomplete channel mapping, or backend failure is a visible blocker rather than an apparently running session with no input. Teleop enables the physical grippers immediately; collection keeps them motor-disabled between takes. Mantis is not available for Collect DAgger or Run Policy.
Settings
Almost nothing you configure is actually per-run — camera assignment, arm stiffness, teleop rate, recording fps and codec, the inference server address — so it all lives in one Settings card directly on the page below the connection tiles, split by what it configures: Axol settings, Mantis settings, and General settings. Click a connection tile’s title to open its scope (the host tile opens General), or switch scopes with the Axol / Mantis / General control in the card header. Axol settings — the arms:
Mantis settings — the handheld rigs:
General settings — the host, shared by both devices:
Values are stored on the Axol host (
~/.almond/settings.json) — not in your browser — so they’re shared by every operation and every operator device, and they’re folded into each operation start automatically (a per-run field always wins over a shared one). Edits stage locally until you press Save (or Discard). Unset fields show the config’s own default as a placeholder; edited ones get a reset button. Export / Import in the card footer move the whole settings file as JSON for backup or copying between robots.
Every setting on these tabs — and every field under Advanced — is documented with its default and meaning on each operation’s Config Parameters page (e.g. Teleoperation). The Settings File page maps each panel setting to the config key it drives, which is what you’re editing in an exported settings.json.
Cameras
Data collection and policy inference also need ZED cameras attached to the host. Axol cameras are assigned under Axol settings → Cameras; the two Mantis slots live under Mantis settings → Cameras. Both camera panels detect the cameras plugged into the host and show each serial, automatically detected type (stereo ZED X / mono ZED-X One), model, and a live preview frame, so you can identify the physical view before assigning it. The Axol panel offers overhead / left arm / right arm. The Mantis panel offers only Left Mantis / Right Mantis and stores that mapping separately, so changing it never overwrites Axol. Press Refresh to re-query the device list and grab previews, or Restart daemon after plugging/unplugging a GMSL camera so the daemon re-scans. Previews are unavailable while an operation owns the cameras. These two hardware-layout panels are the single source of truth for camera settings. Each controls two independent branches:- Stream — what is relayed to the VR headset over WebRTC (used by teleop and collect-data). Run policy relays no video, so it ignores this branch entirely; collect DAgger does relay a headset view for the operator, but at the Record resolution below, so it too ignores this branch.
- Record — what is written to the dataset during data collection (downscaled on the GPU’s VIC, independently of the stream). Because run policy and collect DAgger feed the policy from what’s recorded rather than from a headset stream, they capture their policy observations at this resolution — the size the dataset was recorded at and the policy was trained on — rather than grabbing a larger headset frame they’d only throw away.
SVGA / HD1080 / HD1200, or Off to disable that branch globally) — so the headset can see a high-resolution feed while the dataset stores a lighter SVGA one, or vice versa. Each assigned camera then has independent Stream and Record controls: a single on/off for a mono camera, or per-eye L / R checkboxes for a stereo one (none checked = off for that branch). So you can stream a camera without recording it (or the reverse), choose which mono cameras feed the headset vs. the dataset, stream both eyes of a stereo camera for depth while recording only one, or turn either branch off entirely.
An operation that records a dataset requires at least one assigned camera participating in Record; an assigned stream-only camera does not satisfy Start. Conversely, a camera disabled in both branches is omitted and does not create a false “not detected” blocker.
A stereo overhead captures both eyes from one grab; with both eyes selected it’s rendered per-lens in the headset (streamed as one side-by-side feed) and recorded as the two observation keys overhead_left / overhead_right. A single eye — or a stereo wrist — is exposed under the plain slot name, so it costs and reads exactly like a mono camera.
The assignments and stream/record selection persist on the host with the rest of the shared settings, so every operation and every operator device sees the same camera setup. A Mantis run automatically passes only its left/right Mantis mapping to data collection; an Axol run uses the Axol mapping. Teleop streams whichever cameras have streaming enabled; collect data, run policy, and collect DAgger need at least one camera with recording enabled (they error early otherwise, since a policy needs the cameras it was trained on). Starting any of those three also re-verifies that every participating camera assigned to the active hardware profile is physically present and blocks the start with an alert naming the missing serial if one isn’t — reconnect it, then Refresh (or Restart daemon) in the matching Axol or Mantis Cameras panel. Teleop streams whatever is configured and is never blocked by this check.
Quest USB
The Quest tab (under General settings) manages the optional Quest-over-USB link, which streams controller poses over a USB cable for lower latency. When a headset is plugged into the host and authorized (accept the Allow USB debugging? prompt the first time), the host sets up theadb reverse tunnel automatically and turns green (Controller over USB); the Connect button is a manual fallback / reconnect. Camera video keeps using the LAN — USB carries poses only. Every frame is mirrored over cable and Wi-Fi with one producer id/sequence; the first copy wins and the duplicate is discarded. A dropped cable therefore fails over immediately with no reconnect, and a restored low-latency cable naturally wins again.
The tab also carries the headless Keep awake / Restore buttons: with the headset plugged in and authorized, Keep awake disables its proximity sensor over adb so the VR session keeps running with the headset set down, and Restore brings normal sleep behavior back (a headset reboot also resets it).
Operations
Panel recordings go to the Dataset root setting (default: the LeRobot
cache,
~/.cache/huggingface/lerobot of the account that ran the installer)
and are handed back to that account after every episode save, so the same
datasets are visible from the panel and the CLI.axol-rt Rust realtime core. The core owns CAN at 240 Hz while the operation streams targets; there is no Python-loop backend toggle.
Before you can start
The Start button stays disabled until everything an operation needs is ready, and a “Before you can start” list spells out what’s missing — e.g. Connect Axol, Fix motor fault: R elbow — over temperature, Assign at least one camera, Identify left + right Lighthouse trackers, Fix Ultimate private shared-map AP setup, or Add an approved factory or measured tracker-to-TCP transform. Fix tracker setup in Mantis settings → Tracking (Mantis Tracking); transforms are a file on the host. Tracker setup only gates Mantis collection — Mantis teleop never tracks (it drives the grippers straight from the rig triggers) and starts on CAN alone. The panel refreshes the same readiness the operation start uses. Per-run values (repo ids, task names, …) are remembered by your browser so they reappear on reload; everything else persists on the host with the shared settings.Running, logs, and stopping
Starting an operation streams its logs into the console at the bottom of the page and shows a status badge. The console captures not just the operation’s Python output but also native-library and subprocess output — the ZED SDK, GStreamer, and CUDA, plus the spawned video-relay and recorder processes — so a camera or ZED failure surfaces its actual reason in the UI instead of a bare “exited”. The console auto-follows new output only while you’re scrolled to the bottom; scroll up to read earlier lines and it stops yanking you back down. While you’re scrolled up, a floating arrow button in the lower-right of the log area jumps you back to the latest line and re-engages auto-follow. The download button in the Logs header saves everything currently in the console to a timestamped.log file (disabled when there’s no output) — handy for attaching to a bug report. Press Stop to end it — the request returns immediately and the badge and button flip to Stopping… while a background watchdog tears the op down (force-killing its child subprocesses after a short grace if a save or startup is still blocking). The panel polls until the op is truly gone, then the robot link returns to idle 1 Hz polling. Robot Disconnect likewise stops the running op and waits for it to finish tearing down before releasing the robot link; the host Disconnect, by contrast, is a client-side drop that leaves any running op in place server-side.
Operator view
While teleoperation, collect data, or collect DAgger streams camera video to the headset (camera slots assigned), the control panel mirrors those live camera feeds, so you can watch the robot’s point of view on the dashboard and not only in VR. The panel joins the running operation’s video stream as one more view-only client and shows the same tracks the headset receives — no extra work for the robot, and the active camera layout still decides what streams. A side-by-side stereo overhead is shown cropped to its left eye. (A sim or Jelly only teleop run shows no mirrored feeds — neither relays headset video.) The fullscreen toggle on the camera-feeds header expands the feeds to a full-screen operator display that fits without scrolling; Esc or Exit fullscreen leaves. If the feeds never connect, the panel offers a one-tap button to authorize the stream server’s self-signed certificate — the same one-time step the VR app uses.Episode controls
While Run policy, Collect DAgger, or Collect data is live, an Episode control box appears in the operation card, led by a colored phase badge (Ready, Recording, Saving, Resetting, …) with the episode number and saved count. The box is driven by the host, so it shows the same phase on every connected computer, not just the tab that started the run. To end the run entirely, use Stop in the card header. For Run policy and Collect DAgger (which share the same episode controls — collect DAgger adds the VR grip intervention flow on top):
These mirror the
s / r keystrokes and the Enter prompts the run-policy CLI reads from stdin.
For Collect data, the buttons provide the same episode decisions for Axol and every Mantis source, so managed Lighthouse/Ultimate can collect with no headset at all. Pair them with the mirrored camera feeds:
For Axol or Quest Mantis, the VR controller stays live throughout and whichever panel/headset input arrives first wins. Managed Lighthouse/Ultimate instead starts a take by squeezing both rig triggers together and releasing (this also engages tracking); while recording, either trigger x3 ends and saves, x4 ends and discards — the same outcome as Discard episode. The panel’s Start recording works only while the rig is already engaged. Headset save/discard popups and countdowns are mirrored into the panel when a Quest is the active pose source.
Next steps
Diagnostics dashboard
Live motor telemetry, health tiles, and diagnostics scripts — the sibling
/diagnostics page.serve reference
Flags, TLS, and what the server exposes.
VR interface
The in-repo WebXR app that drives teleop and recording.
