Quickstart
From zero to your first RCAN message in 5 minutes.
01Install the SDK
# Install
pip install rcan
# Optional extras
pip install rcan[http] # registry client (httpx) pip install rcan[crypto] # Ed25519 message signing pip install rcan[all] # everything 02Build a Robot URI
Every robot has a globally unique, resolvable address. No more "which unit was it?"
from rcan import RobotURI
uri = RobotURI.build(
manufacturer="acme",
model="robotarm",
version="v2",
device_id="unit-001",
)
print(uri)
# rcan://registry.rcan.dev/acme/robotarm/v2/unit-001
# Parse from a string
uri = RobotURI.parse("rcan://registry.rcan.dev/acme/robotarm/v2/unit-001")
print(uri.namespace) # acme/robotarm
print(uri.registry_url) # https://registry.rcan.dev/registry/... 03Gate on AI Confidence
The gate doesn't trust the model — that's the point. The model is right 99.99% of the time. The gate catches the 0.01%.
from rcan import RCANMessage, ConfidenceGate
gate = ConfidenceGate(threshold=0.8)
confidence = 0.91 # from your AI model
if gate.allows(confidence):
msg = RCANMessage(
cmd="move_forward",
target=uri,
params={"distance_m": 1.0},
confidence=confidence,
model_identity="Qwen2.5-7B-Q4",
)
print(msg.to_json(indent=2))
else:
print(f"Blocked: confidence {confidence} < threshold {gate.threshold}") For high-stakes actions, add a HiTLGate to require explicit human approval. See §16 AI Accountability.
04Create a Tamper-Evident Audit Record
Every safety-critical action can be sealed into an HMAC-chained record — forensic-grade proof of what the system did, when, at what confidence.
from rcan import CommitmentRecord
from rcan.audit import AuditChain
chain = AuditChain(secret="your-hmac-secret")
record = chain.append(CommitmentRecord(
action="move_forward",
robot_uri=str(uri),
confidence=0.91,
model_identity="Qwen2.5-7B-Q4",
params={"distance_m": 1.0},
safety_approved=True,
))
print(f"Sealed: {record.content_hash[:12]}...")
print(f"Chain valid: {chain.verify_all()}") # True
# Export JSONL for long-term storage or audit
with open("audit.jsonl", "w") as f:
f.write(chain.to_jsonl()) 05Register Your Robot
Get a globally unique RRN (Robot Registration Number). Free.
New RRNs use 12-digit sequences (e.g. RRN-000000000001). Legacy 8-digit RRNs (RRN-000000000001) remain valid — the format is backward compatible.
# If using OpenCastor — built into the setup wizard
castor wizard
# → Step 13: Register with rcan.dev? [Y/n] → ✅ RRN-000000000042
# Or standalone
castor register --config myrobot.rcan.yaml 05bFederated Resolution
Any client can resolve an RRN across the federation — even if registered at a manufacturer's authoritative node:
from rcan import NodeClient
client = NodeClient()
# Resolve an RRN from any node in the federation robot = client.resolve("RRN-BD-000000000001") print(f"Found: {robot['record']['name']} (resolved by {robot['resolved_by']})") # Discover which node is authoritative for a namespace node = client.discover("RRN-BD-000000000001") print(f"Authoritative node: {node['operator']} at {node['api_base']}") # List all known registry nodes nodes = client.list_nodes()
for n in nodes:
print(f" {n['operator']}: {n['namespace_prefix']}") 06Invoke a Skill (§19)
Once your robot is connected, you can invoke named skills using the INVOKE message type (§19.2). The caller sends an INVOKE frame; the robot replies with INVOKE_RESULT (§19.3).
import asyncio
from rcan import RCANClient
async def main():
async with RCANClient("wss://myrobot.local:8765") as client:
# Invoke the pick_and_place skill with a 5 s timeout
result = await client.invoke(
skill="pick_and_place",
params={"target": "red_cube"},
timeout_ms=5000,
)
print(result.status) # "success" | "failure" | "timeout" | "cancelled"
print(result.result) # skill-specific output dict
asyncio.run(main()) import { RCANClient } from 'rcan-ts';
const client = new RCANClient('wss://myrobot.local:8765');
await client.connect();
const result = await client.invoke('pick_and_place', {
params: { target: 'red_cube' },
timeoutMs: 5000,
});
console.log(result.status); // "success" | "failure" | "timeout" | "cancelled"
console.log(result.result); // skill output
// Cancel an in-flight invocation (§19.4)
const invocation = client.invoke('navigate', { params: { goal: 'kitchen' } });
await client.cancelInvoke(invocation.msgId); Wire values: INVOKE = 11,
INVOKE_RESULT = 12, INVOKE_CANCEL = 15.
See §19 full spec for error codes,
cancellation semantics, and the Capability Advertisement integration.
07Emit Telemetry (§20)
RCAN robots report state via STATUS messages that carry a structured Robot State object (§20.3). Joint sensor readings go in the nested joints array (§20.2). All field names, units, and types are defined in the Telemetry Field Registry so consumers can interpret readings without out-of-band documentation.
from rcan import RCANClient
async with RCANClient("wss://myrobot.local:8765") as client:
await client.publish_status({
"ruri": "rcan://myrobot.local",
"battery_percent": 82.5,
"battery_voltage": 12.4,
"uptime_seconds": 3612,
"mode": "autonomous",
"joints": [
{
"joint_name": "shoulder_pan",
"position_rad": 0.523,
"velocity_rad_s": 0.0,
"effort_nm": 1.2,
"temperature_c": 38.1,
}
],
}) Extensible: The registry follows an open-world model — you can add custom fields alongside standard ones. Standard fields use the canonical names defined in §20 so dashboards and monitoring tools can process them without configuration.
08Validate Your Config
# Check L1/L2/L3 conformance
rcan-validate config myrobot.rcan.yaml
# Expected output:
# ✅ L1 — Addressing + message format: passed
# ✅ L2 — Auth + confidence gates: passed
# ⚠️ L3 — hitl_gates not configured (§16)
# Result: L2 (1 warning)
# Or with OpenCastor
castor compliance --config myrobot.rcan.yaml Once you have an RCAN robot, register it at robotregistryfoundation.org. Registration is free and gives your robot a globally unique RRN (Robot Registration Number), enabling federated identity resolution across the RCAN network.