# MouseMouse > The open hub for robot data, models and training. Add a robot, train its first > policy, gate it in sim, and put it on a robot only after a person approves it — > from the browser, the `lucen` CLI or an agent through MCP. A GitHub/HuggingFace-style hub for robots: dataset repos, model repos, robot cards (hardware specification plus a MuJoCo model), imported training runs, and a Training Coach that diagnoses a run against 171 distilled experience cards and 22 numbered doctrine rules, citing them by id. The web app, the `lucen` CLI, the `lucen-mcp` MCP server and the generated TypeScript/Python SDKs are all thin surfaces over one REST API; its OpenAPI contract is the source of truth for every one of them. This file describes what is **built** on this hub. A section at the end lists what is specified and not built, or runs only where the deployment has a paid backend, so an agent does not plan around it. ## In the web app A person needs no terminal for the path from a robot file to a policy on a robot ([/docs/quickstart](/docs/quickstart)), only for jobs they run on their own machine: - **Add your robot** at [/robots/new](/robots/new): a zip of an MJCF or a URDF with its meshes, read in the browser; the control rate, the action and (for a URDF) the base are declared, and a default pose is proposed when the model has no keyframe; a robot check runs `lucen robot init` + `validate`, then Publish. No model file yet? [/robots/new/with-agent](/robots/new/with-agent) builds a prompt for the person's own AI agent — what a Lucen robot file is, the limits and mesh rules of the drop, the checks it faces by code, what is declared at the drop, how to hand the zip back — and [/robots/new/from-a-terminal](/robots/new/from-a-terminal) is the three commands. - **Train a policy** at `/{owner}/{robot}/train`: the template, its settings, a gate while it trains, and where it runs. On the person's own machine the run waits for `lucen worker run`, which trains it and scores its gates. - **Run in sim** on a model page's Rollouts tab: a rollout the hub scores, run by `lucen rollout worker`, shown at `/rollouts/{id}`. - **Connect a robot** from a robot page's Connected tab: a pairing code, `lucen-device pair` on the robot (which holds no API key), and a signed-in person confirms it. - **Deploy to robot** at `/{owner}/{model}/deploy`: the hub's checks, a window, a hold to approve; the live page shows the robot's signed trail and **Stop now**. Disconnecting a robot is on [/settings/devices](/settings/devices). - **Fine-tune** at `/{owner}/{model}/fine-tune`: the next round of a training line. - **Organizations** at [/settings/organizations](/settings/organizations): a lab's shared handle — create one (you are its first owner), add people **by their hub handle** (they must have signed up first; there is no e-mail invitation), make them owner or member, remove them, leave. The same from the CLI: `lucen org create|add|members|remove|leave`. - [/inbox](/inbox): requests waiting on the person — an agent's or a key's — then outcomes. Only a signed-in person confirms a pairing, approves or deploys a policy, overrides a fine-tune rule, or mints a key. The same path from the CLI: [/docs/from-a-terminal](/docs/from-a-terminal). ## API - **Base URL**: `https://api.mousemouse.ai` (the API); the web app is `https://mousemouse.ai`. The CLI and the MCP server have no built-in hub: set `LUCEN_API_URL=https://api.mousemouse.ai`. - **Contract**: [/openapi.yaml](/openapi.yaml) — OpenAPI 3.1, 150 operations, every one carrying an `x-auth-scope`. Rendered at [/docs/api](/docs/api). - **Errors**: RFC 9457 `application/problem+json` on every failure, with `title`, `status`, `detail` and, for validation failures, `errors[]`. - **Pagination**: opaque `cursor` in, `next_cursor` out; `limit` caps at 100. - **Ids**: prefixed ULIDs — `repo_01J8…`, `run_01J8…`, `advice_01J8…`. ## Auth `Authorization: Bearer lucen_sk_…`, with scopes that nest: `read` ⊂ `write` ⊂ `train`. - **no key** — every public read: repo search, repo and robot-card reads, dataset episodes and series, and the whole coach corpus. - **`read`** — the above plus anything the key's user can see, plus their training runs. - **`write`** — plus all data mutations: repos, file uploads, dataset/model meta, robot cards, importing a run trained elsewhere. - **`train`** — plus the endpoints that spend money: submitting runs and asking the coach for a diagnosis or a plan. - **`session`** — a browser session only. Reserved for API key management (`/v1/keys`), so a leaked `write` key can never mint itself a `train` one. The same `session` rule covers device approvals: no API key can approve a policy onto a robot; an agent may only request one. A private resource answers **404, never 403** — a 403 would confirm it exists. Rate limits are 60/min anonymous per IP and 600/min per credential, with `X-RateLimit-*` headers; the two coach endpoints carry a separate daily quota (`X-Coach-Quota-*`). ## Top endpoints - `GET /v1/repos` — search datasets, models and robots (`q`, `kind`, `owner`, `tag`, `sort`). Scope: none. - `GET /v1/repos/{owner}/{repo}` — one repo with its kind-specific metadata. Scope: none. - `GET /v1/robots/{owner}/{repo}/card` — DoF, mass, actuators, joint limits, `mjcf_path`. Scope: none. - `GET /v1/datasets/{owner}/{repo}/episodes` — episodes in a LeRobot-format dataset. Scope: none. - `GET /v1/datasets/{owner}/{repo}/episodes/{n}/series` — decimated per-channel numeric series read out of the parquet by byte range. Scope: none. - `GET /v1/coach/cards` — IDF-ranked search over the experience corpus. Scope: none. - `GET /v1/coach/doctrine` — the 22 numbered rules a report cites by id. Scope: none. - `POST /v1/coach/advice` — diagnose a run; returns diagnosis, cited proposals and an experiment plan. Scope: `train`, spends model budget. - `POST /v1/coach/plan` — plan a training programme from a task brief. Scope: `train`, spends model budget. - `POST /v1/repos/{owner}/{repo}/files/presign-upload` + `…/complete` — content-addressed multipart upload; resumable, and `parts: []` means "we already have these bytes". Scope: `write`. - `POST /v1/repos/{owner}/{repo}/files/presign-download` — 1 h presigned GET URLs. Scope: none. - `POST /v1/runs/import` — record a run trained off-platform, with its resolved config, metrics and scorecard. Scope: `write`. - `GET /v1/runs` and `GET /v1/runs/{id}` — runs visible to the caller. No run is public. Scope: `read`. - `POST /v1/runs` — submit a training run. `executor: worker` (the default) queues it for a runner **you** start on your own GPU machine (`lucen worker run`, or `lucen train -f recipe.yaml --follow`); the hub does not track runners, so `queued` means "no runner has claimed it yet", never "training". `POST /v1/runs/estimate` prices it first; the same `Idempotency-Key` returns the same run. Scope: `train`. - `GET /v1/runs/{id}/logs` — the run's log stream (SSE; `tail`, `follow`), written by the runner as it reports; `POST /v1/runs/{id}/cancel` — cancel a queued run at once or ask the runner to stop a running one. An imported run has no logs by contract (`logs_key` is null). Scope: `read` / `train`. - `POST /v1/rollouts` — queue a sim rollout: a battery of command cells the policy runs in MuJoCo on the robot's own MJCF, executed by a CPU rollout worker (`lucen rollout worker`, or `lucen rollout … --local`) and **scored by the hub** PASS/FAIL per cell (every seed upright, every commanded axis inside [0.85, 1.15]); name a `run_id` and the scorecard is attached to that run for the Coach. The founder's own harness batteries — `c_matrix`, `robust` (ground μ, pushes, the zero command), `recovery` (fallen starts, scored on standing up), `watch` — carry his sim2sim conditions (actuation delay, friction, kp/kd scale, push, power scale, heading, the bridge slew); every cell renders side / front / feet videos; `checkpoint_step` rolls out one checkpoint of a model repo. Scope: `train`, unpriced on CPU. - `POST /v1/runs/{id}/scan` and `GET /v1/runs/{id}/gates` — a band scan of a run's checkpoints (one rollout per checkpoint × 20 seeds) and the gates a run's checkpoints got while it trained (`recipe.gate`: each new `checkpoints/step_/` is queued for a gate — on a `worker` run the runner that trains it scores it, on a `modal` run the hub's sweep dispatches it — and a tripwire can stop the run). Scope: `train` / `read`. - `POST /v1/rollouts/{id}/cancel` — cancel a queued or running rollout. Scope: `train`. - `POST /v1/campaigns` and `POST /v1/campaigns/{id}/rounds` — a campaign (a task on a robot, a budget) and its rounds: each round locks a spec (one declared change, a hypothesis, gates with predictions), trains through `POST /v1/runs` with the task's gate battery (`stand` for a stand), is band-scanned, and gets its verdict from its locked gates. The hub refuses a round that breaks one of three rules — one variable per round, resume only from a PASS checkpoint, a FAIL ends the lineage — with reasons (422), and one its budget cannot cover (409). Only a signed-in person may override a rule (`POST …/rounds/override`, `session`), and only a person may shortlist checkpoints for the robot or close the campaign (`POST …/rounds/{n}/decision`). Scope: `write` / `train`; reads `GET /v1/campaigns[/{id}]`, `GET …/rounds/{n}` are `read`. - `POST /v1/agent/sessions` — start the hub's own cloud agent on a goal (`robot`, `campaign`, a pre-approved `budget_usd`, a `cap_usd` on its model + sandbox spend). It works through the same tools `lucen-mcp` has, plus a sandbox shell where the deployment configures one, sleeps while a run trains and wakes on the run's end or a gate verdict. Any spend above its rules pauses the session (`waiting_on_you`) until a person — signed in, or with their own key — answers `POST /v1/agent/sessions/{id}/approvals/{approval_id}`; the agent's own key is refused (403). `GET …/events` is the session as SSE, resumable with `Last-Event-ID`; `POST …/messages` and `…/cancel` steer it. Model calls are billed at list price against a $5 monthly allowance. In the web app: [/agent](/agent). Scope: `train` to start, message, decide or cancel; `read` to list, get and stream. - `GET /v1/rollouts` and `GET /v1/rollouts/{id}` — rollouts visible to the caller, with per-cell verdicts and presigned video / trajectory / scorecard URLs. No rollout is public. Scope: `read`. - `POST /v1/robot-checks` — the hosted check behind the robot drop: a job runs the CLI's own `lucen robot init` + `validate` on a snapshot of a robot repo's files (`control_hz` and `action` required, `base` for a URDF, an optional `default_pose` passed to `init --default-pose`; nothing defaulted), executed by `lucen robot worker` or, where the deployment names a Modal app, a sandboxed CPU container; the hub never runs MuJoCo. `GET /v1/robot-checks[/{id}]` reads the report, the draft card and its `needs_you`. `POST /v1/robot-checks/{id}/publish` links what the check wrote — the hub hashes every file — and stores the card as the caller. Scope: `write` (capped per key per day) / `read`. - `POST /v1/device-pairings` — a short pairing code for a robot repo (`write`); the robot claims it with `POST /v1/device-pairings/claim` (no credential; it declares its own public key), a person confirms with `POST /v1/device-pairings/{pairing}/confirm` (**`session` only**), and the robot collects its device credential with a request signed by its key. The robot never holds a person's API key. - `POST /v1/devices` — register a robot-side runner (`lucen-device`) with the IO-contract fingerprints it accepts and its public key, the older path for a box set up with a person's key. Scope: `write`. `GET /v1/devices[/{id}]` — devices visible to the caller; none is public. Scope: `read`. `POST /v1/devices/{id}/disconnect` revokes the device's credential and asks anything it runs to stop. Scope: `write`. - `POST /v1/devices/{id}/approval-requests` — ask for "model X onto device Y for T minutes"; pinned to one ONNX and its current sha256; refused (409) when the device will not run that contract. Scope: `write`. Nothing moves until a person approves. - `POST /v1/devices/{id}/approvals` and `POST .../approval-requests/{id}/deny` — the human's decision. Scope: **`session` only — no API key of any scope**. Approving mints a short-lived EdDSA token the driver verifies offline against `GET /v1/devices/signing-key`. - `POST /v1/devices/{id}/approvals/{approval_id}/stop` — end a running approval early; the robot reads it off its next heartbeat and reports a signed `stopped`. Scope: `write` (not an agent session's key, not the device's own). - `POST /v1/devices/{id}/events` — the driver's signed `started` / `heartbeat` / `stopped` / `refused` summaries (scope `write`), plus `degraded` / `recovered` when the local fail-safe fires and clears; `GET .../events`, `.../approvals` — the trail (`read`). Inference is placed by latency class. The **reflex layer** (a 50 Hz locomotion or balance policy) always runs on the robot, from a pulled, digest-checked file, through a device approval; this hub never serves it over a network. The **skill layer** (a VLA / action-chunk policy at 1–10 Hz) may be served as a hub endpoint: - `POST /v1/endpoints` — an endpoint for a model whose `ModelMeta.control_class` is `skill` or `planner`; a `reflex` model is **409**. The tier is chosen from the model's size (params × 2 bytes + 20 % headroom). `executor: worker` (the default) waits `queued` until a human starts `lucen serve` on their own GPU box; the hub does not track servers. Scope: `train`, unpriced on `worker`. `POST /v1/endpoints/estimate` prices it first. - `GET /v1/endpoints[/{id}]`, `GET /v1/endpoints/{id}/sessions` — endpoints visible to the caller and their per-session metering (chunks, GPU-seconds, service and round-trip p50/p95). None is public. Scope: `read`. `DELETE /v1/endpoints/{id}` stops one (`train`). - `POST /v1/endpoints/{id}/claim` + `/report` — the `lucen serve` protocol, mirrored from runs (`train`). The server speaks **openpi's policy-server protocol** (msgpack + numpy over a websocket), so an unmodified `openpi` client connects. - `POST /v1/endpoints/{id}/sessions` — a robot opens a session **only with an approval token a human signed** that is bound to the device, the model digest and the endpoint id; missing, expired or bound elsewhere is 403. No API key can point a robot at an endpoint. Scope: `write` (the driver's key). The robot holds or stops on its own clock when the link degrades. ## MCP `lucen-mcp` exposes the hub's tools over stdio. It is not on PyPI; this hub serves it: ```bash curl -fsSL https://api.mousemouse.ai/install.sh | sh -s -- mcp ``` Then, in `.mcp.json` (Claude Code) or `claude_desktop_config.json` (Claude Desktop): ```json { "mcpServers": { "lucen": { "command": "lucen-mcp", "env": { "LUCEN_API_URL": "https://api.mousemouse.ai", "LUCEN_API_KEY": "lucen_sk_..." } } } } ``` `LUCEN_API_KEY` is optional — without it, every `none`-scope tool still works. The key is only ever an environment variable: no tool takes one as an argument, and no tool can mint, list or revoke one. Setup notes: [/docs/mcp](/docs/mcp). Tools: `search_repos`, `get_repo`, `get_robot_card`, `get_dataset_episodes`, `search_experience`, `get_coach_doctrine`, `get_training_advice`, `get_training_plan`, `get_run_status`, `get_run_logs`, `get_run_config`, `get_run_gates`, `render_rollout`, `cancel_training`, `estimate_training`, `recommend_training`, `submit_training`, `rollout_in_sim`, `get_rollout_status`, `request_deploy`, `get_device_status`, `serve_model`, `get_endpoint_status`, `plan_task`, `get_plan_status`, `create_repo`, `validate_robot`, `set_robot_card`, `init_robot`, `publish_robot`, `list_activity`, `get_my_overview`, `get_campaign`, `get_round`, `propose_round`, `decide_round`, `list_trials`, `get_trial`, `list_my_orgs`, `create_org`, `add_org_member` (by hub handle — no e-mail invitation; removing and leaving are a person's own). Adding a robot needs no `train` scope: `init_robot` compiles a LOCAL MJCF or URDF with MuJoCo and drafts the card + embodiment interface, listing under `needs_you` what a model file cannot state; `publish_robot` validates it in MuJoCo (dry run by default) and publishes; `validate_robot` asks the hub to check a card against the repo's files without writing. A refused card comes back as check codes (`mjcf.exists`, `assets.closure`, `interface.joint_order`, `interface.base`, …). ## Docs - [/docs/quickstart](/docs/quickstart) — in the browser: add a robot from a zip, train its first policy, gate it in sim, connect a robot with a code, deploy and stop it; the one terminal step is the workers for jobs on your own machine. - [/docs/how-it-works](/docs/how-it-works) — the three claims, what a person approves before a policy reaches a robot, what an agent's key can do, the Coach, and Laika. - [/docs/api](/docs/api) — every operation with its auth scope, generated from the contract. - [/docs/mcp](/docs/mcp) — what an agent can do with a scoped key, and the MCP config entry. - [/docs/dataset-format](/docs/dataset-format) — the LeRobotDataset layout the hub reads, and which versions the episode viewer supports. - [/docs/add-your-robot](/docs/add-your-robot) — an MJCF or a URDF to a validated robot page: drop a zip on [/robots/new](/robots/new), or three commands (`lucen robot init|validate|publish`): what is derived, what you must declare, what the hub refuses. - [/docs/connect-your-robot](/docs/connect-your-robot) — pair a robot from its page with a code, and write a `lucen-device` adapter plugin for your own robot: the entry point, the protocols, and the local hold/stop fail-safe the driver probes before it will consume an endpoint. - [/docs/from-a-terminal](/docs/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, gate it in sim. Every command was run verbatim. - [/docs/python](/docs/python) — `from lucen import Hub`: the same engine from a Python script — `hub.pull`, `hub.push`, `hub.runs.create(…, follow=True)`, `hub.rollouts.create(…, local=True)`, one exception family with the CLI's own sentences. Every example is run against a hub by a test. - [/docs/self-host](/docs/self-host) — boot the whole hub on one machine from a checkout, and what a local stack does not have. - [/docs/glossary](/docs/glossary) — every hub word the pages gloss (policy, reflex, sim gate, fingerprint, endpoint, scope, …) in one sentence each, with the page where it is first met. - [/coach](/coach) — browse the doctrine and all 171 experience cards. No account needed. - [/openapi.yaml](/openapi.yaml) — the contract itself. ## Not built yet Named here so nothing plans around them. These operations exist in the contract and have no implementation on any deployment: - **Hosted GPU training** is per deployment. `executor: modal` runs only where the deployment names a deployed Modal app (`MODAL_APP_NAME`): the run is queued, the hub spawns the template's function on the tier's GPU, and the container claims, reports and publishes like a runner, billed `elapsed seconds x tier rate`. On any other deployment — a local stack, CI, one without a Modal account — `executor: modal` answers 503 naming that setting and records nothing. A `worker` run (above) that no runner claims stays `queued`, produces nothing and costs nothing. - **Hosted sim rollouts** are per deployment the same way. With `executor: modal` on a deployment that names a deployed Modal app (`MODAL_APP_NAME`), the hub spawns the app's CPU `rollout` function, which claims and reports like a worker; on any other deployment `executor: modal` answers 503 naming that setting and records nothing, and a `worker` rollout (above) that nobody claims with `lucen rollout worker` stays `queued`. - **Hosted inference endpoints** are per deployment the same way: with `executor: modal` on a deployment that names a Modal app, the hub spawns a container that claims the endpoint with a public `wss://` URL and its sessions are priced by the tier; elsewhere it answers 503 naming the setting and records nothing, and an endpoint is served only by `lucen serve` on a GPU box you start. A torch-checkpoint loader for the VLA templates is a documented seam in `lucen serve`, not a shipped feature: serve ONNX, or register a loader. The training loop that works on every deployment: train on a runner you start (`lucen worker run`, which also scores the run's gates) or record a run trained elsewhere (`lucen runs import`), gate it in sim, and ask the Training Coach about it. The device API is a **consent layer, not a control API**: an agent's key can register a device, request a deployment and read the trail, and **cannot approve** — `POST /v1/devices/{id}/approvals` is `session` only, so a policy reaches a robot only after a person approves it in the web app — holding Deploy on the model's page, or holding Approve on a request in the Inbox. The robot-side `lucen-device` driver then verifies the signed approval offline, pulls the exact bytes, refuses a contract mismatch, runs for the window and stops on its own clock. Nothing here starts, stops, steers or teleoperates a robot; goal commands and teleop through an agent are not built (v1+).