Skip to main content

lucen-device — the robot side of the consent layer

lucen-device runs on the robot's own computer. It holds two things the hub never sees — the device's Ed25519 private key and whatever the robot needs to start a policy — and acts only on approvals a person signed in the web app, which it verifies offline against the hub's published signing key.

The hub's rule is that no API key of any scope can put a policy on a robot (POST /v1/devices/{id}/approvals is session-only). This package is how a policy gets there anyway: the hub records consent, the box does the handoff.

Install

From PyPI, as a tool on the robot's own computer (uv, or pipx install lucen-device):

uv tool install lucen-device

lucen-device depends on lucen-cli, so the same install puts lucen on the PATH beside it (a robot box runs lucen pull anyway). A MouseMouse hub also serves its own copy of these wheels: curl -fsSL <hub API>/install.sh | sh -s -- device installs the version built from the hub's own commit and points the driver at that hub; see the hub's docs page Connect your robot.

Pair (K1) — no person's key on the robot

lucen-device pair K7QF-2M9D --hub https://hub.example/api   # a code a person issued on the hub
lucen-device pair --hub https://hub.example/api             # no code: the box asks for one and shows it

The box generates its Ed25519 key, declares itself (adapter and provenance, fail-safe probe, any --accept contracts) and prints its key fingerprint. A signed-in person opens the pairing on the hub, compares that fingerprint, picks the trust mode and confirms ("Not my robot" drops it; an unconfirmed claim expires with the code, ten minutes). The box then collects — signing with its key — a device credential that acts only as this device, the hub's signing key and the robot card's interface, all pinned in device.json. The owner revokes the credential from the hub (disconnect); every later call is 401, and a running policy stops.

Trust mode. registered (default) runs only the IO contracts this box registered. approved also runs a contract it never registered when a signed approval names it and the policy's embodiment_fingerprint equals the interface fingerprint this box pinned; an unstamped policy additionally needs the approver's acknowledgment in the token and a contract that fits the pinned interface (joint order, control rate, one action per joint), which the box checks itself. The person chooses it at confirmation; afterwards only the box changes it: lucen-device trust approved.

Credentials

A paired box uses its own device credential (in device.json, mode 0600). A box set up with register uses the lucen CLI's: lucen auth login on the box (a write key for the device's owner — it registers and posts telemetry, and by contract it cannot approve anything), or LUCEN_API_KEY / LUCEN_API_URL in the environment. No flag takes a key.

The driver's own state lives in $LUCEN_DEVICE_CONFIG_DIR (default ~/.config/lucen-device): device.json (the registration and the hub signing key pinned at registration), device_key.pem (mode 0600, refused if looser) and models/<approval id>/ (each pulled repo).

Register

lucen-device register --robot lucen/laika --name lab-laika --adapter virtual \
    --accept-from ./omni/omni_c4_ff800.onnx        # and/or --accept <fingerprint>

Registration generates the keypair, fetches and pins the hub's signing key (kid + PEM + issuer), and tells the hub which IO contracts this box will run — as fingerprints (sha256 of a stamped ONNX manifest's canonical contract block). --accept-from reads one out of a policy file; a device that accepts no contract cannot be registered, because it could never be handed anything. If the hub signs with an ephemeral key (no DEVICE_APPROVAL_SIGNING_KEY), the command warns you: approvals will stop verifying when the hub restarts, and lucen-device signing-key --refresh re-pins.

Serve

lucen-device serve            # poll every 10 s, forever
lucen-device serve --once     # handle what is issued now, then exit

