How Configuration is Managed in OpenWork: Global Files, Database Storage, and Runtime Generation

OpenWork manages configuration through a dual-layer architecture that stores global Opencode settings in /.opencode/opencode.json while persisting workspace-specific data in a runtime database, automatically migrating legacy keys and generating a derived openwork-runtime.json file for the Electron engine process.

OpenWork (from the different-ai/openwork repository) implements a sophisticated configuration system that separates global IDE preferences from per-workspace runtime parameters. Understanding how configuration is managed in OpenWork requires examining three distinct layers—global Opencode files, workspace database records, and the generated runtime configuration that feeds the engine process.

The Three-Tier Configuration Architecture

OpenWork splits configuration across global (Opencode) settings, per-workspace OpenWork settings, and derived runtime files. This separation allows global defaults to coexist with workspace-specific overrides while maintaining a flat file interface for the underlying Electron process.

Global Opencode Configuration (/.opencode/opencode.json)

The global Opencode file contains generic OpenWork settings such as default providers and MCP (Model Context Protocol) definitions. According to the source code in apps/server/src/workspace-files.ts, the system locates this file using the opencodeConfigPath() helper:

// apps/server/src/workspace-files.ts
export const opencodeConfigPath = (workspaceRoot: string) =>
  join(workspaceRoot, ".opencode", "opencode.json");

The server reads this file using readJsoncFile(), which parses JSON-C (JSON with comments) and feeds the values into the startup sequence. This global layer serves as the foundation for default values across all workspaces.

Workspace-Specific OpenWork Configuration (Runtime DB)

Unlike the global file, workspace-specific settings live in the runtime database rather than flat files. When a workspace initializes, the server seeds a minimal configuration record via defaultWorkspaceOpenworkConfig() in apps/server/src/workspace-init.ts:

// apps/server/src/workspace-init.ts
export function defaultWorkspaceOpenworkConfig(
  workspaceRoot: string,
  preset: string,
): WorkspaceOpenworkConfig {
  return {
    version: 1,
    workspace: {
      name: basename(workspaceRoot) || "Workspace",
      createdAt: Date.now(),
      preset,
    },
    authorizedRoots: [workspaceRoot],
    reload: null,
  };
}

The ensureWorkspaceFiles() function guarantees this default record exists during startup. The runtime DB now owns the canonical source of truth, though legacy installations may still reference .opencode/openwork.json as a fallback. The persistence layer is handled by seedOpenworkWorkspaceConfigIfEmpty() and related helpers in apps/server/src/openwork-workspace-config-store.ts.

Derived Runtime Configuration (openwork-runtime.json)

The Electron engine process consumes a concrete JSON file generated on-the-fly. The buildOpenworkRuntimeConfigObject() function in apps/server/src/openwork-runtime-config.ts constructs this artifact by merging global and workspace configurations:

// apps/server/src/openwork-runtime-config.ts
export async function buildOpenworkRuntimeConfigObject(
  config: ServerConfig,
  workspaceId: string,
): Promise<RuntimeOpencodeConfig> {
  const openwork = await readOpenworkWorkspaceConfig(config, workspaceId);
  const opencode = await readGlobalRuntimeOpencodeConfig(config);
  // merge legacy + user config
  const merged = mergeLegacyRuntimeConfig(opencode, legacy);
  // …
  return merged;
}

The resulting openwork-runtime.json file is written by writeOpenworkRuntimeConfigFile() and consumed by the engine via openworkRuntimeConfigFilePath().

Legacy Configuration Migration

Older OpenWork installations stored settings directly inside the global Opencode file. During server startup, the system automatically extracts these legacy keys and migrates them into the new runtime model. The legacyRuntimeConfigFromOpenworkConfig() function in apps/server/src/server.ts handles this extraction:

// apps/server/src/server.ts
function legacyRuntimeConfigFromOpenworkConfig(openwork: Record<string, unknown>) {
  const plugin = Array.isArray(openwork.plugin) ? ... : [];
  const mcp = isRecord(openwork.mcp) ? ... : {};
  // …
  return { config: { plugin, mcp, … }, keys };
}

This migration ensures backward compatibility while moving the canonical source of truth to the database-backed workspace configuration store.

Runtime Config Generation Pipeline

