Message Type Reference

The RCAN protocol defines a canonical set of message types covering discovery, control, safety, invocation, registry operations, consent, fleet, and EU AI Act compliance. All messages share a common envelope; the type field selects semantics and routing behaviour. See the live compatibility matrix for the canonical message-type table for each revision.

MessageType Canonicalization β€” Earlier RCAN protocol drafts had a mismatch between the spec's integer table and the rcan-py SDK. Subsequent revisions establish a single canonical table. The previous "spec" numbering (DISCOVER=1, STATUS=2…) is retired; implementations that hardcoded integers must migrate to named enum constants. See the migration note below.
v1.6 Message Changes β€” No new MessageType integers are added in v1.6. Three existing types gain extended payload schemas:
  • Type 9 (DISCOVER): Response payload gains supported_transports field (Β§19 constrained transport negotiation). See Β§19.
  • Type 10 (TRAINING_DATA): JSON-only binary in payload deprecated. Image/video/audio MUST use media_chunks[]. See Β§5.4.
  • Type 12 (FEDERATION_SYNC): Full payload spec defined β€” {source_registry, target_registry, sync_type, payload, signature}. See Β§18.

Β§3 β€” Envelope

Every RCAN message uses the same JSON envelope regardless of type:

{
  "id":              "uuid-v4",          // msg_id β€” unique per message
  "type":            3,                  // MessageType integer (canonical v1.5)
  "priority":        1,                  // 0=LOW 1=NORMAL 2=HIGH 3=SAFETY
  "source":          "rcan://...",       // Sender RURI
  "target":          "rcan://...",       // Recipient RURI (wildcards allowed)
  "payload":         { ... },           // Type-specific data
  "timestamp":       1741000000.0,       // Unix epoch (seconds, required)
  "ttl":             0,                  // Seconds until expiry (0 = no expiry)
  "reply_to":        null,               // ID of message being replied to
  "scope":           ["control"],        // Required RBAC scopes
  "rcan_version":    "1.6",             // MUST be present; validated first
  "qos":             1,                  // 0=fire-and-forget 1=at-least-once 2=exactly-once
  "sender_type":     "human",           // "human"|"robot"|"cloud_function"|"system"
  "key_id":          "kid-abc123"       // Optional: signing key identifier (GAP-09)
}

Β§3.5 β€” Protocol Version Compatibility v1.5

RCAN version strings follow semantic versioning (MAJOR.MINOR). The rcan_version field MUST be the first field validated on any incoming message, before any other processing.

Compatibility Rules

SituationAction
Receiver MAJOR == Sender MAJOR, Receiver MINOR β‰₯ Sender MINORβœ… Accept β€” standard case
Receiver MAJOR == Sender MAJOR, Receiver MINOR < Sender MINORβœ… Accept β€” ignore unknown fields; apply defaults for missing required fields
Receiver MAJOR β‰  Sender MAJOR❌ Reject with VERSION_INCOMPATIBLE error
rcan_version field missing⚠️ Accept with default "1.0"; log warning at NOTICE level

Field Defaults for Missing v1.5 Fields

When a v1.5 receiver processes a v1.4 message missing new fields:

  • delegation_chain missing β†’ treat as no delegation (flat command)
  • qos missing β†’ default to 0 (fire-and-forget)
  • sender_type missing β†’ default to "human"
  • key_id missing β†’ validate against current (only) key in trust set

Version Negotiation (DISCOVER / STATUS)

DISCOVER and STATUS messages MUST include rcan_version in their payload. Peers record each other's protocol version on first contact and apply the lower version's rules for the session.

// DISCOVER payload (v1.5)
{
  "rcan_version": "1.5",
  "capabilities": ["control", "safety", "training"],
  "p66_conformance_pct": 87.0
}

Β§4 β€” Canonical MessageType Table (v1.5+)

This table is authoritative for v1.5 and v1.6. All SDKs MUST use these integer values. Integer values are listed for interop reference only β€” implementations MUST use named enum constants in code.

TypeNameDescriptionPriorityMin QoS
1 COMMAND Motor, action, or behavior command. Requires control scope. NORMAL/HIGH 0
2 RESPONSE Generic response to a prior message. reply_to links to original. NORMAL 0
3 STATUS Telemetry / state snapshot β€” position, battery, mode, health. NORMAL 0
4 HEARTBEAT Periodic liveness ping. No payload required. LOW 0
5 CONFIG Configuration update. See Β§9.2 CONFIG_UPDATE for required payload schema and authorization rules. NORMAL 1
6 SAFETY STOP / ESTOP / RESUME β€” safety-critical. Bypasses all queues. Cannot be overridden by AI. ESTOP MUST use QoS 2. SAFETY 1 (ESTOP=2)
7 SENSOR_DATA Sensor readings (proximity, IMU, temperature, etc.). NORMAL 0
8 AUDIT Audit trail event β€” commitment record, chain hash, tamper evidence. NORMAL 0
9 DISCOVER mDNS / peer discovery probe. Responders reply with RURI, capabilities, rcan_version, and (v1.6) supported_transports. See Β§19 Transport Negotiation. NORMAL 0
10 TRAINING_DATA Training data submission. MUST carry consent_token for biometric/audio/visual data. v1.6: JSON-only binary in payload is deprecated β€” use media_chunks[] for image/video/audio. See Training Consent, Β§5.4 Multi-Modal Payloads. NORMAL 0
11 TRANSPARENCY EU AI Act Art. 13 disclosure β€” robot announces AI nature, capabilities, operator identity to humans. Required for EU deployment from August 2026. NORMAL 0
12 FEDERATION_SYNC Cross-registry federation synchronization. Β§18 full protocol defined in v1.6. Payload: {source_registry, target_registry, sync_type: "consent"|"revocation"|"key", payload, signature}. NORMAL 1
13 ALERT Asynchronous alert to operator β€” battery critical, geofence breach, etc. HIGH 1
14 TELEOP Real-time teleoperation stream frames. MUST use QoS 0 (fire-and-forget); stale frames are discarded. NORMAL 0
15 CHAT Natural language conversation turn. Requires chat scope. NORMAL 0
16 ERROR Error response. Payload: {"code": "...", "detail": "..."}. See Error Codes. NORMAL 0
17 COMMAND_ACK Acknowledgement for QoS β‰₯ 1. Must be sent within 500ms. Links via reply_to. HIGH 0
18 COMMAND_COMMIT Second phase of QoS 2 exactly-once delivery. Receiver sends after processing. See QoS. HIGH 0
19 ROBOT_REVOCATION Registry broadcast: robot identity revoked. Peers MUST invalidate cached keys and consent. See Revocation. HIGH 1
20 CONSENT_REQUEST Request cross-robot access from target robot's owner. See Consent. NORMAL 1
21 CONSENT_GRANT Owner grants requested cross-robot access. Signed by owner JWT. NORMAL 1
22 CONSENT_DENY Owner denies access request. Signed by owner JWT. NORMAL 0
23 FLEET_COMMAND Broadcast command to a robot group. See Β§15. Fleet ESTOP MUST reach ≀100 robots in 500ms. HIGH 1
24 SUBSCRIBE Subscribe to a telemetry stream (observer scope). See Β§8.8. NORMAL 0
25 UNSUBSCRIBE Cancel an active telemetry subscription. NORMAL 0
26 FAULT_REPORT Structured robot fault report. Safety-affecting faults update Protocol 66 manifest. See Β§16. HIGH 1
27 KEY_ROTATION Announce new signing key and JWKS update. See Key Rotation. NORMAL 1
28 TRAINING_CONSENT_REQUEST Request consent for training data collection from a subject. See Training Consent. NORMAL 1
29 TRAINING_CONSENT_GRANT Subject grants training data consent. Signed by subject JWT. NORMAL 1
30 TRAINING_CONSENT_DENY Subject denies training data consent. NORMAL 0
31 COMMAND_NACK Negative acknowledgement β€” command received but rejected (reason in payload). See QoS. HIGH 0

