← Reference

Settings

Settings loaded from ~/.kcode/config.toml (global) overlaid by <project>/.kcode/config.toml, then an optional per-launch --config <path> file, with KCODE_* environment overrides on top.

Full notes from the app

kcode --config alt.toml tries an alternate profile without touching ~/.kcode: the file must exist (a missing or broken path fails the launch instead of being silently ignored), it live-reloads on save like the other two config files, and when that launch starts the daemon, the daemon boots on the same file — attaching to an already-running daemon applies it client-side only and prints a one-line notice, since a running daemon's config can't be swapped mid-flight. Subcommands (doctor, auth, …) don't accept it. <project> is resolved git-style by walking up from the launch directory: KCODE_PROJECT_DIR if set, else the nearest ancestor with a .kcode/ dir (~/.kcode never counts — that's the user scope), else the nearest git toplevel, else the cwd — so running kcode from a subdirectory reuses the surrounding project's config, storage, plugins, and sessions instead of creating a stray .kcode/ where you launched. Edit the generic knobs via /settings (the fullscreen settings page — layout, grid cap, command/skill dirs), or edit either file directly — kcode watches both config files and live-applies a subset on save: the theme, the chat view toggles (session_panels.chat.*), the ambient-backdrop shader, the default_model / default_effort / default_output_style defaults (which relay live to the daemon so newly-spawned sessions boot on them), and the [keymap] shortcut overrides (rebind any app shortcut and it takes effect on the next key), each posting a notice of what was applied. Separately, adding or editing a skill or command file under a configured dir is picked up live by its own file-watcher. providers, cursor, the command_dirs / skill_dirs dir lists themselves, and deeper lib [section] settings still need a restart; the notice says which. (The models.dev model catalogue is not a restart surface: the daemon re-fetches it on a TTL and pushes updates to clients live.) Run kcode config defaults to print every linked lib's settings at their compiled defaults as TOML (pipe it into config.toml as a starting point).

compaction-template.md (file)
The Summary compaction prompt template, editable without recompiling. Resolution: <project>/.kcode/compaction-template.md overrides ~/.kcode/compaction-template.md; when neither exists the compiled default (a structured, anchored template — Objective / Important Details / Work State{Completed,Active,Blocked} / Next Move / Relevant Files) is written to the user path so it is discoverable. Slots: {{conversation}} (required — the serialised older messages) and {{previous_summary}} (optional — the anchoring block on repeat compactions, empty the first time). Loaded once per session, so edits apply to new sessions (or a restart), not retroactively.
API keys (env / auth.toml)
API keys are not a config.toml setting. kcode resolves a provider's credential from its <PROVIDER>_API_KEY environment variable first (env takes precedence — where most users keep keys), then ~/.kcode/auth.toml ([auth.<id>] api_key = "…", mode 0600), then an OAuth login ([oauth.<id>], same file — the sign-in counts as connected for the /model gate, while a real key always wins over it). Add or update a key — or sign in — interactively in Settings (⌘,) › Providers — a searchable provider list; choosing a row opens a masked prompt (⌘V to paste) that writes auth.toml for you, or, for an OAuth-capable provider, a sign-in method choice first (browser or device code — a ChatGPT Pro/Plus login is what unlocks the subscription-only GPT-5.6 models). Keys live on the settings page rather than a session command because one key serves every session (all-session config). Only the non-secret providers.<name>.base_url lives in config.toml.
Supported providers (whitelist)
kcode supports a fixed whitelist of the models.dev catalogue's providers, chosen at build time (provider_catalog::SUPPORTED_CATALOG_IDS, 2026-07-24) — the catalogue itself ships untouched. The list: the six native vendors (Anthropic, OpenAI, Fireworks, DeepSeek, Kimi, MiniMax) plus Kimi For Coding, Baseten, Google (Gemini), Groq, Hugging Face, Llama, OpenCode Zen, and OpenRouter through the generic OpenAI-compatible connector, plus the Google Vertex pair (listed in Settings › Providers but not runnable yet — Vertex needs a dedicated Google-ADC connector). A catalogue provider outside the whitelist is never listed and gets no client even when its env key is set — an ambient GITHUB_TOKEN can no longer route a turn to GitHub Copilot.
(full power-user surface)
This list is curated to the knobs worth knowing. The complete deep-lib section surface — [agent_loop], [kvstore], [http_client], [shell_client], [event_bus], [scheduler], [file_watcher], [plugin_runtime_lua]/[plugin_runtime_wasm], [models_dev], [onboarding], [updater], and more — is enumerated with defaults by kcode config defaults (kept honest by a build-time drift test: every documented section is consumed, every consumed section documented).

269 entries · kcode 0.4.0

