Configuration
Provefab reads provefab.toml from its home directory, ~/.provefab by default. The PROVEFAB_HOME variable moves that directory. A commented example sits at the repository root: provefab.example.toml.
provefab doctor checks configuration, tools and logins. Run it after every change. A successful check does not establish native sandbox compatibility; stages also verify the runtime, profile and policy before launch (see native execution policy).
Writing the file with commands
Section titled “Writing the file with commands”provefab init writes provefab.toml when there is none: the [jev] section and a catalog with the models of the worker CLIs on your PATH (claude gives claude-sonnet and claude-opus, codex gives codex-gpt). It adds no repository, and never changes an existing file. --dry-run prints the file instead. Before starting tasks, remove or replace every worker = "claude-code" model block in an existing or init-generated catalog: all Claude stages currently refuse. The example uses Codex for its standard/frontier tiers; add a Pi model from another provider for cross-provider review. Successful doctor or login checks do not establish stage support.
provefab repos add <owner/name> appends one [[repos]] block at the end of the file and keeps the rest as you wrote it: slug, label = "provefab", base (the repository’s default branch) and gates, detected from the files at the root of the default branch:
| At the root | gates |
|---|---|
Cargo.toml |
cargo fmt -- --check, cargo clippy --all-targets -- -D warnings, cargo test |
package.json |
the install from the lock file (pnpm install --frozen-lockfile with pnpm-lock.yaml, yarn install --frozen-lockfile with yarn.lock, npm ci with package-lock.json, npm install --no-package-lock otherwise, so no lock file is written into the task’s changes), then <pm> run lint, <pm> run typecheck, <pm> test, for the scripts that exist; <pm> is the same package manager |
pyproject.toml, or setup.py or setup.cfg with a tests directory |
ruff check . when ruff is configured ([tool.ruff] or ruff.toml), then pytest |
go.mod |
go vet ./..., go test ./... |
It reads these files through gh without cloning, or from your clone with --path <checkout>: its origin must be that GitHub repository, and the files are read from its committed default branch, not from your working tree. A repository with none of these, with several, or with a package.json without any of the three scripts (npm’s placeholder test script does not count) is refused: write its block by hand. repos add never writes local_path, so Provefab keeps its own clone; set local_path by hand to use yours. It never writes a tracker, a risk policy or post-merge checks either: add them by hand. A task’s worktree starts without installed dependencies: provision required dependencies in trusted locations before running gates. The native policy denies network downloads during checks; older repos add notes suggesting installation inside gates do not grant network access. --dry-run prints the block instead of writing it. The commands’ exit codes are in Operations.
Secrets
Section titled “Secrets”No secret goes in provefab.toml. On macOS, Provefab keeps its secrets in the Keychain. On Linux it keeps them in ~/.provefab/credentials.toml: created with mode 600 (readable and writable by your account only), and refused by run, doctor, login and every command that reads a secret when its mode lets group or others read it (chmod 600 ~/.provefab/credentials.toml fixes it). The file is protected by its permissions, not encrypted. Environment variables take precedence on both systems.
| Secret | Where |
|---|---|
| TypeSafe (Jev) key | TYPESAFE_API_KEY variable, otherwise provefab login jev (macOS Keychain item provefab-typesafe, or typesafe in credentials.toml); on macOS security add-generic-password -s provefab-typesafe -a provefab -w also works |
| Claude subscription login | provefab login claude (directory ~/.provefab/claude) |
| Anthropic API key | provefab login claude --api-key: stored in the Keychain (provefab-anthropic) or as anthropic in credentials.toml; Claude Code asks Provefab for it through apiKeyHelper (directory ~/.provefab/claude-api). If you move the provefab binary, run this login again |
| Codex (ChatGPT) subscription login | provefab login codex (directory ~/.provefab/codex); on Linux with a device code, entered in a browser on another machine |
| OpenAI API key | provefab login codex --api-key (directory ~/.provefab/codex-api) |
| GitHub | gh auth login (Provefab uses gh and git with your permissions) |
| Jira Cloud | provefab login jira --site <site>: e-mail and API token in the Keychain (provefab-jira) or under [jira."<site>"] in credentials.toml; or PROVEFAB_JIRA_EMAIL and PROVEFAB_JIRA_TOKEN |
| Linear | provefab login linear: personal API key in the Keychain (provefab-linear) or as linear in credentials.toml; or PROVEFAB_LINEAR_KEY |
Supported logins. Native execution accepts only the verified authentication modes below. All Claude profiles temporarily refuse before version/auth probes because implicit host context loading has no verified boundary. Select Codex or Pi. Codex workspace/managed plans also refuse before a stage. A successful login does not make an unsupported mode usable. Check your provider’s terms for your intended use separately.
| Field | Default | Role |
|---|---|---|
model |
required | The full Jev version, for example jev-1.13.0. jev-latest is refused, so that a new release never changes routing silently. jev-1.13 (without the patch number) is rejected by the API; provefab doctor reports it. |
underspecified_threshold |
0.7 |
Above this probability that an issue is too vague, Provefab asks a question instead of coding. |
loop_threshold |
0.8 |
Above this, an agent judged stuck or repetitive is stopped (checked every 15 tool calls). |
Provefab still works without a Jev key: every stage runs on the standard tier. There is then no automatic question, no loop detector and no failure triage.
[[models]]: the catalog
Section titled “[[models]]: the catalog”| Field | Default | Role |
|---|---|---|
id |
required | Unique name, used in logs and PR text. |
worker |
required | claude-code (the unmodified claude binary), codex (the unmodified codex binary) or pi. |
model |
required | Model name for that worker: a Claude Code alias (sonnet, opus, haiku), a Codex model (gpt-5.5), or a Pi --model pattern. |
provider |
"" |
Pi only: the --provider. |
tier |
required | fast, standard or frontier. |
max_concurrency |
1 |
How many stages this model may run at once. |
auth |
subscription |
Claude Code and Codex only: subscription (your plan’s login) or api_key (your own API key, set with provefab login <worker> --api-key). Pi reads its provider’s own credentials and always counts as an API key. |
price_id |
none | The price entry to use, as provider/model (for example anthropic/claude-sonnet-5-5), when the automatic match is wrong. |
price_in, price_out |
none | USD per million input and output tokens. Set both to override the fetched prices. |
price_cache_read, price_cache_write |
the input price | USD per million cached tokens read and written, with price_in and price_out. |
quota_weight |
relative price | Subscription models only: how much of your plan one token uses, compared with the cheapest model of the same vendor in the catalog (1.0). It orders that vendor’s models only. |
Subscription or API key, per model. For a worker that supports both modes, currently Codex, you can switch one line and run provefab service install. You can also list the same model twice in the same tier, once per supported sign-in mode: when the subscription hits its usage limit, that entry pauses and the API-key entry takes the next stages. Both still count as the same model family for cross-review. All Claude stages currently refuse native execution. The daily budget (max_stage_runs_per_day) caps API spend too.
How Provefab picks a model
Section titled “How Provefab picks a model”- The tier. Jev rates each issue once, at intake:
- Implementation takes the tier that matches the difficulty, one tier up when Jev is unsure.
- Planning takes the implementation tier when Jev rates the planning simple (
plan_depthbelow 1.5), and one tier above otherwise. An architectural scope always plans onfrontier. - Review takes the implementation tier when a subtle mistake would cost little (
review_riskbelow 1.5),frontierwhen it would cost a lot (3.0 or more), and one tier above otherwise. - Without a Jev key, every stage runs on
standard.
- The model inside the tier, following
[routing] prefer:- subscription models first, then API-key models, the cheapest first;
- among one vendor’s subscriptions, the lowest
quota_weightfirst; between vendors (Claude and Codex, say), file order decides, since a weight only compares models of the same vendor; - a model with no known price comes after the priced ones (subscription or API key);
- ties keep file order.
- The rules that come first:
- a stage never runs below its tier (an empty tier falls back to the nearest configured one, stronger first);
- review avoids the implementer’s model family whenever the catalog allows it, even when that family is cheaper;
- a paused model (usage limit) is skipped;
- an implementation that fails again after a retry moves one tier up.
provefab log <id> shows the tiers and, for each stage, the model and why it was chosen.
[routing]
Section titled “[routing]”| Field | Default | Role |
|---|---|---|
prefer |
subscription |
subscription: your plans first (already paid), API keys when they are paused. api_key: API keys first. cheapest: one list in which subscriptions count as free. |
prices_url |
models.dev | Where prices are fetched from, for a mirror. |
litellm_url |
LiteLLM’s price list | The fallback source, for a mirror. |
Prices. Provefab fetches model prices from models.dev at most once a day, with LiteLLM’s price list as fallback, and keeps them in ~/.provefab/prices.json. Offline, it uses that cache, or the prices built into the binary, and tries again an hour later. A price set in provefab.toml always wins. Claude Code aliases (sonnet, opus, haiku) match the newest model of that family. provefab doctor shows the price each model uses. The request sends no data about you or your code.
Tip: list supported models from at least two providers, for example Codex and a Pi model from another provider, so that reviews are cross-checked by a different model family.
[[repos]]: watched repositories
Section titled “[[repos]]: watched repositories”provefab repos add <owner/name> writes this block for you (see Writing the file with commands).
| Field | Default | Role |
|---|---|---|
slug |
required | owner/name on GitHub, where the code and the pull requests are. |
local_path |
none | Your local clone. Optional: without it, Provefab clones the repository into ~/.provefab/repos/<owner>/<name>. Either way it runs git fetch before every pass and starts from origin/<base>. |
label |
provefab |
The label that triggers Provefab. Only people who can triage the repository can set it: it is the authorization. The derived labels (<label>:in-pr, :needs-info, :failed, :merged) are created at startup on GitHub and Linear. The risk labels (<label>:risk-<category>, including :risk-unknown) are created at startup too. Jira labels are free text and need no creation. |
base |
main |
Branch work starts from, and pull requests target. |
poll_interval |
3m |
How often the repository is polled. |
trust_pi_project |
false |
Pi only: load the repository’s Pi config (avoid for a repository you do not control). |
gates |
required, at least one | Commands run in the worktree after every implementation. All must pass. They decide, not the agents. |
post_merge_checks |
[] |
Optional commands run against the exact merged base commit after a Provefab-created PR merges. On failure, Provefab validates a revert against the current base and opens a human-reviewed revert PR only if the reverted tree passes. It never merges the revert automatically. Enabling it later does not check old merges. Command strings may appear in GitHub comments: never put secrets on the command line, use the environment. |
Provefab Pro reads an optional [repos.merge] table for guarded auto-merge: auto (off by default), max_lines (400), require_test_change (on: only a change that adds or changes tests merges by itself), exclude (paths kept for a human, .github/** by default) allow_public (off: a public repository is never merged automatically, since anyone can write its issues) and allow_risk (risk categories that may still auto-merge; unknown never can). This binary ignores it, says so in provefab doctor and in each PR comment, and never merges. The old keys auto_merge and auto_merge_max_lines are refused with a message naming [repos.merge].
For gates that work well:
- Put in what your CI requires: format, lint, tests.
- Provision dependencies before the task and use offline commands, such as
cargo test --offlineor a test script with its dependencies already available. Detected install commands are suggestions, not permission for downloads. Do not use shared writable caches to work around refusal. - Declare validation scripts, test configuration, fixtures, lockfiles and supporting dependencies in
protected_paths. This may prevent a task from changing those tests; choose the protected validation harness deliberately. - Fast commands speed up every loop.
- If the repository has slow tests, keep a representative subset and let your GitHub CI do the rest.
- Never share a build directory between tasks (for example one
CARGO_TARGET_DIRfor every worktree). Cargo can then reuse binaries built from another task’s code, and the checks pass or fail on the wrong code. Each worktree builds in its owntarget/, which the agent already warmed during implementation. To go faster safely, use a content-addressed cache such assccache(RUSTC_WRAPPER=sccache).
[repos.sandbox]: native execution policy
Section titled “[repos.sandbox]: native execution policy”Every stage and gate receives this repository’s policy, including reproduction, risk checks, post-merge checks and revert verification. Unknown keys in this table are errors.
| Field | Default | Role |
|---|---|---|
runtime |
"srt" |
SRT executable for gates and Pi Bash. Provision it outside the worktree; an absolute path or trusted PATH lookup is accepted. Tested version: 0.0.79, minimum 0.0.79. Missing or unsupported runtime fails before execution. |
allow_local_network |
false |
Gate-only request. true currently refuses on every platform because loopback-only binding is unverified. SRT can allow listening on non-loopback interfaces; no wider permission is inferred. Workers always keep strict network denial. |
protected_paths |
[] |
Existing literal paths, relative to the validation worktree or absolute externally. These paths cannot be written by native commands or Codex patches. Pi direct file tools are disabled; file operations use sandboxed Bash. Claude stages refuse. Missing paths, wildcards, .., mutable symlink aliases and declarations encompassing the worktree root are refused. |
deny_read |
[] |
Additional literal absolute paths that commands and direct read/search tools must not read. Existing ancestors are resolved even for absent secret leaves. |
[repos.sandbox]runtime = "/opt/provefab-runtime/srt"protected_paths = ["tests", "scripts/check.sh", "Cargo.lock", "/opt/validators/project"]deny_read = ["/srv/private/service-token"]SRT must be separately provisioned in a trusted directory using the sandbox-runtime installation instructions. Its dependencies must also remain outside writable roots. Network is denied during checks, including package downloads and local listeners; provision dependencies before a run. The core gates and real Codex commands have been exercised on macOS 26.6.2 (Apple Silicon) with Codex 0.162.1 and SRT 0.0.79. Pi 1.0.3’s installed Bash tool factory was exercised through the actual extension and SRT without a Pi model call. Linux enforcement is unverified; unavailable enforcement fails closed. Keep PROVEFAB_HOME short on macOS: SRT’s private temporary socket path can exceed the OS limit with long home or session paths and refuse execution.
An empty protected_paths list provides no immutable-validation guarantee. Declare every script, test configuration, lockfile and dependency defining success, or keep them outside writable roots. Protecting one entrypoint does not protect its dependency closure. Command and interpreter resolution also matters: PATH entries and aliases through writable roots are excluded. Trusted plugin, guard and authentication inputs inspect every expanded component before canonicalization, including the nearest existing ancestor of absent secret leaves. Stable external aliases remain usable. A protected file missing from a review or reverted tree causes a validation error. NoTools maintenance resolves relative declarations against the real repository while its model runs in a separate empty directory.
All Claude stages temporarily refuse, including clean personal profiles and NoTools, before any host version/auth probe or stage preparation. The implicit host context boundary is unverified: user memory and rules can load outside the command sandbox. Select Codex or Pi in the model catalog. Do not delete user authentication or state to make a run proceed. Support can reopen only after exact-source controls compatible with authentication and Provefab hooks, and a successful supported-stage run, are verified. Codex requires exactly 0.162.1, a dedicated untrusted project profile and only Provefab’s guard hook among effective enabled/trusted hooks. The dedicated config supports basic model/auth fields, projects, hooks and notices only; nonempty mcp_servers, notify, custom providers, profiles and unsupported keys refuse before host startup. Native plugins and external tool routes are disabled by CLI overrides. Pi exposes only sandboxed Bash for file operations, plus structured answers when needed; its direct file/search tools are disabled because pre-operation guards cannot prevent symlink races. Read-only profiles keep no worktree write permission and PR reviews keep their read-only shell allowlist. Codex image viewing is disabled pending boundary verification. See Security.
Earlier validation observed Claude 2.1.299 refusing ~/.claude/state. That historical host-state result did not establish safety for clean profiles. The current refusal applies universally, independently of installed version, authentication or host state.
Codex authentication must use cli_auth_credentials_store = "file", which Provefab also pins at launch. Supported subscription plan claims are free, go, plus, pro, prolite and promax; file API-key logins are supported too. Workspace/managed, unknown or malformed authentication and keyring/auto/ephemeral stores refuse before startup. Provefab also refuses any /etc/codex/config.toml, /etc/codex/managed_config.toml, /etc/codex/requirements.toml, dedicated-profile cloud-config-bundle-cache.json or forced com.openai.codex managed config/requirements preference. Even empty or broken policy files refuse. Keep account membership and host policy stable during a run; preflight cannot seal later administrator or account changes. No source or login data is removed automatically. Newer Codex configuration loaders require review before support.
Codex and Pi use explicit trusted stage prompts. Pi disables implicit AGENTS/CLAUDE and system-prompt file discovery, retaining the explicit stage file. Codex disables project documents, bundled skills and cloud skills, and fixes project skill discovery to the checked working directory. Dedicated-profile AGENTS.md/AGENTS.override.md, HOME/.agents/skills, /etc/codex/skills, and working-directory .codex/skills/.agents/skills refuse before host startup, including broken links. The dedicated skills directory may be absent, empty, or contain only a real non-symlink .system directory; Provefab does not inspect its contents. Custom or aliased caches refuse. Relevant roots and resolved aliases remain protected from worker writes even when absent, so a running worker cannot introduce an automatic skill source. Unrelated ancestor HOME/.codex/skills is not a source when CODEX_HOME differs. App-server starts in the same checked directory. Custom implicit context remains unsupported until its host reads have a verified enforcement boundary.
[repos.risk]: risk-aware policy
Section titled “[repos.risk]: risk-aware policy”Provefab classifies each round’s changed files by path into risk categories. A risky change gets stricter handling: extra checks, a frontier reviewer from another provider when you have one, a visible Risk section in the PR and a label on the issue. The policy raises scrutiny; it does not prove a change is harmless. It works with no configuration, and [repos.risk] is optional. Path matching only: file contents are not read.
Built-in categories (a path matching any pattern puts the change in the category):
| Category | Patterns |
|---|---|
ci |
.github/**, .gitlab-ci.yml, .circleci/** |
dependencies |
**/Cargo.toml, **/Cargo.lock, **/package.json, **/*lock*.json, **/pnpm-lock.yaml, **/yarn.lock, **/go.mod, **/go.sum, **/requirements*.txt, **/pyproject.toml, **/Gemfile* |
migrations |
**/migrations/**, **/*.sql |
infrastructure |
**/Dockerfile*, **/*.tf, k8s/**, helm/**, **/docker-compose*.yml |
secrets-config |
**/.env*, **/*.pem, **/*.key, **/*secret* |
rules |
.provefab/rules.md (see Repository rules) |
Provefab never commits .provefab/ in a task’s pull request, so the rules category marks a change to the rules made by other means; disable it like any built-in.
Patterns. /-separated, anchored at the repository root, case-sensitive. ** matches any number of segments (including none), * any run of characters inside one segment, everything else literally. .gitlab-ci.yml matches only the root file; write **/<name> for any directory. For a rename, both the old and the new path count.
| Field | Default | Role |
|---|---|---|
disable |
[] |
Built-in categories to turn off, by name. |
[repos.risk.categories.<name>] |
none | Adds a category, or extends a built-in of the same name. |
paths |
required for a new category | Patterns of the category. For a built-in, they are added to its patterns. |
checks |
[] |
Commands run in the worktree, after your gates, when the category is detected. For a built-in, they replace its checks. A command already in gates runs once. Same timeout and rerun rules as gates; a failing check is an ordinary gate failure: the round goes back to implementation and counts toward the same attempt limit as other gates. |
reviewer_tier |
frontier |
frontier or standard: the tier of the review when the category is detected. frontier applies only when a frontier model from another provider than the implementer’s is configured; otherwise the usual reviewer from another provider stays (see Usage). |
[[repos]]slug = "acme/api"gates = ["cargo test"]
[repos.risk]disable = ["secrets-config"]
[repos.risk.categories.auth]paths = ["src/auth/**", "src/billing/**"]checks = ["cargo test --test auth"]
[repos.risk.categories.migrations]paths = ["db/schema/**"]checks = ["./scripts/check-migration.sh"]Category names use lowercase letters, digits and -. unknown and none are reserved: Provefab uses unknown when the changed files cannot be computed, and Provefab Pro’s calibration report uses none for changes with no risk category.
Validation errors (an invalid [repos.risk] is refused when the configuration loads, so Provefab does not start; provefab doctor and provefab run report <slug>: [repos.risk]: <message>):
unknown category in disable: <name>:disablenames something that is not built-in.invalid category name: <name>: empty,unknown,none, or not lowercase letters, digits and-.<name>: empty path,<name>: empty check: a blank entry.<name>: path "<p>" must be relative, without "./", a trailing "/" or empty segments: a pattern that can never match a changed path (/src/**,./src/**,src/,src//auth).<name>: no paths: a new category withoutpaths.<name>: reviewer_tier must be standard or frontier.<name> is disabled: the same built-in is indisableand incategories.- An unknown key under
[repos.risk]or a category table is refused too.
provefab doctor prints one risk <slug> line per repository: the category count and names, and how many checks were added. It ends with ; warning: risky changes implemented on <providers> keep a standard reviewer (no frontier model on another provider) when, for one or more providers in your catalog, no frontier model is on another provider. Providers are listed in catalog order and comma-separated (for example claude-code, codex, pi:<provider>). See Usage for what happens to a risky change.
[repos.tracker]: Jira or Linear issues
Section titled “[repos.tracker]: Jira or Linear issues”Absent, the repository’s issues are its GitHub issues. Setup, labels and limits: Jira and Linear.
| Field | Default | Role |
|---|---|---|
kind |
github |
github, jira or linear. |
site |
none | Jira only, required: the site’s host name, such as acme.atlassian.net: dot-separated labels (at least two) of letters, digits and hyphens, not an IP address (no https://, no path). |
project |
none | Jira and Linear, required: the project or team key, [A-Z][A-Z0-9_]*, as in ENG-123. |
Refused at load: an unknown kind or key (credentials never go in this file), site outside Jira, project with github, and, for Jira, a label containing whitespace.
Switch a repository’s kind (or its project) only when the repository has no task history that could collide: provefab run and provefab add refuse to start while a task in progress (including one whose pull request is still watched), or an update still to send, belongs to the previous tracker. A new ticket whose number matches an earlier task of the repository is skipped with a log line. See Jira and Linear.
[limits]
Section titled “[limits]”| Field | Default | Role |
|---|---|---|
stage_timeout |
30m |
Longest an agent stage (plan, implement, review) may run. |
gate_timeout |
20m |
Longest one check command may run. |
max_turns |
plan 40, implement 150, review 40 |
Turn limit per stage. |
review_rounds |
2 |
Correction rounds the review may ask for before a new pass. |
max_auto_passes |
3 |
Automatic new passes per issue before Provefab asks you. |
max_stage_runs_per_day |
60 |
Worker runs over a rolling 24 hours, across all repositories. |
max_drive_steps |
200 |
Most steps one task takes in one go (a safety net). |
retry_delays |
["5m", "15m", "45m"] |
Waits after a transient failure (network, GitHub). After the last one, Provefab asks you. |
Changing the configuration
Section titled “Changing the configuration”- Service running: edit the file, then run
provefab service install --workers N. It reloads the service and takes the currentPATH. - New tool installed (
claude,codex,cargo…): same command, so the service finds it. - New repository:
provefab repos add <owner/name>, or add a[[repos]]block by hand; then runprovefab doctor, thenprovefab run --dry-runto see how its issues would be handled.