Priority Levels

ValueNameBehaviour
0LOWProcessed after NORMAL; background telemetry.
1NORMALStandard queue ordering. Default.
2HIGHJumps ahead of NORMAL in dispatch queue.
3SAFETYBypasses all queues (Β§6 Invariant 2). Exclusively for SAFETY (type 6) messages.

Β§9.2 β€” CONFIG_UPDATE Wire Protocol v1.5

Gap: GAP-07 β€” MessageType CONFIG (5) existed with no payload schema. Any token holder could send arbitrary config payloads.

Required Payload Schema

All CONFIG (type 5) messages MUST include these fields:

{
  "config_hash":       "sha256:abc123...",  // SHA-256 of payload_b64 decoded
  "config_version":    "1.2.0",             // Semver of config being applied
  "diff_only":         false,               // true = payload is a diff patch only
  "payload_b64":       "eyJ...",            // Base64-encoded config JSON
  "requires_restart":  false,               // Robot must restart to apply
  "safety_overrides":  false               // true = modifies safety envelope params
}

Authorization Rules

Change TypeMinimum JWT RoleNotes
Standard config fieldscontrolNormal update flow
safety_overrides: falseownerOwner-only config changes
safety_overrides: truecreatorModifies safety envelope; creator role required
Fields: brain.provider, safety.*creatorAlways require creator; safety_overrides ignored
Protocol 66 Invariant: Confidence gate thresholds (confidence_gate.*) are compile-time constants in the robot runtime. They CANNOT be modified via CONFIG_UPDATE regardless of JWT role. Any CONFIG payload containing these fields MUST be rejected.

Validation Sequence

  1. Validate rcan_version (before any other field)
  2. Verify JWT role meets minimum for the config change type
  3. Decode payload_b64
  4. Compute SHA-256 of decoded bytes; compare to config_hash
  5. If hash mismatch β†’ reject with CONFIG_HASH_MISMATCH error; write audit event
  6. Check for forbidden fields (confidence gates); reject if found
  7. Store current config as rollback snapshot (retain for rollback_grace_s, default 300s)
  8. Apply config; write audit event with config_hash and config_version

CONFIG_ROLLBACK

Send a CONFIG message with payload_b64: "ROLLBACK" and no config_hash to restore the previous snapshot. Requires same JWT role as the update being rolled back. Rollback is unavailable after rollback_grace_s has elapsed.

Β§15 β€” Fleet Broadcast / Group Commands v1.5 SHOULD

Gap: GAP-13 β€” No RCAN message type for fleet broadcast; sequential HTTP calls take O(n) time for fleet ESTOP.

FLEET_COMMAND Payload

{
  "group_id":       "floor-3-bots",          // Fleet group identifier
  "rrn_list":       ["RRN-001", "RRN-002"],  // Explicit member list (optional override)
  "command":        "ESTOP",                  // Command name
  "params":         {},                       // Command parameters
  "require_ack_all": true,                    // Wait for ACK from all members
  "fleet_token":    "eyJ..."                  // JWT with fleet claim listing target RRNs
}

Addressing

  • Local network: UDP multicast on 239.255.66.0/24 port 6600
  • Fallback: TCP unicast to each robot in rrn_list
  • Cloud: POST to /api/v1/fleets/{fleet_id}/command which fans out

Fleet ESTOP SLA

Fleet ESTOP MUST reach all members of a ≀100 robot fleet within 500ms on a local network. Implementations that cannot meet this SLA MUST fall back to individual unicast ESTOP messages in parallel.

Fleet JWT Claims

{
  "sub": "human-uuid",
  "aud": "rcan://fleet/floor-3-bots",
  "fleet": ["RRN-000001", "RRN-000002"],  // Robots this JWT authorizes
  "scope": ["control"],
  "exp": 1741003600
}

Β§8.8 β€” Observer Mode v1.5 SHOULD

Gap: GAP-15 β€” No read-only streaming; observers had to poll individual status endpoints.

Observer JWT Scope

