# Craft Agents Headless Server and Thin‑Client Desktop App Architecture

> Explore the Craft Agents OSS architecture featuring a Bun headless server for AI tasks and session persistence, plus an Electron thin client using WebSocket RPC for remote UI rendering. Discover its split design.

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

---

**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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/server/src/index.ts) parses environment variables and initializes the runtime.

```bash

# 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.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/server-core/runtime/headless.ts) → `createHeadlessPlatform`).

> **Source:** [packages/server/src/index.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/server/src/index.ts) and [packages/server-core/src/bootstrap.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/server-core/src/bootstrap.ts)

### Core server components

#### Session management

The `SessionManager` at [`packages/server-core/src/sessions/SessionManager.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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

- **Sources** (`@craft-agent/shared/sources`) plug into MCP servers, REST APIs, and local file systems.
- **Search/Image services** ([`packages/server-core/src/services/search.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/server-core/src/services/search.ts), [`image-utils.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/image-utils.ts)) provide tool‑level capabilities (document parsing, OCR) used by agents.

### Security and authentication

The headless server enforces security at the transport layer:

- **Token authentication** – `CRAFT_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`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/credentials/credential-manager.ts).

> **Source:** [packages/server-core/src/transport/ws-rpc.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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](https://github.com/lukilabs/craft-agents-oss/blob/main/apps/electron/src/main/index.ts)

### Platform abstraction layer

The file [`apps/electron/src/main/platform.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/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](https://github.com/lukilabs/craft-agents-oss/blob/main/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](https://github.com/lukilabs/craft-agents-oss/blob/main/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](https://github.com/lukilabs/craft-agents-oss/blob/main/apps/electron/src/renderer/utils/session.ts)

### Deep‑link handling

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](https://github.com/lukilabs/craft-agents-oss/blob/main/apps/electron/src/main/deep-link.ts)

---

## Deployment Examples

### Starting a headless server

```bash

# 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

```bash

# 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

```typescript
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`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/server/src/index.ts)) runs all AI agents, sessions, and model fetchers, while the Electron thin client ([`apps/electron/src/main/index.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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`](https://github.com/lukilabs/craft-agents-oss/blob/main/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.