# How OpenWork Handles Workspace Initialization and Profile Creation

> Discover how OpenWork handles workspace initialization and profile creation through a three-step server-side process involving filesystem validation, SQLite profile seeding, and server state registration.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-15

---

**OpenWork initializes workspaces through a three-step server-side process: filesystem validation, SQLite-backed profile seeding, and server state registration, with core logic residing in [`apps/server/src/workspace-init.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/workspace-init.ts) and [`apps/server/src/openwork-workspace-config-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-workspace-config-store.ts).**

This deep dive explores how the OpenWork platform—an open-source workspace management system—creates and configures both local and remote workspaces. The implementation distinguishes itself by storing workspace **profiles** (metadata and configuration) in a runtime **SQLite database** rather than legacy JSON files, while maintaining minimal filesystem requirements for local workspaces.

---

## Workspace Initialization Overview

OpenWork's workspace lifecycle is orchestrated through HTTP API endpoints that invoke specialized server utilities. The process differs for **local workspaces** (filesystem-backed) and **remote workspaces** (network-connected), though both converge on the same profile storage mechanism.

The architecture separates concerns into three layers:

- **Route handlers** in [`apps/server/src/routes/workspaces.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/routes/workspaces.ts) handle HTTP requests
- **Initialization utilities** in [`apps/server/src/workspace-init.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/workspace-init.ts) manage filesystem and preset logic
- **Profile store** in [`apps/server/src/openwork-workspace-config-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-workspace-config-store.ts) handles database persistence

---

## Step 1: Validate and Prepare the Filesystem

When creating a local workspace, the server first ensures the target directory exists and contains required structure. The **POST `/workspaces/local`** endpoint receives a payload with `folderPath`, optional `name`, and optional `preset`.

### Path Resolution and Directory Creation

The endpoint resolves the absolute path and invokes `ensureDir` to create missing directories. It then delegates to **`ensureWorkspaceFiles`** in [`apps/server/src/workspace-init.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/workspace-init.ts):

```typescript
// From apps/server/src/workspace-init.ts – lines 46-68
export async function ensureWorkspaceFiles(
  workspacePath: string,
  preset: string
): Promise<EnsureWorkspaceFilesResult> {
  const normalizedPreset = normalizePreset(preset);
  const result: EnsureWorkspaceFilesResult = {
    changed: false,
    reloadReasons: []
  };

  // Ensures minimal workspace structure exists
  // Currently a no-op for config files (stored in DB)
  const configResult = await ensureOpencodeConfig(workspacePath);
  
  return {
    ...result,
    changed: configResult.changed,
    reloadReasons: [...result.reloadReasons, ...configResult.reloadReasons]
  };
}

```

**Key behaviors:**
- **`normalizePreset`** sanitizes the preset value (defaults to `"starter"`)
- **`ensureOpencodeConfig`** is currently a no-op since configuration migrated to the database

---

## Step 2: Seed the Workspace Profile

OpenWork stores per-workstation metadata—collectively called the **profile**—in a SQLite database rather than [`.opencode/openwork.json`](https://github.com/different-ai/openwork/blob/main/.opencode/openwork.json). This profile contains versioning, naming, timestamps, and preset selection.

### Building the Default Profile

The function **`defaultWorkspaceOpenworkConfig`** in [`apps/server/src/workspace-init.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/workspace-init.ts) constructs the initial profile structure:

```typescript
// From apps/server/src/workspace-init.ts – lines 31-44
export function defaultWorkspaceOpenworkConfig(
  workspaceName: string,
  preset: string
): OpenworkWorkspaceConfig {
  return {
    version: OPENWORK_CONFIG_VERSION,
    workspace: {
      name: workspaceName,
      createdAt: new Date().toISOString(),
      preset: normalizePreset(preset)
    },
    // Additional runtime metadata...
  };
}

```

### Conditional Database Insertion

The route handler invokes **`seedOpenworkWorkspaceConfigIfEmpty`**, which writes the profile only if no existing record exists:

```typescript
// Conceptual usage from apps/server/src/openwork-workspace-config-store.ts
await seedOpenworkWorkspaceConfigIfEmpty(serverConfig, workspaceId, {
  name: workspaceName,
  preset: chosenPreset
});

```

The persistence layer lives in **[`apps/server/src/openwork-workspace-config-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-workspace-config-store.ts)**, which abstracts key-value operations against the SQLite backend.

---

## Step 3: Register in Server State

After profile seeding, the workspace is added to the server's runtime configuration. The endpoint constructs a **`WorkspaceInfo`** object containing:

- Unique identifier (`id`)
- Display name and filesystem path
- Selected preset
- Opencode connection metadata

This object is inserted at the head of `config.workspaces`, then persisted via **`persistServerWorkspaceState`**. Finally, an audit record is created and the API returns the updated workspace list with a `persisted` flag.

From [`apps/server/src/routes/workspaces.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/routes/workspaces.ts) (lines 71-110), the handler implementation:

```typescript
// Representative structure of the POST /workspaces/local handler
const workspaceInfo: WorkspaceInfo = {
  id: generateWorkspaceId(),
  name: validatedName,
  path: absoluteFolderPath,
  preset: normalizedPreset,
  connection: buildOpencodeConnection(absoluteFolderPath)
};

// Add to server state
config.workspaces.unshift(workspaceInfo);
await persistServerWorkspaceState(config);

// Return response
return res.json({
  workspaces: config.workspaces,
  activeId: workspaceInfo.id,
  persisted: true
});

```

---

## Remote Workspace Initialization

Remote workspaces bypass filesystem provisioning. The **POST `/workspaces/remote`** endpoint accepts:

| Parameter | Purpose |
|-----------|---------|
| `baseUrl` | Remote host address |
| `remoteType` | Protocol discriminator (e.g., `"openwork"`) |
| `openworkToken` | Authentication credential (optional) |
| `directory` | Optional override for remote path |

When `remoteType` is `"openwork"`, the server queries the remote host's `/workspaces` endpoint to discover available workspaces, extracts `id` and `displayName`, then seeds the profile through identical database logic.

```javascript
// Create a remote OpenWork workspace via API
await fetch('http://localhost:3000/workspaces/remote', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    baseUrl: 'https://demo.openworklabs.com',
    remoteType: 'openwork',
    openworkToken: 'my-access-token'
  })
});

