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

> Discover the custom JSON-RPC protocol powering WebSocket communication in Craft Agents. Learn about typed requests, event streaming, and reliable connections between desktop and server.

- Repository: [Craft Docs/craft-agents-oss](https://github.com/lukilabs/craft-agents-oss)
- Tags: api-reference
- Published: 2026-04-18

---

**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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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.

```typescript
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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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.

```typescript
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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/protocol/types.ts). The desktop client implementation resides in [`apps/electron/src/transport/client.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/apps/electron/src/transport/client.ts), while the server implementation is found in [`packages/server-core/src/transport/server.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/server-core/src/transport/server.ts).