Configuration reference — .commission.json
.commission.json is the canonical, git-committable configuration for a project.
It lives at the repository root; every Commission command walks up from the working
directory to find it, so agents never have to name the project.
JSON rather than YAML on purpose: commission’s whole agent-facing surface is already
JSON — apply documents, --json output, the MCP contract — and the one file
an agent writes should not be the odd shape out.
A minimal config is two keys:
{
"version": 1,
"project": "myapp"
}Everything else is optional. Blocks you do not write behave as the defaults below, so a solo founder’s config stays two keys while an enterprise repo can declare actors, approval routing, workspace providers, and tracked context.
Rules
- Strict. Unknown keys are refused, never ignored — a typo that silently does nothing is the failure this file exists to prevent. Refusals name the path, list the allowed keys, and suggest the nearest declared key.
- Versioned.
versionis required. A file declaring a version this binary does not speak is refused with the upgrade command, never partially applied. A file with noversionis the pre-versioned shape and is read as such:project,integrations, andnoticesonly, with a notice. - Deterministic. No inference beyond the precedence chain below.
Notices
A pre-versioned file still loads, and says so once per process on stderr, naming the command that ends it:
warning: .commission.json predates config versioning — add "version": 1 to use the full configuration surface
fix: commission config migrate --writeIt is never printed under --json: stderr is part of a machine’s stream, so a
pipeline sees byte-identical output whether or not the repo has migrated.
A repo that has deliberately not migrated turns it off in the file it already
has — notices is one of the keys a pre-versioned file may carry, precisely so
that declining to migrate does not require migrating:
{
"project": "myapp",
"notices": { "deprecations": false }
}Self-documenting files
JSON has no comments, so point $schema at the generated JSON Schema and your
editor supplies completion, hover documentation, and inline validation:
{
"$schema": "https://commission.sh/schema/commission-config.schema.json",
"version": 1,
"project": "myapp"
}Commission ignores $schema entirely — it is there for the editor.
Precedence
Lowest to highest — the last layer to set a key wins, and
commission config explain <key> reports which one did:
defaultsdbfilelocalenvflags
Objects merge key by key. Arrays replace wholesale and scalars replace — array merging is where config systems become unpredictable, so Commission does not do it.
.commission.local.json (the local layer) is for one machine and must not be
committed; it may refine any setting but cannot declare project.
Environment overrides
An explicit table rather than a general convention:
| variable | sets |
|---|---|
COMMISSION_PROJECT | project |
COMMISSION_LEASE | agentDefaults.lease |
COMMISSION_BUDGET | agentDefaults.budget |
COMMISSION_WORKSPACE_STRATEGY | workspace.strategy |
COMMISSION_CONFIG names a config file directly and skips the upward search.
Fields
| key | type | required | description |
|---|---|---|---|
$schema | string | no | path or URL to commission-config.schema.json — ignored by commission, used by editors for completion and hover docs (JSON has no comments, so this is how the file documents itself) |
version | 1 | yes | config format version — RC1 speaks 1 |
project | string | yes | the Commission project slug this repo belongs to; prefixes every task id |
execution | object | no | |
execution.defaultAutonomy | “autonomous” | “supervised” | “human_only” | no | autonomy for tasks that declare none |
execution.preferredActor | string | no | biases selection; never blocks another eligible actor |
execution.mayExecute | string[] | no | roles or capabilities required to claim a task here (empty = anyone) |
execution.requireApproval | object[] | no | approvals every task must collect before done; per-task policy adds to this |
execution.requireApproval[].role | string | no | any actor holding this role may satisfy it |
execution.requireApproval[].actor | string | no | only this specific actor may satisfy it |
execution.requireApproval[].kind | “approval” | “verification” | no | approval is a judgement call; verification is a human confirming something is true |
execution.requireApproval[].reason | string | no | shown in the refusal so the requirement explains itself |
execution.reviewPolicy | map<string, object[]> | no | extra approvals keyed by gate name, e.g. { released: [{ role: owner }] } |
workspace | object | no | |
workspace.strategy | “in-place” | “worktree” | no | in-place: work in the checkout (default) · worktree: a git worktree per task |
workspace.dir | string | no | where worktrees live, relative to the repo root (default .worktrees) |
workspace.providers | string[] | no | ordered environment provider chain, e.g. [gitenv] or [vault, dotenv] |
workspace.ports | object | no | deterministic, collision-free port allocation for parallel workspaces |
workspace.ports.range | [number, number] | yes | inclusive [start, end] |
workspace.ports.perTask | number | no | ports allocated per task (default 1) |
workspace.setup | object[] | no | ordered steps run after the workspace exists |
workspace.setup[].name | string | yes | shown in the event log and in failures |
workspace.setup[].run | string | yes | shell command, executed in the prepared workspace |
workspace.setup[].timeoutMs | number | no | default 120000 |
workspace.setup[].optional | boolean | no | a failure warns instead of aborting prepare |
workspace.teardown | object[] | no | ordered steps run before the workspace is removed |
workspace.teardown[].name | string | yes | shown in the event log and in failures |
workspace.teardown[].run | string | yes | shell command, executed in the prepared workspace |
workspace.teardown[].timeoutMs | number | no | default 120000 |
workspace.teardown[].optional | boolean | no | a failure warns instead of aborting prepare |
context | object | no | |
context.track | string[] | no | committed guidance to version and fingerprint, e.g. [CLAUDE.md, .claude/, docs/adr/] |
context.ignore | string[] | no | globs excluded from the tracked set |
context.bundleBudget | number | no | default token budget for context bundles; unset = no truncation |
context.warnOnDrift | boolean | no | warn when tracked guidance changed since a task was claimed (default true) |
actors | object[] | no | |
actors[].name | string | yes | matches COMMISSION_ACTOR, and appears in events and claims |
actors[].kind | “human” | “agent” | yes | humans own judgment; agents own execution |
actors[].roles | string[] | no | named roles this actor holds, e.g. [owner, approver] |
actors[].capabilities | string[] | no | what this actor can do, e.g. [implement, test, document, review] |
actors[].autonomy | “autonomous” | “supervised” | “human_only” | no | per-actor ceiling; the task policy cannot exceed it |
actors[].default | boolean | no | the actor assumed when COMMISSION_ACTOR is unset |
providers | map<string, object> | no | named provider entries, referenced by workspace.providers |
providers.<name>.type | “dotenv” | “exec” | “gitenv” | “doppler” | “onepassword” | “vault” | yes | which built-in provider implementation backs this entry |
providers.<name>.env | string | no | the provider’s environment/stage name, e.g. dev or staging |
providers.<name>.app | string | no | the provider’s app/scope name, when it has one |
providers.<name>.files | string[] | no | dotenv: files to materialize |
providers.<name>.run | string | no | exec: command that emits KEY=VALUE lines on stdout |
providers.<name>.path | string | no | vault/1password: the secret path or item reference |
providers.<name>.options | map<string, string> | no | provider-specific passthrough options |
integrations | object | no | |
integrations.jira | object | no | |
integrations.jira.baseUrl | string | no | the declared Jira site; a mismatch is refused |
integrations.jira.statusMap | map<string, string> | no | commission status → this site’s status name |
integrations.jira.fields | map<string, string> | no | custom-field ids, e.g. { rank: customfield_10019 } |
integrations.jira.projects | string[] | no | issue-key prefix allowlist, e.g. [RTB, SUP] |
integrations.github | object | no | |
integrations.github.repos | string[] | no | owner/name repos this project may bind to |
integrations.github.autoCloseOnMerge | boolean | no | close a task when its PR merges (gates still apply) |
integrations.github.branchPrefix | string | no | default commission/ |
integrations.selection | “severity” | “sprint” | “due” | “priority” | “rank” | “age”[] | no | ordered work-selection policy for next and ready |
notifications | object | no | |
notifications.onApprovalRequested | boolean | no | |
notifications.onBlockingQuestion | boolean | no | |
notifications.onDivergence | boolean | no | |
notifications.channels | object[] | no | |
notifications.channels[].type | “exec” | “webhook” | yes | exec runs a command; webhook POSTs the inbox item |
notifications.channels[].target | string | yes | the command or URL |
notifications.channels[].events | string[] | no | inbox item kinds to deliver (default: all enabled) |
notices | object | no | |
notices.deprecations | boolean | no | print deprecation notices on stderr (default true) — set false in a repo that has deliberately not migrated; the alternative is to run ‘commission config migrate —write’ and be done with them |
gates | map<string, string[]> | no | gate profiles: gate name → required evidence kinds, e.g. { shipped: [test, commit] } |
agentDefaults | object | no | |
agentDefaults.lease | string | no | default claim lease, e.g. 30m, 2h, 1d, or 0 for none |
agentDefaults.actor | string | no | actor assumed when COMMISSION_ACTOR is unset |
agentDefaults.budget | number | no | default —budget for next and show |
agentDefaults.requireNoteOnDone | boolean | no | refuse done without at least one note (default false) |
branch | object | no | |
branch.prefix | string | no | branch name prefix (default commission/) |
branch.template | string | no | branch name template; supports {id} and {slug} (default {id}-{slug}) |
branch.base | string | no | branch new work forks from, e.g. main |
Related commands
| command | purpose |
|---|---|
commission config show | the fully merged, effective config |
commission config validate [file] | exit 0 or 2 — usable in CI |
commission config explain <key> | the value, the layer that set it, and what it overrode |
commission config init | write a minimal .commission.json for this repo |
commission config migrate | bring a pre-versioned file up to version 1 |
commission config path | which files were considered, in order |
This page is docs/reference/config.md in the Commission
repository, rendered in place — the site keeps no copy of it. It is generated by scripts/generate-config-reference.ts, so it is exhaustive, and editing it by hand is reverted by the next run.