# settings.toml keys

> Every key kin reads from a settings.toml file. The file is the persistent baseline under your environment and flags — it feeds new sessions only (resuming reproduces a session's saved settings, never a live file). For…

Every key `kin` reads from a `settings.toml` file. The file is the persistent baseline under your environment and flags — it feeds **new sessions only** (resuming reproduces a session's saved settings, never a live file). For the full precedence model, see [Configuration](/docs/kin/getting-started/configuration/).

<!-- SOURCE: settings.toml.example, src/kin/harness/presets.py, src/kin/harness/model_routes.py, src/kin/harness/media_routing.py, src/kin/harness/settings/, src/kin/harness/backends/factory.py, src/kin/harness/backends/responses.py, src/kin/harness/sandbox.py, src/kin/harness/sandbox_access.py, src/kin/harness/git_trust.py, src/kin/harness/command_safety.py, src/kin/harness/env.py, src/kin/harness/compaction.py, src/kin/harness/persistence/, src/kin/harness/session/, src/kin/tui/modals/, src/kin/tui/theme.py, src/kin/harness/tools/_media.py, src/kin/harness/tools/inspect_media.py, src/kin/harness/tools/git_tool.py, src/kin/harness/tools/github_tool.py, src/kin/harness/tools/web_search.py, src/kin/harness/tools/outpost.py, src/kin/harness/tools/settings_tool.py, src/kin/harness/tools/diagnostics.py, src/kin/harness/term_notify.py, src/kin/tui/mux.py, src/kin/harness/shelf.py, src/kin/tui/shelf_watch.py -->

## File locations

`kin` reads layered TOML files, parsed with the standard-library `tomllib` (no extra dependency). The kin home (`~/.kin`, relocatable with `KIN_HOME`) holds three files with distinct owners:

| File | Path | Role |
|---|---|---|
| Global | `~/.kin/settings.toml` | **Your** machine-wide baseline — preferences and the provider catalogue. The file this page's keys normally live in. |
| Credentials | `~/.kin/credentials.toml` | **Kin-managed** secret store (`provider_keys`, `api_key`, `brave_api_key`, `outpost_token`, `rig_token`), created `0600`. Written by `kin connect`, `/models`, and the key prompts — you never need to edit it. |
| Grants | `~/.kin/grants.toml` | **Kin-managed** authority stores (`sandbox_grants`, `git_host_grants`). Audit it freely; manage it via `/grants` and the approval modals. |
| Project | `<workdir>/.kin/settings.toml` | Per-repo overlay, merged on top of the global layer. |

All layers merge into one view — every key below behaves identically wherever it is stored; the sibling files exist so `settings.toml` stays a short file that is entirely yours. A secret or grant key found in a legacy `settings.toml` still works and moves to its sibling file automatically on the next settings write. Run [`kin settings`](/docs/kin/reference/cli/#kin-settings) to see the effective configuration, which file supplies each key, and configuration lint.

The project file overlays the global layer key by key. Every file is optional — a missing or malformed file degrades to "no settings" with a warning (set `KIN_STRICT_SETTINGS=1` to raise instead). A complete, commented template ships as `settings.toml.example` in the repo; a fresh machine's first interactive run also writes a short comments-only starter.

> **Project files can't set global-only keys**
>
> A handful of keys are **stripped** (with a warning) from any project `.kin/settings.toml` — see [Global-only keys](#global-only-keys). Set those in the global file, the environment, or a flag.

## Keys

The **scope** column marks whether a key may live in a project file (`project-ok`) or only in the global file / environment / CLI (`global-only`).

### Backend and model

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `provider` | Provider family: `openai` or `anthropic`. | `openai` (or `anthropic` when a bare `base_url` is set and neither identity axis is selected) | project-ok | `KIN_PROVIDER` |
| `wire_api` | OpenAI wire shape: public values `chat` or `responses`. Either implies `provider = "openai"` and bypasses the bare-URL Anthropic heuristic. `provider = "openai"` without this key and the built-in OpenAI preset both remain on Chat. Anthropic Messages is an internal value, not valid settings input. | `chat` for OpenAI | project-ok | `KIN_WIRE_API` |
| `model` | Model id. Set to `default` (any stable alias you register with your server) to follow whatever model is currently loaded; the harness probes `/v1/models` on session start and piggybacks `response.model` off each turn so the displayed name always reflects the live server. The alias survives model swaps on the wire. | `gpt-4o-mini` (openai) / `claude-opus-4-8` (anthropic) | project-ok | `KIN_MODEL` |
| `base_url` | OpenAI-compat endpoint URL for the default backend (trailing `/v1` trimmed for the Anthropic wire). A selected provider preset owns its endpoint; only an explicit caller URL overrides that preset identity. | none | global-only | `KIN_BASE_URL` |
| `api_key` | Endpoint auth. | none (`not-needed` is sent) | global-only | `KIN_API_KEY` / `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` |
| `context_window` | Token budget for the context meter. Pins/overrides numeric server limits (`max_model_len` / `context_length` / `max_input_tokens` from `/v1/models`); capability metadata is still discovered. | `200000`, then refined from the server's `/v1/models` metadata when `base_url` is set | project-ok | `KIN_CONTEXT_WINDOW` |
| `max_tokens` | Per-response output cap. | `16000` (anthropic) / `8192` (openai) | project-ok | `KIN_MAX_TOKENS` |
| `provider_preset` | Provider preset id (`openai`, `anthropic`, `minimax`, `zai`, or a `[[providers]]` id). Bundles wire/endpoint/auth identity; curated rows also supply a default model, while direct OpenAI/Anthropic keep it explicit. | none | global-only | `KIN_PRESET` |
| `provider_keys` | Per-provider key table (`{<preset_id> = "<key>"}`) — the recommended shape when you swap between multiple providers. Read first by the factory's chain (most specific selector — beats the generic `api_key` + env vars) so a stray `ANTHROPIC_API_KEY` baked into an image can't silently shadow the key you stored for the active provider. `/providers` writes here when you paste a key. | none | global-only (also `_SECRET_SETTINGS_KEYS` — redacted wholesale to `[SET]` on read) | — |
| `provider_tiers` | Per-provider capability tier table. MiniMax uses `plus` / `max` / `ultra` and Z.ai uses `lite` / `pro` / `max` to size ordinary subagent capacity. `/providers` writes the selected tier. | `{}` (provider defaults: MiniMax `plus`, Z.ai `lite`) | global-only | — |
| `model_favorites` | List of `"provider_id/model"` strings the bare `/model` picker pins to the top (a `★` Favorites group). Toggle a row with `f` in the picker. | `[]` | global-only | — |
| `model_routes` | Named route tables (`[model_routes.<id>]`) that bind a trusted provider preset to an explicit model and optional capability/execution overrides. See [Model routes](#model-routes). | `{}` | global-only | — |
| `model_assignments` | Operator bindings for `mca`, `vision`, `rig`, `task`, `workflow`, `utility` (housekeeping side-calls such as session auto-titles), plus `[model_assignments.profiles]`. Values are route ids. | `{}` | global-only | — |

### Custom endpoints

A custom endpoint is a `[[providers]]` array-of-tables block — a self-contained
preset (id, wire, base URL, default model) you can refer to from
`provider_preset`. A custom row's id can shadow a built-in (so operators can
override a built-in's URL without code changes), but the built-in lookup
runs first so the curated catalogue wins for the common case.

```toml
[[providers]]
id = "openrouter"
provider = "openai"
wire_api = "chat" # or "responses"
base_url = "https://openrouter.ai/api/v1"
default_model = "anthropic/claude-3.7-sonnet"
models = ["anthropic/claude-3.7-sonnet"]
```

OpenAI custom rows may set `wire_api = "chat"` (the default) or opt into
`wire_api = "responses"`. Anthropic rows imply Messages and reject an OpenAI
wire value. A row may carry `reasoning_effort = "<level>"` as the endpoint's
own default effort (opaque string, serve-owned vocabulary); on a Responses row
this also opts the stream into reasoning-summary deltas — required for
thinking-by-default serves like DS4 (see
[Models & providers](/docs/kin/guide/models-and-providers/#effort-picker)).
Custom rows may also set `reasoning_profile = "poolside"`. That profile is
valid only on an `anthropic` row and selects Laguna's per-request
`chat_template_kwargs.enable_thinking` control; an unknown profile or an
OpenAI-wire pairing is rejected instead of guessed. See
[Laguna S 2.1 on vLLM](/docs/kin/guide/models-and-providers/#laguna-s-21-on-vllm).
For the qualified text-only Responses shape backed by Paddock-managed
llama.cpp, see [Paddock and llama.cpp
Responses](/docs/kin/guide/models-and-providers/#paddock-and-llamacpp-responses).

Add a custom endpoint via `/providers` → "Custom endpoint…" or by hand.
See [Provider presets](/docs/kin/guide/providers/) for the full shape and
which keys are recognized.

> **`model` is an alias for `default_model` on custom rows**
>
> The one-key `model = "default"` shape (no `default_model`, no `models`) is
> accepted as a convenience for one-line single-model / stable-alias rows —
> the parser aliases `model` → `default_model` and seeds `models = [model]`
> so the `/model` picker still surfaces a pickable row. Use the explicit
> two-key form (`default_model` + `models = [...]`) when the picker should
> show every model your server actually serves.

### Per-provider keys (`[provider_keys]`)

When you swap between multiple providers, the recommended storage shape is the `[provider_keys]` table — one key per preset id. The factory's resolution chain (`_preset_key` in `src/kin/harness/backends/factory.py`) reads `provider_keys[<active_preset_id>]` **before** falling back to environment variables / `api_key`, so a key stored here is the most specific selector. Direct OpenAI consults `OPENAI_API_KEY` then `KIN_API_KEY`; direct Anthropic consults `ANTHROPIC_API_KEY` then `KIN_API_KEY`, and neither borrows the other vendor's conventional key. Compatibility/custom presets retain the broad legacy environment chain because their wire does not identify the credential issuer:

```toml
# ~/.kin/settings.toml (human-owned, non-secret selection)
provider_preset = "zai"          # which preset is "active" for next session
model = "glm-5.2"

[provider_tiers]
minimax = "plus"
zai = "lite"
```

Kin writes the corresponding secret table to its 0600 credential store:

```toml
# ~/.kin/credentials.toml (Kin-managed; shown only to explain storage)
[provider_keys]
zai = "sk-..."
minimax = "sk-..."
```

`/providers` writes to this table when you paste a key, so the next session and future routed calls find the key — no `/reload`, no manual settings.toml editing. For direct OpenAI/Anthropic rows, leaving the exact-model field blank refreshes the catalog for future route dispatch without switching the live MCA; running children keep their prior snapshot. The Outpost dashboard's per-provider paste-key field writes to the same table (see [Outpost](/docs/kin/guide/outpost/)). Top-level `api_key` is the generic fallback (one key, one endpoint); `[provider_keys]` is the per-provider shape.

### Model routes

Model routes separate *what a call is for* from the endpoint wire. Every route
references an existing built-in or custom provider preset; routes never own a
URL or credential. Direct `openai` and `anthropic` preset ids are built in, so
they work beside MiniMax, Z.ai, and custom `[[providers]]` rows. Their
empty preset URL means the vendor SDK endpoint and does not inherit an ambient
`KIN_BASE_URL` used by the default backend.

```toml
# ~/.kin/settings.toml
[provider_keys]
openai = "sk-..."
anthropic = "sk-ant-..."

[model_routes.primary]
provider_preset = "anthropic"
model = "your-text-model-id"
description = "Main collaboration model"
vision = false
effort = "high"
max_tokens = 16000
selectable_for = ["task", "workflow"]

[model_routes.visual]
provider_preset = "openai"
model = "your-vision-model-id"
description = "Isolated image inspection"
vision = true
selectable_for = ["task"]

[model_assignments]
mca = "primary"
vision = "visual"
rig = "visual"
task = "primary"
workflow = "primary"
utility = "visual"   # housekeeping side-calls (session auto-titles)

[model_assignments.profiles]
researcher = "primary"
```

Each `[model_routes.<id>]` accepts only these fields:

| Field | Required | Meaning |
|---|---:|---|
| `provider_preset` | yes | Built-in or custom preset id. This is the endpoint and credential trust boundary. |
| `model` | yes | Exact model id sent on that preset's wire. |
| `description` | no | Operator description shown to humans and, only when selectable, to the task/workflow model. |
| `vision` | no | Authoritative `true` / `false` semantic-vision override. Omitted starts as `unknown`; Kin caches a supported or capability-rejected media attempt until reload. |
| `reasoning_profile` | no | Closed route request contract. `qwen-llamacpp` enables budget-labelled Qwen choices without applying nonstandard fields to other routes/providers. |
| `effort` | no | Per-route effort value, validated against that preset/model's picker vocabulary. |
| `max_tokens` | no | Positive per-response output cap for this route. |
| `selectable_for` | no | Explicit model-choice allowlist containing only `"task"` and/or `"workflow"`. Operator assignments do not need this opt-in. |

Resolution is deterministic: profile assignment, then consumer assignment,
then an allowed requested `route`, then a requested legacy `model`, then the
current MCA backend. `route` and `model` are mutually exclusive, and an invalid
model clone is an error rather than a silent fallback. Forks retain their
parent's exact backend. See [Models & providers](/docs/kin/guide/models-and-providers/#model-routes)
for media isolation, persistence, usage, and `/reload` behavior.

For `reasoning_profile = "qwen-llamacpp"`, valid effort values are `default`,
`off`, `thinking-128`, `thinking-512`, `thinking-1024`, and `thinking-2048`.
The route's `max_tokens` is a total output allowance and must exceed an enabled
budget. A Utility assignment also requires 512 final-answer tokens beyond the
budget. See [Qwen bounded thinking](/docs/kin/guide/models-and-providers/#qwen-llamacpp-bounded-thinking).

`utility` is assignment-only: it binds kin-internal housekeeping side-calls
(session auto-titles today) to a lightweight route so tiny requests stay off
the main endpoint's prompt cache. It is never model-selectable, its calls are
best-effort (a busy route skips the call rather than queueing), and a failed
utility call keeps the first-message fallback title instead of retrying on the
main backend. Unassigned, housekeeping stays on the main backend as before.

### Sampling and compatible reasoning controls

`temperature`, `top_p`, and `top_k` apply only on the OpenAI-compat wire. The
Anthropic wire ignores per-request sampling, so set it on the server instead.
`enable_thinking` has the explicit Poolside exception described below.

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `temperature` | Sampling temperature (`0` = greedy). | provider default | project-ok | `KIN_TEMPERATURE` |
| `top_p` | Nucleus sampling. | provider default | project-ok | `KIN_TOP_P` |
| `top_k` | Top-k sampling (via `extra_body`). | provider default | project-ok | `KIN_TOP_K` |
| `enable_thinking` | Chat-template thinking soft switch for Qwen-style OpenAI-compatible serves and an Anthropic custom provider with `reasoning_profile="poolside"` (`false` hard-disables thinking). | unset (model/server decides) | project-ok | `KIN_ENABLE_THINKING` |
| `reasoning_effort` | OpenAI-proper reasoning-effort hint (real OpenAI / OpenRouter / Z.ai OpenAI-compat on GLM-5.2+; any custom Responses-wire endpoint). Top-level wire field on `chat.completions.create` — NOT inside `extra_body`; on the Responses wire it rides `reasoning: {"effort": ..., "summary": "auto"}` (the summary opt-in is what makes a thinking-by-default serve stream reasoning deltas). Per-serve vocab (3-value OpenAI vs 7-value Z.ai vs the Responses pass-through) is dispatched by the picker (see [Models & providers](/docs/kin/guide/models-and-providers/)); the resolver itself is opaque. Not for Qwen vLLM Chat serves (use `enable_thinking` above). A `[[providers]]` row may carry its own `reasoning_effort` default, which wins over this top-level file value when that row is selected; the higher-precedence `KIN_REASONING_EFFORT` environment override still wins over both. Cross-wire knob for the picker is `effort`, so this key is **not** in the [model-writable](#editing-settings-from-inside-kin) set — reach it via `effort=...` which goes through the human-approval gate. | unset | project-ok | `KIN_REASONING_EFFORT` |
| `strict_tools` | Strict tool schemas on the streaming registry path (OpenAI-compat wire only). Each tool spec is normalized at request-build time (`additionalProperties: false`, all-required, optionals as `["type","null"]` unions — the registry's canonical schemas are never mutated) and stamped `strict: true`, so endpoints that honor it (OpenAI-proper; vLLM ≥ 0.24 `auto` tool choice) grammar-enforce arguments. A no-op on vLLM 0.23 `auto` calls; forced tool calls (the workflow `schema=` path) are strict-normalized unconditionally regardless of this key. Human-only (not model-writable). See [Strict decoding](/docs/kin/guide/models-and-providers/#strict-decoding-schema-valid-tool-calls). | `false` | project-ok | `KIN_STRICT_TOOLS` |

### Fleet safety

Governance knobs for a shared, self-hosted endpoint — see [Fleet safety](/docs/kin/guide/headless/#fleet-safety-the-per-run-token-budget) in the headless guide.

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `token_budget` | Per-run cumulative token budget: once a run's summed prompt+completion tokens (across every model round, subagent rounds included) cross this, the agent loop stops cleanly at the next model-round boundary — done reason `token_budget`, headless exit `1`. A "run" is one root turn plus everything it fans out; the next turn starts fresh. Project-ok: the default is unlimited, so a project file can only *impose* a tighter cap (fail-safe). Human-only — deliberately **not** model-writable: the budget exists to stop a runaway model, so the model can never raise its own leash. `0` = off. | `0` (off) | project-ok | `KIN_TOKEN_BUDGET` |
| `max_turns` | Per-turn model-round cap: the maximum model↔tool round-trips one user turn may run before it ends with done reason `turn_cap` (headless exit `1`). Independent of `token_budget` — the two limits are checked at the same round boundary. `0` = **unlimited** (the default) — the doom-loop guard (3× identical call+result) is the real no-progress backstop, so an unbounded cap is safe for large-model, hours-long runs. Project-ok: the default is unlimited, so a project file can only *impose* a tighter cap (fail-safe). Human-only — not model-writable (an operator leash, like `token_budget`). | `0` (unlimited) | project-ok | `KIN_MAX_TURNS` |
| `request_priority` | Legacy vLLM priority-scheduling passthrough (OpenAI-compat wire **only**). Rides `extra_body["priority"]` on every request; lower = scheduled earlier, with KV-swap preemption. **Serve-gated:** only meaningful when the server runs `--scheduling-policy priority` — vLLM errors on any non-zero priority otherwise, and current DS4 does not implement the field, which is why the default is *absent from the request*, not `0`. Human-only (not model-writable). See [Models & providers](/docs/kin/guide/models-and-providers/#priority-scheduling-passthrough-vllm). | unset (absent) | project-ok | `KIN_REQUEST_PRIORITY` |

### Media and caching

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `image_detail` | OpenAI vision tier for tool-returned images (`low` / `high` / `auto`). | `low` | project-ok | `KIN_IMAGE_DETAIL` |
| `effort` | Anthropic adaptive-thinking effort. | `high` | project-ok | `KIN_EFFORT` |
| `thinking_type` | Anthropic-wire `thinking.type` knob — z.ai / MiniMax / Fable honor this as their canonical reasoning control (NOT `output_config.effort`). Common values: `adaptive` (default), `enabled` (force on), `disabled` (off — avoid on serves that 400 on it). | `adaptive` | project-ok | `KIN_THINKING_TYPE` |
| `cache_ttl` | Anthropic system-prompt prefix-cache TTL (`1h` opts into the 1-hour cache). | 5 minutes | project-ok | `KIN_CACHE_TTL` |

### Permissions and OS sandbox

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `mode` | Permission mode at launch (`auto` / `strict`; the legacy `default` / `accept-edits` / `plan` still resolve via the alias map). | `auto` | global-only | `KIN_MODE` |
| `sandbox` | OS sandbox backend for auto-mode shell (`auto` / `seatbelt` / `bwrap` / `off` / `container`). `container` trusts an external boundary — auto-mode shell runs unconfined instead of asking (see the [auto-mode guide](/docs/kin/guide/auto-mode-and-sandbox/#trust-the-container)). | `auto` | global-only | `KIN_SANDBOX` |
| `sandbox_network` | Allow outbound networking inside Seatbelt/bubblewrap while retaining workspace write confinement and secret masking. No effect for `off`/`container`. | `true` | global-only | `KIN_SANDBOX_NETWORK` |
| `sandbox_strict` | Opt `strict` mode's shell into the OS sandbox (so an un-vetted command runs contained instead of prompting). | `false` | global-only | `KIN_SANDBOX_STRICT` |
| `sandbox_default` | **Legacy** (pre-two-mode) name, still honored as a fallback for `sandbox_strict`. Prefer `sandbox_strict`. | `false` | global-only | `KIN_SANDBOX_DEFAULT` |
| `sandbox_grants` | Human-approved `[[sandbox_grants]]` rows written by the typed sandbox-access modal. Each row binds an `sbg_…` id and exact canonical `workspace` to `write_paths`, `network`, and `ssh_agent` capabilities. Invalid, sensitive, or mismatched rows are ignored fail-closed. Prefer `/grants` over hand-editing. | `[]` | global-only | — |
| `git_host_grants` | Human-approved, secret-free `[[git_host_grants]]` rows written by the Git credential modal's **Trust host** action. Each `ghg_…` row binds SSH/HTTPS + resolved user/host/port to a route fingerprint and display label; it never stores a token, key, socket path, remote URL, or repository path. A changed SSH/helper route asks again. Invalid rows are inert. Manage with `/grants`. | `[]` | global-only | — |
| `sandbox_always_allow` | **Inactive migration data.** Older Kin versions stored `fnmatch` command patterns here. Current Kin lists them in `/grants` as requiring reapproval, but they never skip the OS sandbox. Remove them with `/grants` or by hand after migration. | `[]` | global-only | — |
| `shell_allowlist` | When true, provably read-only shell commands auto-allow; false sends every command to the approval modal. | `true` | global-only | `KIN_SHELL_ALLOWLIST` |

### Planning

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `persist_plans` | **Retired, no-op.** Plans are now saved to `<workdir>/.kin/plans/<date>-NN-<slug>.md` unconditionally on every `write_plan` call — this key no longer gates anything; kept only so an existing `settings.toml` doesn't error. | `true` | project-ok | `KIN_PERSIST_PLANS` |

### Sessions and persistence

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `session_dir` | Where session journals are written. | `~/.kin/projects` | global-only | `KIN_SESSION_DIR` |
| `no_save` | Run ephemerally: write no session journal or durable prompt history (not resumable; composer recall remains memory-only for this process). | `false` | global-only | `KIN_NO_SAVE` |
| `prompt_history_cap` | Number of interactive composer submissions retained per project for ↑/↓ and ++ctrl+r++ across restarts. Includes model prompts, slash commands, and literal `!` command text, but never shell output; headless `kin -p` prompts are excluded. `0` disables durable loading and writes without deleting the existing store, so re-enabling restores retained entries. Negative or malformed values fall back to `100`. `--no-save`, `KIN_NO_SAVE`, and `no_save = true` always make recall memory-only. Human-only (prompt retention is a privacy choice, not a model knob). | `100` | project-ok | `KIN_PROMPT_HISTORY_CAP` |
| `detach` | Run interactive launches as [background sessions](/docs/kin/guide/sessions/#background-sessions-detach-reattach) — `/detach` leaves the session running, `kin` in the same folder picks it back up. **On by default when tmux ≥ 3.2 is on `PATH`** (DR 0097); set `false` to opt out. A missing/too-old tmux degrades silently. `kin --no-detach` / `--detach` override per launch. Project-ok (it only re-hosts the same process locally — no egress, secret, or containment change). | on (tmux available) | project-ok | `KIN_DETACH` |

### Notifications

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `notify` | The [terminal bell / OSC 9 walk-away notification](/docs/kin/guide/sessions/#terminal-notifications) — a bare BEL always, plus a native desktop notification on a verified OSC-9-capable terminal (Ghostty / iTerm2 / WezTerm / kitty). Fires on a turn that ran 15s+ and on every needs-input prompt (TUI), or on process completion when stderr is a real terminal (`kin -p`). Default **on**; opt-*out* kill switch. Project-ok (it only toggles whether a static-string escape sequence is written; carries no model/server text). | `true` | project-ok | `KIN_NOTIFY` |
| `update_check` | The session-start [update-availability check](/docs/kin/getting-started/install/#updating): one best-effort GET of `https://get.kinra.ai/version.json` (24h-cached) that renders the one-line "Update available … run: kin update" note. Privacy: the request carries only your kin version in its User-Agent. Default **on**; opt-*out*. Project-ok (the endpoint is a constant — a project file can only turn the check *off*). Explicit `kin update` / `kin doctor` runs don't consult it. | `true` | project-ok | `KIN_UPDATE_CHECK` |

### Appearance

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `theme` | TUI theme name. `kin` inherits the terminal background; `kin-graphite` is the opaque Graphite+ fallback. Bare `/theme` cycles that pair, while an explicit registered Textual theme name is also accepted. The command persists the choice here. Project-ok (pure display — no egress, secret, or containment impact). | `kin` | project-ok | `KIN_THEME` |
| `spinner` | The busy-spinner style name (`rain` · `braille` · `arc` · `toggle` · `line` — the vibe table lives in [Identity](/docs/kin/concepts/identity/#spinner-vibes)). Unknown names fall back to the default; `KIN_ASCII` forces `line`. Pick interactively with `/spinner` (which persists here). Project-ok (pure display — no egress, secret, or containment impact). | `rain` | project-ok | `KIN_SPINNER` |

### Context and compaction

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `env_context` | Include the Environment + git-state blocks in the system prompt. | `true` | project-ok | `KIN_ENV_CONTEXT` |
| `compact_threshold` | Auto-compact history once prompt usage crosses this fraction of the window (`0.1`–`0.98`). The effective trigger is capped below the hard input limit after reserving `max_tokens` output plus framing margin. | unset (off) | project-ok | `KIN_COMPACT_THRESHOLD` |
| `compact_keep_turns` | Minimum recent turns to preserve when compacting. | `4` | project-ok | `KIN_COMPACT_KEEP_TURNS` |

### Skills and web

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `strict_skills` | Raise on malformed skill / agent frontmatter instead of graceful-degrading. | `false` | project-ok | `KIN_STRICT_SKILLS` |
| `brave_api_key` | Brave Search key; enables the `web_search` / `web_context` tools. | none | global-only | `BRAVE_API_KEY` / `KIN_BRAVE_API_KEY` |
| `search_enabled` | Register the [`search_workspace`](/docs/kin/guide/workspace-search/) tool — ranked, chunk-aware **lexical** full-text search over workspace content (stdlib SQLite FTS5; the complement to `grep`, not semantic/vector search). Default **on**; set false to remove it. The index DB lives outside the workspace under `~/.kin/index/`. Project-ok (a local index only — no network/egress/secret). | `true` | project-ok | `KIN_SEARCH_ENABLED` |
| `shelf_enabled` | Enable edge-triggered [shelf](/docs/kin/guide/shelf/) board hints and the TUI's fixed-refspec returned-branch watcher. | `true` | project-ok | `KIN_SHELF_ENABLED` |
| `shelf_watch_interval` | Seconds between TUI probes of `refs/heads/shelf/*`; read once at mount and clamped to a 30-second minimum. | `120` | project-ok | `KIN_SHELF_WATCH_INTERVAL` |
| `run_code_enabled` | Register the [`run_code` / `run_code_reset`](/docs/kin/guide/tools/#code-interpreter-python) interpreter tools — the stateful per-session Python kernel. Default **on**; the tools ride the full `shell` permission gate (sandboxed in `auto`, asks in `strict`), so this is an opt-*out* kill switch. Project-ok (it only unregisters tools; it grants nothing the gate doesn't). | `true` | project-ok | `KIN_RUN_CODE` |
| `diagnostics_after_edit` | Append a lint pass's output to [`write_file`/`edit_file`](/docs/kin/guide/tools/#filesystem) results on a nonzero exit (`ruff check`, v1 — `shutil.which`-gated, never blocks or fails the write). Default **on**; opt-*out* kill switch. | `true` | project-ok | `KIN_DIAGNOSTICS` |
| `diagnostics_ty` | Also run an opt-in `ty` typecheck pass inside diagnostics-after-edit. Default off — `ty` is slow and typed-project-specific. | `false` | project-ok | `KIN_DIAGNOSTICS_TY` |
| `snapshots` | The [file snapshot-before-write safety net](/docs/kin/guide/sessions/#file-snapshots) — a `git commit-tree` checkpoint under `refs/kin-snapshots/` taken before the first edit of every turn, restorable via `/rewind files`/`both`. Default **on**; opt-*out* kill switch. A no-op outside a git workspace. Project-ok (only toggles whether the checkpoint runs; never touches the real index/HEAD/worktree). | `true` | project-ok | `KIN_SNAPSHOTS` |
| `paste_file_mentions` | Convert [Finder drag-drops / terminal pastes of absolute file paths into `@`-mentions](/docs/kin/guide/at-mentions/#pasting-files-drag-drop-terminal-paste) in the Composer (default **on**). Off → pastes insert verbatim, identical to pre-Rock-3 behavior. Project-ok (it only re-routes a paste from raw-text-insert to mention-insert — no egress / secret / containment surface beyond what a manual paste would carry); human-only (a UX toggle, NOT model-writable). | `true` | project-ok | `KIN_PASTE_MENTIONS` |
| `rig_enabled` | Register the [Rig](/docs/kin/guide/rig/) tool family when URL and token are also configured. Project-ok because it can only remove the tools; the endpoint and credential remain global-only. | `true` | project-ok | `KIN_RIG` |
| `rig_url` | Rig's semantic API origin. Global-only so a cloned project cannot redirect a bearer-authenticated tool to a hostile endpoint. | none | global-only | `KIN_RIG_URL` |
| `rig_token` | Rig bearer credential, masked on every settings surface. Containers normally use `KIN_RIG_TOKEN_FILE`. | none | global-only | `KIN_RIG_TOKEN` |
| `browser_enabled` | Legacy no-op retained for migration-safe settings validation. | `true` | project-ok | `KIN_BROWSER` |
| `browser_allow_private` | Legacy no-op retained for migration-safe settings validation. Use the explicit `task rig:local` operator path for local/private targets. | `false` | global-only | `KIN_BROWSER_PRIVATE` |
| `memory_enabled` | Register the [`memory` tool](/docs/kin/guide/memory/) + the recall injections (the stable index block and per-turn top-5 recall). Default **on**; this is the opt-*out* kill switch. Project-ok (it only unregisters/disables; containment lives in the tool's path guard). | `true` | project-ok | `KIN_MEMORY` |
| `memory_reflect` | Run the [end-of-run reflection pass](/docs/kin/guide/memory/#memory-capture-at-session-end) — a bounded extra turn over the live `memory` tool, fired automatically on a terminal clean `kin -p` stop with no pending human gate and at TUI quit. Project-ok (writes ride the same guarded store + containment as the tool). | `true` | project-ok | `KIN_MEMORY_REFLECT` |
| `memory_dir` | The [memory root](/docs/kin/guide/memory/) — where the `memory` tool reads and writes, and where the memory recall index lives. Global-only: memory mutations auto-apply in `auto` *because* they're contained to this root, so a cloned repo's project file must never be able to repoint that contained write surface at an arbitrary directory. | `~/.kin/memory` | global-only | `KIN_MEMORY_DIR` |
| `ssh_hosts` | **Deprecated no-op** retained so existing settings files keep loading. Ordinary SSH uses `shell`; `KIN_SSH_HOSTS` is no longer read. | `[]` | global-only | — |
| `outpost_url` | The origin of your [Outpost](/docs/kin/guide/outpost/) (e.g. `https://outpost.kinra.ai`) — the [`outpost`/`outpost-send` tools'](/docs/kin/guide/tools/#outpost) target. Both tools register only when this AND `outpost_token` are set. Global-only for the same reason `ssh_hosts` is: a cloned repo's project file must never be able to repoint the tool at a hostile endpoint. | none | global-only | `KIN_OUTPOST_URL` |
| `outpost_token` | The bearer token (`svc_<id>.<secret>`) authenticating the `outpost`/`outpost-send` tools against your Outpost's v1 API. A credential — masked to `[SET]`/`[NOT SET]` wherever settings render, same as `api_key`. | none | global-only | `KIN_OUTPOST_TOKEN` |

### Observability

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `spans_enabled` | Write OTel-GenAI-shaped [JSONL spans](https://github.com/kinra-ai/kin/blob/dev/docs/guide/evals.md#the-span-sink) — one `invoke_agent` per turn, a `chat` per model round, an `execute_tool` per tool call — to `<session_id>.spans.jsonl` next to the journal. Metadata only (timing / names / token counts; never prompt or tool text). Project-ok (a local file, no network/egress/secret) and human-only (not model-writable). | `false` | project-ok | `KIN_SPANS` |
| `pricing_enabled` | Display a running USD cost total alongside the `/tokens` counts (verbose breakdown) and as a recessive cell on the StatusBar (`$0.04`). Sourced from a stdlib-only pricing table keyed by `(provider, model id)` — see [Cost display](/docs/kin/guide/models-and-providers/#cost-display). Default **off** (opt in) — the primary endpoint is local vLLM (no cost) and the subscription providers (MiniMax/z.ai) make per-token cost noise; turn it on with `KIN_PRICING=1` / `pricing_enabled = true` / the [`/cost on`](/docs/kin/guide/slash-commands/) slash command (which also persists the choice). An unpriced serve (local vLLM, an id not in the table) shows nothing rather than a misleading `$0.00`. Project-ok (a local computation + display only; no network, no secret, no containment impact) and human-only (observability, never a sampling knob). | `false` | project-ok | `KIN_PRICING` |

### Background subagents

| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
| `agent_stall_timeout` | Seconds without model/tool activity before an active, non-paused task agent becomes advisory `stale`. Staleness regains the MCA's attention but never cancels or restarts the child. Human-only and clamped to 30–3600 seconds. | `120` | project-ok | `KIN_AGENT_STALL_TIMEOUT` |

## Editing settings from inside kin

Two ways to change this file without leaving the TUI, both gated on a human diff-approval:

- **`/edit-settings`** (or `/config` → settings) — *you* ask a scoped, read-only editor subagent to draft the whole file; you approve the diff. It may set any non-secret key.
- **`/settings`** — turns on a per-turn settings guide so the *model* can help tune settings mid-conversation via the `read_settings` / `propose_settings` [tools](/docs/kin/guide/tools/#settings). The model is held to a tighter boundary than `/edit-settings`:

    | Tier | Keys the model may propose |
    |---|---|
    | **Model-writable** | `temperature`, `top_p`, `top_k`, `max_tokens`, `context_window`, `enable_thinking`, `effort`, `thinking_type`, `cache_ttl`, `model` |
    | **Human-only** (refused, hand-edit this file) | `mode`, `sandbox`, `sandbox_strict`, `sandbox_grants`, `sandbox_always_allow`, `git_host_grants`, `shell_allowlist` (containment/trust); `api_key`, `base_url`, `brave_api_key` (secrets/endpoints); `provider`, `wire_api`, `provider_preset`, `provider_tiers`, `providers`, `model_favorites`, `model_routes`, `model_assignments` (presets/routes); `reasoning_effort` (cross-wire conflict — use `effort` which the picker translates per-serve) |

    The model-writable list is an **explicit positive allowlist** — a key is refused until it's deliberately added, so a future setting can't silently become model-writable. `read_settings` masks every credential to `[SET]`/`[NOT SET]`. A proposed change goes through the same diff modal and applies to **new sessions only** (the live session's mode/model are human-only — use `/mode` and `/model`). See [Model-writable settings](/docs/kin/guide/modes-and-permissions/#model-writable-settings).

## Global-only keys

These keys are honored from the global `~/.kin/settings.toml`, the environment, or a CLI flag — but are stripped (with a one-line warning) from any project `.kin/settings.toml`:

```
base_url, api_key, mode, sandbox, sandbox_strict, sandbox_default,
sandbox_grants, sandbox_always_allow, git_host_grants, shell_allowlist,
session_dir, no_save, brave_api_key,
provider_preset, providers, provider_keys, provider_tiers, model_favorites,
model_routes, model_assignments, sandbox_network, ssh_hosts,
browser_allow_private, rig_url, rig_token, outpost_url, outpost_token
```

The reason is the threat model of a cloned repository. A project file you
didn't write could otherwise repoint your wire and leak your key (`base_url` +
`api_key`), launch you straight into autonomous mode (`mode`), weaken or
disable OS containment (`sandbox`, `sandbox_strict`, `sandbox_network`, the
legacy `sandbox_default`, `shell_allowlist`), grant itself typed sandbox
authority (`sandbox_grants`) or trusted Git credential use
(`git_host_grants`), redirect the bearer-authenticated [Rig](/docs/kin/guide/rig/)
or [`outpost`/`outpost-send`](/docs/kin/guide/tools/#outpost) tools to a hostile
endpoint (`rig_url` / `outpost_url`), redirect where sessions land
(`session_dir`, `no_save`, `brave_api_key`), or route your global `api_key`
through a third-party endpoint (`provider_preset` + `providers` +
`provider_keys`), or assign prompts and images to one of those endpoints
(`model_routes` + `model_assignments`). Keeping them global-only means a checkout can't silently
retarget your endpoint, authorize its own remote route, route your key through
an attacker-controlled host, or weaken your safety posture.
`sandbox_always_allow` and `ssh_hosts` remain in the set only as inert
migration-compatible data. The exact denylist and the layering rules are
documented in the repo's
[`REFERENCE.md` § Settings & config layering](https://github.com/kinra-ai/kin/blob/dev/REFERENCE.md).

Secrets (`api_key`, `provider_keys`, `brave_api_key`, `outpost_token`, and
`rig_token`) belong to Kin's global credential store at
`~/.kin/credentials.toml`, which is created mode 0600 and never committed.
Use Kin's key prompts instead of editing it. A legacy secret in global
`settings.toml` remains readable and migrates on the next settings write.
