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:
- verify the token offline —
algisEdDSA,kidis the pinned one, the signature holds,exp/nbf/iathold (10 s leeway),audislucen-device,issis the pinned issuer,subis this device; - cross-check the listing against the signature (id and digest);
- pull the model repo with
lucen pull's code (every file digest-checked), then compare the ONNX to the digest inside the token; - read the ONNX's own
lucen_manifestand refuse a contract this box did not register (or one that differs from what the hub pre-checked); - load the ONE adapter this box registered, check the embodiment, pass it the robot card's safe stop, walk its staged start;
- start the adapter, post
started, heartbeat everyheartbeat_sseconds (2 s by default;--heartbeatoverrides it), stop atexpires_aton this box's clock (or at--max-run-seconds, a local cap), poststopped; - stop when the link is lost. A heartbeat the hub does not acknowledge
within the interval is a miss and a warning.
link_lost_heartbeatsmisses 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 signedstopped(reasonlink_lost, with the misses it counted andstop_sequence) waits in the outbox and is posted when the link returns;serve --oncewaits 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 indevice.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—policyonly: shells out to the box's own start/stop/status commands, fromdevice.jsonadapter_config(register --adapter-config FILE.json) orLUCEN_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_cmdfirst (30 s cap), thenSIGTERMto the start command's whole process group (it runs in its own session, so a runner forked bysh -cis reached), thenSIGKILLafter 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 ofcommand(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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lucen_device-0.3.0.tar.gz | 87.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.