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.
  • TRAINING_DATA (type 36 in the current table): JSON-only binary in payload deprecated. Image/video/audio MUST use media_chunks[]. See §5.4.
  • 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":    "3.2",             // 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": "3.2",
  "capabilities": ["control", "safety", "training"],
  "p66_conformance_pct": 87.0
}

Canonical MessageType table

These are the integer values rcan-py and rcan-ts use today (types 1 to 45). The normative list is in the spec; if this page and the spec disagree, the spec wins. Values are listed for interop reference: use the named enum constants in code, not the integers.

TypeNameSectionDirectionDescription
1COMMAND§3operator → robotMotor, action, or behavior command. Requires control scope.
2RESPONSE§3robot → operatorGeneric response to a prior message. reply_to links to original.
3STATUS§3robot → operatorTelemetry / state snapshot — position, battery, mode, health.
4HEARTBEAT§3bidirectionalPeriodic liveness ping. No payload required.
5CONFIG§8operator → robotConfiguration update. See §9.2 CONFIG_UPDATE for required payload schema and authorization rules.
6SAFETY§6bidirectionalSTOP / ESTOP / RESUME — safety-critical. Bypasses all queues. Cannot be overridden by AI. ESTOP MUST use QoS 2.
7AUTH§5bidirectionalAuthentication: session token and expiry. See Authentication.
8ERROR§3bidirectionalError response. Payload: {"code": "...", "detail": "..."}. See Error Codes.
9DISCOVER§4broadcastmDNS / peer discovery probe. Responders reply with RURI, capabilities, rcan_version, and (v1.6) supported_transports. See §19 Transport Negotiation.
10PENDING_AUTH§5robot → operatorAn AI-proposed command waiting for human authorization (§16.4 HiTL gate).
11INVOKE§19.2operator → robotSkill or behavior invocation request.
12INVOKE_RESULT§19.3robot → operatorResult of an INVOKE.
13INVOKE_CANCEL§19.4operator → robotBest-effort cancellation of an in-flight INVOKE.
14REGISTRY_REGISTER§21.4robot → registryRegister a robot with a registry.
15REGISTRY_RESOLVE§21.5any → registryResolve an RRN to its registry record.
16TRANSPARENCY§16robot → audit logEU AI Act Art. 13 disclosure — robot announces AI nature, capabilities, operator identity to humans. Required for EU deployment from August 2026.
17COMMAND_ACK§3robot → operatorAcknowledgement for QoS ≥ 1. Must be sent within 500ms. Links via reply_to.
18COMMAND_NACK§3robot → operatorNegative acknowledgement — command received but rejected (reason in payload). See QoS.
19ROBOT_REVOCATION§13broadcastRegistry broadcast: robot identity revoked. Peers MUST invalidate cached keys and consent. See Revocation.
20CONSENT_REQUEST§11.2robot → robotRequest cross-robot access from target robot's owner. See Consent.
21CONSENT_GRANT§11.2owner → requesterOwner grants requested cross-robot access. Signed by owner JWT.
22CONSENT_DENY§11.2owner → requesterOwner denies access request. Signed by owner JWT.
23FLEET_COMMAND§15operator → fleetBroadcast command to a robot group. See §15. Fleet ESTOP MUST reach ≤100 robots in 500ms.
24SUBSCRIBE§20any → robotSubscribe to a telemetry stream (observer scope). See Observer Mode.
25UNSUBSCRIBE§20any → robotCancel an active telemetry subscription.
26FAULT_REPORT§16robot → operatorStructured robot fault report. Safety-affecting faults update Protocol 66 manifest. See §16.
27KEY_ROTATION§8.6broadcastAnnounce new signing key and JWKS update. See Key Rotation.
28COMMAND_COMMIT§5.3bidirectionalSecond phase of QoS 2 exactly-once delivery. Receiver sends after processing. See QoS.
29SENSOR_DATA§20robot → subscriberSensor readings (proximity, IMU, temperature, etc.).
30TRAINING_CONSENT_REQUEST§17operator → ownerRequest consent for training data collection from a subject. See Training Consent.
31TRAINING_CONSENT_GRANT§17owner → requesterSubject grants training data consent. Signed by subject JWT.
32TRAINING_CONSENT_DENY§17owner → requesterSubject denies training data consent.
33CONTRIBUTE_REQUEST§creditscoordinator → robotIdle-compute work unit offered to a robot.
34CONTRIBUTE_RESULT§creditsrobot → coordinatorResult of a contributed work unit.
35CONTRIBUTE_CANCEL§creditsrobot → coordinatorRobot withdraws from a work unit.
36TRAINING_DATA§3robot → data pipelineTraining 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.
37COMPETITION_ENTER§competitionsrobot → fleetCompetition entry.
38COMPETITION_SCORE§competitionsrobot → fleetCompetition score.
39SEASON_STANDING§competitionscloud → robotSeason standings.
40PERSONAL_RESEARCH_RESULT§competitionsrobot → local gatewayPersonal research result.
41AUTHORITY_ACCESS§authorityauthority → robotAudit request from an authority (EU AI Act Art. 16(j)).
42AUTHORITY_RESPONSE§authorityrobot → authorityAudit data returned to the authority.
43FIRMWARE_ATTESTATION§firmwarerobot → registrySigned firmware manifest. See Firmware Attestation.
44SBOM_UPDATE§sbomrobot → registryUpdated CycloneDX SBOM. See SBOM.
45AUTHORIZE§16.4authorising principal → robotApproves or denies a PENDING_AUTH. Owner role or higher.

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
}

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 16
  • HANDOFF: was 19 (reserved) → removed (deferred to v1.6)

AUTHORIZE, which approves or denies a PENDING_AUTH (§16.4), is type 45. Older schemas carried 9, which is DISCOVER.

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