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 | |
|---|---|---|---|
| rcan-3.7.0.tar.gz | 198.6 kB | 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
|