# How OpenWork Headless Web Mode Works Without Electron

> Discover how OpenWorks headless web mode provides a desktop experience without Electron. Learn about its Vite web server, server process, and process isolation.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: internals
- Published: 2026-08-17

---

**OpenWork's headless web mode delivers a fully functional desktop-like experience using only a Vite web server and the OpenWork server process, eliminating the need for Electron binaries through process isolation and token-based authentication.**

The `different-ai/openwork` repository provides a lightweight, browser-based alternative to native desktop applications. By orchestrating a Vite development server alongside the OpenWork server process, this architecture provides complete API access and UI rendering without bundling Chromium or Node.js within a single Electron executable.

## Startup Sequence

The headless web mode launch is orchestrated by [`scripts/dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web.ts), which manages an eight-step initialization process defined between lines 18 and 284.

### Environment Preparation

The launcher creates a `tmp/` directory to store runtime logs and the manifest file. This isolated workspace prevents conflicts with other development instances and persists data across restarts.

### Instance Detection and Reuse

Before spawning new processes, the script checks for an existing manifest at [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) (lines 71-86). If health endpoints respond successfully, the script reuses the running instance unless the `--replace` flag is provided, which triggers termination of existing PIDs.

### Dynamic Port Resolution

Free ports are automatically selected for the Vite UI (defaulting to 5178) and the OpenWork server (defaulting to 8778). This resolution occurs in lines 107-137 of [`dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/dev-headless-web.ts), ensuring no hardcoded conflicts with other local services.

### Configuration Merging

The isolated server configuration stored in [`tmp/headless-server.json`](https://github.com/different-ai/openwork/blob/main/tmp/headless-server.json) is merged with the current workspace settings. This persistence layer ensures that UI-created workspaces survive process restarts, implemented in [`scripts/dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web-lib.ts) between lines 78-115.

### Process Spawning

Two detached child processes are launched:

- **Vite UI**: Executed via `pnpm --filter @openwork/app exec vite` with custom `VITE_*` environment variables (lines 210-226)
- **OpenWork Server**: Started via Bun executing [`apps/server/src/cli.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/cli.ts) with merged configuration and CORS origins (lines 234-246)

The detachment prevents terminal session termination from killing the stack.

### Runtime Manifest Generation

The script writes a JSON manifest to [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) containing the web URL, API URL, authentication tokens (`token` and `hostToken`), process IDs, workspace path, and optional Den proxy settings (lines 162-208 in [`dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/dev-headless-web-lib.ts)).

### Graceful Shutdown Handling

Signal handlers for `SIGINT` and `SIGTERM` trigger orderly termination of all child processes via the cleanup routine in lines 257-284, while preserving the manifest for subsequent reuse.

## Core Architectural Components

### Launcher Script

[`scripts/dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web.ts) serves as the entry point and orchestrator. It manages the `--replace` flag for force-restarting stacks, supports detached execution with `--detach`, and handles the lifecycle of both Vite and server processes through Node.js child process APIs.

### Helper Library

[`scripts/dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web-lib.ts) provides utilities for cryptographically secure token generation, CORS origin construction, and configuration merging. These functions replace Electron's context bridge and secure storage with standard HTTP-based authentication patterns.

### Vite Development Server

Rather than embedding a browser runtime, the architecture serves the React UI (`@openwork/app`) through a standard Vite dev server. Clients access the application through regular browser tabs at `http://127.0.0.1:5178` (by default), eliminating the need for a wrapped Chromium window.

### OpenWork Server

[`apps/server/src/cli.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/cli.ts) functions as the local API layer, handling workspace management, sessions, and plugin execution. It accepts the merged configuration from [`tmp/headless-server.json`](https://github.com/different-ai/openwork/blob/main/tmp/headless-server.json) and exposes endpoints on the resolved server port (default 8778).

### Runtime Manifest

[`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) acts as a service registry for downstream tooling. The manifest structure includes:

- `webUrl`: Vite UI endpoint
- `openworkUrl`: API server endpoint
- `token`: Owner-level authentication
- `hostToken`: Administrative authentication
- `pids`: Process identifiers for cleanup operations
- `denProxy`: Cloud control plane configuration

## How It Avoids Electron

The architecture eliminates Electron through four specific design decisions:

**Process Isolation Over Bundling**

Instead of packaging the UI and server within a single Electron main/renderer process structure, OpenWork spawns them as independent child processes running in separate process groups. This isolation prevents terminal session kills from cascading while maintaining clear operational boundaries suitable for headless servers.

**Standard Web Technologies**

The UI runs as a plain web bundle served by Vite. Users interact through standard browser tabs rather than a wrapped native window, removing the dependency on Electron's renderer process architecture and associated memory overhead.

**Same-Origin Proxy Strategy**

When integrating with Den (the cloud control plane), the Vite dev server proxies `/api/den` requests to the remote target. This maintains same-origin security policies without requiring Electron's IPC bridge for cross-origin requests, as implemented in the proxy configuration logic.

**Token-Based Authentication**

Authentication relies on `token` and `hostToken` values stored in the runtime manifest and transmitted via HTTP headers. This replaces Electron's `safeStorage` API with standard bearer token patterns compatible with curl, automated testing frameworks, and CI/CD pipelines.

## Running Headless Web Mode

### Starting a New Instance

Launch the complete stack with process detachment:

```bash
pnpm dev:headless-web --detach

```

The launcher outputs the accessible URLs:

```

[dev:headless-web] Web URL: http://127.0.0.1:5178
[dev:headless-web] OpenWork server: http://127.0.0.1:8778

```

### Reusing Existing Instances

To connect to a running stack without spawning duplicate processes:

```bash
pnpm dev:headless-web

```

If [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) references healthy endpoints, the script prints the existing URLs and exits immediately.

### Force Restarting

Kill the existing stack and start fresh:

```bash
pnpm dev:headless-web --replace

```

This sends `SIGTERM` followed by `SIGKILL` to the PIDs recorded in the manifest before initialization.

### Programmatic Manifest Access

Read the runtime configuration from [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json):

```typescript
import { readFile } from "node:fs/promises";

async function loadManifest() {
  const data = await readFile("tmp/dev-headless-web.json", "utf8");
  const manifest = JSON.parse(data);
  console.log("Web UI:", manifest.webUrl);
  console.log("API:", manifest.openworkUrl);
  console.log("Auth Token:", manifest.token);
}

loadManifest();

```

### Custom Configuration

Run with a specific workspace or disable Den proxying:

```bash
OPENWORK_WORKSPACE=/path/to/my/workspace \
OPENWORK_DEV_DEN_PROXY=0 \
pnpm dev:headless-web

```

Environment variables override defaults before port resolution and config merging occur in the launcher script.

## Summary

- **OpenWork's headless web mode** replaces Electron with a Vite server and Bun-based API process, reducing resource overhead and native dependencies while maintaining full functionality.
- The **launcher script** ([`scripts/dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web.ts)) manages an eight-step initialization including port resolution, config merging, and manifest generation for durable state management.
- **Process isolation** through detached child processes and POSIX signal handling ensures stable long-running instances without desktop environment lock-in.
- **Token-based authentication** stored in [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) enables secure API access via standard HTTP headers, eliminating the need for Electron-specific secure storage mechanisms.
- The architecture supports **instance reuse** through health checks and manifest persistence, optimizing development workflow startup times while preventing port conflicts.

## Frequently Asked Questions

### How does OpenWork handle authentication without Electron's secure storage?

OpenWork generates cryptographically secure tokens (`token` for ownership, `hostToken` for administration) in [`scripts/dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/scripts/dev-headless-web-lib.ts). These tokens are stored in the runtime manifest at [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) and transmitted via HTTP headers between the UI and server. This approach relies on filesystem permissions for security rather than OS-specific keychains, making it compatible with containerized and CI/CD environments where Electron's `safeStorage` API would be unavailable.

### Can I run multiple headless web instances simultaneously?

Yes, provided you specify distinct workspace directories. The launcher resolves free ports automatically (defaulting to 5178 for Vite and 8778 for the API), but you should ensure unique `OPENWORK_WORKSPACE` values to prevent database conflicts. Each instance writes to its own isolated manifest and log files within the `tmp/` directory, allowing parallel development on different projects.

### What happens if the launcher process crashes or is killed?

The Vite and OpenWork server processes run in **detached process groups** spawned with detached flags in [`dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/dev-headless-web.ts) (lines 210-246). If the launcher exits unexpectedly, the child processes continue running. You can reconnect to these persistent instances by running `pnpm dev:headless-web` without the `--replace` flag, which detects the running processes via the manifest file and re-establishes the connection without restarting services.

### How do I access the OpenWork API from external tools when running headless mode?

The runtime manifest at [`tmp/dev-headless-web.json`](https://github.com/different-ai/openwork/blob/main/tmp/dev-headless-web.json) contains the `openworkUrl` endpoint and authentication tokens required for API access. External scripts can parse this file to obtain the base URL and required `Authorization` headers. The server enables CORS origins dynamically based on the Vite URL (constructed in [`dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/dev-headless-web-lib.ts)), allowing browser-based tools and command-line utilities running on localhost to interact with the API seamlessly.