Configuration
Every config key, default, and env var — file locations, scope, and precedence.
On this page
File locations and precedence
Configuration is layered JSON, merged with later layers winning:
built-in defaults
→ ~/.arcturn/config.json (user scope)
→ <cwd>/.arcturn/config.json (project scope, skipped if cwd is the user root)
→ ARCTURN_MODEL environment variableMost keys simply overwrite (project beats user, env beats both). Three are different:
permissionsaccumulates across layers instead of replacing — a user rule and a project rule can both apply. See Permissions for the rule schema and how the four permission modes interact with it.hooksalso accumulates — a user-level hook and a project-level hook both fire for the same lifecycle event. See Lifecycle hooks.permissionModeis clamped: a project layer may only narrow it, never widen it. From narrowest to widest the modes areplan→default→acceptEdits→yolo, so a repository asking forplanis honored while one asking foryolowhen you are atdefaultis ignored with a warning. Otherwise a checked-in config would switch off your permission prompts for anyone who cloned the repository. To run a project at a wider mode deliberately, set it in your own~/.arcturn/config.jsonor pass--permission-mode, which wins over both layers in either direction.
A malformed or unreadable config file is a warning, never a crash: Arcturn falls back to
the layers it could read. An unrecognized top-level key is likewise a warning (unknown config key "..." (ignored)), not a hard failure — so a typo doesn't stop the CLI from
starting, and upgrading Arcturn never breaks on an older config written for a newer key.
Override where the user directory itself lives with ARCTURN_HOME (default ~/.arcturn).
Config key reference
Every key .arcturn/config.json accepts, in both user and project files:
| Key | Type | Default | Notes |
|---|---|---|---|
model |
string | string[] |
"anthropic/claude-sonnet-5-5" |
An array is a failover chain: first entry primary, later ones tried on overload/rate-limit/unreachable. |
permissionMode |
"default" | "acceptEdits" | "plan" | "yolo" |
"default" |
Starting permission mode. A project layer may only narrow it (see above). See Permissions. |
permissions |
PermissionRule[] |
[] |
Accumulates across layers. See Permissions. |
thinking |
"off" | "low" | "medium" | "high" | "xhigh" | "max" |
"off" |
Extended-thinking level for thinking-capable models. A level the model does not accept is clamped down to the nearest one it does. See Extended thinking. |
providers.anthropic.fallbacks |
"default" |
(unset) | Opt in to Anthropic's server-side refusal fallback for models that support it. See Refusals and server-side fallbacks. |
providers.anthropic.thinkingBinding |
"error" | "drop_block" |
(unset) | First-party anthropic only. Sends thinking.block_binding.prefix_mismatch_behavior (beta thinking-binding-controls-2026-08-01): "error" fails a request whose history was edited after a thinking block was produced (useful in CI), "drop_block" lets the API drop the unusable block and carry on. What it reports surfaces as a notice. Setting it also makes contextEditing.mode default to "auto". Ignored by the compatible, Bedrock and Vertex adapters. See Preserved thinking. |
compaction.shape |
"simple" | "keep-tail" | "server" |
per model | How compaction rewrites history. Default is simple (one summary replaces everything) on Claude 4.6+ through anthropic, anthropic-compatible, bedrock and vertex, keep-tail elsewhere. server lets the provider compact (Claude API only). See Context management. |
contextEditing.enabled |
boolean |
true |
Clear stale tool results from the outgoing request. false turns it off. |
contextEditing.mode |
"local" | "server" | "auto" |
"local" ("auto" if providers.anthropic.thinkingBinding is set) |
Who clears old tool results: arcturn in recorded passes, the provider (clear_tool_uses_20250919, Claude API only), or whichever fits the model. arcturn doctor shows the choice and why. See Context management. |
contextEditing.minCharsPerPass |
integer ≥ 0 |
50000 |
Characters a local elision pass must save before it runs. Each pass rewrites already-sent history once, so passes are batched. |
contextEditing.keepRecentTurns / .minCharsToElide / .maxTotalToolResultChars / .protectToolNames |
integer / integer / integer / string[] |
3 / 1000 / 100000 / ["todo", "plan"] |
Which results are eligible: trailing turns kept verbatim, smallest result worth stubbing, history size before editing starts, tools never elided (replaces the default list). |
theme |
string |
"dark" |
"dark", "light", or a custom theme file name under ~/.arcturn/themes / <cwd>/.arcturn/themes. |
ui |
"screen" | "inline" |
"screen" |
"screen" is the full-screen, alternate-screen TUI; "inline" is the classic bottom-of-scrollback block. |
systemPromptAppend |
string |
(none) | Extra text appended verbatim to the system prompt. |
hooks |
HookConfig |
{} |
Lifecycle hooks. Accumulates across layers. See Lifecycle hooks. |
lsp |
"off" | "on" |
"off" |
Language-server diagnostics after write/edit. See LSP diagnostics. |
sandbox |
"off" | "workspace-write" |
"off" |
OS filesystem sandbox for bash's foreground commands. See Tools. |
maxCostUsd |
number ≥ 0 |
(unset = no limit) | Abort a run once it has cost this many USD. Same guard as --max-cost. |
maxTurns |
integer > 0 |
200 (core default) |
Turn ceiling for a run. Same guard as --max-turns. Shortly before the ceiling bites, the run is warned in-conversation so it wraps up and delivers instead of being cut off mid-work — see The wrap-up warning for the trip point, and for why a ceiling of 1 or 2 gets no warning at all. |
subagentMaxTurns |
integer > 0 |
64 |
Turn ceiling for one delegated sub-agent or scout. |
requestStallTimeoutMs |
integer ≥ 0 |
120000 |
Abort a streaming LLM request that emits no event for this long — a stalled/dead socket, not a slow one — and retry or fail it over as a transient network error. Not a total-duration cap: a long, actively streaming turn (extended thinking, a big response) is never interrupted, because the timer resets on every event. 0 disables the guard. |
verify |
string | VerifyConfig |
(unset) | Command run after edits; failures are fed back to the model. String is sugar for { command }. See below. |
audit |
boolean |
false |
Record an append-only audit trail per session. See Audit trail and arcturn audit. |
insights |
boolean |
true |
Keep the local insights ledger (~/.arcturn/insights/) — parks, silent turns, step failures, run costs. Names and numbers only; no prompts, reasoning, paths or content. false writes nothing at all. See Insights. |
retro |
{ auto?, model? } |
{ auto: true } |
auto controls whether /workflow prints the retro hint after a run with a park, a failed step or a retried step; model overrides the model arcturn retro's read-only sub-agent runs on (default: the configured judgment tier, else the subagent route). See Retro. |
brain.enabled |
boolean |
true |
Build, inject and expose the distilled repository map every agent reads. false builds nothing, injects nothing and does not register the brain tool. See Project brain. |
brain.autoRefresh |
boolean |
true |
Refresh the brain — and distil that run's lessons into runs.md — after a workflow run reaches a terminal status. Paused runs are skipped. |
brain.maxChars |
integer > 0 |
6000 |
Character budget for the fenced brain block injected into system prompts. |
brain.maxDirs |
integer > 0 |
40 |
How many directories get their own note. Busiest first; a directory over 80 files is split one level deeper, to a depth of three. |
brain.model |
string |
(unset) | Catalog id or tier:<name> for the read-only distiller sub-agent. Unset uses the fast tier when route.tiers.fast is configured, else the subagent route. |
provenance |
boolean |
false |
Record reasoning-level provenance so arcturn blame <file> can explain a line. See Provenance. |
dryRun |
boolean |
false |
Route file mutations to a shadow copy for review instead of the real tree. Same as --dry-run. See Dry-run mode. |
speculation |
boolean |
false |
Keep editing speculatively while a permission prompt is open. |
route |
RouterConfig |
{} |
Per-role model overrides (main, subagent, compaction, title), plus tiers and advisor: { executor, advisor } (an executor model consults a stronger advisor model; first-party Anthropic only). See below and Model routing. |
providers.anthropic.toolSearch |
"bm25" | "regex" |
"bm25" |
Which native tool-search variant deferred tools use on first-party Anthropic. See Deferred tools. |
websearch |
{ provider: "anthropic" | "builtin", maxUses?: integer } |
{ provider: "builtin" } |
Serve websearch with the provider's own web search where the model and platform have one; otherwise the built-in tool stays. See Tools. |
webfetch |
{ provider: "anthropic" | "builtin", maxUses?: integer } |
{ provider: "builtin" } |
The same for fetch. "anthropic" is honoured only from your user config. |
skills.synthesisModel |
string |
(unset) | Catalog id or tier:<name> the arcturn skill synthesize / /skills synthesize drafting sub-agent uses. Unset uses the fast tier when route.tiers.fast is configured, else the subagent route. See Synthesizing a skill from a run. |
providers |
Record<string, ProviderEntry> |
{} |
Extra provider endpoints — a gateway, a vLLM cluster, Ollama elsewhere — reachable as <name>/<model>. Merged key-wise with the user layer winning, unlike route. A project-declared entry is inert until approved. See below and Providers. |
sessionTitles |
boolean |
true |
Generate a session title with one small LLM call (on the title route) after an interactive session's first completed run. false turns the call off. See Sessions. |
taint |
"off" | "warn" | "confirm" | "deny" |
"warn" |
How to treat a mutating call that echoes untrusted fetched content. See Injection defense. |
canary |
"off" | "warn" | "deny" |
"off" |
How to treat an outbound call carrying a planted canary token. See Injection defense. |
canaries |
string[] |
(unset) | Literal values that must never leave this machine; concatenates across layers. |
consensus |
{ models: string[], sampleRate?, similarityThreshold? } |
(unset) | Cross-check sampled turns against extra models; costs one extra call per listed model per sampled turn. |
In "screen" mode the transcript scrolls inside the app: mouse wheel, PgUp/PgDn, and — while
the view is scrolled up — Home and End; sending a prompt returns to the live tail. A pure
vertical scroll is painted by moving the terminal's own rows (a DECSTBM scroll region) rather
than repainting the screen, which is what keeps a flick smooth on a slow connection. If your
terminal misrenders it, ARCTURN_NO_SCROLL_REGION=1 turns that shortcut off and every frame
takes the plain row diff.
Any key not in this table is rejected with a warning and ignored — there is no silent
extension surface beyond systemPromptAppend.
verify
{ "verify": "pnpm test" }{
"verify": {
"command": "pnpm typecheck",
"globs": ["*.ts", "*.tsx"],
"timeoutMs": 60000,
"runOn": "edit"
}
}command— shell command, run in the runtime's working directory through the platform's shell:/bin/sh -con macOS/Linux,%ComSpec% /d /s /c(cmd.exeby default) on Windows. See Platform support for what that means for a command written with POSIX syntax.commandis not portable across the two shells — neither is a lifecycle hook's command, for the same reason..arcturn/config.jsonis plain JSON with no per-platform branching, so a config checked in for a team that develops on both macOS/Linux and Windows can't put a shell one-liner straight incommand; point it instead at something that is portable — annpm/pnpmscript ("pnpm verify", with the platform-specific part insidepackage.json) or a Node script invoked directly ("node scripts/verify.mjs").globs— restricts which edited paths trigger the command. This is a simple suffix/segment check, not full glob syntax: a leading*("*.ts") matches a path ending with the rest of the pattern; anything else matches the whole path, a trailing path suffix, or an exact path segment. There is no**and no mid-pattern*. Omitted or empty matches every edited path.timeoutMs— defaults to 60000 (60s).runOn—"edit"(default: runs after every successfulwrite/edit) or"manual".
A passing verify appends nothing to the tool result (quiet on green); a failing one
appends the failure, roughly the trailing 40 lines, so the model sees its own breakage on
the very next turn. It never turns a successful write/edit into a failure.
route
Route a cheaper model to work that doesn't need the flagship:
{
"route": {
"subagent": "anthropic/claude-haiku-5-5",
"compaction": "anthropic/claude-haiku-5-5",
"title": "anthropic/claude-haiku-5-5"
}
}Each of main, subagent, compaction, title is an independent model id. An absent
key falls back to main's route, and an absent main falls back to whatever model the
session is actually running (typically what --model resolved to). A stale or typo'd id
never throws or blocks startup — it falls back to the main model and surfaces a warning.
You don't have to edit this block by hand: /model route --auto picks a cheaper
same-provider model and writes route.subagent and route.compaction into your user
config (merging into the existing block), and /model route <kind> <id> /
/model route clear [kind] manage single keys. See
Model routing for the full precedence.
Provider API keys
Arcturn resolves an API key for a model in this order:
- A per-provider key passed explicitly to the client.
- A shared explicit key passed to the client.
- The model spec's own
apiKeyEnv, if it names one. - The provider's default environment variable.
- Provider-specific fallback variables.
A model declared in a providers config block skips that ladder entirely. Such a spec
is registered key-exclusive: its apiKeyEnv is the only variable consulted, and an unset
one means no key rather than a fall-through to OPENAI_API_KEY / ANTHROPIC_API_KEY or to
an explicit client-wide key. A file that names an endpoint and a credential gets exactly
the credential it named.
The default variables per provider:
| Provider | Default env var | Fallbacks |
|---|---|---|
anthropic |
ANTHROPIC_API_KEY |
ANTHROPIC_AUTH_TOKEN |
openai |
OPENAI_API_KEY |
— |
google |
GOOGLE_API_KEY |
GEMINI_API_KEY, GOOGLE_GENAI_API_KEY |
openai-compatible |
OPENAI_API_KEY |
— |
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
export GOOGLE_API_KEY=...An API key is the only credential Arcturn takes for a model provider. There is no subscription sign-in — see Model providers.
The model catalog
Arcturn ships a curated catalog of ModelSpecs — context window, max output tokens, cost
per million tokens, and capability flags (tools, vision, thinking, caching) — for
every model it supports out of the box, keyed as <provider>/<model>:
anthropic/claude-fable-5-1
anthropic/claude-opus-5-5
anthropic/claude-sonnet-5-5
anthropic/claude-haiku-5-5
anthropic/claude-opus-4-8
anthropic/claude-sonnet-4-6
openai/gpt-5.1
openai/gpt-5.1-codex
google/gemini-3-pro-preview
google/gemini-2.5-flashPick one on the command line (arcturn --model anthropic/claude-sonnet-5-5), in-session
(/model openai/gpt-5.1), or in code via requireModel(id) from @arcturn/ai.
Switching models mid-session is recorded as a state entry in the session tree, so
resuming later restores the model you were using. arcturn --list-models prints the full
catalog and exits; arcturn --list-providers prints every provider and preset endpoint.
OpenAI-compatible endpoints
Any endpoint that speaks the OpenAI chat-completions wire format — a local model server,
a self-hosted gateway, a third-party router — works as an openai-compatible provider,
and an Anthropic-Messages endpoint works as anthropic-compatible.
Declare one in the providers config key:
{
"providers": {
"mycorp": {
"baseUrl": "https://llm.corp.internal/v1",
"apiKeyEnv": "MYCORP_LLM_KEY",
"protocol": "openai",
"label": "MyCorp Gateway",
"models": [{ "model": "llama-70b", "contextWindow": 128000 }]
}
}
}Ids are namespaced <name>/<model>, so arcturn --model mycorp/llama-70b works and the
entry shows up in --list-models, --list-providers, /model and arcturn doctor.
Omitting models is fine: ids then pass through to the wire verbatim.
apiKeyEnv is required for a remote endpoint (a loopback one may omit it and is then
contacted with no credential), baseUrl must be https: (or loopback http:) and free of
control characters, and a project file may not name a variable holding a credential you
keep for another service — including any built-in preset's, compared case-insensitively. A
project-declared endpoint is also parsed-but-inert until you approve it once.
Providers has the full rule list, and
Permissions has the consent
model.
Registering from code still works and still wins: openaiCompatible({ model, baseUrl, apiKeyEnv }) in @arcturn/ai — see Embedding with the SDK — runs after the
config block, so an extension can override a config entry. Once registered, either way
behaves like any other catalog entry, with the same cost/context-window bookkeeping.
Command-line flags
Every flag arcturn accepts. Long flags may be written --flag value or --flag=value;
boolean flags accept a --no- prefix.
| Flag | Alias | Takes | Description |
|---|---|---|---|
--print |
-p |
— | Non-interactive: run to completion, print the final assistant message, exit. |
--output-format <fmt> |
text | json |
With --print: text (default) prints the final message; json emits NDJSON of every agent event. Requires --print. |
|
--model <id> |
-m |
value | Model to use (see --list-models). |
--continue |
-c |
— | Resume the most recent session in this directory. Mutually exclusive with --resume. |
--resume <sessionId> |
-r |
value | Resume a specific session. |
--permission-mode <mode> |
default | acceptEdits | plan | yolo |
Starting permission mode. | |
--cwd <dir> |
value | Working directory for tools, config, and sessions. | |
--no-mcp |
— | Do not start any configured MCP servers. | |
--max-turns <n> |
integer > 0 | Stop a run after n model turns. | |
--max-cost <usd> |
number > 0 | Abort the run once it has cost this much. | |
--dry-run / --no-dry-run |
— | Send file edits to a shadow copy; review with /diff. |
|
--host <iface> |
value | With serve: interface to bind (default 127.0.0.1). |
|
--port <n> |
0–65535 | With serve: port to bind (default 7717). |
|
--token <secret> |
value | With serve: shared auth token (generated if omitted). |
|
--web |
— | With serve: also serve the browser client. |
|
--web-port <n> |
0–65535 | Port for the browser client (0 or omitted picks one). | |
--web-origin <origin> |
value, repeatable | Extra browser origin allowed to open a socket. Repeat to allow several. | |
--cassette <file> |
value | With bisect: the VCR recording to compare against. |
|
--record <file> |
value | Record this run's model and tool calls to a cassette. | |
--list-models |
— | Print the model catalog and exit. | |
--list-providers |
— | Print every provider and preset endpoint, and exit. | |
--help |
-h |
— | Show help. |
--version |
-v |
— | Print the version. |
Positional commands (arcturn <command> ...) take the place of a prompt: auth,
completions, replay, audit, insights, blame, bisect, serve, acp, attach. See arcturn --help for each, and Sessions, Replay & bisect,
Provenance, and Audit trail for the ones owned by
other pages.
Anything not recognized as a flag or a positional command becomes prompt text — including
everything after a literal --.
Other environment variables
| Variable | Effect |
|---|---|
ARCTURN_MODEL |
Overrides the configured model (wins over every config layer). |
ARCTURN_HOME |
Overrides ~/.arcturn as the user-scope root. |
BRAVE_API_KEY |
Used by the websearch tool when present; falls back to scraping DuckDuckGo otherwise. |
MCP config
A server's entry also takes "sampling": true (default off) to let that server ask the
session's model for a completion, billed to the session; see
MCP: servers that ask back.
MCP servers are declared in a JSON file shaped { "servers": { ... } }, read from
~/.arcturn/mcp.json merged with <cwd>/.arcturn/mcp.json — see MCP for the
full schema and ${ENV_VAR} expansion rules.
Extended thinking
thinking (config key) or --thinking <level> is not currently a CLI flag — set it via
.arcturn/config.json's thinking key (off, low, medium, high, xhigh, max) to
control how much extended reasoning a thinking-capable model does before it answers or
calls a tool. This maps to the ThinkingLevel type shared by every provider adapter, so the
same setting works regardless of which model you're running.
Each model declares the levels it accepts, and a level it lacks is clamped down to the
nearest one it has: Claude Opus 4.6 and Sonnet 4.6 have no xhigh, so xhigh runs as
high there; OpenAI models stop at high, and Gemini tops out at HIGH (a 32K budget).
What off means is per model, and three of them changed. Some Claude models think
unless told not to, so off has to be sent as something:
| Model | off is sent as |
|---|---|
| Claude 4.6 – 4.8, and the budget-era models | thinking omitted — these do not think by default |
| Claude Sonnet 5, Haiku 5.5 | thinking: {type: "disabled"} (Sonnet 5 used to run adaptive thinking under off) |
| Claude Sonnet 5.5 | thinking: {type: "between_tools"} |
| Claude Fable 5 / 5.1, Opus 5, Opus 5.5 | no thinking key and effort: "low" — thinking cannot be disabled on these (Opus 5 used to run at its default effort under off) |
Adaptive requests to Opus 4.7 and later ask for display: "summarized", because the API's
default became omitted and the thinking pane would otherwise be blank. Opus 5.5 always
carries an explicit effort — its API default is medium, not high.
Compaction
Arcturn automatically compacts the conversation before it would overflow the model's
context window, reserving 16,384 tokens of headroom. What it keeps depends on
compaction.shape: on Claude with adaptive thinking (4.6 and later) one summary replaces
the whole history, because the thinking on those models is bound to the history a summary
replaces; elsewhere it keeps roughly the most recent 20,000 tokens verbatim beside the
summary. All of it is tunable; see Sessions for the knobs and
how a compaction is recorded in the session tree, and
Context management for the reasoning.
Writing a permission rule or setting from code
persistSetting(key, value, scope, paths) and persistPermissionRule(rule, paths) in
@arcturn/cli's config module write one key or one rule back to the right file for its
scope ("project" → <cwd>/.arcturn/config.json, otherwise ~/.arcturn/config.json) —
this is what the TUI's "always allow" permission prompt and /config commands call under
the hood. Session-scoped permission rules are intentionally never written to disk: they
live only in the running process's permission engine.