For every approval the hub lists as issued for this device:

  1. verify the token offline — alg is EdDSA, kid is the pinned one, the signature holds, exp/nbf/iat hold (10 s leeway), aud is lucen-device, iss is the pinned issuer, sub is this device;
  2. cross-check the listing against the signature (id and digest);
  3. pull the model repo with lucen pull's code (every file digest-checked), then compare the ONNX to the digest inside the token;
  4. read the ONNX's own lucen_manifest and refuse a contract this box did not register (or one that differs from what the hub pre-checked);
  5. load the ONE adapter this box registered, check the embodiment, pass it the robot card's safe stop, walk its staged start;
  6. start the adapter, post started, heartbeat every heartbeat_s seconds (2 s by default; --heartbeat overrides it), stop at expires_at on this box's clock (or at --max-run-seconds, a local cap), post stopped;
  7. stop when the link is lost. A heartbeat the hub does not acknowledge within the interval is a miss and a warning. link_lost_heartbeats misses in a row (5 by default, about ten seconds) are link loss: the adapter is held (hold(), when it has one) and then stopped, in the same order a person's Stop uses, and the approval is over on the robot's side. Nothing restarts the policy. The signed stopped (reason link_lost, with the misses it counted and stop_sequence) waits in the outbox and is posted when the link returns; serve --once waits up to a minute for that, then exits and warns. The rule counts answers, never clocks, so clock skew cannot fire it. Both numbers live in device.json (heartbeat_s, link_lost_heartbeats).

The trail says verified (step 1) and pulled (step 3) before started. A person's Stop arrives on a heartbeat's answer: the adapter is held (hold(), when it has one) and then stopped at once, in M17d's fail-safe order, and stopped says stop_requested with stop_sequence: [hold, stop], within one heartbeat plus a round trip. Only a signed-in person can press it; no API key can. A robot that cannot reach the hub does not wait for the Stop: it stops the policy itself about ten seconds after it loses the hub (step 7). Stop is a convenience over the network, not a safety system: keep a hardware emergency stop within reach.

Every refusal is posted back as a signed refused event with a machine reason (bad_signature, unknown_kid, expired, wrong_device, digest_mismatch, unstamped, contract_mismatch, adapter_refused, …) so the person who approved can read why nothing happened. Every event is signed with the device key; the hub verifies it against the key you registered.

Adapters

Adapters are plugins (M17d). The adapter is the only part of the driver that knows a robot: it takes a verified policy (or a fresh action chunk) and makes the robot do it. Three ship here; any other robot installs its own — see examples/device-adapter/ (141 lines, a fake robot, both protocols) and the docs page /docs/connect-your-robot.

lucen-device adapters                      # what is installed — reads metadata, imports nothing
lucen-device adapters --check example-arm  # import ONE, list its protocols, probe its fail-safe

An adapter is a class registered in the entry-point group lucen_device.adapters by an installed distribution (the built-ins register through the same group), or named directly for unpackaged code: --adapter my_robot.adapter:MyArm / --adapter ./my_arm.py:MyArm. Loading one is code execution on the robot box. So: the operator names it at register and the driver loads that one and no other; listing never imports; a name two distributions claim — a third party registering virtual included — is refused; serve refuses (adapter_changed) when the registered name now resolves to a different distribution or class; and the distribution, version and class go into the device's adapter_info on the hub and into every signed started event.

