Skip to content
Download PDF

Configuration ​

Forge follows a zero-config default, full override philosophy. It works with no configuration at all — but when you need to change something, everything is overridable through a single optional file: .forge/config.yaml.

One file, all optional

.forge/config.yaml is the single optional per-project override file. Every section and every key in it is optional. Omit what you do not need, and Forge uses its built-in defaults.

Resolution order ​

Forge resolves configuration in four layers. Later layers win:

  1. Built-in defaults — baked into the binary.
  2. .forge/config.yaml — per-project overrides, committed with the repo.
  3. .forge/config.local.yaml — per-machine overrides, gitignored.
  4. Environment variables and per-invocation CLI flags — highest precedence.

See where a value came from

Run forge config explain to print the fully resolved configuration with the origin layer annotated for every value — so you always know whether a setting came from a default, the project file, the local file, or an env var.

Schema version ​

schema_version is the only top-level scalar key — it is not a section. It declares which version of the config schema the file is written against.

  • schema_version (int) — current version is 1. Omit it and Forge assumes 1; there is no need to set it until a future schema revision exists.
yaml
schema_version: 1

Sections ​

All sections below are optional.

SectionPurpose
workflowTDD, adaptive depth, forced depth, phase overrides
agentsWhich agents are configured and at what scope
repoGit-hosting provider, self-hosted hosts, related repos
memoryBackends for spec memory, codebase memory, team sync
learnContinuous-learning ledger: candidate threshold, auto-approve
budgetSpend ceilings enforced against the per-change budget ledger
personaThe assistant's voice and language
docsiteforge docsite publish target, output, sections — not install/dev/build
modelsPer-phase model/provider assignments
mcpMCP servers to expose
reviewReview gate and provider
guardrailsPermission posture injected into each agent (level); other keys reserved
extensionsInstalled extensions

workflow ​

  • strict_tdd (bool) — enforce tests-first. Default: true.
  • adaptive_depth (bool) — let Forge triage per-change depth. Default: true.
  • force_depth (trivial | standard | full) — pin depth for all changes. Default: unset — depth is decided per-change by adaptive_depth instead.
  • phases (list) — override the phase list. Default: unset — Forge uses its built-in spine (explore, propose, specify, clarify, plan, tasks, implement, verify, archive).
  • breaker_threshold (int) — how many consecutive forge verify failures open the per-change circuit breaker. Default: unset, which resolves to 3. forge verify --max-failures <n> overrides it for one run. See The Circuit Breaker.
yaml
workflow:
  strict_tdd: true               # default: true
  adaptive_depth: true           # default: true
  force_depth: standard          # default: unset (trivial|standard|full)
  breaker_threshold: 5           # default: unset -> 3
  phases:                        # default: unset -> built-in spine
    - explore
    - propose
    - specify
    - clarify
    - plan
    - tasks
    - implement
    - verify
    - archive

agents ​

  • scope (global | workspace) — where agent config is written. Default: global.
  • enabled (list) — restrict to specific agents. Default: unset — every detected/installed agent is enabled.
yaml
agents:
  scope: workspace               # default: global
  enabled:                       # default: unset (all detected agents)
    - claude-code
    - cursor
    - opencode

repo ​

Git-hosting context and related repositories. All optional — with no repo: section, the provider is auto-detected from the git remote. See Git Providers & Related Repos.

  • provider (gitlab | github | gitea | bitbucket | generic) — force the provider, overriding remote-based auto-detection. Default: unset (auto-detect from the git remote).
  • hosts (map) — map a self-hosted host to a provider kind, e.g. gitlab.example.com: gitlab. Default: unset (empty map).
  • related (list) — sibling repos in a multi-repo project; each takes name, and a path (local checkout) or url, plus an optional provider override (else derived from the url/path). Default: unset (no related repos).
yaml
repo:
  provider: ""                   # empty = auto-detect from the remote
  hosts:
    gitlab.example.com: gitlab
  related:
    - name: api
      path: ../api
    - name: web
      url: https://github.com/acme/web

