RCAN Protocol Specification
Table of Contents
- 01. Robot URI (RURI)
- 02. Role-Based Access
- 03. Message Format
- 04. Discovery (mDNS)
- 05. Authentication
- 06. Safety Invariants
- 07. Federation
- 08. Robot Config (RCAN File)
- 09. Capabilities
- 10. Autonomous Navigation
- 11. Behavior Scripts
- 12. Depth & Sensing
- 13. Telemetry Streaming
- 14. Provider Management
- 15. Swarm Coordination
1. Robot URI (RURI)
Every robot has a globally unique RURI. Two syntactic forms are valid:
Canonical form
rcan://<registry>/<manufacturer>/<model>/<device-id>[:<port>][/<capability>] Local shorthand
When no registry domain is needed (LAN-only deployments), the authority section
uses dot-separated segments. Parsers MUST expand this to the canonical form with
local.rcan as the registry:
rcan://<manufacturer>.<model>.<instance>[/<capability>] Example: rcan://opencastor.rover.abc123 expands to rcan://local.rcan/opencastor/rover/abc123
# Canonical form (with registry)
rcan://continuon.cloud/continuon/companion-v1/d3a4b5c6
rcan://continuon.cloud/continuon/companion-v1/d3a4b5c6/arm
rcan://my-server.lan/acme/bot-x1/a1b2c3d4:9000/teleop
# Local shorthand (no registry; implies local.rcan)
rcan://acme.bot-x1.a1b2c3d4 # → rcan://local.rcan/acme/bot-x1/a1b2c3d4
rcan://opencastor.rover.abc123/nav # → rcan://local.rcan/opencastor/rover/abc123/nav
# Special addresses
rcan://local.rcan/discovered/192.168.1.42:8080 # mDNS-discovered device Components
| Component | Description | Required |
|---|---|---|
registry | Root registry domain (continuon.cloud, local.rcan) | Canonical only |
manufacturer | Manufacturer or organisation namespace | Yes |
model | Robot model identifier | Yes |
device-id | UUID or 8-char hex short-form; dot-form uses any alphanumeric slug | Yes |
port | Communication port (default: 8000) | No |
capability | Specific endpoint path (e.g. /arm, /nav) | No |
Validation patterns
# Canonical RURI
^rcan://([a-z0-9][a-z0-9.-]*[a-z0-9])/([a-z0-9][a-z0-9-]*[a-z0-9])/([a-z0-9][a-z0-9-]*[a-z0-9])/([0-9a-f]{8}(?:-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?)(?::(d{1,5}))?(/[a-z][a-z0-9/-]*)?$
# Local shorthand (authority contains dots, no path segments for identity)
^rcan://([a-z0-9][a-z0-9-]*).([a-z0-9][a-z0-9-]*).([a-z0-9]{4,36})(/[a-z][a-z0-9/-]*)?$ Wildcard matching: * may appear in any segment for audience
matching in JWT tokens (e.g. rcan://continuon.cloud/continuon/companion-v1/*
grants access to all devices of that model).
2. Role-Based Access Control
RCAN defines a five-level role hierarchy. Higher roles inherit all permissions of lower roles.
| Role | Level | Rate limit | Session TTL | Key permissions |
|---|---|---|---|---|
| CREATOR | 5 | Unlimited | None | Full hardware/software control, OTA push, safety override |
| OWNER | 4 | 1000/min | 8 h | Configuration, skill installation, user management |
| LEASEE | 3 | 500/min | 2 h | Time-bound operational control, cannot change ownership |
| USER | 2 | 100/min | 1 h | Operational control within allowed modes |
| GUEST | 1 | 10/min | 5 min | Status viewing, read-only telemetry, text chat |
Scopes
Scopes are fine-grained permissions included in JWT tokens:
| Scope | Minimum role | Grants |
|---|---|---|
status | GUEST | Read health, telemetry, sensor data |
control | USER | Issue commands, drive motors, run behaviors |
config | OWNER | Modify RCAN config, reload, OTA |
training | OWNER | Submit episodes, trigger learner, export data |
admin | CREATOR | Safety overrides, hardware resets, key rotation |
Gateway roles (implementation note)
Implementations MAY expose a simplified three-level gateway role system for human operators, mapped onto RCAN roles as follows:
| Gateway role | RCAN role | Default scopes |
|---|---|---|
admin | OWNER (4) | status, control, config, training |
operator | LEASEE (3) | status, control |
viewer | GUEST (1) | status |
3. Message Format
All RCAN messages use a common envelope. Implementations MUST support JSON encoding; Protobuf encoding is RECOMMENDED for bandwidth-constrained links.
message RCANMessage {
string version = 1; // "1.1.0"
string message_id = 2; // UUID v4
string source_ruri = 3; // Sender RURI
string target_ruri = 4; // Recipient RURI or "broadcast"
string auth_token = 5; // JWT session token (optional for DISCOVER)
MessageType type = 6;
bytes payload = 7; // Type-specific JSON payload
int64 timestamp_ms = 8;
int32 ttl_ms = 9; // Time-to-live (0 = no expiry)
Priority priority = 10;
string reply_to = 11; // RURI to reply to (optional)
repeated string scope = 12; // Required scopes for this message
enum MessageType {
DISCOVER = 1;
STATUS = 2;
COMMAND = 3;
STREAM = 4;
EVENT = 5;
HANDOFF = 6;
ACK = 7;
ERROR = 8;
}
enum Priority {
LOW = 1;
NORMAL = 2;
HIGH = 3;
SAFETY = 4; // Always processed first; cannot be rate-limited
}
} Payload types
| MessageType | Required scope | Payload fields |
|---|---|---|
DISCOVER | none | capabilities[], ruri |
STATUS | status | state, battery_v, loop_latency_ms |
COMMAND | control | instruction, image_b64 (optional) |
STREAM | status | See §13 Telemetry Streaming |
EVENT | status | event_type, data |
HANDOFF | control | target_ruri, reason |
ACK | — | ref_id, ok |
ERROR | — | code, message, ref_id |
4. Discovery (mDNS)
For LAN discovery without cloud connectivity, implementations MUST broadcast:
_rcan._tcp.local. Required TXT records
ruri=rcan://continuon.cloud/continuon/companion-v1/d3a4b5c6
model=companion-v1
caps=nav,vision,chat,teleop,arm,status
roles=owner,user,guest
version=1.1.0
name=Living Room Companion
status=idle caps MUST be a comma-separated list of standard capability names (§9).
status SHOULD be one of: idle, running, paused, error.
5. Authentication
RCAN JWT (device-level)
Full RCAN tokens include all identity and fleet claims:
{
"sub": "550e8400-e29b-41d4-a716-446655440000",
"iss": "rcan://continuon.cloud/continuon/companion-v1/d3a4b5c6",
"aud": "rcan://continuon.cloud/continuon/companion-v1/*",
"role": "owner",
"scope": ["status", "control", "config", "training"],
"fleet": ["d3a4b5c6", "a1b2c3d4"],
"exp": 1735689600,
"iat": 1735603200
} Gateway JWT (operator-level)
Implementations MAY also issue simplified gateway tokens for human operators. These MUST be mapped to a RCAN role before authorization decisions are made:
{
"sub": "alice",
"role": "operator",
"iss": "opencastor-gateway",
"exp": 1735689600,
"iat": 1735603200
}
/* Gateway roles map to RCAN roles:
admin → OWNER (level 4)
operator → LEASEE (level 3)
viewer → GUEST (level 1) */ Verification order
- Validate JWT signature (HMAC-SHA256 minimum; RS256 recommended for production)
- Check
expandiatclaims - Verify
audmatches the robot's RURI (wildcard matching allowed) - Map
roleto RCAN level; check level ≥ required for requested scope - If
fleetis present, verify target device-id is in the list
Token endpoint
POST /auth/token
Body: { "username": "alice", "password": "..." }
Reply: { "access_token": "...", "token_type": "bearer", "role": "operator" } 6. Safety Invariants
These are protocol requirements, not suggestions:
- Local safety always wins. No remote command can bypass on-device safety checks, bounds checking, or emergency-stop state.
- Graceful degradation. Network loss MUST trigger a safe-stop within one
latency_budget_msinterval, not undefined behavior. - Audit trail. All COMMAND and CONFIG messages MUST be logged with: principal identity, RURI, timestamp (ms), message_id, outcome (
ok/blocked/error). - Rate limiting. Commands MUST be throttled per role per source RURI (see §2 role table).
- Timeout enforcement. Control sessions expire per session TTL (see §2). Explicit renewal MUST be required before timeout; implicit renewal MUST NOT occur.
- SAFETY priority bypass. Messages with
Priority.SAFETYMUST skip rate-limiting queues and be processed before any other message. - Prompt injection defense. Implementations that forward NL instructions to an LLM MUST scan for injection patterns before calling the model. A
ScanVerdict.BLOCKresult MUST return an error Thought without calling the model.
7. Federation
RCAN is federated like email, not centralized like a platform:
rcan://continuon.cloud/...— Managed servicercan://my-local-server.lan/...— Self-hostedrcan://robotics-co-op.org/...— Community run
The Right to Redirect: An OWNER (level 4) can always change the robot's upstream registry without losing local discovery.
Local Supremacy: Even if the cloud registry deletes your ID,
_rcan._tcp.localdiscovery always works.
8. Robot Configuration (RCAN File)
A RCAN config file (extension .rcan.yaml) is the primary artifact
that describes a robot. Implementations MUST validate configs against the JSON Schema
published at https://rcan.dev/schema/rcan.schema.json before starting any runtime.
Required top-level keys
| Key | Type | Description |
|---|---|---|
rcan_version | string (semver) | Spec version this config targets |
metadata | object | Identity: robot_name, robot_uuid, author, license |
agent | object | LLM provider, model, latency budget, safety flags |
physics | object | Kinematic type, DoF, chassis dimensions |
drivers | array (≥1) | Hardware driver configurations |
network | object | Telemetry and remote access flags |
rcan_protocol | object | Port, capabilities, mDNS, JWT flags |
Reference example
rcan_version: "1.1.0"
metadata:
robot_name: "Alex"
robot_uuid: "550e8400-e29b-41d4-a716-446655440000"
author: "craigm26"
license: "Apache-2.0"
manufacturer: "opencastor"
model: "rover"
tags: [rpi5, camera, i2c, rover]
agent:
provider: "huggingface"
model: "Qwen/Qwen2.5-VL-7B-Instruct"
latency_budget_ms: 3000
safety_stop: true
provider_fallback:
enabled: true
provider: "ollama"
model: "llama3.2:3b"
quota_cooldown_s: 3600
alert_channel: "telegram"
physics:
type: "differential_drive"
dof: 2
wheel_circumference_m: 0.21
turn_time_per_deg_s: 0.008
drivers:
- protocol: "pca9685"
port: "/dev/i2c-1"
address: "0x40"
channels:
left_motor: 0
right_motor: 1
camera:
type: "oakd"
resolution: [640, 480]
framerate: 30
depth_enabled: true
rcan_protocol:
port: 8000
capabilities: [nav, vision, chat, teleop, status]
enable_mdns: true
enable_jwt: true The full JSON Schema (with all optional blocks: tiered_brain, reactive, learner,
camera, audio, channels, swarm, ros2, provider_fallback) is
published at rcan.dev/schema/rcan.schema.json.
9. Capabilities
Capabilities are advertised in rcan_protocol.capabilities and in mDNS TXT records.
They control which RCAN message types are accepted and what scope is required.
| Capability | Scope Required | Description | Standard Endpoints |
|------------|---------------|--------------------------------------|-------------------------|
| status | STATUS | Health, telemetry, sensor readings | GET /api/status |
| nav | CONTROL | Autonomous waypoint navigation | POST /api/nav/waypoint |
| teleop | CONTROL | Direct real-time motor commands | POST /api/action |
| vision | STATUS | Camera stream, depth overlay | GET /api/stream/mjpeg |
| chat | CONTROL | NL command → brain → action | POST /api/command |
| arm | CONTROL | Manipulator/gripper control | POST /api/action | Implementations MAY declare custom capabilities using reverse-domain notation:
com.acme.gripper. Custom capabilities MUST require at least control scope.
Auto-detection rules
- Any driver with
protocol: pca9685orprotocol: dynamixel→ nav + teleop - A
driversentry withprotocol: dynamixeland kinematics → arm camerablock present → visionagentblock present → chat- Always declared → status
11. Behavior Scripts
Behavior scripts are YAML files that define named sequences of robot steps. They provide repeatable, auditable automation without custom code.
# behavior.yaml — RCAN Behavior Script v1.1
name: patrol_loop
steps:
- type: speak
text: "Starting patrol"
- type: waypoint
distance_m: 1.0
heading_deg: 0
speed: 0.5
- type: think
instruction: "Describe what you see"
- type: wait
seconds: 2
- type: waypoint
distance_m: 1.0
heading_deg: 180 # turn around
- type: speak
text: "Patrol complete"
- type: stop Standard step types
| Type | Required fields | Description |
|---|---|---|
waypoint | distance_m, heading_deg | Dead-reckoning move (§10) |
wait | seconds | Pause execution |
think | instruction | Send NL prompt to brain; result available as last_thought |
speak | text | TTS output via audio system |
stop | — | Motor e-stop; also terminates behavior |
command | action (dict) | Send raw action dict directly to driver |
Behavior API
POST /api/behavior/run { path: "behaviors/patrol.yaml", behavior: "patrol_loop"}
POST /api/behavior/stop
GET /api/behavior/status → { running, current_step, behavior_name} Only one behavior may run at a time. Running a new behavior MUST stop the current one first.
12. Depth & Sensing
Robots with stereo depth cameras (e.g. Intel OAK-D, RealSense) SHOULD implement the
depth sensing API. Implementations without depth hardware MUST return
{"available": false}.
// GET /api/depth/obstacles
{
"available": true,
"left_cm": 45, // min depth in left third
"center_cm": 82, // min depth in centre third
"right_cm": 38, // min depth in right third ← nearest
"nearest_cm": 38,
"timestamp_ms": 1735603215123
}
// GET /api/depth/frame
// Returns: JPEG image with JET colormap depth overlay (45% opacity)
// Content-Type: image/jpeg Zone definition
The depth frame is divided into three equal vertical thirds:
- left_cm — minimum depth in columns 0–W/3
- center_cm — minimum depth in columns W/3–2W/3
- right_cm — minimum depth in columns 2W/3–W
- nearest_cm — minimum across all three zones
The depth overlay image (GET /api/depth/frame) MUST use the JET colormap
(blue=far, red=near) blended over the RGB frame.
Safety integration
Implementations SHOULD integrate obstacle zone readings into the COMMAND pipeline:
a nearest_cm below the configured agent.min_obstacle_m * 100 value MUST
trigger an e-stop before the motor command is dispatched.
13. Telemetry Streaming
Implementations MUST expose a WebSocket endpoint at /ws/telemetry that pushes
robot state at a configurable rate (default: 5 Hz).
// WS /ws/telemetry?token=<jwt>
// Server pushes at 5 Hz (200 ms interval)
{
"ts": 1735603215123, // Unix ms
"loop_latency_ms": 412,
"loop_count": 8423,
"battery_v": 7.4, // null if not available
"provider": "huggingface",
"using_fallback": false,
"obstacles": {
"left_cm": 45,
"center_cm": 82,
"right_cm": 38,
"nearest_cm": 38
},
"nav_running": false,
"behavior_name": null,
"paused": false
} Authentication
The WebSocket endpoint SHOULD accept a token query parameter
(wss://robot:8000/ws/telemetry?token=<jwt>).
Unauthenticated connections MUST be rejected if rcan_protocol.enable_jwt: true.
Field requirements
| Field | Required | Type | Description |
|---|---|---|---|
ts | MUST | int (Unix ms) | Server timestamp |
loop_latency_ms | MUST | number | Last perception-action loop duration |
loop_count | MUST | int | Total loop iterations since start |
provider | SHOULD | string | Active LLM provider name |
using_fallback | SHOULD | bool | Whether the fallback provider is active |
battery_v | SHOULD | number | null | Battery voltage; null if not available |
obstacles | SHOULD | object | null | See §12 obstacle zone schema |
paused | MUST | bool | Whether perception-action loop is paused |
14. Provider Management
An RCAN robot runtime manages one or more LLM "brains" and MUST handle provider failures gracefully. Two fallback strategies are defined:
Quota fallback (provider_fallback)
When the primary provider returns a quota or billing error (HTTP 402/429 or keywords: credits exhausted, rate limit, quota), the runtime MUST:
- Switch transparently to the configured
provider_fallbackprovider - Record the switch timestamp
- Alert the operator via the configured
alert_channel - After
quota_cooldown_s, attempt to restore the primary on the next request
Offline fallback (offline_fallback)
When the runtime detects internet loss (HTTP reachability check), it MUST switch to a local provider (Ollama, llama.cpp, MLX) automatically. The switch back to cloud occurs after connectivity is restored.
RCAN config blocks
provider_fallback:
enabled: true
provider: "ollama" # target fallback provider
model: "llama3.2:3b"
quota_cooldown_s: 3600 # seconds before retrying primary
alert_channel: "telegram" # channel to notify on switch
offline_fallback:
enabled: true
provider: "ollama"
model: "llama3.2:3b"
check_interval_s: 30
alert_channel: "telegram" Health check interface
All provider adapters MUST implement:
health_check() → { "ok": bool, "latency_ms": float, "error": str | null } The runtime SHOULD call health_check() on the fallback provider at startup
and surface the result at GET /api/provider/health.
15. Swarm Coordination
A swarm is a named set of RCAN robots that can be queried and commanded
together. Swarm membership is declared in a swarm.yaml node registry.
Node registry format
# config/swarm.yaml — RCAN Swarm Node Registry
nodes:
- name: "alex"
host: "alex.local"
ip: "192.168.68.91" # static IP fallback
port: 8000
token: "<OPENCASTOR_API_TOKEN for this node>"
rcan: "~/OpenCastor/alex.rcan.yaml"
tags: [rpi5, camera, i2c, rover]
added: "2026-02-21" Swarm operations
// POST /api/command (broadcast to all swarm nodes)
// Implementors SHOULD forward to swarm peers when X-Swarm-Broadcast: true
{
"instruction": "stop all motors",
"broadcast": true
}
// CLI shorthand
// castor swarm command --instruction "go forward" [--node alex]
// castor swarm stop [--node alex]
// castor swarm status [--json] Required swarm endpoints
| Endpoint | Description |
|---|---|
GET /api/status | Per-node health (includes ruri, version, caps) |
POST /api/command + X-Swarm-Broadcast: true | Fan-out command to all peers |
POST /api/stop | Immediate e-stop; MUST respond within 500 ms |
Swarm safety rule
A swarm broadcast MUST NOT override a node's local e-stop state. If a node is in e-stop, it MUST acknowledge the broadcast but MUST NOT execute the command.