Skip to content
Docs menu

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.