The process of generating the final runtime configuration involves several discrete steps:

  1. Read workspace config from the runtime DB using readOpenworkWorkspaceConfig()
  2. Read global config via readGlobalRuntimeOpencodeConfig()
  3. Extract legacy values with legacyRuntimeConfigFromOpenworkConfig() if present
  4. Merge configurations using mergeLegacyRuntimeConfig() to resolve conflicts
  5. Write the artifact to disk via writeOpenworkRuntimeConfigFile()

This pipeline ensures that workspace-specific overrides always take precedence over global defaults, while legacy values are preserved during the transition period.

Programmatic Configuration Access

Developers can read or update workspace configurations through the server's HTTP API, defined in apps/server/src/routes/workspaces.ts.

Reading Workspace Configuration

Send a GET request to retrieve the stored WorkspaceOpenworkConfig:

// Example using the OpenWork client SDK
import { OpenWorkClient } from "@openwork/sdk";

const client = new OpenWorkClient({ baseUrl: "http://localhost:8787" });
const workspaceId = "my-workspace";

async function getConfig() {
  const config = await client.get(`/w/${workspaceId}/openwork`);
  console.log("Workspace config:", config);
}

Updating Configuration (Owner Scope)

Authenticated owners can modify settings via PATCH, which persists changes to the runtime DB and triggers a workspace reload:

// apps/server/src/routes/workspaces.ts
router.patch("/w/:workspaceId/openwork", async (ctx) => {
  const { workspaceId } = ctx.params;
  const body = await ctx.request.json();
  await writeOpenworkWorkspaceConfig(config, workspaceId, body);
  // schedule a reload for the affected workspace
  reloadBaselineRefreshers.get(config)?.(workspaceId, ["config"]);
  return jsonResponse({ ok: true });
});

Client-side implementation:

import { OpenWorkClient } from "@openwork/sdk";

const client = new OpenWorkClient({
  baseUrl: "http://localhost:8787",
  token: process.env.OPENWORK_HOST_TOKEN, // owner token
});

async function setReloadAuto(workspaceId: string, enable: boolean) {
  await client.patch(`/w/${workspaceId}/openwork`, {
    reload: { auto: enable },
  });
  console.log(`Reload auto ${enable ? "enabled" : "disabled"} for ${workspaceId}`);
}

Complete Configuration Flow Example

A typical lifecycle demonstrates how these layers interact:

  1. Server startup reads the global Opencode file from /.opencode/opencode.json using readJsoncFile()
  2. Workspace creation invokes defaultWorkspaceOpenworkConfig() to seed a DB record with version metadata and authorized roots
  3. Legacy detection triggers legacyRuntimeConfigFromOpenworkConfig() to extract old plugin and MCP definitions from the global file
  4. Runtime generation calls buildOpenworkRuntimeConfigObject() to compose the final JSON, merging global defaults with workspace-specific overrides
  5. API mutation allows clients to PATCH updates; changes trigger reloads via reloadBaselineRefreshers to synchronize the engine process

Summary

Frequently Asked Questions

Where does OpenWork store global configuration settings?

OpenWork stores global Opencode settings in /.opencode/opencode.json (or any user-provided JSON-C file). The server reads this file using readJsoncFile() from apps/server/src/workspace-files.ts during startup to establish baseline defaults for provider definitions and MCP configurations.

How does OpenWork handle workspace-specific configuration?

Workspace-specific settings are stored in the runtime database rather than flat files. When a workspace initializes, defaultWorkspaceOpenworkConfig() in apps/server/src/workspace-init.ts creates a default record containing the workspace name, creation timestamp, authorized roots, and reload settings. The seedOpenworkWorkspaceConfigIfEmpty() function in apps/server/src/openwork-workspace-config-store.ts ensures this record persists.

What is the purpose of the openwork-runtime.json file?

The openwork-runtime.json file is a derived artifact generated on-the-fly by buildOpenworkRuntimeConfigObject() in apps/server/src/openwork-runtime-config.ts. It merges global Opencode settings, workspace-specific database records, and any legacy configurations into a single consumable format for the Electron engine process.

How can I programmatically update OpenWork configuration?

Use the server API endpoints defined in apps/server/src/routes/workspaces.ts. Send a GET request to /w/:workspaceId/openwork to read configuration, or a PATCH request (requiring owner scope) to modify it. The server persists changes via writeOpenworkWorkspaceConfig() and automatically schedules a reload using reloadBaselineRefreshers to synchronize the workspace state.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →