A JSON Schema repository defining the communication protocol for Modulr agent interfaces. This specification provides type-safe, versioned message schemas for agent control, signalling, and system capabilities.
The Modulr Agent Interface Specification defines a standardized protocol for communication between agents and control systems. All messages follow a common envelope structure with versioned, validated payloads.
- Agent: Commands and responses for agent control (movement, ping/pong, capabilities)
- Signalling: WebRTC signalling messages (offer, answer, ICE candidates, capabilities, ping/pong)
- Common: Shared infrastructure schemas (envelope, error)
This is only required for validating changes to the repo.
- Node.js 20 or higher
- npm
npm installRun the comprehensive validation suite:
npm run validateThis performs:
- Schema Validation: Validates all JSON schemas are well-formed
- Example Validation: Ensures all examples conform to their schemas
- Semantic Versioning: Checks version consistency across files
- Example Coverage: Verifies every message schema has an example
- Index Completeness: Ensures index.json references all schemas
All protocol messages share a common envelope:
{
"type": "category.message",
"version": "0.1",
"id": "unique-message-id",
"timestamp": "2026-01-05T14:35:22Z",
"payload": { },
"meta": { }
}type: Fully-qualified message type (e.g.,agent.movement)version: Protocol version in MAJOR.MINOR formatid: Unique identifier for correlation and tracingtimestamp: RFC 3339 timestamppayload: Message-specific data (optional for some messages)correlationId: Optional field for request/response correlationmeta: Optional metadata not relevant to protocol semantics
Controls agent movement with forward and turn velocities.
{
"type": "agent.movement",
"version": "0.1",
"payload": {
"forward": 1.0,
"turn": -0.3
}
}forward: Forward velocity, range -1.0 to 1.0turn: Turn velocity, range -1.0 to 1.0
Health check messages without payload.
The web client can use this to check that the agent is available by sending a single ping message. The agent will then respond with a pong message, referencing the ID sent in the original ping.
Declares supported protocol versions.
{
"type": "agent.capabilities",
"version": "0.1",
"payload": {
"versions": ["0.0", "0.1"]
}
}Available in: v0.1+
The location messages enable the webapp to manage named locations on the robot. Locations are identified by their unique name. Each location represents a saved position with optional orientation.
Commands (webapp → robot):
Create a new location with a unique name.
{
"type": "agent.location.create",
"version": "0.1",
"payload": {
"name": "Warehouse Loading Dock",
"position": {
"x": 12.5,
"y": 8.3,
"z": 0.0
},
"orientation": {
"yaw": 1.57
},
"metadata": {
"zone": "loading"
}
}
}All fields match the shared location definition:
name: Unique name of the location (required)position: Coordinates (x, y required; z optional)orientation: Optional orientation (yaw, pitch, roll in radians)metadata: Optional metadata object
List all stored locations on the robot.
{
"type": "agent.location.list",
"version": "0.1",
"payload": {}
}Returns all locations with their full data.
Overwrite an existing location by name.
{
"type": "agent.location.update",
"version": "0.1",
"payload": {
"name": "Warehouse Loading Dock",
"position": {
"x": 12.8,
"y": 8.5
}
}
}The location name identifies which location to update. All fields must be provided to completely overwrite the location.
Delete a location by name.
{
"type": "agent.location.delete",
"version": "0.1",
"payload": {
"name": "Old Warehouse Location"
}
}Response (robot → webapp):
Success response for location operations.
{
"type": "agent.location.response",
"version": "0.1",
"correlationId": "original-request-id",
"payload": {
"operation": "list",
"locations": [
{
"name": "Warehouse Loading Dock",
"position": {
"x": 12.5,
"y": 8.3,
"z": 0.0
},
"orientation": {
"yaw": 1.57
}
},
{
"name": "Assembly Station 1",
"position": {
"x": 5.2,
"y": 10.8
}
}
]
}
}operation: The operation performed (create, list, update, delete)locations: Array of all locations (for list operation)
If locations are not specified, the message signifies that the operation completed successfully. If there is an error, the agent.error message is returned instead.
On failure, robot sends agent.error with correlationId matching the request.
{
"type": "agent.error",
"version": "0.1",
"correlationId": "original-request-id",
"payload": {
"code": "LOCATION_NOT_FOUND",
"message": "Location 'Warehouse A' does not exist",
"details": {
"operation": "update",
"requestedName": "Warehouse A"
}
}
}Available in: v0.4+
The navigation messages enable the webapp to command the robot to navigate to a saved location by name. The robot reports status updates via a response message correlated to the original request.
Commands (webapp → robot):
Start navigating to a saved location.
{
"type": "agent.navigation.start",
"version": "0.4",
"payload": {
"name": "Warehouse Loading Dock"
}
}name: Name of the saved location to navigate to (required)
Cancel the current navigation.
{
"type": "agent.navigation.cancel",
"version": "0.4",
"payload": {}
}No payload fields — cancels whatever navigation is currently in progress.
Response (robot → webapp):
Status update for a navigation operation.
{
"type": "agent.navigation.response",
"version": "0.4",
"correlationId": "original-request-id",
"payload": {
"status": "started",
"name": "Warehouse Loading Dock"
}
}status: Navigation status —started,completed,cancelled, orfailedname: Name of the location being navigated to (required)message: Optional human-readable detail (e.g. failure reason)
If the robot is not currently navigating when a cancel is received, it returns agent.error with code NAVIGATION_NOT_ACTIVE.
The correct protocol for signalling is as follows:
- Robot sends
signalling.registermessage to the signalling server to register its availability. - Server registers the robot and maintains its availability status.
- Server may send a
signalling.pki_challengeto verify the robot's identity. The robot must respond with asignalling.pki_responsecontaining the signed challenge. If verification succeeds, the server sendssignalling.pki_verified. Registration is not considered complete until verification is confirmed. - Client wants to connect to a robot. Client sends
signalling.offerwith a generated session ID to the signalling server. - Server validates the client's permissions and forwards the offer to the robot.
- Robot receives the offer and sends
signalling.answerback through the server to the client. - Both sides exchange
signalling.ice_candidatemessages through the server until a valid connection can be achieved. - Both sides send
signalling.connectedmessages to the server, then proceed to exchange control and media messages directly. - Once the connection drops, both sides send
signalling.disconnectedmessages to the server.
Sent by the robot to register its availability with the signalling server.
{
"type": "signalling.register",
"version": "0.1",
"payload": {
"agentId": "robot-001",
"capabilities": {
"videoCodecs": ["H264", "VP8"],
"audioCodecs": ["opus"]
},
"metadata": {
"model": "MR-5000",
"firmwareVersion": "2.1.0"
}
}
}agentId: Unique identifier for the robotcapabilities: Optional capabilities for session negotiationmetadata: Optional metadata about the robot (model, firmware version, etc.)
WebRTC SDP offer for connection establishment.
WebRTC SDP answer in response to offer.
ICE candidate for WebRTC connection.
Indicates that a WebRTC connection has been successfully established.
{
"type": "signalling.connected",
"version": "0.1",
"payload": {
"connectionId": "conn-9876543210",
"iceConnectionState": "connected",
"dataChannelState": "open"
}
}connectionId: Unique identifier for this connection sessioniceConnectionState: The ICE connection state (connected or completed)dataChannelState: Optional state of the data channel
Indicates that a WebRTC connection has been lost or closed.
{
"type": "signalling.disconnected",
"version": "0.1",
"payload": {
"connectionId": "conn-9876543210",
"reason": "timeout",
"iceConnectionState": "disconnected",
"details": {
"duration": "900s"
}
}
}connectionId: Unique identifier for the connection that was lostreason: Reason for disconnection (closed, failed, timeout)iceConnectionState: Optional ICE connection state at time of disconnectiondetails: Optional additional details about the disconnection
Declares supported signalling protocol versions.
Available in: v0.2+
Health check messages for the signalling server connection. Either side can send a signalling.ping to verify the signalling server is reachable. The server responds with a signalling.pong, referencing the ID of the original ping via correlationId.
{
"type": "signalling.ping",
"version": "0.2",
"id": "b5c7d9e1-f3a5-6b7c-8d9e-0f1a2b3c4d5e",
"timestamp": "2026-01-06T12:00:00Z"
}Available in: v0.3+
After a robot sends signalling.register, the server may issue a signalling.pki_challenge to verify the robot's identity. The robot must sign the challenge string and reply with a signalling.pki_response. If verification succeeds, the server sends signalling.pki_verified to confirm the robot is registered. Registration is not considered complete until pki_verified is received.
{
"type": "signalling.pki_challenge",
"version": "0.3",
"id": "c6d8e0f2-a4b6-7c8d-9e0f-1a2b3c4d5e6f",
"timestamp": "2026-01-06T12:00:01Z",
"payload": {
"challenge": "3q2+7w==base64encodedchallengestring=="
}
}{
"type": "signalling.pong",
"version": "0.2",
"id": "b5c7d9e1-f3a5-6b7c-8d9e-0f1a2b3c4d5f",
"correlationId": "b5c7d9e1-f3a5-6b7c-8d9e-0f1a2b3c4d5e",
"timestamp": "2026-01-06T12:00:01Z"
}
"type": "signalling.pki_response",
"version": "0.3",
"id": "c6d8e0f2-a4b6-7c8d-9e0f-1a2b3c4d5e70",
"correlationId": "c6d8e0f2-a4b6-7c8d-9e0f-1a2b3c4d5e6f",
"timestamp": "2026-01-06T12:00:02Z",
"payload": {
"signature": "base64encodedsignatureofchallenge=="
}
}{
"type": "signalling.pki_verified",
"version": "0.3",
"id": "c6d8e0f2-a4b6-7c8d-9e0f-1a2b3c4d5e71",
"correlationId": "c6d8e0f2-a4b6-7c8d-9e0f-1a2b3c4d5e70",
"timestamp": "2026-01-06T12:00:03Z",
"payload": {
"agentId": "robot-001"
}
}challenge: The challenge string the robot must signsignature: The robot's cryptographic signature of the challengeagentId: The agent ID confirmed as verified
Errors are category-specific: agent.error for agent errors and signalling.error for signalling errors.
Agent-related errors with agent-specific error codes.
{
"type": "agent.error",
"version": "0.1",
"payload": {
"code": "MOVEMENT_FAILED",
"message": "Agent movement command failed: obstacle detected",
"details": {
"obstacleType": "wall",
"sensorReading": 0.15
}
}
}Agent Error Codes:
| Code | Description |
|---|---|
INVALID_MESSAGE |
Message structure is malformed |
UNSUPPORTED_VERSION |
Protocol version not supported |
VALIDATION_FAILED |
Message failed schema validation |
INVALID_PAYLOAD |
Payload doesn't match message schema |
UNSUPPORTED_MESSAGE_TYPE |
Unknown message type |
MOVEMENT_FAILED |
Movement command failed (obstacle, limit, hardware) |
LOCATION_NOT_FOUND |
Requested location does not exist |
LOCATION_ALREADY_EXISTS |
Location with that name already exists |
LOCATION_NAME_INVALID |
Location name contains invalid characters |
NAVIGATION_ALREADY_ACTIVE |
Navigation already in progress |
NAVIGATION_NOT_ACTIVE |
No navigation in progress to cancel |
AGENT_UNAVAILABLE |
Agent is offline or unreachable |
CAPABILITY_MISMATCH |
Incompatible protocol versions |
INTERNAL_ERROR |
Unexpected agent error |
Signalling and WebRTC connection errors.
{
"type": "signalling.error",
"version": "0.1",
"payload": {
"code": "CONNECTION_FAILED",
"message": "WebRTC connection failed to establish",
"details": {
"iceState": "failed"
}
}
}Signalling Error Codes:
| Code | Description |
|---|---|
INVALID_MESSAGE |
Message structure is malformed |
UNSUPPORTED_VERSION |
Protocol version not supported |
VALIDATION_FAILED |
Message failed schema validation |
INVALID_PAYLOAD |
Payload doesn't match message schema |
UNSUPPORTED_MESSAGE_TYPE |
Unknown message type |
CONNECTION_FAILED |
WebRTC connection failed |
UNAUTHORIZED |
Authentication required |
FORBIDDEN |
Insufficient permissions |
TIMEOUT |
Operation timed out |
CAPABILITY_MISMATCH |
Incompatible protocol versions |
ICE_FAILED |
ICE candidate gathering or connectivity failed |
SDP_INVALID |
SDP offer/answer is invalid |
INTERNAL_ERROR |
Unexpected signalling error |
The details field provides additional context specific to each error.
The protocol follows semantic versioning (MAJOR.MINOR):
- MAJOR version: Incompatible API changes
- MINOR version: Backward-compatible functionality additions
- Schema
$idversion must match file path version - Schema descriptions should reference the correct version
- All schemas in a version directory must use that version
See CONTRIBUTING.md for the contribution workflow and guidelines.
MIT License - see LICENSE file for details.