RCAN Protocol Specification

Version 1.1.0 Draft View v1.0.0 →

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

ruri-examples.txt
# 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

ComponentDescriptionRequired
registryRoot registry domain (continuon.cloud, local.rcan)Canonical only
manufacturerManufacturer or organisation namespaceYes
modelRobot model identifierYes
device-idUUID or 8-char hex short-form; dot-form uses any alphanumeric slugYes
portCommunication port (default: 8000)No
capabilitySpecific endpoint path (e.g. /arm, /nav)No

Validation patterns

ruri-validation.regex
# 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.

RoleLevelRate limitSession TTLKey permissions
CREATOR5UnlimitedNoneFull hardware/software control, OTA push, safety override
OWNER41000/min8 hConfiguration, skill installation, user management
LEASEE3500/min2 hTime-bound operational control, cannot change ownership
USER2100/min1 hOperational control within allowed modes
GUEST110/min5 minStatus viewing, read-only telemetry, text chat

Scopes

Scopes are fine-grained permissions included in JWT tokens:

ScopeMinimum roleGrants
statusGUESTRead health, telemetry, sensor data
controlUSERIssue commands, drive motors, run behaviors
configOWNERModify RCAN config, reload, OTA
trainingOWNERSubmit episodes, trigger learner, export data
adminCREATORSafety 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 roleRCAN roleDefault scopes
adminOWNER (4)status, control, config, training
operatorLEASEE (3)status, control
viewerGUEST (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_envelope.proto
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

MessageTypeRequired scopePayload fields
DISCOVERnonecapabilities[], ruri
STATUSstatusstate, battery_v, loop_latency_ms
COMMANDcontrolinstruction, image_b64 (optional)
STREAMstatusSee §13 Telemetry Streaming
EVENTstatusevent_type, data
HANDOFFcontroltarget_ruri, reason
ACKref_id, ok
ERRORcode, message, ref_id

4. Discovery (mDNS)

For LAN discovery without cloud connectivity, implementations MUST broadcast:

_rcan._tcp.local.

Required TXT records

mdns-txt-records.txt
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:

rcan_session_token.jwt
{
  "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:

gateway_token.jwt
{
  "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

  1. Validate JWT signature (HMAC-SHA256 minimum; RS256 recommended for production)
  2. Check exp and iat claims
  3. Verify aud matches the robot's RURI (wildcard matching allowed)
  4. Map role to RCAN level; check level ≥ required for requested scope
  5. If fleet is 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:

  1. Local safety always wins. No remote command can bypass on-device safety checks, bounds checking, or emergency-stop state.
  2. Graceful degradation. Network loss MUST trigger a safe-stop within one latency_budget_ms interval, not undefined behavior.
  3. Audit trail. All COMMAND and CONFIG messages MUST be logged with: principal identity, RURI, timestamp (ms), message_id, outcome (ok/blocked/error).
  4. Rate limiting. Commands MUST be throttled per role per source RURI (see §2 role table).
  5. Timeout enforcement. Control sessions expire per session TTL (see §2). Explicit renewal MUST be required before timeout; implicit renewal MUST NOT occur.
  6. SAFETY priority bypass. Messages with Priority.SAFETY MUST skip rate-limiting queues and be processed before any other message.
  7. Prompt injection defense. Implementations that forward NL instructions to an LLM MUST scan for injection patterns before calling the model. A ScanVerdict.BLOCK result 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 service
  • rcan://my-local-server.lan/... — Self-hosted
  • rcan://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.local discovery 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

KeyTypeDescription
rcan_versionstring (semver)Spec version this config targets
metadataobjectIdentity: robot_name, robot_uuid, author, license
agentobjectLLM provider, model, latency budget, safety flags
physicsobjectKinematic type, DoF, chassis dimensions
driversarray (≥1)Hardware driver configurations
networkobjectTelemetry and remote access flags
rcan_protocolobjectPort, capabilities, mDNS, JWT flags

Reference example

alex.rcan.yaml
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.

standard-capabilities.md
| 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: pca9685 or protocol: dynamixelnav + teleop
  • A drivers entry with protocol: dynamixel and kinematics → arm
  • camera block present → vision
  • agent block 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.

patrol_loop.behavior.yaml
# 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

TypeRequired fieldsDescription
waypointdistance_m, heading_degDead-reckoning move (§10)
waitsecondsPause execution
thinkinstructionSend NL prompt to brain; result available as last_thought
speaktextTTS output via audio system
stopMotor e-stop; also terminates behavior
commandaction (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}.

obstacle-zones.json
// 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).

telemetry-message.json
// 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

FieldRequiredTypeDescription
tsMUSTint (Unix ms)Server timestamp
loop_latency_msMUSTnumberLast perception-action loop duration
loop_countMUSTintTotal loop iterations since start
providerSHOULDstringActive LLM provider name
using_fallbackSHOULDboolWhether the fallback provider is active
battery_vSHOULDnumber | nullBattery voltage; null if not available
obstaclesSHOULDobject | nullSee §12 obstacle zone schema
pausedMUSTboolWhether 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:

  1. Switch transparently to the configured provider_fallback provider
  2. Record the switch timestamp
  3. Alert the operator via the configured alert_channel
  4. 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

swarm.yaml
# 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

swarm-broadcast.json
// 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

EndpointDescription
GET /api/statusPer-node health (includes ruri, version, caps)
POST /api/command + X-Swarm-Broadcast: trueFan-out command to all peers
POST /api/stopImmediate 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.

Edit this page on GitHub Last updated: 2/22/2026