Docs / Reference / ade.toml

ade.toml

One file configures a project: providers and models per role, the test command, protected paths and rigour per path. This is the template ade init writes, comments and all; edit it, then rerun ade init.

TOML
# Atlas ADE configuration. `ade init` writes this once; edit it, then rerun `ade init` to
# regenerate .claude/settings.json, .claude/agents/ and AGENTS.md from it.

[git]#

TOML
[git]
main = "main"
# ade pushes to this remote, and only trunk, the ade-records branch and tags matching push_tags: no force, no deletes,
# fast-forward only (`ade push`). A repo without this remote is simply not pushed.
remote = "origin"
# "land": every landing pushes once it has merged; a refused push is printed and the card stays landed.
# "manual": only `ade push` pushes.
push = "land"
push_tags = ["checkpoint/*"]
# Branch mirrors as "src:dst" (["master:main"]): dst follows src, fast-forward only, never forced or deleted. With a
# GitHub remote, ade init writes .github/workflows/ade-mirror.yml so GitHub moves dst after each push of src (no
# machine pushes dst); elsewhere ade push sends it. A first init copies these from remote.<remote>.push refspecs.
mirror = []

[tests]#

TOML
[tests]
# {tests} becomes the test files the graph selects, in one run; an empty selection runs the bare command.
# {test} instead runs the command once per selected file, for tests that are scripts (`npx tsx {test}`).
# {python} is the interpreter running ade, so the same line works on Windows, macOS and Linux.
command = "{python} -m unittest {tests}"
# Commands run first for every change that touches code, such as a typecheck (`npm run typecheck`).
always = []
# Used when a touched path is unknown to the graph or no test reaches it. Empty = whole suite.
critical = []
timeout = 600
# A failure whose output matches this gets one isolated retry; anything else fails at once.
timing = "timed? ?out|deadline exceeded|ETIMEDOUT|ECONNRESET|temporarily unavailable"

[lanes]#

TOML
[lanes]
# Gitignored folders a lane needs but a worktree does not check out, such as dependencies (["node_modules"],
# [".venv"]). Each lane links them from the main checkout (a junction on Windows) instead of installing again.
share = []

[rigour]#

TOML
[rigour]
# Paths whose output can cause harm: every test that reaches them runs. Everything else is orchestration-grade.
proof_grade = []
# Prose paths that land without review.
no_review = ["*.md", "docs/*"]

[graph]#

TOML
[graph]
out = "graph.json"
exclude = []
tests = ["tests/*", "test_*.py", "*/test_*.py", "*.test.*", "*.spec.*", "*/__tests__/*"]
# Scripts whose `ade <verb>` lines are bound to those verbs in the graph and checked at land, so a renamed verb or a
# new required argument cannot leave a script behind.
scripts = ["*.sh", "*.ps1", "*.cmd", "tools/*", "scripts/*", ".github/workflows/*"]
# What every added or changed code file should carry: "tests" (a test reaches it through imports) and "header"
# (the five-line header). `require` stops land until it is there; `note` lands and records what is missing.
# Tests are minimal: one robust invariant where it guards something lasting, never a literal
# example, so a missing test is noted, not blocked. Projects started on ade require the header.
require = []
note = ["tests"]
untested_ok = []

[guards]#

TOML
[guards]
# The bash guard (hook 2) refuses only these classes, in every permission mode. Secret files are never
# printed by a reader; pass them by path. Writers are refused on [protected] paths.
secrets = [".env", ".env.*", "*.pem", "*.key", "id_rsa*", "id_ed25519*", "*.p12"]
secret_ok = [".env.example", ".env.sample", ".env.template"]
readers = ["cat", "less", "more", "head", "tail", "bat", "grep", "rg", "egrep", "awk", "sed", "cut", "sort",
           "xxd", "od", "hexdump", "strings", "base64", "nl", "type", "Get-Content", "gc", "jq", "yq"]
writers = ["tee", "cp", "mv", "rm", "truncate", "ln", "install", "dd"]

[protected]#

TOML
[protected]
# Edits by an agent are refused here; change them by hand, then rerun `ade init`.
paths = ["ade.toml", ".claude/settings.json", ".claude/settings.local.json", ".claude/agents/*", "AGENTS.md", ".git/*"]

[budgets]#

TOML
[budgets]
core_lines = 20000
hooks = 5
refusal_codes = 50
handover_tokens = 20000
brief_tokens = 250
core = ["*.py"]
core_exclude = ["tests/*", "test_*.py", "*/test_*.py"]

# Per-component line budgets: name = { globs, lines }. Every core file must match exactly one row;
# `ade budget` reports the rest as "unassigned" or "overlapping", each with a budget of 0. Empty = total only.

[budgets.components]#

TOML
[budgets.components]

# Providers are adapters: argv templates with {prompt} and {model}. `fake` is built in,
# deterministic and spends no usage; set ADE_PROVIDER=fake for tests and rehearsals.

[providers.claude]#

TOML
[providers.claude]
# {mode} is [permissions] lane_mode: lanes are isolated worktrees, and hooks guard edits and shell in every mode.
# --output-format json carries the call's own usage report (tokens, cache, cost, turns); ade keeps it locally.
cmd = ["claude", "-p", "{prompt}", "--model", "{model}", "--permission-mode", "{mode}", "--output-format", "json"]
timeout = 1800
# Dropped from the environment of each claude call: what a host Claude Code session (one that runs ade) sets, so
# the child runs as your own login, not as a child of that session. ANTHROPIC_BASE_URL is here because a host
# points it at its own proxy; if you set it yourself on purpose, take it out of this list.
scrub_env = ["CLAUDECODE", "CLAUDE_CODE_*", "CLAUDE_AGENT_SDK_*", "CLAUDE_PID", "CLAUDE_EFFORT", "CLAUDE_PLUGIN_DATA",
             "ANTHROPIC_BASE_URL"]

[providers.codex]#

TOML
[providers.codex]
# --json prints events with token counts; a rate_limits window, when one appears, feeds the week meter.
# Neither engine reported a week window in the first real runs (27 Oct), so meters read "week not reported" and show
# the kit's own 7-day spend instead. codex reports tokens but no cost; claude reports both.
cmd = ["codex", "exec", "--json", "-m", "{model}", "{prompt}"]
timeout = 1800

[conduct]#

TOML
[conduct]
# `ade conduct` routes each stop of the loop by ade/route.toml. A card that has woken a model this many times parks;
# a route wait gives up after this long and parks.
wakes = 2
wait = "30m"

[watch]#

TOML
[watch]
# Minutes a lane log may stay silent before the watcher reads it as stalled (`ade wait --lane`, `ade watch`).
stall = 10

[q]#

TOML
[q]
# The output store (.ade/q.sqlite): ade's own long output, and anything run as `ade q run -- CMD`, is kept whole
# there and shown as its head, its tail, error-looking lines and a handle to page through it (`ade q show q17`).
# Text within both caps prints whole.
cap_lines = 200
cap_bytes = 20000
head = 40
tail = 40
page_lines = 200
sql_rows = 200
keep_days = 14
# Claude Code's own cap on Bash output, written into .claude/settings.json env as BASH_MAX_OUTPUT_LENGTH when above
# 0 and the file sets none. 0 leaves the platform's default, which already keeps very long output out of context.
bash_chars = 0

[cockpit]#

TOML
[cockpit]
# "cli": the terminal board is the whole cockpit. "orca": also mirror each lane into Orca as a worktree with a
# terminal running `ade watch <card>`, and each stop as the worktree's status comment. "auto": orca when an Orca is
# running, cli when not. Orca never drives the loop; `ade cockpit --mode` overrides this for one run.
mode = "cli"

[usage]#

TOML
[usage]
# Your weekly share per provider: the fraction of each account's week the kit may use. The cockpit and status
# line show each meter against it once that provider reports a week window; until then, "week not reported" and the kit's 7-day spend.
share = { claude = 1.0, codex = 1.0 }

# The roster: one source for .claude/agents/ade-<role>.md and AGENTS.md. Models are family
# aliases (opus, sonnet, gpt-6-sol) so each provider resolves its latest release.

[roles.pilot]#

TOML
[roles.pilot]
provider = "claude"
what = "Flies the board: picks cards, answers needs-judgement stops, decides, lands."
models = { claude = "opus", codex = "gpt-6-sol" }
tools = "Read, Grep, Glob, Bash"

[roles.builder]#

TOML
[roles.builder]
provider = "claude"
what = "Builds one card in its lane: the smallest change that meets the card's done line."
models = { claude = "sonnet", codex = "gpt-6-sol" }
tools = "Read, Edit, Write, Grep, Glob, Bash"

[roles.reviewer]#

TOML
[roles.reviewer]
provider = "claude"
what = "Reviews one lane diff; answers APPROVE on the first line, or returns a patch."
models = { claude = "sonnet", codex = "gpt-6-sol" }
tools = "Read, Grep, Glob"

# Every rule carries its reason; settings.json cannot hold comments, so the reason lives here.

[permissions]#

TOML
[permissions]
# The mode `ade init` writes for your own sessions when none is set; bypassPermissions is allowed (one line away).
mode = "acceptEdits"
# The mode lane builders and reviewers run in.
lane_mode = "bypassPermissions"

[permissions.allow]#

TOML
[permissions.allow]
"Bash(ade *)" = "the kit's own verbs; each writes its own record and refuses with a cure"
"Bash(ade push*)" = "ade's own push: trunk, ade-records and [git] push_tags only, fast-forward only, never forced; raw git push stays unallowed"
"Bash(git status*)" = "read-only repository state"
"Bash(git diff*)" = "read-only; reviewers and builders read their own diff"
"Bash(git log*)" = "read-only history"
"Bash(python3 -m unittest*)" = "the configured test command; edit with [tests].command"

[permissions.deny]#

TOML
[permissions.deny]
"Read(./.env)" = "secrets are passed by path, never read"
"Read(./.env.*)" = "secrets are passed by path, never read"
"Bash(git push --force*)" = "history is never rewritten"
"Bash(rm -rf *)" = "destructive; lanes are removed by `ade land`"