Protocol Methods
policy start / stop / status / is_running run the approved file on the robot
chunks begin / observe / apply / hold / resume / stop / status / is_running consume a hub endpoint
staged_start (optional) stage(name, interface) "safe", then "default_pose", before the policy
safe_stop (optional) set_safe_stop(mode) the robot card's safety.safe_stop decides what hold / stop mean
  • virtual — every protocol. Validates the manifest and runs a clock at the policy's control rate for the approved window. It simulates nothing: no inference (the ONNX is never loaded), no physics, no actuators. It exists so the whole consent path runs in CI on a laptop, and every heartbeat says so.
  • command — policy only: shells out to the box's own start/stop/status commands, from device.json adapter_config (register --adapter-config FILE.json) or LUCEN_DEVICE_START_CMD / LUCEN_DEVICE_STOP_CMD / LUCEN_DEVICE_STATUS_CMD, never from the hub. Placeholders: {model_path}, {manifest_path}, {interface_path}, {seconds}, {approval_id}, {safe_stop}; a shell string or a JSON list; an unknown placeholder is refused before anything starts. Stop: stop_cmd first (30 s cap), then SIGTERM to the start command's whole process group (it runs in its own session, so a runner forked by sh -c is reached), then SIGKILL after 10 s. Unverified on hardware — there is no robot here; the plumbing is tested with a shell command standing in for the runner.
  • lucen-legged — the deprecated alias of command (M13's name for it; there was never anything legged in it). Existing registrations keep serving.

The robot card's interface, on the box. register pins the robot card's embodiment interface and interface_fingerprint (refresh on purpose: lucen-device interface --refresh). safety.safe_stop reaches set_safe_stop() — and if the card asks for something other than hold and the adapter has no such hook, nothing starts; default_pose reaches the staged start; a policy stamped with a different embodiment_fingerprint is refused (embodiment_mismatch), and when either side is silent the started event records the check as not made.

Other commands

lucen-device status                 # registration, adapter provenance, issued approvals, last events
lucen-device trust [MODE]           # show the trust mode, or change it (the box's call alone, signed)
lucen-device signing-key [--refresh] # the pinned hub key; --refresh re-pins it
lucen-device interface [--refresh]   # the pinned robot-card interface fingerprint
lucen-device version

Consuming a hub endpoint (M15)

An approval may name an endpoint instead of a file to run: "let this device consume hub endpoint X" — a skill-layer model (action chunks at 1–10 Hz) served by lucen serve on a GPU box. The driver then does not pull the model. It opens a session on the hub with the approval token (POST /v1/endpoints/{id}/sessions), checks that the hub's and the server's model_sha256 are the digest inside the token and that the served contract is one this box registered, connects over openpi's protocol with the session token as the Api-Key header, and runs the loop at the contract's control rate.

The fail-safe is local and mandatory. One chunk period is the deadline; a chunk that arrives late is dropped, never applied; two consecutive misses or a dropped link call the adapter's hold() (freeze a safe posture, or the adapter's own stop), post degraded, and reconnect until the window ends; when chunks flow again the adapter resume()s and recovered is posted. Only an adapter that implements begin / observe / apply / hold / resume / stop and whose hold() then stop() run clean on a fresh instance may consume an endpoint: register establishes endpoint_capable from the adapter's own code and that probe — never from a list of names (virtual and the example yes, command no) — and the hub refuses a request naming an endpoint on any other device. hold() and stop() must therefore work in any state and never raise. The loop does not trust them anyway: a hold() that raises or does not return within a second is answered with stop(), a degraded event saying hold_failed, and the end of the run; an exception out of observe() / apply() holds and ends it; and a watchdog thread holds and stops a loop that has made no progress for ten seconds (a coarse backstop, not a real-time guarantee — the robot's own controller must also stop when commands stop). A queued endpoint or an unreachable server is deferred (the approval stays issued; the next poll tries again), not refused.

The device reports its own metering — chunks applied, round-trip p50/p95, deadline misses, how often it held — to the session on the hub every heartbeat; the endpoint page shows it beside the server's numbers.

Apache-2.0. © 2026 Lucen Robotics.

Metadata

Release files for lucen-device 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lucen-device 0.3.0
File Size Uploaded Provenance
lucen_device-0.3.0.tar.gz 87.4 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for lucen-device 0.3.0
File Interpreter ABI Platform
lucen_device-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 189.6 kB

Release files / lucen_device-0.3.0.tar.gz

Download URL lucen_device-0.3.0.tar.gz
Size 87.4 kB
Tags Source
SHA-256 checksum
How to use checksums
a04e90e61e3d19dabdf632d37f4520e3ffd7351a4a81cc94b8f445cab9d070d1
BLAKE2b-256 checksum
How to use checksums
b3f3889e7b74d8a2d2081092af50c0e2e2c9026227cb5182247e969ad2381e4e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / lucen_device-0.3.0-py3-none-any.whl

Download URL lucen_device-0.3.0-py3-none-any.whl
Size 102.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c03663685e939dd4649fae6a009abe00690c77bd48b7b3f7cabaf3c1a9264e46
BLAKE2b-256 checksum
How to use checksums
0fe4909abcd89163686a2e1b08f7f5d7c61bb99a390db77c98014287450b10cd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Security for lucen-device 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for lucen_device-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page