Context
The forge settings surface (#454) landed with the file-based managed mechanism: a fixed OS-path managed-settings.json (+ managed-settings.d/) loaded at the top of precedence, non-developer-overridable (forge-core/settings/layers.go — loadManagedLayer, ManagedSettingsPath). Claude Code additionally supports a server-managed mechanism: "the servers/gateway hold the policy; the client keeps a local cache it applies at startup and replaces on each successful fetch." Forge should gain the same, because the initializ platform already has a control plane that injects policy server-side — server-managed settings are the natural client-side counterpart, letting an org manage the developer surface centrally without dropping files on every machine.
This is the last of the managed delivery mechanisms noted on #454 (the others — macOS configuration profile, Windows HKLM/HKCU registry — are separate follow-ups). Server-managed is the highest-value one here given the existing control plane.
Goal
Add a server-managed settings source in the managed tier: fetch the org's Settings JSON from a control-plane endpoint at startup, cache it locally, apply it at managed precedence, and replace the cache on each successful fetch. Non-developer-overridable, like the file-based managed layer.
Design
Reuse the existing platform-callout pattern. Forge already calls the control plane for admission (FORGE_ADMISSION_URL), the PDP, the remote session store, and the MCP platform token — all sending Org-Id + Workspace-Id (from FORGE_ORG_ID/FORGE_WORKSPACE_ID) + Authorization: Bearer ${FORGE_PLATFORM_TOKEN} + X-Workload-Token (#444). The settings fetch should use the same headers/contract (see forge-cli/runtime/admission_engine.go / forge-core/mcp/platform_token.go for the shape). Endpoint via a new env, e.g. FORGE_SETTINGS_URL (engaged only when set, mirroring admission's opt-in).
Response shape. The endpoint returns the same settings.Settings JSON (channels/models/tools/skills/env) — no new schema; the server-managed layer is just another source feeding the existing merge/lock machinery.
Loader integration (forge-core/settings):
- Add the server-managed source to
LoadAllLayers in the managed tier. Decide precedence within the managed tier vs the file-based managed-settings.json (Claude Code documents an explicit order — pick one, e.g. server-managed over file, or file over server; state it). A managed available_models lock from either must remain authoritative.
- Cache: persist the last successful fetch to a local file (e.g.
~/.forge/managed-settings.cache.json, 0600). At startup: fetch → on success, apply + overwrite cache; on failure, fall back to the cached copy (last-known-good).
Failure semantics (decide + document). Because managed settings can carry a lock (models.available_models), a failed fetch with no cache must not silently widen what the org allows. Options to settle: (a) no server-managed layer when both fetch and cache are absent (consistent with file-absent) — simplest; (b) fail-closed / refuse to start when a fetch was expected (env set) but unavailable and uncached. Recommend (a) with a clear startup log, since the authoritative security enforcement is server-side policy anyway (settings are the developer surface).
Non-overridability. The endpoint + cache path are not developer-redirectable in production (same principle as the fixed file path — FORGE_SETTINGS_URL is an operator/deploy-time env like FORGE_ADMISSION_URL, not a per-developer override; the cache is 0600 and treated as managed-tier).
Acceptance
Related
Context
The forge settings surface (#454) landed with the file-based managed mechanism: a fixed OS-path
managed-settings.json(+managed-settings.d/) loaded at the top of precedence, non-developer-overridable (forge-core/settings/layers.go—loadManagedLayer,ManagedSettingsPath). Claude Code additionally supports a server-managed mechanism: "the servers/gateway hold the policy; the client keeps a local cache it applies at startup and replaces on each successful fetch." Forge should gain the same, because the initializ platform already has a control plane that injects policy server-side — server-managed settings are the natural client-side counterpart, letting an org manage the developer surface centrally without dropping files on every machine.This is the last of the managed delivery mechanisms noted on #454 (the others — macOS configuration profile, Windows HKLM/HKCU registry — are separate follow-ups). Server-managed is the highest-value one here given the existing control plane.
Goal
Add a server-managed settings source in the managed tier: fetch the org's
SettingsJSON from a control-plane endpoint at startup, cache it locally, apply it at managed precedence, and replace the cache on each successful fetch. Non-developer-overridable, like the file-based managed layer.Design
Reuse the existing platform-callout pattern. Forge already calls the control plane for admission (
FORGE_ADMISSION_URL), the PDP, the remote session store, and the MCP platform token — all sendingOrg-Id+Workspace-Id(fromFORGE_ORG_ID/FORGE_WORKSPACE_ID) +Authorization: Bearer ${FORGE_PLATFORM_TOKEN}+X-Workload-Token(#444). The settings fetch should use the same headers/contract (seeforge-cli/runtime/admission_engine.go/forge-core/mcp/platform_token.gofor the shape). Endpoint via a new env, e.g.FORGE_SETTINGS_URL(engaged only when set, mirroring admission's opt-in).Response shape. The endpoint returns the same
settings.SettingsJSON (channels/models/tools/skills/env) — no new schema; the server-managed layer is just another source feeding the existing merge/lock machinery.Loader integration (
forge-core/settings):LoadAllLayersin the managed tier. Decide precedence within the managed tier vs the file-basedmanaged-settings.json(Claude Code documents an explicit order — pick one, e.g. server-managed over file, or file over server; state it). A managedavailable_modelslock from either must remain authoritative.~/.forge/managed-settings.cache.json,0600). At startup: fetch → on success, apply + overwrite cache; on failure, fall back to the cached copy (last-known-good).Failure semantics (decide + document). Because managed settings can carry a lock (
models.available_models), a failed fetch with no cache must not silently widen what the org allows. Options to settle: (a) no server-managed layer when both fetch and cache are absent (consistent with file-absent) — simplest; (b) fail-closed / refuse to start when a fetch was expected (env set) but unavailable and uncached. Recommend (a) with a clear startup log, since the authoritative security enforcement is server-side policy anyway (settings are the developer surface).Non-overridability. The endpoint + cache path are not developer-redirectable in production (same principle as the fixed file path —
FORGE_SETTINGS_URLis an operator/deploy-time env likeFORGE_ADMISSION_URL, not a per-developer override; the cache is0600and treated as managed-tier).Acceptance
FORGE_SETTINGS_URLset, forge fetchesSettingsfrom the control plane at startup (withOrg-Id/Workspace-Id/Bearer/X-Workload-Token), applies it in the managed tier, and caches it.available_modelslock stays authoritative.forge settingsshows the server-managed layer (source = control plane) alongside the file layers.Related
X-Workload-Token+ tenancy headers (Agent identity L1–L4: forge runtime work to complete the capability (platform side shipped) #444 / feat(identity): present per-agent workload token on platform callouts (#444 item 1) #445).