Skip to content

Configuration files

Jarvis Code CLI writes all long-term preferences — which model to use, which API key to fill in, how many steps an Agent can run per turn — into TOML (a plain-text configuration format with a clear structure) files. Change them once and they take effect on every startup. Agent and runtime settings live in config.toml; terminal-UI and client preferences (theme, editor, notifications, auto-update) live in a companion tui.toml.

Default location: ~/.jarvis-code/config.toml, created automatically on first run.

Config file location

The CLI reads configuration from ~/.jarvis-code/config.toml. To relocate the data directory, override it with the JARVIS_CODE_HOME environment variable:

sh
export JARVIS_CODE_HOME=/path/to/jarvis-code-home

The config file path then becomes $JARVIS_CODE_HOME/config.toml. Regardless of where the directory lives, the file name is always config.toml.

TIP

TOML field names always use snake_case, for example default_model and max_context_size. If a key contains ., you must quote it — for example [models."gpt-4.1"] — otherwise TOML treats . as a nested table separator.

Complete example

The following example covers the most commonly used configuration fields. You can copy it and adjust as needed:

toml
default_model = "jarvis-code/k3"
default_permission_mode = "manual"
default_plan_mode = false
merge_all_available_skills = true
telemetry = false

[providers."managed:jarvis-code"]
type = "kimi"
base_url = "https://api.kimi.com/coding/v1"
api_key = ""

[models."jarvis-code/k3"]
provider = "managed:jarvis-code"
model = "k3"
max_context_size = 1048576
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]
display_name = "K3"
support_efforts = [ "low", "high", "max" ]
default_effort = "max"

[models."jarvis-code/kimi-for-coding"]
provider = "managed:jarvis-code"
model = "kimi-for-coding"
max_context_size = 262144
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]

[models."jarvis-code/kimi-for-coding-highspeed"]
provider = "managed:jarvis-code"
model = "kimi-for-coding-highspeed"
max_context_size = 262144
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]

[thinking]
enabled = true
effort = "high"
keep = "all"

[loop_control]
max_attempts_per_step = 10
reserved_context_size = 50000

[background]
max_running_tasks = 4
keep_alive_on_exit = false

[services.moonshot_search]
base_url = "https://api.kimi.com/coding/v1/search"
api_key = ""

[services.moonshot_fetch]
base_url = "https://api.kimi.com/coding/v1/fetch"
api_key = ""

[[permission.rules]]
decision = "allow"
pattern = "Read"

[[permission.rules]]
decision = "deny"
pattern = "Bash(rm -rf*)"

