Reference
Configuration reference
Every Handbooks setting, with its flag, environment variable, config-file key, type and default — generated from the registry.
Generated by pnpm run config:docs from the settings registry — do not hand-edit; packages/cli/src/docs-drift.test.ts fails the build if this drifts from SETTINGS.
Every setting resolves through the same layers, highest priority first: flag > shell env > .env > handbook.config.yaml > default. The first layer that supplies a value wins and every layer below it is ignored for that setting. Run handbook config — or handbook config --command <name> to see one subcommand only — to check what actually resolved and which layer it came from.
One camelCase key in the registry drives all three surfaces at once: a flag, an environment variable, and a config-file key. Prefixing any of them with a command name scopes that surface to one subcommand, and it is the same transform on all three — HANDBOOK_<KEY> becomes HANDBOOK_<COMMAND>_<KEY>, and key becomes <command>Key whether written flat or nested one level under <command>:. A setting marked (scoped) below only accepts the prefixed env name, because its meaning changes per command (--out, --lang in the skill package).
Three top-level settings point at the layers above, and are themselves outside the registry, resolved once before every other setting — which is also why none of them can be set by the thing they load: an --env key inside handbook.config.yaml, an --env-file line inside .env, or a --config key inside that same file would have nothing left to read it.
--env <name> (or HANDBOOK_ENV) selects a per-environment cascade — the only one of the three with both a flag and an environment-variable form, since it names an environment rather than pointing at one exact file.
--env-file <path> loads exactly that one file, bypassing the cascade below.
--config <path> names one exact config file, bypassing the environment-aware discovery below (default: the nearest handbook.config.yaml-family file found by walking up from the working directory, stopping at a repo boundary).
With no --env-file, the CLI loads a cascade of .env* files instead of one fixed file, highest precedence first. The existing applyEnvFile rule — never override a key already set — is what makes a cascade nothing more than "call it in this order, first file to set a key wins":
| # | file | who | scope | committed? |
|---|
| 1 | shell environment | — | — | always wins |
| 2 | .env.<name>.local | personal | this environment only | no (gitignored) |
| 3 | .env.<name> | team | this environment only | yes |
| 4 | .env.local | personal | every environment | no (gitignored) |
| 5 | .env | team | baseline | yes |
Rows 2 and 3 only apply when --env/HANDBOOK_ENV names an environment. With neither set, only rows 4 and 5 load — exactly what loaded before this cascade existed, so an existing setup with no .env.local sees no change at all.
--config aside, discovery still walks up from the working directory and stops at a repo boundary, but at every directory visited it now checks first for handbook.config.<name>.{yaml,yml,json} (only when an environment is named) before the plain handbook.config.yaml et al. — so a named file always wins over a plain file sitting in that same directory, even when a plain file exists at a level closer to the working directory. With no environment named, discovery is unchanged.
Run handbook config to see which environment is active and exactly which files it loaded, in precedence order — a cascade on top of four value layers is too many possible sources to track from memory, and a layer this command cannot show is no different from a layer that does not work.
Worked example for readWorkers (flag --read-workers <n>, default 12):
| surface | flat | scoped to generate |
|---|
| env | HANDBOOK_READ_WORKERS | HANDBOOK_GENERATE_READ_WORKERS |
handbook.config.yaml key | readWorkers | generateReadWorkers |
The config-file forms are interchangeable: a flat readWorkers: ... and a nested generate: { readWorkers: ... } mean the same thing, because the file is flattened by the same camelCase join before it is read.
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
source | --source <dir> | HANDBOOK_SOURCE | path | required | source root; required for analyze/generate/plan/apply, optional elsewhere (hash freshness for validate/skill, and the tree a backup belongs to for rollback) |
work | --work <dir> | HANDBOOK_WORK | path | required | work directory holding pipeline artifacts; optional for skill, where it adds coverage.json |
lang | --lang <lang> | HANDBOOK_LANG | enum (auto, plus any registered language) | auto | source language; auto detects and merges every registered language |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
llmApiKey | — | HANDBOOK_LLM_API_KEY, OPENAI_API_KEY | string | "" (empty) | API key for the LLM endpoint; use EMPTY for keyless local endpoints. Never a flag and never allowed in the config file |
llmProvider | --provider <name> | HANDBOOK_LLM_PROVIDER, OPENAI_PROVIDER | enum (openai|anthropic|gemini) | openai | LLM wire format; 'openai' covers every OpenAI-compatible endpoint (most of them) |
llmModel | --model <id> | HANDBOOK_LLM_MODEL, OPENAI_MODEL | string | gpt-4o-mini | model identifier |
llmBaseUrl | --base-url <url> | HANDBOOK_LLM_BASE_URL, OPENAI_BASE_URL | string | https://api.openai.com/v1 | any OpenAI-compatible endpoint (hosted, vLLM, LiteLLM, a proxy); a URL with embedded credentials is refused in the config file, which gets committed |
llmMaxTokens | --max-tokens <n> | HANDBOOK_LLM_MAX_TOKENS, OPENAI_MAX_TOKENS | int | 16000 | max output tokens per request |
llmTimeout | --timeout <sec> | HANDBOOK_LLM_TIMEOUT, OPENAI_TIMEOUT | int | 300 | per-request deadline in seconds; a stalled call is retried rather than allowed to hold a phase hostage |
llmMaxRetries | --llm-retries <n> | HANDBOOK_LLM_MAX_RETRIES | int | 6 | retry attempts per request; 0 means a single attempt |
llmRetryBackoff | --llm-retry-backoff <sec> | HANDBOOK_LLM_RETRY_BACKOFF | int | 3 | base backoff between retries, in seconds |
llmConcurrency | --llm-concurrency <n> | HANDBOOK_LLM_CONCURRENCY | int | 16 | global cap on concurrent requests through one client |
llmExtraBody | — | HANDBOOK_LLM_EXTRA_BODY, OPENAI_EXTRA_BODY | json | — | vendor fields merged into every request body; model/messages/token fields cannot be overridden. Free-form, so it is treated as a secret: never a flag and never allowed in the config file |
source | --source <dir> | HANDBOOK_SOURCE | path | required | source root; required for analyze/generate/plan/apply, optional elsewhere (hash freshness for validate/skill, and the tree a backup belongs to for rollback) |
work | --work <dir> | HANDBOOK_WORK | path | required | work directory holding pipeline artifacts; optional for skill, where it adds coverage.json |
lang | --lang <lang> | HANDBOOK_LANG | enum (auto, plus any registered language) | auto | source language; auto detects and merges every registered language |
phase | --phase <spec> | HANDBOOK_PHASE | string | all | all | 1 | 2 | 2a | 2b | 2c | 3, or a comma list |
strategy | --strategy <s> | HANDBOOK_STRATEGY | enum (file|member) | — | file (default) or member; unset keeps the work dir's recorded strategy |
skeleton | --skeleton <path> | HANDBOOK_SKELETON | path | — | user-authored skeleton.yaml, required for the member strategy |
narrateLang | --narrate-lang <l> | HANDBOOK_NARRATE_LANG | enum (en|zh|hi|es|pt|ru|ja|de) | en | prose language |
detail | --detail <d> | HANDBOOK_DETAIL | enum (brief|deep) | brief | card depth |
synthMode | --synth-mode <m> | HANDBOOK_SYNTH_MODE | enum (oneshot|doctor) | oneshot | skeleton synthesis mode |
maxDoctorRounds | --max-doctor-rounds <n> | HANDBOOK_MAX_DOCTOR_ROUNDS | int | 6 | doctor convergence rounds |
readWorkers | --read-workers <n> | HANDBOOK_READ_WORKERS | int | 12 | concurrent card batches |
readBatchSize | --read-batch-size <n> | HANDBOOK_READ_BATCH_SIZE | int | — | files per card batch; unset means 1 for --detail deep and 8 for brief |
maxCharsPerFile | --max-chars-per-file <n> | HANDBOOK_MAX_CHARS_PER_FILE | int | 0 | truncate each file at n chars; 0 means no limit |
assignBatchSize | --assign-batch-size <n> | HANDBOOK_ASSIGN_BATCH_SIZE | int | 25 | cards per assignment batch |
assignWorkers | --assign-workers <n> | HANDBOOK_ASSIGN_WORKERS | int | 12 | concurrent assignment batches |
organizeWorkers | --organize-workers <n> | HANDBOOK_ORGANIZE_WORKERS | int | 8 | concurrent stage-organize calls |
narrateWorkers | --narrate-workers <n> | HANDBOOK_NARRATE_WORKERS | int | 8 | concurrent narration calls |
resume | --resume | HANDBOOK_RESUME | bool | false | skip files that already have a completed card |
refresh | --refresh | HANDBOOK_REFRESH | bool | false | ignore phase-3 caches |
llmCache | --llm-cache | HANDBOOK_LLM_CACHE | bool | false | cache raw LLM replies under /phase3/cache; disabled by --refresh |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
work | --work <dir> | HANDBOOK_WORK | path | required | work directory holding pipeline artifacts; optional for skill, where it adds coverage.json |
title | --title <title> | HANDBOOK_TITLE | string | System Handbook | handbook title for rendered outputs |
out | --out <dir> | HANDBOOK_RENDER_OUT (scoped) | path | — | output location; render defaults to /handbook, plan writes a file, skill writes a directory |
html | --html | HANDBOOK_HTML | bool | false | also render the multi-page HTML site under /html |
htmlSingle | --html-single | HANDBOOK_HTML_SINGLE | bool | false | also render a single self-contained HTML page |
agentSite | --agent-site | HANDBOOK_AGENT_SITE | bool | false | also render the agent locator index under /agent |
llmsTxt | --llms-txt | HANDBOOK_LLMS_TXT | bool | false | also write llms.txt and llms-full.txt next to the markdown |
sourceBaseUrl | --source-base-url <url> | HANDBOOK_SOURCE_BASE_URL | string | — | link file cards to the source at / |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
source | --source <dir> | HANDBOOK_SOURCE | path | — | source root; required for analyze/generate/plan/apply, optional elsewhere (hash freshness for validate/skill, and the tree a backup belongs to for rollback) |
work | --work <dir> | HANDBOOK_WORK | path | — | work directory holding pipeline artifacts; optional for skill, where it adds coverage.json |
out | --out <dir> | HANDBOOK_SKILL_OUT (scoped) | path | required | output location; render defaults to /handbook, plan writes a file, skill writes a directory |
handbook | --handbook <dir> | HANDBOOK_SKILL_HANDBOOK (scoped) | path | required | rendered handbook directory; required for skill, optional context for plan |
name | --name <slug> | HANDBOOK_NAME | string | required | skill slug (lowercase-hyphen) |
project | --project <name> | HANDBOOK_PROJECT | string | — | human project name for prose |
agentDir | --agent-dir <dir> | HANDBOOK_AGENT_DIR | path | — | rendered agent locator site; ships under references/agent/ |
bodyLang | --lang <l> | HANDBOOK_SKILL_BODY_LANG (scoped) | enum (en|zh|hi|es|pt|ru|ja|de) | en | SKILL.md body language; frontmatter stays English for routing |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
source | --source <dir> | HANDBOOK_SOURCE | path | — | source root; required for analyze/generate/plan/apply, optional elsewhere (hash freshness for validate/skill, and the tree a backup belongs to for rollback) |
skill | --skill <dir> | HANDBOOK_SKILL | path | required | skill directory to validate |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
llmApiKey | — | HANDBOOK_LLM_API_KEY, OPENAI_API_KEY | string | "" (empty) | API key for the LLM endpoint; use EMPTY for keyless local endpoints. Never a flag and never allowed in the config file |
llmProvider | --provider <name> | HANDBOOK_LLM_PROVIDER, OPENAI_PROVIDER | enum (openai|anthropic|gemini) | openai | LLM wire format; 'openai' covers every OpenAI-compatible endpoint (most of them) |
llmModel | --model <id> | HANDBOOK_LLM_MODEL, OPENAI_MODEL | string | gpt-4o-mini | model identifier |
llmBaseUrl | --base-url <url> | HANDBOOK_LLM_BASE_URL, OPENAI_BASE_URL | string | https://api.openai.com/v1 | any OpenAI-compatible endpoint (hosted, vLLM, LiteLLM, a proxy); a URL with embedded credentials is refused in the config file, which gets committed |
llmMaxTokens | --max-tokens <n> | HANDBOOK_LLM_MAX_TOKENS, OPENAI_MAX_TOKENS | int | 16000 | max output tokens per request |
llmTimeout | --timeout <sec> | HANDBOOK_LLM_TIMEOUT, OPENAI_TIMEOUT | int | 300 | per-request deadline in seconds; a stalled call is retried rather than allowed to hold a phase hostage |
llmMaxRetries | --llm-retries <n> | HANDBOOK_LLM_MAX_RETRIES | int | 6 | retry attempts per request; 0 means a single attempt |
llmRetryBackoff | --llm-retry-backoff <sec> | HANDBOOK_LLM_RETRY_BACKOFF | int | 3 | base backoff between retries, in seconds |
llmConcurrency | --llm-concurrency <n> | HANDBOOK_LLM_CONCURRENCY | int | 16 | global cap on concurrent requests through one client |
llmExtraBody | — | HANDBOOK_LLM_EXTRA_BODY, OPENAI_EXTRA_BODY | json | — | vendor fields merged into every request body; model/messages/token fields cannot be overridden. Free-form, so it is treated as a secret: never a flag and never allowed in the config file |
source | --source <dir> | HANDBOOK_SOURCE | path | required | source root; required for analyze/generate/plan/apply, optional elsewhere (hash freshness for validate/skill, and the tree a backup belongs to for rollback) |
out | --out <dir> | HANDBOOK_PLAN_OUT (scoped) | path | — | output location; render defaults to /handbook, plan writes a file, skill writes a directory |
handbook | --handbook <dir> | HANDBOOK_PLAN_HANDBOOK (scoped) | path | — | rendered handbook directory; required for skill, optional context for plan |
request | --request <text> | HANDBOOK_REQUEST | string | required | the natural-language change request |
maxTurns | --max-turns <n> | HANDBOOK_MAX_TURNS | int | 30 | agent turn budget |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
source | --source <dir> | HANDBOOK_SOURCE | path | required | source root; required for analyze/generate/plan/apply, optional elsewhere (hash freshness for validate/skill, and the tree a backup belongs to for rollback) |
plan | --plan <file> | HANDBOOK_PLAN | path | required | plan file produced by handbook plan |
dryRun | --dry-run | HANDBOOK_DRY_RUN | bool | false | verify only, never write |
backupRoot | --backup-root <dir> | HANDBOOK_BACKUP_ROOT | path | — | where backups go; defaults to /.handbook-patches |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
source | --source <dir> | HANDBOOK_SOURCE | path | — | source root; required for analyze/generate/plan/apply, optional elsewhere (hash freshness for validate/skill, and the tree a backup belongs to for rollback) |
backup | --backup <dir> | HANDBOOK_BACKUP | path | required | backup directory containing manifest.json |
force | --force | HANDBOOK_FORCE | bool | false | restore even files that changed after the patch |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
llmApiKey | — | HANDBOOK_LLM_API_KEY, OPENAI_API_KEY | string | "" (empty) | API key for the LLM endpoint; use EMPTY for keyless local endpoints. Never a flag and never allowed in the config file |
llmProvider | --provider <name> | HANDBOOK_LLM_PROVIDER, OPENAI_PROVIDER | enum (openai|anthropic|gemini) | openai | LLM wire format; 'openai' covers every OpenAI-compatible endpoint (most of them) |
llmModel | --model <id> | HANDBOOK_LLM_MODEL, OPENAI_MODEL | string | gpt-4o-mini | model identifier |
llmBaseUrl | --base-url <url> | HANDBOOK_LLM_BASE_URL, OPENAI_BASE_URL | string | https://api.openai.com/v1 | any OpenAI-compatible endpoint (hosted, vLLM, LiteLLM, a proxy); a URL with embedded credentials is refused in the config file, which gets committed |
llmMaxTokens | --max-tokens <n> | HANDBOOK_LLM_MAX_TOKENS, OPENAI_MAX_TOKENS | int | 16000 | max output tokens per request |
llmTimeout | --timeout <sec> | HANDBOOK_LLM_TIMEOUT, OPENAI_TIMEOUT | int | 300 | per-request deadline in seconds; a stalled call is retried rather than allowed to hold a phase hostage |
llmMaxRetries | --llm-retries <n> | HANDBOOK_LLM_MAX_RETRIES | int | 6 | retry attempts per request; 0 means a single attempt |
llmRetryBackoff | --llm-retry-backoff <sec> | HANDBOOK_LLM_RETRY_BACKOFF | int | 3 | base backoff between retries, in seconds |
llmConcurrency | --llm-concurrency <n> | HANDBOOK_LLM_CONCURRENCY | int | 16 | global cap on concurrent requests through one client |
llmExtraBody | — | HANDBOOK_LLM_EXTRA_BODY, OPENAI_EXTRA_BODY | json | — | vendor fields merged into every request body; model/messages/token fields cannot be overridden. Free-form, so it is treated as a secret: never a flag and never allowed in the config file |
work | --work <dir> | HANDBOOK_WORK | path | required | work directory holding pipeline artifacts; optional for skill, where it adds coverage.json |
title | --title <title> | HANDBOOK_TITLE | string | System Handbook | handbook title for rendered outputs |
case | --case <dir> | HANDBOOK_CASE | path | required | case directory: edited/ + plan.md + change.diff |
useLlm | --no-llm | HANDBOOK_USE_LLM | bool | true | set false for a structural refresh only, with prose marked stale |
refreshRendered | --no-render | HANDBOOK_REFRESH_RENDERED | bool | true | set false to skip refreshing already-rendered outputs under /handbook |
corrections | --corrections <file> | HANDBOOK_CORRECTIONS | path | — | agent-reported corrections.jsonl; its files widen the refresh set |
cardDetail | --detail <d> | HANDBOOK_RESYNC_CARD_DETAIL (scoped) | enum (brief|deep) | — | card depth for regenerated cards; unset matches the existing handbook |
proseLang | --narrate-lang <l> | HANDBOOK_RESYNC_PROSE_LANG (scoped) | enum (en|zh|hi|es|pt|ru|ja|de) | — | prose language for regenerated cards; unset matches the existing handbook |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
llmApiKey | — | HANDBOOK_LLM_API_KEY, OPENAI_API_KEY | string | "" (empty) | API key for the LLM endpoint; use EMPTY for keyless local endpoints. Never a flag and never allowed in the config file |
llmProvider | --provider <name> | HANDBOOK_LLM_PROVIDER, OPENAI_PROVIDER | enum (openai|anthropic|gemini) | openai | LLM wire format; 'openai' covers every OpenAI-compatible endpoint (most of them) |
llmModel | --model <id> | HANDBOOK_LLM_MODEL, OPENAI_MODEL | string | gpt-4o-mini | model identifier |
llmBaseUrl | --base-url <url> | HANDBOOK_LLM_BASE_URL, OPENAI_BASE_URL | string | https://api.openai.com/v1 | any OpenAI-compatible endpoint (hosted, vLLM, LiteLLM, a proxy); a URL with embedded credentials is refused in the config file, which gets committed |
llmMaxTokens | --max-tokens <n> | HANDBOOK_LLM_MAX_TOKENS, OPENAI_MAX_TOKENS | int | 16000 | max output tokens per request |
llmTimeout | --timeout <sec> | HANDBOOK_LLM_TIMEOUT, OPENAI_TIMEOUT | int | 300 | per-request deadline in seconds; a stalled call is retried rather than allowed to hold a phase hostage |
llmMaxRetries | --llm-retries <n> | HANDBOOK_LLM_MAX_RETRIES | int | 6 | retry attempts per request; 0 means a single attempt |
llmRetryBackoff | --llm-retry-backoff <sec> | HANDBOOK_LLM_RETRY_BACKOFF | int | 3 | base backoff between retries, in seconds |
llmConcurrency | --llm-concurrency <n> | HANDBOOK_LLM_CONCURRENCY | int | 16 | global cap on concurrent requests through one client |
llmExtraBody | — | HANDBOOK_LLM_EXTRA_BODY, OPENAI_EXTRA_BODY | json | — | vendor fields merged into every request body; model/messages/token fields cannot be overridden. Free-form, so it is treated as a secret: never a flag and never allowed in the config file |
port | --port <n> | HANDBOOK_PORT | int | 4860 | port to listen on; 0 picks any free port |
host | --host <addr> | HANDBOOK_HOST | string | 127.0.0.1 | bind address; stays on loopback unless you set it (containers need 0.0.0.0). The CSRF guard still requires a loopback Host header |
stateDir | --state-dir <dir> | HANDBOOK_STATE_DIR | path | — | where studio.json and managed work dirs live; defaults to $HOME/.handbook-studio |
| key | flag | env | type | default | doc |
|---|
logLevel | — | HANDBOOK_LOG_LEVEL | enum (debug|info|warn|error|silent) | info | log verbosity; -v/--verbose and -q/--quiet are shorthand for debug/error |
forCommand | --command <name> | HANDBOOK_FOR_COMMAND | string | — | show only the settings that apply to this subcommand; its env/file/default layers are inspectable here, but that command's own flags are not (pass them to the command itself) |
json | --json | HANDBOOK_JSON | bool | false | machine-readable output |
check | --check | HANDBOOK_CHECK | bool | false | validate only; exit non-zero if anything is invalid or missing |