The observer scope is read-only and MUST NOT be combined with any control scope (control, safety, training) in the same JWT. Servers MUST reject any write operation from an observer-scoped token, regardless of payload.

SUBSCRIBE / UNSUBSCRIBE

// SUBSCRIBE payload
{
  "stream": "status",          // "status" | "telemetry" | "audit"
  "filter": {"rrn": "RRN-001"} // Optional filter
}

// Server responds with COMMAND_ACK containing subscription ID
// Then delivers matching events as server-sent events or WebSocket frames

Streaming Endpoints

  • GET /api/stream/status β€” robot status updates (SSE)
  • GET /api/stream/telemetry β€” sensor data stream (SSE / WebSocket)
  • GET /api/stream/audit β€” audit events (SSE, requires audit scope)

Observer connections are terminated when the JWT expires. The server MUST close the SSE connection and return HTTP 401.

Β§16 β€” Structured Fault Reporting v1.5 SHOULD

Gap: GAP-20 β€” Only a generic ERROR type existed; no fault taxonomy or safety manifest integration.

FAULT_REPORT Payload

{
  "fault_code":      "SENSOR_PROXIMITY_FAILURE",  // See taxonomy below
  "severity":        "critical",                   // "info"|"warning"|"error"|"critical"
  "subsystem":       "sensor",                     // subsystem prefix
  "description":     "Left IR sensor not responding",
  "affects_safety":  true,                         // Degrades P66 invariant?
  "safe_to_continue": false                        // Robot should halt?
}

Standard Fault Code Taxonomy

PrefixExample Codes
SENSOR_SENSOR_PROXIMITY_FAILURE, SENSOR_IMU_DRIFT, SENSOR_CAMERA_OFFLINE
MOTOR_MOTOR_OVERCURRENT, MOTOR_ENCODER_FAULT, MOTOR_THERMAL
BATTERY_BATTERY_CRITICAL, BATTERY_CELL_FAULT, BATTERY_CHARGING_FAULT
NETWORK_NETWORK_REGISTRY_UNREACHABLE, NETWORK_PEER_TIMEOUT
SAFETY_SAFETY_WATCHDOG_EXPIRED, SAFETY_ESTOP_STUCK
AUTH_AUTH_KEY_EXPIRED, AUTH_TOKEN_REVOKED
CONFIG_CONFIG_HASH_MISMATCH, CONFIG_VERSION_DOWNGRADE

Safety Manifest Integration

Faults with affects_safety: true MUST update the Protocol 66 manifest with an active_faults list. The manifest's safe_to_continue aggregate is false if any active fault has safe_to_continue: false. Operators receive a push notification for severity: "critical" faults.

Migration from v1.4

Breaking change: The MessageType integer table changed between v1.4 spec and v1.5. Any code comparing msg.type == 1 (DISCOVER in old spec) must be updated. Always use named enum constants β€” MessageType.DISCOVER β€” never hardcoded integers.

Old spec integers that changed:

  • DISCOVER: was 1 β†’ now 9
  • STATUS: was 2 β†’ now 3
  • COMMAND: was 3 β†’ now 1
  • TRANSPARENCY: was 18 β†’ now 11
  • HANDOFF: was 19 (reserved) β†’ removed (deferred to v1.6)

rcan-py's internal enum (COMMAND=1, STATUS=3) was already closer to the v1.5 canonical table; only minor renaming is needed there (AUTH→SENSOR_DATA, AUTHORIZE→AUDIT). See docs/v1.5-tracking.md for SDK migration checklist.

Python Usage

from rcan.message import MessageType, RCANMessage, SPEC_VERSION

# Create a COMMAND message (type 1)
msg = RCANMessage.command(
    source="rcan://rcan.dev/acme/arm/v1/unit-001",
    target="rcan://rcan.dev/acme/arm/v1/unit-001",
    payload={"cmd": "move_forward", "speed": 0.5},
)

# SPEC_VERSION is the single source of truth β€” do not hardcode version strings
print(SPEC_VERSION)  # "1.10"

See Also