269 shown
KeyTypeDefaultWhat it does
agent_framework
agent_framework.hot_reloadbooleantrueRe-read the agent-kind directories when a file under them changes, so an edited or newly added markdown agent takes effect in the running session instead of only in the next one. On (the default) each session arms a watcher over ~/.kcode/agents/ and <project>/.kcode/agents/. Set false and no watcher is armed: the registry keeps the set it loaded at session start, and the session skips a watch registration. Restart-only (a deep lib [section] knob).
agent_framework.respect_gitignorebooleantrueHonour .gitignore rules while walking the agent-kind directories (~/.kcode/agents/ and <project>/.kcode/agents/ — markdown agent definitions, see the Agent Kinds section). A gitignored agent file is skipped. Kinds load at session spawn and, unless agent_framework.hot_reload is off, reload in place when a kind file changes. Restart-only (a deep lib [section] knob).
agent_framework.respect_ignorebooleantrueHonour .ignore rules while walking the agent-kind directories — the .ignore-file twin of agent_framework.respect_gitignore. Restart-only (a deep lib [section] knob).
agent_loop
agent_loop.default_modelstring"claude-haiku-4-5"Documented only by its default. Run kcode config defaults for the full table.
agent_loop.doom_loop_thresholdinteger3Documented only by its default. Run kcode config defaults for the full table.
agent_loop.max_tool_concurrencyinteger8How many concurrent_safe tool calls run at once within one round (R#11). When a round yields several calls, the loop resolves permissions in order, then dispatches the safe ones (fs_read, glob, grep, web_fetch/web_search, view_image, memory_search/memory_list, browser_screenshot, read-only shell.*) in a bounded parallel batch — completions stream to the transcript as they land — while everything else still runs one-at-a-time in the model's order; the model-facing tool results are reassembled in call order either way, and a batch abort synthesizes explicit cancellation results for the unfinished calls. 1 (or 0) restores fully sequential dispatch.
agent_loop.max_tool_roundsinteger1024Cap on tool-call rounds in a single turn before the loop hard-stops with an error (continuation rule 3). Restart-only for the daemon; headless reads it per run.
agent_loop.provider_retry{ max_attempts, initial_delay_ms, max_delay_secs, jitter }4 attempts, 1s initial, 30s cap, full jitterTransient-failure retry policy for provider calls (429s, 5xx, network blips, stall-guard timeouts). Applies only while a call has produced NO output — a request-time error or a stream that hasn't yielded its first item — so a retry never duplicates streamed text; classification is the provider's own classify_error plus the stall guard's synthetic timeouts. While a retry backs off, the chat's working strip reads 'Provider not responding — retry n/m in Ns' instead of a bare spinner (2026-07-31), and headless ndjson emits a provider_retry line. Context-overflow errors are excluded — they go to compaction recovery. Restart-only for the daemon.
agent_loop.provider_retry.initial_delay_msinteger1000Documented only by its default. Run kcode config defaults for the full table.
agent_loop.provider_retry.jitterstring"full"Documented only by its default. Run kcode config defaults for the full table.
agent_loop.provider_retry.max_attemptsinteger4Documented only by its default. Run kcode config defaults for the full table.
agent_loop.provider_retry.max_delay_secsinteger30Documented only by its default. Run kcode config defaults for the full table.
agent_loop.provider_stall_timeout_msinteger300000The stall guard (2026-07-31): the longest provider silence before the call is treated as failed. Provider clients deliberately carry no whole-request timeout — it would abort long SSE streams — so without this a server that accepts the request and then goes quiet hangs the turn forever (the 'everything looks stuck' case). Before the stream's first item a stall retries through agent_loop.provider_retry (nothing emitted yet, so no duplication); mid-stream it ends the turn with 'Provider stalled mid-reply — no data for Ns. Resend to retry.' The non-streaming path caps the whole call at 4× this value. While any turn is provider-silent ≥3s with no tool mid-run, the working strip appends a live '· waiting Ns' clock, and each settled turn's footer names the vendor's share: '▣ model · elapsed · provider 24.2s ×3 · ttfb 1.2s' (headless: per-round provider_round ndjson lines). 0 disables the guard. Restart-only for the daemon.
agent_loop.response_documentbooleantrueDocumented only by its default. Run kcode config defaults for the full table.
agent_loop.streaming_detectionbooleantrueDocumented only by its default. Run kcode config defaults for the full table.
agent_loop.time_metadatabooleantrueAppend a <cp:time utc="…" local="…"> stamp to every message kcode authors — the user's turn, each tool result, each channel injection. A model has no clock, so without this it cannot tell whether the last message was thirty seconds or three days ago, how long a build took, or that it is 2am where you sit; stamping each message as it is written turns the history itself into the record of elapsed time. A tool result's stamp also carries duration_ms (that call's own wall time) and a user-role message's stamp carries origin (typed/queued/channel/nudge). Written once per message and never re-stamped, so the provider's prompt cache still holds. Assistant messages are never stamped. The stamp is hidden from the chat transcript and the markdown export. Restart-only for the daemon.
browser_host
browser_host.executable_pathstring?An explicit Chrome-family browser binary for the #66 browser stack — wins over discovery and the downloaded bundle (a CHROME_PATH env var sits between this and discovery). Unset = discover Chrome, Chromium, Edge, Brave, or Arc. Restart-only.
browser_host.profilestring"sandbox"Which browser profile sessions run on: sandbox (a fresh ephemeral profile per browser launch, wiped with it) or the name of a persistent profile under <data>/browser-profiles/<name>/, created on first use and kept across sessions (letters, digits, -, _). Restart-only.
browser_host.screenshot_dirstring?<data>/screenshotsWhere browser_screenshot saves each capture as <session>-<stamp>.jpg (2026-09-07) — the result names the file, the chat shows the picture inline, and the model can re-read it with view_image. Restart-only.
cc_hooks
cc_hooks.enabledbool?false(Removed — the Claude-Code hooks compat stack was dropped, Decision 16, 2026-08-16.) This key is no longer read; the live project-hook switch is hooks_enabled (P7 [[hooks]] automation).
(top level)
command_dirsstring[]?["~/.kcode/commands"]Markdown command source directories. Supports ~ and $HOME expansion. Each *.md file becomes a client-side /<name> slash command whose body is the prompt, with $ARGUMENTS / $1,$2… substitution (see /commands). Unset resolves to ~/.kcode/commands plus <project>/.kcode/commands; setting this key replaces the list entirely. A command file — or a subdirectory of them — symlinked into a configured dir is followed (command_framework.follow_symlinks, on by default), so a centrally-managed setup that symlinks individual commands in works (#175). Adding or editing a command file under a configured dir is picked up live by a file-watcher (no restart); changing this dir list itself is restart-only — the registry and its watcher resolve the dirs once at startup. Plugin bundles contribute commands separately: <plugin>/commands/*.md under the plugin roots load after these dirs (a configured command wins a name collision with a bundle's) — setting this key does not affect the plugin channel. GPU build only.
default_effortstring?Default reasoning effort every new session boots on. Allowed values: minimal, low, medium, high (an unknown value or unset clears the default). Only applies to models that support effort tuning. Set it from the GPU Settings page's General → Default effort cycle (click to advance (provider default) → minimal → low → medium → high → back to provider default) — the cycle twin of the Default model picker; the pick persists here and relays a live SetDefaultEffort to the daemon so newly-spawned sessions use it without a restart. Editing this key in config.toml directly relays the same live SetDefaultEffort on save (#165). Override a single session per-turn with /effort <level>.
default_modelstring?Default model id every new session boots on (unset ⇒ Fireworks GLM 5.2, accounts/fireworks/models/glm-5p2, when a Fireworks key is present, else the Anthropic default). Set it from the GPU Settings page's General → Default model picker (a searchable list of every provider's models — the keyed providers first, then the unkeyed ones under Provider · no key sections; picking a model there asks for that provider's key first and sets the default once it lands (2026-09-05) — plus a “Provider default” option) — the pick persists here and relays a live SetDefaultModel to the daemon, so newly-spawned sessions use it without a restart. A direct edit of this key in config.toml relays the same live SetDefaultModel on save (#165). Override a single session per-turn with /model <id>, or a whole launch with kcode --model <id> (applies to sessions that window spawns; the session keeps the model across restarts, like /model). GPU build only for the picker; the key is read on every build.
default_output_stylestring?defaultActive output style — selects the tone/format instructions appended to the agent's system prompt as an ## Output style section (augmenting, not replacing, kcode's curated style). Accepts a built-in name or a plugin-bundled style's name; None/unset or an unknown name falls back to the default built-in. Change with /output-style — the pick persists here and relays a live SetDefaultOutputStyle to the daemon so newly-spawned sessions use it without a restart; editing this key in config.toml directly relays the same on save (#57). Live sessions keep the style they were built with.
disabled_pluginsstring list[]Plugin names switched off (2026-08-13): a listed plugin is skipped at session stack build on every path (discovery, hot reload, manual load) — its own discovery outcome, mirroring the project policy blocklist, reported as your choice rather than a load failure. Written by the Settings › Plugins Enabled toggle (a read-modify-write list edit, so two project windows never clobber each other's entries); hand-editing works too. Its own top-level key — never [plugins.<name>] enabled, which would collide with a plugin's own settings vocabulary. User scope; applies to new sessions.
hooks[[hooks]] array (project scope only)Project automation (P7): declare hooks in <project>/.kcode/config.toml and kcode injects an input line into a session of that project when something happens — no plugin required. Each [[hooks]] entry: name (required — the hook's identity in notices, /control, and reloads), on = fs:file-changed (optional filter glob over project-relative paths; gitignore-respecting, kcode's own .git/.kcode writes never fire) | timer:cron (requires schedule, a 6-field cron expression) | app:turn-started / app:turn-finished / app:turn-error (session lifecycle in that project) | worktree:create / worktree:remove (W5 — a worktree checkout of this project came into existence with a session on it, or was deleted: /worktree new and the agent's spawn_worktree_session fire create (adopt does not — that checkout already existed); /worktree remove, the clean stop-time sweep, and the post-merge sweep fire remove. Declared in the base project's config and fired on the base project's stack, so session = "active" targets the base project's sessions. The fire data carries {session_id, slug, branch, path, project, env} plus slot/port/domain when the row has a slot — env being the rendered [worktree.env] overlay — and a prompt line splices any of it via {{slug}}, {{port}}, {{env.KEY}} etc., so prompt = "run: createdb app_{{slug}}" names the real checkout; this is how projects script the heavy isolation kcode doesn't own — create/drop a database, compose up/down. filter/schedule are validation errors here) | webhook:<name> (R#39 — fired by an external trigger of the same name: kcode trigger <name> … or any ExternalTrigger sender — or an authenticated POST to the loopback webhook listener (session_daemon.webhook_*, 2026-08-12) — fires every webhook hook declared for it, through the normal injection + concurrency path; when no webhook hook matches, the trigger's text injects into the target session directly), exactly one of run = "/name" (a skill's body becomes the injected line — resolved daemon-side, so markdown commands and UI verbs like /theme aren't resolvable; an unknown name drops the fire with a notification; no arguments in v1) or prompt = "…" (injected verbatim) — unless the entry is webhook-only — and optionally webhook = "https://…" (2026-07-29: the daemon POSTs a JSON fire record {hook, kind, source, intent, data, project, fired_at} to the URL on every fire, fire-and-forget over one pooled client per project — 10s timeout, failures and non-2xx logged never fatal; loopback/private hosts allowed; composes with run/prompt or stands alone — a webhook-only entry never touches a session and rejects session/concurrency), session = active (default — the project's most-recently-active live session; none live ⇒ the fire is dropped with a notification) | new (spawns a fresh session titled after the hook) | title:<t> (the live session with that title), concurrency = drop (default — suppress while a previous fire's injected turn is incomplete) | queue | replace (supersede the pending, never a running turn) | parallel, and enabled (default true). Hooks never interrupt a running turn — fires wait for the next turn boundary — and a hook-injected turn never fires app:turn-* hooks (no chaining). Every fire lands a <cp:hook-fired> row on /control; every drop (no target, unknown command, rate limit) and every invalid declaration lands in the notification center prefixed hooks:. Gated twice: the global hooks_enabled opt-in above AND project trust — the trust dialog lists each declared hook by name, and adding a hook to an already-trusted project re-prompts with the addition. Saving the project config live-reloads the set (added hooks start, removed stop, changed restart; unchanged keep their in-flight state). A hooks key in ~/.kcode/config.toml (user scope) never compiles — hooks are project automation; you'll get a one-time notice.
hooks_enabledbool?falseGlobal opt-in for project automation hooks (the [[hooks]] array below). Off by default — nothing compiles or fires anywhere until you set it. A top-level key rather than a [hooks] table because the project-scope declaration form is a [[hooks]] array at the same TOML path, and one key can't be a table in one file and an array in another. Daemon-read once at boot, so changing it needs a daemon restart (quit the window fully; the daemon exits with it). Hooks additionally require the project to be trusted — see the Permission Modes section's Project trust entry.
keymapmap?(built-in defaults)GPU client mode: a top-level [keymap] block that rebinds Mission Control's app-level keyboard shortcuts without a recompile — an action = chord table where the value is a single chord string (tasks = "cmd-y"), a list of chords (text_size_increase = ["cmd-=", "cmd-+"]), or false to unbind the action entirely (new_session = false). A chord is cmd- ctrl- alt- shift- prefixes (any order, friendly aliases command-/super-/meta-/control-/option-/opt- accepted) plus a key token — e.g. cmd-shift-], ctrl-b, cmd-- (⌘ + the - key), cmd-,. An override replaces that action's default chords; unlisted actions keep their built-in defaults. The rebindable action names and their defaults: new_session (⌘N), switch_session (⌘K, the recall switcher), jump_to_session_1jump_to_session_9 (⌘1–9, maximize the Nth grid tile — each slot rebindable on its own), focus_next_session (⌘⇧], move the maximized focus to the next session), focus_prev_session (⌘⇧[), mission_control (⌘M, the way home to the grid), tile_actions (⌘↩, the context menu on the selected tile), move_tile_later (⌘⌃], reorder the selected grid tile later), move_tile_earlier (⌘⌃[), command_palette (⌘P), notifications (⌘⇧N, the notification center), stats (⌘⇧S, the cross-session cost roll-up), tasks (⌘T), settings (⌘,, the settings page), cheatsheet (⌃B, this shortcut list), jump_to_last_message (⌘J), text_size_increase (⌘=/⌘+), text_size_decrease (⌘−), text_size_reset (⌘0). Esc (stop turn / back out) and Ctrl-C (unconditional cancel / exit) stay built-in and are not rebindable in v1 — they are the fixed escape hatches. A key that names no known action, a chord that does not parse, or two actions colliding on one chord (the later one is dropped, your override wins over an unchanged default) each surface a notice naming the problem; every binding that resolved cleanly still applies. Editing the block in config.toml re-applies it live on save — a rebind takes effect on the very next key, no restart. The ⌃B keyboard cheatsheet shows your effective bindings, so a rebound chord appears there too (an unbound action shows ); only Esc/Ctrl-C are listed fixed. GPU build only.
modelsModelEntry[]?Catalogue of selectable models. Each entry: { id, display?, provider }. Drives /model autocomplete and /models listing.
show_reasoningbool?trueInitial visibility for assistant reasoning blocks. Toggle live via /cot.
show_toolsbool?trueInitial visibility for tool-call bubbles (name + args + result). Toggle live via /show-tools.
skill_dirsstring[]?["~/.kcode/skills"]Skill source directories. Same shape and resolution as command_dirs (unset ⇒ ~/.kcode/skills plus <project>/.kcode/skills; setting the key replaces the list). Each skill becomes a /<name> slash command surfaced by SkillRegistry (see /skills). A skill file or skill directory symlinked into a configured dir is followed (skill_framework.follow_symlinks, on by default), so a setup that symlinks individual skills in works (#175). Adding or editing a skill file under a configured dir is picked up live by a file-watcher (no restart); changing this dir list itself is restart-only. Plugin bundles contribute skills separately: <plugin>/skills/*.md under the plugin roots load after these dirs (a configured skill wins a name collision with a bundle's) — setting this key does not affect the plugin channel.
themestring?wingman-darkDisplay theme name. The GPU client resolves it against its Wingman theme catalogue — the bundled wingman-dark / wingman-light / wingman-high-contrast, any ~/.kcode/themes/*.ron|*.json, plus any plugin-bundled <plugin>/themes/*.ron|*.json — falling back to wingman-dark when unset or the name isn't a Wingman theme. Change it live with /theme (picker on no-args, /theme <name> to set directly). Editing this key in config.toml also re-applies the theme live on save (#165), diff-gated so it never clobbers an in-app /theme pick.
theme_modestring?systemWhich mode a theme resolves at: "dark" or "light" to lock one, "system" to follow the OS appearance and re-theme live when it changes, or "document" to use whatever the theme file itself declares. Only matters for a theme that declares both modes (a colour authored as (dark: …, light: …)); a single-mode theme renders the same either way. Live-reloads on a config.toml save, like theme. GPU build only.
command_framework
command_framework.follow_symlinksbooleantrueFollow symlinks encountered while walking a command_dirs directory. On by default so a centrally-managed setup — where individual command files (or subdirectories) under ~/.kcode/commands are symlinks into a shared repo — resolves to the real files (#175). A symlinked root command dir is followed regardless of this flag; it governs symlinks met during the walk. Restart-only (a deep lib [section] knob).
command_framework.respect_gitignorebooleantrueDocumented only by its default. Run kcode config defaults for the full table.
command_framework.respect_ignorebooleantrueDocumented only by its default. Run kcode config defaults for the full table.
compaction
compaction.auto_compact_thresholdfloat0.85Documented only by its default. Run kcode config defaults for the full table.
compaction.auto_compact_tokensinteger300000Absolute context-token budget that triggers an automatic compaction regardless of the percentage thresholds (2026-08-09 — on by default at 300K so a 1M-window model's practically unreachable 0.70/0.85 fractions aren't the only trigger). 0 disables (percentages only). A session's /autocompact overrides it live; a respawn re-arms this default when the session row is unset. Daemon-side — no live reload.
compaction.max_tool_result_fractionfloat0.25The largest fraction of the model's context window any single tool result may occupy. A tool result larger than this (e.g. a big web fetch or a whole-file read) is truncated in place with a [Tool result truncated to fit the context window] marker before compaction measures the context — so one oversized recent result can't fill the window and wedge the session (Snip/Summary preserve recent turns and otherwise can't reclaim it). 0 disables the clamp.
compaction.microcompact_keep_lastinteger3How many results per compaction.microcompact_tools kind survive a microcompact pass, counted newest-first across the whole buffer. 0 clears every old result of those kinds.
compaction.microcompact_toolsarray[ "web_fetch", "web_search" ]Microcompact (no LLM call): in the Snip threshold band, clear old tool results only for these tool names — stale, low-value kinds whose results are useless once superseded — keeping the last compaction.microcompact_keep_last results per kind wherever they fall in the buffer. Cheaper and less destructive than the generic Snip (code reads and other results stay). An empty list disables microcompact; the band runs the generic Snip instead.
compaction.preserve_recent_turnsinteger4Documented only by its default. Run kcode config defaults for the full table.
compaction.restore_filesinteger5Post-compaction file restore (R#30): after a Summary compaction, re-read this many of the session's most-recently-read files verbatim into the buffer (source: the fs tools' read-state ledger), so the agent doesn't have to rediscover the files it was working in. Files whose reads survived compaction (path still present in the preserved tail) are skipped; each file is capped at 5k tokens (truncated with a marker). 0 disables the restore.
compaction.restore_skillsinteger3Post-compaction skill restore (2026-08-12): after a Summary compaction, re-inject this many of the session's most-recently-invoked skills' current instructions into the buffer (source: the run_skill invocation ledger, re-resolved against the live skill registry — a skill edited mid-session restores current, a deleted one drops out). A skill whose invocation survived compaction (its name still in the preserved tail) is skipped; each skill is capped at 5k tokens (head-truncated with a marker). 0 disables the restore. The skill catalog needs no restore — it lives in the run_skill tool's descriptor, outside the compacted history.
compaction.restore_skills_token_budgetinteger25000Total token budget for one post-compaction skill-restore pass (byte-estimated at ~4 bytes/token; Claude Code's 25k budget is the precedent). The restore walks the invoked-skill recency list until it has compaction.restore_skills skills or this budget is spent, whichever comes first. 0 disables the restore.
compaction.restore_token_budgetinteger50000Total token budget for one post-compaction file-restore pass (byte-estimated at ~4 bytes/token). The restore walks the recency list until it has compaction.restore_files files or this budget is spent, whichever comes first. 0 disables the restore.
compaction.snip_thresholdfloat0.7Documented only by its default. Run kcode config defaults for the full table.
compaction.summary_max_tokensinteger20000Output-token ceiling for one compaction summarization run. Far past useful — the cap bounds a provider retry loop or a model that ignored the summary template so a runaway summary can't burn the model's whole output budget. Applies to the next built compaction engine (per-session boot or /compact).
event_bus
event_bus.subscriber_buffer_sizeinteger256Documented only by its default. Run kcode config defaults for the full table.
eventsource_fs
eventsource_fs.enabledbooleantrueMaster switch for the session's project file watcher. On (the default) the watcher feeds core.fs.change trigger events and the batched <cp:fs-change> stream, both gitignore-filtered. Set false and no watch is registered at all — file triggers and <cp:fs-change> go silent for every session, and the session stack stops paying the watch cost (the default backend is an FSEvents stream per watcher, with a polling snapshot-diff fallback — file_watcher.backend, default "fsevents"; the poller's file_watcher.poll_interval_ms defaults to 500ms — so the saving is one background stream, or one poll thread and its periodic directory scans when the fallback is in force). Restart-only (a deep lib [section] knob).
eventsource_fs.self_write_ttl_secsinteger5How long (seconds) a recorded agent self-write suppresses the matching path from the external <cp:fs-change> stream — the window that keeps the agent's own Write/Edit churn out of its file-change notices. The session's project file watcher also feeds core.fs.change trigger events (gitignore-filtered, .git/ always excluded) that plugin kcode.on_event("core.fs.change", …) handlers hook. Restart-only (daemon-read).
eventsource_timer
eventsource_timer.cronsarray[]Cron expressions (six-field: sec min hour dom mon dow) registered as session timer triggers. Each fire publishes a core.timer.tick trigger event — hookable by a plugin's kcode.on_event("core.timer.tick", …) handler and rendered on the /control drawer as <cp:event-trigger>. Empty (the default) means no timer source or scheduler task runs for the session. An invalid expression logs a warning and is skipped. Restart-only (daemon-read).
eventsource_timer.default_jitter_msinteger0Documented only by its default. Run kcode config defaults for the full table.
feedback
feedback.endpointstring""The kcode feedback API base URL the daemon POSTs /feedback <message> to (the path /v1/feedback is appended; e.g. https://api.kcode.sh). The body carries the message plus the build identity (version, build id, OS version), with the salted machine fingerprint as the bearer token when it computes. Empty (the default) makes /feedback answer "feedback is not configured" without any network call. Restart-only (the daemon reads it once at boot).
file_watcher
file_watcher.backendstring"fsevents"Documented only by its default. Run kcode config defaults for the full table.
file_watcher.debounce_window_msinteger200Documented only by its default. Run kcode config defaults for the full table.
file_watcher.max_paths_per_eventinteger256Documented only by its default. Run kcode config defaults for the full table.
file_watcher.poll_interval_msinteger500Documented only by its default. Run kcode config defaults for the full table.
headless
headless.max_turn_countinteger50Turn cap for the headless one-shot arm (kcode -p): the run stops after this many provider turns even if the model is still asking for tools. Reads the layered [headless] section (env form KCODE_HEADLESS__MAX_TURN_COUNT); listed by kcode config defaults.
headless.ndjson_flush_bytesinteger2048Delta-batch flush threshold for kcode -p --output-format ndjson: streamed text/thinking chunks accumulate and emit as one JSON line when the buffer holds a newline or reaches this many bytes, whichever comes first — set 1 for per-chunk lines (lowest latency, most lines). The ndjson schema (one versioned JSON object per turn event, final envelope last) lives in libs/headless/spec.md. Env form KCODE_HEADLESS__NDJSON_FLUSH_BYTES.
http_client
http_client.default_timeout_secsinteger30Documented only by its default. Run kcode config defaults for the full table.
judge_orchestration
judge_orchestration.enabledbool?falseMaster switch for the round-end judge (continuation rule 9): when on, the agent loop consults the judge agent kind at round end and its emit_judge_verdict continue/stop verdict feeds the continuation cascade. Off by default — set true to opt in. On-by-default was tried 2026-07-23 and reverted 2026-07-25: the structural cascade already decides when a turn ends, and the judge costs an extra full-price model call at every turn ending. This section is the judge's one config surface (before P15 the switch had no config path at all). Restart-only (a deep lib [section] knob, read when the daemon composes a session).
judge_orchestration.max_invocations_per_turnint?3Hard cap on judge invocations within a single turn; at the cap the loop force-stops instead of looping on verdicts. Restart-only.
judge_orchestration.timeout_secsint?30How long the parent turn waits for the judge subagent to publish its verdict before proceeding without one (the cascade then falls through to its default). Restart-only.
kvstore
kvstore.busy_timeout_secsinteger5How long a connection waits on a locked database before it fails (PRAGMA busy_timeout). The daemon opens the user store once and shares that pool across sessions, so this rarely trips. Restart-only.
kvstore.journal_modestring"wal"sqlite journal mode for both stores: wal (readers never block the writer; the default), delete, or truncate. Restart-only.
kvstore.mmap_size_bytesinteger268435456Memory-map budget per connection (PRAGMA mmap_size, 256 MiB by default): reads of a mapped page skip the read syscall and sqlite's page cache copy. 0 turns mapping off. Applied on every connection since 2026-09-05 (it was accepted but never set before). Restart-only.
kvstore.project_root_overridepath?unsetPins the project-scope store to <path>/.kcode/storage.db instead of the project root kcode resolved. Restart-only.
kvstore.synchronousstring"normal"How hard sqlite syncs a commit to disk (PRAGMA synchronous): off, normal, full, or extra. Under the default WAL journal, normal syncs the log only at a checkpoint, so a per-turn transcript save costs no fsync of its own; a commit is still durable across an app crash, and only a power loss or OS crash can roll back the newest commits. full (sqlite's own default) fsyncs the WAL on every commit — pick it when that last-commit window matters more than write latency (2026-09-05). Restart-only.
kvstore.user_db_path_overridepath?unset (~/.kcode/storage.db)Where the user-scope sqlite store lives. Every project shares this one file for user-scope rows (memories, prompt history, the cost ledger, workspace state). Restart-only.
logger
logger.formatstring"json"Log line format: json (machine-greppable, one object per line) or pretty (human-readable multi-line). Restart-only.
logger.levelstring"info"Minimum level written to the log file: trace | debug | info | warn | error. A non-empty RUST_LOG environment variable overrides this entirely (standard tracing EnvFilter syntax). Restart-only.
logger.log_dirstring?~/.kcode/logDirectory for kcode's diagnostic log files — daily-rolling kcode.log.YYYY-MM-DD (JSON lines by default, for machines) plus two human-read side logs written as one tab-separated row per event (2026-08-09): transport.log.YYYY-MM-DD (2026-08-08) with every outbound HTTP request’s outcome — method, URL (query stripped), status, TTFB, and on failure the full error cause chain — the first stop when a provider turn fails; and agentloop.log.YYYY-MM-DD with the agent loop's branch decisions (turn/round start, continuation rulings, the compaction gate, doom bails, retries, overflow recovery) and the numbers behind each. Tail them with make logs / make logs-transport / make logs-agent. Logs never write to the terminal: the TUI owns the screen and headless stdout/stderr are wire formats. A leading ~/ expands to your home directory. Restart-only.
logger.retention_filesinteger10How many daily log files to keep per stream — the app log (kcode.log.*), the transport log and the agent-loop log each keep this many, today's included; older ones are deleted at startup. 0 keeps everything. Restart-only.
lsp_client
lsp_client.diagnostics_cap_msinteger2500Total wait cap for a fresh diagnostics publish after the post-edit sync; on timeout the result simply carries no digest (fail-soft, never an error). Restart-only.
lsp_client.diagnostics_debounce_msinteger150Quiet window after the first fresh publishDiagnostics following an edit, so a multi-part publish settles before the digest reads it. Restart-only.
lsp_client.diagnostics_enabledbooleantruePost-edit diagnostics digest (R#37): after fs_edit/fs_write, sync the file to its language server and append fresh errors to the tool result + a <cp:lsp-diagnostics> emission. Only already-spawned servers are probed — an edit never boots one; a language without a running server is silently skipped. Restart-only.
lsp_client.diagnostics_max_per_fileinteger20Max error rows the post-edit digest appends per edited file. Restart-only.
lsp_client.outline_max_depthinteger3Maximum directory depth the lsp tool's lsp_outline operation walks when building a workspace-wide symbol outline. Restart-only.
lsp_client.outline_skip_patternsarray[ "target/", "node_modules/", "vendor/" ]Relative-path substrings that exclude files from the lsp_outline walk. Restart-only.
lsp_client.prewarm_countinteger2How many of a project's top languages (ranked by source-file count, computed via a gitignore-respecting walk so target//node_modules/ don't skew it) get their language server pre-warmed — spawned + indexing in the background — the moment a session starts (2026-08-11). The agent's first lsp navigation call then hits a warm or warming server instead of paying rust-analyzer's full cold start mid-turn. Only languages with a configured server are eligible; a missing binary is logged and skipped, never fails the session. 0 disables pre-warming (fully lazy — servers then spawn on first use). Restart-only.
lsp_client.server_initialization_timeout_secsinteger60How long to wait for an LSP server's initialize handshake (and each subsequent request) before declaring it bad. Restart-only.
lsp_client.serversmap?{ rust = "rust-analyzer", typescript/javascript = "typescript-language-server --stdio", go = "gopls", python = "pyright-langserver --stdio", c/cpp = "clangd", bash = "bash-language-server start" }Per-language LSP server-command map driving the agent's lsp tool and the post-edit diagnostics digest — language id (rust, python, typescript, go, …) to the command that spawns its server (single string, whitespace-split, e.g. typescript-language-server --stdio). Defaults cover Rust, TypeScript/JavaScript, Go, Python, C/C++, and Bash (R#37); a missing binary only matters when that language is actually used (servers spawn lazily). In the daemon the pool is shared per project — one language server per language serves every session rooted there, and it shuts down when the project's last session stops. Restart-only.
lsp_client.servers.bashstring"bash-language-server start"Documented only by its default. Run kcode config defaults for the full table.
lsp_client.servers.cstring"clangd"Documented only by its default. Run kcode config defaults for the full table.
lsp_client.servers.cppstring"clangd"Documented only by its default. Run kcode config defaults for the full table.
lsp_client.servers.gostring"gopls"Documented only by its default. Run kcode config defaults for the full table.
lsp_client.servers.javascriptstring"typescript-language-server --stdio"Documented only by its default. Run kcode config defaults for the full table.
lsp_client.servers.pythonstring"pyright-langserver --stdio"Documented only by its default. Run kcode config defaults for the full table.
lsp_client.servers.ruststring"rust-analyzer"Documented only by its default. Run kcode config defaults for the full table.
lsp_client.servers.typescriptstring"typescript-language-server --stdio"Documented only by its default. Run kcode config defaults for the full table.
mcp_protocol
mcp_protocol.max_concurrent_pendinginteger256In-flight requests allowed per MCP server; a further call fails fast with json_rpc.client.too_many_pending instead of queueing. The same figure bounds the buffer of responses that arrive before their call registered. Env form KCODE_MCP_PROTOCOL__MAX_CONCURRENT_PENDING.
mcp_protocol.probe_timeout_secsinteger8How long kcode mcp list may spend probing one server (connect → initialize → count tools) before that row reads probe timed out, so a slow npx cold start or a dead remote never hangs the listing. The probe connects on the rest of this section too (wired 2026-09-05 — before that it ran on the compiled defaults with a hard-coded 8s cap). Env form KCODE_MCP_PROTOCOL__PROBE_TIMEOUT_SECS.
mcp_protocol.request_timeout_secsinteger30How long one request to an MCP server may wait for its response before the call fails with a timeout. Applied to every server the MCP plugin runtime connects to, on first connect and on every supervisor reconnect (wired 2026-09-05 — earlier builds documented the key but connected on the compiled default). Env form KCODE_MCP_PROTOCOL__REQUEST_TIMEOUT_SECS.
mcp_protocol.sse_endpoint_timeout_secsinteger15How long an SSE-transport connect waits for the server's endpoint event (the MCP SSE handshake naming the per-session message URL) before the connect fails with mcp_protocol.sse.endpoint_timeout. Env form KCODE_MCP_PROTOCOL__SSE_ENDPOINT_TIMEOUT_SECS.
memory_consolidator
memory_consolidator.enabledbooleantrueGPU/daemon build only (#147 P5B): turn the automatic background memory consolidator on. On by default (2026-08-20) — the daemon distils recent session transcripts into new memories at a turn boundary once both cadence gates pass. Toggle it live with /memory consolidate on|off (which persists this key AND relays a live SetConsolidatorEnabled, so it applies without a restart); a manual /memory consolidate or the agent's run_command consolidate always runs regardless. A hand-edit of this key applies on restart (the daemon reads it once at boot).
memory_consolidator.max_corpus_tokensinteger200000Documented only by its default. Run kcode config defaults for the full table.
memory_consolidator.max_new_memories_per_runinteger3GPU/daemon build only (#147 P5B): cap on how many memories one consolidation run may create or update — bounds cost + churn. Restart-only (daemon-read).
memory_consolidator.max_transcript_sessionsinteger10GPU/daemon build only (#147 P5B): how many recent sessions (sharing the triggering session's project) feed one run's transcript corpus — bounds input size + cost. Restart-only (daemon-read).
memory_consolidator.min_hours_between_runsinteger24GPU/daemon build only (#147 P5B): minimum whole hours since the last run before an automatic consolidation may fire. One of the two cadence gates (both must pass); a manual run ignores it. Restart-only (daemon-read).
memory_consolidator.min_sessions_between_runsinteger5GPU/daemon build only (#147 P5B): minimum new sessions observed since the last run before an automatic consolidation may fire (derived from the session list, so it survives restarts). The other cadence gate; a manual run ignores it. Restart-only (daemon-read).
memory_consolidator.user_scope_min_confidencefloat0.8GPU/daemon build only (#147 P5B): a distilled candidate the consolidator tags user (global) scope is only written to the user store when its confidence is at least this; a lower-confidence cross-project guess is downgraded to project scope, keeping the global store clean. Restart-only (daemon-read).
memory
memory.auto_recallbooleanfalsePer-turn memory auto-recall (P11): when on, each turn's input queries the memory store's ranking (relevance × confidence × recency) and the top-k hits at or above the confidence floor inject as a <cp:memory-recall> block beside your message, visible in /control and as a one-line 'Recalled N memories into context: …' transcript marker in the chat (2026-08-11). Auto-recalls never stamp a memory's recency (last_used_at) and raise no <cp:memory> operation events — only explicit recalls count as use. Off by default. Toggle it live from Settings › Advanced › "Memory auto-recall" (2026-08-13): the toggle persists this key AND relays it through the daemon's generic ApplySettings settings relay, so running sessions honour the change at their next turn — no restart. A hand-edit of this key still applies on the next daemon boot only.
memory.auto_recall_confidence_floorfloat0.8Only memories at or above this confidence (0.0–1.0) inject on auto-recall; user-saved memories default to 1.0, consolidator-distilled ones carry their distillation confidence. Default 0.8 (raised from 0.5, 2026-08-13): only high-confidence memories inject uninvited. Live-appliable through the daemon's ApplySettings settings relay (no UI row yet); a hand-edit applies on the next daemon boot.
memory.auto_recall_top_kinteger3How many memories one auto-recall injects at most. Live-appliable through the daemon's ApplySettings settings relay (no UI row yet); a hand-edit applies on the next daemon boot.
models_dev
models_dev.ttl_secsinteger300Documented only by its default. Run kcode config defaults for the full table.
models_dev.urlstring"https://models.dev/api.json"Documented only by its default. Run kcode config defaults for the full table.
notification_log
notification_log.capacityinteger500GPU/daemon build only: how many entries the durable §9 notification center keeps in its one global, cross-project log (the ~/.kcode user scope); the oldest drop past this. The center records session state transitions and replays on attach — distinct from the transient per-session command-reply notices and the live tile-attention glow. Restart-only.
notification_log.triggers.completionbooleanfalseGPU/daemon build only: raise a §9 notification-center entry when a working session finishes (returns to idle). Off by default (#145) — a notification on every completed turn buries the ones that need you. Toggle it live from the Settings page's General → 'Notify: turn complete' row — the toggle persists here and relays a live SetNotificationTriggers to the daemon, so it applies without a restart. A hand-edit of this key applies on restart.
notification_log.triggers.errorbooleantrueGPU/daemon build only: raise a §9 notification-center entry when a session hits an error. One of the three center triggers (needs-input + error notify by default; completion is opt-in — #145). Toggle it live from the Settings page's General → 'Notify: errors' row — the toggle persists here and relays a live SetNotificationTriggers to the daemon, so it applies without a restart. A hand-edit of this key applies on restart.
notification_log.triggers.needs_inputbooleantrueGPU/daemon build only: raise a §9 notification-center entry when a session starts needing your input. One of the three center triggers (needs-input + error notify by default; completion is opt-in — #145). Toggle it live from the Settings page's General → 'Notify: needs input' row — the toggle persists here and relays a live SetNotificationTriggers to the daemon, so it applies without a restart. A hand-edit of this key applies on restart.
notifications
notifications.soundbooleantruePlay the OS alert sound with an escalated OS notification (§11.4's audible attention channel): an unfocused-window escalation — a permission request, a needs-input, an error, an agent reach-out, or a turn finishing — posts the banner and sounds with it. On by default; the sound rides the OS notification, so the OS's own notification-sound settings still govern it. Read once at GPU client startup (restart-only).
onboarding
onboarding.dns_check_timeout_secondsinteger3Documented only by its default. Run kcode config defaults for the full table.
permissions
permissions.default_modestring"Default"Documented only by its default. Run kcode config defaults for the full table.
permissions.deny_external_paths_defaultbooleantrueDocumented only by its default. Run kcode config defaults for the full table.
permissions.plan_evidence_timeout_msinteger500How long an interactive permission ask waits for the shell planner's evidence (resolved binary, parsed argv, side-effect class, glob match counts) before showing the plain command-string prompt instead (P3). The evidence card applies to bash asks only; expiry or a planner failure degrades the card, never the decision — the prompt still appears, just without the evidence lines. Env form KCODE_PERMISSIONS__PLAN_EVIDENCE_TIMEOUT_MS.
plugin_host
plugin_host.hot_reloadbooleantrueRe-read a plugin when a file under one of its directories changes, so an edited plugin takes effect in the running session instead of only in the next one. On (the default) each session arms one watcher covering whichever of the built-in, user (~/.kcode/plugins/) and project (<project>/.kcode/plugins/) roots exist. Set false and no watch is registered: the session keeps the plugin set it loaded at start, and skips the watch cost (an FSEvents stream by default — file_watcher.backend; the polling snapshot-diff fallback's file_watcher.poll_interval_ms defaults to 500ms). Restart-only (a deep lib [section] knob).
plugin_host.max_concurrent_invocations_per_plugininteger4Soft cap on how many tool invocations may be dispatched into a single plugin's runtime at once. Reserved — the runtime adapters do not enforce it yet. Restart-only (a deep lib [section] knob).
plugin_links
plugin_links.<name>stringA linked plugin (2026-08-13): <name> = "/abs/dir" under the top-level [plugin_links] table loads the plugin from that folder — anywhere on the filesystem, outside the managed plugin dirs. Written by Settings › Plugins › "+ Add plugin from folder…" (the daemon validates the folder's plugin.toml first) and cleared by Remove; hand-editing works too. A link whose path stops existing rows as missing in Settings › Plugins with a loud notice, never a silent skip. Its own namespace — never inside [plugins.<name>], whose keys are the plugin's settings. User scope; read at session build, so a change applies to new sessions.
plugin_marketplace
plugin_marketplace.cache_ttl_secsinteger86400Documented only by its default. Run kcode config defaults for the full table.
plugin_runtime_lua
plugin_runtime_lua.max_execution_secondsinteger30Documented only by its default. Run kcode config defaults for the full table.
plugin_runtime_lua.memory_limit_bytesinteger67108864Documented only by its default. Run kcode config defaults for the full table.
plugin_runtime_wasm
plugin_runtime_wasm.compile_cachebooleantrueCache compiled wasm plugin modules on disk under <data>/wasm-cache/ — wasmtime's own compile cache, keyed by the module bytes and the compiler configuration and evicted by size and count — so a module compiles once per build and bytes, not once per session stack or launch. Set false to compile on every load. Env form KCODE_PLUGIN_RUNTIME_WASM__COMPILE_CACHE. Restart-only (a deep lib [section] knob).
plugin_runtime_wasm.fuel_budgetinteger1000000000Documented only by its default. Run kcode config defaults for the full table.
plugin_runtime_wasm.max_execution_secondsinteger30Documented only by its default. Run kcode config defaults for the full table.
plugin_runtime_wasm.memory_limit_bytesinteger67108864Documented only by its default. Run kcode config defaults for the full table.
plugins
plugins.<name>.<key>per-plugin schemaPer-plugin settings (P-A, 2026-07-28): a plugin declares its schema as [settings.<key>] tables in its plugin.toml (type string|integer|number|boolean|enum + default + description); users override values here (project config layers over user config). The plugin reads the resolved value via kcode.settings.get(key). Validated against the plugin's declared schema with soft issues — what parses applies, the rest is reported, never fatal. Read at session build (daemon-side), so edits apply to new sessions. Every key under [plugins.<name>] belongs to the plugin's own settings vocabulary — the management keys live in their own top-level namespaces ([plugin_links] and disabled_plugins, 2026-08-13), so a plugin setting named path or enabled (kcode-tail declares path) works normally.
procs
procs.poll_msinteger2000The /procs snapshot poll cadence in milliseconds (T4) — how often the daemon re-scans the process table and pushes ProcsSnapshots while a client watches. Default 2000. The scan runs only while someone watches; closing the overlay cancels it to zero work.
procs.show_allbooleanfalseBypass the /procs dev-relevance filter and show every process (T4) — a debug toggle for tuning the watch table. Default false.
procs.watcharray< table >[]The user watch rules layered ahead of the built-in /procs classifier table (T4), as an array of tables [[procs.watch]] with match (exe-name or command-line substring, case-insensitive), kind (build | server | database | watcher; runner is an alias for watcher), and an optional label. First-match-wins, so a user rule overrides a built-in matching the same text; rules with an unknown kind or empty match are dropped.
providers
providers.<name>.base_urlstring?Per-provider base URL for custom endpoints. Useful for proxies and self-hosted deployments. Baked into the HTTP client at startup — a change needs a restart. A value that doesn't parse as a URL fails startup with a clear validation error instead of silently using the vendor's default endpoint.
scheduler
scheduler.default_jitter_secsinteger0Documented only by its default. Run kcode config defaults for the full table.
scheduler.default_missed_tick_policystring"skip"Documented only by its default. Run kcode config defaults for the full table.
scheduler.max_concurrent_actionsinteger1Documented only by its default. Run kcode config defaults for the full table.
scheduler.publish_events_on_busbooleanfalseDocumented only by its default. Run kcode config defaults for the full table.
session_daemon
session_daemon.agent_activity_max_per_windowinteger20Cap on agent set_activity reports per session within the activity window — the label-churn guard on the working-strip / tile-marker feed. Sits well above the notify cap (one report per step is fine); over-cap reports are silently dropped with a daemon warn log. Restart-only.
session_daemon.agent_activity_window_secsinteger60The agent-activity window length in seconds (pairs with agent_activity_max_per_window). Restart-only.
session_daemon.agent_fork_max_per_windowinteger3Cap on agent-initiated fork_session forks per source session within the fork window — the fork-bomb guard. Over-cap forks are dropped with a notification-center entry. User /fork is never capped. Restart-only.
session_daemon.agent_fork_window_secsinteger600The agent-fork window length in seconds (pairs with agent_fork_max_per_window). Restart-only.
session_daemon.agent_mail_max_per_windowinteger10Cap on cross-session send_message deliveries per sender session within the mail window (W2b) — the mail-storm guard: each delivery to an idle recipient drives a wake turn (a real model call), so uncapped mail amplifies like a fork bomb. An over-cap send returns as a tool error the sender model reads. Within-session mail is never capped. Restart-only.
session_daemon.agent_mail_window_secsinteger60The agent-mail window length in seconds (pairs with agent_mail_max_per_window). Restart-only.
session_daemon.agent_notify_max_per_windowinteger5Cap on agent notify posts per session within the notify window — the spam guard on the notification center. Over-cap notifies are silently dropped with a daemon warn log (no notification about notifications). Restart-only.
session_daemon.agent_notify_window_secsinteger60The agent-notify window length in seconds (pairs with agent_notify_max_per_window). Restart-only.
session_daemon.agent_worktree_max_per_windowinteger3Cap on agent-initiated spawn_worktree_session spawns per parent session within the worktree window (W2) — the worktree-bomb guard. Unlike over-cap forks (dropped with a notification), an over-cap spawn returns as a tool error the parent model reads. User /worktree new is never capped. Restart-only.
session_daemon.agent_worktree_window_secsinteger600The agent-worktree window length in seconds (pairs with agent_worktree_max_per_window). Restart-only.
session_daemon.auto_exportbooleantrueThe always-on session export: the daemon keeps <export dir>/<session-id>.jsonl current for every session — a full overwrite of the stable per-session file at each turn boundary and session stop (detached and best-effort; a failure warns in the daemon log and never affects the turn), removed when the session is deleted, so the export set always mirrors the session store. Feeds analysis / fine-tuning / memory pipelines outside kcode; /share remains the on-demand two-format export. Set false to turn the feature off. Restart-only (the daemon reads it once at boot).
session_daemon.conn_queue_capacityinteger8192GPU client mode: the bound on one client connection's outbound message queue. A client that stops reading — a wedged window, a paused debugger — is cut at this many queued messages instead of growing the daemon without bound; its reconnect supervisor dials back in and replays the session snapshot. A bounded queue allocates as it fills, so the large default costs nothing while a client keeps up. Env form KCODE_SESSION_DAEMON__CONN_QUEUE_CAPACITY. Restart-only. GPU/daemon build only.
session_daemon.control_history_capacityinteger512GPU client mode: how many control-plane (<cp:*>) rows the daemon keeps per live session and replays to the /control overlay on attach. Older rows evict oldest-first; the lifetime counters survive eviction. GPU/daemon build only.
session_daemon.debug_push_msinteger1000The /debug window's snapshot cadence (#69): how often (milliseconds) the daemon rebuilds and pushes the full debug snapshot while at least one client is subscribed — no /debug window open anywhere means no tick and zero snapshot work. Restart-only (the daemon reads it once at boot).
session_daemon.export_dirstring?~/.kcode/exportsWhere the always-on session exports land (pairs with auto_export). Unset = exports/ under the kcode data dir; a leading ~ expands. Restart-only.
session_daemon.goal_driver_stall_turnsinteger3The /goal driver's no-progress guard (#59): after this many consecutive goal-driven turns that called no tool, the driver disarms with a needs-input notification instead of nudging in circles. 0 disables the guard. Restart-only.
session_daemon.idle_grace_msinteger5000GPU/daemon build only: how long (milliseconds) the shared background daemon lingers after its last client disconnects before exiting. The daemon is a guaranteed singleton that dies with the app (#166) — closing the last kcode window drops the client connection, and once no client remains the daemon shuts down after this grace window (the grace absorbs a client restart/reconnect without tearing down live sessions). Restart-only.
session_daemon.keep_awakebooleantrueKeep the Mac awake while an agent works (macOS only): the daemon holds a no-idle-sleep power assertion (PreventUserIdleSystemSleep, via the keepawake crate) whenever ≥1 live session is mid-turn or has watched background commands outstanding (any backgrounded bash command is exit-watched by default, and shell_spawn with notify_on_exit: true — an idle session with a build in flight is exactly the walk-away case), and releases it the moment every session is idle with nothing watched. Long agent runs and background builds survive the machine's idle-sleep timer, on AC and battery, while an idle kcode never pins the machine awake. The display may still sleep; lid-closed clamshell sleep is unaffected; the assertion is process-scoped so a daemon crash releases it. Inspect live with pmset -g assertions. Restart-only (the daemon reads it once at boot).
session_daemon.procs_poll_msinteger2000Documented only by its default. Run kcode config defaults for the full table.
session_daemon.procs_watcharray[]Documented only by its default. Run kcode config defaults for the full table.
session_daemon.prompt_history_maxinteger200Cap on the project-scoped prompt-history row (the composer's ↑/↓ recall list, persisted in <project>/.kcode/storage.db and surviving restarts): the newest this many submitted prompts/commands are kept. Restart-only (the daemon reads it once at boot).
session_daemon.restart_max_per_windowinteger3Cap on per-session restart-storm respawns within the restart window: a session whose agent runner keeps crashing is respawned at most this many times per window, then left stopped with an error. Guards process respawns — distinct from the doom-loop guard, which detects repeated identical tool calls inside a turn. Restart-only.
session_daemon.restart_window_secsinteger60The restart-storm window length in seconds (pairs with restart_max_per_window). Restart-only.
session_daemon.startup_deadline_msinteger30000GPU/daemon build only: how long (milliseconds) a freshly-spawned daemon waits for its first client to connect before giving up and exiting. Guards against an orphaned daemon that never gets a client (e.g. the spawning app died during launch). Restart-only.
session_daemon.webhook_enabledbooleanfalseWhether the daemon binds the loopback webhook listener (2026-08-12). On — with at least one webhook_tokens entry — POSTs to http://127.0.0.1:<webhook_port>/webhook/<name> fire the webhook:<name> trigger event: project [[hooks]] with on = "webhook:<name>" and plugin kcode.on_event("webhook:<name>", …) handlers hear it (Observe intent — never an agent turn; the socket-trusted kcode trigger remains the only external doorway that can inject a turn). Public exposure stays the user's own tunnel. Restart-only (daemon-read).
session_daemon.webhook_portinteger4517The webhook listener's port on 127.0.0.1 (the bind address is fixed at loopback). Restart-only (daemon-read).
session_daemon.webhook_tokenstable?{}Shared-secret token per trigger name — [session_daemon.webhook_tokens] deploy = "s3cret". The allowlist: only named entries are fireable, each POST authenticating with Authorization: Bearer <token> (or ?token= for senders that cannot set headers); an undeclared name is 404, a wrong token 401. Empty (the default) refuses to start the listener even when enabled. Restart-only (daemon-read).
session_panels
session_panels.chat.auto_collapse_turnsbooleantrueGPU client mode: auto-collapse older conversation turns so the transcript stays focused on the current exchange. On by default: every turn except the most recent folds to a clickable one-line summary — a disclosure glyph, your instruction's first line, and a dim · N tools · reply tail; click the one-liner to expand it. An expanded turn carries its own collapse control in that same left gutter — on the user message that opens the turn — click it to fold the turn back up; a just-finished live turn also carries the same on its ▣ model · elapsed completion footer (the opener control is always present, including on restored history where no footer renders). A per-turn click always overrides the policy for that turn, and jumping (⌘J or the Cmd-P picker) into a collapsed turn expands it. Set false to render every turn in full. Re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.block_windowing.enabledbooleantrueGPU client mode: whether a settled chat message windows its own blocks — lays out only the paragraphs, code blocks and tables near the viewport and renders the rest as spacers of their recorded sizes, so one very long message costs a frame what the part on screen costs, not the whole message (scroll geometry and text selection are unchanged). On by default. Set false to lay every block of every message out on every frame — the escape hatch if a long message ever renders wrong, and what the scaling probe uses for its before/after table. Re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.block_windowing.margin_viewportsfloat1.0GPU client mode: how far beyond the visible transcript a settled message still lays out for real, in viewports — 1.0 (the default) keeps one viewport above and one below ready, so a fast scroll lands on real blocks; 0.0 windows to the viewport alone (the cheapest frame, but a fling can show a block's placeholder for one frame before it lays out). Re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.editor_modestring"insert"GPU client mode: the composer's editor mode — insert (default — every keystroke types, Escape peels the overlay stack) or vim (a modal composer: it boots in INSERT; Escape enters NORMAL where h/l/w/b/e/0/$/gg/G/f/t move, x/dd/D/cc/C/yy/p and d/c/y-plus-motion edit with a count prefix, i/a/A/o/O re-enter INSERT, u/ctrl-r undo/redo, Enter submits, and a NORMAL/INSERT chip sits above the composer; Escape in NORMAL still peels the overlay stack). Re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.font_sizefloat18.0GPU client mode: absolute chat transcript font size in points. Defaults to 18 pt (2026-08-08); an explicit 0 follows the active theme — the theme body size plus the chat's +2 pt reading bump (17 pt on the bundled themes); a positive value pins every transcript message (prose, with code/math/headings scaled proportionally) to that size across all chat panels, and the metadata tiers (timestamps, captions, the status bar) scale by the same factor so they keep their proportion against the content. Adjust live from the settings page's General → 'Chat font size' stepper (half-point steps, 8–30 pt, Auto below the floor); re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.input_font_sizefloat18.0GPU client mode: absolute composer (chat input) font size in points. Defaults to 18 pt (2026-08-08); an explicit 0 follows the transcript size (or the active theme when that is also 0); a positive value pins the composer text size across all chat panels, independent of the transcript size. Adjust live from the settings page's General → 'Composer font size' stepper; re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.max_input_rowsinteger0GPU client mode: cap the chat composer's auto-growing height, in rows. 0 (default) is automatic — the composer grows with what you paste or type up to the larger of 12 rows or half the window height (raised 2026-08-12 so the composer fits the full current message), then scrolls internally (#41). A positive value pins the cap to that many rows regardless of window size. Re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.reply_smear.animation_length_msinteger150GPU client mode: how long (milliseconds) the reply smear takes to settle as the reply tip advances — each corner of the cursor quad is a critically-damped spring; this is its settle time. Larger = a longer, lazier trail. Live-reloads on a config.toml save. GPU build only.
session_panels.chat.reply_smear.colorstring""GPU client mode: the reply smear colour as a hex string ("#5B9EF9", the # optional). Empty (the default) rides the theme text colour — the reply smear's signature, distinct from the user caret smear (which rides the accent). A malformed value falls back to the text colour too. Live-reloads on a config.toml save. GPU build only.
session_panels.chat.reply_smear.enabledbooleantrueGPU client mode: whether the reply smear draws — a Neovide-style animated cursor bar that trails the streaming reply's glyph tip through the chat as the agent's text lays out (and shows in the Mission Control grid tile miniatures too). The agent-output twin of the user's caret smear (session_presenter.cursor_smear.*), with its own independent knobs. On by default; set false for no reply cursor. Suppressed by the global animation switch (reduce-motion turns it off, since it auto-advances with the text). Live-reloads on a config.toml save. macOS/Metal only. GPU build only.
session_panels.chat.reply_smear.short_animation_length_msinteger40GPU client mode: the reply smear's settle time (milliseconds) for a short, adjacent-glyph advance (a single token landing next to the last), kept snappy — as distinct from a long jump such as a line wrap (animation_length_ms). Live-reloads on a config.toml save. GPU build only.
session_panels.chat.reply_smear.trail_sizeinteger100GPU client mode: the reply smear's trail length as a percent, 0–100. 100 (default) is the full Neovide smear — the trailing corner of the cursor quad lags farthest behind as the reply advances; 0 is no trail (a rigid bar). Values above 100 clamp to 100. Live-reloads on a config.toml save. GPU build only.
session_panels.chat.reply_smear.width_pxinteger4GPU client mode: the reply smear bar's width in pixels — the opaque bar painted at the streaming glyph tip, behind the text. Live-reloads on a config.toml save. GPU build only.
session_panels.chat.show_controlbooleanfalseGPU client mode: show control-plane status lines — the injected <cp:*> channel messages (e.g. the per-turn permission context) — as dim status rows in the chat panel, arriving live on the control stream during a turn and decoded from the session buffer on a reload. Hidden by default, matching the terminal UI (which never shows them). Toggle live via /show-control; re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.show_thinkingbooleanfalseGPU client mode: the default chain-of-thought (reasoning text) visibility for a session that has never run /cot (hidden by default). Gates only the reasoning text — the live ✦ Thinking… indicator shows regardless (#170). A per-session /cot toggle overrides this and is persisted on the session row, so it is restored on --resume; re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.show_timestampsbooleantrueGPU client mode: show dim right-aligned timestamps on chat messages. Shown by default, matching opencode. Times are written as 12-hour with an AM/PM marker. Every rendered time in the app goes through one shared vocabulary (message headers, the notification center, and the /stats and /debug “as of” footers), so no two surfaces can disagree — they used to, in three different formats, one of which showed a UTC instant as if it were your local time. Toggle live via /timestamps; re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.show_toolsbooleantrueGPU client mode: show tool-call bubbles (name + args + result) in the chat panel. Shown by default, matching the terminal UI; the agent invokes tools either way — this gates only the view. Toggle live via /show-tools; re-applied to live panels on a config.toml save. GPU build only.
session_panels.chat.working_stylestring"dot-matrix"GPU client mode: which working-state ('thinking') animation the chat panel's busy strip plays while a turn is in flight — eight frame-driven styles (per-display-frame interpolation like the cursor smears, never tick-driven, all stateless): dot-matrix (default — kcode's signature: the website's dot-matrix hero at strip scale, a 15×5 LED grid lit by the Mission Control backdrop's own noise field and sweep, showing a pictogram of the turn's phase — soft sparkles gathering into a line while the model thinks, a >_ prompt with a blinking cursor while a tool runs, three lines being written while the reply streams, a fork with one lit branch per running agent, a bar collapsing inward while compacting, amber pause bars while the turn is parked on you, a single slow-blinking standby LED through a provider retry, and a checkmark that reveals, holds and fades over ~0.9 s when the turn completes; the field's shimmer follows event recency, idling as a stall grows), dots (the chat-native typing indicator: three accent dots rising and fading in a staggered wave), equalizer (five audio-meter pill bars dancing on two incommensurate sines), sweep (a Larson-scanner accent head bouncing along a short track, towing a Gaussian trail), orbit (a dot circling a ··· ellipsis), hairline (a 1px gradient line whose bright centre slides with a Gaussian falloff), nebula (a faint accent bloom in the panel backdrop, fading on the first streamed token), and comet (an accent glow travelling the composer's L-frame). An unknown value — including the removed wander — falls back to dot-matrix. With animations off, every style renders its static first frame (the dot matrix: the phase's resolved picture, and no post-turn checkmark linger). Cycle it live from the settings page's General → 'Working animation' row; re-applied to live panels on a config.toml save. GPU build only.
session_panels.files.editorstring?(auto-detect)GPU client mode: the command used to open a file clicked in the /files overlay or in an lsp tool result row. The clicked file's absolute path is appended to the command, or substituted for a {} placeholder if the command contains one; an LSP row also carries a 1-indexed line, so a bare known editor gets its line-jump argument appended (VS Code-family via --goto file:line, Zed/Sublime via a positional file:line) and a {line} placeholder is substituted where you place it (e.g. code --goto {}:{line}). A /files click carries no line and opens the whole file. When unset, kcode auto-detects the first GUI editor on PATH — cursor, code, zed, subl, windsurf — and falls back to macOS open (which respects your file associations, but cannot target a line). An all-session setting editable on the Settings page; captured at startup, so restart-only. GPU build only.
session_presenter
session_presenter.animation_fpsinteger30GPU client mode: how often decorative motion repaints, in frames per second — the chat panel's busy-turn pump (the tool spinner, the working-state animation, the stall clock, the dot-matrix done linger), the paint of streamed reply text (a burst of provider deltas inside one period lands in one paint; the text arrives whole, and every other turn event — a tool starting, the turn ending — paints at once), the reply smear, the empty-grid invitation's breathing pulse, and Mission Control's tile breath + ambient backdrop shader pump. Every one of these reads the wall clock, so a lower rate changes only how many frames they cost, not how fast they move; interaction transitions (the hover peek, the wordmark fade, text-selection auto-scroll) and the global caret smear stay at display rate. 0 = every display frame (120 a second on a ProMotion display). Default 30: the motion reads as continuous while a live turn's redraw work drops to a quarter of display rate (2026-09-04). Cycle it live from the settings page's General → 'Motion frame rate' row (15 → 24 → 30 → 60 → Display rate); live-reloads on a config.toml save, diff-gated. GPU build only.
session_presenter.animationsbool?trueGPU client mode: the one switch for every animation in the app — the in-flight tool spinner, the global caret smear and the per-chat reply smear, the grid miniature pumps, the context menu's open/close ease, the underlay hints' breathing pulse, the notification drawer's slide, and the ambient backdrop shader. Off renders each of them static at rest (the spinner becomes a still '⋯', the smears jump instead of trailing, the hints sit at full presence, the shader freezes on the frame it was showing) and stops the frame requests that drove them, so nothing shifts position between the two modes. On unless you set it to false. kcode does not consult macOS System Settings → Accessibility → Display → Reduce motion: that was built and then removed on 2026-07-26, because the only documented way to read it links AppKit into the process and that broke the library's tests. Motion sensitivity is also why it is one setting rather than a per-animation matrix. Flip it live from the settings page's General → Animations toggle, the ⌘P palette's 'Toggle animations', or the context menu — all three persist here, so the choice survives a relaunch. Live-reloads on a config.toml save, diff-gated so an unrelated save never clobbers a runtime toggle.
session_presenter.approach.enabledbooleantrueGPU client mode: whether a plain cursor hover over a grid session tile announces it with a subtle rise + soft edge-glow (the approach feel, before the ~1s dwell-peek). Set false and a hover still solidifies the tile for readability but adds no rise or glow. Restart-only. GPU build only.
session_presenter.approach.glow_pctinteger100GPU client mode: the hover edge-glow strength, as a percent of the authored approach halo (0 = off, 100 = full). A softer, tighter foreground light-ring than the pick-up halo, so a hovered tile announces without reading as loud as a grabbed one. Default 100. Restart-only. GPU build only.
session_presenter.approach.lift_pctinteger45GPU client mode: how far the hover rise reaches, as a percent (0 = the edge-glow alone, no rise; 100 = the full pick-up depth a pressed tile shows). gpui has no element transform, so the lift is expressed as depth-shadow rise. Default 45. Restart-only. GPU build only.
session_presenter.cursor_smear.animation_length_msinteger150GPU client mode: how long (milliseconds) the smear takes to settle on a long jump — e.g. from the chat input to a picker row. Each corner of the cursor quad is a critically-damped spring; this is its settle time. Larger = a longer, lazier smear. Live-reloads on a config.toml save. GPU build only.
session_presenter.cursor_smear.colorstring""GPU client mode: the smear colour as a hex string ("#5B9EF9", the # optional). Empty (the default) rides the theme accent colour — the caret smear's signature as your cursor, distinct from the reply smear (which rides the text colour); a malformed value falls back to the accent too. Live-reloads on a config.toml save. GPU build only.
session_presenter.cursor_smear.enabledbooleantrueGPU client mode: whether the global cursor smear draws — a Neovide-style trail on the text caret that follows it wherever it moves, including jumps between the chat input and the overlay pickers (Cmd-K switcher, Cmd-P palette, / slash menu). On by default; set false for a plain caret. Also honours the global animation switch (reduce-motion turns it off regardless). Live-reloads on a config.toml save. macOS/Metal only. GPU build only.
session_presenter.cursor_smear.short_animation_length_msinteger40GPU client mode: the settle time (milliseconds) for a short, adjacent-glyph caret move (typing), kept snappy so per-keystroke motion doesn't feel laggy — as distinct from a long region jump (animation_length_ms). Live-reloads on a config.toml save. GPU build only.
session_presenter.cursor_smear.trail_sizeinteger100GPU client mode: the smear's trail length as a percent, 0–100. 100 (default) is the full Neovide smear — the trailing corner of the cursor quad lags farthest behind on a move; 0 is no trail (the cursor jumps rigidly). Values above 100 clamp to 100. Live-reloads on a config.toml save. GPU build only.
session_presenter.cursor_smear.width_pxinteger4GPU client mode: the smeared caret bar's width in pixels — an opaque bar drawn over the native caret (about twice a typical 2px caret, so the smear reads clearly). Live-reloads on a config.toml save. GPU build only.
session_presenter.default_layoutstring"Grid"The layout the Mission Control window opens in before a persisted workspace loads — Grid | Focused | Tabbed | Sidebar (Sidebar: the macOS split view — a left rail lists every session, the focused session fills the rest; added 2026-07-31). The GPU window is the only frontend. Cycle the live layout from the settings page (⌘,), the ⌘P palette, or the right-click context menu.
session_presenter.dir_pickerstring"app"GPU client mode: which directory picker ⌘N (and the context menu / palette 'New session') opens for a new session. "app" = the in-app browser modal (favorites, recents, breadcrumb navigation, inline new-folder creation — see the Cmd-N shortcut); "native" = the macOS folder dialog (NSOpenPanel). Switched live from Settings (⌘,) › General › Directory picker (a cycle row) and read fresh on every open, so a flip applies to the very next ⌘N; a direct config edit applies the same way (no restart, though the key is only read client-side at picker-open time). GPU build only.
session_presenter.drag_threshold_pxinteger8GPU client mode: how far (px) a press must move on a Mission Control tile before it arms a drag. gpui's built-in default is 2px, so a click that jitters a few pixels between press and release is swallowed as a drag and never opens the session; the default 8 keeps a slightly-imperfect click on an unzoomed tile a click (maximize) while a deliberate drag still reorders/groups. Restart-only. GPU build only.
session_presenter.favorite_dirsarray[]GPU client mode: the in-app directory picker's starred directories — the sidebar's Favorites section. Absolute paths; a leading ~ is expanded for hand-edited entries. Written as the whole list every time a directory is starred/unstarred in the picker (⌘D or the row star), so the file always mirrors the picker. GPU build only.
session_presenter.grid_maxinteger4GPU client mode: the Mission Control grid's column cap (1–8), persisted to the daemon when you switch layout or step it (− / +) on the settings page's General section. GPU build only.
session_presenter.mono_font_familystring?(theme default)GPU client mode: the mono (code) font family code blocks, diffs, and tool output render in. Unset = the active theme's authored mono family (Menlo). Set it live from the Settings page's General → Mono font picker (a searchable list of every installed font, plus “Theme default”); the pick persists here and re-installs the active theme at once. The binary maps it onto Wingman's theming.mono_font_family. A hand-edit of this key applies on restart. GPU build only.
session_presenter.peek.dwell_msinteger1000GPU client mode: how long the pointer must dwell on a grid session tile before it floats (milliseconds). Default 1000 (one second) — deliberate, so a passing hover doesn't trip the float. Restart-only. GPU build only.
session_presenter.peek.enabledbooleantrueGPU client mode: whether a hover-dwell over a grid session tile floats it to ~75% for a transient read (peek-zoom). Set false to turn peek off entirely. Restart-only. GPU build only.
session_presenter.settings_navstring"sidebar"GPU client mode: the settings page's navigation layout. "sidebar" = the macOS-System-Settings left rail of sections (the default); "tabs" = a horizontal tab bar under the title. Same sections, selection, and keyboard drive either way (↑/↓ move the selection; in the tab-bar layout ←/→ also move it, → moves the tab, and Enter alone dives into a section's rows). Switched live from Settings (⌘,) › General › Settings navigation (a cycle row — an open page re-lays out in place) and read fresh on every open, so a direct config edit applies to the next settings open. GPU build only.
session_presenter.shader.backdropstring"shader"GPU client mode: which one thing paints behind the Mission Control grid — shader (the ambient shader named by shader.background, the dot-matrix LED field ported from the kcode website), image (the still at shader.background_image), video (the loop at shader.background_video), or off (the plain themed background; the per-tile light and reactive feed stand down too). One explicit choice: the image and video paths keep their values while not chosen (the Settings rows say not in use), so switching back needs no re-pick. Picking an image or video on the Settings page chooses it; cycling the backdrop style chooses the shader; clearing the chosen file falls back to the shader. A chosen image or video that can't be read (missing, unreadable, not a format the decoder accepts) is a fallback, not a reset: the shader paints instead, the row says can't be read, and a notice names the file and the reason — the grid never goes dark without saying why. Cycle it live on the Settings page's General → Backdrop (Shader / Image / Video / Off); a config-save re-applies it without a restart. A config written before 2026-09-03 with the old shader.enabled switch is migrated on load to the choice its rules used to paint (a video path → video, else an image path → image, else enabled = falseoff, else shader), and the stale key is removed. GPU build only.
session_presenter.shader.backgroundstring"dot-matrix"GPU client mode: which shader is the ambient backdrop while shader.backdrop = "shader" — a bundled built-in name (dot-matrix, the LED-grid field ported from the kcode website, which also lights shader.wordmark into its dots on the empty grid) or the stem of a ~/.kcode/shaders/<name>.glsl Shadertoy-style file you've dropped in. Cycle it on the Settings page's General → Backdrop style (which also chooses the shader backdrop), or set it here; a config-save re-applies live (the config value is watched; the shader source file itself is read once on select, not hot-watched). A name that doesn't resolve (a removed built-in, a typo, a missing file) falls back to dot-matrix with a warning in the log rather than leaving the grid dark. Unset paints no backdrop. The row says not in use while an image or video is the backdrop and showing as fallback while the shader paints in place of a chosen file that can't be read. GPU build only.
session_presenter.shader.background_imagestring?(none)GPU client mode: an absolute path to a still image (PNG/JPEG/…) painted cover-fit behind the Mission Control grid while shader.backdrop = "image" — a wallpaper for the control room. While it shows, the ambient background shader (and its wordmark) stand down (the bundled shaders paint opaquely and would hide it); the per-tile light and post-process still run. The path is kept while another backdrop is chosen (the Settings row says not in use). A file that can't be read leaves the shader painting as the fallback, marks the row can't be read, and posts a notice naming the file and the reason — the grid never goes blank over a bad path. Grid mode only — a maximized chat never shows it. Pick it on the Settings page's General → Background image (a native file dialog; a pick also chooses the image backdrop). A picked file is copied into ~/.kcode/backdrops/image/ and the copy's path is what this key keeps: the dialog's permission to read a file under a macOS-guarded folder (Dropbox and other cloud-storage mounts, Desktop, Documents, Downloads) lasts only for that launch, and the copy stays readable at every launch. One copy at a time — a new pick replaces it, and clearing the row removes it; a path written here by hand is used as it is and never removed. If the copy fails, the picked file is used in place and a notice says so. Clear it with the row's ✕ (or Backspace / Delete on the selected row), which falls back to the shader when the image was the backdrop, or by removing the key. A config-save re-applies live. GPU build only.
session_presenter.shader.background_videostring?(none)GPU client mode: an absolute path to a video file (anything macOS AVFoundation decodes — MP4/MOV/…) played muted and looping, cover-fit behind the Mission Control grid while shader.backdrop = "video" — a live wallpaper. It stands the ambient background shader (and its wordmark) down the way the image does; the per-tile light and post-process still run. The path is kept while another backdrop is chosen (the Settings row says not in use). A file that can't be opened leaves the shader painting as the fallback, marks the row can't be read, and posts a notice — the grid never goes blank over a bad path. Grid mode only, and it pauses on a held frame while the grid is hidden or animations are off (the motion switch), so nothing decodes invisibly. Pick it on the Settings page's General → Background video (a native file dialog; a pick also chooses the video backdrop). A picked file is copied into ~/.kcode/backdrops/video/ and the copy's path is what this key keeps, for the same reason as the image: the dialog's permission to read a file under a macOS-guarded folder lasts only for that launch. One copy at a time — a new pick replaces it, clearing the row removes it, a hand-written path is used as it is; a failed copy uses the picked file in place and says so. Clear it with the row's ✕ (or Backspace / Delete on the selected row), which falls back to the shader when the video was the backdrop, or by removing the key. A config-save re-applies live. macOS GPU build only.
session_presenter.shader.per_tilebooleantrueGPU client mode: whether each session tile in the grid carries its own reactive light field — a calm coloured light around the frosted tile — a reactive edge-glow plus an animated field in the margins — that brightens with that session's activity, warms as its context window fills, quickens with its reasoning effort, and reserves an amber glow when it needs you. Painted per tile (Grid mode only) from each session's own live state, distinct from the single grid-wide ambient backdrop. Stands down (the row says not in use) while shader.backdrop = "off". Toggle it live on the Settings page's General → Per-tile activity light. GPU build only.
session_presenter.shader.post_processstring?(none)GPU client mode: an optional full-window post-process shader run over the composited frame (same built-in-name-or-~/.kcode/shaders/*.glsl resolution as the background). Unset — the default — means no post pass. No settings-page control yet; set it here, and a config-save re-applies live. GPU build only.
session_presenter.shader.reactivebooleantrueGPU client mode: whether the ambient backdrop reacts to live session state — brightening with the grid's mean activity, warming when a session needs you, and blooming when a turn completes. When false the backdrop still paints but is fed a zeroed, static state. Stands down (the row says not in use) while shader.backdrop = "off". Toggle it live on the Settings page's General → Backdrop reacts to activity. GPU build only.
session_presenter.shader.wordmarkstring"kcode"GPU client mode: the text the dot-matrix backdrop lights into its LEDs on the empty Mission Control grid — the kcode website's hero, where the dots spell the name. It fades out once the first session tile lands and back in when the grid empties, and the empty-grid invitation drops its own logo and sits below it while it shows. Blank ("") means no wordmark (the invitation then carries the brand itself). Only meaningful while a background shader is painting — with an image, a video, or nothing chosen as shader.backdrop, nothing reads it (the Settings row says not in use). Edit it on the Settings page's General → Backdrop wordmark; a config-save re-applies live. GPU build only.
session_presenter.solo_hint_linger_msinteger5000GPU client mode: how long (milliseconds) the single-session '⌘N to start another session' underlay hint lingers before it auto-dismisses, so it teaches the spawn gesture once each time the grid drops to a lone tile instead of being a permanent fixture (#171). It re-shows on the next entry into the single-tile state. 0 keeps it up for as long as one tile remains (the pre-#171 permanent behavior). Does not affect the zero-session empty-state invitation, which is always shown. Restart-only. GPU build only.
session_presenter.switcher.recent_per_projectinteger3GPU client mode: in the project-first recall switcher (⌘K), how many of a drilled-in project's most-recent stopped sessions show before the 'show older' toggle row — keeps a project with a long history to a short list. Detached (live, off-grid) sessions are always shown in full; only the stopped pile is capped, and typing a search bypasses the cap (it spans the whole history). Restart-only. GPU build only.
session_presenter.tile_blurint?unsetGPU client mode: the resting Mission-Control tile's backdrop-blur radius in px. Unset = follow the active theme's authored tile_blur (the default — the theme owns the frost); 0 = clean glass (no blur, just the translucent tint over the backdrop); n = that blur radius. Separate from tile_opacity so you can keep the glass look while dropping the blur-tint. Applies to a tile at rest only. Adjust it live on the Settings page's General → Tile blur stepper (4 px steps; below 0 steps back to Auto, 0 shown as Off); a config-save re-applies it too. GPU build only.
session_presenter.tile_opacityinteger0GPU client mode: the resting Mission-Control tile's translucency, as a percent. 0 = follow the active theme's authored tile_rest_opacity (the default — the theme owns how much an idle card recedes into the ambient backdrop); 1–100 overrides it (lower = more of the ambient shader/background shows through). Applies to a tile at rest only — hover, maximize, and an attention glow still solidify to fully opaque, so the engagement cue is never lost. Adjust it live on the Settings page's General → Tile opacity stepper (5% steps, 0 shown as Auto); a config-save re-applies it too. GPU build only.
session_presenter.ui_font_familystring?(theme default)GPU client mode: the UI font family every proportional text role renders in — chat body, titles, captions, chrome, overlays. Unset = the active theme's authored family, which is now a monospace family (Menlo) — kcode's whole interface renders monospace by default (#185, matching a terminal coding agent); set this to any proportional family to override. Set it live from the Settings page's General → UI font picker (a searchable list of every font installed on your machine, plus a “Theme default” option to clear the override); the pick persists here and re-installs the active theme so the new font applies at once. The binary maps it onto Wingman's theming.ui_font_family so the window opens in your font. A hand-edit of this key applies on restart. GPU build only.
session_presenter.ui_scaleinteger100GPU client mode: the global UI text scale, as a percent (100 = the theme's authored font sizes). The binary maps it onto Wingman's theming.text_scale (÷100) so the window opens at your zoom. Change it live with ⌘+/⌘− (⌘0 resets to 100) or the Settings page's General → Text size stepper (50–300%, 10% steps); each change re-installs the active theme scaled and persists here. Scales every font — chat, chrome, overlays — since they all read the theme's type scale. A hand-edit of this key applies on restart. GPU build only.
shell_client
shell_client.blocking_request_ceiling_msinteger86400000Documented only by its default. Run kcode config defaults for the full table.
shell_client.blocking_request_margin_msinteger5000Documented only by its default. Run kcode config defaults for the full table.
shell_client.events_buffer_capinteger256Documented only by its default. Run kcode config defaults for the full table.
shell_client.fast_death_window_msinteger2000Documented only by its default. Run kcode config defaults for the full table.
shell_client.max_consecutive_fast_deathsinteger2Documented only by its default. Run kcode config defaults for the full table.
shell_client.request_timeout_msinteger30000Documented only by its default. Run kcode config defaults for the full table.
shell_host
shell_host.default_timeout_msint?Default wall-clock budget for shell.exec when the request omits one. Unset waits indefinitely (the bash tool has its own tool_bash.default_timeout_seconds on top).
shell_host.preview_linesinteger50Lines of head/tail preview in each shell result; the full stream stays in the host's output stash. Applies inside the re-exec'd shell host, so it covers bash and every shell.* tool.
shell_host.resource_poll_interval_msinteger500How often each running command's per-pid resources (cpu/rss/io) are sampled for the run footprint shown on bash results. 0 disables sampling.
skill_framework
skill_framework.follow_symlinksbooleantrueFollow symlinks encountered while walking a skill_dirs directory. On by default so a centrally-managed setup — where individual skill files or skill directories under ~/.kcode/skills are symlinks into a shared repo — resolves to the real files (#175). A symlinked root skills dir is followed regardless; this governs symlinks met during the walk. Restart-only (a deep lib [section] knob).
skill_framework.respect_gitignorebooleantrueDocumented only by its default. Run kcode config defaults for the full table.
skill_framework.respect_ignorebooleantrueDocumented only by its default. Run kcode config defaults for the full table.
snapshot
snapshot.additional_excludesarray[ "*.log", "node_modules/", ".kcode/" ]Documented only by its default. Run kcode config defaults for the full table.
snapshot.blob_grace_secsinteger3600How long (seconds) an unreferenced snapshot blob is kept before a collection deletes it. A capture in another process may have written its blobs but not yet the manifest naming them; the grace covers that window. Default 3600.
snapshot.gc_interval_secsinteger300The least time (seconds) between two blob collections of one project. A collection runs after a retention prune removed a manifest, and no more often than this; the snapshot gc log line reports each one. Default 300.
snapshot.large_file_threshold_mbinteger100Documented only by its default. Run kcode config defaults for the full table.
snapshot.respect_project_gitignorebooleantrueDocumented only by its default. Run kcode config defaults for the full table.
snapshot.retention_countinteger50How many rewind checkpoints one project keeps (R#74), per kind — turn-start checkpoints and per-edit captures each against their own cap: the oldest manifests beyond it are pruned after each capture, and the blobs only the pruned manifests named are collected afterwards. Default 50; 0 = keep everything.
startup_trace
startup_trace.slow_startup_warn_msfloat3000.0Launch-to-event-loop total (milliseconds) above which the GPU client's always-on startup-phase summary logs at warn (naming the stalled phase) instead of info — the slow-launch self-diagnosis in the daily log. Read once per launch at boot-complete.
subagent
subagent.advisor_enabledbooleantrueMaster switch for the advisor tool (a read-only sub-agent the model calls for a second opinion). On by default; off removes the tool from the agent's set entirely. Restart-only (read at stack build).
subagent.advisor_modelstring?(unset)The model the advisor tool runs on, as a full model id or a catalogue display label. It must differ from the calling session's model (a same-model call is refused — a second opinion from the same model is no opinion). Unset (the default) leaves the advisor unconfigured: the tool then tells the user to set this key rather than silently auto-picking (2026-08-20). Restart-only (read at stack build).
subagent.prompt_preview_max_charsinteger240Documented only by its default. Run kcode config defaults for the full table.
telemetry_otel
telemetry_otel.endpointstring?OTLP collector URL. The config key wins; the OTEL_EXPORTER_OTLP_ENDPOINT env var is consulted only when the key is unset. Unset everywhere means telemetry is fully off — nothing leaves the machine. When set, kcode bridges tracing spans to your collector and records the fed instruments (R#08): cost + token counters at the agent-loop charge sites, a turn-latency histogram, and a session spawn/stop counter — categorical attributes only (provider/model/kind/event; an invariant test pins the allowlist so no attribute can ever carry a path or content). Independently of telemetry, a panic hook writes a local crash bundle to ~/.kcode/crashes/<timestamp>.txt (build identity, panic + location, backtrace, log tail) that is never uploaded — the next launch logs a notice per un-seen bundle. Every network egress and its gate is enumerated in docs/manual/privacy.md. Restart-only.
telemetry_otel.headersarray[]Extra key/value header pairs sent to the collector (auth tokens etc.), e.g. [["x-api-key", "…"]]. Restart-only.
telemetry_otel.metric_export_interval_secsinteger5Cadence of the periodic metric exporter. Restart-only.
telemetry_otel.protocolstring"grpc"OTLP transport: grpc or http_protobuf. Restart-only.
telemetry_otel.sample_ratefloat1.0Trace sampling ratio, 0.0–1.0 (parent-based). Restart-only.
telemetry_otel.service_namestring"kcode"service.name resource attribute on exported spans/metrics. Restart-only.
telemetry_otel.service_versionstring"0.4.0"service.version resource attribute on exported spans/metrics. Defaults to the telemetry-otel crate's version, which tracks kcode's workspace version. Restart-only.
tool_bash
tool_bash.default_timeout_secondsinteger120Wall-clock budget for a bash tool call that doesn't pass its own timeout. On expiry the command keeps running in the background and the result says so — nothing is killed.
tool_bash.default_truncate_bytesinteger20480Output size returned inline to the agent before truncation kicks in; the full output stays in the shell host's stash, readable via shell.output.
tool_bash.persist_shell_statebooleantrueCarry shell state across bash calls like one continuous terminal: each run's cd and exported-env changes (including sourced activations like a venv) fold into a per-session overlay the next bash/shell_spawn seeds from. The engine still builds a fresh shell per command — only cwd and exported vars carry. A single call opts out with fresh_state: true; set false to restore fully stateless calls (the agent then carries state by passing a result's cwd_change/env_changes back via the cwd/env args).
tool_framework
tool_framework.tool_search_max_resultsinteger10Documented only by its default. Run kcode config defaults for the full table.
tool_web
tool_web.default_body_cap_bytesinteger1048576web_fetch response-body cap; output beyond it is dropped with a truncation marker. Restart-only.
tool_web.default_timeout_secsinteger30Per-request timeout for web_fetch (overridable per call via its timeout_ms argument) and web_search. Restart-only.
tool_web.search_api_keystring?API key for tool_web.search_provider. Env form KCODE_TOOL_WEB__SEARCH_API_KEY. Every search backend maps to one normalized result shape (rank / title / URL / snippet), so switching providers changes nothing about what the model sees. Restart-only.
tool_web.search_base_urlstring?Override the search provider's endpoint base URL (self-hosted gateways, proxies). Unset = the provider's public endpoint. Restart-only.
tool_web.search_depthstring"advanced"Tavily's search_depth (2026-09-07): basic is one fast pass, advanced a deeper crawl with better-ranked, more relevant snippets — the default, at two Tavily API credits per call instead of one. Brave and Exa ignore it. Restart-only.
tool_web.search_include_answerbooleantrueAsk Tavily for a short written answer to the query alongside the hits (2026-09-07); the tool's text leads with it as Answer: and the structured result carries it as answer. Brave and Exa ignore it. Restart-only.
tool_web.search_max_resultsinteger8Cap on results per web_search call. The tool's count argument may ask for fewer, never more. Restart-only.
tool_web.search_providerstring?The web_search backend: brave, tavily, or exa (case-insensitive). The tool registers only when this AND tool_web.search_api_key are both set — no key ⇒ the tool is absent from the agent's toolset (P6). The provider must be named explicitly; a key with no provider (or an unknown name) skips registration with a startup warning rather than guessing which host to send the key to. Restart-only.
triggers_framework
triggers_framework.default_concurrency_policystring"Parallel"Documented only by its default. Run kcode config defaults for the full table.
triggers_framework.default_outputstring"Silent"Documented only by its default. Run kcode config defaults for the full table.
triggers_framework.hook_action_timeout_secsinteger60Documented only by its default. Run kcode config defaults for the full table.
triggers_framework.max_concurrent_hook_actionsinteger8Documented only by its default. Run kcode config defaults for the full table.
triggers_framework.rate_limits.per_minuteinteger60Documented only by its default. Run kcode config defaults for the full table.
triggers_framework.rate_limits.per_turninteger50Documented only by its default. Run kcode config defaults for the full table.
triggers_framework.rate_limits.start_new_cooldown_secsinteger30Documented only by its default. Run kcode config defaults for the full table.
updater
updater.check_on_startupbooleantrueBoot-time update check (default true): at client launch one detached signed-index fetch posts a single OS notification when a newer release exists, naming the shape-appropriate action (kcode upgrade for a cargo-bin install, the download page for an .app). Fail-silent — it never blocks the window. kcode upgrade always checks regardless.
updater.default_channelstring"stable"Release channel kcode upgrade checks when --channel isn't given. KCODE_RELEASE_CHANNEL overrides it (env wins over config on this path). Read per invocation of kcode upgrade; the binary self-update flow itself is deferred v1 scope.
worktree
worktree.auto_remove_cleanbooleantrueStop-time sweep for worktree sessions (R#75): when a session bound to a worktree stops and its checkout is clean (no uncommitted files, no commits past the base), the daemon removes the checkout, the kcode/<name> branch, and the session row. Dirty checkouts are always kept (fail-closed). Default true.
worktree.envPer-project env template table (W1): each KEY = "value" under [worktree.env] is rendered and injected into a worktree session's shell environment (the in-process shell and every command it runs — crash restarts included). Values may use {{slot}} (the worktree's stable isolation slot, lowest-free ≥ 1 per project, persisted for the worktree's life), {{slug}}, {{project}} (the base directory name), {{port}} (= worktree.port_base + slot), {{domain}} (<slug>.localhost — macOS resolves *.localhost to loopback system-wide, and browsers scope cookies by host, so distinct slugs isolate browser state with no proxy), and the {{N+slot}} arithmetic form for extra port families (VITE_PORT = "{{5173+slot}}"). A bad entry is skipped with a logged warning; the rest still apply. Project scope is the natural home — each project owns its port/database conventions. kcode also always injects KCODE_WORKTREE_SLUG/SLOT/DOMAIN/BASE identity vars (appended last, so templates can't shadow them). Empty by default.
worktree.port_baseinteger3000Base for the {{port}} worktree env template variable (W1): a worktree session's {{port}} renders as port_base + slot, so the worktree in slot 1 gets 3001. The base checkout is implicitly slot 0. Default 3000.