Configuration¶
Open Cowork is configured through open-cowork.config.json.
The file is validated against open-cowork.config.schema.json at runtime. Full resolved configs must also declare contractVersion: 1, the current versioned downstream distribution contract. Partial overlays may omit it because they merge over the built-in default.
Top-level sections¶
The default upstream config is organized into: - contractVersion - allowedEnvPlaceholders - branding - auth - providers - tools - skills - mcps - agents - agentStarterTemplates - toolTrace - builtInAgents - permissions - compaction - i18n - telemetry
Branding¶
branding controls app-level identity:
{
"branding": {
"name": "Open Cowork",
"appId": "com.opencowork.desktop",
"dataDirName": "open-cowork",
"helpUrl": "https://github.com/joe-broadhead/open-cowork",
"projectNamespace": "opencowork"
}
}
Typical downstream changes: - app name - app id - help URL - data directory name - project namespace, if you intentionally want a different on-disk overlay directory than the upstream .opencowork/ - optional sidebar and Home copy surfaces for downstream distributions
Compatibility note: the public GitHub repo is
open-cowork, but the upstreamappIdandprojectNamespaceintentionally keep the historicalopencoworkform for bundle-ID and on-disk back-compat. Change those only when you are deliberately creating a distinct downstream distribution and are prepared to migrate app state.
Sidebar and Home surfaces¶
Downstream builds can tune the first-run product surface without patching React components. These fields are optional; unset fields preserve upstream Open Cowork copy and layout.
{
"branding": {
"name": "Acme Cowork",
"sidebar": {
"top": {
"variant": "logo-text",
"logoAsset": "branding/acme-logo.svg",
"mediaSize": 36,
"mediaFit": "vertical",
"mediaAlign": "center",
"title": "Acme AI",
"subtitle": "Private workspace",
"ariaLabel": "Acme AI workspace"
},
"lower": {
"text": "Acme internal build",
"secondaryText": "Support from Data Platform.",
"linkLabel": "Get help",
"linkUrl": "https://internal.acme.example/cowork-help"
}
},
"home": {
"greeting": "What should {{brand}} work on today?",
"subtitle": "Ask a question or delegate to an approved agent.",
"composerPlaceholder": "Ask {{brand}} anything",
"suggestionLabel": "Start with",
"statusReadyLabel": "Online"
}
}
}
branding.sidebar.top.variant accepts icon, text, icon-text, logo, or logo-text. Logo-backed variants should use logoAsset, a relative path to an image bundled under the package branding/ resource directory, such as branding/acme-logo.svg. The app rejects absolute paths, traversal, remote URLs, missing files, and non-image extensions before exposing the asset to the renderer. Legacy logoDataUrl values are still accepted as a compatibility fallback, but new downstream builds should ship image assets instead of base64 config blobs.
branding.sidebar.top.mediaSize controls the logo/icon media size in pixels and defaults to 28. Values must be between 16 and 96. mediaFit accepts vertical or horizontal: leave it unset to preserve the legacy square bounding box, use vertical for square or tall marks whose height should define the sidebar presence, and use horizontal for wide wordmarks whose width should be fixed. mediaAlign accepts start, center, or end and controls icon/logo placement for icon-only or logo-only branding.
branding.sidebar.lower.linkUrl accepts only https:// and mailto: links. The renderer re-checks that allowlist before rendering a link.
Cloud public branding¶
cloud.publicBranding controls deployer-facing web and gateway surfaces for cloud workspaces. It is separate from desktop branding because many operators ship one desktop app that can connect to different cloud orgs.
{
"cloud": {
"publicBranding": {
"productName": "Acme Cowork",
"shortName": "AC",
"logoUrl": "https://assets.acme.example/cowork/logo.png",
"supportUrl": "https://support.acme.example/cowork",
"privacyUrl": "https://legal.acme.example/privacy",
"securityUrl": "https://security.acme.example/cowork",
"legalUrl": "https://legal.acme.example/terms",
"theme": {
"background": "#0c0d0f",
"elevated": "#1f2329",
"surface": "#141619",
"text": "#eceef1",
"accent": "#2f6bf0",
"accent2": "#5a8cf5",
"accentHover": "#5a8cf5",
"accentForeground": "#ffffff"
},
"managedOrgConnectionLabels": {
"desktopToken": "Acme Desktop token",
"gatewayToken": "Acme Gateway token",
"apiToken": "Acme API token",
"cloudUrl": "Acme Cloud URL"
}
}
}
}
The cloud service exposes the same metadata from GET /api/config; the browser dashboard renders it directly, and the gateway returns it from health/readiness metadata for provider setup tooling. Helm deployments can set the equivalent cloud.branding and gateway.branding values.
Cloud Web defaults to the shared Desktop Mercury graphite palette, Mona Sans / Schibsted Grotesk font stacks, and the structural token scale from packages/shared/src/design-tokens.ts. Public branding may override color and brand presentation keys such as background, surface, elevated, text, mutedText, accent, accent2, accentHover, accentForeground, semantic tones, shadowCard, shadowElevated, and bgImage. Do not use public branding to fork spacing, radius, control heights, typography scale, or runtime behavior. Legacy light partial theme overrides are still normalized for existing deployments.
Environment placeholders¶
Config strings may reference environment variables using {env:NAME}.
For safety, placeholders only work when the variable name is explicitly listed in allowedEnvPlaceholders:
{
"allowedEnvPlaceholders": ["OPENROUTER_BASE_URL"],
"providers": {
"custom": {
"internal-router": {
"options": {
"baseURL": "{env:OPENROUTER_BASE_URL}"
}
}
}
}
}
Unknown placeholders fail config loading with a clear error. This is intentional: Open Cowork does not implicitly pull arbitrary secrets from the host environment.
Auth¶
auth.mode controls whether the app uses an external authentication flow.
The upstream default is no sign-in:
Enabling Google OAuth (downstream)¶
Upstream ships with no OAuth credentials. A downstream distribution that wants to gate access behind Google sign-in registers its own OAuth client in Google Cloud Console and wires it through config — nothing in the upstream repo is shared or reused. The consent screen the end user sees shows the downstream's branding (e.g. "Acme Agent wants to access your Google account"), not Open Cowork's.
1. Register an OAuth client in Google Cloud Console
- Create (or reuse) a GCP project for your distribution.
- Configure the OAuth consent screen with your branding, publisher info, and the scopes you want to request.
- Create an OAuth 2.0 Client ID of type Desktop app. Google provides a client ID and a "client secret". Desktop OAuth secrets are not truly confidential (they can't be, in a public binary), but they're still sensitive — treat them like a moderate secret.
2. Wire the credentials into your downstream config
Use env placeholders so the secret never lands in a committed config file:
{
"allowedEnvPlaceholders": [
"ACME_GOOGLE_OAUTH_CLIENT_ID",
"ACME_GOOGLE_OAUTH_CLIENT_SECRET"
],
"auth": {
"mode": "google-oauth",
"googleOAuth": {
"clientId": "{env:ACME_GOOGLE_OAUTH_CLIENT_ID}",
"clientSecret": "{env:ACME_GOOGLE_OAUTH_CLIENT_SECRET}",
"scopes": [
"openid",
"https://www.googleapis.com/auth/userinfo.email"
]
}
}
}
Ship the config in your downstream root, and set ACME_GOOGLE_OAUTH_CLIENT_ID / ACME_GOOGLE_OAUTH_CLIENT_SECRET through your MDM / packaging pipeline. Unset vars resolve to empty strings (which will fail the login flow cleanly) — the app never silently falls back to an upstream client.
3. Default scopes
If you don't set googleOAuth.scopes, the app requests:
openid
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/cloud-platform
The third one is broad — it gives the app a GCP access token the OpenCode runtime can write into an ADC file for Vertex AI / BigQuery MCPs. If your downstream only needs identity verification (not GCP API access), drop it:
4. Token storage
After a successful login, refresh + access tokens are encrypted via Electron's safeStorage and stored in the app's userData directory. On platforms where safeStorage is unavailable, the app logs an error and fails the save in production — it will not fall back to plaintext. Dev/test contexts may still use plaintext for local iteration.
Open Cowork never commits OAuth credentials to the repo. The googleOAuth.clientId and googleOAuth.clientSecret fields are downstream configuration, not upstream constants.
Providers¶
providers defines: - available provider ids - provider descriptors shown in the UI - default provider and model - optional custom provider wiring
This is where downstream distributions usually customize: - model menus - default provider/model - provider descriptions - provider-specific options
Upstream Open Cowork keeps openrouter as the default provider and also ships a direct openai descriptor. Direct OpenCode-native providers can use an optional API key typed into Open Cowork, or provider auth methods such as ChatGPT Plus/Pro when the bundled OpenCode runtime exposes them. Open Cowork does not reimplement those auth flows; it opens the authorization URL returned by OpenCode and lets OpenCode persist the credential inside the managed runtime home.
For direct built-in providers, the model picker is runtime-backed: once the OpenCode runtime is running, Open Cowork overlays client.provider.list() models onto the static descriptor. That keeps OpenAI/Codex and downstream OpenCode-native provider model menus current without hardcoding every upstream model id in this repo.
Downstream builds can reuse the same path for any OpenCode-native provider. Add the provider id to providers.available, add a descriptor with "runtime": "builtin", and leave models: [] if OpenCode should own the live model catalog. If OpenCode exposes provider auth for that id through client.provider.auth(), Open Cowork will show the returned OAuth methods and call OpenCode's provider.oauth.authorize / provider.oauth.callback APIs directly; the app does not implement provider-specific login logic.
Dynamic model catalogs¶
Any provider descriptor may declare a dynamicCatalog block to pull its model list from an HTTPS endpoint at runtime. The hardcoded models[] entries stay pinned at the top of the picker as Featured models; the dynamic list is overlaid beneath them, deduplicated by id.
{
"providers": {
"descriptors": {
"openrouter": {
"name": "OpenRouter",
"credentials": [ ... ],
"defaultModel": "deepseek/deepseek-v4-flash:free",
"models": [
{ "id": "deepseek/deepseek-v4-flash:free", "name": "DeepSeek V4 Flash (free)" },
{ "id": "anthropic/claude-sonnet-4", "name": "Claude Sonnet 4" }
],
"dynamicCatalog": {
"url": "https://openrouter.ai/api/v1/models",
"responsePath": "data",
"idField": "id",
"nameField": "name",
"descriptionField": "description",
"contextLengthField": "context_length",
"sha256": "optional 64-character response hash",
"cacheTtlMinutes": 60
}
}
}
}
}
url— HTTPS JSON endpoint to fetch. Plain HTTP dynamic catalogs are rejected so model metadata cannot be rewritten in transit.sha256— optional SHA-256 hash of the exact response body. Use it when distributing catalogs from a static endpoint or release asset.responsePath— dotted path to the model array in the response body ("data"for OpenRouter, omit if the body itself is an array).idField/nameField/descriptionField/contextLengthField— field names on each model record. Defaults:id,name,description,context_length.authHeader— optionalAuthorizationheader value (use config placeholders if you need to reference a secret via{env:NAME}).cacheTtlMinutes— refresh window for the disk cache. Defaults to 60.defaultModel— optional provider-local default. When a user switches to this provider, Open Cowork picks this model if it exists in the current configured, cached, or runtime model catalog. If it is absent at that moment, the app silently falls back to OpenCode's runtime default, the app-wideproviders.defaultModel, or the first available model.
Fetches are best-effort: network failures fall back to the last cached catalog, and cache misses fall back to the hardcoded models[] alone, so the app never blocks on an unreachable endpoint. A manual Refresh button on the Models tab lets users force a refetch.
For upstream releases, the featured OpenRouter model ids in open-cowork.config.json are checked during the release audit. If a provider renames or retires a featured id between releases, the dynamic catalog can still surface the live provider list when configured, but provider-local defaultModel, app-wide providers.defaultModel, and the featured list should be updated before the next tagged release.
Any provider with a public "list models" endpoint can be wired up the same way — OpenRouter is just an example. Downstream distributions can keep models[] only (strict static behavior) or add a dynamicCatalog for discovery, without touching code.
Some OpenCode-native providers are dormant until the runtime receives a minimal provider config entry. GitHub Copilot is one example in the pinned OpenCode runtime. Use runtimeActivation: "config" for those providers:
{
"providers": {
"available": ["openrouter", "openai", "github-copilot"],
"descriptors": {
"github-copilot": {
"runtime": "builtin",
"runtimeActivation": "config",
"name": "GitHub Copilot",
"description": "Use GitHub Copilot through OpenCode's native Copilot login flow.",
"credentials": [],
"models": []
}
}
}
}
runtimeActivation: "config" does not add a credential path. It only causes Open Cowork to pass { provider: { "github-copilot": { "name": "GitHub Copilot" } } } into the managed OpenCode runtime so OpenCode can expose its own provider catalog and OAuth/device-code prompts. Leave the field unset for providers such as OpenAI where OpenCode already exposes auth methods without a name-only config entry; unnecessary activation can change how OpenCode chooses between browser login and API-key auth.
Cloud deployments must treat Copilot separately from normal BYOK API-key providers. The upstream cloud BYOK path only admits providers with declared secret credentials, so the default managed BYOK profile does not broker user Copilot credentials. A deployer-managed Copilot account or other hosted model requires an explicit cloud profile and policy decision outside this local desktop provider connection.
Config layers merge by provider id. If a downstream build inherits an upstream provider descriptor but does not want runtime activation, set runtimeActivation: "implicit" explicitly in that downstream descriptor.
Model Price And Context Overrides¶
Open Cowork reads model pricing and context windows from OpenCode's native provider catalog (provider.list) whenever the runtime exposes them. Use config only for deliberate downstream overrides or for custom providers whose catalog cannot report model metadata.
{
"providers": {
"modelInfo": {
"github-copilot/claude-sonnet-4": {
"limit": { "context": 200000 },
"cost": {
"input": 3,
"output": 15,
"cache_read": 0.3,
"cache_write": 3.75
}
}
}
}
}
The same limit and cost blocks are also accepted on entries in providers.descriptors[provider].models[] and providers.custom[provider].models. Values use OpenCode's units: USD per 1M tokens for cost.*, and tokens for limit.context. A configured model id may be either model or provider/model; Open Cowork stores both aliases so downstream bundles do not need renderer-specific glue.
Update Release Sources¶
The upstream app checks public GitHub Releases by default. Downstream builds can replace that with an explicit update release source without changing renderer code. Release-source auth is resolved only in the main process; the renderer receives a safe label, channel, and status reason, never bearer tokens, signed URLs, bucket paths, or request headers.
{
"updates": {
"enabled": true,
"manualFallbackUrl": "https://github.com/joe-broadhead/open-cowork/releases",
"releaseSource": {
"kind": "github-releases",
"owner": "joe-broadhead",
"repo": "open-cowork",
"channel": "latest",
"label": "GitHub Releases"
}
}
}
Supported releaseSource.kind values:
github-releases— public GitHub Releases by default. Downstream private GitHub feeds can setauth.kind: "github-token"and pass the token through an allowlisted config placeholder.generic-http— an HTTPS directory containing Electron updater metadata such aslatest-mac.yml. Optionalauth.kind: "static-headers"attaches private headers in the main process.gcs— a private Google Cloud Storage feed laid out asgs://<bucket>/<prefix>/<channel>/latest-mac.yml. Useauth.kind: "google-oauth"to reuse the app Google sign-in, orauth.kind: "signed-url-broker"to ask a downstream broker for a short-lived updater-compatible generic feed.
Example GCS OAuth feed:
{
"auth": {
"mode": "google-oauth",
"googleOAuth": {
"clientId": "your-google-client-id",
"scopes": [
"openid",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/devstorage.read_only"
]
}
},
"updates": {
"enabled": true,
"manualFallbackUrl": "https://support.example.test/releases",
"releaseSource": {
"kind": "gcs",
"label": "Private release feed",
"bucket": "acme-cowork-releases",
"prefix": "desktop",
"channel": "latest",
"auth": {
"kind": "google-oauth",
"requiredScopes": [
"https://www.googleapis.com/auth/devstorage.read_only"
]
}
}
}
}
Example signed URL broker:
{
"updates": {
"enabled": true,
"manualFallbackUrl": "https://support.example.test/releases",
"releaseSource": {
"kind": "gcs",
"label": "Private release feed",
"bucket": "acme-cowork-releases",
"channel": "latest",
"auth": {
"kind": "signed-url-broker",
"brokerUrl": "https://updates.example.test/cowork/broker"
}
}
}
}
Private update sources still obey the signed-install gate: in-app download and restart-to-install are available only for signed packaged macOS builds with feed metadata. Development, unsigned, unsupported, misconfigured, or unauthenticated builds stay on the manual fallback path.
Tools¶
tools defines the curated tool catalog shown in the app.
Each entry can describe: - id - name - icon - description - runtime namespace/patterns - allow/ask patterns - writeAccess: explicit mutating-capability metadata for Plan-mode delegation. Set false for approval-gated read-only tools with ambiguous names such as run_query or namespace wildcards such as mcp__charts__*; set true for mutating tools whose names do not include obvious write verbs. Namespace wildcards without matching configured-tool metadata are treated conservatively as write-capable for Plan-mode delegation.
Tool Trace Rules¶
toolTrace controls how tool calls are grouped in chat trace summaries. Downstream builds can add rules without patching renderer code:
{
"toolTrace": {
"additionalRules": [
{
"id": "ticket",
"label": "ticket action",
"pluralLabel": "ticket actions",
"match": [
{ "prefixes": ["mcp__jira__", "jira_"] }
]
}
]
}
}
Rules are evaluated in order. additionalRules run before the upstream defaults, so downstream-specific MCPs can override generic buckets. Each matcher can use exact, prefixes, and contains; when a matcher contains multiple fields, all listed fields must match. Tool ids that do not match any rule fall back to tool call.
User-added custom MCPs can also set chat trace labels from the Add MCP form. Those labels are stored in Open Cowork sidecar metadata and are scoped the same way as the MCP itself.
Skills¶
skills defines bundled, app-visible skills.
Each skill entry can map to: - a source skill directory - linked tool ids - description and badge text
MCPs¶
mcps defines bundled MCP servers shipped by the app.
The upstream core ships: - agents - charts - clock - knowledge - semantic-ui - skills - workflows
User-added MCPs are stored separately from the shipped config.
The bundled skills MCP receives OPEN_COWORK_CUSTOM_SKILLS_DIR from the app when it is spawned. That value must be an absolute app-managed directory, not a filesystem root, the user's home directory, or a downstream-controlled override.
The bundled agents MCP receives a per-runtime loopback bridge URL and bearer token from the app. It can preview and save only custom agents, using the same validation and permission-building path as the desktop UI; built-in agents remain code-owned and read-only.
Reusing the app's Google OAuth session for Google MCPs¶
When auth.mode is google-oauth and the user has signed in, the app writes a standard application_default_credentials.json file in its userData directory. Any MCP that sets googleAuth: true gets that file wired into its subprocess via the GOOGLE_APPLICATION_CREDENTIALS env var, so libraries like googleapis (Node), google-auth (Python), or the gcloud CLI authenticate without a second prompt:
{
"mcps": [
{
"name": "sheets",
"type": "local",
"description": "Google Sheets MCP",
"authMode": "none",
"packageName": "sheets",
"googleAuth": true
}
]
}
The same flag is available on user-added custom MCPs (CustomMcpConfig.googleAuth). Key details:
- Scopes must match. The access token only has the scopes listed in
auth.googleOAuth.scopes. A Sheets MCP needshttps://www.googleapis.com/auth/spreadsheets; extend your scopes list to cover every API the downstream MCP set will hit. - No sign-in, no injection. When
auth.modeisnoneor no token has been written yet,GOOGLE_APPLICATION_CREDENTIALSis NOT set — the MCP spawns as usual and fails authenticated calls cleanly (rather than silently falling through to a different identity). - Trust boundary.
googleAuthis opt-in because any MCP that receives the env var can read the user's Google access token. Only enable it for MCPs your distribution trusts.
MCP credential fields¶
Bundled MCPs can declare credentials[] metadata for the Tools & Skills detail panel. Each field persists to integrationCredentials[mcpName][key]; envSettings and headerSettings can then map those stored values into the spawned MCP. Remote MCPs with authMode: "api_token" also get a per-MCP preflight check after credentials are saved. The preflight distinguishes missing credentials, rejected tokens (401), forbidden or policy responses (403), network/proxy failures, and MCP protocol/tool-list failures. Credential changes reload the OpenCode runtime when setup is complete, so users do not need to restart the desktop app manually.
Text fields are the default. For discrete modes, set type to "select" or "radio" and provide options[]. Use when to hide a dependent field unless another credential has the expected value. Hidden fields are only hidden in the UI; their stored values are preserved and are not cleared when the selector changes.
Downstream builds can add credentialHelp to a bundled MCP to surface provider-specific guidance with preflight failures, such as required PAT scopes, SSO authorization, repository restrictions, or organization policy settings.
Remote bundled MCPs that intentionally live on an internal network can set allowPrivateNetwork: true. This preserves the normal DNS-aware SSRF guard for public builds by default; cloud metadata endpoints remain blocked even when private network access is enabled.
{
"mcps": [
{
"name": "my-mcp",
"type": "local",
"description": "Example MCP",
"authMode": "api_token",
"credentialHelp": "If this token is rejected, confirm it has the required scopes and any organization SSO authorization.",
"envSettings": [
{ "env": "MY_MCP_AUTH_METHOD", "key": "authMethod" },
{ "env": "MY_MCP_API_KEY", "key": "apiKey" },
{ "env": "MY_MCP_SSO_USER", "key": "ssoUser" }
],
"credentials": [
{
"key": "authMethod",
"label": "Authentication method",
"description": "How to authenticate with the service",
"type": "select",
"options": [
{ "label": "API key", "value": "api_key", "hint": "Static API credentials" },
{ "label": "SSO", "value": "sso", "hint": "Browser-based single sign-on" }
],
"required": true
},
{
"key": "apiKey",
"label": "API key",
"description": "Your API key",
"secret": true,
"when": { "key": "authMethod", "op": "eq", "value": "api_key" }
},
{
"key": "ssoUser",
"label": "SSO email",
"description": "Your login email address",
"when": { "key": "authMethod", "op": "eq", "value": "sso" }
}
]
}
]
}
Custom MCP approval mode¶
User-added custom MCPs default to permissionMode: "ask": agents can be assigned the MCP, but OpenCode still asks before each tool call. For MCPs you control or trust, the Tools & Skills UI can mark the MCP as trusted. That persists permissionMode: "allow" in Open Cowork's mcp.open-cowork.json sidecar metadata and generates OpenCode-native allow patterns for agents that include the MCP.
Leave the default for third-party or newly-tested MCPs. permissionMode does not bypass agent-specific denied method patterns, so maintainers can still block destructive methods even on a trusted MCP.
Agents¶
agents defines built-in product agents.
Each agent can specify: - label, description, instructions - linked skills (skillNames) and tool ids (toolIds) - allow/ask tool patterns (allowTools, askTools) - UI color, hidden, mode (primary or subagent) - inference overrides: model, variant, temperature, top_p, steps, options
Read-only configured subagents are available from both Build and Plan mode. Configured subagents with shell, file-write, or ask-only write permissions are available from Build mode only, preserving Plan mode's read-only contract.
Workflow setup has one reserved configured-agent contract: workflow-designer. When workflows are enabled, downstream builds should keep that agent id in agents; otherwise the Workflows page cannot open setup threads. If a downstream build intentionally renames or removes it, update the workflow setup policy in code and config together.
Inference overrides map directly to the SDK's AgentConfig fields. Any unset field inherits session defaults. Use these to route an agent to a different model, tune temperature per agent, or cap runaway tool loops with steps.
{
"agents": [
{
"name": "market-research",
"description": "Research analyst focused on market sizing.",
"instructions": "Use web search…",
"model": "openrouter/anthropic/claude-sonnet-4",
"temperature": 0.3,
"steps": 20
}
]
}
Overriding built-in agents¶
The configurable Cowork built-ins (build, plan, general, explore) can be tuned or silenced via builtInAgents:
{
"builtInAgents": {
"explore": {
"model": "openrouter/anthropic/claude-haiku-4-5",
"temperature": 0.2,
"steps": 30
},
"general": {
"disable": true
}
}
}
disable: true removes the agent from the runtime entirely — it will no longer appear in the UI or accept delegations. Any inference field can be set independently; unset fields keep Cowork's defaults.
Code-owned product agents, including Autoresearch, are intentionally not configured through builtInAgents; they remain read-only product policy.
Agent starter templates¶
agentStarterTemplates controls the quick-start cards shown in the agent builder. Templates are UI scaffolding only: creating or editing an agent still writes a normal OpenCode-native configured agent. Downstream builds can replace the upstream examples with company-specific roles, skills, and tool presets without forking the renderer.
{
"agentStarterTemplates": [
{
"id": "internal-analyst",
"label": "Internal analyst",
"description": "Reviews internal reports and prepares a concise brief.",
"color": "info",
"instructions": "Use approved internal MCPs and cite sources.",
"toolIds": ["internal-reports"],
"skillNames": ["analysis"],
"temperature": 0.2,
"steps": 30
}
]
}
Compaction¶
compaction controls how OpenCode handles long conversations. When the context window is close to full, OpenCode runs a summarizer agent that rewrites older turns into a shorter form so the session can continue.
{
"compaction": {
"auto": true,
"prune": true,
"reserved": 10000,
"agent": {
"model": "openrouter/anthropic/claude-sonnet-4",
"prompt": "You are a session summarizer. Keep technical details…",
"temperature": 0.2
}
}
}
auto— fire compaction automatically when the context fills. Setfalseto disable and rely on the manual Summarize now action in the context panel.prune— drop stale tool outputs during compaction so the summary stays focused on conversational turns.reserved— token budget kept free so compaction can still run near the context limit. Raise this for models with very large context windows.agent— optional overrides for the compaction agent itself. Use this to point summarization at a cheaper model, or to provide a custom system prompt. Maps to SDKConfig.agent.compaction.
Users can also trigger compaction manually from the Context tab in the app — useful when you want to preempt an imminent auto-compaction or trim a long exploratory session.
Permissions¶
permissions defines app-level default runtime policy:
{
"permissions": {
"bash": "allow",
"fileWrite": "allow",
"task": "allow",
"web": "allow",
"webSearch": true
}
}
bashandfileWriteare app-level maximum policies. The default upstream maximum isallow, which lets users choose Off, Ask, or Allow in Settings. Fresh Open Cowork profiles still default to Ask so side-effecting shell/file actions require confirmation until the user explicitly opts into Allow. Downstream builds can set either value toaskordenyto remove the no-prompt option or make the corresponding Settings control unable to grant that capability.webcontrols OpenCode's nativewebfetchandcodesearchpermissions.webSearchcontrols OpenCode's nativewebsearchpermission and enables OpenCode's Exa-backed native search integration for non-OpenCode providers by settingOPENCODE_ENABLE_EXA=1in the managed runtime. OpenCode does not require an Exa API key for this, but search queries are sent to Exa's hosted service.
Localization and telemetry¶
Downstream builds can ship a partial localization overlay:
{
"i18n": {
"locale": "de-DE",
"strings": {
"home.greeting": "Woran soll {{brand}} heute arbeiten?"
}
}
}
Unset strings fall back to the built-in English copy. locale controls Intl.NumberFormat and Intl.DateTimeFormat output.
Upstream Open Cowork keeps telemetry local on disk. Downstream builds that need a remote collector can enable:
{
"allowedEnvPlaceholders": ["ACME_TELEMETRY_TOKEN"],
"telemetry": {
"enabled": true,
"endpoint": "https://events.acme.example/ingest",
"headers": {
"Authorization": "Bearer {env:ACME_TELEMETRY_TOKEN}"
}
}
}
Telemetry payloads are sanitized for secrets and home-directory paths before local write or remote forwarding. Remote forwarding is best-effort and uses a 2-second timeout.
Downstream customization model¶
If you are preparing a custom internal build, the normal path is:
- copy and edit
open-cowork.config.json - ship your own branding
- add bundled MCPs or skills
- adjust provider and agent definitions
The goal is to keep product customization in config and content, not buried in code.
See Downstream Customization for the full reference — merge order, environment variables, skill/MCP overlay resolution, and a worked example of a branded internal distribution.