# Understanding the Workspace-Server-Client Architecture for Communicating with OpenCode in OpenWork

> Learn how OpenWork's workspace-server-client architecture decouples your UI from OpenCode. Discover how the Workspace Server securely proxies all OpenCode interactions via HTTP.

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

---

**OpenWork implements a three-tier workspace-server-client architecture where the Workspace Server acts as a stable token-authenticated gateway, proxying all OpenCode interactions via HTTP endpoints while keeping the UI client completely decoupled from the underlying OpenCode binary.**

OpenWork extends the OpenCode platform through this sophisticated architectural pattern that isolates the user interface from direct OpenCode dependencies. The `different-ai/openwork` repository implements this design to enable the desktop application to run across multiple sandboxed environments—from Electron to headless Chrome—while maintaining persistent, authenticated connections to the LLM-driven code assistant engine.

## Core Components of the Architecture

The architecture separates concerns into three distinct layers, ensuring that UI reloads or sandbox changes never disrupt the underlying code assistant session.

### Workspace Server (openwork-server)

The **Workspace Server** serves as the canonical authority for workspace definitions and token management. Located at [`apps/server/src/cli.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/cli.ts), this component issues stable **host-token** and **workspace-token** pairs that authenticate all subsequent operations. The server maintains a local HTTP API (typically bound to `http://127.0.0.1:<port>`) and uses the OpenCode SDK (`@opencode-ai/sdk/v2/client`) to communicate with the OpenCode binary.

Rather than exposing the OpenCode CLI directly, the server constructs workspace-scoped paths such as `/workspace/<id>/…` and forwards these requests to the OpenCode process via its JSON-RPC protocol. This abstraction allows the server to inject authentication headers and handle process lifecycle management without client involvement.

### Client Interface (UI/Electron)

The **Client** layer consists of a React-based desktop UI running inside an Electron sandbox. This client never communicates directly with the OpenCode binary. Instead, it issues HTTP requests to the Workspace Server's endpoints—such as `/workspace/:id/mcp/openwork-cloud/health`—using standard fetch operations.

The server handles all token injection, meaning the UI sees a consistent API surface regardless of how many times the underlying OpenCode instance restarts. This decoupling enables the frontend to function identically whether running in Electron, headless Chrome, or cloud-based development environments like Daytona.

### OpenCode Engine

The **OpenCode** component provides the underlying LLM-driven code assistant capabilities, including skill execution and file-system operations. It runs as a binary—specified by the `OPENWORK_OPENCODE_BIN` environment variable (defaulting to `opencode`)—launched by the Workspace Server with `--host-token` and `--workspace` flags. The server spawns this process and maintains a persistent JSON-RPC connection for all subsequent operations.

## Communication Flow and Token Management

The interaction between these components follows a strict initialization and request proxying sequence that ensures configuration persistence and secure authentication.

### Server Startup and Environment Configuration

During initialization, the Workspace Server reads the active workspace path from the `OPENWORK_WORKSPACE` environment variable, defaulting to `process.cwd()` when unspecified. It then spawns the OpenCode binary with the workspace path and a generated host token.

In [`dev/scripts/dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/dev/scripts/dev-headless-web.ts), the server startup logic configures these parameters:

```typescript
// Lines 269 and 359 in dev/scripts/dev-headless-web.ts
const serverArgs = [
  "apps/server/src/cli.ts",
  "--host", "127.0.0.1",
  "--port", String(serverPort),
  "--token", SERVER_TOKEN,
  "--workspace", workspace,          // ← workspace path from OPENWORK_WORKSPACE
];

```

The `OPENWORK_OPENCODE_BIN` environment variable determines which binary the server executes, allowing developers to swap OpenCode versions without modifying client code.

### Persistent Workspace Configuration

To prevent user-added workspaces from disappearing during server restarts, the architecture implements a merge-based configuration strategy in [`dev/scripts/dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/dev/scripts/dev-headless-web-lib.ts). Rather than overwriting the persisted [`opencode.json`](https://github.com/different-ai/openwork/blob/main/opencode.json) file, the server checks for existing workspace entries and appends new ones only when absent.

The logic at lines 95-135 demonstrates this safe merging:

```typescript
// dev/scripts/dev-headless-web-lib.ts (lines 95-135)
if (hasWorkspace) {
  // keep existing workspace entry
  workspaces: existingWorkspaces
} else {
  // add the new workspace without overwriting
  workspaces: [...existingWorkspaces, { path: workspaceRoot }],
}

```

This approach ensures that manually configured workspaces survive process restarts while programmatically added workspaces are still persisted.

### HTTP Request Proxying

When the Client requires OpenCode functionality—such as health checks or skill execution—it sends HTTP requests to the Workspace Server. The server decorates these requests with the appropriate workspace-token and forwards them to the OpenCode JSON-RPC endpoint.

The file `dev/scripts/repro-engine-mcp-evidence.mjs` demonstrates this pattern between lines 355 and 485:

```typescript
// Client request through Workspace Server to OpenCode
const health = await serverJson(openwork.baseUrl,
  `/workspace/${encodeURIComponent(workspaceId)}/mcp/openwork-cloud/health`);

```

On the UI side, components like [`packages/ui/src/components/WorkspaceProvider.tsx`](https://github.com/different-ai/openwork/blob/main/packages/ui/src/components/WorkspaceProvider.tsx) consume these endpoints using standard fetch operations with Bearer token authentication:

```typescript
fetch(`${process.env.VITE_DEN_API_BASE_URL}/workspace/${workspaceId}/mcp/openwork-cloud/health`, {
  headers: { Authorization: `Bearer ${workspaceToken}` },
})
  .then(res => res.json())
  .then(data => console.log("OpenCode health:", data));

```

## Implementation Examples

The following patterns demonstrate how the architecture handles key operations:

**Spawning the OpenCode sidecar with authentication flags:**

```typescript
// dev/scripts/dev-headless-web.ts
const opencodeProcess = spawn(OPENWORK_OPENCODE_BIN, [
  "--host-token", hostToken,
  "--workspace", workspacePath,
  "--port", opencodePort,
]);

```

**Merging workspace configuration safely:**

```typescript
// dev/scripts/dev-headless-web-lib.ts
const config = {
  ...existingConfig,
  workspaces: existingWorkspaces.some(w => w.path === workspaceRoot) 
    ? existingWorkspaces 
    : [...existingWorkspaces, { path: workspaceRoot }],
};

```

**Proxying MCP (Model Context Protocol) requests:**

```typescript
// dev/scripts/repro-engine-mcp-evidence.mjs
const evidence = await serverJson(
  baseUrl, 
  `/workspace/${id}/mcp/openwork-cloud/evidence`,
  { method: "POST", body: requestBody }
);

```

## Summary

- **Workspace Server** acts as the sole intermediary between Client and OpenCode, managing tokens and process lifecycle.
- **Client applications** communicate only via HTTP to the Workspace Server, never directly to OpenCode, enabling sandbox flexibility.
- **Configuration merging** in [`dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/dev-headless-web-lib.ts) ensures workspace persistence across restarts without data loss.
- **Environment variables** `OPENWORK_WORKSPACE` and `OPENWORK_OPENCODE_BIN` control workspace root and binary location.
- **Token-based authentication** uses host-token and workspace-token pairs to secure all OpenCode interactions.

## Frequently Asked Questions

### How does the OpenWork client communicate with OpenCode?

The OpenWork client communicates exclusively through the Workspace Server's HTTP API endpoints, such as `/workspace/:id/mcp/openwork-cloud/health`. The client sends standard HTTP requests with Bearer token authentication, and the server proxies these requests to the OpenCode binary via JSON-RPC, injecting the necessary workspace-token and host-token credentials automatically.

### What prevents workspace configuration loss during server restarts?

The server implements a merge-based configuration strategy in [`dev/scripts/dev-headless-web-lib.ts`](https://github.com/different-ai/openwork/blob/main/dev/scripts/dev-headless-web-lib.ts) (lines 95-135) that checks the persisted [`opencode.json`](https://github.com/different-ai/openwork/blob/main/opencode.json) for existing workspace entries before writing. If the current workspace exists, the server preserves the existing list; if not, it appends the new workspace without overwriting user-added configurations, ensuring continuity across process restarts.

### Why doesn't the UI connect directly to OpenCode?

Direct connection would couple the UI to OpenCode's process lifecycle and command-line interface, breaking when the binary restarts or when running in restricted sandboxes like Electron or Daytona. By routing all traffic through the Workspace Server, the UI sees a stable API surface regardless of OpenCode state changes, enabling hot-reloading and multi-environment deployment without configuration changes.

### What environment variables control the OpenCode binary execution?

Two critical environment variables govern the sidecar process: `OPENWORK_WORKSPACE` defines the active workspace folder (defaulting to `process.cwd()`), while `OPENWORK_OPENCODE_BIN` specifies the path to the OpenCode executable (defaulting to `opencode`). These are processed in [`dev/scripts/dev-headless-web.ts`](https://github.com/different-ai/openwork/blob/main/dev/scripts/dev-headless-web.ts) at lines 269 and 359 during server initialization.