rcan-py
Python SDK for the RCAN protocol. Build robots that communicate securely, audit every action, enforce safety gates locally, and register with the Robot Registry Foundation. Pinned protocol version: see the live compatibility matrix.
Spec: rcan.dev · Docs: docs.rcan.dev · Package: PyPI rcan
Where this fits in the stack
This repo is the Python SDK layer — the library any Python planner, gateway, or driver links against to speak RCAN. Everything below is independent; adopt one, or all seven.
| Layer | Piece | What it is |
|---|---|---|
| Declaration | ROBOT.md | The file a robot ships at its root. YAML frontmatter + markdown prose. Declares identity, capabilities, safety gates. Spec + Python CLI. |
| Agent bridge | robot-md-mcp | MCP server that exposes a ROBOT.md to Claude Code, Claude Desktop, Cursor, Zed, Gemini CLI — any MCP-aware agent. |
| Wire protocol | RCAN | How robots, gateways, and planners talk. Signed envelopes, LoA enforcement, PQC crypto. Think HTTP for robots. |
| Python SDK ← this | rcan-py | pip install rcan — RCANMessage, RobotURI, ConfidenceGate, HiTLGate, AuditChain, RegistryClient. |
| TypeScript SDK | rcan-ts | npm install rcan-ts — same API surface for Node + browser. |
| Registry | Robot Registry Foundation | Permanent RRN identities. Public resolver at /r/<rrn>. Like ICANN for robots. |
| Reference runtime | OpenCastor | Open-source robot runtime — connects LLM brains to hardware bodies. One implementation of RCAN. |
Install
pip install rcan
Optional extras:
pip install rcan[http] # httpx for registry client
pip install rcan[crypto] # cryptography for Ed25519 signing
pip install rcan[all] # everything
Quick Start
from rcan import RobotURI, RCANMessage, ConfidenceGate
from rcan.audit import AuditChain, CommitmentRecord
from rcan.replay import ReplayCache
# 1. Address a robot
uri = RobotURI.build(manufacturer="acme", model="arm", version="v2", device_id="unit-001")
print(uri) # rcan://registry.rcan.dev/acme/arm/v2/unit-001
# 2. Gate on model confidence before acting
gate = ConfidenceGate(threshold=0.8)
confidence = 0.91
if not gate.allows(confidence):
raise SystemExit("confidence below threshold")
msg = RCANMessage(
cmd="move_forward",
target=uri,
params={"distance_m": 1.0},
confidence=confidence,
)
# 3. Replay prevention: reject a msg_id seen inside the window
cache = ReplayCache(window_s=30)
allowed, reason = cache.check_and_record(msg.msg_id, msg.timestamp)
assert allowed, reason
allowed, reason = cache.check_and_record(msg.msg_id, msg.timestamp)
assert not allowed # the same msg_id again is a replay
# 4. HMAC-chained audit log
chain = AuditChain(secret="your-hmac-secret")
chain.append(CommitmentRecord(
action="move_forward",
params={"distance_m": 1.0},
robot_uri=str(uri),
confidence=confidence,
model_identity="example-model",
safety_approved=True,
))
assert chain.verify_all()
print(msg.to_json())
Modules
| Module | Description |
|---|---|
rcan.message |
Core RCANMessage envelope with all v1.6 fields |
rcan.address |
RobotURI — parse, build, and validate RCAN robot addresses |
rcan.audit |
AuditChain + CommitmentRecord — tamper-evident HMAC-chained logs |
rcan.gates |
ConfidenceGate, HiTLGate — safety gates for AI-driven actions |
rcan.replay |
ReplayCache — sliding-window replay attack prevention (GAP-03) |
rcan.clock |
ClockSyncStatus — NTP clock sync verification (GAP-04) |
rcan.qos |
QoSLevel — FIRE_AND_FORGET / ACKNOWLEDGED / EXACTLY_ONCE (GAP-11) |
rcan.consent |
Consent wire protocol — request/grant/deny (GAP-05) |
rcan.revocation |
Robot identity revocation with TTL cache (GAP-02) |
rcan.training_consent |
Training data consent, GDPR/EU AI Act Annex III §5 (GAP-10) |
rcan.delegation |
Command delegation chain, max 4 hops, Ed25519-signed (GAP-01) |
rcan.offline |
Offline operation mode — ESTOP always allowed (GAP-06) |
rcan.fault |
FaultCode structured fault taxonomy (GAP-20) |
rcan.federation |
Federated consent — cross-registry trust, DNS discovery (GAP-16) |
rcan.transport |
Constrained transports — compact CBOR, 32-byte ESTOP minimal, BLE (GAP-17) |
rcan.multimodal |
Multi-modal payloads — inline/ref media, streaming (GAP-18) |
rcan.identity |
Level of Assurance — LoA policies, JWT parsing (GAP-14) |
rcan.keys |
Key rotation with JWKS-compatible KeyStore (GAP-09) |
rcan.config_update |
CONFIG_UPDATE protocol with safety scope enforcement (GAP-07) |
rcan.node |
NodeClient — resolve RRNs across federated registry nodes (§17) |
rcan.validate |
L1/L2/L3 conformance validation for configs, messages, URIs |
rcan.assurance |
RCAN Appendix C evidence checks: gate-decision chains, envelopes, recovery fields (informative) |
Protocol 66 Compliance
Protocol 66 is RCAN's safety layer. Key invariants enforced by this SDK:
- ESTOP always delivered —
QoSLevel.EXACTLY_ONCE, never blocked by QoS downgrade - Local safety wins —
offlinemodule enforces local limits even without cloud connectivity - Confidence gates run locally —
ConfidenceGatenever makes a network call - Audit chain required —
AuditChain.verify_all()before executing any flagged command
from rcan import RCANMessage
from rcan.offline import OfflineModeManager
from rcan.safety import make_estop_message, validate_safety_message
robot = "rcan://registry.rcan.dev/acme/arm/v2/unit-001"
estop = make_estop_message(robot, reason="operator button")
assert validate_safety_message(estop) == []
offline = OfflineModeManager()
offline.go_offline()
allowed, why = offline.can_accept_command(RCANMessage(cmd="estop", target=robot))
assert allowed, why # ESTOP is never blocked, even offline
allowed, why = offline.can_accept_command(
RCANMessage(cmd="move_forward", target=robot), local_network=False
)
assert not allowed # remote commands are blocked while offline
CLI
# Validate a robot config (L1/L2/L3 conformance)
rcan-validate config myrobot.rcan.yaml
# Validate a RCAN message
rcan-validate message command.json
# Verify an audit chain
rcan-validate audit audit.jsonl
# Validate a Robot URI
rcan-validate uri 'rcan://registry.rcan.dev/acme/arm/v2/unit-001'
# Run all checks
rcan-validate all myrobot.rcan.yaml
Spec Compliance
Implements the RCAN protocol; run pytest for the current test count. See the live compatibility matrix for the pinned version this SDK targets.
Covered sections: §1 Robot URI · §2 RBAC · §3 Message Format · §5 Authentication · §5.3 QoS · §6 Safety Invariants · §8.3 Replay Prevention · §8.4 Clock Sync · §8.5 Sender Type · §8.6 Key Lifecycle · §8.7 Level of Assurance · §9 Capabilities · §11 Behavior Scripts · §12 Command Delegation · §13 Revocation · §14 Offline Mode · §16 AI Accountability · §17 Distributed Registry · §18 Federation · §19 Constrained Transport · §21 Registry Integration
Physical assurance (RCAN Appendix C, informative)
rcan.assurance ports the reference verifier from
RCAN Appendix C,
the Physical Assurance Profile. The appendix is informative and these helpers
are optional: nothing in RCAN requires them, and using them changes no
conformance level.
RCAN is one open implementation of the Bounded Embodiment property; the property and its vendor-neutral method do not depend on RCAN.
import json
from rcan.assurance import (
audit_authority, check_envelope_recovery, check_stop_fields,
replay_against_envelope, verify_chain,
)
chain = json.load(open("gate-decisions.json")) # list of gate_decision records
envelope = json.load(open("envelope.json"))
findings = [
*verify_chain(chain, expected_head=None), # pass an anchored head if you hold one
*audit_authority(chain, envelope),
*replay_against_envelope(chain, envelope),
*check_envelope_recovery(envelope), # stop.safe_state, recovery.*
*check_stop_fields(chain), # stop_kind, reason_returned
]
for f in findings:
print(f.seq, f.code, f.detail)
What these helpers do and do not tell you:
- They verify evidence, not robots. They check that a
gate_decisionlog is internally consistent, that executed authority-gated commands name a principal and an authority, and that applied commands sit inside the declared envelope. A passing chain says nothing about whether the machine behaved as logged. - Without an anchored head,
verify_chaincannot detect records removed from the end of a chain. Passexpected_headwhen you hold the last hash from somewhere the log writer cannot rewrite. replay_against_envelopejudges onlylinear_mps,angular_radpsand a 2Dtarget. Any other applied field is reported asUNCHECKED_FIELDS, never silently passed.check_envelope_recoveryandcheck_stop_fieldscheck only the optional recovery fields from Appendix C.1.3 (stop.safe_state,recovery,stop_kind,reason_returned) and their conditional rules:stop_kindonly withstop,reason_returnednever withallow,estop_resetonlymanual. They are not a full JSON Schema validation.- Physical assurance levels BE-1 to BE-3 (
BE_LEVELS; named A1 to A3 before rcan-spec #226, still exported asASSURANCE_LEVELS) are a separate axis from RCAN protocol conformance levels L1 to L4. An L3 robot can be BE-1, and neither level implies the other. A BE-level is self-declared unless third-party evidence accompanies it.
Conformance is not certification.
Ecosystem
| Package | Purpose |
|---|---|
| rcan-py (this) | Python SDK, PyPI rcan |
| rcan-ts | TypeScript SDK, npm rcan-ts |
| rcan-spec | Protocol spec, published at rcan.dev |
| RCAN docs | Guides and reference |
| ROBOT.md | Single-file robot manifest |
| OpenCastor | Robot runtime, one implementation of RCAN |
| RRF | Robot identity registry |
Current versions: see the live compatibility matrix.
Contributing
Issues and PRs welcome at github.com/RobotRegistryFoundation/rcan-py.
Spec discussions: github.com/RobotRegistryFoundation/rcan-spec/issues
License
MIT
Stewarded by the Robot Registry Foundation. RCAN is an open standard; issues and PRs welcome. See CONTRIBUTING.md.
Metadata
Release files for rcan 3.7.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 | |
|---|---|---|---|---|
| rcan-3.7.0.tar.gz | 198.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rcan-3.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 334.7 kB
Release files / rcan-3.7.0.tar.gz
| Download URL | rcan-3.7.0.tar.gz |
|---|---|
| Size | 198.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
191fec57be9e5eeb50f641f4c750e08a89b923e88627e68a962215a2093e9660
|
|
BLAKE2b-256 checksum How to use checksums |
c86bf110e3fe08c1f56c87b7c71e9d41d0dd0d8a5af2ce934040ee86071a5a50
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / rcan-3.7.0-py3-none-any.whl
| Download URL | rcan-3.7.0-py3-none-any.whl |
|---|---|
| Size | 136.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4f1d971abd1ac41d7bb25d1f801b6f77168b405dbe6881720fe3faac20ecfef6
|
|
BLAKE2b-256 checksum How to use checksums |
f08bad919c0c2f6e454aa6887af59d0adfb9bc201df2c4bdb842379a9cbd7274
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for rcan 3.7.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.