How OpenWork Handles Workspace Initialization and Profile Creation
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 and 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.tshandle HTTP requests - Initialization utilities in
apps/server/src/workspace-init.tsmanage filesystem and preset logic - Profile store in
apps/server/src/openwork-workspace-config-store.tshandles 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:
// 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:
normalizePresetsanitizes the preset value (defaults to"starter")ensureOpencodeConfigis 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. This profile contains versioning, naming, timestamps, and preset selection.
Building the Default Profile
The function defaultWorkspaceOpenworkConfig in apps/server/src/workspace-init.ts constructs the initial profile structure:
// 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:
// 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, 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 (lines 71-110), the handler implementation:
// 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.
// 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
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
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 |
Filesystem validation, preset normalization, ensureWorkspaceFiles, defaultWorkspaceOpenworkConfig |
apps/server/src/routes/workspaces.ts |
HTTP endpoints for local/remote creation, workspace lifecycle management |
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
folderPathvalidation and minimal file provisioning viaensureWorkspaceFilesinapps/server/src/workspace-init.ts - Profiles store persistently in SQLite through
apps/server/src/openwork-workspace-config-store.ts, not legacy JSON files - Default profiles are constructed by
defaultWorkspaceOpenworkConfigand seeded conditionally viaseedOpenworkWorkspaceConfigIfEmpty - Remote workspaces skip filesystem steps but perform host discovery and identical profile seeding
- Server state registration finalizes creation by persisting
WorkspaceInfothroughpersistServerWorkspaceState
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 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →