Skip to content

[Feature]: forge settings surface — user + managed (MDM) layers for enabled channels / models / tools / gateway #454

Description

@initializ-mk

Problem

What forge offers for tools, models, and channels during forge init / forge run / forge try is pre-built / hardcoded, with no configurable settings surface — and no way for an enterprise to centrally set defaults or constrain the offered set on a fleet:

  • Channels are a hardcoded factory switch — createPlugin() (forge-cli/cmd/channel.go:236) + defaultRegistry() (:250) enumerate slack/telegram/msteams; adding/removing an offered channel is a code change.
  • Builtin tools offered in the quickstart are a hardcoded list — quickstartPreset() (forge-cli/cmd/try.go:248): BuiltinTools: []string{"http_request","datetime_now","math_calculate"}.
  • Model options come from CLI flags with baked-in provider names — --provider openai|anthropic|gemini|ollama (try.go:56), and the wizard's provider choices in forge init.
  • The model gateway endpoint (base_url / auth_scheme / auth_header_name, ModelRef, forge-core/types/config.go:281) is set per-agent in forge.yaml, not injectable from a machine/org setting.

There is no user-level or org-level (MDM) settings layer that configures which channels/models/tools are enabled or defaulted, or that injects the gateway endpoint centrally.

What exists today (and what's missing)

Forge already has a three-layer policy system — forge-core/security/platform_policy_layers.go: LayerSystem (/etc/forge/policy.yaml), LayerUser (~/.forge/policy.yaml), LayerWorkspace — with FirstLayerDenyingTool / FirstLayerForbiddingModel / FirstLayerDenyingChannel / egress-max and a one-way tighten ratchet.

But that is a deny/restrict construct (a layer can forbid a tool/model/channel at runtime). It is not an enablement/config/defaults construct: it can't say "these are the channels offered in forge init," "this is the default model," "this is the gateway URL," or "this org's fleet may only use these models." The offered/default surface is hardcoded (above).

Goal

Add a layered settings surface for forge, mirroring Claude Code settings + managed settings: a user layer and a managed (enterprise, MDM-droppable) layer, so enabled channels, enabled model options / providers / gateway endpoint, enabled builtin tools, and defaults all become configurable — and init / run / try read the offered/default/enabled set from settings instead of hardcoding it.

Precedence (mirror Claude Code, highest → lowest)

  1. Managed settings — org, non-overridable (with a security-stricter-wins exception for restrictive keys). OS system dirs + MDM + drop-in dir:
    • macOS: /Library/Application Support/forge/managed-settings.json (+ MDM config profile domain), Linux/WSL: /etc/forge/managed-settings.json, Windows: C:\Program Files\forge\managed-settings.json; plus managed-settings.d/*.json merged alphabetically.
  2. Command line — forge --settings <file> / existing flags for one session.
  3. Project local — .forge/settings.local.json.
  4. Shared project — .forge/settings.json.
  5. User — ~/.forge/settings.json.

Configurable surface (initial set)

Key Controls Replaces today's hardcode
channels.enabled (+ available) which channel adapters forge init/run/channel add offer/enable createPlugin/defaultRegistry switch
models.availableModels (allowlist) + models.default + models.providers.enabled which providers/models are offered + the default; managed layer can lock the allowlist (Claude Code's availableModels semantic) --provider flag enum + wizard choices
models.gateway (base_url, auth_scheme, auth_header_name) inject the model gateway endpoint centrally (per-user or org) per-agent forge.yaml ModelRef only
tools.builtins.enabled which builtin tools are offered/defaulted quickstartPreset list
env, defaults for egress posture, etc. fleet defaults —

Settings vs policy — SEPARATE surfaces (decided)

Settings and the existing policy.yaml layers stay separate constructs — they are different surfaces with different owners and delivery paths:

Settings (this issue) Policy (platform_policy_layers.go, exists)
Role Positive: config / enablement / defaults / gateway injection — what's offered Negative: deny / restrict / tighten — what's forbidden
Surface Developer surface management (client-side) Server surface equivalent — the runtime deny enforcement
Delivery user ~/.forge/settings.json → managed managed-settings.json (MDM / system dir) injected server-side via the control plane (platform policy, already wired), AND can be delivered client-side alongside managed-settings.json in the same system dir / drop-in
Semantics list keys merge; managed can additionally lock an allowlist (availableModels-style) one-way tighten ratchet; stricter layer wins

So: do NOT fold policy deny-keys into settings. Settings manages the developer/client surface (offered/enabled/defaulted channels, models, tools, gateway); policy remains the server/control-plane deny layer. They're complementary — an org can ship managed-settings.json (developer-surface constraints + defaults) next to a policy.yaml (deny) on the fleet, while the control plane injects the same policy server-side. The two loaders stay independent; a managed lock in settings (positive allowlist) is the counterpart to a policy deny (negative), and both can be present.

List keys merge across settings layers (like Claude Code's permissions.allow); allowlist/lock keys and the managed layer follow "managed wins / stricter wins." Policy keeps its own existing merge/tighten semantics — unchanged by this issue.

Deliverables

  • Settings schema ($schema-published, like Claude Code) + a loader with the 5-layer precedence, list-merge, and managed-lock semantics; OS system-dir + drop-in + MDM discovery.
  • Wire forge init, forge run, forge try, and forge channel to read the offered/enabled/default sets (channels, models/providers, builtins, gateway endpoint) from resolved settings instead of hardcoded lists/flags.
  • Managed non-overridability + security-stricter-wins exceptions; a forge settings inspect/verify command (mirror Claude Code's precedence-debug affordance).
  • Docs: a settings + managed-settings reference under docs/, and how it relates to docs/security/platform-policy.md (the deny layers).

Acceptance

  • A user ~/.forge/settings.json can set the default model, enabled channels, enabled builtins, and the model gateway base_url/auth_scheme; init/run/try honor it.
  • A managed managed-settings.json (system dir / MDM) overrides user/project values and can lock the model allowlist so --model/forge.yaml can't select outside it.
  • Removing a channel from channels.enabled (managed) makes forge run --with <that> refuse it, and forge channel add not offer it.
  • The offered set in init/try is no longer hardcoded — it derives from resolved settings.
  • Precedence + merge + managed-lock covered by tests; a forge settings command shows the effective value + which layer set it.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions