From a terminal
The CLI path: install lucen, sign in with a key, pull and push a dataset, import a run, train on a runner you start and gate it in sim — every command run verbatim.
The same hub from the lucen CLI: a dataset pulled and pushed back as your
own, a run the Training Coach can read, a policy trained on a runner you
start, and the sim gate run on your own CPU. The Quickstart
walks the browser's path; what only a signed-in person may do — mint a key,
approve a policy onto a robot — stays in the browser on purpose. The same
engine is a Python API for scripts and notebooks: From Python
(from lucen import Hub).
Every command on this page was run verbatim before it was published, and
docs.test.ts fails the build if this file grows a command that was not.
Where a command names this hub, it is printed with the address of the hub
serving this page: https://api.mousemouse.ai for the API, https://mousemouse.ai for the web app.
Before you start
You need uv and an account on this hub — sign in with the button at the top right.
The lucen packages are not on PyPI — if a package by that name turns up
there, it is not ours. This hub serves its own: the install line below fetches
them from https://api.mousemouse.ai, built from the same version as the hub, and points the
CLI at this hub. It uses uv when you have it, else Python 3.12+ with venv.
1 · Install the CLI
curl -fsSL https://api.mousemouse.ai/install.sh | sh -s -- cli
lucen version
lucen 0.1.0
2 · Create a key and sign in
Every programmatic call carries a scoped API key. Scopes nest:
read ⊂ write ⊂ train, so a key that can push data cannot spend money on
GPUs unless you gave it train.
Create one in API keys. Only a signed-in browser session can do
that, by design: no API key may mint another key, or a leaked write key could
promote itself. Then tell the CLI which hub to use and paste the key when it
asks:
lucen auth login --url https://api.mousemouse.ai
API key:
Signed in as you (scopes: read, write, train) at https://api.mousemouse.ai
Saved to /Users/you/.config/lucen/credentials (mode 0600)
The CLI has no built-in hub: until a URL is stored (or LUCEN_API_URL is
set) every command refuses and says how to set one, rather than sending your
key to a guessed address. The key never appears on a command line — there is no
--api-key flag anywhere. It lands in ~/.config/lucen/credentials at mode
0600 and the CLI refuses to read that file if the permissions are looser.
LUCEN_API_KEY and LUCEN_API_URL override it for CI.
lucen auth whoami
you · lucen_sk_2UBHN-Vl… · scopes: read, write, train
https://api.mousemouse.ai (key from file)
2b · Find your way: quickstart, status, open
lucen --help is grouped by task — Get started, Robots, Data and
models, Train and test, Run on a robot, Lab — and lucen <group> with
no subcommand prints that group's help. Three commands exist only to orient
you. lucen quickstart prints the next three to five commands for where you
are and runs none of them; a flag names the situation (--robot-file PATH,
--robot OWNER/SLUG, --hf DATASET, --no-file), or it asks on a terminal:
lucen quickstart --robot lucen/laika
MouseMouse quickstart — your robot is on the hub (lucen/laika); the MouseMouse hub at https://api.mousemouse.ai.
1. lucen runs create --template ppo_mujoco --robot lucen/laika --executor worker
Queue a PPO run for this robot, driven by its interface; it waits for a runner.
2. lucen worker run
In a second terminal: claims the run, trains on this machine (free) and publishes the policy as a model repo (needs the rl extra: install `cli[rl]`).
3. lucen rollout you/MODEL --robot lucen/laika --battery smoke --local
Gate the published policy in MuJoCo on your CPU; the hub scores every cell (MODEL is the repo the runner printed).
4. lucen open you/MODEL
The model page: lineage, scorecard, and Run on a robot (a person approves).
Nothing was run or copied. `you` is your handle (`lucen auth whoami` prints it).
lucen status is one screen — who you are, which MouseMouse hub, and what is
yours right now, from the same /v1/me/overview the web's workspace rail reads:
lucen status
you · key lucen_sk_2UBHN-Vl… from file · scopes read, write, train
MouseMouse hub https://api.mousemouse.ai · lucen-api 0.1.0 (dev) · web https://mousemouse.ai
Runs 0 queued · 0 running · 0 in all
Rollouts 0 queued · 0 running · 0 in all
Endpoints 0 queued · 0 running · 0 in all
Approvals none waiting
Robots 0 connected · 0 online (seen within 60 s) · 0 running a policy
Plans 0 active · 0 waiting on approval
Spend $0.0000 on models today — Since midnight UTC you used 0 coach calls, …
lucen open turns a repo, a run id or a rollout id into its page and opens
it in your browser; --print just prints the address, which the CLI learns
from the hub itself:
lucen open lucen/laika --print
https://mousemouse.ai/lucen/laika
Every command takes --json, and an error under --json is one JSON object
on stdout. Without it an error is always three lines — what happened, what the
hub said, and Fix: — with the exit code saying which kind: 1 your mistake
or the hub's answer, 2 refused (no key, a missing scope) or cancelled, 3
the hub could not be reached. Shell completion is lucen --install-completion
(or lucen completion zsh to see the script).
lucen completion zsh
3 · Pull a dataset
lucen pull lucen/laika-demo ./laika-demo
Downloading 10 file(s), 51.0 kB (0 already local)
Pulled lucen/laika-demo: 10 file(s) (51.0 kB)
Files stream in parallel and each one is hashed while it is written; a file is
renamed into place only when its sha256 matches what the tree said it would be.
A second pull into the same directory downloads nothing.
4 · Push it back as your own
Replace you with your handle (lucen auth whoami prints it):
lucen push you/laika-demo ./laika-demo --kind dataset --public -d "My first Lucen dataset"
Created and pushed you/laika-demo: 10 file(s), 0 uploaded (0 B), 10 deduped
Zero bytes moved. Blobs are content-addressed and the hub had already hashed these ones itself, so your push linked the existing objects instead of re-uploading them — across accounts, paths and repos. Change one file and only that file uploads. Kill a push mid-transfer and the next one re-presigns exactly the parts that never landed.
Open https://mousemouse.ai/you/laika-demo and the Episodes tab will play the video
against synced per-joint charts.
lucen repo ls --owner you
repo kind visibility updated description
you/laika-demo dataset public 2026-09-02 My first Lucen dataset
4b · Import a dataset from Hugging Face
The LeRobot datasets on the Hugging Face Hub
come across in one command. It reads the dataset's meta/info.json on the Hub
first and refuses, with the reason, anything that is not a LeRobotDataset the
hub reads (v2.0, v2.1 or v3.0); then it prints the plan — the revision, the
episodes, frames, fps and cameras, the size on the Hub, the licence the dataset
card declares, where the files go — and asks:
lucen datasets import hf://lerobot/pusht
Import hf://lerobot/pusht @ 7628202a2180 (main) → you/pusht (private)
LeRobotDataset v3.0 · 206 episodes · 25,650 frames · 10 fps · 1 camera (observation.image) · robot_type unknown
8 file(s), 7.7 MB on the Hub · licence mit · README.md: the dataset's own
Files go from Hugging Face to /Users/you/.cache/lucen/hf/lerobot/pusht/7628202a2180972f291ba1bc6723834921e72c19, then to the hub; bytes the hub already holds are not uploaded again.
Import it? [Y/n]: y
Created and imported hf://lerobot/pusht → you/pusht (private): 8 file(s), 8 uploaded (7.7 MB)
LeRobotDataset v3.0 · 206 episodes · 25,650 frames · 10 fps · 1 camera (observation.image) · robot_type unknown; the hub reads its episodes.
description set to the provenance.
https://mousemouse.ai/you/pusht?tab=episodes
The files go from Hugging Face to your machine — through the Hub's own client,
so an interrupted download resumes where it stopped and HF_TOKEN opens a
gated dataset — and from there to the hub through the same push as above: run
it twice and the second run uploads nothing. The hub then reads the dataset
back the way its Episodes tab does before the repo can be made public
(--public). Where it came from, the revision and the licence are written as
the repo's description, and a README.md only when the dataset ships none;
the dataset card Hugging Face shows is the README when there is one. --to you/other-name picks the repo, --revision a branch or commit, --yes skips
the question in a script, and --json prints the plan and the result as one
object. Nothing runs on the hub: there is no hosted import.
lucen datasets ls --owner you
repo kind visibility updated description
you/pusht dataset private 2026-10-09 Imported from https://huggingface.co/datasets/ler…
5 · Import a training run
A run trained anywhere — your workstation, a cluster, Isaac — can be recorded on the hub so the Coach can read it:
printf 'seed: 0\nlr: 3.0e-4\nepisodes: 3\n' > run-config.yaml
lucen runs import --config run-config.yaml --dataset lucen://you/laika-demo --template quickstart
Imported run_01M1FYAWR191NM7X0ZEPFSE5ZF (succeeded): config
https://mousemouse.ai/runs/run_01M1FYAWR191NM7X0ZEPFSE5ZF
Pass --config the resolved config, not the template you edited: the Coach
diffs a run against its --parent to attribute a regression, and a diff of two
templates attributes nothing. --metrics and --scorecard are optional; a
scorecard must be JSON, because its PASS rows become standing constraints on
every proposal the Coach makes about the run.
lucen runs ls
run kind status template parent created
run_01M1FYAWR191NM7X0ZEPFS… external succeeded quickstart — 2026-09-02
No run is public — lucen runs ls shows only runs you own or whose org you
belong to, and a run you may not see is a 404 rather than a 403.
6 · Train on your own machine, and watch it
The other path executes a recipe for real, on a machine you start a runner
on — free, because the hardware is yours. The built-in stub_ppo template
trains nothing (it writes a synthetic curve and a stamped ONNX in about a
second) but walks the whole road: claim, stream, publish, report. In a second
terminal, start a runner with the same credentials:
lucen worker run
Then, back here, write a recipe and submit it with --follow, which streams
the runner's output to stderr until the run ends and exits by its outcome
(0 succeeded, 1 failed, 2 canceled):
printf 'template: stub_ppo\ndataset: []\nhyperparams:\n iterations: 40\n' > recipe.yaml
lucen train -f recipe.yaml --follow
Submitted run_01M2J0K3EXAMPLE0000000000; following its log (Ctrl-C detaches, the run keeps going)
| [lucen-stub-ppo] resolved config written; 40 iterations, seed 0
| iter 1 reward 0.874 survival 0.62
…
| iter 40 reward 11.464 survival 1.00
run_01M2J0K3EXAMPLE0000000000 ended: succeeded
The run page shows the same log live, with the metrics curve under it, and
a Cancel button; lucen runs cancel RUN_ID does the same from here — a
queued run ends at once, a running one is stopped by its runner within a
few seconds. Stop the runner in the other terminal with Ctrl-C when you are
done. A runner that dies mid-run does not strand it: the hub re-queues a run
whose heartbeat is older than five minutes for the next lucen worker run.
7 · Gate it in sim
A policy is not done when the loss curve looks good; it is done when it
stands and tracks its commands. The sim gate runs a model repo's ONNX in
MuJoCo against a robot repo's own MJCF and meshes — under the policy's
io_contract, refusing a mismatch rather than running the wrong thing — one
command cell at a time, and the hub scores it: a cell PASSes when every
seed stays upright for the whole horizon and every commanded axis tracks
inside [0.85, 1.15] of the command, the source harness's own rule. The engine
is an extra so the plain CLI stays small:
curl -fsSL https://api.mousemouse.ai/install.sh | sh -s -- "cli[rollout]"
lucen/laika-stand-v1b is the standing policy in service on Laika (the hub
publishes the policies the robot runs — the kind of model you pull and gate,
not a stand-in). Roll it out on lucen/laika with the five-cell smoke
battery, right here on your CPU:
lucen rollout lucen/laika-stand-v1b --robot lucen/laika --battery smoke --local
Created rollout_01M4CCECWB9TE30B712S36QNA3: battery smoke, 5 cell(s) x 2 seed(s) x 5 s
Claimed rollout_01M4CCECWB9TE30B712S36QNA3 as your-laptop; running the battery here
| policy: lucen/laika-stand-v1b/stand_v1b.onnx (760724 bytes)
| robot: lucen/laika/laika_v2_mjcf.xml + 13 asset(s)
| contract: profile (default), obs 45 = base_ang_vel(3) + projected_gravity(3) + commands(3) + joint_pos_rel(12) + joint_vel(12) + last_action(12), action 12, clip ±1, 50 Hz, joints l_hip_pitch_joint, …, r_ankle_roll_joint
| engine: mujoco 3.12.0, onnxruntime 1.30.0, dt 0.002 s x 10 = 50 Hz control, per-group kp 12 kd 0.6, kp 20 kd 1, kp 30 kd 1.5
| cell stand: 2/2 upright (2.3s)
| cell vx+0.30: 2/2 upright, tracking vx -0% (1.3s)
| cell vy+0.10: 2/2 upright, tracking vy -0% (1.3s)
| cell vy-0.10: 2/2 upright, tracking vy 0% (1.3s)
| cell yaw+0.50: 2/2 upright, tracking yaw 0% (1.3s)
| uploaded 20 artifact(s), 766221 bytes
rollout_01M4CCECWB9TE30B712S36QNA3: succeeded — lucen/laika-stand-v1b on lucen/laika, battery smoke, seed 0, horizon 5 s
claimed by your-laptop
cell verdict upright tracking video
stand PASS 2/2 — feet/side/front
vx+0.30 FAIL 2/2 vx -0% feet/side/front
vy+0.10 FAIL 2/2 vy -0% feet/side/front
vy-0.10 FAIL 2/2 vy 0% feet/side/front
yaw+0.50 FAIL 2/2 yaw 0% feet/side/front
1/5 cells PASS
https://mousemouse.ai/rollouts/rollout_01M4CCECWB9TE30B712S36QNA3
A standing policy stands: stand PASSes with both seeds upright for the
whole horizon, and every commanded cell FAILs at 0 % tracking, because it was
never asked to walk — which is what the gate is for. (11 s on a laptop CPU.)
That page shows the three mp4s per cell — side, front and feet — over the
joint chart, and the scorecard table. The mp4 needs an offscreen GL context
and ffmpeg; on a box without either the gate still scores and the rollout
says video unavailable: … instead. Pass --run RUN_ID (the Run in sim form on the model page's
Rollouts tab prefills the run the model came from) and the terminal report also writes the scorecard
onto that run — POST /v1/coach/advice then lists every PASS cell under
experiment_plan.constraints without anyone copying anything. Without
--local the rollout waits queued for a lucen rollout worker on any
machine with the extra installed.
What each of these costs, and where it runs, is the Quickstart's table.
8 · Share it with your lab
A lab is two people. An organization is a handle you share: every member publishes robots, models, datasets and runs under it, and an owner decides who is in it. Create one — you are its first owner:
lucen org create my-lab
Created organization my-lab — you are its first owner.
https://mousemouse.ai/my-lab
Add your lab by their hub handles: lucen org add my-lab USERNAME
Then add each person by their hub handle. There is no e-mail invitation on this hub, so they must have signed up first; a handle nobody signed up with is refused with exactly that sentence.
lucen org add my-lab ada
ada is now a member of my-lab.
lucen org members my-lab
member role name added
you owner you 2026-10-09
ada member ada 2026-10-09
--role owner on add makes someone an owner (or changes an existing
member's role), lucen org remove my-lab ada removes them, lucen org leave my-lab is how you leave, and lucen org ls lists yours. An organization keeps
at least one owner: the last one can neither be demoted nor leave until
someone else is made an owner — the hub says so, with the fix in the sentence.
The same page in the browser is Organizations.
Next
- Quickstart — the same journey in the browser, from a zip to a policy on a robot.
- Add your robot — an MJCF or a URDF to a validated robot page, in three commands.
- Dataset format — what the episode viewer reads, and what it refuses.
- MCP setup — what an agent can do with a scoped key.
- API reference — all of it, with the scope each operation needs.
- Training Coach — the doctrine and the 171 cards every citation resolves to.