memory ​

  • spec (engram) — backend for spec memory. Default: engram.
  • codebase (engram | graphify | graphiti) — backend for codebase memory. Default: engram.
  • team_sync (bool) — enable committable team memory export. Default: unset — detected as on iff the repo has a git remote.
yaml
memory:
  spec: engram                   # default: engram
  codebase: engram               # default: engram
  team_sync: true                # default: unset -> detected (on iff repo has a remote)

learn ​

  • threshold (int) — pending-candidate ceiling used by forge learn review / promote to flag candidates as eligible. Default: unset (no ceiling — <= 0 also means no ceiling).
  • auto_approve (bool) — allow forge learn promote to write without an explicit --approve. Default: false, so promotion stays human-gated.
yaml
learn:
  threshold: 20                  # default: unset (no ceiling)
  auto_approve: false            # default: false

budget ​

Ceilings the per-change spend ledger is enforced against. Each dimension is independent, and each is opt-in: unset (or <= 0) means no ceiling for that dimension — the ledger still accumulates, it just never blocks. The matching --max-* flag on forge budget record / forge budget check overrides the configured value for one run.

  • max_tokens (int) — cap on the change's cumulative reported tokens. Default: unset (no ceiling).
  • max_usd (float) — cap on the change's cumulative reported dollar spend. Default: unset (no ceiling). forge run has no flag for this one, so a ceiling here is the only way to enforce it inside the run loop.
  • max_tool_calls (int) — cap on the change's cumulative reported tool calls. Default: unset (no ceiling).

Reaching exactly a limit is within budget; strictly over it blocks. See The Spend Budget.

yaml
budget:
  max_tokens: 500000             # default: unset (no ceiling)
  max_usd: 25.00                 # default: unset (no ceiling)
  max_tool_calls: 2000           # default: unset (no ceiling)

persona ​

Selects the standing persona and conversation language.

  • mode (forge | forge-neutral-artifacts | neutral | custom) — the persona to apply. Defaults to neutral.
  • language (string) — the conversation language, as a language code. Defaults to en.

The key is mode, not id

There is no persona.id key. A persona.id: entry is silently ignored by the resolver — the persona is selected with persona.mode.

The persona modes and the replies-vs-artifacts distinction are documented under Personas. The --persona flag on forge install overrides this for a single run.

yaml
persona:
  mode: forge
  language: en

docsite ​

Controls forge docsite publish — rendering forge's .forge/ state into a documentation site you already own. Every field is optional; with no docsite: section the generator and output directory are detected from files already on disk.

This section is publish-only — it has no install-time meaning

forge docsite install/dev/build scaffold and run a separate generator toolchain inside .forge/ itself (a different site from the one publish renders into). Which target is installed lives entirely in .forge/.docsite-install.json, written by install — not here. Setting docsite.target does not select what install scaffolds, and install never reads or writes this section. There are zero new config keys for install/dev/build: the manifest is the sole authority for what is installed, and dev/build always read it, never this config.

  • target (auto | vitepress | docusaurus | mintlify | starlight | mkdocs | neutral) — which generator to emit for. Default: auto, which detects the generator from disk and falls back to neutral.
  • out (string) — output directory, relative to the repo root. Default: unset, so it is derived from detection.
  • pattern (string) — the information-architecture pattern for the generated site. Default: auto, which picks one from the project type.
  • sections (list) — top-level sections to generate, in order. Default: [guide, reference, decisions].
  • scaffold (bool) — let publish write the generator's config file when it is missing. Default: false, and never applies under target: auto. This gate is publish-only: install always scaffolds its own template set unconditionally, regardless of this key — running install is itself the explicit consent this gate exists to require of publish.
  • llms_txt (bool) — also emit /llms.txt alongside the site. Default: true.
  • auto_on_archive (bool) — regenerate the site during the archive phase. Default: false.
  • exclude (list) — glob patterns kept out of the source scan. Default: ["**/*_test.go", "vendor/**", "node_modules/**"].
yaml
docsite:
  target: vitepress               # default: auto
  out: docs                       # default: unset (derived from detection)
  pattern: auto                   # default: auto
  sections:                       # default: [guide, reference, decisions]
    - guide
    - reference
    - decisions
  scaffold: true                  # default: false
  llms_txt: true                  # default: true
  auto_on_archive: false          # default: false
  exclude:                        # default: ["**/*_test.go", "vendor/**", "node_modules/**"]
    - "**/*_test.go"
    - "vendor/**"
    - "node_modules/**"

models ​

A per-phase map assigning a provider/model to phases such as explore, design, and implement. Default: unset (empty map) — each phase uses the coding agent's own default model unless a phase key here overrides it.

yaml
models:                           # default: unset (empty map)
  explore: anthropic/claude-opus
  design: anthropic/claude-opus
  implement: anthropic/claude-sonnet

mcp ​

  • servers (list) — MCP servers to expose. Default: [context7].
yaml
mcp:
  servers:                        # default: [context7]
    - context7
    - filesystem

review ​

  • gate (off | pre-commit | pre-push | ci) — when review runs. Any other value is treated as off, so the gate fails closed rather than activating an unknown mode. The init wizard's Review Gate step offers exactly these four modes. Default: off.
  • provider — the review provider. Default: unset.
yaml
review:
  gate: pre-push                  # default: off (off|pre-commit|pre-push|ci)
  provider: anthropic             # default: unset

guardrails ​

What guardrails controls today

guardrails is carried through the resolver untyped (an arbitrary key/value map), and one key in it is enforced: the guarded-vs-bypass posture that Forge injects into each agent's own permission config during install and sync. Every other key in this section is still reserved for a later phase.

  • level (off | guarded | strict | bypass) — the permission posture. This is the key the forge init wizard writes. Default: unset, which resolves to the guarded posture.
  • default_mode (guarded | bypass) — an accepted alias for level, so a file written against the published PRD schema works unchanged. Default: unset.

Only the value bypass changes behavior, and only when a config layer sets it explicitly — it is never selected automatically. Every other value, including an absent guardrails section, resolves to the guarded fail-safe posture:

  • Guarded (default) — each agent is asked to confirm tool calls: Claude Code gets defaultMode: default, OpenCode/Kilocode get bash: "*": ask, and Codex gets approval_policy: on-request.
  • Bypass — restores auto-accept (Claude Code bypassPermissions, Codex approval_policy: never) and, on Claude Code, installs a blocking PreToolUse hook for destructive git commands as the substitute for the confirmation that auto-accept removes.

The secrets deny-list overlay is injected in both postures. Other keys — posture, secrets.deny, destructive_actions — are accepted and carried through but not read; they illustrate the intended future shape only.

The wider guardrails story (the deny-list contents, the destructive-action hook, and forge secscan) is documented under The Guardrails Gate.

yaml
guardrails:
  level: guarded                  # default: unset -> guarded
  secrets:
    deny:
      - AWS_SECRET_ACCESS_KEY
      - STRIPE_SECRET_KEY

extensions ​

  • A list of { id, version } entries for installed extensions. Default: unset (empty list — no extensions installed).
yaml
extensions:                       # default: unset (empty list)
  - id: forge-graphql
    version: 1.2.0
  - id: forge-terraform
    version: 0.4.1

Complete example ​

The following config.yaml shows non-default values across every section. Copy it as a starting point and delete anything you do not need — remember that every key is optional.

yaml
# .forge/config.yaml — every section and key is optional.

schema_version: 1               # config schema version (current: 1)

workflow:
  strict_tdd: true              # tests-first (default: true)
  adaptive_depth: true          # triage depth per change (default: true)
  force_depth: standard         # pin all changes to a depth: trivial|standard|full
  breaker_threshold: 5          # consecutive verify failures that open the breaker (unset = 3)
  phases:                       # override the phase list (advanced)
    - explore
    - propose
    - specify
    - clarify
    - plan
    - tasks
    - implement
    - verify
    - archive

agents:
  scope: workspace              # global|workspace (default: global)
  enabled:                      # restrict to specific agents
    - claude-code
    - cursor
    - opencode

repo:                           # omit entirely to auto-detect from the remote
  provider: gitlab              # gitlab|github|gitea|bitbucket|generic
  hosts:                        # map self-hosted hosts to a provider kind
    gitlab.example.com: gitlab
  related:                      # sibling repos in a multi-repo project
    - name: api
      path: ../api
    - name: web
      url: https://github.com/acme/web

memory:
  spec: engram                  # spec memory backend
  codebase: graphify            # engram|graphify|graphiti
  team_sync: true               # export committable team memory

learn:
  threshold: 20                 # pending-candidate ceiling (unset or <=0 = none)
  auto_approve: false           # keep `forge learn promote` human-gated (default)

budget:                         # per-change spend ceilings (unset or <=0 = none)
  max_tokens: 500000            # token ceiling
  max_usd: 25.00                # dollar ceiling
  max_tool_calls: 2000          # tool-call ceiling

persona:
  mode: forge                   # persona mode — see /guide/components#personas
  language: en

docsite:                        # `forge docsite publish` only — install/dev/build read
                                 # .forge/.docsite-install.json instead, never this section
  target: vitepress             # auto|vitepress|docusaurus|mintlify|starlight|mkdocs|neutral
  out: docs                     # output dir (default: derived from detection)
  pattern: auto                 # information-architecture pattern
  sections:                     # default: [guide, reference, decisions]
    - guide
    - reference
    - decisions
  scaffold: true                # write the generator config if missing (default: false)
  llms_txt: true                # also emit /llms.txt (default: true)
  auto_on_archive: false        # regenerate during archive (default: false)
  exclude:                      # kept out of the source scan
    - "**/*_test.go"
    - "vendor/**"
    - "node_modules/**"

models:                         # per-phase provider/model assignments
  explore: anthropic/claude-opus
  design: anthropic/claude-opus
  implement: anthropic/claude-sonnet

mcp:
  servers:                      # default: [context7]
    - context7
    - filesystem

review:
  gate: pre-push                # off|pre-commit|pre-push|ci
  provider: anthropic

guardrails:
  level: guarded                # off|guarded|strict|bypass; only bypass changes
                                # the permissions injected into each agent
  posture: ask                  # reserved: carried through, not read today
  secrets:
    deny:
      - AWS_SECRET_ACCESS_KEY
      - STRIPE_SECRET_KEY
  destructive_actions: ask      # reserved: carried through, not read today

extensions:
  - id: forge-graphql
    version: 1.2.0
  - id: forge-terraform
    version: 0.4.1

Local overrides are per-machine

.forge/config.local.yaml is gitignored on purpose. Use it for machine-specific settings (a local model endpoint, a personal provider key selection) that should never be shared with the team. Anything meant for the whole team belongs in .forge/config.yaml.

Environment variables ​

Select settings can be overridden at the highest precedence for CI or one-off runs — with a per-invocation CLI flag or a FORGE_* environment variable — without editing any file. Because these win over every file layer, they are ideal for temporarily forcing behavior in a pipeline. For example:

sh
forge install --scope workspace           # override install scope for one run
forge install --channel beta              # override release channel for one run
FORGE_ACTOR="ci-bot" forge trace show --change c1   # attribute events in CI
FORGE_BUDGET_MAX_TOKENS=200000 forge run --change c1  # tighten the spend ceiling for one pipeline

Any two-level config key maps to a FORGE_* variable by upper-casing the path and replacing the dot with an underscore: workflow.breaker_threshold becomes FORGE_WORKFLOW_BREAKER_THRESHOLD, budget.max_usd becomes FORGE_BUDGET_MAX_USD.

The FORGE_INIT_* variables (FORGE_INIT_AGENTS, FORGE_INIT_PRESET, FORGE_INIT_SCOPE, FORGE_INIT_YES, …) drive forge init headlessly. For the full flag-and-environment surface, see the CLI reference.

Next steps ​