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 the full precedence model, see Configuration.
File locations¶
kin reads two layered TOML files, parsed with the standard-library tomllib (no extra dependency):
| File | Path | Role |
|---|---|---|
| Global | ~/.kin/settings.toml |
Your machine-wide baseline. The base directory is relocatable with the KIN_HOME environment variable. |
| Project | <workdir>/.kin/settings.toml |
Per-repo overlay, merged on top of the global file. |
The project file overlays the global one key by key. Both are 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.
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. 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 |
Wire: openai or anthropic. |
openai (or anthropic when base_url is set and provider is unset) |
project-ok | KIN_PROVIDER |
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 (trailing /v1 trimmed for the Anthropic wire). |
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. | 200000, then refined from the server's max_model_len 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 |
First-party provider preset id (minimax, zai, or a [[providers]] id). Bundles wire + URL + default model + auth style. |
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) |
— |
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 | — |
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.
[[providers]]
id = "openrouter"
provider = "openai"
base_url = "https://openrouter.ai/api/v1"
default_model = "anthropic/claude-3.7-sonnet"
models = ["anthropic/claude-3.7-sonnet"]
Add a custom endpoint via /providers → "Custom endpoint…" or by hand.
See Provider presets 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 api_key / env vars, so a key stored here is the most specific selector:
# ~/.kin/settings.toml
provider_preset = "zai" # which preset is "active" for next session
model = "glm-5.2"
[provider_keys]
zai = "sk-..."
minimax = "sk-..."
/providers writes to this table when you paste a key, so the next session finds the key — no /reload, no manual settings.toml editing. The Outpost dashboard's per-provider paste-key field writes to the same table (see Outpost). Top-level api_key is the generic fallback (one key, one endpoint); [provider_keys] is the per-provider shape.
Sampling (OpenAI-compat wire only)¶
These apply only on the OpenAI-compat wire. The Anthropic wire (including a vLLM serve reached via the heuristic) ignores per-request sampling — set it on the server instead.
| 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 |
Qwen3-style thinking soft switch (false hard-disables thinking). |
unset (model decides) | project-ok | KIN_ENABLE_THINKING |
reasoning_effort |
OpenAI-proper reasoning-effort hint (real OpenAI / OpenRouter / Z.ai OpenAI-compat on GLM-5.2+). Top-level wire field on chat.completions.create — NOT inside extra_body. Per-serve vocab (3-value OpenAI vs 7-value Z.ai) is dispatched by the picker (see Models & providers); the resolver itself is opaque. Not for Qwen vLLM (use enable_thinking above). Cross-wire knob for the picker is effort, so this key is not in the model-writable 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. |
false |
project-ok | KIN_STRICT_TOOLS |
Fleet safety¶
Governance knobs for a shared, self-hosted endpoint — see Fleet safety 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 |
vLLM priority-scheduling passthrough (OpenAI-compat wire only — the Anthropic /v1/messages protocol has no priority field, so the Anthropic wire silently ignores this). Rides extra_body["priority"] on every request; lower = scheduled earlier, with genuine KV-swap preemption. Serve-gated: only meaningful when the server runs --scheduling-policy priority — vLLM errors on any non-zero priority otherwise, which is why the default is absent from the request, not 0. Human-only (not model-writable). See Models & providers. |
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). |
auto |
global-only | KIN_SANDBOX |
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_always_allow |
List of fnmatch command patterns (["uv *", "task *"]) that skip the OS-sandbox wrap in auto mode — the persistent tier of the ask_sandbox_override flow's "Always allow" choice. Empty = every auto-mode shell is sandboxed. Writable only through the override tool's human-approved always-allow path (NOT model-writable); hand-edit to manage it directly. |
[] |
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 journal; not resumable). | false |
global-only | KIN_NO_SAVE |
Notifications¶
| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
notify |
The terminal bell / OSC 9 walk-away notification — 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 |
Appearance¶
| Key | Meaning | Default | Scope | Env |
|---|---|---|---|---|
spinner |
The busy-spinner style name (bloom · braille · arc · toggle · line — the vibe table lives in Identity). 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). |
bloom |
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 usage crosses this fraction of the window (0.1–0.98). |
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 tool — a ranked, chunk-aware full-text search over workspace content (stdlib SQLite FTS5; the complement to grep). The index DB lives outside the workspace under ~/.kin/index/. Project-ok (a local index only — no network/egress/secret). |
false |
project-ok | KIN_SEARCH_ENABLED |
run_code_enabled |
Register the run_code / run_code_reset 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 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 — 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 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 |
browser_enabled |
Register the browser tool — the text-only headless browser (Playwright + Chromium, one per session). Default on, but the tool only registers when the optional browser extra is installed (uv sync --extra browser + uv run playwright install chromium); this key is the opt-out kill switch. Project-ok (it only unregisters the tool; it grants nothing the browser permission gate + the tool's scheme/SSRF boundary don't). |
true |
project-ok | KIN_BROWSER |
browser_allow_private |
Let the browser tool navigate to private/loopback/link-local addresses (local dev servers) — its SSRF guard refuses them by default. Global-only for the same reason ssh_hosts is: it grants network reach, and a cloned repo's project file must not be able to point your browser at the cloud metadata endpoint or an internal admin panel. |
false |
global-only | KIN_BROWSER_PRIVATE |
memory_enabled |
Register the memory tool + 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 — a bounded extra turn over the live memory tool, fired automatically on a clean kin -p stop 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 — 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 |
Allowlist of hosts (or ~/.ssh/config aliases) the opt-in ssh tool may reach. Both the opt-in switch (empty = the tool isn't registered) and the per-host gate. A TOML list; KIN_SSH_HOSTS is comma-separated. |
[] |
global-only | KIN_SSH_HOSTS |
outpost_url |
The origin of your Outpost (e.g. https://outpost.kinra.ai) — the outpost/outpost-send tools' 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 — 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. 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 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 |
|---|---|---|---|---|
bg_completion_reminders |
Inject the metadata-only <system-reminder> for completed bg subagents on the next depth-0 turn (true / false). The agent panel still shows completions when this is off. |
true |
project-ok | KIN_BG_REMINDERS |
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 theread_settings/propose_settingstools. 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,modelHuman-only (refused, hand-edit this file) mode,sandbox,sandbox_strict,sandbox_always_allow,shell_allowlist(containment);api_key,base_url,brave_api_key(secrets/endpoints);provider,provider_preset,providers,model_favorites(presets);reasoning_effort(cross-wire conflict — useeffortwhich 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_settingsmasks 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/modeand/model). See 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_always_allow, shell_allowlist, session_dir, no_save, brave_api_key,
provider_preset, providers, provider_keys, model_favorites, ssh_hosts,
browser_allow_private, 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, the legacy sandbox_default, shell_allowlist), grant itself a sandbox bypass (sandbox_always_allow), add a deploy target for the out-of-sandbox ssh tool (ssh_hosts), point the browser tool at your private network (browser_allow_private), redirect where sessions land (session_dir, no_save, brave_api_key), route your global api_key through a third-party endpoint (provider_preset + providers + provider_keys), or repoint the outpost/outpost-send tools at a hostile endpoint that would then receive your bearer token (outpost_url + outpost_token). Keeping them global-only means a checkout can't silently retarget your endpoint, route your key through an attacker-controlled host, or weaken your safety posture. The exact denylist and the layering rules are documented in the repo's REFERENCE.md § Settings & config layering.
Secrets (api_key, brave_api_key) belong only in the global file, which lives in your $HOME and isn't committed — chmod 600 ~/.kin/settings.toml, like ~/.aws/credentials.