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:
- Built-in defaults — baked into the binary.
.forge/config.yaml— per-project overrides, committed with the repo..forge/config.local.yaml— per-machine overrides, gitignored.- 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 is1. Omit it and Forge assumes1; there is no need to set it until a future schema revision exists.
schema_version: 1Sections
All sections below are optional.
| Section | Purpose |
|---|---|
workflow | TDD, adaptive depth, forced depth, phase overrides |
agents | Which agents are configured and at what scope |
repo | Git-hosting provider, self-hosted hosts, related repos |
memory | Backends for spec memory, codebase memory, team sync |
learn | Continuous-learning ledger: candidate threshold, auto-approve |
budget | Spend ceilings enforced against the per-change budget ledger |
persona | The assistant's voice and language |
docsite | forge docsite publish target, output, sections — not install/dev/build |
models | Per-phase model/provider assignments |
mcp | MCP servers to expose |
review | Review gate and provider |
guardrails | Permission posture injected into each agent (level); other keys reserved |
extensions | Installed 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 byadaptive_depthinstead.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 consecutiveforge verifyfailures open the per-change circuit breaker. Default: unset, which resolves to3.forge verify --max-failures <n>overrides it for one run. See The Circuit Breaker.
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
- archiveagents
scope(global|workspace) — where agent config is written. Default:global.enabled(list) — restrict to specific agents. Default: unset — every detected/installed agent is enabled.
agents:
scope: workspace # default: global
enabled: # default: unset (all detected agents)
- claude-code
- cursor
- opencoderepo
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 takesname, and apath(local checkout) orurl, plus an optionalprovideroverride (else derived from the url/path). Default: unset (no related repos).
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/webmemory
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.
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 byforge learn review/promoteto flag candidates as eligible. Default: unset (no ceiling —<= 0also means no ceiling).auto_approve(bool) — allowforge learn promoteto write without an explicit--approve. Default:false, so promotion stays human-gated.
learn:
threshold: 20 # default: unset (no ceiling)
auto_approve: false # default: falsebudget
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 runhas 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.
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 toneutral.language(string) — the conversation language, as a language code. Defaults toen.
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.
persona:
mode: forge
language: endocsite
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 toneutral.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) — letpublishwrite the generator's config file when it is missing. Default:false, and never applies undertarget: auto. This gate ispublish-only:installalways scaffolds its own template set unconditionally, regardless of this key — runninginstallis itself the explicit consent this gate exists to require ofpublish.llms_txt(bool) — also emit/llms.txtalongside 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/**"].
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.
models: # default: unset (empty map)
explore: anthropic/claude-opus
design: anthropic/claude-opus
implement: anthropic/claude-sonnetmcp
servers(list) — MCP servers to expose. Default:[context7].
mcp:
servers: # default: [context7]
- context7
- filesystemreview
gate(off|pre-commit|pre-push|ci) — when review runs. Any other value is treated asoff, 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.
review:
gate: pre-push # default: off (off|pre-commit|pre-push|ci)
provider: anthropic # default: unsetguardrails
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 theforge initwizard writes. Default: unset, which resolves to the guarded posture.default_mode(guarded|bypass) — an accepted alias forlevel, 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 getbash: "*": ask, and Codex getsapproval_policy: on-request. - Bypass — restores auto-accept (Claude Code
bypassPermissions, Codexapproval_policy: never) and, on Claude Code, installs a blockingPreToolUsehook 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.
guardrails:
level: guarded # default: unset -> guarded
secrets:
deny:
- AWS_SECRET_ACCESS_KEY
- STRIPE_SECRET_KEYextensions
- A list of
{ id, version }entries for installed extensions. Default: unset (empty list — no extensions installed).
extensions: # default: unset (empty list)
- id: forge-graphql
version: 1.2.0
- id: forge-terraform
version: 0.4.1Complete 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.
# .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.1Local 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:
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 pipelineAny 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
- The Workflow — what the
workflowsection controls. - Agents — the agents the
agentssection enables. - Commands — including
config explain. - CLI reference — every flag and its environment-variable equivalent.
- Best Practices — recommended settings.