Craft Agents WebSocket Protocol: Custom JSON-RPC Communication Between Desktop and Server

Craft Agents uses a custom JSON-based RPC protocol layered on top of standard WebSocket connections, implementing typed request-response pairs, event streaming, and automatic reconnection with sequence-numbered reliability.

Craft Agents OSS (lukilabs/craft-agents-oss) is an open-source AI agent framework that relies on real-time bidirectional communication between its desktop Electron client and headless server. Rather than raw socket messaging, the WebSocket communication protocol employed by Craft Agents is a structured, versioned RPC layer that handles authentication, heartbeat management, and guaranteed message delivery.

Protocol Architecture and Message Envelope

Transport Layer and RPC Abstraction

The foundation uses standard WebSocket connections (ws:// or wss://) between the desktop Electron process and the headless server. The desktop leverages the Node.js ws library in the main process or native WebSocket in the renderer, while the server accepts upgrades over HTTP.

However, the application does not exchange raw text or binary blobs directly. Instead, it implements a custom JSON-RPC protocol defined in the shared @craft-agent/shared/protocol package. This architecture separates concerns into distinct layers:

  1. Transport: Raw WebSocket connection managed by standard libraries.
  2. Envelope: JSON-encoded MessageEnvelope structures that wrap every payload with metadata.
  3. RPC Logic: Typed request-response routing, event streaming, and error handling implemented by WsRpcClient and WsRpcServer.

MessageEnvelope Structure

Every frame exchanged between client and server follows the MessageEnvelope interface defined in packages/shared/src/protocol/types.ts. This envelope standardizes communication with the following fields:

  • type: The message category, which must be one of handshake, handshake_ack, request, response, event, error, or sequence_ack.
  • id: A correlation UUID that pairs requests with responses.
  • protocolVersion: A version string (currently "1.0" as defined by PROTOCOL_VERSION) ensuring compatibility during handshake.
  • channel: Optional routing identifier for namespaced RPC handlers.
  • args, result, error: Payload fields carrying method parameters, return values, or error details.
  • seq and lastSeq: Sequence numbers enabling reliable delivery and replay.

Desktop Client Implementation

The Electron desktop application implements the protocol through the WsRpcClient class located in apps/electron/src/transport/client.ts. This client manages the full lifecycle of the WebSocket connection, from initial handshake through reconnection and graceful shutdown.

Connection Establishment and Handshake

Connection establishment follows this sequence:

  1. Mode Inference: The client analyzes the URL to determine if it connects to a local development endpoint (ws://127.0.0.1 or localhost) or a remote server, setting the mode to 'local' or 'remote' accordingly via the inferMode utility.
  2. Handshake: Upon opening the socket, the client sends a handshake message containing protocolVersion, workspaceId, and optionally a JWT token or session cookie for authentication.
  3. Event Loop: Once the server responds with handshake_ack, the client enters the main loop, processing incoming event messages and dispatching request messages for RPC calls.

The client also implements automatic reconnection with exponential backoff, buffering outgoing requests during disconnection and replaying them once the WebSocket resumes.

import { WsRpcClient } from '@craft-agent/electron/transport/client';

// URL is taken from CRAFT_SERVER_URL (e.g. ws://127.0.0.1:9100)
const client = new WsRpcClient('ws://127.0.0.1:9100', {
  workspaceId: 'my-workspace',
  token: process.env.CRAFT_SERVER_TOKEN,   // remote auth token
  autoReconnect: true,
});

// Wait for handshake to complete
await client.invoke('ping');   // simple test RPC

Server Implementation

On the server side, the WsRpcServer class in packages/server-core/src/transport/server.ts handles incoming WebSocket upgrades and manages client sessions. This headless server supports both local development and remote production deployments with unified protocol handling.

RPC Routing and Event Buffering

Key responsibilities include:

  • Upgrade Handling: The server listens for HTTP upgrade requests and establishes the raw WebSocket connection.
  • Handshake Validation: Upon receiving the handshake message, the server verifies the protocolVersion matches PROTOCOL_VERSION and authenticates the client using the provided token or session cookie.
  • RPC Dispatching: Validated connections are mapped to a channel namespace. The server routes request messages to registered handlers and returns response envelopes with matching id correlation.
  • Event Broadcasting: The server can push event messages to specific clients or broadcast to all clients in a workspace using the push method.

The server maintains buffers for disconnected clients (EVENT_BUFFER_MAX_SIZE of 500 events and DISCONNECTED_CLIENT_TTL_MS of 60 seconds) to enable event replay upon reconnection.

import { WsRpcServer } from '@craft-agent/server-core/transport/server';

// Optional token validator for remote clients
const server = new WsRpcServer({
  port: 9100,
  requireAuth: true,
  validateToken: async (t) => t === process.env.CRAFT_SERVER_TOKEN,
  // Serve the embedded Web UI on the same port (optional)
  httpHandler: (req, res) => webUiHandler(req, res),
});

server.handle('ping', () => 'pong');
await server.listen();   // starts listening on ws(s)://0.0.0.0:9100

Reliability and Flow Control Mechanisms

The WebSocket protocol implements several mechanisms to ensure reliable delivery over the inherently unreliable transport.

Sequence Numbers and Heartbeats

Sequence Numbers and Acknowledgements: Every message carries a monotonic seq number. Clients periodically send sequence_ack messages (controlled by SEQUENCE_ACK_INTERVAL_MS of 5 seconds) confirming the last sequence received. If a client disconnects and reconnects, it sends lastSeq in the new handshake, allowing the server to replay missed events from its buffer.

Heartbeat Management: The server sends periodic heartbeats every HEARTBEAT_INTERVAL_MS (30 seconds). If a client misses HEARTBEAT_MAX_MISSED (2) consecutive heartbeats, the connection is terminated and the client triggers reconnection logic.

Request Timeouts: RPC calls enforce a REQUEST_TIMEOUT_MS (30 seconds) deadline. If a response is not received within this window, the client rejects the promise with a timeout error.

Authentication and Security

The protocol supports multiple authentication strategies during the initial handshake phase.

JWT Bearer Tokens: Remote clients provide a token field in the handshake message containing a JWT. The server validates this against the CRAFT_SERVER_TOKEN environment variable or a custom validateToken function.

Session Cookies: For the embedded Web UI served by the same server, authentication occurs via standard HTTP session cookies transmitted during the WebSocket upgrade request. The server extracts and validates these cookies during handshake processing.

Local Mode Bypass: When the client detects a local endpoint (ws://127.0.0.1 or localhost), it sets mode to 'local', allowing the server to optionally relax authentication requirements for development workflows.

Summary

  • Craft Agents implements a custom JSON-RPC protocol over standard WebSocket connections to enable real-time desktop-to-server communication.
  • The MessageEnvelope structure in packages/shared/src/protocol/types.ts standardizes all messages with types like handshake, request, response, and event.
  • Desktop clients use WsRpcClient (apps/electron/src/transport/client.ts) to manage connections, authenticate, and handle automatic reconnection with exponential backoff.
  • Server processes use WsRpcServer (packages/server-core/src/transport/server.ts) to route RPC calls, buffer events for disconnected clients, and enforce protocol version compatibility.
  • Reliability features include sequence numbering (seq, lastSeq), sequence_ack acknowledgements, server heartbeats (30-second intervals), and 30-second request timeouts.
  • Authentication supports JWT tokens and session cookies, with relaxed rules for local development endpoints.

Frequently Asked Questions

What underlying transport does Craft Agents use for desktop-server communication?

Craft Agents uses standard WebSocket connections (ws:// or wss://) as the underlying transport layer. The desktop Electron process connects to the headless server using the Node.js ws library or native WebSocket, over which the custom JSON-RPC protocol is layered.

How does the protocol handle network interruptions and reconnections?

The protocol implements automatic reconnection with exponential backoff on the client side. It uses sequence numbers (seq, lastSeq) and sequence_ack messages to track delivered messages. The server maintains an event buffer (EVENT_BUFFER_MAX_SIZE of 500 events) for disconnected clients, replaying missed events upon reconnection based on the lastSeq provided in the new handshake.

What authentication methods are supported during the WebSocket handshake?

During the initial handshake message, clients can provide a JWT bearer token in the token field for remote authentication. Alternatively, when connecting via the embedded Web UI, authentication occurs through HTTP session cookies transmitted during the WebSocket upgrade. Local development connections (ws://127.0.0.1) can operate with relaxed authentication requirements.

Where is the protocol definition located in the source code?

The core protocol definitions, including the MessageEnvelope interface, message types, and protocol constants like PROTOCOL_VERSION, are located in packages/shared/src/protocol/types.ts. The desktop client implementation resides in apps/electron/src/transport/client.ts, while the server implementation is found in packages/server-core/src/transport/server.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →