Craft Agents Headless Server and Thin‑Client Desktop App Architecture

Craft Agents OSS implements a split‑architecture where a Bun‑based headless server handles all AI workloads and session persistence, while an Electron thin client serves purely as a remote UI renderer connecting via authenticated WebSocket RPC.

The lukilabs/craft-agents-oss repository separates compute from presentation, enabling users to run a single headless server for multiple desktop clients or operate fully local embedded mode. This article examines the architecture of the headless server and thin‑client desktop app, detailing how they communicate, authenticate, and scale from personal workflows to fleet deployments.


Architectural Overview

Craft Agents operates in two complementary modes that share the same RPC interface but distribute components differently.

Mode Runtime Location Core Responsibilities
Headless server Bun‑based Node‑like process (no UI) Session storage, agent execution, WebSocket RPC endpoint, LLM model fetchers, source connectors, credential store, background services.
Thin‑client desktop Electron GUI application Connects to remote headless server via WebSocket, forwards UI actions, receives agent responses, renders interface. When no remote URL is supplied, boots an embedded server (full‑client mode).

The processes communicate exclusively through a secure WebSocket RPC channel authenticated with a bearer token (CRAFT_SERVER_TOKEN). TLS encryption (wss://) is optionally enforced via certificate configuration.

High‑level data flow


┌───────────────────────┐      WebSocket (ws/wss) + token      ┌───────────────────────┐
│  Thin‑client Electron │ ─────────────────────────────────────►│   Headless server     │
│  (renderer + preload) │  ←─────────────────────────────────────│  (Bun + @craft‑agent│
│  UI only, no session  │   RPC calls (RPC_CHANNELS)            │   server‑core)       │
└───────────────────────┘                                      └───────────────────────┘

The Electron preload script (apps/electron/src/preload/*.ts) exposes an IPC bridge (__transport:status, __get-ws-port, etc.) that the renderer uses to obtain the WS endpoint and token. The headless server (packages/server/src/index.ts) boots the shared @craft-agent/server-core stack, creates a SessionManager, registers RPC handlers, and starts listening on CRAFT_RPC_HOST:CRAFT_RPC_PORT.


Headless Server Architecture

All business logic (sessions, sources, model fetchers, credential handling) lives in @craft-agent/server-core and shared packages under packages/*.

Entry point and bootstrap

The server entry point at packages/server/src/index.ts parses environment variables and initializes the runtime.


# Generate a token (optional)

CRAFT_SERVER_TOKEN=$(openssl rand -hex 32)

# Launch the server (bind to all interfaces, TLS optional)

CRAFT_RPC_HOST=0.0.0.0 CRAFT_RPC_PORT=9100 \
  CRAFT_SERVER_TOKEN=$CRAFT_SERVER_TOKEN \
  bun run packages/server/src/index.ts

Startup emits the connection details:


CRAFT_SERVER_URL=ws://0.0.0.0:9100
CRAFT_SERVER_TOKEN=...

The bootstrapServer function from @craft-agent/server-core/bootstrap assembles subsystems:

  • Creates a WsRpcServer that validates incoming tokens (validateSessionCookie when the WebUI is enabled).
  • Instantiates SessionManager (persists sessions to ~/.craft-agent/workspaces/.../sessions).
  • Calls registerCoreRpcHandlers for core RPC like sessions:*, sources:*, settings:*.
  • Provides platform abstraction via PlatformServices implemented as a headless platform (packages/server-core/runtime/headless.tscreateHeadlessPlatform).

Source: packages/server/src/index.ts and packages/server-core/src/bootstrap.ts

Core server components

Session management

The SessionManager at packages/server-core/src/sessions/SessionManager.ts handles creation, persistence, branching, export/import, and event streaming.

  • Stores data in JSONL files under the workspace directory (~/.craft-agent/.../sessions).
  • Emits RPC events (SESSION_STARTED, SESSION_ENDED) pushed to connected thin clients.

Model fetchers and LLM integration

Model fetchers abstract LLM provider specifics (Anthropic, Pi, OpenRouter, Bedrock, etc.).

Each fetcher in packages/server-core/src/model-fetchers/ (e.g., anthropic.ts) implements:

  • getCredentials – retrieves API key or OAuth token from the encrypted credential store.
  • Model client instantiation.

The initModelRefreshService refreshes model lists on a timer.

Sources and services

Security and authentication

The headless server enforces security at the transport layer:

  • Token authenticationCRAFT_SERVER_TOKEN is required for every RPC call via validateSessionCookie in the WebSocket handshake.
  • TLS encryption – optional via CRAFT_RPC_TLS_CERT and CRAFT_RPC_TLS_KEY; enforced when listening on non‑loopback addresses.
  • Credential encryption – AES‑256‑GCM encrypted store at packages/shared/credentials/credential-manager.ts.

Source: packages/server-core/src/transport/ws-rpc.ts


Thin‑Client Desktop App Architecture

The Electron application acts as a remote viewport into the headless server, delegating all compute to the server process.

Electron main process

The entry point at apps/electron/src/main/index.ts orchestrates the client lifecycle:

  • Loads user shell environment (loadShellEnv), configures Sentry, i18n, logging, and CLI‑tool bundling.
  • Detects client‑only mode when CRAFT_SERVER_URL is set. In this mode it skips server bootstrapping (no SessionManager, no model fetchers) and only creates UI windows.
  • When not in client‑only mode, boots an embedded server (same code path as the headless server) and then creates UI windows.
  • Registers the WebUI HTTP handler (optional built‑in settings UI) and the custom protocol craftagents:// for deep linking.
  • Sets up IPC bridges:
    • __get-ws-port / __get-ws-token → provide the renderer with connection details.
    • __transport:status → surface transport diagnostics.
    • __dialog:* → expose native dialogs to the renderer.

Source: apps/electron/src/main/index.ts

Platform abstraction layer

The file apps/electron/src/main/platform.ts implements PlatformServices for the Electron environment:

  • Provides native theme, shell, logging, error capture, and badge updates.
  • In thin‑client mode, the same PlatformServices instance is passed to the remote server via the RPC connection, allowing the server to call back into the UI (e.g., updating the dock badge).

Source: apps/electron/src/main/platform.ts

Preload bridge and renderer isolation

The preload scripts at apps/electron/src/preload/*.ts expose a context bridge (window.craftBridge) that the React renderer consumes:

  • Provides synchronous helpers (__get-web-contents-id, __get-workspace-id).
  • Provides asynchronous RPC calls (invokeRpcChannel).

Source: apps/electron/src/preload/bootstrap.ts

Connection flow and RPC communication

The thin‑client connection establishes a secure pipeline to the headless server:

  1. Startup – Electron reads CRAFT_SERVER_URL and CRAFT_SERVER_TOKEN from the environment.
  2. Main process supplies these values to the renderer via IPC (__get-ws-port, __get-ws-token).
  3. Renderer creates a WsRpcClient (from @craft-agent/server-core/transport) that:
    • Connects to the remote server.
    • Authenticates with the token.
    • Subscribes to RPC channels (sessions:*, sources:*, etc.).
  4. All UI actions (e.g., "send message", "add source") invoke RPC calls; the remote server processes them and streams results back.

Source: apps/electron/src/renderer/utils/session.ts

The app registers craftagents:// as a default protocol. When a deep link is opened, the main process forwards it to the appropriate window using handleDeepLink.

Source: apps/electron/src/main/deep-link.ts


Deployment Examples

Starting a headless server


# Generate a secure token

CRAFT_SERVER_TOKEN=$(openssl rand -hex 32)

# Launch the server (bind to all interfaces, TLS optional)

CRAFT_RPC_HOST=0.0.0.0 CRAFT_RPC_PORT=9100 \
  CRAFT_SERVER_TOKEN=$CRAFT_SERVER_TOKEN \
  bun run packages/server/src/index.ts

The console outputs the connection details:


CRAFT_SERVER_URL=ws://0.0.0.0:9100
CRAFT_SERVER_TOKEN=...

Connecting the thin client


# Point the desktop app at the remote server

CRAFT_SERVER_URL=ws://my-vps.example.com:9100 \
CRAFT_SERVER_TOKEN=YOUR_SERVER_TOKEN \
bun run electron:start

The app skips local server bootstrapping and forwards all session actions to the remote endpoint.

Invoking RPC from the renderer

import { rpc } from '@craft-agent/shared/transport';

async function listSessions(workspaceId: string) {
  const sessions = await rpc.invoke('sessions:LIST', { workspaceId });
  console.log('Remote sessions:', sessions);
}

The rpc object is instantiated in the preload bridge using the WS URL and token supplied by the main process via __get-ws-port and __get-ws-token.


Summary

  • Compute/UI separation: The headless server (packages/server/src/index.ts) runs all AI agents, sessions, and model fetchers, while the Electron thin client (apps/electron/src/main/index.ts) only renders the UI and forwards actions.
  • Secure WebSocket RPC: All communication flows through WsRpcServer and WsRpcClient (@craft-agent/server-core/transport), authenticated via CRAFT_SERVER_TOKEN with optional TLS encryption.
  • Flexible deployment: Set CRAFT_SERVER_URL to run in thin‑client mode against a remote server, or omit it to boot an embedded server within the Electron app.
  • Unified platform abstraction: Both headless and Electron modes implement the same PlatformServices interface (apps/electron/src/main/platform.ts vs. createHeadlessPlatform), allowing identical server code to run in either environment.
  • Modular services: Session management, credential encryption (AES‑256‑GCM), source connectors, and model refresh services are shared across both deployment modes via @craft-agent/server-core.

Frequently Asked Questions

How does the thin client authenticate with the headless server?

The thin client authenticates by passing a bearer token via the WebSocket connection handshake. When the Electron app starts in client‑only mode (with CRAFT_SERVER_URL set), it reads CRAFT_SERVER_TOKEN from the environment and supplies it to the renderer via the __get-ws-token IPC bridge. The renderer’s WsRpcClient includes this token in the connection headers, which the server validates using validateSessionCookie in packages/server-core/src/transport/ws-rpc.ts.

Can the desktop app run without any remote server?

Yes. When CRAFT_SERVER_URL is not provided, the Electron main process (apps/electron/src/main/index.ts) boots an embedded server using the same @craft-agent/server-core bootstrap logic as the headless server. This creates a SessionManager, registers RPC handlers, and starts listening on a local port. The renderer then connects to this local endpoint via ws://localhost, functioning as a full‑client rather than a thin client.

Where is session data stored in headless mode?

Session data is persisted to the filesystem by the SessionManager (packages/server-core/src/sessions/SessionManager.ts). Sessions are stored as JSONL files under the workspace directory located at ~/.craft-agent/workspaces/{workspaceId}/sessions. The headless server handles all persistence, branching, export, and import operations, while the thin client merely displays the data streamed over RPC.

What encryption standards protect credentials in the headless server?

Credentials are encrypted using AES‑256‑GCM via the shared credential manager (packages/shared/credentials/credential-manager.ts). When the headless server boots, it initializes the credential store, which encrypts API keys and OAuth tokens before writing them to disk. The thin client never handles raw credentials; it only requests the server to perform operations using stored keys via the sources:* and settings:* RPC channels.

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 →