```

---

## Programmatic Workspace Creation

The following examples demonstrate direct API usage for common initialization scenarios.

### Create a Local Workspace

```javascript
const resp = await fetch('http://localhost:3000/workspaces/local', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    folderPath: '/Users/me/my-project',
    name: 'My Project',
    preset: 'starter'  // optional, defaults to "starter"
  })
});
const data = await resp.json();
console.log('Created workspace ID:', data.activeId);

```

### Read a Seeded Profile Directly

```javascript
import { readOpenworkWorkspaceConfig } from './openwork-workspace-config-store.js';

const profile = await readOpenworkWorkspaceConfig(serverConfig, workspaceId);
console.log('Workspace profile:', profile);
// Output: { version: 1, workspace: { name: '...', createdAt: '...', preset: '...' } }

```

---

## Key Source Files and Responsibilities

| File | Responsibility |
|------|---------------|
| [`apps/server/src/workspace-init.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/workspace-init.ts) | Filesystem validation, preset normalization, `ensureWorkspaceFiles`, `defaultWorkspaceOpenworkConfig` |
| [`apps/server/src/routes/workspaces.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/routes/workspaces.ts) | HTTP endpoints for local/remote creation, workspace lifecycle management |
| [`apps/server/src/openwork-workspace-config-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-workspace-config-store.ts) | Database abstraction, profile seeding, KV store operations |

---

## Summary

- **OpenWork workspace initialization** separates filesystem preparation from profile management
- **Local workspaces** require `folderPath` validation and minimal file provisioning via `ensureWorkspaceFiles` in [`apps/server/src/workspace-init.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/workspace-init.ts)
- **Profiles store persistently** in SQLite through [`apps/server/src/openwork-workspace-config-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-workspace-config-store.ts), not legacy JSON files
- **Default profiles** are constructed by `defaultWorkspaceOpenworkConfig` and seeded conditionally via `seedOpenworkWorkspaceConfigIfEmpty`
- **Remote workspaces** skip filesystem steps but perform host discovery and identical profile seeding
- **Server state registration** finalizes creation by persisting `WorkspaceInfo` through `persistServerWorkspaceState`

---

## Frequently Asked Questions

### What is the difference between a workspace and a profile in OpenWork?

A **workspace** represents the actual project environment—either a local directory or remote connection. A **profile** is the metadata record stored in SQLite containing the workspace name, creation timestamp, preset selection, and versioning. The profile abstracts configuration from the physical workspace location.

### Why does OpenWork use SQLite instead of JSON for profiles?

According to the OpenWork source code, SQLite provides atomic transactional guarantees, simplified querying, and eliminates filesystem lock contention. The migration from [`.opencode/openwork.json`](https://github.com/different-ai/openwork/blob/main/.opencode/openwork.json) to database storage also enables centralized management of remote workspace metadata without requiring local file access.

### Can I customize the default preset during workspace creation?

Yes. The `preset` parameter in **POST `/workspaces/local`** accepts any string value, which `normalizePreset` processes. While `"starter"` is the documented default, the validation logic in [`apps/server/src/workspace-init.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/workspace-init.ts) permits arbitrary presets for future extensibility.

### What happens if I attempt to create a workspace in an existing directory?

The `ensureDir` call succeeds silently if the directory exists. `ensureWorkspaceFiles` then performs incremental validation—adding only missing required files. Profile seeding via `seedOpenworkWorkspaceConfigIfEmpty` is idempotent: it skips insertion if a profile already exists for that workspace ID, preventing duplicate records.