# OpenWork Migration System for Profile and Workspace Upgrades: A Technical Deep Dive

> Explore the OpenWork migration system for seamless developer profile and workspace upgrades. Learn how lazy data transformations automate enhancements.

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

---

**The OpenWork migration system automatically upgrades developer profiles and workspace configurations through lazy, pure-data transformations triggered at server boot or first config read.**

OpenWork manages persistent user state across two distinct layers: **developer profiles** that encapsulate per-instance runtime data, and **workspace configurations** that define project settings. When new releases introduce schema changes, the OpenWork migration system ensures seamless upgrades without manual intervention. This article examines the implementation details, source code paths, and exact behavior of both migration paths based on the `different-ai/openwork` repository.

## How Profile Migrations Work in OpenWork

Profile migrations handle structural changes to the **runtime SQLite database** and associated per-profile state. Each development instance runs under a specific profile—whether the default profile, one created via `pnpm dev:worktree`, or a custom-named profile. The profile folder stores the `runtime.sqlite` database, credential caches, and workspace lock files.

### Detecting Schema Version at Startup

The migration detection begins in [`dev/ee/packages/den-db/scripts/bootstrap.ts`](https://github.com/different-ai/openwork/blob/main/dev/ee/packages/den-db/scripts/bootstrap.ts). When the server starts, the bootstrap script checks `PRAGMA user_version` against the expected schema version:

```typescript
// From dev/ee/packages/den-db/scripts/bootstrap.ts
// The bootstrap script runs automatically when starting any profile
await migrate(db, { migrationsFolder });

```

If `user_version` lags behind the current version, the system sequentially applies SQL migration files from `dev/ee/packages/den-db/drizzle/`. This Drizzle-managed folder contains all DDL and DML statements required to bring older profiles forward.

### Automatic Migration Application

The profile migration process follows these steps:

1. **Baseline detection** – The script logs "recording X committed migrations as baseline" to establish the starting state
2. **Sequential application** – Each pending migration file executes in order, altering tables, adding columns, or migrating data as needed
3. **Version advancement** – `PRAGMA user_version` updates automatically, preventing re-application

This entire process runs without user interaction. Simply starting a profile—whether through `pnpm dev` for the default profile or `pnpm dev:worktree` for a stable worktree profile—triggers any necessary migrations.

```bash

# Example: Starting a profile that automatically runs pending DB migrations

pnpm dev:worktree  # Creates stable profile folder and applies migrations if needed

```

## How Workspace Configuration Migration Works

Workspace configuration presents a distinct migration challenge. Legacy OpenWork installations stored workspace settings in a JSON file at [`.opencode/openwork.json`](https://github.com/different-ai/openwork/blob/main/.opencode/openwork.json). Modern versions store this data in the `workspace_config` table of `runtime.sqlite`. The OpenWork migration system handles this transition through **lazy on-read migration**.

### The On-Read Migration Pattern

The migration triggers when any code calls `readOpenworkWorkspaceConfig()` from [`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 typically happens via the `GET /workspace/:id/config` endpoint:

```typescript
// From apps/server/src/openwork-workspace-config-store.ts
// Triggers automatically on first config read
const config = await readOpenworkWorkspaceConfig(serverConfig, workspaceId);

```

The function implements the following detection logic:

1. Query the `workspace_config` table for the workspace ID
2. If found, return the DB-stored configuration
3. If missing, check for legacy [`.opencode/openwork.json`](https://github.com/different-ai/openwork/blob/main/.opencode/openwork.json) on disk
4. If legacy file exists, parse JSON and insert into `workspace_config` table
5. Return the configuration (now sourced from DB for future reads)

### Backward Compatibility Guarantees

The workspace migration preserves **backward compatibility** deliberately. The legacy JSON file remains untouched on disk—no deletion occurs. Existing workspaces continue functioning without modification, while new workspaces write directly to the database. The API response shape remains identical, including fields like `openwork.version`, `authorizedRoots`, and workspace preset information.

```typescript
// Example: Reading workspace config triggers migration if needed
import { readOpenworkWorkspaceConfig } from "./openwork-workspace-config-store.js";

const workspaceId = "ws_legacy_fixture";
const config = await readOpenworkWorkspaceConfig(serverConfig, workspaceId);

// If legacy openwork.json existed, it has now been copied into DB
console.log(config.version);          // → 1
console.log(config.authorizedRoots);  // → ["/absolute/path/to/workspace"]

```

## Migration Safety and Testing

Both migration paths operate as **pure-data transformations**—no arbitrary code executes in the user environment. The system includes comprehensive test coverage to verify correctness.

### Profile Migration Verification

Profile migrations rely on Drizzle's established migration framework, with SQL files version-controlled in `ee/packages/den-db/drizzle/`. Each migration is idempotent and transactional where possible.

### Workspace Migration Test Implementation

The end-to-end test in [`apps/server/src/openwork-config-db-migration.e2e.test.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-config-db-migration.e2e.test.ts) validates the complete JSON-to-DB migration flow. This test proves:

- Legacy [`openwork.json`](https://github.com/different-ai/openwork/blob/main/openwork.json) files are correctly detected and parsed
- Database rows are created with expected fields (`version = 1`, `authorizedRoots`, `workspace.preset`)
- API responses maintain identical payload shapes post-migration
- Subsequent reads use the database as source of truth

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`dev/ee/packages/den-db/scripts/bootstrap.ts`](https://github.com/different-ai/openwork/blob/main/dev/ee/packages/den-db/scripts/bootstrap.ts) | Bootstraps runtime DB and executes profile migrations at startup |
| `dev/ee/packages/den-db/drizzle/` | Directory containing all SQL migration files for profile schema changes |
| [`apps/server/src/openwork-workspace-config-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-workspace-config-store.ts) | Runtime implementation of workspace config reads and lazy migration |
| [`apps/server/src/openwork-config-db-migration.e2e.test.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-config-db-migration.e2e.test.ts) | End-to-end test verifying legacy JSON to DB migration behavior |

## Summary

- **Profile migrations** run automatically at server startup, detecting schema version via `PRAGMA user_version` and applying SQL files from the Drizzle migration folder
- **Workspace config migrations** trigger lazily on first read through `readOpenworkWorkspaceConfig()`, transparently moving data from [`.opencode/openwork.json`](https://github.com/different-ai/openwork/blob/main/.opencode/openwork.json) to the `workspace_config` table
- Both migrations are **non-destructive** and **backward-compatible**, preserving existing files and maintaining API response shapes
- The system is **pure-data**—no user code execution occurs during upgrades
- Extensive testing in [`openwork-config-db-migration.e2e.test.ts`](https://github.com/different-ai/openwork/blob/main/openwork-config-db-migration.e2e.test.ts) guarantees migration safety

## Frequently Asked Questions

### What triggers OpenWork profile migrations?

Profile migrations trigger automatically when starting any OpenWork server instance. The bootstrap script in [`dev/ee/packages/den-db/scripts/bootstrap.ts`](https://github.com/different-ai/openwork/blob/main/dev/ee/packages/den-db/scripts/bootstrap.ts) runs `PRAGMA user_version` against `runtime.sqlite` and applies pending migrations from `ee/packages/den-db/drizzle/` if the database schema is outdated. No manual command is required—simply running `pnpm dev` or `pnpm dev:worktree` executes any needed upgrades.

### Will my existing workspace configurations break when upgrading OpenWork?

No. The OpenWork migration system specifically preserves backward compatibility for workspace configurations. Legacy [`.opencode/openwork.json`](https://github.com/different-ai/openwork/blob/main/.opencode/openwork.json) files remain on disk and functional. The system only copies data into the database on first read, then uses the DB as the source of truth for subsequent accesses. The API response format remains identical, ensuring no client-side changes are needed.

### What happens if a migration fails?

Profile migrations run through Drizzle's transactional migration system, which applies changes atomically where SQLite semantics allow. Failed migrations surface as startup errors, preventing the server from booting with an inconsistent state. Workspace config migrations operate as single-row insertions after parsing JSON—if parsing fails, the original error propagates without partial database writes.

### How do I create a development profile that persists across upgrades?

Use `pnpm dev:worktree` instead of `pnpm dev`. This command creates a **stable profile folder** with its own `runtime.sqlite` database and workspace lock. The profile survives git branch switches and OpenWork version upgrades, with automatic migrations applied whenever schema changes occur.