Reference · page 2 of 6
Every key with its default, and how packs and your file layer together.
foreman.config.json sits at the root of your project and is committed, so the whole team runs the same factory. This page lists every key it may hold, what each one does, its default and the rule foreman config check applies to it. Most people set a dozen keys and leave the rest. You need this page when foreman config check complains, when a pack’s defaults surprise you, or when you want to know exactly what a step will do before the first release.
foreman config show prints your file with every default filled in, which is the quickest way to see the result of the layering below. foreman doctor runs the same check first. While a run is going and you break the file, the run keeps the last config that passed and foreman status says so; every other command refuses the invalid file until you fix it. See Every command for both commands.
How the layers combine
Foreman resolves the file in three layers, each merged over the one before:
- Foreman’s defaults, listed on this page.
- The defaults of the pack your file names with
pack, if any. Today the only pack issalesforce, and its defaults are on Salesforce config. A file withoutpackgets layers 1 and 3 only. - Your file.
Plain objects merge key by key and the later layer wins. Arrays replace rather than join: your checks list is the whole list, and so is your fileLocks.ignore.
Seven blocks replace as a whole at every layer, because half a default would be wrong: roles.builders, resources, stories, env, ship, notify and console. A project that names its own builders does not also get developer; an adapter’s options belong to that adapter alone; a project’s ship replaces the pack’s as the pack’s replaces Foreman’s. So a ship block of your own must carry its own steps, and a stories block of your own must carry its adapter. The team layer of a setting knows this: foreman settings set ship.ci.timeoutMinutes 45 --team starts the ship block from its defaults when your file has none, so the default steps are not lost.
The pack’s own block (salesforce) is not one of the seven. Your values win key by key over the pack’s defaults for it.
On top of the resolved file sits the local settings layer, .foreman/settings.json, which holds the settings you change on one machine. It is not part of the file and never committed. Settings and switches says which keys are also settings.
A key the validator does not know is an error, so a typo such as slot shows up instead of being ignored. Every error names its JSON path, such as checks[1].when, and foreman config check prints them all at once.
Top level
| Key | What it does | Default | Rule |
|---|---|---|---|
version |
The config format version, so a later Foreman can migrate old files. | none | Required; must be 1. |
project.name |
The project’s name, used in notification subjects and the dashboard. | none | Required; a non-empty string. project holds only name. |
pack |
Turns a pack on and adds its defaults as layer 2. The pack’s own block, the top-level key of the same name, is then allowed and checked by the pack. | none | Must name an installed pack; today salesforce. |
baseBranch |
The branch stories are cut from and, unless ship.pr.into says otherwise, merged into. |
main |
Non-empty string. |
remote |
The git remote slots are cut from and story branches are pushed to. | origin |
Non-empty string. |
branchPrefix |
The start of every story branch name, followed by the story id in lower case, such as story/acme-12. |
story/ |
Non-empty string. |
stateDir |
The state folder, where Foreman keeps everything it writes for a run. FOREMAN_STATE_DIR in the environment overrides it. See Files Foreman writes. |
.foreman |
Non-empty string. |
envFile |
The project’s env file, holding the project’s own keys. It is linked into every slot worktree so the code under test finds its variables. | .env.local |
Non-empty string. |
foremanEnvFile |
Foreman’s own keys (the Jira sign-in, the Resend key, the dashboard token). Never linked or copied into a slot, so no agent can read it. | .env.foreman |
Non-empty string; must not name the same file as envFile. |
slots |
How many repo copies (git worktrees) Foreman keeps, one per story building. Also a setting; a change needs a restart. | 1 |
A whole number from 1 to 8. The Salesforce pack lowers the top to one slot per build org. |
workers |
How many of those slots build at the same time. Also a setting. | 1 |
A whole number from 1 to 8 and not more than slots. |
rounds |
How many times the builder may fix what the tester or reviewers found before the story fails. Also a setting. | 3 |
A whole number from 1 to 10. |
features |
The feature switches, each on or off. They are settings too, so most people change them with foreman features rather than in the file. Every switch and its default is on Settings and switches. |
all as listed there | Only the known switches, each true or false; jiraCommentDetail is summary or full. |
keepAwake |
The old place of features.keepAwake. Still read, as the team value of that switch unless the file sets the switch too. |
none | Deprecated. foreman config check prints a warning telling you how to move it, and the next team write of any setting moves it for you. |
install
install is what a slot runs to make a fresh worktree ready, such as installing dependencies. It runs when the hash of the when file changes, or when the creates path is missing from the worktree. Set install to null for no install step. The merge-main ship step runs it too, before the checks, when a file matching when changed in the merge.
| Key | What it does | Default | Rule |
|---|---|---|---|
install.run |
The shell command. | npm ci |
Required when install is an object; non-empty string. |
install.when |
The file whose change means the install must run again, such as a lock file. | package-lock.json |
Required; non-empty string. |
install.creates |
A path the install produces, such as node_modules. When it is missing the install runs whatever the lock file says. foreman init sets it. |
none | Non-empty string. |
checks
checks is the list of commands that prove the project works. Each entry is { run, when, quick? }. The tester runs every check whose when matches a file the story changed; the merge-main ship step does the same on the merged tree when no later step tests merged code. foreman checks --changed runs them in a session, and a check that already ran on an unchanged tree is reused rather than run again. See Story sessions.
| Key | What it does | Default | Rule |
|---|---|---|---|
checks[].run |
The shell command. | none | Required; non-empty string. |
checks[].when |
always, or a glob a changed file must match for the check to run, such as src/**. |
none | Required; always or a glob string. |
checks[].quick |
A faster command for a change in progress: what the builder runs while building (foreman checks --quick). The full run still decides at test time. A quick run is never reused. |
none | Non-empty string. |
Your list replaces the default (empty) list and any pack list as a whole.
generated
generated names files a command produces, such as a lockfile or a generated client. A conflict in one of them during merge-main is settled by running its regenerate command and committing, instead of stopping the release. These paths are also never claimed by file locks, so two stories may both change them.
| Key | What it does | Default | Rule |
|---|---|---|---|
generated[].path |
The file, as a path or glob. | none | Required; non-empty string. |
generated[].regenerate |
The command that rebuilds it. | none | Required; non-empty string. |
fileLocks
Two stories building at once never change the same file: after the architect’s design, the slot claims every file the design names, and a story whose design names a held file waits. fileLocks.ignore lists the files every story may touch without a claim. See Slots.
| Key | What it does | Default | Rule |
|---|---|---|---|
fileLocks.ignore |
Globs for files no story claims. Every generated path is left out of claims too. |
["CHANGELOG.md"] |
A list of non-empty strings; fileLocks holds only ignore. Your list replaces the default. |
checkRuns
| Key | What it does | Default | Rule |
|---|---|---|---|
checkRuns.maxParallel |
How many full test runs may go at once across all slots. Unset, Foreman allows one per 8 cores, and at least one. Also a setting. | unset | A whole number from 1 to 64; checkRuns holds only maxParallel. |
resources
An exclusive resource is something only one story at a time may change, such as the database schema. A slot takes the resource when it picks a story whose design touches one of its paths, and a story that needs a held resource waits. The resources ship step stops a release (exit code 7) when a branch grew changes to a resource another live slot holds. See How Foreman picks and How a story ships.
| Key | What it does | Default | Rule |
|---|---|---|---|
resources.<name> |
One resource. The name is how the architect and the lock files refer to it. | {} |
The name must be a slug: lower-case letters, digits and hyphens, starting with a letter, and not a reserved word. |
resources.<name>.paths |
The files or globs that belong to it. | none | Required; a non-empty list of non-empty strings. |
resources.<name>.describe |
What it is, in words the architect and the release messages use. | none | Required; non-empty string. |
Your resources block replaces the default as a whole.
roles
roles names the agent that plays each part of the pipeline and lets you add traits and pick a model per role. Replacing an agent name swaps in your own agent; everything else about the role stays. The builders object replaces as a whole, so a project that names one builder has only that builder. Agents and traits explains the roles; this table is the rule book.
| Key | What it does | Default | Rule |
|---|---|---|---|
roles.architect.agent |
The agent that designs the story and writes the test plan. | foreman:architect |
Required when the block is given; non-empty string. The block holds only agent, traits, model. |
roles.architect.traits |
Extra rules for this role, as inline text or paths to .md or .txt files. |
none | A list of non-empty strings. A listed .md or .txt file that does not exist is an error. |
roles.architect.model |
The Claude model this role runs on. Unset, it runs on the story session’s model. Also a setting. | unset | One of haiku, sonnet, opus, fable. |
roles.builders.<name> |
One builder. The architect picks a builder by its use. |
developer |
The name must be a slug and not a reserved word. The block holds only agent, use, traits, model, fullChecks. |
roles.builders.<name>.agent |
The agent that builds. | foreman:builder |
Required; non-empty string. |
roles.builders.<name>.use |
When the architect should pick this builder, in words. | everything |
Required; non-empty string. |
roles.builders.<name>.traits |
As for the architect. | none | A list of non-empty strings. |
roles.builders.<name>.model |
As for the architect. Also a setting. | unset | One of haiku, sonnet, opus, fable. |
roles.builders.<name>.fullChecks |
true makes this builder run the full checks while building instead of each check’s quick command. |
none | true or false. |
roles.tester.agent |
The agent that re-runs the checks and verifies “Done when”. | foreman:tester |
As for the architect; the block holds only agent, traits, model. |
roles.tester.traits, roles.tester.model |
As for the architect. | none, unset | As for the architect. |
roles.reviewers[] |
The reviewers that run once the tester approves. Your list replaces the default list. | compliance (foreman:compliance-reviewer) and security (foreman:security-reviewer) |
Each entry holds only name, agent, prompt, traits, model, engine. |
roles.reviewers[].name |
The reviewer’s name, used in findings, settings and the dashboard. | none | Required; a slug, not a reserved word, and not used twice. |
roles.reviewers[].agent |
The agent that reviews. | none | Required; non-empty string. |
roles.reviewers[].prompt |
Extra instructions for this reviewer. | none | A string. |
roles.reviewers[].traits, roles.reviewers[].model |
As for the architect. The model setting is named roles.reviewers.<name>.model. |
none, unset | As for the architect. |
roles.reviewers[].engine |
Runs this review through another agent CLI instead of Claude, with the Claude reviewer as the fallback. See Agents and traits. | none | An object holding only name, model, minutes. |
roles.reviewers[].engine.name |
Which CLI. | none | Required; today only antigravity. |
roles.reviewers[].engine.model |
That CLI’s model name. | none | Required; non-empty string. |
roles.reviewers[].engine.minutes |
How long the outside review may take before Foreman falls back. | none | A whole number from 1 to 180. |
Trait files for every role, without touching the config, go under .claude/foreman/traits/ as all.md or <role>.md. Order: the pack’s traits, then those files, then the traits list, then what Foreman learned, so your own text wins.
stories
stories says where stories come from. The block replaces as a whole, so give the adapter and all its options together. The validator checks only that adapter is a non-empty string; each adapter checks its own options when it opens, and the options that are also settings (stories.jql, stories.pollSeconds, stories.ownerLabel, stories.transitions) are type-checked by the settings registry on both layers.
markdown
One markdown file per story in a folder. The default, and the shape Story files describes.
| Key | What it does | Default | Rule |
|---|---|---|---|
stories.adapter |
Selects the source. | markdown |
Required; markdown. |
stories.dir |
The folder, relative to the project or absolute. | stories |
Required. |
stories.dirEnv |
An environment variable that, when set and not empty, replaces dir, for a notes vault that lives elsewhere on another machine. |
none | A variable name. |
stories.dirEnvSuffix |
A path joined to that variable’s value, such as Stories when the variable names the vault root. |
none | A string. |
stories.idPattern |
What a story id looks like, as a regular expression source. | the markdown source’s own pattern | A regular expression source, such as [A-Z]+-\\d+. |
stories.fields |
Which frontmatter key holds each of group, groupName, order, dependsOn, blockedBy. |
group, group_name, order, depends_on, blocked_by |
An object of those keys. |
stories.ownerTask |
{ id?, title? }, regular expression sources; a story matching one is yours to do, never picked. {} turns owner-task detection off. |
the source’s own patterns | An object. |
stories.resourcesByGroup |
{ resourceName: [groups] }: every story in those groups needs that exclusive resource. |
none | An object of lists. |
stories.groupLabel |
What a group is called in the story prompts. | Group |
A string. |
stories.groupSummary |
Where a finished group’s summary is written: a path relative to dir with {group}, {groupName} and {groupLabel} filled in. Unset, no summaries. |
none | A string. |
stories.progressHeading |
The heading the dated progress lines go under. | ## Progress notes |
A string. |
stories.track |
Where status changes go: file edits the notes in place; state keeps them in an overlay under the state folder and touches a note only in the story’s own commit that marks it done. |
state when dir is inside the project, else file |
file or state. |
stories.newId |
The id template for follow-up stories Foreman creates: {n} is one more than the highest number of that shape, {group} the origin’s group. |
F-{n} |
Must match idPattern. |
jira
The issues a Jira Cloud JQL filter finds. Stories from Jira covers sign-in, mapping and write-back.
| Key | What it does | Default | Rule |
|---|---|---|---|
stories.adapter |
Selects the source. | none | jira. |
stories.site |
The site host, such as acme.atlassian.net. |
none | Required. |
stories.jql |
The filter; its ORDER BY is the pick order. Also a setting. |
none | Required; non-empty text. |
stories.pollSeconds |
How long Foreman reuses the story list before asking Jira again; 0 asks every time. Also a setting. |
60 |
A whole number of at least 0. |
stories.doneWhenField |
A custom field id holding “Done when”, such as customfield_10050. |
none | A string. |
stories.ownerLabel |
Issues with this label are your tasks; Foreman never picks them. Also a setting. | foreman-owner |
Non-empty text. |
stories.newIssueType |
The issue type of the follow-ups Foreman creates. | Task |
A string. |
stories.transitions |
{ event: transition name } for started, blocked, review, failed, shipped. An event left out moves nothing. Also a setting. |
{} |
Only those five keys, each non-empty text. |
stories.briefsDir |
Where briefs are written, relative to the project. | <stateDir>/stories |
A string. |
salesforce
Cases in one sandbox become stories. It needs pack: "salesforce" and stories.org must be an org in salesforce.orgs that is a sandbox; the validator refuses stories.adapter: "salesforce" without the pack. The block’s keys (org, where or listView, orderBy, pollSeconds, idPattern, doneWhenField, dependsOnField, ownerField, ownerValue, statuses, briefsDir) are on Salesforce config, with the salesforce block itself. The Salesforce pack is a preview: its release paths pass their own tests, but no story has yet shipped through them on a live org, and the Gearset path has not run against a live Gearset pipeline.
env
env is the slot environment: what each slot starts so its copy of the project can run alone. The block replaces as a whole. Every adapter gives each slot FOREMAN_SLOT=wN. Slots explains each adapter in use.
| Key | What it does | Default | Rule |
|---|---|---|---|
env.adapter |
none, docker or supabase; the Salesforce pack adds sandbox-pool and scratch and sets one of them itself. |
none |
Required; non-empty string. |
env.ports (none, docker) |
{ NAME: base }: each variable is set to base + n for slot wN with none, and base + 100·n with docker. |
none | An object of numbers. |
env.composeFile (docker) |
The compose file. | compose.yaml |
A string. |
env.projectPrefix (docker, supabase) |
The compose or Supabase project name prefix; each slot is <prefix>-wN. |
none | Required for both adapters. |
env.portStep (supabase) |
How far every port moves per slot. | 100 |
A number. |
env.extraPorts (supabase) |
{ NAME: { base, step } }: more variables to move per slot. |
none | An object. |
env.excludeStudio (supabase) |
Start without Supabase Studio. | true |
true or false. |
env.colima (supabase) |
Use Colima’s Docker socket. | false |
true or false. |
The Salesforce pack’s sandbox-pool and scratch adapters take no options; what they do is on Build orgs.
ship
ship is the release: the ordered steps a story branch goes through after review, and the options of each step. The block replaces as a whole, so a ship of your own must list steps. How a story ships walks through the steps and their exit codes; Deploy targets covers the deploy and Supabase steps. Two feature switches drop steps from the list at run time: mergeableRecheck off leaves out mergeable-recheck, and watchRollback off leaves out watch and runs no rollback.
| Key | What it does | Default | Rule |
|---|---|---|---|
ship.steps |
The steps, in order. Core steps: merge-main, resources, prepare-migrations, supabase-credentials, push, pr, ci, migrate-supabase, mergeable-recheck, merge, deploy-github, smoke, watch. A pack adds its own. |
["merge-main", "resources", "push", "pr", "ci", "mergeable-recheck", "merge"] |
Required; a non-empty list of non-empty strings. rollback may not appear: it runs only on failure. watch in the list needs ship.watch.command. |
ship.productionHosts |
The hosts the tester and reviewers must never reach while they check a story (the production guard). Unset, Foreman uses the smoke URLs’ hosts and the Supabase project’s API and database hosts. | none | A non-empty list of host names with no scheme or path. |
ci
| Key | What it does | Default | Rule |
|---|---|---|---|
ship.ci.required |
Only these check names count; each must appear, finish and pass. Empty, every check counts and CI is detected by settling. | [] |
A list of non-empty strings. |
ship.ci.timeoutMinutes |
How long to wait in all for the checks. Also a setting. | 30 |
A positive number. |
ship.ci.settleSeconds |
How long the checks must stay finished with no new one appearing, so a fast check cannot pass before the real CI registers. Also a setting. | 90 |
A positive number. |
pr and merge
| Key | What it does | Default | Rule |
|---|---|---|---|
ship.pr.into |
The branch pull requests merge into. Stories are still cut from baseBranch. Also a setting. |
unset (baseBranch) |
A plain branch name; with the Salesforce pack, never its productionBranch. |
ship.pr.mergeMethod |
How the merge step merges. |
merge |
One of merge, squash, rebase. |
ship.merge.keepBranch |
Keep the story branch after its pull request merges, remote and local. Also a setting. | false |
true or false; merge holds only keepBranch. |
deploy-github
Waits for the host’s deployment of the merge commit, as reported to GitHub. Anything but success exits 5 and writes the HALT file.
| Key | What it does | Default | Rule |
|---|---|---|---|
ship.deploy-github.environment |
The GitHub deployment environment to watch. | Production |
A string. |
ship.deploy-github.timeoutMinutes |
How long to wait for the deployment. Also a setting. | 15 |
A number. |
ship.deploy-github.statusContext |
The commit status the host posts, such as Vercel. With it set, a host that refuses the deployment outright stops the release at once with the host’s own words instead of waiting. |
none | A string. |
ship.deploy-github.rollbackHint |
How to roll back by hand, added to the halt email of a deploy or smoke failure. | none | A string. |
smoke
| Key | What it does | Default | Rule |
|---|---|---|---|
ship.smoke.urls |
Live pages to fetch after the deploy, without following redirects. No answer, or a status of 500 or more, exits 5 and halts. Also a setting. | [] |
A list of non-empty strings. |
ship.smoke.expect |
{ url: status }: a URL named here must answer exactly that status. |
none | Each status a whole number from 100 to 599. |
watch and rollback
| Key | What it does | Default | Rule |
|---|---|---|---|
ship.watch.command |
A shell command that checks production, run with sh -c in the project root with the env file loaded. Any non-zero exit stops the release with code 5, halts and, with ship.rollback set, rolls back. |
none | Required when watch is in steps; non-empty string. |
ship.watch.minutes |
How long to watch, running the command once at the start and once at the end. Also a setting. | 10 |
A positive number. |
ship.watch.everySeconds |
How often to run it. Also a setting. | 60 |
A positive number. |
ship.watch.timeoutSeconds |
The longest one run may take. | 120 |
A positive number. |
ship.rollback.command |
The command that puts production back, such as vercel rollback --yes, run once when a step fails after a successful deploy and watchRollback is on. FOREMAN_STORY is set to the story. |
none | Required when rollback is given; non-empty string. Never listed in steps. |
ship.rollback.timeoutSeconds |
The longest the rollback may take; longer is a failed rollback. | 300 |
A positive number. |
prepare-migrations, supabase-credentials and migrate-supabase
| Key | What it does | Default | Rule |
|---|---|---|---|
ship.prepare-migrations.dir |
Where the branch’s migrations live. Migrations that now sort before the base’s newest are renamed to follow it. | supabase/migrations |
A string. |
ship.migrate-supabase.projectRef |
The hosted Supabase project the migrations are pushed to, after CI and before the merge. | none | Required for the step. |
ship.migrate-supabase.dir |
The migrations folder, read by supabase-credentials too. |
supabase/migrations |
A string. |
supabase-credentials has no options: it stops the release with code 3 when the branch has migrations and the hosted credentials are missing from the env file.
notify and console
notify is how Foreman reaches you when the run stops, a story is blocked or a release parks; console is where the run’s state is published and your answers are read from. Both blocks replace as a whole. Notifications lists the events; Dashboard covers the default console.
| Key | What it does | Default | Rule |
|---|---|---|---|
notify.adapter |
none writes each notification to <stateDir>/outbox/ and sends nothing; email-resend emails you through Resend with RESEND_API_KEY from the env file. |
none |
Required; non-empty string. |
notify.to |
Where emails go. Unset, FOREMAN_NOTIFY_EMAIL, then your git email. Also a setting. |
unset | Non-empty text. |
notify.from |
The sender. Unset, RESEND_FROM_EMAIL. |
unset | A string. |
notify.subjectPrefix |
The start of every subject line. | [<project name> factory] |
A string. |
console.adapter |
dashboard serves the local dashboard; files writes status.json, reads control.json and keeps decisions under decisions/ in the state folder, for foreman status and foreman decide; artifacts publishes to two claude.ai artifact pages. |
dashboard |
Required; non-empty string. |
console.host (dashboard) |
The address the dashboard listens on. The default is the loopback address, which keeps it on this computer only; anything else needs the dashboard token. Also a setting; a change needs a restart. | the loopback address | Non-empty text. |
console.port (dashboard) |
The dashboard’s port. Also a setting; a change needs a restart. | 4317 |
A whole number from 1 to 65535. |
console.commandCenterUrl, console.decisionsUrl (artifacts) |
The two artifact pages. | none | Required for the adapter. |
console.model (artifacts) |
The Claude model of the small headless call that pushes each update. | haiku |
A model alias. |
console.everySeconds (artifacts) |
How often to push. | 60 |
A number. |
watchdog
The watchdogs stop or report a story that has stopped moving. All eight keys are minutes or hours, as a positive number; fractions are allowed. Only the first two are settings. When things go wrong says what each one does to the run.
| Key | What it does | Default | Rule |
|---|---|---|---|
watchdog.quietMinutes |
Stops an agent session that has printed nothing for this long. Also a setting. | 30 |
A positive number. |
watchdog.buildMinutes |
Stops a story with no stage change or agent finishing for this long, and any story at four times it. Also a setting. | 120 |
A positive number. |
watchdog.idleMinutes |
Reports the run stuck when a story can be built and a slot is free, yet nothing has started, for this long. | 10 |
A positive number. |
watchdog.errorMinutes |
Reports the run stuck when the supervisor’s loop has failed with the same error for this long. | 5 |
A positive number. |
watchdog.activityMinutes |
Reports a story stuck when its session has shown no activity (no stage change, log output or CPU) for this long. | 20 |
A positive number. |
watchdog.progressMinutes |
Lists a story as slow in foreman status when its session is still active but has had no stage change for this long. |
45 |
A positive number. |
watchdog.outliveMinutes |
Ends a session that printed its final result, or recorded its ending, and is still running this long after. | 1 |
A positive number. |
watchdog.ownerTaskHours |
How long an owner task may hold a story before you are told once more, by email. | 24 |
A positive number. |
state and cloud
state says where the run state lives; cloud tunes an unattended run. Run state on a branch and Run in the cloud explain both.
| Key | What it does | Default | Rule |
|---|---|---|---|
state.store |
files keeps the state folder as plain files; branch makes it a git worktree of an orphan branch, so a laptop and a cloud runner share one copy through the remote. |
files |
files or branch; state holds only store and branch. |
state.branch |
That branch. | foreman-state |
A plain branch name, and not baseBranch. |
cloud.envVars |
The project’s key variables the runner writes into envFile at start, besides those the adapters already need. |
[] |
A list of environment variable names. |
cloud.idleMinutes |
In a container, how long to wait with the dashboard up when the backlog is empty before picking again. | 10 |
A positive number. |
cloud.actions.cron |
The schedule foreman cloud actions init writes into the GitHub Actions workflow. |
*/30 * * * * |
Five cron fields of digits, *, /, , and -; actions holds only cron. |
session and claude
session shapes what a story session may load; claude says what happens when the Console API key runs out of credit. session has no Foreman default, so a missing mcpServers means none and a missing userSettings means false; a pack may set extraTools or mcpServers, and your list replaces the pack’s. Story sessions explains the lean session.
| Key | What it does | Default | Rule |
|---|---|---|---|
session.extraTools |
Tool names added to every story session’s allowed tools, such as mcp__acme__query. |
none | A list of non-empty strings, each with no spaces and not starting with -; session holds only extraTools, mcpServers, userSettings and model. |
session.mcpServers |
The servers from the project’s .mcp.json that story sessions load. |
none | A list of server names: a letter or digit first, then letters, digits, _, . and -. |
session.userSettings |
Whether sessions also load your user-level Claude Code settings, CLAUDE.md and memory. | false |
true or false. |
session.model |
The model of the story session itself, which only moves the story between stages and ships it. It does not set the agents’ models; those are roles.<role>.model. Unset, the Claude CLI’s own default applies. Also a setting. |
unset | One of haiku, sonnet, opus, fable. |
claude.fallback |
subscription switches sessions to your subscription when the key runs out; none pauses the run. Also a setting. |
subscription |
One of subscription, none; claude holds only fallback. |
learning
learning controls what Foreman learns from repeated findings and how much it may spend doing so. Every key here is also a setting. How Foreman learns explains lessons, trials and undo.
| Key | What it does | Default | Rule |
|---|---|---|---|
learning.auto |
Foreman learns traits and skills from repeated mistakes by itself and says so in Waiting on you. | true |
true or false; learning holds only these eleven keys. |
learning.apply |
The agents get learned traits and skills from the next story. Off still learns but uses none of it. | true |
true or false. |
learning.model |
The Claude model that proposes learned traits. | haiku |
One of haiku, sonnet, opus. |
learning.skillModel |
The Claude model that writes learned skills. | sonnet |
One of haiku, sonnet, opus. |
learning.window |
How many stories using a learned item Foreman watches before keeping or rolling it back. | 5 |
A whole number of at least 3. |
learning.maxPerRole |
The most learned traits one role may hold. | 8 |
A whole number of at least 0; 0 adds none. |
learning.maxSkills |
The most learned skills Foreman keeps. | 10 |
A whole number of at least 0. |
learning.maxEvaluating |
How many learned items may be on trial at once. | 2 |
A whole number of at least 0. |
learning.maxPerDay |
The most learned changes Foreman makes in one day. | 3 |
A whole number of at least 0. |
learning.maxUsdPerDay |
A daily spend cap for the learner sessions, in US dollars. | set by Foreman | A number of at least 0. |
learning.redact |
Words or patterns removed from findings before the learner sees them. An entry written /pattern/flags is a regular expression; anything else is literal text. |
[] |
A list of non-empty strings. |
A full example for Acme
Acme is a Node project deployed on Vercel, with stories in Jira and email through Resend. Everything not written here keeps its default.
{
"version": 1,
"project": { "name": "acme" },
"baseBranch": "main",
"slots": 2,
"workers": 2,
"rounds": 3,
"install": { "run": "npm ci", "when": "package-lock.json", "creates": "node_modules" },
"checks": [
{ "run": "npm test", "when": "always", "quick": "npm run test:unit" },
{ "run": "npm run lint", "when": "src/**" }
],
// The lockfile settles its own merge conflicts.
"generated": [{ "path": "package-lock.json", "regenerate": "npm install --package-lock-only" }],
"resources": {
"schema": { "paths": ["supabase/migrations/**"], "describe": "the database schema" }
},
"roles": {
"reviewers": [
{ "name": "compliance", "agent": "foreman:compliance-reviewer" },
{ "name": "security", "agent": "foreman:security-reviewer", "model": "opus" }
]
},
// The whole block: adapter and options together.
"stories": {
"adapter": "jira",
"site": "acme.atlassian.net",
"jql": "project = ACME AND status = \"To Do\" ORDER BY Rank",
"doneWhenField": "customfield_10050",
"transitions": { "started": "In Progress", "review": "In Review", "shipped": "Done" }
},
// Each slot gets PORT = 3000 + n.
"env": { "adapter": "none", "ports": { "PORT": 3000 } },
// A ship block of its own must list every step.
"ship": {
"steps": ["merge-main", "resources", "push", "pr", "ci", "mergeable-recheck", "merge", "deploy-github", "smoke", "watch"],
"ci": { "required": ["test"], "timeoutMinutes": 30 },
"pr": { "mergeMethod": "squash" },
"deploy-github": { "environment": "Production", "statusContext": "Vercel", "rollbackHint": "vercel rollback --yes" },
"smoke": { "urls": ["https://acme.example/health"], "expect": { "https://acme.example/health": 200 } },
"watch": { "command": "node scripts/check-errors.mjs", "minutes": 10 },
"rollback": { "command": "vercel rollback --yes" }
},
// RESEND_API_KEY lives in .env.foreman, never here.
"notify": { "adapter": "email-resend", "subjectPrefix": "[acme factory]" },
"watchdog": { "quietMinutes": 30, "buildMinutes": 120 },
"features": { "followUpsOfFollowUps": false },
"learning": { "window": 5 }
}
Was this page useful? Something missing or wrong?Email us and say which section.