[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.jarvis-code/hooks/check-bash.mjs"
timeout = 5

Top-level fields

Fields in the config file fall into two categories: top-level scalars that directly control default behavior, and nested tables (providers, models, thinking, etc.) that each have their own structure, described individually in the sections below.

FieldTypeDefaultDescription
default_modelstringDefault model alias; must be defined in models
default_permission_modestringmanualDefault permission mode for new sessions; one of manual (prompt each time), yolo (auto-approve tool actions, but the agent may still ask questions), or auto (fully autonomous — the agent decides everything without asking)
default_plan_modebooleanfalseWhether new sessions start in Plan mode (produce a plan before executing) by default
merge_all_available_skillsbooleantrueWhether to merge Agent Skills from all available directories
extra_skill_dirsarray<string>Extra skill search directories, layered on top of the default directories
extra_agent_dirsarray<string>Extra custom agent search directories, layered on top of the default directories
builtin_product_skillsbooleantrueWhether the built-in skills that document Jarvis Code itself are offered to the model: update-config, custom-theme, mcp-config, check-jarvis-code-docs, and import-from-cc-codex. Turning them off trims their names and descriptions from the system prompt, at the cost of the guided flows for those tasks. Read by the default agent-core-v2 engine; ignored when JARVIS_CODE_LEGACY_FLAG=1 selects the legacy engine
telemetrybooleanfalseWhether anonymous telemetry is enabled; it remains disabled unless explicitly set to true
providerstable{}API provider table → providers
modelstableModel alias table → models
thinkingtableDefault parameters for Thinking mode → thinking
loop_controltableAgent loop control parameters → loop_control
backgroundtableBackground task runtime parameters → background
tasktableCurrent name for the background-task settings; it is merged over [background] when both are present → task
subagenttableAgent subagent limits → subagent
swarmtableAgentSwarm subagent limits → swarm
secondary_modeltableSubagent model pool → secondary_model
visual_modeltableModel used to inspect media on text-only models → visual_model
workflowstableDynamic workflow limits and directories → workflows
mcptableGlobal MCP timeouts → mcp
token_countingtableContext-size reporting strategy → token_counting
toolstableGlobal tool switch → tools
imagetableImage compression parameters → image
memorytablePersistent-memory limits → memory
servicestableBuilt-in external service configuration → services
permissiontableInitial permission rules → permission
hooksarray<table>Lifecycle hooks; see Hooks
identitytableCustom agent identity → identity
model_catalogtableProvider model-catalog refresh settings → model_catalog
crontableScheduled-task runtime settings → cron
experimentaltable<string, boolean>{}Per-flag experimental-feature overrides → experimental

The following sections cover each nested table in turn.

Note

The engine reads config.toml through a per-section registry: an unknown top-level key is passed through untouched rather than rejected, and jarvis doctor reports it as a warning. A registered section that fails its own schema is an error.

providers

Each entry in the providers table defines an API provider, keyed by a unique name. The CLI reads credentials only from here — it does not fall back to shell environment variables automatically. Running export JARVIS_API_KEY in the terminal does not give any provider its key; you must write it explicitly in the config file (see Config overrides).

FieldTypeRequiredDescription
typestringYesProvider type: kimi, anthropic, openai, openai_responses, google-genai, vertexai
model_source"static" | "discover" | "oauth-catalog"NoHow the provider supplies its models
api_keystringNoAPI key, written in plain text in the config file
base_urlstringNoAPI base URL
default_modelstringNoProvider-default model identifier
oauthtableNoOAuth credential reference (storage and key fields); injected automatically by the login flow — normally no need to write this by hand
envtable<string, string>NoFallback source for provider credentials; see below
custom_headerstable<string, string>NoCustom HTTP headers attached to each request
sourcetable<string, unknown>NoProvider source metadata retained by catalog imports

env sub-table: You can write provider-conventional key names (such as JARVIS_API_KEY) inside [providers.<name>.env] as a fallback source for api_key / base_url. This sub-table is read only from the config file and does not modify the shell environment:

toml
[providers.kimi.env]
JARVIS_API_KEY = "sk-xxx"
JARVIS_BASE_URL = "https://api.moonshot.ai/v1"

Priority: api_key field > env sub-table key > if both are absent, startup fails with an error.

models

Each entry in the models table defines a model alias (the name used in default_model or the -m flag), keyed by a unique name.

FieldTypeRequiredDescription
providerstringYesName of the provider to use; must be defined in providers
modelstringYesModel identifier sent to the server when calling the API
provider_idstringNoProvider catalog identifier
namestringNoCatalog model name
aliasesarray<string>NoAdditional aliases for the model
api_keystringNoPer-model API key
oauthtableNoPer-model OAuth credential reference
protocolstringNoPer-model protocol override
max_context_sizeintegerYesMaximum context length in tokens; must be at least 1
max_input_sizeintegerNoDeclared per-request input limit when it sits below the total window (e.g. gpt-5: 400k window, 272k input). Compaction, context-overflow checks, and usage ratios prefer it; completion budgeting keeps the total window. Resolution clamps it to max_context_size
max_output_sizeintegerNoPer-request output token cap (maps to max_tokens). Currently only the anthropic provider honors it. When set for a Claude model, this explicit value overrides the built-in server-side maximum
capabilitiesarray<string>NoCapability tags to add explicitly: thinking, always_thinking, image_in, video_in, audio_in, tool_use. Unioned with the capabilities auto-detected by the provider — entries can only be added, never removed
support_effortsarray<string>NoThinking effort levels the model accepts. For kimi, selecting another value at runtime fails; when model resolution carries an unsupported configured or previous value, the session falls back to the target model's default_effort and reports that effective value to the UI. A Thinking-capable Kimi model without this field uses boolean on / off. Other providers pass concrete values unchanged when their protocol has a native effort field; protocols that expose only levels or token budgets perform the required format conversion. Managed and open-platform refreshes may rewrite this field; to pin it manually, set [models."<alias>".overrides] support_efforts instead
default_effortstringNoDefault thinking effort for the model. Managed and open-platform refreshes may rewrite this field; to pin it manually, set [models."<alias>".overrides] default_effort instead
off_effortstringNoEffort value sent on the wire to disable thinking (e.g. none for xai grok). Only meaningful for models that declare such an encoding (catalog imports set it): turning thinking Off then sends this value instead of omitting the effort field — the only way to actually stop reasoning on models that reason by default
base_urlstringNoPer-model endpoint override (written by catalog imports for gateway models served away from the provider default). Resolution prefers it over the provider's base_url; only takes effect together with protocol
display_namestringNoName shown in the UI; falls back to model when unset
reasoning_keystringNoopenai provider only. Override the field name used for reasoning content when the gateway returns it under a non-standard name; by default reasoning_content, reasoning_details, and reasoning are auto-detected
adaptive_thinkingbooleanNoanthropic provider only. Force adaptive thinking on or off, overriding the version inference based on the model name. Omit to infer automatically (Claude ≥ 4.6 uses adaptive)
beta_apibooleanNoWhether this model uses its provider's beta API surface

When an alias contains ., use a quoted key:

toml
[models."gpt-4.1"]
provider = "openai"
model = "gpt-4.1"
max_context_size = 1047576

Model overrides

Use [models."<alias>".overrides] for user overrides that must survive provider-model refreshes. Runtime consumers read the effective value: the override when present, otherwise the top-level field.

toml
[models."jarvis-code/kimi-for-coding"]
provider = "managed:jarvis-code"
model = "kimi-for-coding"
max_context_size = 262144

[models."jarvis-code/kimi-for-coding".overrides]
max_context_size = 131072
display_name = "Kimi for Coding (custom)"

[models."<alias>".overrides] accepts ordinary model fields such as max_context_size, max_input_size, max_output_size, capabilities, display_name, reasoning_key, adaptive_thinking, support_efforts, default_effort, and off_effort. It does not accept identity / routing fields: provider, model, protocol, beta_api, and base_url.

You can also switch models temporarily without touching the config file — by setting JARVIS_MODEL_* environment variables, the CLI synthesizes a temporary provider in memory that does not persist after restart. See Define a model from environment variables.

secondary_model

Subagents inherit the model the main agent is running by default. The [secondary_model] section makes this configurable: it offers subagents a pool of candidate models plus a default binding — typically a cheaper model for subtasks that do not need the main model's capability.

Subagent model pool

This feature is experimental and disabled by default. Enable it with JARVIS_CODE_EXPERIMENTAL_SECONDARY_MODEL=1, or the master JARVIS_CODE_EXPERIMENTAL_FLAG=1; it takes effect in every launch mode, including the interactive TUI. While the experiment is off, the pool keys stay inert: subagents inherit the caller's model and session startup skips the pool validation.

The minimal configuration is one line — a lone default_model is a pool with a single entry:

toml
[secondary_model]
default_model = "jarvis-code/kimi-for-coding-highspeed"
FieldTypeDefaultDescription
default_modelstringThe default model for subagents
modelstable<string, string>Subagent model pool. Each key is the alias of a configured [models] entry; each value is the selection hint shown to the main agent
forcebooleanfalsePin every subagent to default_model, taking the choice away from the main agent
default_effortstringThe thinking effort every spawned subagent binds with; outranks the bound model entry's own default_effort

Constraints between the fields:

  • default_model: required when a models table is configured, and must be one of its keys.
  • models: values may be Chinese or English; an empty string lists the alias with no hint.
  • force: requires default_model and cannot be combined with a models table — the table exists to offer a choice, and force removes it.
  • default_effort is section-wide: every spawn binds it regardless of the chosen pool entry (or the forced model). For per-entry efforts, leave it unset and use model variants (see below).
  • primary is a reserved alias (see below) and cannot be a pool key.

In the interactive TUI, the /secondary-model command (alias /subagent-model) opens a model selector: the choice is written to default_model (when a models table exists and the picked alias is not in it, an entry with an empty description is added), and newly spawned subagents pick up the new default immediately — no session restart needed.

A configured pool — an explicit models table or a lone default_model — enables model selection: the Agent / AgentSwarm tools gain a model parameter, and the tool description lists the pool (the default marked [default]) so the main agent can choose per spawn. Pool keys can only reference configured [models] entries — the jarvis-code/* aliases below are provisioned by /login:

toml
[secondary_model]
default_model = "jarvis-code/kimi-for-coding-highspeed"
[secondary_model.models]
"jarvis-code/k3" = "Pick this for hard problems. Strong at complex reasoning, algorithm design, deep debugging, math, and systematic challenges."
"jarvis-code/kimi-for-coding-highspeed" = "Fast but priced higher. Good for latency-sensitive tasks: daily refactoring, code explanation, small edits, and summaries."
"jarvis-code/kimi-for-coding" = "A balanced coding workhorse. Good for most feature development and code-change tasks."

A spawn resolves the subagent's model in this order:

  1. An explicit model passed in the tool call
  2. default_model

Rules for the model parameter:

  • It accepts any pool alias, or "primary" — the model the caller itself is running, always valid even when not in the pool.
  • When neither default_model nor models is configured, the parameter is not advertised and subagents inherit the caller's model.
  • Binding a pool alias does not inherit the caller's thinking effort. The section's default_effort wins when set. Otherwise, [thinking].enabled = false keeps Thinking off; when Thinking is enabled, resolution continues with the bound model entry's default_effort, the global [thinking].effort, then the middle of the bound model's support_efforts.
  • "primary" inherits both the model and the effort level from the caller.
  • A value that is neither a pool alias nor "primary" fails the spawn with an error listing the available choices.

To take the choice away from the main agent and run every subagent on one fixed model, add force = true:

toml
[secondary_model]
default_model = "jarvis-code/kimi-for-coding-highspeed"
force = true

With force set, the model parameter is not advertised (just like when nothing is configured) and every spawn binds default_model; an explicit model argument, "primary" included, is rejected with an error.

Different thinking efforts per pool entry

Binding a pool alias lands the subagent on the bound model's default effort. You can exploit this by registering a "variant" entry for the same underlying model, so the main agent picks the thinking level together with the alias:

  1. Register a second entry for the same underlying model in [models], overriding only default_effort via [models."<alias>".overrides].
  2. List both the original alias and the variant alias in the pool.
toml
# "jarvis-code/k3" is provisioned by /login (default: high); this registers
# a max-effort variant of the same model
[models.k3-max]
provider = "managed:jarvis-code"
model = "k3"
max_context_size = 1048576
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]
support_efforts = [ "low", "high", "max" ]

[models.k3-max.overrides]
default_effort = "max"

[secondary_model]
default_model = "jarvis-code/k3"
[secondary_model.models]
"jarvis-code/k3" = "Default high effort. Good for most implementation, analysis, and multi-turn interaction tasks."
k3-max = "The same model at max thinking effort. Good for the hardest subtasks."

Two prerequisites:

  • The underlying model must declare support_efforts (under managed:jarvis-code only the k3 family currently declares effort levels).
  • The variant is a standalone entry and does not inherit fields from the entry it points at — copy capabilities, support_efforts, and the other metadata over in full, otherwise default_effort has no effect (it must be a member of support_efforts).

Note the asymmetry between the main agent and pool-bound subagents: for the main agent, a configured global [thinking].effort overrides the variant's default_effort; for subagents the variant's default_effort wins over the global value, and only [secondary_model].default_effort outranks it. Value and fallback rules follow the [models] entry's default_effort.

Note

Configuration errors fail loudly instead of falling back silently. Session creation, resume, and fork all fail at startup when:

  • default_model is missing, is not a pool key, or a pool key does not resolve to a configured [models] entry;
  • force is set without default_model, or combined with a models table.

visual_model

The visual model is a companion model configuration for vision-only work — typically a vision-capable model you pin so image / screenshot / video inspection tasks can run even when your main coding model is text-only. It is a default binding, not a forced one: the resolver binds the configured visual model by default, and tools that perform visual work can expose a model parameter accepting the symbolic values "visual" (the configured visual model) and "primary" (the caller's own model) — the same shape as the subagent model choice. When unset, visual tasks bind the caller's model.

When set, the binding is live: a text-only main model keeps the ReadMediaFile tool, which delegates image / video inspection to the visual model, and image / video parts pasted directly into a message are replaced with a text hint pointing at the visual model instead of failing the request. The terminal UI accepts pasted media whenever the current model handles it directly or a visual model is configured.

FieldTypeDefaultDescription
modelstringThe alias of a configured [models] entry, e.g. jarvis-code/kimi-vision (any provider, not limited to Kimi models). Should be a vision-capable entry (image_in and/or video_in listed in its capabilities)
default_effortstringThinking effort applied when visual tasks bind the visual model. Unset, the effort resolves naturally (global [thinking] config → the bound model's default effort) instead of inheriting the caller's effort. Follows the main model's thinking-effort semantics: models with strict effort validation (e.g. Kimi models) fall back to their default effort for unsupported values; other providers receive the value as-is
Other fieldsAccepts every field of [models."<alias>".overrides] (max_context_size, max_output_size, support_efforts, …) as a model patch applied only to visual tasks
toml
[visual_model]
model = "jarvis-code/kimi-vision"
default_effort = "low"
max_output_size = 8192

model / default_effort can be overridden by the JARVIS_VISUAL_MODEL / JARVIS_VISUAL_EFFORT environment variables, which take higher priority than config.toml. While an env override is set, it never leaks into config.toml — writes restore the env-free raw value.

Configuration errors fail loudly instead of falling back silently: session creation, resume, and fork all fail at startup when model is set but does not resolve to a configured [models] entry.

thinking

thinking sets the global default behavior for Thinking mode.

FieldTypeDefaultDescription
enabledbooleantrueWhether Thinking is enabled by default for new sessions; set to false to force Thinking off
effortstringThinking effort level (for example low, medium, high, xhigh, max). Non-Kimi providers do not remap concrete effort values when the upstream protocol accepts them; if the provider rejects the value, choose one that the model supports. Protocols that expose only levels or token budgets still require format conversion. Kimi models with support_efforts fall back to their model default when this configured value is not listed; Kimi models without that list treat every enabled value as boolean on
keepstring"all"Preserved Thinking passthrough. On kimi it is sent as thinking.keep; on anthropic (Claude and Kimi's Anthropic-compatible mode) it is sent as a context_management clear_thinking_20251015 edit (enabling keep routes Anthropic requests to the beta Messages API; an off-value disables keep and returns to the standard endpoint). "all" preserves prior turns' reasoning (reasoning_content / Anthropic thinking blocks); set to an off-value (false/0/no/off/none/null) to disable. Overridden by JARVIS_MODEL_THINKING_KEEP; only injected while Thinking is on

Deprecated fields

FieldDeprecated inDescription
default_thinking0.21.0Top-level boolean, replaced by [thinking] enabled. Migrate default_thinking = true to enabled = true, and default_thinking = false to enabled = false.
thinking.mode0.21.0One of auto / on / off, replaced by [thinking] enabled. mode = "off" becomes enabled = false; mode = "on" and mode = "auto" are equivalent to enabled = true (the default) and can be removed.
loop_control.max_retries_per_step0.32.0Replaced by loop_control.max_attempts_per_step (the value was always a total-attempt limit, including the first try). The old key is ignored and reports a warning on startup; rename it in config.toml.
loop_control.max_steps_per_run0.32.0Replaced by loop_control.max_steps_per_turn. The old key is ignored and reports a warning on startup; rename it in config.toml.

loop_control

loop_control governs the step count limit, the per-step attempt limit, and the threshold that triggers automatic context compaction in the Agent execution loop.

FieldTypeDefaultDescription
max_steps_per_turnintegerMaximum steps per turn; unset or 0 means unlimited
max_attempts_per_stepinteger10Maximum total attempts for a failing step, including the initial attempt
reserved_context_sizeintegerNumber of tokens reserved for model output; automatic compaction is triggered when the remaining context window falls below this value

max_steps_per_turn can be overridden by the JARVIS_LOOP_MAX_STEPS_PER_TURN environment variable, and max_attempts_per_step by JARVIS_LOOP_MAX_ATTEMPTS_PER_STEP; both take higher priority than the config file. The former JARVIS_LOOP_MAX_RETRIES_PER_STEP variable is deprecated but still honored (with a startup warning) when the new one is unset.

Retries only apply to transient failures — connection errors, timeouts, HTTP 429 rate limits, and 5xx server errors. A 429 caused by an exhausted quota or insufficient account balance is not retried and fails immediately, since it cannot succeed until the account is recharged.

token_counting

token_counting selects which context token count is reported externally — the value behind the context-size display. Internal logic (automatic compaction triggers, budgets, and overflow backoff) always uses both provider-reported usage and estimates, regardless of this setting.

FieldTypeDefaultDescription
strategy"measured+estimated" | "measured" | "estimated""measured+estimated"measured+estimated reports the live size — the provider-reported usage of each exchange plus an estimate of the not-yet-measured tail — floored by the last measured total; measured reports provider usage alone, so the display only moves when an exchange completes; estimated reports a pure estimate with provider usage ignored — the fallback for providers that do not report usage or report it unreliably

strategy can be overridden by the JARVIS_TOKEN_COUNTING_STRATEGY environment variable, which takes higher priority than config.toml.

background

background controls the concurrency behavior of background tasks (launched via the Bash tool or the Agent tool's run_in_background=true parameter).

Changed

This section is now named task. background still works and keeps the same fields; when both tables are present, [task] wins key by key.

FieldTypeDefaultDescription
max_running_tasksintegerMaximum number of background tasks running concurrently
keep_alive_on_exitbooleanfalseWhether to keep still-running background tasks when the session closes. By default, Jarvis Code requests that all background tasks stop before the process exits; set this to true only when you want tasks to outlive the session. In print mode (jarvis -p), this is only a legacy fallback used when print_background_mode is unset: true is equivalent to print_background_mode = "drain"
kill_grace_period_msinteger5000Grace period in milliseconds after session close, a manual stop, or a task timeout requests graceful termination. If a task is still running after this period, Jarvis Code attempts to force-stop it
bash_auto_background_on_timeoutbooleantrueWhen a foreground Bash command hits its timeout, move it to a background task instead of killing it — the agent is notified when it completes, and the backgrounded command is bounded by the bash_task_timeout_s default background timeout. Set to false to kill timed-out foreground commands instead
bash_task_timeout_sinteger600Default timeout (seconds) for background Bash tasks when the call omits timeout; also used to re-arm foreground commands moved to the background on timeout. 0 means no timeout — the task runs until it exits or the model stops it. Explicit per-call timeout values are unaffected. In print mode (jarvis -p) the default is 0 unless explicitly set
print_background_mode"exit" | "drain" | "steer""steer"Print mode (jarvis -p) only. Governs how pending background tasks are handled once the main agent's turn ends: "exit" exits immediately; "drain" waits for every background task to reach a terminal state before exiting (results are not fed back to the main agent); "steer" stays alive so a completing background task — like a background subagent — injects a synthetic user message that steers the main agent into a new turn, looping until a turn ends with no pending background tasks or a limit is hit. Takes precedence over the keep_alive_on_exit print fallback
print_wait_ceiling_sinteger2147483In print mode (jarvis -p), the wall-clock ceiling (seconds) for the wait/steer loop when print_background_mode is "drain" or "steer" (the default is ~24.8 days — effectively unbounded). Has no effect outside print mode or when it is "exit"
print_max_turnsinteger100000In print mode (jarvis -p) with print_background_mode = "steer", the maximum number of new turns that may be triggered by background-task completions, to keep the steering loop bounded (the default is effectively unbounded)

keep_alive_on_exit can be overridden by the JARVIS_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT environment variable, and max_running_tasks by JARVIS_CODE_BACKGROUND_MAX_RUNNING_TASKS; both take higher priority than config.toml.

In print mode (jarvis -p "<prompt>"), Jarvis Code stays alive after the main agent's turn as long as background tasks are still pending: each completion is fed back to the main agent as a synthetic user message, steering it into a new turn (print_background_mode = "steer" by default), and the run exits once a turn ends with nothing pending. The loop is bounded by print_wait_ceiling_s and print_max_turns, both effectively unbounded by default. Background work is never killed by a wall-clock cap in print mode either: background Bash tasks default to no timeout (bash_task_timeout_s = 0), and subagents run without a timeout ([subagent] timeout_ms and [swarm] timeout_ms both default to 0 unless explicitly set), so only the model itself stops a task. Set print_background_mode to "drain" to wait for tasks without feeding results back, or "exit" to end the run as soon as the main agent finishes.

task

task is the current name of the background-task section. It accepts exactly the same fields as background; background is kept as a deprecated alias so existing config files keep working.

When both tables are present, they are merged with [task] winning key by key:

toml
[background]
max_running_tasks = 4
kill_grace_period_ms = 5000

[task]
kill_grace_period_ms = 10000

The effective configuration above is max_running_tasks = 4 and kill_grace_period_ms = 10000. Prefer writing only [task] in new config files.

subagent

subagent controls how subagents spawned by the Agent tool run.

FieldTypeDefaultDescription
timeout_msinteger7200000 (2 hours)Maximum wall-clock time (milliseconds) a single Agent subagent is allowed to run before it is settled as timed_out. 0 means no timeout — the subagent runs until it finishes or the model stops it. This is the background-task manager's per-task timeout for each subagent task, so it applies to both foreground and background subagents. In print mode (jarvis -p) the default is 0 unless explicitly set. Note: any value above 2147483647 (about 24.8 days) is clamped to roughly 24.8 days by the runtime

timeout_ms can be overridden by the JARVIS_SUBAGENT_TIMEOUT_MS environment variable, which takes higher priority than config.toml.

workflows

workflows tunes the run limits of dynamic workflows and declares extra workflow directories.

FieldTypeDefaultDescription
max_concurrencyinteger4Maximum number of subagents a workflow runs concurrently (116)
max_agent_callsinteger50Maximum number of agent() calls a single workflow run may make
max_duration_msinteger1800000 (30 minutes)Maximum wall-clock time (milliseconds) of a single workflow run
max_script_bytesinteger262144 (256 KB)Maximum size in bytes of a workflow script file; larger files are skipped during discovery
extra_workflow_dirsarray<string>Additional workflow directories, scanned at the extra scope (between user and built-in precedence)

swarm

swarm controls how subagents launched by the AgentSwarm tool run, independently of [subagent].

FieldTypeDefaultDescription
timeout_msinteger7200000 (2 hours)Maximum wall-clock time (milliseconds) a single AgentSwarm subagent is allowed to run. On timeout that subagent is aborted and marked as failed in the aggregated report (Subagent timed out.); the other subagents are unaffected. 0 means no timeout — the subagent runs until it finishes or the model stops it. In print mode (jarvis -p) the default is 0 unless explicitly set. Note: any value above 2147483647 (about 24.8 days) is clamped to roughly 24.8 days by the runtime

timeout_ms can be overridden by the JARVIS_CODE_SWARM_TIMEOUT_MS environment variable, which takes higher priority than config.toml.

model_catalog

model_catalog controls how often the CLI refreshes the model list of providers that publish one (managed providers and registry imports).

FieldTypeDefaultDescription
refresh_interval_msintegerMinimum interval in milliseconds between two catalog refreshes. 0 disables the interval check so every eligible start refreshes
refresh_on_startbooleanWhether to refresh the catalog when the engine starts

cron

cron controls the scheduler behind the scheduled task tools. Every field is also bound to an environment variable, which takes priority over the config file.

FieldTypeDefaultEnvironment variableDescription
disabledbooleanfalseJARVIS_DISABLE_CRONDisable scheduled tasks entirely; the CronCreate, CronList, and CronDelete tools stop firing
no_jitterbooleanfalseJARVIS_CRON_NO_JITTERFire exactly on the cron expression instead of applying the deterministic jitter that spreads load
no_stalebooleanfalseJARVIS_CRON_NO_STALENever mark a long-lived recurring task as stale, so it is not auto-deleted after 7 days
debugbooleanfalseJARVIS_CRON_DEBUGEmit scheduler debug logging
manual_tickbooleanfalseJARVIS_CRON_MANUAL_TICKStop the automatic poll loop; ticks must be driven manually. Intended for tests
clockstringJARVIS_CRON_CLOCKOverride the scheduler clock. Intended for tests
poll_interval_msinteger | nullJARVIS_CRON_POLL_INTERVAL_MSPoll interval in milliseconds; null disables polling

The boolean environment variables in this section are strict on/off switches: 1 enables, anything else leaves the value unset.

mcp

FieldTypeDefaultDescription
startup_timeout_msinteger30000 (30 seconds)Global default connection (startup + tool discovery) timeout in milliseconds for all MCP servers. Accepts 12147483647. A per-server startupTimeoutMs in mcp.json always wins over this section and the environment variable; when neither is set, the default applies
tool_timeout_msinteger60000 (60 seconds)Global default single tool-call timeout in milliseconds for all MCP servers. Accepts 12147483647. A per-server toolTimeoutMs in mcp.json always wins over this section and the environment variable; when neither is set, the client built-in default applies

startup_timeout_ms and tool_timeout_ms can be overridden by the JARVIS_MCP_STARTUP_TIMEOUT_MS and JARVIS_MCP_TOOL_TIMEOUT_MS environment variables respectively, which take higher priority than config.toml. See MCP for the full MCP server configuration.

identity

Customizes how the agent identifies itself. Leave it unset and nothing changes.

FieldTypeDefaultDescription
namestringDisplay name the agent calls itself in the system prompt (fills the ${product_name} slot, including in your own SYSTEM.md and agent files)
slugstringderived from nameMachine identifier used in protocol fields: the User-Agent product token sent to third-party providers, and the client name announced to MCP servers. Derived from name when omitted: lowercased, with every run of non-alphanumeric characters folded to -
toml
[identity]
name = "Acme Dev Agent"
slug = "acme-dev"        # optional

Both fields can be set through the JARVIS_CODE_IDENTITY_NAME and JARVIS_CODE_IDENTITY_SLUG environment variables, which take higher priority than config.toml and are never written back to it — convenient for containers and CI, where writing a config file is awkward.

A name that contains no ASCII letters or digits (for example a purely Chinese name) leaves nothing to derive a slug from and falls back to agent; write slug explicitly if you need a specific protocol token.

The identity is resolved once at startup and holds for the life of the process — it is announced to MCP servers and providers when connections are made, so it cannot change midway. Edits to this section take effect on the next start, for new sessions: a resumed session keeps the system prompt it was recorded with, since its past turns already speak under that identity. Likewise, an MCP OAuth authorization keeps the client registration it was granted under; reset that server's authentication to register under the new identity.

This section is read by the default agent-core-v2 engine. It is ignored by the legacy jarvis / jarvis -p path selected with JARVIS_CODE_LEGACY_FLAG=1; jarvis server always uses agent-core-v2.

tools

tools is the global tool switch: it applies to every agent in all sessions and intersects with each agent's own tools / disallowedTools policy.

FieldTypeDefaultDescription
enabledarray<string>Global allowlist: when non-empty, only the listed tools are available; omitting the field or setting an empty array imposes no constraint
disabledarray<string>Global denylist, applied after enabled

Name matching follows the same rules as the same-named fields in an agent file: built-in tools match by exact name (such as Read), and MCP tools match with globs (such as mcp__github__*). Three entry shapes never match anything and are reported with a warning: a wildcard outside an mcp__ pattern (enabled = ["*"] disables every tool, disabled = ["*"] disables none), an mcp__ literal missing the tool segment (mcp__github — use mcp__github__* for a whole server), and a name no registered or built-in tool has (matching is case-sensitive).

toml
[tools]
disabled = ["EnterPlanMode", "ExitPlanMode", "mcp__github__*"]

Note

Like the tools / disallowedTools fields of an agent file, this section shapes the tools shown to the model and is enforced again before execution. Permission rules remain a separate control for operations that require approval.

image

image controls how images are compressed before being sent to the model, across every ingestion point (pasted images, ReadMediaFile reads, images in MCP tool results, and so on).

FieldTypeDefaultDescription
max_edge_pxinteger2000Longest-edge ceiling in pixels. Larger images are scaled down proportionally to fit; raising it preserves more detail at the cost of larger request bodies
read_byte_budgetinteger262144 (256 KB)Per-image byte budget for images the model reads for itself (ReadMediaFile default reads). It bounds the accumulated request-body size when the model keeps screenshotting and reading images; fine detail stays reachable through the region parameter, which reads a crop back at full fidelity (region and full_resolution are not subject to this budget)

max_edge_px can be overridden by the JARVIS_IMAGE_MAX_EDGE_PX environment variable and read_byte_budget by JARVIS_IMAGE_READ_BYTE_BUDGET; both take higher priority than config.toml.

memory

The [memory] table contains the limits used by persistent memory in engine v2 and the switch for automatic extraction. Persistent memory is a native engine-v2 capability that is always on; there is no enabled field for the capability itself, but extraction_enabled below toggles automatic extraction (on by default).

FieldTypeDefaultDescription
recall_max_entriesinteger5Maximum memory entries selected for one recall
recall_max_bytes_per_entryinteger4096Maximum UTF-8 bytes rendered for one recalled entry's body
recall_max_session_bytesinteger61440Maximum UTF-8 bytes for the complete recalled-memory envelope in one recall injection
extraction_max_turnsinteger5Maximum recent user turns included in one automatic extraction run
extraction_enabledbooleantrueWhether automatic memory extraction is enabled (at the end of a turn and at the end of the run/session); when false, no extraction runs and no drafts are written, and re-enabling resumes from where it left off
toml
[memory]
recall_max_entries = 5
recall_max_bytes_per_entry = 4096
recall_max_session_bytes = 61440
extraction_max_turns = 5
extraction_enabled = true

Automatic extraction is a native engine-v2 capability that runs after every completed turn — for the main agent and subagents alike — and again at the end of the run and when the session closes, so nothing left behind by the last turn is lost. Extracted memories never land in the user scope: the scope is normalized to project when the workspace is trusted and to workspace otherwise, and subagents can only persist to workspace/project. The explicit Memory tool is not affected and keeps all three scopes.

It sanitizes safe drafts and persists them automatically, excluding duplicates that are already visible in the memory catalog and duplicates within the same run. A turn in which the agent already wrote memory successfully through the Memory tool is skipped rather than extracted. Transient persistence failures keep the affected drafts for retry after a later completed turn; terminal memory errors (such as project writes in an untrusted workspace) are dropped rather than retried. This deduplication does not provide atomic cross-process idempotency. Extraction never sends credential-shaped transcript content to the model, and drafts are redacted and rejected again before persistence.

experimental

experimental stores persistent per-flag overrides for experimental features. Each key is a flag id and each value is a boolean; the table is free-form, so a flag that this build does not register is simply ignored.

toml
[experimental]
tower = true
search_worker = false

The resolution order for a flag is: its own environment variable > this section > the flag's registered default. The master switch JARVIS_CODE_EXPERIMENTAL_FLAG=1 enables every registered flag for the process.

The /experiments panel in the TUI writes to this section, so you rarely need to edit it by hand. For the flag inventory, ids, environment variables, and defaults, see Experimental flags.

services

services configures built-in web search, web fetch, and optional result reranking. The recognized tables are:

  • moonshot_search: Moonshot web search backend.
  • moonshot_fetch: Moonshot web fetch backend.
  • langsearch: LangSearch web search backend.
  • brave: Brave Search web search backend.
  • rerank: Optional semantic reranker that can reorder results from any search backend.

Alongside the backend tables, the active_search_provider field of [services] selects which backend serves WebSearch: brave, langsearch, or moonshot.

Brave Search is not selected automatically: configure a [services.brave] API key and explicitly select Brave with active_search_provider = "brave" or jarvis search set brave. LangSearch web search works the same way: configure a [services.langsearch] API key and explicitly select it with active_search_provider = "langsearch" or jarvis search set langsearch. Moonshot search remains available when neither is the active provider. On the default agent-core-v2 engine, selecting a search provider is atomic: the selected backend must have a valid API key or its WebSearch implementation and specialized tools are unavailable; there is no fallback to another backend.

When active_search_provider names a backend, that backend serves WebSearch with no fallback: if its credentials are missing, web search is simply unavailable. When active_search_provider is absent, the runtime keeps the legacy precedence — configured LangSearch first, then configured Moonshot, then the managed Jarvis Code OAuth search service. Running jarvis search set brave or jarvis search set langsearch both configures the backend and selects it (writes active_search_provider), migrating older configs to explicit selection.

In the TUI, Settings → Web Search shows the current search and rerank providers at the top. Web search provider only configures or edits Moonshot, LangSearch, or Brave — it preserves the current selection and does not switch the active backend. Use Active web search provider to explicitly switch which configured backend serves WebSearch, and Rerank provider to configure, enable, disable, edit, or remove semantic reranking independently. Configuring Moonshot can reuse the current Jarvis Code OAuth login or configure an API key for the China or Global API region.

Moonshot services

moonshot_search and moonshot_fetch accept the same fields:

FieldTypeRequiredDescription
base_urlstringNoService API URL
api_keystringNoAPI key
oauthtableNoOAuth credential reference, same structure as providers.*.oauth
custom_headerstable<string, string>NoCustom HTTP headers attached to each request

base_url and api_key can also come from environment variables, which take priority over the config file: JARVIS_WEB_SEARCH_BASE_URL / JARVIS_WEB_SEARCH_API_KEY for moonshot_search, and JARVIS_WEB_FETCH_BASE_URL / JARVIS_WEB_FETCH_API_KEY for moonshot_fetch. An env base URL defines a separate service endpoint, so the persisted API key, OAuth reference, and custom headers are not forwarded to it; set the matching env API key when that endpoint requires authentication. An env API key without an env base URL keeps the configured endpoint and custom headers but replaces both configured credential forms. Setting the base URL and API key through env without any config section also enables the service.

toml
[services.moonshot_search]
base_url = "https://api.moonshot.cn/v1/search"
api_key = "sk-xxx"

[services.moonshot_fetch]
base_url = "https://api.moonshot.cn/v1/fetch"
api_key = "sk-xxx"

langsearch calls the LangSearch Web Search API. Configure it from Settings → Web Search in the TUI, with jarvis search set langsearch, or by editing config.toml.

FieldTypeDefaultDescription
api_keystringLangSearch API key; required to activate this backend
base_urlstringhttps://api.langsearch.comAPI base URL
tierstringfreeRate-limit tier: free, tier1, tier2, or tier3
freshnessstringnoLimitResult age filter sent to LangSearch: oneDay, oneWeek, oneMonth, oneYear, or noLimit
summarybooleantrueRequest generated summaries and use them as result snippets when available
countinteger10Number of results per request, from 1 to 10
custom_headerstable<string, string>Custom HTTP headers attached to each request

brave calls the Brave Search API. It requires explicit selection and a valid API key. Configure it from Settings → Web Search in the TUI, with jarvis search set brave, or by editing config.toml. Running jarvis search set brave also selects it (active_search_provider = "brave"); in the TUI, configure it under Web search provider first, then switch to it with Active web search provider.

FieldTypeDefaultDescription
api_keystringBrave Search API key; required to activate this backend
base_urlstringhttps://api.search.brave.com/res/v1API base URL
custom_headerstable<string, string>Custom HTTP headers attached to each request
toml
[services]
active_search_provider = "brave"

[services.brave]
api_key = "YOUR_API_KEY"

When Brave is the active backend and its API key is valid, WebSearch is served by Brave, and engine v2 also makes a set of specialized Brave tools available. See Brave Search tools for the tool list. Provision an API key and review the per-endpoint rate limits from the official Brave Search API dashboard; pricing and quotas are set by Brave, not by Jarvis Code.

Semantic rerank

rerank is independent of the selected search backend. When enabled, it sends search results to the configured semantic reranker after the active backend (Brave, LangSearch, or Moonshot) returns them. Reranking is best-effort: if the rerank request fails, the original search result order is preserved.

FieldTypeDefaultDescription
enabledbooleantrueWhether to rerank search results
providerstringRerank provider; currently only langsearch is supported
api_keystringRerank API key; when omitted, reuses services.langsearch.api_key
base_urlstringhttps://api.langsearch.comRerank API base URL
custom_headerstable<string, string>Custom HTTP headers attached to each request

The following example activates LangSearch search and the LangSearch Semantic Rerank API:

toml
[services.langsearch]
api_key = "YOUR_API_KEY"
tier = "free"
count = 10

[services.rerank]
enabled = true
provider = "langsearch"
# api_key = "YOUR_RERANK_API_KEY" # Omit to reuse services.langsearch.api_key

Use jarvis search status to inspect the current configuration, jarvis search use <provider> to switch the active backend, jarvis search clear langsearch / jarvis search clear brave to remove a backend, and jarvis search clear rerank to remove rerank settings. These commands update config.toml directly.

permission

permission sets permission rules that are automatically loaded when a session starts, controlling whether the Agent needs user confirmation before calling a tool. Rules are written as a [[permission.rules]] array of tables, matched in order — the first matching rule takes effect.

FieldTypeRequiredDescription
decisionstringYesAction on match: allow (permit immediately), deny (reject immediately), ask (prompt each time)
scopestringNoRule scope: turn-override, session-runtime, project, user; defaults to user
patternstringYesMatch pattern in the form ToolName or ToolName(arg-pattern), e.g. Read or Bash(rm -rf*)
reasonstringNoRule description for debugging and auditing

Built-in tool names are listed in Built-in tools. Most built-in tools that accept rule arguments define their own matching subject, such as Bash(command-pattern) or Read(path-pattern). AgentSwarm, MCP tools, and custom tools can only be matched by tool name — argument patterns are not supported for them.

toml
[[permission.rules]]
decision = "allow"
pattern = "Read"

[[permission.rules]]
decision = "allow"
pattern = "Grep"

[[permission.rules]]
decision = "deny"
pattern = "Bash(rm -rf*)"

[[permission.rules]]
decision = "ask"
pattern = "Bash"

TIP

MCP server declarations are configured in ~/.jarvis-code/mcp.json or the project-local .jarvis-code/mcp.json, not in config.toml. The interactive configuration entry point is /mcp-config; see Model Context Protocol.

tui.toml

Alongside config.toml, the CLI keeps terminal-UI and client preferences in a companion tui.toml in the same directory (~/.jarvis-code/tui.toml, or $JARVIS_CODE_HOME/tui.toml when overridden). It is created with defaults on first run, and the interactive commands /config, /theme, and /editor write to it for you — so you rarely need to edit it by hand. If the file is malformed, the CLI falls back to defaults and shows a notice instead of failing to start.

FieldTypeDefaultDescription
themestringautoColor theme: auto (follow the terminal), dark, light, or the name of a custom theme
render_latexbooleantrueRender LaTeX math expressions ($…$, $$…$$) in Markdown messages as Unicode text; false keeps the raw source
disable_paste_burstbooleanfalseDisable the non-bracketed paste-burst fallback that keeps rapid multi-line pastes from submitting line by line
cache_expiry_hintbooleantrueShow a dialog when resuming a long-idle session or submitting after a long idle stretch, warning that the context cache has likely expired and offering to compact or start a new session (v2 engine only)
bannerbooleanfalseShow the remote promo banner below the welcome panel at startup
[editor].commandstring""External editor command for composing long input; empty falls back to $VISUAL / $EDITOR
[notifications].enabledbooleantrueWhether desktop notifications are sent
[notifications].notification_conditionstringunfocusedWhen to notify: unfocused (only when the terminal is not focused) or always
[upgrade].auto_installbooleantrueWhether new versions are installed automatically
[status_line].itemsstring[][]Built-in slots to show on the first footer line and their order: mode, goal, model, tasks, cwd, git, tips. Unset keeps the default layout; unknown ids are skipped with a warning
[status_line].commandstring""Custom status line command. Its first stdout line replaces the first footer line, with a JSON snapshot (model, cwd, git branch, permission mode, plan mode, context usage, session id, version) passed on stdin. Runs are capped at 300ms and throttled to once per second; failures fall back to the built-in layout
toml
# ~/.jarvis-code/tui.toml
theme = "auto" # "auto" | "dark" | "light" | custom theme name
render_latex = true # false keeps LaTeX math in messages as raw source
disable_paste_burst = false # true disables non-bracketed paste-burst fallback
cache_expiry_hint = true # false disables the "cache expired" dialog on resume / idle submit
banner = false # true shows the remote promo banner below the welcome panel

[editor]
command = "" # empty uses $VISUAL / $EDITOR

[notifications]
enabled = true
notification_condition = "unfocused" # "unfocused" | "always"

[upgrade]
auto_install = true

# [status_line]
# items = ["mode", "goal", "model", "tasks", "cwd", "git", "tips"]
# command = "~/.jarvis-code/statusline.sh"

Changes apply on the next start, or immediately with /reload-tui (which reloads only tui.toml); /reload reloads both config.toml and tui.toml.

Project-local configuration

In addition to the user-level files under ~/.jarvis-code, Jarvis Code reads a project-local configuration file at <project-root>/.jarvis-code/local.toml. It holds settings that are specific to one project checkout and typically should not be shared with teammates.

The file is created automatically when you add an extra workspace directory with /add-dir and choose to remember it for the project. You rarely need to edit it by hand.

[workspace]

The [workspace] table groups project-level workspace settings:

FieldTypeRequiredDescription
additional_dirarray<string>NoAdditional workspace directories, stored as absolute paths. Written automatically when you confirm "remember this directory" in /add-dir; read back on startup so the directories are available in every session of this project
toml
[workspace]
additional_dir = ["/absolute/path/to/shared"]

Because directories are stored as absolute paths, which are specific to your machine, we recommend adding .jarvis-code/local.toml to your project's .gitignore so it is not committed.

Next steps