Protocol / Message Reference
Shared message types for controller-agent WebSocket communication in the Nimbus cluster protocol.
The nimbus-protocol module defines the shared message types exchanged between the Nimbus controller and agent nodes over WebSocket. It is a dependency of both nimbus-core and nimbus-agent.
Overview
All cluster communication uses JSON-serialized messages with a type discriminator field. The protocol is defined in two files:
dev.nimbuspowered.nimbus.protocol.ClusterMessage-- Sealed class containing all message typesdev.nimbuspowered.nimbus.protocol.ServiceHeartbeat/RemoteFileEntry-- Data classes used inside messagesdev.nimbuspowered.nimbus.protocol.StateManifest/StateFileEntry-- State-sync manifest types used by the controller's state-sync REST API (not part of the WebSocket protocol; served over the TLS cluster port)dev.nimbuspowered.nimbus.protocol.clusterJson-- Pre-configuredkotlinx.serialization.json.Jsoninstance
val clusterJson = Json {
ignoreUnknownKeys = true
encodeDefaults = true
classDiscriminator = "type"
}Message types
Controller to Agent
| Type | Class | Purpose |
|---|---|---|
AUTH_RESPONSE | AuthResponse | Accept/reject agent authentication |
START_SERVICE | StartService | Launch a new service on the agent |
STOP_SERVICE | StopService | Stop a running service |
SEND_COMMAND | SendCommand | Forward a console command to a service |
HEARTBEAT_REQUEST | HeartbeatRequest | Request system metrics from agent |
TEMPLATE_INFO | TemplateInfo | Template metadata (name, hash, download URL, size) |
SHUTDOWN_AGENT | ShutdownAgent | Gracefully shut down the agent |
DISCARD_SYNC_WORKDIR | DiscardSyncWorkdir | Tell the agent to delete the local working directory for a sync-enabled service (fields: serviceName) |
FILE_LIST_REQUEST | FileListRequest | Request a directory listing from the agent for remote file management |
FILE_READ_REQUEST | FileReadRequest | Request the contents of a file on the agent |
FILE_WRITE_REQUEST | FileWriteRequest | Write or overwrite a file on the agent |
FILE_DELETE_REQUEST | FileDeleteRequest | Delete a file or directory on the agent |
Agent to Controller (file management responses)
| Type | Class | Purpose |
|---|---|---|
FILE_LIST_RESPONSE | FileListResponse | Returns directory listing for FileListRequest |
FILE_READ_RESPONSE | FileReadResponse | Returns file contents for FileReadRequest |
FILE_WRITE_RESPONSE | FileWriteResponse | Acknowledges or reports error for FileWriteRequest |
FILE_DELETE_RESPONSE | FileDeleteResponse | Acknowledges or reports error for FileDeleteRequest |
Agent to Controller
| Type | Class | Purpose |
|---|---|---|
AUTH_REQUEST | AuthRequest | Authenticate with token, node info, and resources |
HEARTBEAT_RESPONSE | HeartbeatResponse | CPU, memory, and per-service status |
SERVICE_STATE_CHANGED | ServiceStateChanged | Report service state transition |
SERVICE_STDOUT | ServiceStdout | Forward a stdout line from a service |
SERVICE_PLAYER_COUNT | ServicePlayerCount | Report player count for a service |
COMMAND_RESULT | CommandResult | Result of a forwarded command |
TEMPLATE_REQUEST | TemplateRequest | Request a template download |
LOG_MESSAGE | LogMessage | Forward a log message to the controller |
Key message details
StartService
The most complex message -- contains everything needed to launch a service: serviceName, groupName, port, templateName, templateHash, software, version, memory, jvmArgs, jarName, forwardingMode, forwardingSecret, isStatic, isModded, apiUrl, apiToken, nimbusProperties, javaVersion, bedrockPort, bedrockEnabled, and more. See ClusterMessage.kt for the full field list.
The apiToken sent to game servers is a restricted service token (HMAC-derived from the admin token), limiting API access to service-level endpoints only. Velocity proxy services receive the full admin token for bridge plugin access.
AuthRequest
Sent by the agent after connecting. Fields: token, nodeName, maxMemory, maxServices, currentServices, agentVersion, os, arch, hostname, osVersion, cpuModel, availableProcessors, systemMemoryTotalMb, javaVersion, javaVendor, publicHost, runningServices, protocolVersion.
publicHost is the publicly reachable IP or hostname reported by the agent for player routing. runningServices is a list of services the agent was already managing before the (re-)connection, used during the controller's reconciliation window to avoid duplicate restarts.
Protocol version
Both AuthRequest and AuthResponse carry an integer protocolVersion. The current value is exposed as the compile-time constant ClusterMessage.CURRENT_PROTOCOL_VERSION (in 0.13.0: 1).
During the handshake, the controller compares the agent's reported version to its own before checking the auth token. On mismatch, the controller replies with AuthResponse(accepted = false, reason = "protocol version mismatch: agent=X, controller=Y", protocolVersion = <controller>) and closes the WebSocket with VIOLATED_POLICY. The ordering is intentional: when both versions and tokens disagree, the operator sees the real cause (incompatible builds) instead of a misleading token error.
The agent recognises a version-mismatch reject via a local compare of the advertised versions, logs a single ERROR, and exits the reconnect loop immediately -- no exponential backoff, no silent retry churn. Incompatible deployments fail loud and fast; the fix is to bring both controller and agent to matching Nimbus versions and restart.
Both fields default to 1 on the wire. Combined with clusterJson.ignoreUnknownKeys = true, this preserves the rolling-upgrade path between 0.12.x and 0.13.0 nodes that don't yet emit the field.
When to bump CURRENT_PROTOCOL_VERSION. Only when an existing ClusterMessage field changes semantics, is renamed, or is removed. Adding a new @SerialName'd message type, or adding a new optional field to an existing type with a sensible default, does not require a bump -- older peers ignore unknown keys and fall through to the default.
ServiceHeartbeat
Included in HeartbeatResponse.services -- per-service status with serviceName, groupName, state, port, pid, playerCount, and optional customState.
ServiceHandle interface
The ServiceHandle interface (dev.nimbuspowered.nimbus.service.ServiceHandle in nimbus-core) abstracts over local and remote processes with methods for stdoutLines, isAlive(), pid(), sendCommand(), waitForReady(), stopGracefully(), awaitExit(), and destroy(). For remote services, the controller translates these calls into ClusterMessage instances sent over the WebSocket.
Message flow examples
Starting a service on a remote node
Controller Agent
│ │
├── StartService ───────────────→ │
│ ├── ensureTemplate() (download if hash mismatch)
│ ├── copyTemplate() + patchConfigs()
│ ├── launch JVM process
│ ←── ServiceStateChanged ──────┤ (state=STARTING)
│ ├── monitor stdout for ready pattern
│ ←── ServiceStdout ────────────┤ (forwarded log lines)
│ ←── ServiceStateChanged ──────┤ (state=READY)
│ │Heartbeat cycle
Controller Agent
│ │
├── HeartbeatRequest ───────────→ │
│ ←── HeartbeatResponse ────────┤ (cpu, memory, service list)
│ │Graceful shutdown
Controller Agent
│ │
├── ShutdownAgent ──────────────→ │
│ ├── stopAll() (send "stop" to each service)
│ ├── close connections
│ └── exitNext steps
- Architecture -- System overview and module structure
- Agent Node -- Agent runtime details
- WebSocket Reference -- Client-facing event stream protocol