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:
- Read workspace config from the runtime DB using
readOpenworkWorkspaceConfig() - Read global config via
readGlobalRuntimeOpencodeConfig() - Extract legacy values with
legacyRuntimeConfigFromOpenworkConfig()if present - Merge configurations using
mergeLegacyRuntimeConfig()to resolve conflicts - 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:
- Server startup reads the global Opencode file from
/.opencode/opencode.jsonusingreadJsoncFile() - Workspace creation invokes
defaultWorkspaceOpenworkConfig()to seed a DB record with version metadata and authorized roots - Legacy detection triggers
legacyRuntimeConfigFromOpenworkConfig()to extract old plugin and MCP definitions from the global file - Runtime generation calls
buildOpenworkRuntimeConfigObject()to compose the final JSON, merging global defaults with workspace-specific overrides - API mutation allows clients to
PATCHupdates; changes trigger reloads viareloadBaselineRefreshersto synchronize the engine process
Summary
- Global settings reside in
/.opencode/opencode.json(JSON-C) and are parsed byreadJsoncFile()inapps/server/src/workspace-files.ts - Workspace configuration is stored in the runtime database, initialized by
defaultWorkspaceOpenworkConfig()inapps/server/src/workspace-init.ts - Legacy migration automatically extracts old keys via
legacyRuntimeConfigFromOpenworkConfig()inapps/server/src/server.ts - Runtime artifacts are generated by
buildOpenworkRuntimeConfigObject()inapps/server/src/openwork-runtime-config.tsand written toopenwork-runtime.json - API access is provided through
/w/:workspaceId/openworkendpoints inapps/server/src/routes/workspaces.ts, with mutations triggering coordinated reloads
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →