返回 CodeWhale
CONFIGURATION.md
根目录 / docs / CONFIGURATION.md
1 # Configuration
2
3 codewhale reads configuration from a TOML file plus environment variables.
4 At process startup it may also load literal built-in-provider credentials from
5 a workspace-local `.env` file. Use the tracked `.env.example` as the template;
6 copy it to `.env`, then add only credential values.
7
8 A workspace is not configuration authority. Codewhale therefore ignores
9 config/profile/home paths, provider/model/base-URL routing, MCP/plugin state,
10 approval/sandbox/shell posture, executable paths, runtime settings, and every
11 other non-credential `.env` entry. Variable expansion is rejected so a
12 repository cannot substitute an ambient secret into a credential value. Use
13 `config.toml`, CLI flags, or values exported by the launching shell for those
14 explicit control-plane settings. `.env` is read from a stable regular-file
15 handle, is capped at 1 MiB, and symbolic links, reparse points, and multiply
16 linked files are rejected.
17
18 ## Constitution, project instructions, and repo authority
19
20 Codewhale has several instruction surfaces. They are deliberately separate so a
21 personal constitution, repo policy, project instructions, and runtime security
22 controls do not blur together.
23
24 - **Bundled global Constitution** — the compiled base law in the binary. It is
25 the default floor for every session.
26 - **User-global constitution** — the normal guided setup output. Manage it with
27 `/constitution` or `/setup`; Codewhale stores structured data at
28 `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)
29 and renders it into a separate `<codewhale_user_constitution>` prose block.
30 This can express preferences and stop conditions, but it does not change
31 runtime approval policy, sandbox, shell, network, trust, or MCP permissions.
32 - **Repo-local constitution** — optional project policy in
33 `.codewhale/constitution.json`, described below.
34 - **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the
35 canonical file for "how should an agent work in this repo." Run `/init` to
36 scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as
37 compatibility fallbacks.
38 - **Memory and handoffs** — recalled state. Useful, but lower authority than
39 constitutions and project instructions.
40
41 ### Managing the user-global constitution (`/setup` and `/constitution`)
42
43 On first launch Codewhale runs a short **constitution-first** setup path:
44 language → provider/model readiness → runtime posture → create or confirm your
45 constitution. The bundled/default constitution is always valid, so you can
46 defer; reopen the hub any time with `/setup`.
47
48 On the **Constitution** step:
49
50 - **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again
51 ratifies and saves a fresh structured `constitution.json`.
52 - **`A`** (shown only when a provider is configured) asks your first configured
53 model to draft the constitution. Drafting is **not** saving: the draft is
54 rendered through the same preview and you still press **`G`** to ratify
55 before anything persists.
56 - **`K`** keeps your existing loaded constitution unchanged (shown only when a
57 valid file is already present).
58 - **`U`** (or `/constitution bundled`) records the bundled/default law.
59
60 `/constitution` (alias `/law`) is the primary management surface once you are
61 set up. Subcommands: `status` (the default), `preview`, `review`, `repo` (the
62 repo-local law block), `explain`, `edit`/`guided`, `repair`, `posture`, and
63 `bundled`. Managing the constitution never changes runtime approval, sandbox,
64 shell, network, trust, default mode, or MCP authority — those stay in runtime
65 posture/config.
66
67 Each repo can carry two distinct, complementary files:
68
69 - **`AGENTS.md`** — ordinary project working instructions.
70 - **`.codewhale/constitution.json`** — Codewhale-specific **repo authority /
71 prioritization policy**: when local sources conflict, which should Codewhale
72 trust first, and what to verify before claiming a task is done. `.codewhale/`
73 lives inside the repo (like `.github/`). Example:
74
75 ```json
76 {
77 "schema_version": 1,
78 "authority": [
79 "current user request",
80 "live code and tests",
81 "GitHub issue/PR details",
82 "AGENTS.md",
83 "memory",
84 "old handoffs"
85 ],
86 "protected_invariants": [
87 "do not break old-session transcript replay"
88 ],
89 "branch_policy": "PRs target the integration branch, not main",
90 "verification_policy": {
91 "before_claiming_done": ["run focused tests", "read changed files back"]
92 },
93 "escalate_when": [
94 "a destructive action was not explicitly authorized"
95 ]
96 }
97 ```
98
99 All fields are optional. When present, the file is rendered into the system
100 prompt as concise prose in a higher-authority block. Legacy `WHALE.md` files
101 are ignored and reported as migration-only diagnostics.
102
103 Each `protected_invariants` entry may be either a plain string (advisory
104 prose, the historical shape) or an object carrying path globs, which is
105 additionally **mechanically enforced** in the tool gate. See
106 [Enforced repo-law invariants](#enforced-repo-law-invariants) below.
107
108 This is the **repo-local law** layer in Codewhale's hierarchy: *bundled global
109 Constitution* → *user-global constitution* (`$CODEWHALE_HOME/constitution.json`,
110 rendered as prose) → *repo constitution* (`.codewhale/constitution.json`, this
111 file) → *AGENTS/project instructions* → *memory and handoffs* → *current
112 request and live evidence for the active turn*. Runtime policy
113 (permissions/sandbox/cost limits enforced in code) is separate from all of
114 these prompt layers. The repo constitution gives project decision rules; it
115 does not replace the bundled Constitution, the user-global constitution, or
116 the current user request.
117
118 > **`WHALE.md` is deprecated.** It overlapped confusingly with `AGENTS.md`.
119 > Codewhale no longer reads `WHALE.md` as project or global context. If one is
120 > present, setup/context diagnostics report it as ignored so you can migrate it.
121 > Move ordinary instructions to `AGENTS.md` and Codewhale-specific authority
122 > policy to `.codewhale/constitution.json`. Personal standing guidance belongs
123 > in `/constitution` / `$CODEWHALE_HOME/constitution.json`. (The global
124 > Codewhale Constitution shipped in the model prompt is a separate thing and is
125 > unaffected.)
126
127 ### Enforced repo-law invariants
128
129 By default a `protected_invariants` entry is advisory prose: it is rendered into
130 the prompt as guidance the agent should honor, but nothing stops a write. An
131 entry written as an **object with `paths`** is different — it compiles into a
132 mechanical write hold that the engine's tool gate evaluates before the write
133 runs. The law becomes mechanism, not just a request.
134
135 An enforced entry has this shape:
136
137 ```json
138 {
139 "schema_version": 1,
140 "protected_invariants": [
141 "Keep DeepSeek support first-class.",
142 {
143 "text": "The wire format is frozen; protocol changes need a human.",
144 "paths": ["crates/protocol/**"],
145 "action": "block"
146 },
147 {
148 "text": "Release notes need human review.",
149 "paths": ["CHANGELOG.md"],
150 "action": "ask"
151 }
152 ]
153 }
154 ```
155
156 - `text` — required. The reason surfaced on the hold. An empty `text` is skipped.
157 - `paths` — workspace-relative globs (globset syntax, e.g. `crates/protocol/**`,
158 `**/secrets.toml`, `CHANGELOG.md`). An object with no usable `paths` stays
159 advisory-only despite the object shape.
160 - `action` — optional, defaults to `ask`. `ask` force-prompts in Ask and
161 Auto-Review; in Full Access it denies the protected write without opening a
162 modal. `block` **denies the write outright** in every posture.
163
164 Semantics:
165
166 - **Tighten-only.** The schema has no allow/widen shape, so law can only *add*
167 holds — a crafted constitution can never grant authority or weaken a gate
168 above it.
169 - **Not bypassable by mode.** Like the built-in safety floor, an `ask` hold
170 force-prompts in Ask and Auto-Review. Full Access never opens approval
171 modals, so the same hold fails closed as a hard block; `block` always denies.
172 Mode cannot turn a hold off.
173 - **Repo-local only.** Only the repo's `.codewhale/constitution.json`
174 participates. The user-global constitution stays advisory prose and never
175 reaches this mechanism.
176 - **Fails safe.** A missing file, parse error, or invalid glob degrades to
177 fewer or zero rules — never a hold on unprotected paths and never a poisoned
178 gate. Across matches the strongest action wins, so `block` outranks `ask`.
179 - **Leaves a receipt.** Every hold emits a `tool.repo_law_decision` tool-audit
180 event naming the invariant, the matched path, and the source file; the
181 approval/denial reason names the invariant too.
182
183 **Coverage is deliberately limited.** Holds are evaluated only for the write
184 tools `write_file`, `edit_file`, `apply_patch`, and `fim_edit`, and only
185 against the filesystem targets named in their inputs (`path`/`target`/
186 `destination`/`file_path`, `changes[].path`, and unified-diff /
187 `apply_patch`-envelope headers). A shell command that writes a protected path is **not** held by
188 repo law — those writes are still governed by the ordinary approval, sandbox,
189 and shell-write gates, not by this mechanism.
190
191 ### Expert full base-prompt override (#3638)
192
193 The global Constitution (the base system prompt, normally compiled in from
194 `crates/tui/src/prompts/text.rs` as `BASE_PROMPT`) can be replaced per-user
195 without rebuilding. This is
196 an expert escape hatch, not the normal `/constitution` guided setup output.
197 Because this is a prompt trust boundary, it takes **two deliberate steps** — a
198 file alone is not enough:
199
200 1. Drop the replacement at `~/.codewhale/prompts/constitution.md` (under
201 `$CODEWHALE_HOME` when set).
202 2. Set the explicit opt-in flag `CODEWHALE_ALLOW_BASE_PROMPT_OVERRIDE=1`
203 (`true`/`on`/`yes` also accepted).
204
205 If the file exists but the flag is unset, the override is **ignored** (with a
206 log line pointing to the flag) and the bundled Constitution stays in place.
207 This is intended for repurposing the TUI beyond software engineering — e.g.
208 long-form writing or document review — where the engineering-oriented base
209 prompt is a poor fit. It is loaded once at startup; a **missing or empty file
210 is a no-op**, so existing installs keep the bundled prompt.
211
212 Scope is deliberately narrow: only the byte-stable **base prompt segment** is
213 overridable. Mode deltas, the approval policy, the tool taxonomy, Context
214 Management, and the Compaction Relay are still owned by Codewhale's runtime
215 assembly, so an override **cannot remove safety-relevant guidance** (sandbox,
216 approvals) — it only swaps the task/voice framing. To customize ordinary
217 personal behavior, prefer `/constitution`; to customize per-repo behavior,
218 prefer `AGENTS.md` + `.codewhale/constitution.json` above.
219
220 ## Where It Looks
221
222 Default config path:
223
224 - `~/.codewhale/config.toml`
225 - Legacy fallback: `~/.deepseek/config.toml`
226
227 Overrides:
228
229 - CLI: `codewhale --config /path/to/config.toml`
230 - Env: `CODEWHALE_CONFIG_PATH=/path/to/config.toml`
231 - Legacy env alias: `DEEPSEEK_CONFIG_PATH=/path/to/config.toml`
232
233 If both are set, `--config` wins. Environment variable overrides are applied after the file is loaded.
234
235 ### TUI editability audit
236
237 Inside the TUI, run `/config audit` to see which documented keys can be changed
238 from the current session, which ones can also be persisted, and which ones stay
239 file-only or restart-only. The audit includes current values for the high-impact
240 runtime controls such as `approval_policy`, `allow_shell`,
241 `stream_chunk_timeout_secs`, `base_url`, `mcp_config_path`, and the
242 `[subagents]` concurrency/depth/timeout keys.
243
244 Use the command's "Command / reason" column as the source of truth before
245 editing by hand. For example, `/config approval_mode on-request --save` writes
246 top-level `approval_policy = "on-request"`, while provider base URLs are saved
247 but still require restarting the model client.
248
249 ### User workspace entries
250
251 Interactive Agent sessions expose shell tools by default with approval gating
252 unless you explicitly disable them. For a shell opt-in that should live in the
253 user's global config for noninteractive or durable-task profiles rather than in
254 the repository, add a workspace-scoped entry:
255
256 ```toml
257 [workspace.'/absolute/path/to/project']
258 allow_shell = true
259 ```
260
261 The entry applies only when the launched workspace path matches the table key.
262 The legacy `[projects."/absolute/path/to/project"]` table is also accepted for
263 this user-owned override.
264
265 In interactive mode, the per-project overlay
266 `<workspace>/.codewhale/config.toml` is applied after this user entry. A
267 project-level `allow_shell = false` can still tighten the session; project-level
268 `allow_shell = true` is ignored.
269
270 ### Per-project overlay (#485)
271
272 When the TUI starts in a workspace that contains a regular-file
273 `<workspace>/.codewhale/config.toml`, the safe values declared in that file are
274 merged on top of the global config. Legacy
275 `<workspace>/.deepseek/config.toml` files are still read when the Codewhale path
276 is absent. Symlinked project config files are rejected. This lets a repo suggest
277 a model or tighten local safety posture without touching the user's
278 `~/.codewhale/config.toml`. Pass `--no-project-config` to skip the overlay for
279 one launch.
280
281 Supported keys in the project overlay (top-level fields only):
282
283 | Key | Effect |
284 |---|---|
285 | `model` | override `default_text_model` |
286 | `reasoning_effort` | force `"high"` / `"max"` for a complex repo |
287 | `approval_policy` | only values that tighten the user's current permission posture |
288 | `sandbox_mode` | only values that tighten the user's current sandbox posture |
289 | `notes_path` | keep notes in-repo |
290 | `max_subagents` | clamp sub-agent concurrency for a constrained repo (clamped to 1..=128) |
291 | `allow_shell` | `false` can disable shell access; `true` is ignored |
292
293 The overlay is intentionally narrow — it covers the fields a repo
294 maintainer is most likely to want to standardize across contributors.
295 Credential, endpoint, provider-selection, MCP config, hooks, skills,
296 retry, hotbar bindings, and `instructions = [...]` settings stay user-global.
297 If a repo-local config declares `api_key`, `base_url`, `provider`,
298 `mcp_config_path`, `hotbar`, `allow_shell = true`, or `instructions`,
299 Codewhale ignores that key and keeps the user's global setting.
300
301 The `codewhale` facade and `codewhale-tui` binary share the same config file for
302 DeepSeek auth and model defaults. `codewhale auth set --provider deepseek` (and
303 the legacy `codewhale login --api-key ...` alias) saves the key to
304 `~/.codewhale/config.toml` (migrating legacy `~/.deepseek/config.toml` on first
305 launch when needed), and `codewhale --model deepseek-v4-flash` is forwarded to
306 the TUI as `DEEPSEEK_MODEL`.
307
308 That provider credential is distinct from the optional managed-product
309 account. `codewhale account login` starts the Codewhale browser device flow;
310 `codewhale account status` and `codewhale account logout` inspect or remove the
311 session for the selected `--profile`. Account sessions are stored in the OS
312 credential manager. A file-backed session store exists only as an explicit
313 development fallback. `codewhale account keys list|set|remove` manages the
314 signed-in account's BYOK vault without displaying secret values. The older
315 `codewhale cloud ...` spelling remains a command alias.
316
317 ### Credential read precedence (#5197)
318
319 Credential reads are **folder-independent by default**: every layer below is
320 user-global or process-scoped, so a key saved in one repo resolves identically
321 in every other repo. Repo-local config never carries credential material — the
322 project overlay above reads only its allowlisted keys and ignores `api_key`,
323 and a credential write aimed at a workspace-scoped config is rescoped to the
324 user-global `~/.codewhale/config.toml` (#5045, #5193).
325
326 For the active provider, the runtime resolves the API key in this exact order
327 (first match wins):
328
329 1. **Route-specific auth contract.** Routes whose `auth_mode` disables API
330 keys stop here with no credential. OAuth routes use their explicitly
331 consented token: `openai-codex` reads `OPENAI_CODEX_ACCESS_TOKEN` or the
332 consent-granted Codex CLI login (read-only, never refreshed or rewritten);
333 `[providers.xai] auth_mode = "oauth"` reads Codewhale's own xAI
334 device-login store (or a consent-granted Grok CLI file).
335 2. **Explicit CLI key.** `--api-key` forwarded with its source marker wins
336 over every saved slot; for `deepseek`/`deepseek-CN` it also wins over the
337 root `api_key`.
338 3. **Config file `api_key`.** The `[providers.<name>] api_key` table slot for
339 the active provider, plus the legacy root `api_key` for
340 `deepseek`/`deepseek-CN` and the literal `provider = "custom"` route.
341 File-owned keys stay bound to their file-owned endpoint: when the
342 environment replaces the route's base URL with a custom host, the saved
343 key is not sent there.
344 4. **`api_key_env` binding.** `[providers.<name>] api_key_env = "VAR"` reads
345 the named environment variable. For custom providers an unset or empty
346 binding is a loud error, not a silent fallback (#5104).
347 5. **Secret store.** The durable per-provider slot written by
348 `codewhale auth set` (file-backed under `~/.codewhale/secrets/` by
349 default; the OS keyring only when explicitly selected). Skipped for named
350 custom routes, self-hosted providers, custom endpoints other than an
351 explicitly authenticated loopback, and routes whose `auth_mode` needs no
352 key.
353 6. **Ambient environment.** The provider's own variable
354 (`DEEPSEEK_API_KEY`, `OPENROUTER_API_KEY`, `MOONSHOT_API_KEY`, …).
355 Ambient keys are only ever sent to the provider's official endpoint and
356 are skipped under the same conditions as the secret store.
357 7. **Keyless fallback.** Self-hosted providers and loopback endpoints may run
358 with no credential; every other route fails with provider-specific setup
359 guidance.
360
361 Legacy compatibility: `~/.deepseek/config.toml` is migrated into
362 `~/.codewhale/config.toml` on first launch, `DEEPSEEK_*` environment
363 variables remain accepted aliases for the `CODEWHALE_*` forms, and
364 `DEEPSEEK_SECRET_BACKEND` is the legacy alias for `CODEWHALE_SECRET_BACKEND`.
365
366 Run `codewhale auth status` to inspect the active provider's config
367 file, OS keyring backend, environment variable, winning source, and last-four
368 label without printing the key itself. The command only probes the active
369 provider's keyring entry.
370
371 For hosted, generic OpenAI-compatible, self-hosted, OpenAI Responses, or native
372 Anthropic providers, set `provider = "<id>"` or pass
373 `codewhale --provider <id>`. The canonical provider IDs are `deepseek`,
374 `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `volcengine`,
375 `openrouter`, `xiaomi-mimo`, `novita`, `fireworks`, `siliconflow`, `arcee`,
376 `siliconflow-CN`, `moonshot`, `sglang`, `vllm`, `ollama`, `huggingface`,
377 `together`, `qianfan`, `openai-codex`, `anthropic`, `openmodel`, `zai`,
378 `stepfun`, `minimax`, and `deepinfra`.
379 For the provider-by-provider registry, including wire protocol, auth variables,
380 default base URLs, model IDs, and capability metadata, see
381 [PROVIDERS.md](PROVIDERS.md).
382 The facade saves provider credentials to the shared user config and forwards
383 the resolved key, base URL, provider, and model to the TUI process. Use
384 `codewhale auth set --provider nvidia-nim --api-key "YOUR_NVIDIA_API_KEY"` or
385 `codewhale auth set --provider openai --api-key "YOUR_OPENAI_COMPATIBLE_API_KEY"` or
386 `codewhale auth set --provider atlascloud --api-key "YOUR_ATLASCLOUD_API_KEY"` or
387 `codewhale auth set --provider wanjie-ark --api-key "YOUR_WANJIE_API_KEY"` or
388 `codewhale auth set --provider xiaomi-mimo --api-key "YOUR_XIAOMI_KEY"` or
389 `codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"` or
390 `codewhale auth set --provider siliconflow --api-key "YOUR_SILICONFLOW_API_KEY"` or
391 `codewhale auth set --provider arcee --api-key "YOUR_ARCEE_API_KEY"` or the
392 matching provider ID from [PROVIDERS.md](PROVIDERS.md) to save provider keys
393 through the facade. The generic `openai` provider defaults
394 to `https://api.openai.com/v1`, accepts `OPENAI_BASE_URL`, and defaults to
395 `deepseek-v4-pro` for OpenAI-compatible gateways. `atlascloud` defaults to
396 `https://api.atlascloud.ai/v1`, accepts `ATLASCLOUD_BASE_URL`, and uses
397 `deepseek-ai/deepseek-v4-flash` as its default model. `wanjie-ark` targets
398 Wanjie Ark's OpenAI-compatible endpoint at
399 `https://maas-openapi.wanjiedata.com/api/v1`, defaults to `deepseek-reasoner`,
400 and passes model IDs through unchanged because Wanjie model access is
401 account-scoped. SGLang, vLLM, and Ollama are
402 self-hosted and can run without an API key by default. Ollama defaults to
403 `http://localhost:11434/v1` and sends model tags such as `codewhale-coder:1.3b`
404 or `qwen2.5-coder:7b` unchanged. Self-hosted providers and loopback custom
405 URLs (`localhost`, `127.0.0.1`, `[::1]`, `0.0.0.0`) do not read the secret store
406 unless API-key auth is explicitly requested; use an env var or config-file key
407 when a local server does require bearer auth.
408 SiliconFlow defaults to `https://api.siliconflow.com/v1`, accepts
409 `SILICONFLOW_BASE_URL`, and uses `deepseek-ai/DeepSeek-V4-Pro` by default.
410 `provider = "siliconflow-CN"` selects the China regional default
411 `https://api.siliconflow.cn/v1` with the `[providers.siliconflow_cn]` table and
412 `SILICONFLOW_API_KEY` credential slot.
413 Arcee AI defaults to `https://api.arcee.ai/api/v1`, accepts `ARCEE_BASE_URL`,
414 and uses `trinity-large-thinking` by default for Codewhale agent work.
415 `trinity-large-preview` is also listed as a direct Arcee API model; OpenRouter's
416 `arcee-ai/trinity-large-thinking` remains the OpenRouter namespaced form, while
417 the direct Arcee provider uses the bare `trinity-large-thinking` ID. Direct
418 Arcee large-model API calls are tracked as 256K-context BF16 serving; Thinking
419 is reasoning-capable, while Preview is not marked as a thinking model.
420
421 ### Custom OpenAI-Compatible Gateways
422
423 For a single third-party service that implements the OpenAI Chat Completions
424 API, the simplest setup is the built-in `openai` provider name pointed at the
425 gateway:
426
427 ```toml
428 provider = "openai"
429 default_text_model = "your-model-id"
430
431 [providers.openai]
432 api_key = "YOUR_OPENAI_COMPATIBLE_API_KEY"
433 base_url = "https://your-gateway.example/v1"
434 ```
435
436 Put the endpoint under `[providers.openai]`, not the legacy top-level
437 `base_url`, so the OpenAI-compatible provider receives it. `default_text_model`
438 is the model ID sent to the gateway; `[providers.openai].model` can be used as
439 the OpenAI-provider-specific override.
440
441 If you keep several OpenAI-compatible gateways, or need a stable name for an
442 AgentProfile provider pin, define a user-named custom provider table:
443
444 ```toml
445 provider = "lm-studio"
446
447 [providers.lm-studio]
448 kind = "openai-compatible"
449 base_url = "http://127.0.0.1:1234/v1"
450 api_key = "lm-studio"
451 model = "qwen-2.5-7b"
452 ```
453
454 Custom provider names may be selected with `provider = "<name>"`,
455 `--provider <name>`, or an AgentProfile `provider = "<name>"` when the matching
456 `[providers.<name>]` table exists.
457
458 StepFun has a first-class provider entry, so keep Coding Plan credentials and
459 base URL scoped to `[providers.stepfun]`:
460
461 ```toml
462 provider = "stepfun"
463
464 [providers.stepfun]
465 api_key = "YOUR_STEPFUN_API_KEY"
466 base_url = "https://api.stepfun.ai/step_plan/v1"
467 model = "step-3.7-flash"
468 ```
469
470 `/provider` setup asks which StepFun billing route the key belongs to —
471 pay-as-you-go (`https://api.stepfun.ai/v1`) or a Step Plan subscription
472 (`https://api.stepfun.ai/step_plan/v1`) — and validates the key against the
473 endpoint you pick before saving it. The answer is written to
474 `[providers.stepfun].base_url` and nowhere else. If that key already holds a
475 base URL Codewhale does not recognize as one of those two routes, the question
476 is skipped and your value is left untouched.
477
478 Alibaba Bailian / Model Studio DashScope Qwen routes use the same OpenAI
479 provider shape:
480
481 ```toml
482 provider = "openai"
483
484 [providers.openai]
485 api_key = "YOUR_DASHSCOPE_API_KEY"
486 base_url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
487 model = "qwen-plus"
488 context_window = 1000000
489 ```
490
491 Use the regional DashScope `compatible-mode/v1` base URL that matches the
492 region of your API key. Codewhale keeps `qwen-plus` scoped to the `openai`
493 provider route and does not infer a different provider from the model prefix.
494 The same rule applies to all provider-prefixed model strings: a prefix such as
495 `deepseek-ai/...` or `deepseek/...` is a provider-owned wire ID under the
496 selected provider, not an automatic switch to the DeepSeek provider.
497 Set `context_window` to the gateway/model's real total context window when it
498 differs from Codewhale's static model metadata. See
499 [Context length (context window)](#context-length-context-window) for the full
500 resolution order and for how to check which value is in effect.
501
502 If the gateway accepts `POST /chat/completions` but rejects
503 `/v1/chat/completions`, set a provider-local `path_suffix`:
504
505 ```toml
506 [providers.openai]
507 base_url = "https://your-gateway.example/v1"
508 path_suffix = "/chat/completions"
509 ```
510
511 The suffix applies only to chat-completion requests. Model listing and
512 DeepSeek beta paths keep their built-in routing so a generic gateway override
513 does not accidentally rewrite `/models` or `/beta/completions`.
514
515 For private gateways with broken or intercepted certificates, use
516 `SSL_CERT_FILE` with a trusted CA bundle. The legacy provider-table key
517 `insecure_skip_tls_verify = true` is still parsed so `codewhale doctor` can
518 report stale configs, but provider clients reject it instead of disabling TLS
519 certificate verification.
520
521 Local HTTP endpoints such as Ollama, SGLang, and vLLM are allowed by default
522 when they use localhost or loopback addresses. For a non-local `http://`
523 gateway, launch with `DEEPSEEK_ALLOW_INSECURE_HTTP=1` only on a trusted network:
524
525 ```bash
526 DEEPSEEK_ALLOW_INSECURE_HTTP=1 codewhale
527 ```
528
529 Third-party OpenAI-compatible gateways that need extra request headers can set
530 `http_headers = { "X-Model-Provider-Id" = "your-model-provider" }` at the top
531 level or under a provider table such as `[providers.deepseek]`. When configured,
532 codewhale sends those custom headers on model API requests. The equivalent
533 environment override is `DEEPSEEK_HTTP_HEADERS`, using comma-separated
534 `name=value` pairs such as
535 `X-Model-Provider-Id=your-model-provider,X-Gateway-Route=dev`. `Authorization`
536 and `Content-Type` are managed by the client and are not overridden by this
537 setting.
538
539 ### Vision Model
540
541 Codewhale's chat provider and `image_analyze` tool are configured separately.
542 The main chat path remains the selected text/tool provider; image analysis runs
543 through `[vision_model]` when the `vision_model` feature is enabled.
544
545 Xiaomi's current image-understanding docs include `mimo-v2.5` for image input.
546 To use MiMo for `image_analyze`, configure the vision model explicitly:
547
548 ```toml
549 [features]
550 vision_model = true
551
552 [vision_model]
553 model = "mimo-v2.5"
554 api_key = "YOUR_XIAOMI_KEY"
555 base_url = "https://api.xiaomimimo.com/v1"
556 ```
557
558 The example above uses Xiaomi MiMo's pay-as-you-go OpenAI-compatible endpoint.
559 If you are using a Token Plan key (`tp-...`) for `[vision_model]`, you must set
560 `base_url` explicitly because this generic OpenAI-compatible block does not
561 auto-select MiMo endpoints. Use
562 `https://token-plan-sgp.xiaomimimo.com/v1` for Singapore accounts,
563 `https://token-plan-cn.xiaomimimo.com/v1` for China-region accounts, or
564 `https://token-plan-ams.xiaomimimo.com/v1` for Europe/Amsterdam accounts.
565
566 ### Auto Model Routing (`[auto.router]`)
567
568 With `model = "auto"`, Codewhale routes each turn between a strong and a cheap
569 model. The routing decision comes from a small classifier call, or from a local
570 heuristic when no classifier route is available.
571
572 **There is no default classifier.** With `[auto.router]` unset, Auto is local
573 and free: it uses the heuristic and makes no classifier call, whatever keys you
574 hold. Holding a DeepSeek key used to elect `deepseek-v4-flash` automatically;
575 that was removed because it spent tokens on a route the user never chose and
576 privileged one provider over the rest (`crates/tui/src/config.rs:2392-2402`).
577 Electing a network classifier is now something you write down.
578
579 Point the classifier at any configured provider with `[auto.router]`:
580
581 ```toml
582 [auto.router]
583 provider = "zai"
584 model = "glm-5-turbo"
585 thinking = "off" # optional; defaults to off
586 ```
587
588 A classifier call happens only when `[auto.router]` is set *and* that provider
589 has a key — `router_available = router_configured && has_api_key_for(...)`
590 (`crates/tui/src/model_inventory.rs:206-218`). Either condition failing means
591 the heuristic decides, not a failure. The turn's route receipt (`/status` →
592 Auto) records which one it was.
593
594 To bootstrap MCP and skills directories at their resolved paths, run `codewhale-tui setup`.
595 To only scaffold MCP, run `codewhale-tui mcp init`.
596
597 Note: `setup`, `doctor`, `mcp`, `features`, `sessions`, `resume`/`fork`, `exec`,
598 `review`, and `eval` are subcommands of the `codewhale-tui` binary, and the
599 `codewhale` dispatcher accepts every one of them as a passthrough
600 (`TuiPassthroughArgs`, `crates/cli/src/lib.rs:244-429`) — the dispatcher's
601 surface is a **superset**, not a distinct set. It adds commands the TUI binary
602 does not have of its own: `auth`, `config`, `model`, `thread`, `sandbox`,
603 `app-server`, `mcp-server`, `completions`, `login`/`logout`, `account`,
604 `metrics`, `update`, `lane`, `workflow`, `web`. Plain prompts are forwarded to
605 `codewhale-tui`.
606
607 ### Startup Update Checks
608
609 By default, the TUI starts a background check for the latest stable Codewhale
610 release and shows a short toast only when a newer release is available and the
611 official release assets are complete. The check never blocks startup, never
612 blocks a turn, and fails silently when offline.
613
614 Disable the startup check entirely for air-gapped, corporate-proxy, or managed
615 desktop environments:
616
617 ```toml
618 [update]
619 check_for_updates = false
620 ```
621
622 #### Throttling
623
624 The answer is cached in `~/.codewhale/update-check.json` and reused for
625 `check_interval_hours` (default `24`). Only the *network request* is throttled —
626 the notice still appears on every launch while an update is outstanding. Set `0`
627 to check on every launch.
628
629 ```toml
630 [update]
631 check_interval_hours = 24
632 ```
633
634 A failed check is not cached, so an outage does not suppress the notice until
635 the interval elapses.
636
637 #### Automatic suppression
638
639 Checks are skipped, without contacting the network, when any of these is set to
640 a non-falsey value:
641
642 | Variable | Why |
643 | --- | --- |
644 | `CODEWHALE_NO_UPDATE_CHECK` | Explicit opt-out. |
645 | `NO_UPDATE_NOTIFIER` | The cross-CLI convention, honored for compatibility. |
646 | `CI`, `CONTINUOUS_INTEGRATION`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, `JENKINS_URL`, `TEAMCITY_VERSION`, `TF_BUILD` | Automated build; nobody is at the terminal. |
647
648 Values of `""`, `0`, `false`, `no`, and `off` do not count as set, so a
649 `CI=false` export does not disable checks for ordinary users.
650
651 #### Which update command is offered
652
653 Codewhale never installs anything on its own — it only tells you an update
654 exists. The command it names depends on how the running binary was installed,
655 detected from its path:
656
657 | Install | Command offered |
658 | --- | --- |
659 | GitHub release binary (including Termux) | `codewhale update` |
660 | npm (`node_modules` on the path) | `npm install -g codewhale@latest` |
661 | Homebrew (`Cellar` / `linuxbrew` prefix) | `brew upgrade deepseek-tui` |
662 | `cargo install` (`~/.cargo/bin`) | `cargo install codewhale-cli --locked --force` |
663
664 For package-managed installs the notice also warns against `codewhale update`:
665 replacing a binary Homebrew or npm owns leaves the manager describing a version
666 that is no longer on disk, and the next upgrade silently reverts you.
667
668 Override the detection with `CODEWHALE_INSTALL_METHOD=npm|homebrew|cargo|binary`
669 if you relocated the binary somewhere the path heuristics cannot read.
670
671 To redirect the startup check, set `update_uri` to an internal endpoint that
672 returns GitHub-compatible latest-release JSON. Minimal mirror metadata with a
673 `tag_name` field is accepted; if `assets` are present, Codewhale requires the
674 same uploaded asset set as the official release before showing the toast.
675
676 ```toml
677 [update]
678 check_for_updates = true
679 update_uri = "https://internal.mirror.example/codewhale/releases/latest"
680 ```
681
682 When `update_uri` is not set, startup checks honor release mirror environment
683 variables such as `CODEWHALE_RELEASE_BASE_URL` before falling back to the
684 official GitHub API endpoint. If a configured `update_uri` cannot be fetched or
685 parsed and a release mirror env var is set, the TUI falls back to that mirror
686 instead of failing startup.
687
688 ## Context length (context window)
689
690 Also called context size, context limit, max context, or window. This is the
691 total token window Codewhale budgets against, and it drives the header/footer
692 context percent, the auto-compaction trigger, context-pressure checks, and the
693 request output cap. If Codewhale compacts at 128K on a model you know serves a
694 1M window, this is the setting to change (#5134).
695
696 **See what is in effect, and where the value came from.** Every one of these
697 prints the resolved window *and* its source:
698
699 - `/status` — a `Context window:` row with the percent and token counts, and a
700 `Window source:` row naming the provenance and the exact key that overrides
701 it.
702 - `/config` → Provider — `Context window` (your override, or `(not set)`) and
703 `Effective context window` (`1048576 tokens · configured`). Typing
704 `context length` in the `/config` filter jumps straight to them.
705 - `/context report` — `Window: 1048576 tokens (12.4% used, ...; source: configured)`.
706 - `/context json` — machine-readable `context_window_tokens` and
707 `context_window_source`.
708
709 **Change it** with the provider-table key `context_window`:
710
711 ```toml
712 [providers.moonshot]
713 context_window = 1048576
714 ```
715
716 or from the CLI:
717
718 ```bash
719 codewhale config set providers.moonshot.context_window 1048576
720 codewhale config unset providers.moonshot.context_window # back to automatic
721 ```
722
723 Use the table for the provider you are actually on (`providers.openai`,
724 `providers.deepseek`, `providers.moonshot`, …); `/status` names it for you. The
725 value is a positive token count for the route's *total* window.
726
727 ### How the effective window is resolved
728
729 First match wins, and the source label each surface prints is exactly this
730 rung:
731
732 1. `configured` — `[providers.<name>] context_window` in `config.toml`. A hard
733 override: nothing below it can raise or lower the result. Read-time aliases:
734 `contextWindow`, `context_window_tokens`, `contextWindowTokens`,
735 `context_length`, `contextLength`.
736 2. `provider-reported` — route-scoped 1M metadata a provider actually reported
737 for the Kimi Code `k3` route, when it was observed within the last 24 hours.
738 3. `static Kimi Code safe floor` — 262,144 tokens for Kimi Code memberships,
739 because 1M access is plan-gated (Allegretto and above).
740 4. `catalog` — the bundled route catalog (hand-curated offerings first, then
741 the bundled Models.dev rows). For `openai-codex`, a fresh (under 24 hours)
742 `$CODEX_HOME` model roster corrects this rung.
743 5. `fallback` — the static per-provider capability table: 200,000 for
744 Anthropic-wire routes, 128,000 for `openai-codex`, 8,192 for Ollama,
745 otherwise Codewhale's static per-model metadata, and finally 128,000 when
746 the model is unknown.
747
748 There is no environment variable for the context window, and no per-model
749 override key. The per-provider `context_window` is the only user knob, which is
750 also why it is the right one to set when a gateway or self-hosted runtime
751 serves a window Codewhale's catalog does not model. Codewhale will not invent a
752 window it cannot justify — it falls back to a conservative value and labels it
753 `fallback`.
754
755 ### Adjacent knobs
756
757 - `auto_compact_threshold_percent` (settings.toml; also accepted as
758 `auto_compact_threshold`; `10`–`100`, default `80`): the share of the usable
759 input budget — the window minus reserved output headroom — at which
760 auto-compaction fires. Editable from `/config`. Raising the window without
761 touching this raises the absolute compaction point along with it.
762 - `auto_compact` (settings.toml, on/off): turns automatic compaction off
763 entirely; `/compact` and Ctrl+L stay available.
764 - `CODEWHALE_MAX_OUTPUT_TOKENS` (environment variable; legacy alias
765 `DEEPSEEK_MAX_OUTPUT_TOKENS`): overrides the requested output cap, which is
766 otherwise derived from the window. There is no `max_output_tokens` key in
767 `config.toml`.
768
769 See [Settings File](#settings-file-persistent-ui-preferences) for the
770 compaction settings and [Token Quantities and
771 Drivers](#token-quantities-and-drivers) for what each displayed token number
772 actually measures.
773
774 ## Profiles
775
776 You can define multiple profiles in the same file:
777
778 ```toml
779 api_key = "PERSONAL_KEY"
780 default_text_model = "deepseek-v4-pro"
781
782 [profiles.work]
783 api_key = "WORK_KEY"
784 base_url = "https://api.deepseek.com/beta"
785
786 [profiles.nvidia-nim]
787 provider = "nvidia-nim"
788 api_key = "NVIDIA_KEY"
789 base_url = "https://integrate.api.nvidia.com/v1"
790 default_text_model = "deepseek-ai/deepseek-v4-pro"
791
792 [profiles.fireworks]
793 provider = "fireworks"
794 default_text_model = "accounts/fireworks/models/deepseek-v4-pro"
795
796 [profiles.siliconflow]
797 provider = "siliconflow"
798 default_text_model = "deepseek-ai/DeepSeek-V4-Pro"
799
800 [profiles.siliconflow.providers.siliconflow]
801 base_url = "https://api.siliconflow.com/v1"
802
803 [profiles.openai-compatible]
804 provider = "openai"
805
806 [profiles.openai-compatible.providers.openai]
807 base_url = "https://openai-compatible.example/v4"
808 model = "glm-5"
809
810 [profiles.atlascloud]
811 provider = "atlascloud"
812
813 [profiles.atlascloud.providers.atlascloud]
814 base_url = "https://api.atlascloud.ai/v1"
815 model = "deepseek-ai/deepseek-v4-flash"
816
817 [profiles.sglang]
818 provider = "sglang"
819 base_url = "http://localhost:30000/v1"
820 default_text_model = "deepseek-ai/DeepSeek-V4-Pro"
821
822 [profiles.vllm]
823 provider = "vllm"
824 base_url = "http://localhost:8000/v1"
825 default_text_model = "deepseek-ai/DeepSeek-V4-Pro"
826
827 [profiles.ollama]
828 provider = "ollama"
829 base_url = "http://localhost:11434/v1"
830 default_text_model = "codewhale-coder:1.3b"
831 ```
832
833 Select a profile with:
834
835 - CLI: `codewhale --profile work`
836 - Env: `DEEPSEEK_PROFILE=work`
837
838 If a profile is selected but missing, codewhale exits with an error listing available profiles.
839
840 ## Harness Profiles
841
842 v0.9 adds a config data model for model-specific harness posture. This is a
843 preview schema: it can be parsed and tested, but runtime provider/model
844 selection and prompt/tool behavior are wired in later v0.9 slices.
845 When no configured profile matches, the resolver falls back to built-in seed
846 profiles for the model families listed in the cutline doc. Configured profiles
847 always take precedence over those seeds.
848
849 ```toml
850 [[harness_profiles]]
851 provider_route = "deepseek"
852 model_pattern = "deepseek-v4.*"
853
854 [harness_profiles.posture]
855 kind = "cache-heavy" # standard | cache-heavy | lean | custom
856 max_subagents = 10 # 0 means runtime default
857 prefer_codebase_search = false
858 compaction_strategy = "prefix-cache" # default | prefix-cache | aggressive
859 tool_surface = "full" # full | read-only | auto
860 safety_posture = "standard" # standard | strict | permissive
861 ```
862
863 Unknown posture names or unknown keys inside a harness profile fail config
864 deserialization instead of silently becoming `custom`. That is intentional:
865 once runtime wiring consumes these profiles, a typo should be visible.
866 The v0.9 implementation order and automatic-creator boundary are documented in
867 [`HARNESS_PROFILE_CUTLINE.md`](rfcs/HARNESS_PROFILE_CUTLINE.md).
868
869 ## Environment Variables
870
871 Most runtime environment variables override config values. API-key variables are
872 fallbacks after saved config and keyring credentials.
873
874 The three user-facing slots — provider, model, base URL — expose `CODEWHALE_*`
875 aliases. When both forms are set the `CODEWHALE_*` value wins; the
876 `DEEPSEEK_*` form is kept for older shells:
877
878 - `CODEWHALE_PROVIDER` (preferred) / `DEEPSEEK_PROVIDER` (legacy alias) —
879 `deepseek|deepseek-anthropic|nvidia-nim|openai|atlascloud|wanjie-ark|volcengine|openrouter|xiaomi-mimo|novita|fireworks|siliconflow|arcee|siliconflow-CN|moonshot|sglang|vllm|ollama|huggingface|together|qianfan|openai-codex|anthropic|openmodel|zai|stepfun|minimax|deepinfra`
880 - `CODEWHALE_MODEL` (preferred) / `DEEPSEEK_MODEL` (legacy alias) — default model for the active provider
881 - `CODEWHALE_BASE_URL` (preferred) / `DEEPSEEK_BASE_URL` (legacy alias) — base URL for the active provider
882
883 `CODEWHALE_BASE_URL` applies to the **active** route only. A request pinned to
884 another provider — a subagent or fleet child, a routed tool, the per-turn
885 auto-router, a picker preview — resolves its endpoint from that provider's own
886 `[providers.<table>]`, then its provider-scoped variable (`MOONSHOT_BASE_URL`,
887 `OPENAI_BASE_URL`, …), then that provider's default. It never inherits the
888 active session's host, and a custom route with no configured `base_url` fails
889 closed on a loopback placeholder rather than borrowing another provider's
890 endpoint. The legacy root `base_url` behaves the same way: written in your
891 config file it stays shared by the DeepSeek and DeepSeek-CN identities as it
892 always has, but a value the environment wrote belongs to the identity it was
893 addressed to. A managed-config overlay that supplies or reselects the effective
894 route's endpoint takes the generic override away from every route.
895
896 Remaining variables:
897
898 - `DEEPSEEK_API_KEY`
899 - `DEEPSEEK_ANTHROPIC_BASE_URL`
900 - `DEEPSEEK_HTTP_HEADERS` (custom model request headers, comma-separated `name=value` pairs)
901 - `DEEPSEEK_DEFAULT_TEXT_MODEL` (extra legacy alias of `DEEPSEEK_MODEL`)
902 - `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` (stream idle timeout in seconds; default `900`, clamped to `1..=3600`)
903 - `DEEPSEEK_STREAM_OPEN_TIMEOUT_SECS` (connection setup + response-header wait in seconds; default `45`, clamped to `5..=300`; distinct from the per-chunk idle timeout)
904 - `CODEWHALE_CACHE_MAXIMAL` (`1`/`true`/`on`/`yes`) — cache-maximal context mode (#528). When on, the Repo Working Set block materializes the **full current contents** of the top active files into the system prompt each turn (deterministic order, byte-bounded), instead of only listing their paths. The block stays byte-stable while those files are unchanged so DeepSeek's KV prefix cache keeps hitting; editing a file cache-misses from its block onward. Off by default (path list only). Byte caps default to 24 KB per file / 96 KB total.
905 - `NVIDIA_API_KEY` or `NVIDIA_NIM_API_KEY` (preferred when provider is `nvidia-nim`; falls back to `DEEPSEEK_API_KEY`)
906 - `NVIDIA_NIM_BASE_URL`, `NIM_BASE_URL`, or `NVIDIA_BASE_URL`
907 - `NVIDIA_NIM_MODEL`
908 - `OPENAI_API_KEY`
909 - `OPENAI_BASE_URL`
910 - `OPENAI_MODEL`
911 - `ATLASCLOUD_API_KEY`
912 - `ATLASCLOUD_BASE_URL`
913 - `ATLASCLOUD_MODEL`
914 - `WANJIE_ARK_API_KEY`, `WANJIE_API_KEY`, or `WANJIE_MAAS_API_KEY`
915 - `WANJIE_ARK_BASE_URL`, `WANJIE_BASE_URL`, or `WANJIE_MAAS_BASE_URL`
916 - `WANJIE_ARK_MODEL`, `WANJIE_MODEL`, or `WANJIE_MAAS_MODEL`
917 - `VOLCENGINE_API_KEY`, `VOLCENGINE_ARK_API_KEY`, or `ARK_API_KEY`
918 - `VOLCENGINE_BASE_URL`, `VOLCENGINE_ARK_BASE_URL`, or `ARK_BASE_URL`
919 - `VOLCENGINE_MODEL` or `VOLCENGINE_ARK_MODEL`
920 - `OPENROUTER_API_KEY`
921 - `OPENROUTER_BASE_URL`
922 - `OPENROUTER_MODEL`
923 - `XIAOMI_MIMO_TOKEN_PLAN_API_KEY`, `MIMO_TOKEN_PLAN_API_KEY`, `XIAOMI_MIMO_API_KEY`, `XIAOMI_API_KEY`, or `MIMO_API_KEY`
924 - `XIAOMI_MIMO_BASE_URL` or `MIMO_BASE_URL`
925 - `XIAOMI_MIMO_MODEL` or `MIMO_MODEL`
926 - `XIAOMI_MIMO_MODE` or `MIMO_MODE` (`token-plan-sgp`, `token-plan-cn`,
927 `token-plan-ams`, or `pay-as-you-go`)
928 - `NOVITA_API_KEY`
929 - `NOVITA_BASE_URL`
930 - `NOVITA_MODEL`
931 - `FIREWORKS_API_KEY`
932 - `FIREWORKS_BASE_URL`
933 - `FIREWORKS_MODEL`
934 - `HUGGINGFACE_API_KEY` or `HF_TOKEN` (`HF_TOKEN` is a fallback alias accepted when provider is `huggingface`)
935 - `HUGGINGFACE_BASE_URL` or `HF_BASE_URL`
936 - `HUGGINGFACE_MODEL` or `HF_MODEL`
937 - `SILICONFLOW_API_KEY`
938 - `SILICONFLOW_BASE_URL`
939 - `SILICONFLOW_MODEL`
940 - `ARCEE_API_KEY`
941 - `ARCEE_BASE_URL`
942 - `ARCEE_MODEL`
943 - `TOGETHER_API_KEY`
944 - `TOGETHER_BASE_URL`
945 - `TOGETHER_MODEL`
946 - `QIANFAN_API_KEY` or `BAIDU_QIANFAN_API_KEY`
947 - `QIANFAN_BASE_URL` or `BAIDU_QIANFAN_BASE_URL`
948 - `QIANFAN_MODEL` or `BAIDU_QIANFAN_MODEL`
949 - `OPENAI_CODEX_ACCESS_TOKEN` or `CODEX_ACCESS_TOKEN`
950 - `OPENAI_CODEX_BASE_URL` or `CODEX_BASE_URL`
951 - `OPENAI_CODEX_MODEL` or `CODEX_MODEL`
952 - `OPENAI_CODEX_ACCOUNT_ID` or `CODEX_ACCOUNT_ID`
953 - `ANTHROPIC_API_KEY`
954 - `ANTHROPIC_BASE_URL`
955 - `ANTHROPIC_MODEL`
956 - `ZAI_API_KEY` or `Z_AI_API_KEY`
957 - `ZAI_BASE_URL` or `Z_AI_BASE_URL`
958 - `ZAI_MODEL` or `Z_AI_MODEL`
959 - `STEPFUN_API_KEY` or `STEP_API_KEY`
960 - `STEPFUN_BASE_URL` or `STEP_BASE_URL`
961 - `STEPFUN_MODEL` or `STEP_MODEL`
962 - `MINIMAX_API_KEY`
963 - `MINIMAX_BASE_URL`
964 - `MINIMAX_MODEL`
965 - `DEEPINFRA_API_KEY` or `DEEPINFRA_TOKEN`
966 - `DEEPINFRA_BASE_URL`
967 - `DEEPINFRA_MODEL`
968 - `MOONSHOT_API_KEY` or `KIMI_API_KEY`
969 - `MOONSHOT_BASE_URL` or `KIMI_BASE_URL`
970 - `MOONSHOT_MODEL`, `KIMI_MODEL_NAME`, or `KIMI_MODEL`
971 - `SGLANG_BASE_URL`
972 - `SGLANG_MODEL`
973 - `SGLANG_API_KEY` (optional; many localhost SGLang servers do not require auth)
974 - `VLLM_BASE_URL`
975 - `VLLM_MODEL`
976 - `VLLM_API_KEY` (optional; many localhost vLLM servers do not require auth)
977 - `OLLAMA_BASE_URL`
978 - `OLLAMA_MODEL`
979 - `OLLAMA_API_KEY` (optional; many localhost Ollama servers do not require auth)
980 For every product-level `CODEWHALE_*` variable below, the matching legacy
981 `DEEPSEEK_*` name is still read as a compatibility fallback; when both are set,
982 the `CODEWHALE_*` value wins.
983
984 - `CODEWHALE_LOG_LEVEL` or `RUST_LOG` (`info`/`debug`/`trace` enables lightweight verbose logs)
985 - `CODEWHALE_SKILLS_DIR`
986 - `CODEWHALE_MCP_CONFIG`
987 - `CODEWHALE_NOTES_PATH`
988 - `CODEWHALE_MEMORY` (`1|on|true|yes|y|enabled` turns user memory on)
989 - `CODEWHALE_MEMORY_PATH`
990 - `CODEWHALE_TELEMETRY` / `DEEPSEEK_TELEMETRY` (legacy alias) — opt-in product
991 telemetry, off by default. Accepts `0|1|true|false|yes|no|on|off|enabled|
992 disabled`. An explicit "off" is a **floor**: it beats `--telemetry true` and
993 `telemetry = true` in config, and a value this list cannot read also resolves
994 to off, because a typo in a kill switch must never resolve to "on". See
995 [`TELEMETRY.md`](TELEMETRY.md).
996 - `CODEWHALE_TELEMETRY_ENDPOINT` / `DEEPSEEK_TELEMETRY_ENDPOINT` (legacy alias)
997 — `https://`, or plain `http://` only for loopback. Overrides the config file.
998 Unset selects the shipped default,
999 `https://telemetry.codewhale.net/v1/telemetry`; setting it to the **empty
1000 string** routes batches to a local dry-run file and contacts nobody. Either
1001 way it only decides where an already-enabled session sends — it cannot turn
1002 telemetry on.
1003 - `CODEWHALE_ALLOW_SHELL` (`1`/`true` enables)
1004 - `CODEWHALE_APPROVAL_POLICY` (`on-request|untrusted|never`)
1005 - `CODEWHALE_SANDBOX_MODE` (`read-only|workspace-write|danger-full-access|external-sandbox`)
1006 - `CODEWHALE_MANAGED_CONFIG_PATH`
1007 - `CODEWHALE_REQUIREMENTS_PATH`
1008 - `CODEWHALE_MAX_SUBAGENTS` (clamped to `1..=128`)
1009 - `CODEWHALE_TASKS_DIR` (runtime task queue/artifact storage, default
1010 `~/.codewhale/tasks`, with legacy `~/.deepseek/tasks` fallback when only the
1011 legacy directory exists)
1012 - `CODEWHALE_ALLOW_INSECURE_HTTP` (`1`/`true` allows non-local `http://` base URLs; default is reject)
1013 - `CODEWHALE_FORCE_HTTP1` (`1|true|yes|on` pins the HTTP client to HTTP/1.1, disabling HTTP/2; useful on Windows or behind proxies that mishandle long-lived H2 streams)
1014 - `CODEWHALE_HOME` (override the base data directory; defaults to `~/.codewhale`).
1015 If you previously exported `DEEPSEEK_HOME`, rename it to `CODEWHALE_HOME`;
1016 the old env var is not used for new Codewhale state paths.
1017 - `CODEWHALE_RELEASE_BASE_URL` (release asset mirror used by `codewhale update`
1018 and by TUI startup update checks when `[update].update_uri` is not set, or as
1019 a fallback when that configured URI cannot be fetched)
1020 - `CODEWHALE_AUTOMATIONS_DIR` (override the automations storage directory; uses
1021 `~/.codewhale/automations` by default, with legacy `~/.deepseek/automations`
1022 fallback when only the legacy directory exists)
1023 - `NO_ANIMATIONS` (`1|true|yes|on` forces `low_motion = true` and
1024 `fancy_animations = false` at startup, regardless of the saved
1025 settings; see [`docs/ACCESSIBILITY.md`](./ACCESSIBILITY.md)).
1026 - `SSL_CERT_FILE` — corporate-proxy / TLS-inspecting MITM users
1027 point this at a PEM bundle (or single DER cert) and the cert(s)
1028 get added alongside the platform's system trust store. Failures
1029 log a warning and continue — the existing system roots still
1030 apply.
1031
1032 ### Instruction sources (`instructions = [...]`, #454)
1033
1034 Add a list of additional system-prompt sources that get
1035 concatenated, in declared order, alongside the auto-loaded
1036 `AGENTS.md`:
1037
1038 ```toml
1039 instructions = [
1040 "./AGENTS.md",
1041 "~/.codewhale/global.md",
1042 "~/team/agents-shared.md",
1043 ]
1044 ```
1045
1046 Rules:
1047
1048 - Paths run through `expand_path` so `~` and env vars work.
1049 - Each file is capped at 100 KiB; oversized files are
1050 truncated with a `[…elided]` marker rather than skipped.
1051 - Missing files are skipped with a tracing warning so a stale
1052 entry doesn't fail the launch.
1053 - Only user-owned config, profiles, and managed config may set this array.
1054 Project config (`<workspace>/.codewhale/config.toml`, or legacy
1055 `<workspace>/.deepseek/config.toml`) ignores `instructions` so a cloned repo
1056 cannot choose arbitrary local files to place into the prompt.
1057
1058 ### Hooks
1059
1060 Hooks are a **TUI runtime feature**. They fire from the interactive TUI and
1061 the engine turn loop it drives; `codewhale exec`, the CLI subcommands, the
1062 app-server / ACP surfaces, and the `workflow` tool do not fire them.
1063
1064 [`docs/HOOKS.md`](HOOKS.md) is the authoritative reference for all eleven hook
1065 events — their firing points, environment variables, stdin payloads, timeout
1066 and background semantics, and which three of them can steer Codewhale. The
1067 sections below cover the configuration surface and the steering contracts in
1068 more depth.
1069
1070 Two contract points worth reading there before writing a hook:
1071
1072 - `background = true` means **submitted and never awaited**. The hook still
1073 gets the documented stdin payload and the same timeout, but it has no exit
1074 code and cannot steer.
1075 - A condition that references context its event never carries (an `exit_code`
1076 condition outside `tool_call_after` / `on_error`, a `mode` condition on
1077 `shell_env`, a tool condition on a non-tool event) is **rejected at load**,
1078 logged, and shown in `/hooks list`. It does not silently never match.
1079 Rejection is per entry, so a broken hook never drops another one that merely
1080 shares its `name` or is likewise unnamed.
1081
1082 ### `/hooks` listing
1083
1084 Run `/hooks` (or `/hooks list`) inside the TUI to see every
1085 configured lifecycle hook grouped by event, including each
1086 hook's name, command preview, effective timeout, and condition. When
1087 `[hooks].default_timeout_secs` is set it replaces every per-hook
1088 `timeout_secs`, and the listing shows that effective value and names the
1089 override rather than echoing the per-hook number. A
1090 `default_timeout_secs = 0` is rejected at load — it would expire every hook
1091 in the config immediately — so the override is ignored, per-hook
1092 `timeout_secs` applies, the listing shows that per-hook value with no
1093 override provenance, and the rejection appears under `configuration
1094 problems`. The
1095 `[hooks].enabled` flag's state is shown at the top so it's
1096 obvious when hooks are globally suppressed, and any entry rejected
1097 at load is listed under `configuration problems` with the reason.
1098 Hooks are configured under `[[hooks.hooks]]` entries — see
1099 [`docs/HOOKS.md`](HOOKS.md) for the full schema.
1100
1101 ### Mutable `message_submit` hooks
1102
1103 `message_submit` hooks run before a submitted message is added to
1104 history or sent to the model. Unlike observer-only lifecycle hooks,
1105 non-background `message_submit` hooks can replace or block the
1106 submitted text.
1107
1108 ```toml
1109 [[hooks.hooks]]
1110 event = "message_submit"
1111 command = "~/.codewhale/hooks/inject-context.sh"
1112 timeout_secs = 2
1113 continue_on_error = true
1114 ```
1115
1116 The hook receives JSON on stdin:
1117
1118 ```json
1119 {
1120 "event": "message_submit",
1121 "text": "original user text",
1122 "text_bytes": 18,
1123 "text_original_bytes": 18,
1124 "text_truncated": false,
1125 "session_id": "sess_12345678",
1126 "workspace": "/path/to/workspace",
1127 "mode": "agent",
1128 "model": "deepseek-chat",
1129 "total_tokens": 1234
1130 }
1131 ```
1132
1133 The entire serialized document is capped at 32 KiB. Codewhale retains the
1134 largest UTF-8-safe `text` prefix that fits after JSON escaping and bounded
1135 metadata, and the three `text_*` fields make truncation explicit. Immediate
1136 messages, restored queue entries, merged steers, and prior-hook replacements
1137 all cross this same serialization boundary.
1138
1139 If the hook exits `0` and prints JSON with a non-empty string `text` field,
1140 that value replaces the submitted text:
1141
1142 ```json
1143 { "text": "replacement user text" }
1144 ```
1145
1146 Exit `0` with empty stdout, or stdout JSON without `text`, leaves
1147 the current text unchanged. A JSON `text` field must not be empty;
1148 `{"text":""}` is treated as invalid stdout and ignored. Exit `2`
1149 blocks the submission before the turn starts; a structured `reason` field can
1150 provide the bounded, redacted status message shown in the TUI. Raw stdout,
1151 stderr, and process-error text are not copied into denial receipts.
1152 Other non-zero exits follow the hook's `continue_on_error` setting.
1153 Timeouts and spawn failures are also surfaced as transient TUI status
1154 messages when `continue_on_error = true` lets submission continue.
1155
1156 Multiple `message_submit` hooks run in config order, and each hook
1157 receives the text produced by the previous hook. Hooks marked
1158 `background = true` are observer-only and cannot transform or block
1159 the message — they still receive the same stdin payload and the same
1160 environment, they are simply never awaited. Existing environment
1161 variables remain available.
1162 `shell_env` hooks keep their existing `KEY=VALUE` stdout contract;
1163 JSON stdout contracts exist for `message_submit` (above) and
1164 `tool_call_before` (below).
1165
1166 ### `tool_call_before` decision hooks
1167
1168 `tool_call_before` hooks run before each tool call executes. In
1169 addition to the legacy hard deny (exit code `2`, which always wins
1170 regardless of stdout), a foreground hook may print a JSON decision on
1171 stdout with exit code `0`:
1172
1173 ```json
1174 {
1175 "decision": "allow" | "deny" | "ask",
1176 "reason": "human-readable explanation (used for deny)",
1177 "updatedInput": { "command": "ls -la" },
1178 "additionalContext": "text appended to the tool result for the model"
1179 }
1180 ```
1181
1182 All fields are optional. Empty stdout, non-JSON stdout, and JSON
1183 without a `decision` field behave exactly as before (allow). An
1184 unrecognized `decision` string logs a fixed warning without echoing the
1185 untrusted value and is treated as allow.
1186
1187 - `deny` blocks the tool; the model receives a permission-denied tool
1188 result containing `reason`.
1189 - `ask` forces the interactive approval prompt in Ask and Auto-Review even for
1190 tools that would otherwise auto-run. Full Access does not open tool-approval
1191 prompts, so hook `ask` does not downgrade that posture.
1192 - `updatedInput` must be a JSON object; it replaces the tool input
1193 before execution. When several hooks supply it, the last hook wins.
1194 - `additionalContext` is appended to the tool result sent back to the
1195 model as `[hook context] ...`. Multiple hooks' contexts are
1196 concatenated.
1197
1198 When multiple hooks match, precedence is deny > ask > allow. Hooks
1199 marked `background = true` cannot steer tool calls — they are
1200 submitted and never awaited, so they have no verdict to contribute.
1201
1202 A foreground hook that produced no verdict at all — it hit its timeout, the
1203 process could not be started, or a strict process exited non-zero without an
1204 explicit JSON decision — is not treated as permission. If
1205 *that* hook is configured with `continue_on_error = false`, the outcome
1206 denies the tool call and the denial names the hook and a bounded reason.
1207 Strictness is read off the hooks that actually matched this call, so a
1208 strict gate scoped to another tool cannot deny it, and a lenient hook's
1209 timeout does not deny merely because a strict hook exists elsewhere in
1210 config. Under the default `continue_on_error = true` the outcome is
1211 logged and the call proceeds.
1212
1213 `reason` and `additionalContext` are capped (2 000 characters per field,
1214 8 000 for the concatenated context of one call) and stripped of control
1215 characters before they reach the TUI or the model.
1216
1217 Example deny hook:
1218
1219 ```toml
1220 [[hooks.hooks]]
1221 event = "tool_call_before"
1222 command = '''echo '{"decision":"deny","reason":"blocked by project policy"}' '''
1223 condition = { type = "tool_name", name = "exec_shell" }
1224 ```
1225
1226 Example ask hook (force approval for every MCP tool):
1227
1228 ```toml
1229 [[hooks.hooks]]
1230 event = "tool_call_before"
1231 command = '''echo '{"decision":"ask"}' '''
1232 condition = { type = "tool_name", name = "mcp__*" }
1233 ```
1234
1235 Example input rewrite:
1236
1237 ```toml
1238 [[hooks.hooks]]
1239 event = "tool_call_before"
1240 command = "~/.codewhale/hooks/clamp-shell-timeout.sh"
1241 condition = { type = "tool_name", name = "exec_shell" }
1242 ```
1243
1244 where the script reads the hook context, then prints
1245 `{"updatedInput": {...}}` with the adjusted arguments.
1246
1247 `tool_name` conditions support `*` globs: `mcp__*` matches every MCP
1248 tool (e.g. `mcp__github__create_issue`) but not built-ins like
1249 `read_file`; exact names keep matching exactly. Other regex
1250 metacharacters in the pattern are matched literally.
1251
1252 ### Project-local hooks
1253
1254 Repositories can ship policy in `<workspace>/.codewhale/hooks.toml`,
1255 using the same shape as the `[hooks]` table (top-level fields plus
1256 `[[hooks]]` entries). Project hooks are executable shell
1257 configuration, so Codewhale only loads them after the workspace has
1258 been trusted in user-owned config through the trust prompt or a
1259 `[projects."<workspace>"] trust_level = "trusted"` entry. Session
1260 `/trust on` mode does not enable repo-supplied hooks by itself, and
1261 repo-local legacy markers such as `.deepseek/trusted` do not enable
1262 project hooks. Once trusted, project hooks are appended after global
1263 hooks from `config.toml`, so they run last and, for `updatedInput`,
1264 win ties. A malformed trusted project file logs a warning and startup
1265 falls back to global hooks only.
1266
1267 ```toml
1268 # .codewhale/hooks.toml
1269 [[hooks]]
1270 event = "tool_call_before"
1271 command = '''echo '{"decision":"deny","reason":"no shell in this repo"}' '''
1272 condition = { type = "tool_name", name = "exec_shell" }
1273 ```
1274
1275 ### Turn-end observer hooks
1276
1277 `turn_end` hooks observe the end of each model turn after post-turn
1278 state, usage totals, cost accounting, notifications, receipts, and
1279 queue recovery have been updated. They receive JSON on stdin and are
1280 observer-only: stdout is ignored, failures are logged as warnings, and
1281 the hook cannot block user input, mutate the transcript, or change the
1282 next queued follow-up.
1283
1284 Observer-only UI events share one 32-entry queue and two persistent workers;
1285 the terminal loop uses non-blocking submission and does not create a thread per
1286 event. A full queue or unavailable dispatcher drops that observer event and is
1287 kept as an event-specific error toast, independent of ordinary agent/turn
1288 status text.
1289
1290 ```toml
1291 [[hooks.hooks]]
1292 event = "turn_end"
1293 command = "~/.codewhale/hooks/turn-audit.sh"
1294 timeout_secs = 2
1295 continue_on_error = true
1296 ```
1297
1298 The payload includes common hook metadata plus post-turn accounting:
1299
1300 ```json
1301 {
1302 "event": "turn_end",
1303 "session_id": "sess_12345678",
1304 "workspace": "/path/to/workspace",
1305 "mode": "agent",
1306 "created_at": "2026-07-12T10:30:00+00:00",
1307 "model_backed": true,
1308 "provider": "deepseek",
1309 "model": "deepseek-chat",
1310 "billing_surface": null,
1311 "turn_id": "turn_12345678",
1312 "status": "completed",
1313 "error": null,
1314 "duration_ms": 1834,
1315 "usage": {
1316 "input_tokens": 1200,
1317 "output_tokens": 180,
1318 "prompt_cache_hit_tokens": 900,
1319 "prompt_cache_miss_tokens": 300,
1320 "prompt_cache_write_tokens": 0,
1321 "reasoning_tokens": null,
1322 "reasoning_replay_tokens": null
1323 },
1324 "totals": {
1325 "session_tokens": 1380,
1326 "conversation_tokens": 1380,
1327 "input_tokens": 1200,
1328 "output_tokens": 180
1329 },
1330 "tool_count": 2,
1331 "queued_message_count": 1,
1332 "stop_hook_active": false
1333 }
1334 ```
1335
1336 `created_at` anchors time-window pricing; `provider` and `model` identify the
1337 effective route used for model-backed turns. `billing_surface` is an optional,
1338 non-secret classification derived from the endpoint that actually served the
1339 turn. Recognized StepFun routes emit `stepfun-payg` or `stepfun-plan`; the raw
1340 base URL is never written to hook or runtime records. Runtime `TurnRecord`
1341 exports call the same field `effective_billing_surface`, which `scorecard`
1342 accepts as an alias. This keeps subscription quota separate from token-priced
1343 usage. Unrecognized and custom endpoints remain `null` and unpriced.
1344
1345 Shell-only lifecycle completions set `model_backed` to `false` and may report a
1346 `null` provider; offline scorecards exclude those records from model token and
1347 cost totals. Completion-only shell, manual-compaction, and purge events that do
1348 not have a matching `TurnStarted` retain the observer notification with a
1349 synthetic `lifecycle_<uuid>` turn id and the time the completion was observed.
1350
1351 For `interrupted` or `failed` turns, `status` reflects that terminal
1352 state and `error` carries the engine error string when one is available.
1353 `stop_hook_active` is reserved for future re-entry protection and is
1354 currently always `false`.
1355
1356 ### Sub-agent lifecycle hooks
1357
1358 `subagent_spawn` and `subagent_complete` hooks observe sub-agent lifecycle
1359 events. They receive bounded JSON metadata on stdin and are observer-only:
1360 hook failures are logged as warnings and do not block sub-agent scheduling,
1361 change prompts, or change results. For these observer events,
1362 `continue_on_error` has no effect: later matching hooks still run even when an
1363 earlier hook exits non-zero.
1364
1365 ```toml
1366 [[hooks.hooks]]
1367 event = "subagent_complete"
1368 command = "~/.codewhale/hooks/subagent-audit.sh"
1369 timeout_secs = 2
1370 continue_on_error = true
1371 ```
1372
1373 `subagent_spawn` receives:
1374
1375 ```json
1376 {
1377 "event": "subagent_spawn",
1378 "agent_id": "agent_12345678",
1379 "session_id": "sess_12345678",
1380 "workspace": "/path/to/workspace",
1381 "mode": "agent",
1382 "model": "deepseek-chat",
1383 "total_tokens": 1234,
1384 "prompt_preview": "bounded prompt preview",
1385 "prompt_truncated": false
1386 }
1387 ```
1388
1389 `subagent_complete` receives the same common fields plus terminal metadata:
1390
1391 ```json
1392 {
1393 "event": "subagent_complete",
1394 "agent_id": "agent_12345678",
1395 "session_id": "sess_12345678",
1396 "workspace": "/path/to/workspace",
1397 "mode": "agent",
1398 "model": "deepseek-chat",
1399 "total_tokens": 1234,
1400 "status": "completed",
1401 "result_preview": "bounded result preview",
1402 "result_truncated": false
1403 }
1404 ```
1405
1406 Previews are capped before delivery so lifecycle hooks do not receive full
1407 sub-agent prompts, transcripts, or unbounded results. Use the transcript handle
1408 returned by `agent` when full sub-agent details are needed.
1409
1410 ### Running-turn input
1411
1412 Composer shortcuts keep the same role throughout a session:
1413
1414 - **Enter** sends when idle and queues a next-turn follow-up while busy. The
1415 behavior does not change before versus after the provider's first token.
1416 - With an empty composer and queued follow-ups visible, **Enter** sends the
1417 oldest queued follow-up into the active turn now.
1418 - **Ctrl+Enter** (or **Cmd+Enter** when the terminal forwards it) explicitly
1419 steers the active turn. It sends normally when idle.
1420 - **Shift+Enter**, **Alt+Enter**, and **Ctrl+J** always insert a newline.
1421 - **Ctrl+G** and **Ctrl+S** only stash drafts; they never send or steer.
1422
1423 ### Composer stash (`/stash`, Ctrl+G / Ctrl+S)
1424
1425 Press **Ctrl+G** in the composer to park the current draft to
1426 `~/.codewhale/composer_stash.jsonl`. `/stash list` shows parked
1427 drafts with one-line previews and timestamps; `/stash pop`
1428 restores the most recently parked draft (LIFO); `/stash clear`
1429 wipes the file. Capped at 200 entries; multiline drafts round-trip intact.
1430 **Ctrl+S** remains an alias in terminals that forward it; Cursor and VS Code
1431 reserve Ctrl+S for Save, so Ctrl+G is the portable default.
1432
1433 ## Settings File (Persistent UI Preferences)
1434
1435 codewhale also stores user preferences in:
1436
1437 - `~/.codewhale/settings.toml` on new installs
1438 - `~/.deepseek/settings.toml` or the legacy platform config-dir
1439 `deepseek/settings.toml` when an existing settings file is present
1440
1441 Notable settings include `auto_compact`, which uses a model-aware default-on
1442 policy for known context windows up to the 1M-token V4 class. Automatic
1443 compaction runs before the active model limit and carries the compacted summary
1444 forward into the next request. The trigger defaults to
1445 `auto_compact_threshold_percent = 80`. Users who prefer manual continuity can
1446 persist `auto_compact = false`; manual `/compact` / Ctrl+L remains available.
1447 You can inspect or update these from the TUI with `/settings` and `/config`
1448 (interactive editor).
1449
1450 Common settings keys:
1451
1452 - `theme` (`system`, `terminal`, `dark`, `light`, `grayscale`,
1453 `catppuccin-mocha`, `tokyo-night`, `dracula`, `gruvbox-dark`, `claude`,
1454 `matrix`, `solarized-light`; default `system`): `system` follows terminal
1455 background detection, `dark`/`light` use the Codewhale Whale pair,
1456 `terminal` inherits the host terminal, `grayscale` is the low-opinion
1457 black/white theme, and the named community presets apply across the TUI.
1458 Aliases such as `whale`, `mono`, `black-white`, `tokyonight`, and `gruvbox`
1459 are accepted. In Whale, cobalt blue owns action/focus, seafoam owns live
1460 work, Signal Gold owns human decisions and the whale, coral owns warnings,
1461 rose owns danger, violet owns Operate, and green remains completed/verified.
1462 Text labels, markers, and motion policy carry the same states when color is
1463 unavailable; color is never the only cue.
1464 User-authored overlays live only at `~/.codewhale/themes/<name>.json` (or
1465 `$CODEWHALE_HOME/themes/<name>.json`) and are selected with
1466 `/theme custom:<name>`. The filename is a bounded slug, symlinks and files
1467 over 64 KiB are refused, colors must be `#RRGGBB`, and unknown fields fail
1468 validation. `/theme schema` prints the embedded JSON Schema and `/theme path`
1469 shows the exact directory. An overlay names one compiled `base` theme and
1470 changes only listed semantic colors; it cannot include or read another file.
1471 - `auto_compact` (on/off, model-aware default on for known context windows
1472 unless explicitly configured)
1473 - `auto_compact_threshold_percent` (10-100, default `80`): pre-send
1474 auto-compaction threshold used only when `auto_compact` is enabled.
1475 - `paste_burst_detection` (on/off, default on): fallback rapid-key paste
1476 detection for terminals that do not emit bracketed-paste events. This is
1477 independent of terminal bracketed-paste mode.
1478 - `work_surface_placement` (`top`, `left`, `right`, or `off`; default `top`):
1479 places the work bar — Tasks / To-do / Workers — above the transcript (the
1480 default top bar), in a side rail, or hides it entirely (`off`). Side
1481 choices fall back to the top layout on narrow terminals without changing
1482 the saved preference. Set it live with
1483 `/config work_surface_placement right --save` (or `left` / `top` / `off`).
1484 - `rail_panel` (`tasks`, `agents`, `context`, `pinned`; default `tasks`, alias
1485 key `rail`): which panel the work bar shows. Panel selection is orthogonal
1486 to placement. `tasks` is the full live work list (to-dos, then sub-agents);
1487 `agents` narrows to the sub-agent rows; `pinned` shows the goal plus the
1488 to-do checklist; `context` is a read-only session-facts list. In every
1489 panel except `context`, rows are selectable and clickable and open their
1490 detail surface. `Alt+!`/`Alt+@`/`Alt+#`/`Alt+$` switch panels live.
1491 - `work_surface_top_height` (2–16) and `work_surface_side_width` (26–80):
1492 ceilings for the top strip's height and a side rail's width. Both are
1493 normally persisted by dragging the divider rather than edited by hand; the
1494 strip still auto-fits its content below the ceiling.
1495 - `focus_texture` (`off`, `scrim`, or `grain`; default `off`): focus-context
1496 texture for modal views. `scrim` dims the already-rendered background
1497 outside the focused modal toward the theme surface; `grain` sprinkles
1498 sparse dots over blank cells there. The texture is static (no time
1499 component, so it is unaffected by `low_motion`), never writes over a cell
1500 that carries text, and preserves the 4.5:1 body-text contrast floor
1501 wherever both colors are resolvable. It is skipped entirely on frames
1502 below the ambient-life minimum size and when the focused modal already
1503 covers 90% or more of the frame. Set it live with
1504 `/config focus_texture scrim --save`.
1505 - `mention_menu_limit` (integer, default `128`): maximum number of
1506 `@`-mention popup candidates retained before the composer renders the
1507 visible window. The visible rows still depend on terminal height.
1508 - `mention_walk_depth` (integer, default `6`): maximum workspace depth for
1509 `@`-mention completion walks. Set to `0` for unlimited depth in deeply
1510 nested workspaces; keep the default in very large repos unless needed.
1511 - `mention_menu_behavior` (`fuzzy`, `browser`; default `fuzzy`): controls how
1512 `@`-mention completions are populated. `fuzzy` searches the workspace and
1513 applies mention frecency. `browser` lists only the immediate children of the
1514 currently typed directory segment in deterministic alphabetical order.
1515 - `show_thinking` (on/off)
1516 - `thinking_default_expanded` (on/off, default off): renders thinking blocks
1517 expanded initially when `show_thinking` is enabled. Space still toggles the
1518 selected block, so setting this to `true` inverts the default without
1519 removing per-block folding. This is useful in SSH/tmux environments where
1520 the Space binding may be intercepted.
1521 - `show_tool_details` (on/off)
1522 - `inline_diffs` (`full`, `summary`, or `off`; default `full`): controls the
1523 inline presentation of successful structured File mutations. `full` shows a
1524 bounded red/green diff and semantic statistics, `summary` keeps only the
1525 statistics, and `off` keeps the calm changed-file outcome. All three retain
1526 the exact applied change in the selected File receipt's Alt/Option+V detail.
1527 Failure and cancellation never render a successful diff. Save the choice
1528 with `/config inline_diffs <mode> --save`.
1529 - `locale` (`auto`, `en`, `ja`, `zh-Hans`, `zh-Hant`, `pt-BR`, `es-419`, `vi`,
1530 `ko`; default `auto`): UI chrome locale. `auto` checks `LC_ALL`,
1531 `LC_MESSAGES`, then `LANG`; unsupported locale selections resolve to English.
1532 `zh-Hant` is a shipped partial pack, so strings it does not yet provide fall
1533 back to English. The runtime also exposes the resolved locale in the system
1534 prompt as the fallback natural language for V4 reasoning and replies when the
1535 latest user message is ambiguous. Clear user language still takes priority;
1536 Chinese turns should produce Chinese `reasoning_content` and Chinese final
1537 replies even when the resolved locale is English.
1538 - `background_color` (`#RRGGBB`, `RRGGBB`, or `default`): optional main TUI
1539 background color applied to the root, header, transcript, and footer
1540 surfaces while preserving panel contrast.
1541 - `cost_currency` (`usd`, `cny`; default `usd`): currency used by the footer,
1542 context panel, `/cost`, `/tokens`, and long-turn notification summaries. The
1543 aliases `rmb` and `yuan` normalize to `cny`.
1544 - `default_mode` (`agent`, `plan`, or `operate`; legacy values are accepted for migration but are not live mode vocabulary)
1545 - `launch_screen` (`on`/`off`; default `off`): show the pre-session New/
1546 Resume/Worktree menu. With it off, Codewhale enters a new session directly;
1547 resume remains available in-session.
1548 - `sidebar_focus` (legacy, migration-only): the classic right sidebar this key
1549 configured was removed in the 0.9.4 rail unification. The key is still read
1550 once so old settings carry forward, then folds into the live keys:
1551 `pinned`/`work`/`plan`/`todos` become `rail_panel = "pinned"`,
1552 `agents`/`subagents` become `rail_panel = "agents"`, `context`/`session`
1553 become `rail_panel = "context"`, `tasks`/`auto` (the old default) become the
1554 `tasks` panel, `sessions` enables `sessions_rail`, and `hidden` turns the
1555 work bar off via `work_surface_placement = "off"`. An explicit `rail_panel`
1556 in the file always wins over the migrated value. Configure the work bar with
1557 `rail_panel` and `work_surface_placement`, not this key.
1558 - `sessions_rail` (`on`/`off`; default `off`): show the persistent Sessions
1559 rail in the sidebar panel stack. Rows list this workspace's recent
1560 non-archived sessions, newest first, with the active one marked; activating a
1561 row opens the session picker preselected on it (`/sessions open <id>`), so
1562 resume keeps its single implementation. Rows are projected from cached
1563 session metadata — the rail never reads a transcript per frame, and never
1564 contacts a provider.
1565 - `session_auto_resume` (`on`/`off`; default `off`): reattach to this
1566 workspace's most recent session when Codewhale starts. Off by default so
1567 plain `codewhale` keeps starting fresh. `--resume`, `--continue`, and
1568 `--fresh` always take precedence. When it is on, startup still refuses to
1569 resume a session that is archived, fails to load, or is recorded against a
1570 different workspace; each of those falls back to a fresh transcript and says
1571 which session was skipped and why. It applies to the interactive launch only
1572 — `codewhale "<prompt>"` and `codewhale exec` are never silently prefixed
1573 with a prior conversation.
1574 - `max_input_history` (number of submitted input history entries; cleared
1575 drafts are also kept locally for composer history search). Note the spelling:
1576 the serde field on disk is `max_input_history`
1577 (`crates/tui/src/settings.rs:426`, default 100). `max_history` is the key
1578 name accepted by `/config set` and `settings.set()` (`settings.rs:1388`), not
1579 a settings.toml key — writing `max_history` into the file is silently
1580 ignored.
1581 - `default_model` (model name override)
1582
1583 `/task digest` (alias `/tasks digest`) renders the canonical Work Graph
1584 operations and four-state To-do list as plain text, running work first. It
1585 reads the same snapshots as the styled Work surface and owns no parallel
1586 progress state.
1587
1588 Plan and Act are the everyday visible modes in the UI; Operate is an explicit
1589 preview entry while its Workflow control surface is still being built. Switch
1590 between them with `/mode`. For compatibility, older settings files with
1591 `default_mode = "normal"` still load as `agent`.
1592
1593 Localization scope is tracked in [LOCALIZATION.md](LOCALIZATION.md). The v0.7.6
1594 core pack covers high-visibility TUI chrome only; provider/tool schemas,
1595 personality prompts, and full documentation remain English unless explicitly
1596 translated later.
1597
1598 Readability semantics:
1599
1600 - Selection uses a unified style across transcript, composer menus, and modals.
1601 - Footer hints use a dedicated semantic role (`FOOTER_HINT`) so hint text stays readable across themes.
1602
1603 ### Token Quantities and Drivers
1604
1605 DeepSeek V4 prefix caching makes token labels matter. These quantities are kept
1606 separate:
1607
1608 | Quantity | Meaning | Allowed to drive |
1609 |---|---|---|
1610 | Active request input estimate | Conservative estimate of the next request's live system prompt and transcript payload. | Header/footer context percent, auto-compaction trigger, opt-in Flash seam trigger, and emergency overflow preflight. |
1611 | Reserved response headroom | The internal turn budget plus safety headroom. v0.8.16 keeps normal turns at `262144` reserved output tokens and adds `1024` safety tokens for context-window checks, even though V4 capability metadata reports the official `384000` max output. | Emergency overflow budget checks only. |
1612 | Cumulative API usage | Provider-reported input plus output tokens summed across completed API calls; multi-tool turns may count the same stable prefix more than once. | Session usage and approximate cost telemetry only. |
1613 | Prompt cache hit/miss | Provider cache telemetry for the most recent call when available. | Cache-hit display and cost estimation only; never compaction or seam triggers. |
1614 | Context percent | Active request input estimate divided by the model context window. | Display only; it mirrors the active-input basis used by context safeguards. |
1615 | Cost estimate | Approximate spend from provider usage and configured DeepSeek rates. | Display only. |
1616
1617 For known context-window models, including 1M-class V4 models, replacement
1618 compaction is enabled by default unless the user explicitly configures
1619 `auto_compact = false`. It fires at the active model's compaction threshold and
1620 replays the generated summary through the stable system prompt on the next
1621 request. Unknown model ids remain opt-in.
1622
1623 ### Command Migration Notes
1624
1625 If you are upgrading from older releases:
1626
1627 - Old: `/codewhale`
1628 New: `/links` (aliases: `/dashboard`, `/api`)
1629 - Old: `/set model deepseek-reasoner`
1630 New: `/config` and edit the `model` row to `deepseek-v4-pro` or `deepseek-v4-flash`
1631 - Old: visible `Normal` mode or `default_mode = "normal"`
1632 New: use `Agent` / `default_mode = "agent"`; legacy `normal` still maps to `agent`
1633 - Old: discover `/set` in slash UX/help
1634 New: use `/config` for editing and `/settings` for read-only inspection
1635
1636 ## Key Reference
1637
1638 ### Core keys (used by the TUI/engine)
1639
1640 - `provider` (string, optional): `deepseek` (default), `deepseek-anthropic`, `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `volcengine`, `openrouter`, `xiaomi-mimo`, `novita`, `fireworks`, `siliconflow`, `arcee`, `siliconflow-CN`, `moonshot`, `sglang`, `vllm`, `ollama`, `huggingface`, `together`, `qianfan`, `openai-codex`, `anthropic`, `openmodel`, `zai`, `stepfun`, `minimax`, `deepinfra`, `sakana`, `longcat`, `opencode-go`, `meta`, `telecomjs`, or `xai`. Legacy `deepseek-cn` configs are still accepted as an alias for `deepseek`; DeepSeek uses the same official host [`https://api.deepseek.com`](https://api-docs.deepseek.com/) worldwide. `deepseek-anthropic` targets DeepSeek's Anthropic Messages-compatible endpoint at `https://api.deepseek.com/anthropic` using `DEEPSEEK_API_KEY`; `nvidia-nim` targets NVIDIA's NIM-hosted DeepSeek endpoints through `https://integrate.api.nvidia.com/v1`; `openai` targets a generic OpenAI-compatible endpoint, defaulting to `https://api.openai.com/v1`; `atlascloud` targets AtlasCloud's OpenAI-compatible endpoint at `https://api.atlascloud.ai/v1`; `wanjie-ark` targets Wanjie Ark's OpenAI-compatible endpoint at `https://maas-openapi.wanjiedata.com/api/v1`; `volcengine` targets Volcengine Ark's OpenAI-compatible coding endpoint at `https://ark.cn-beijing.volces.com/api/coding/v3`; `openrouter` targets `https://openrouter.ai/api/v1`; `xiaomi-mimo` targets Xiaomi MiMo's OpenAI-compatible endpoint, using `https://token-plan-sgp.xiaomimimo.com/v1` by default for Token Plan keys (`tp-...`) and `https://api.xiaomimimo.com/v1` for pay-as-you-go keys. For Token Plan accounts outside the Singapore default, set `base_url` explicitly or use `mode = "token-plan-cn"` for China and `mode = "token-plan-ams"` for Europe/Amsterdam; `novita` targets `https://api.novita.ai/openai/v1`; `fireworks` targets `https://api.fireworks.ai/inference/v1`; `siliconflow` targets SiliconFlow, defaulting to `https://api.siliconflow.com/v1`; `arcee` targets Arcee AI's OpenAI-compatible endpoint at `https://api.arcee.ai/api/v1`; `siliconflow-CN` targets the SiliconFlow China regional endpoint through `[providers.siliconflow_cn]`; `moonshot` targets Moonshot/Kimi, defaulting to `https://api.moonshot.ai/v1`; `sglang` targets a self-hosted OpenAI-compatible endpoint, defaulting to `http://localhost:30000/v1`; `vllm` targets a self-hosted vLLM OpenAI-compatible endpoint, defaulting to `http://localhost:8000/v1`; `ollama` targets Ollama's OpenAI-compatible endpoint, defaulting to `http://localhost:11434/v1`; `huggingface` targets Hugging Face Inference Providers at `https://router.huggingface.co/v1`; `together` targets Together AI at `https://api.together.xyz/v1`; `qianfan` targets Baidu Qianfan at `https://api.baiduqianfan.ai/v1`; `openai-codex` targets ChatGPT/Codex OAuth; `anthropic` targets Claude's native Messages API; `openmodel` targets OpenModel's Anthropic-compatible Messages API at `https://api.openmodel.ai`; `zai` targets Z.ai at `https://api.z.ai/api/coding/paas/v4`; `stepfun` targets StepFun at `https://api.stepfun.ai/v1`; `minimax` targets MiniMax at `https://api.minimax.io/v1`; `deepinfra` targets DeepInfra at `https://api.deepinfra.com/v1/openai`; `sakana` targets Sakana AI Fugu at `https://api.sakana.ai/v1`; `longcat` targets Meituan LongCat at `https://api.longcat.chat/openai/v1`; `opencode-go` targets the subscription-backed OpenCode Go Chat Completions route at `https://opencode.ai/zen/go/v1`; `meta` targets Meta Model API; `telecomjs` targets TelecomJS TokenHub at `https://aigw.telecomjs.com/v1`; and `xai` targets xAI's API-key or OAuth route.
1641 - `opencode-zen` (string provider value): selects the model-aware OpenCode Zen gateway through `[providers.opencode_zen]`. The default base URL is `https://opencode.ai/zen/v1`, the default model is `gpt-5.5`, and credentials come from `api_key`, `OPENCODE_ZEN_API_KEY`, or fallback `OPENCODE_API_KEY`—never ChatGPT/Codex OAuth. `OPENCODE_ZEN_BASE_URL` and `OPENCODE_ZEN_MODEL` are accepted. The selected model is resolved through the curated Zen catalog: GPT uses Responses, Claude/Qwen use Anthropic Messages, and the documented DeepSeek/MiniMax/GLM/Kimi/Grok/free rows use Chat Completions. Gemini and unknown models fail closed because Codewhale has no proven supported wire contract for them. See the exact current model groups in [`PROVIDERS.md`](PROVIDERS.md#opencode-zen-protocol-catalog).
1642 - `minimax-anthropic` (string provider value): selects MiniMax's Anthropic-compatible Messages route through `[providers.minimax_anthropic]`. The default Base URL is `https://api.minimax.io/anthropic`; set `https://api.minimaxi.com/anthropic` for China. Keep the `/anthropic` suffix because Codewhale appends `/v1/messages`. The route uses `MINIMAX_API_KEY` and defaults to `MiniMax-M3`; `MiniMax-M2.7` is also registered. Official M3 input modalities are text, image, and video, with adaptive or disabled thinking. M2.7 is text-only and always keeps thinking enabled.
1643 - `api_key` (string, required for hosted providers): must be non-empty for DeepSeek/hosted providers (or set the provider API key env var). Self-hosted SGLang, vLLM, and Ollama can omit it.
1644 - `auth_mode` (string, optional provider-table key): selects a provider-specific authentication contract. Kimi Code membership uses `auth_mode = "api_key"` (or omit the field), a key created in the [Kimi Code console](https://www.kimi.com/code/console), `base_url = "https://api.kimi.com/coding/v1"`, and bare `model = "k3"` for K3. Codewhale gives that route a safe 262,144-token baseline; set `context_window = 1048576` only when the Kimi Code plan includes 1M access (Allegretto and above). `k3[1m]` is a Claude Code-only convention, not an API model ID, and Codewhale rejects it instead of silently changing the wire model or assuming an entitlement. `model = "kimi-for-coding"` remains the valid K2.7 compatibility route available to all Kimi Code members. Legacy `auth_mode = "kimi_oauth"` fails closed with API-key guidance and never probes, reads, refreshes, or rewrites `kimi_cli`/`kimi_code_cli` credential files. First-class OAuth requires Codewhale's own vendor-registered client identity and remains tracked in #4417.
1645 - `base_url` (string, optional): defaults to `https://api.deepseek.com/beta` for DeepSeek's OpenAI-compatible Chat Completions API, including legacy `provider = "deepseek-cn"` configs. Other defaults are `https://api.deepseek.com/anthropic` for `deepseek-anthropic`, `https://integrate.api.nvidia.com/v1` for `nvidia-nim`, `https://api.openai.com/v1` for `openai`, `https://api.atlascloud.ai/v1` for `atlascloud`, `https://maas-openapi.wanjiedata.com/api/v1` for `wanjie-ark`, `https://ark.cn-beijing.volces.com/api/coding/v3` for `volcengine`, `https://openrouter.ai/api/v1` for `openrouter`, `https://token-plan-sgp.xiaomimimo.com/v1` for `xiaomi-mimo` when the API key starts with `tp-...` and `https://api.xiaomimimo.com/v1` otherwise, `https://api.novita.ai/openai/v1` for `novita`, `https://api.fireworks.ai/inference/v1` for `fireworks`, `https://api.siliconflow.com/v1` for `siliconflow`, `https://api.siliconflow.cn/v1` for `siliconflow-CN`, `https://api.arcee.ai/api/v1` for `arcee`, `https://api.moonshot.ai/v1` for `moonshot`, `https://api.minimax.io/v1` for `minimax`, `https://api.openmodel.ai` for `openmodel`, `https://api.z.ai/api/coding/paas/v4` for `zai`, `https://api.stepfun.ai/v1` for `stepfun`, `https://api.deepinfra.com/v1/openai` for `deepinfra`, `https://api.sakana.ai/v1` for `sakana`, `https://router.huggingface.co/v1` for `huggingface`, `https://api.together.xyz/v1` for `together`, `https://api.baiduqianfan.ai/v1` for `qianfan`, `https://chatgpt.com/backend-api` for `openai-codex`, `https://api.anthropic.com` for `anthropic`, `http://localhost:30000/v1` for `sglang`, `http://localhost:8000/v1` for `vllm`, and `http://localhost:11434/v1` for `ollama`. Set `base_url = "https://token-plan-cn.xiaomimimo.com/v1"` for China-region Xiaomi MiMo Token Plan accounts or `base_url = "https://token-plan-ams.xiaomimimo.com/v1"` for Europe/Amsterdam accounts. Set `https://api.deepseek.com` or `https://api.deepseek.com/v1` explicitly to opt out of DeepSeek beta features.
1646 - `telecomjs` base URL and catalog: `[providers.telecomjs]` defaults to `https://aigw.telecomjs.com/v1`; `TELECOMJS_BASE_URL` overrides it. With `TELECOMJS_API_KEY`, `/models` refreshes a key-scoped catalog without mixing rows into another provider.
1647 - `context_window` (integer, optional provider-table key): override the total context window for the active `[providers.<name>]` route when an OpenAI-compatible gateway, hosted model alias, or self-hosted runtime has a different limit than Codewhale's static model table. For example, `[providers.openai] context_window = 1000000` lets an OpenAI-compatible DashScope/Qwen route budget against a 1M-token window instead of the conservative fallback. For Kimi Code K3, keep `model = "k3"` and set `[providers.moonshot] context_window = 1048576` only when the membership plan includes 1M access; otherwise omit it to retain the 262,144-token safe baseline. The value must be greater than 0 and affects prompt context notes, compaction thresholds, context-pressure checks, and request output caps. Full resolution order, and how to see which rung produced the current window: [Context length (context window)](#context-length-context-window).
1648 - `path_suffix` (string, optional provider-table key): override the chat-completions path for OpenAI-compatible gateways that do not serve `/v1/chat/completions`. For example, `[providers.openai] path_suffix = "/chat/completions"` sends chat requests to the unversioned base URL plus `/chat/completions`; `models` and `beta/*` requests keep their normal routing.
1649 - `reasoning_stream_style` (string, optional provider-table key): override how streaming reasoning is separated from answer text for the active provider route. Use `separate_field` for `reasoning_content` / `reasoning` deltas, `inline_tags` for gateways that stream `<think>...</think>` inside `delta.content`, or `none` to render incoming content exactly as answer text.
1650 - `[providers.<name>.auth]` (table, optional): provider-scoped auth source metadata. `source = "command"` stores a command argv plus optional `timeout_ms`; `source = "secret"` stores a `secret_id`. This slice lets provider readiness, `/provider`, and doctor JSON report the auth source class without exposing command argv output or secret values; executing commands and resolving external secret material is handled by the follow-up resolver work.
1651 - `insecure_skip_tls_verify` (bool, optional provider-table key): legacy compatibility key, disabled by default. When true on the active provider table, provider clients reject the configuration instead of skipping TLS certificate verification. Use `SSL_CERT_FILE` for corporate or private CA bundles; `codewhale doctor` reports stale uses of this setting.
1652 - `default_text_model` (string, optional): defaults to `deepseek-v4-pro` for DeepSeek, `deepseek-anthropic`, and generic OpenAI-compatible endpoints, `deepseek-ai/deepseek-v4-pro` for NVIDIA NIM, `deepseek-ai/deepseek-v4-flash` for AtlasCloud, `deepseek-reasoner` for Wanjie Ark, `DeepSeek-V4-Pro` for Volcengine Ark, `deepseek/deepseek-v4-pro` for OpenRouter and Novita, `mimo-v2.5-pro` for Xiaomi MiMo, `accounts/fireworks/models/deepseek-v4-pro` for Fireworks, `deepseek-ai/DeepSeek-V4-Pro` for SiliconFlow and DeepInfra, `trinity-large-thinking` for Arcee AI, `kimi-k2.7-code` for Moonshot, `MiniMax-M3` for MiniMax, `GLM-5.2` for Z.ai, `step-3.7-flash` for StepFun, `ernie-4.0-turbo-8k` for Qianfan, `fugu` for Sakana AI, `deepseek-ai/DeepSeek-V4-Pro` for SGLang/vLLM, and `deepseek-v4-flash` for Ollama. Hugging Face and Together AI both default to `deepseek-ai/DeepSeek-V4-Pro`; `openai-codex` defaults to `gpt-5.5`; `anthropic` defaults to `claude-sonnet-4-6`; `openmodel` defaults to `deepseek-v4-flash`. Current public DeepSeek IDs are `deepseek-v4-pro` and `deepseek-v4-flash`, both with 1M context windows, 384K max output, and thinking mode enabled by default. DeepSeek retires `deepseek-chat` and `deepseek-reasoner` on July 24, 2026; direct first-party routes migrate both to `deepseek-v4-flash`, with omitted reasoning settings preserving their former non-thinking (`off`) and thinking (`high`) intent. Explicit `reasoning_effort` wins, and provider-owned ids on Wanjie Ark, aggregators, self-hosted runtimes, and custom endpoints are not globally rewritten. SiliconFlow retains its own mapping: `deepseek-reasoner` and `deepseek-r1` select its Pro model while `deepseek-chat` and `deepseek-v3` select Flash. Provider-specific mappings translate `deepseek-v4-pro` / `deepseek-v4-flash` to each provider's model ID where supported. OpenRouter also recognizes recent large IDs such as `arcee-ai/trinity-large-thinking`, `minimax/minimax-m3`, `minimax/minimax-m2.7`, `xiaomi/mimo-v2.5-pro`, `qwen/qwen3.6-flash`, `qwen/qwen3.6-35b-a3b`, `qwen/qwen3.6-max-preview`, `qwen/qwen3.6-27b`, `qwen/qwen3.6-plus`, `qwen/qwen3.7-max`, `google/gemma-4-31b-it`, `moonshotai/kimi-k2.7-code`, `moonshotai/kimi-k2.6`, `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free`, and `nvidia/nemotron-3-ultra-550b-a55b`; direct Arcee uses bare IDs such as `trinity-large-thinking` and `trinity-large-preview`; direct Moonshot recognizes `kimi-k3`, `kimi-k2.7-code`, and `kimi-k2.6`. The exact Kimi Code endpoint recognizes bare `k3` for K3 and `kimi-for-coding` for K2.7; those membership IDs are distinct from the direct Moonshot IDs and are never rewritten across routes. Direct MiniMax recognizes `MiniMax-M3` and the documented M2.x chat model IDs; direct Z.ai recognizes `GLM-5.2`, `GLM-5.1`, `GLM-5.3`, and `GLM-5-Turbo`, and OpenRouter recognizes the matching `z-ai/glm-5.1`, `z-ai/glm-5.2`, `z-ai/glm-5.3`, and `z-ai/glm-5-turbo` IDs — `GLM-5.3` / `z-ai/glm-5.3` are recognized ids only. Z.ai had not released GLM-5.3 as of 2026-08-03, so those routes fail upstream until it ships; they inherit their catalog metadata from `GLM-5.2` pending official Z.ai release metadata, carry no price, and do not change the Z.ai default; direct Sakana recognizes `fugu` and `fugu-ultra-20260615`; direct Xiaomi MiMo recognizes chat IDs `mimo-v2.5-pro`, `mimo-v2.5-pro-ultraspeed`, and `mimo-v2.5`, while TTS IDs are selected through `codewhale speech` / `tts`. Generic `openai`, `atlascloud`, `wanjie-ark`, `xiaomi-mimo`, `arcee`, `moonshot`, `minimax`, `openmodel`, `zai`, `stepfun`, `qianfan`, `sakana`, and Ollama model IDs are passed through unchanged after known aliases are normalized. OpenRouter and SiliconFlow provider configs with a custom `base_url` also preserve explicit model values, which lets OpenAI-compatible gateways accept bare model IDs. Use `/models` or `codewhale models` to discover live IDs from your configured endpoint. `CODEWHALE_MODEL` overrides this for a single process; `DEEPSEEK_MODEL` is the legacy alias.
1653 - TelecomJS uses `deepseek-v4-pro` only as a conservative pre-refresh fallback. Once its key-scoped `/models` catalog is available, the picker uses those live rows; Codewhale omits unsupported reasoning request fields on this route.
1654 - `reasoning_effort` (string, optional): `off`, `low`, `medium`, `high`, `max`, `xhigh`, or `ultracode`; defaults to the configured UI tier. DeepSeek Platform receives top-level `thinking` / `reasoning_effort` fields. Direct Moonshot `kimi-k3` on exact `https://api.moonshot.ai/v1` is always-thinking and receives only top-level `reasoning_effort = "low" | "high" | "max"`; `off` normalizes to `low`, and `medium` to `high`. Kimi Code membership `k3` on exact `https://api.kimi.com/coding/v1` instead receives nested `thinking.effort`, and its `off` setting also normalizes to enabled `low`. Normal dispatched `auto` uses Codewhale's auto-reasoning selector and sends a concrete route-normalized tier; only an omitted reasoning setting leaves the provider default in control. Neighboring gateways and model/endpoint combinations retain the generic Moonshot contract. OpenAI Codex normalizes stale `off` to `low` and sends `max` / `ultracode` as Responses `xhigh`. Z.ai receives documented `thinking` controls and treats enabled thinking as the GLM coding high/max lane. NVIDIA NIM receives equivalent settings through `chat_template_kwargs`.
1655 - `verbosity` (string, optional): `normal` or `concise`. `normal` keeps the
1656 default conversational prompt. `concise` appends a prompt discipline block
1657 for direct, low-chatter output; CLI noninteractive commands (`exec` and
1658 `eval`) default to `concise` unless config/env/CLI overrides it.
1659 Override per process with `CODEWHALE_VERBOSITY` or the legacy
1660 `DEEPSEEK_VERBOSITY` alias.
1661 - `telemetry` (bool, optional): opt-in product telemetry, **`false` by
1662 default**. Setting it to `true` is only half of the switch: nothing is
1663 collected unless the first-run notice has also been answered with "Enable" on
1664 this machine, so a `telemetry = true` written before 0.9.4 stays inert. An
1665 explicit `false` here is the *opt-out* — it deletes the random install id,
1666 truncates every buffered event, and leaves a tombstone that every later run
1667 re-asserts for as long as the key says `false` — while the unset default
1668 simply never collects. It is also a floor: `--telemetry true` and
1669 `CODEWHALE_TELEMETRY=1` both lose to it, and turning telemetry back on means
1670 writing `true` here. Override per process with `CODEWHALE_TELEMETRY` (legacy
1671 alias `DEEPSEEK_TELEMETRY`), where an explicit "off" is a hard floor that
1672 beats both this key and `--telemetry true` — but is a *kill switch*, not an
1673 opt-out: it stops the run and erases nothing, so a harness disabling
1674 telemetry for one command never discards the machine owner's install id or
1675 dry-run records. A repo-local `.codewhale/config.toml` cannot set it. Full
1676 schema and red lines:
1677 [`TELEMETRY.md`](TELEMETRY.md).
1678 - `telemetry_endpoint` (string, optional): where batches are POSTed. Leaving it
1679 unset selects the shipped default,
1680 **`https://telemetry.codewhale.net/v1/telemetry`** — the first-party ingest
1681 service described in [`TELEMETRY.md`](TELEMETRY.md), whose source is in
1682 `telemetry-ingest/`. This key decides only *where* an enabled session sends;
1683 it cannot turn telemetry on, and it is read only after `telemetry` above
1684 resolved true and the first-run notice was answered with "Enable". Setting it
1685 to the **empty string** is how you stay enabled and contact nobody: each batch
1686 is then written to `$CODEWHALE_HOME/telemetry/dryrun.jsonl` and no HTTP client
1687 is constructed at all, so you can read exactly what would have been sent. Any
1688 other value replaces the default outright. `https://` is required; plain
1689 `http://` is accepted only for loopback hosts, and there is no environment
1690 variable that overrides that refusal. A rejected endpoint turns telemetry off
1691 for the run rather than falling back to plaintext or to the default. Override
1692 per process with `CODEWHALE_TELEMETRY_ENDPOINT` (legacy alias
1693 `DEEPSEEK_TELEMETRY_ENDPOINT`), where an empty value means the same "contact
1694 nobody". A repo-local `.codewhale/config.toml` cannot set it.
1695 - `allow_shell` (bool, optional): in interactive TUI Agent sessions, omitting
1696 this keeps shell tools available with approval prompts; setting it to `false`
1697 hides shell tools. Headless, durable-task, and other noninteractive profiles
1698 keep the conservative omitted-field default and require `allow_shell = true`
1699 to expose shell. Plan mode always hides shell; Full Access enables shell and
1700 auto-approval.
1701 - `approval_policy` (string, optional): `on-request`, `untrusted`, or `never`. Runtime `approval_mode` editing in `/config` also accepts `on-request` and `untrusted` aliases.
1702 - `sandbox_mode` (string, optional): `read-only`, `workspace-write`, `danger-full-access`, `external-sandbox`.
1703 Platform support is not identical. macOS uses Seatbelt when its runtime
1704 probe succeeds. Linux uses bubblewrap only when `prefer_bwrap = true` and
1705 `/usr/bin/bwrap` is executable; without that opt-in it reports no OS command
1706 sandbox. Windows does not currently advertise an OS sandbox; its planned helper contract starts
1707 with process-tree containment only and must not be described as read-only
1708 filesystem isolation, workspace-write enforcement, network blocking,
1709 registry isolation, or AppContainer isolation until those are implemented.
1710 - The cross-layer relationship between mode admission, hooks, registered tool
1711 requirements, typed rules, auto-review, repo law, human approval, and the
1712 execution sandbox is defined in
1713 [Authorization order](AUTHORIZATION_ORDER.md).
1714 - `permissions.toml` (sibling file, optional): typed permission rule records
1715 loaded next to `config.toml`, for example `~/.codewhale/permissions.toml`.
1716 This active user file is the only permission-rule source today; project
1717 config overlays do not load a project-local `permissions.toml`. A rule's
1718 optional `workspace` field is its repository scope, not a second source.
1719 Manually authored `[[rules]]` entries accept `tool`, optional `command` or
1720 `path`, optional absolute `workspace`, optional `command_exact = true`, and
1721 optional `action = "deny" | "ask" | "allow"`; omitted `action` defaults to
1722 `"ask"`. `workspace` limits a rule to that repository, while
1723 `command_exact = true` changes a command rule from the historical
1724 arity-aware prefix match to a complete-command match. `deny` blocks matching
1725 invocations before mode-based
1726 approval handling, `allow` skips approval for matching invocations, and
1727 `ask` forces approval only in modes that can prompt. Outside the TUI
1728 auto-approve path, a matching `ask` rule under `approval_policy = "never"`
1729 is rejected because no prompt can be shown. In Full Access / auto-approval sessions,
1730 `ask` rules do not downgrade the session into prompting or blocking; explicit
1731 `deny` rules still block according to the current execution-policy logic.
1732
1733 In a supported approval card, press `S` to allow the request once and append
1734 exact `action = "ask"` rules to this file. For eligible safe requests, choose
1735 **Always allow this exact rule in this repo** (shortcut `P`) to append an
1736 `action = "allow"` rule with the current absolute `workspace` scope.
1737 Remembered shell grants set `command_exact = true`, so later commands with
1738 extra arguments do not inherit the grant. File and patch grants retain the
1739 exact workspace-relative paths produced by the existing validation path.
1740 Supported saves are intentionally narrow:
1741 `exec_shell` stores the exact approved command string; `write_file` and
1742 `edit_file` store the exact workspace-relative file path; `apply_patch`
1743 stores one exact workspace-relative `path` rule per validated touched file
1744 from apply-patch preflight. Existing exec command matching remains
1745 arity-aware for manually authored prefix rules; approval-card allow grants
1746 use complete-command matching. File paths are normalized to the same
1747 workspace-relative form used by runtime matching.
1748
1749 `read_file` rules can still be authored manually when you want future reads
1750 of a specific path to ask, allow, or deny, but the approval UI does not save
1751 `read_file` rules. Commands classified as requiring approval or dangerous,
1752 critical approval cards, and repo-law prompts cannot save allow grants and
1753 continue to require review.
1754
1755 `/permissions` (or `/permissions list`) is the narrow rule-management
1756 surface. It lists each numbered rule with the active user-file source, its
1757 exact effective matcher (tool-wide, command prefix, exact command, or exact
1758 normalized path), global or repository scope, and whether that scope
1759 applies in the current workspace. `/config ask-rules` remains a compatibility
1760 entry to the same list.
1761
1762 Deletion is review-gated: `/permissions remove <number>` only previews the
1763 selected rule and prints a confirmation command. That command carries an
1764 opaque token bound to the exact file bytes and rule index; if another writer
1765 changes `permissions.toml`, confirmation fails instead of deleting a rule
1766 that moved into the old position. Confirmed removal and approval-card appends
1767 share the adjacent `permissions.toml.lock`, preserve unrelated TOML comments
1768 and formatting, and atomically replace the file. The running TUI reloads the
1769 user ruleset without clearing session-only approvals.
1770
1771 This editor intentionally does not create or rewrite rules, persist deny
1772 choices from approval cards, expand globs, or create broad
1773 directory/recursive rules. Author those supported exact/prefix records
1774 manually when needed.
1775 - `[[hotbar]]` (array of tables, optional): user-owned 1-8 slot bindings for
1776 the TUI hotbar. Each entry has `slot`, `action`, and optional `label`.
1777 Omitting `hotbar` uses the built-in default eight slots. Setting
1778 `hotbar = []` disables all default slots. When one or more `[[hotbar]]`
1779 tables are present, that list replaces the defaults; missing slots stay
1780 empty. Invalid slots outside `1..=8` are skipped with a warning, duplicate
1781 slots use the later entry, and unknown action IDs are kept so the UI can show
1782 a disabled/unknown cell instead of silently deleting user config. Trusted
1783 user config, profiles, and managed config replace the whole list; project
1784 overlays cannot change hotbar bindings. Setup or wizard flows that persist
1785 hotbar bindings write this same schema to the resolved `~/.codewhale/config.toml`
1786 path, preserving legacy `~/.deepseek/config.toml` only when that fallback file
1787 is already the active config.
1788
1789 ```toml
1790 [[hotbar]]
1791 slot = 1
1792 action = "mode.plan"
1793 label = "Plan"
1794
1795 [[hotbar]]
1796 slot = 2
1797 action = "session.compact"
1798 ```
1799 - `[auto_review]` (table, optional): deterministic tool-call review policy.
1800 This layer sits on top of the existing permission posture; it can hold or block a
1801 tool call, but it is not an auto-push, auto-merge, or hosted review service.
1802 Block rules are checked first, then the built-in safety floor, then allow
1803 rules. In Ask and Auto-Review, a safety hold opens approval; in Full Access
1804 or a non-interactive `never` posture it fails closed as a hard block. The
1805 safety floor still covers publish-like actions and destructive
1806 background/headless actions even if an allow rule matches.
1807
1808 ```toml
1809 [auto_review]
1810 natural_language_guidance = "Prefer read-only inspection until the user asks for writes."
1811
1812 [[auto_review.allow]]
1813 id = "read-only-inspection"
1814 action_kind = "read"
1815 reason = "Read-only inspection is safe to run automatically."
1816
1817 [[auto_review.block]]
1818 id = "no-release-publish"
1819 action_kind = "publish"
1820 reason = "Release and publish actions require maintainer review."
1821 ```
1822
1823 Rule matchers are exact `tool`, `action_kind`, and/or
1824 `text_contains` against the current user intent. At least one matcher is
1825 required. `action_kind` accepts `read`, `write`, `shell`, `network`, `git`,
1826 `mcp_read`, `mcp_action`, `browser`, `secret`, `publish`, `destructive`, or
1827 `unknown`; invalid names fail config validation instead of becoming broad
1828 rules. `natural_language_guidance` is recorded on the runtime policy and audit
1829 event, but deterministic rules and the built-in safety floor are the enforced
1830 behavior in current builds.
1831
1832 Auto-review decisions emit `tool.auto_review_decision` audit events when tool
1833 audit logging is enabled. Future PreToolUse/PostToolUse hooks can add
1834 observer input around this layer, but the configured auto-review policy is
1835 evaluated before a tool call is allowed to proceed.
1836 - `managed_config_path` (string, optional): managed config file loaded after user/env config.
1837 - `requirements_path` (string, optional): requirements file used to enforce allowed approval/sandbox values.
1838 - `max_subagents` (int, optional): defaults to `64` and is clamped to `1..=128`.
1839 - `subagents.*` (optional compatibility table): per-Fleet-role model defaults
1840 for `agent`. Explicit tool `model` values win, then role
1841 overrides, then the parent runtime model. Supported convenience keys are
1842 `default_model`, `worker_model`, `scout_model`, `planner_model`,
1843 `reviewer_model`, `custom_model`, `max_concurrent`, `max_admitted`,
1844 `launch_concurrency`, `token_budget`, `api_timeout_secs`, and
1845 `heartbeat_timeout_secs`. The v0.9.x keys `explorer_model`, `awaiter_model`,
1846 and `review_model` remain accepted as aliases. The `[subagents]
1847 max_concurrent` value overrides
1848 top-level `max_subagents` and is also clamped to `1..=128`. `[subagents]
1849 max_admitted` (aliases: `max_total`, `admission_limit`) is the bounded total
1850 of queued plus running sub-agents; it defaults to `1024`
1851 (`MAX_SUBAGENT_ADMISSION`, `crates/tui/src/config/subagent_limits.rs:21`,
1852 applied at `config.rs:6400`) so high-fanout turns can queue and drain while
1853 runtime launch pressure remains bounded, and is clamped to
1854 `max_concurrent..=1024`. `[subagents]
1855 launch_concurrency` sets how many direct children start at once before the
1856 rest queue for a launch slot; it defaults to the resolved `max_subagents` cap
1857 and is clamped to `1..=max_subagents` (the deprecated
1858 `interactive_max_launch` key is accepted as an alias, with the new key
1859 winning when both are set). `[subagents] token_budget` is an optional
1860 aggregate token ceiling for each root `agent` run and its descendants; unset
1861 or `0` preserves unlimited legacy behavior. `[subagents] api_timeout_secs`
1862 controls the per-step API timeout for sub-agent model calls and is clamped to
1863 `1..=3600`, with `0` or unset preserving the 600 second default; a timed-out
1864 attempt is retried with exponential backoff (up to 5 retries) before the
1865 step interrupts with a preserved checkpoint.
1866 `[subagents] heartbeat_timeout_secs` controls stale running agent cleanup,
1867 defaults to `300`, and is clamped to `30..=3600` while staying above the
1868 resolved API timeout. `[subagents.providers.<provider>]` accepts the same
1869 fanout, depth, budget, and timeout knobs (`enabled`, `max_concurrent`,
1870 `max_admitted`, `launch_concurrency`, `max_depth`, `token_budget`,
1871 `api_timeout_secs`, `heartbeat_timeout_secs`) and inherits the global
1872 `[subagents]` value for any key you omit. Provider keys accept canonical
1873 names such as `deepseek`, `zai`, `openrouter`, `anthropic`, plus convenience
1874 aliases such as `glm` for Z.ai and `deepseek_api` for direct DeepSeek:
1875
1876 ```toml
1877 [subagents]
1878 max_concurrent = 20
1879 launch_concurrency = 20
1880 max_admitted = 200
1881 max_depth = 6
1882
1883 [subagents.providers.deepseek]
1884 max_concurrent = 20
1885 launch_concurrency = 20
1886 max_admitted = 200
1887
1888 [subagents.providers.glm]
1889 max_concurrent = 4
1890 launch_concurrency = 3
1891 max_admitted = 12
1892 max_depth = 2
1893
1894 [subagents.providers.openrouter]
1895 max_concurrent = 5
1896 launch_concurrency = 3
1897 max_admitted = 20
1898 ```
1899
1900 `/config subagents status` prints both global values and the active
1901 provider's resolved profile so rate-limit tuning is visible in the TUI.
1902 `[subagents.models]` accepts lower-case Fleet role keys such as `worker`,
1903 `scout`, `planner`, `reviewer`, `builder`, and `verifier`; legacy type keys
1904 remain accepted during v0.9.x. Values are validated
1905 against the active provider at spawn time; direct DeepSeek requires DeepSeek
1906 IDs, while OpenAI-compatible/custom provider routes pass explicit model IDs
1907 through to that provider. To route a child to a different provider than the
1908 parent session, save a Fleet/AgentProfile with explicit `provider` and
1909 `model` fields (including user-named custom providers such as `lm-studio`)
1910 and call `agent(profile: "...")`; see [SUBAGENTS.md](SUBAGENTS.md).
1911 - `skills_dir` (string, optional): defaults to `~/.codewhale/skills` (each skill is
1912 a directory containing `SKILL.md`). Workspace-local `.agents/skills` or
1913 `./skills` are preferred when present; the runtime also discovers global
1914 agentskills.io-compatible `~/.agents/skills` and the broader Claude-ecosystem
1915 `~/.claude/skills`. First launch installs versioned bundled skills for common
1916 workflows including skill creation, delegation, MCP/plugin scaffolding,
1917 documents, presentations, spreadsheets, PDFs, and Feishu/Lark. Only
1918 CodeWhale-owned roots (`<workspace>/.codewhale/skills` and
1919 `~/.codewhale/skills`) are writable install/import targets; compatible harness
1920 roots stay read-only. Bare `/skills` opens the Skills Manager (owned-only,
1921 zero network). See [SKILLS.md](SKILLS.md) for the manager, audit statuses,
1922 provenance markers, and mutation rules, and
1923 [CLAUDE_PLUGIN_COMPAT.md](CLAUDE_PLUGIN_COMPAT.md) for the supported boundary
1924 between portable `SKILL.md` bundles and Claude Code plugin runtimes.
1925 - `[skills].scan_codewhale_only` (bool, default `false`): when `true`, session
1926 skill discovery ignores cross-tool roots such as `.claude/skills`,
1927 `.opencode/skills`, `.cursor/skills`, and `~/.agents/skills`. Codewhale still
1928 scans `<workspace>/.codewhale/skills`, `~/.codewhale/skills`, and any explicit
1929 `skills_dir` override. The Skills Manager can still toggle a local compatible
1930 audit scan independently of this runtime knob — see [SKILLS.md](SKILLS.md).
1931 - `[skills].registry_url` / `[skills].max_install_size_bytes` (optional): used by
1932 `/skills --remote`, `/skills suggest <task>`, `/skills sync`, and `/skill
1933 install|update`. The default manager open path does not contact the registry.
1934 - `[verifier].enabled` (bool, default `false`): enables automatic
1935 claim-of-done verifier preview once that runtime trigger is active. The
1936 manual `run_verifiers` tool is still available when this is false.
1937 - `[verifier].verdict_policy` (string, default `"hunt"`): maps verifier
1938 `pass` / `partial` / `fail` into the goal verdict vocabulary
1939 `hunted` / `wounded` / `escaped`. `"hunt"` is the only shipped policy today;
1940 unknown values are rejected so future policies can be added deliberately.
1941 - `mcp_config_path` (string, optional): defaults to `~/.codewhale/mcp.json`, with
1942 legacy `~/.deepseek/mcp.json` fallback when the Codewhale path is absent.
1943 It is visible in `/config` and can be changed from the TUI. The new path is
1944 used immediately by `/mcp`, but rebuilding the model-visible MCP tool pool
1945 requires restarting the TUI.
1946 - `notes_path` (string, optional): defaults to `~/.codewhale/notes.txt`, with
1947 legacy `~/.deepseek/notes.txt` fallback when the Codewhale path is absent, and
1948 is used by the model-visible `note` tool.
1949 - `[memory].enabled` (bool, optional): defaults to `false`. When `true`,
1950 the TUI loads the user memory file into a `<user_memory>` prompt block,
1951 enables `# foo` quick-capture in the composer, surfaces the `/memory`
1952 slash command, and registers the `remember` tool. The same toggle is
1953 available via `DEEPSEEK_MEMORY=on`.
1954 - `memory_path` (string, optional): anchors the native memory store. The
1955 configured filename is **not** the file that is written. Under the Native
1956 backend (the only backend) the store is re-rooted to
1957 `<parent-of-memory_path>/memory/global/MEMORY.md` — so the default
1958 `~/.codewhale/memory.md` yields `~/.codewhale/memory/global/MEMORY.md`
1959 (plus workspace-scoped files and a rebuildable SQLite FTS5 index). See
1960 [`MEMORY.md`](MEMORY.md) for the full feature surface (`# foo` composer
1961 prefix, `/memory` slash command, `remember` tool, opt-in toggle).
1962 - `snapshots.*` (optional): side-git workspace snapshots for file rollback:
1963 - `[snapshots].enabled` (bool, default `true`)
1964 - `[snapshots].max_age_days` (int, default `7`)
1965 - snapshots live under
1966 `~/.codewhale/snapshots/<project_hash>/<worktree_hash>/.git`, with legacy
1967 `~/.deepseek/snapshots/...` fallback when only the legacy state exists, and
1968 never use the workspace's own `.git` directory
1969 - `context.*` (optional):
1970 - `[context].enabled` (bool, default `false`)
1971 - `[context].project_pack` (bool, default `false`): include a deterministic
1972 project context pack (a large pretty-printed directory listing) in the
1973 stable prompt prefix (#4781). Useful for weak tool-calling models; the
1974 model can rebuild the same information with one `File` call.
1975 - The former seam-manager keys (`verbatim_window_turns`, `l1_threshold`,
1976 `l2_threshold`, `l3_threshold`, `seam_model`) are **ignored** — parsed
1977 for backward compatibility but read nowhere since 2026-07-23.
1978 - `retry.*` (optional): retry/backoff settings for API requests:
1979 - `[retry].enabled` (bool, default `true`)
1980 - `[retry].max_retries` (int, default `3`)
1981 - `[retry].initial_delay` (float seconds, default `1.0`)
1982 - `[retry].max_delay` (float seconds, default `60.0`)
1983 - `[retry].exponential_base` (float, default `2.0`)
1984 - `[notifications].method` (string, optional): `auto`, `osc9`, `bel`, or
1985 `off`. Defaults to `auto`. The TUI fires this on completed (successful)
1986 turns whose elapsed time meets `threshold_secs`; failed and cancelled
1987 turns are silent. `auto` resolves to `osc9` for `iTerm.app`, `Ghostty`,
1988 and `WezTerm` (detected via `$TERM_PROGRAM`). Otherwise the fallback is
1989 `bel`; on Windows the BEL path is routed through `MessageBeep(MB_OK)`.
1990 - `[notifications].threshold_secs` (int, optional): defaults to `30`.
1991 Only completed turns whose elapsed time meets or exceeds this fire a
1992 notification.
1993 - `[notifications].include_summary` (bool, optional): defaults to
1994 `false`. When `true`, the notification body includes the elapsed
1995 duration and the turn's cost in the configured display currency.
1996 - `[notifications].completion_sound` (string, optional): `off`, `beep`,
1997 `bell`, or `file`. Defaults to `beep`. `file` plays the WAV path from
1998 `[notifications].sound_file` on Windows.
1999 - `[notifications].sound_file` (path, optional): path to a custom WAV file
2000 used when `completion_sound = "file"`.
2001 - `[notifications].quiet` (bool, optional): defaults to `false`. Quiet
2002 mode — suppresses every desktop notification (all categories, all
2003 delivery methods) and the paired `event_sound` cues, without changing
2004 `method` or the per-category switches. The turn-completion chime
2005 (`completion_sound`) is governed separately.
2006 - `[notifications.events]` (table, optional): per-category
2007 desktop-notification switches; every key defaults to `true`. Keys:
2008 `turn-complete`, `subagent-terminal`, `approval-needed`,
2009 `input-needed`, `elevation-needed`, `model-notify`. A disabled
2010 category is suppressed on every delivery mechanism (OSC 9, Kitty,
2011 Ghostty, BEL, macOS Notification Center).
2012 - `[notifications.event_sound]` (table, optional): opt-in, deterministic
2013 per-event sound cues. Keys: `enabled` (bool, default `false`), `events`
2014 (array of kebab-case event names, default `["turn-complete",
2015 "approval-needed"]`), `min_interval_ms` (int, default `2000`), `quiet`
2016 (bool, default `false`). See "Event sound cues" below.
2017 - `tui.alternate_screen` (string, optional): `auto`, `always`, or `never`. This is retained for config compatibility, but interactive sessions now always use the TUI-owned alternate screen so host terminal scrollback cannot hijack the viewport.
2018 - `tui.mouse_capture` (bool, optional, default `true` on non-Windows terminals and on Windows Terminal/ConEmu/Cmder when the alternate screen is active; `false` on legacy Windows console and inside JetBrains JediTerm — PyCharm/IDEA/CLion/etc. — where mouse-event escapes leak into the input stream as garbled text, see #878 / #898): enable internal mouse scrolling, transcript selection, right-click context actions, and transcript scrollbar dragging. TUI-owned drag selection copies only transcript text, removes visual wrap-column line breaks from paragraphs, and keeps selection scoped to the transcript pane. Set this to `false` or run with `--no-mouse-capture` for raw terminal selection; set it to `true` or run with `--mouse-capture` to opt in anywhere it's defaulted off. On raw terminal selection, especially on legacy Windows console or when mouse capture is disabled, selection may cross the right sidebar and include visual wraps because the terminal, not the TUI, owns the selection.
2019 - `tui.terminal_probe_timeout_ms` (int, optional, default `500`): startup terminal-mode probe timeout in milliseconds. Values are clamped to `100..=5000`; timeout emits a warning and aborts startup instead of hanging indefinitely.
2020 - `tui.stream_chunk_timeout_secs` (int, optional, default `900`): per-SSE-chunk idle timeout for streamed model responses. Slow local or compatible servers can raise this with `/config stream_chunk_timeout_secs <seconds>`; `0` maps to the default and explicit values must be `1..=3600`. The legacy `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` env var is still honored when this key is omitted.
2021 - `tui.header_items` (array of strings, optional, default `[]`): opt-in header chips. Set `header_items = ["tokens"]` under `[tui]` to show the session input, cache-hit, and output token counts. Narrow terminals elide the optional chip; wide terminals show it alongside context utilization.
2022 - `tui.osc8_links` (bool, optional, default on for macOS/Linux, off for Windows): emit OSC 8 escape sequences around URLs in transcript output so supporting terminals (iTerm2, Terminal.app 13+, Ghostty, Kitty, WezTerm, Alacritty, recent gnome-terminal/konsole) can open them with the terminal's link gesture—usually Cmd-click on macOS and Ctrl-click on Linux/Windows. Terminals without OSC 8 support render the plain label and ignore the escape. The escapes are emitted out-of-band (not inside buffer cells), so column corruption is not a concern; set `false` only for terminals that misrender the OSC 8 terminator itself. Windows legacy consoles default off; opt in with `true`.
2023 - `hooks` (optional): lifecycle hooks configuration (see `config.example.toml`).
2024 - `features.*` (optional): feature flag overrides (see below).
2025
2026 ### Workspace notes
2027
2028 `/note` manages a simple notes file in the current workspace at
2029 `.deepseek/notes.md`. Existing `/note <text>` usage still appends a note.
2030 The management forms are:
2031
2032 | Command | Action |
2033 |---|---|
2034 | `/note <text>` | Append a note (legacy shorthand) |
2035 | `/note add <text>` | Append a note explicitly |
2036 | `/note list` | List notes with temporary 1-based numbers |
2037 | `/note show <n>` | Show the full note at number `n` |
2038 | `/note edit <n> <text>` | Replace note `n` with new text |
2039 | `/note remove <n>` | Delete note `n`; `rm` and `delete` are aliases |
2040 | `/note clear` | Empty the workspace notes file |
2041 | `/note path` | Show the resolved workspace notes path |
2042
2043 The numbers shown by `/note list` are not stored in the file; they are derived
2044 from the current order each time notes are read. This keeps the file format
2045 compatible with the existing `---`-separated notes.
2046
2047 ### User memory
2048
2049 User memory is split across one top-level path setting and one opt-in
2050 toggle table:
2051
2052 ```toml
2053 # Anchors the store only — actual writes go to
2054 # ~/.codewhale/memory/global/MEMORY.md (see MEMORY.md).
2055 memory_path = "~/.codewhale/memory.md"
2056
2057 [memory]
2058 enabled = true
2059 ```
2060
2061 Notes:
2062
2063 - `memory_path` stays at the top level beside `notes_path` and
2064 `skills_dir`; it is not nested under `[memory]`.
2065 - The configured path is an **anchor**: its parent directory gains
2066 `memory/global/MEMORY.md`, workspace-scoped files, and `index.db`.
2067 Pointing `memory_path` at the native layout path itself would double-nest
2068 (`…/memory/global/memory/global/MEMORY.md`); keep the legacy-style
2069 anchor filename.
2070 - `DEEPSEEK_MEMORY_PATH` overrides the anchor path from the environment.
2071 - `DEEPSEEK_MEMORY=on` (also `1`, `true`, `yes`, `y`, or `enabled`)
2072 flips the feature on without editing `config.toml`.
2073 - The feature is inert when disabled: no file is injected, `# foo`
2074 falls through to normal message submission, and the model does not
2075 see the `remember` tool.
2076 - See [`MEMORY.md`](MEMORY.md) for examples and the full `/memory`
2077 command surface.
2078
2079 ### Goal loop (`[goal]`)
2080
2081 Operate-mode goals run to their completion gate: the only terminal stops are a
2082 verified completion, a blocked report, or an exhausted configured token budget
2083 (#5052). The decision core also accepts an optional time budget, but the TUI
2084 does not currently configure or expose one. A configurable safety backstop
2085 still halts a pathological loop that never emits a terminal signal:
2086
2087 ```toml
2088 [goal]
2089 # Safety backstop on automatic goal continuation passes.
2090 # Default: 100. Set 0 to disable the backstop entirely so only
2091 # completion/blocked or budget exhaustion stop the run.
2092 max_continuations = 100
2093 ```
2094
2095 When the backstop fires, the goal pauses with a status message naming
2096 `[goal] max_continuations` and a warning is logged; resume the goal after
2097 inspecting progress, or raise/disable the backstop.
2098
2099 ### Notifications
2100
2101 The TUI can emit a desktop notification (OSC 9 escape or plain BEL) when a turn **completes successfully** and took longer than a threshold, so you can tab away while a long task runs. Failed or cancelled turns are intentionally silent — the notification is a "your task is ready" cue, not a generic ping. Configuration lives under `[notifications]`:
2102
2103 ```toml
2104 [notifications]
2105 method = "auto" # auto | osc9 | bel | off
2106 threshold_secs = 30 # only notify when the turn took >= this many seconds
2107 include_summary = false # include elapsed time + cost in the notification body
2108 completion_sound = "beep" # off | beep | bell | file
2109 sound_file = "E:\\google\\downloads\\notify.wav" # for completion_sound = "file"
2110 quiet = false # true suppresses every desktop notification
2111
2112 [notifications.events] # per-category switches; all default to true
2113 turn-complete = true # an agent turn finished
2114 subagent-terminal = true # a sub-agent reached a terminal status
2115 approval-needed = true # a tool call is blocked on your approval
2116 input-needed = true # the agent asked a question and is blocked
2117 elevation-needed = true # the sandbox denied a tool and needs a decision
2118 model-notify = true # the model called the `notify` tool
2119 ```
2120
2121 `quiet = true` is the one-flag "stop interrupting me" switch: it silences
2122 every category on every delivery mechanism while leaving the rest of your
2123 notification configuration intact, so flipping it back restores your exact
2124 previous policy. `[notifications.events]` disables single categories the
2125 same way — a disabled category is suppressed at the emission path, so it
2126 cannot leak through one specific protocol. A suppressed notification also
2127 suppresses its paired `[notifications.event_sound]` cue (no orphaned bells
2128 for events you turned off); the turn-completion chime (`completion_sound`)
2129 is governed separately.
2130
2131 Method semantics:
2132
2133 - `auto` (default) — picks `osc9` for `iTerm.app`, `Ghostty`, and `WezTerm` (detected via `$TERM_PROGRAM`). Otherwise it falls back to `bel`; on Windows that BEL path is routed through `MessageBeep(MB_OK)`.
2134 - `osc9` — emit `\x1b]9;<msg>\x07`. Inside tmux the sequence is wrapped in DCS passthrough so it reaches the outer terminal.
2135 - `bel` — emit a single `\x07` byte. Use this on Windows only if you actively want the chime back.
2136 - `off` — disable post-turn notifications entirely.
2137
2138 Windows users who run inside a known OSC-9 terminal (e.g. WezTerm on Windows) keep getting OSC-9 notifications. Set `method = "off"` to disable threshold-based desktop notifications entirely.
2139
2140 `completion_sound = "file"` is for Windows users who want a per-application
2141 completion sound without changing the global Windows sound scheme. It plays the
2142 configured WAV `sound_file` asynchronously via the native Windows audio API.
2143
2144 #### Event sound cues
2145
2146 `[notifications.event_sound]` is an opt-in, deterministic policy that emits a
2147 terminal-bell-level cue when specific notification events fire (approval
2148 prompts, blocked-on-input, sub-agent completion, and so on). It is **off by
2149 default**; with `enabled = false` nothing is emitted, which is the
2150 platform-safe no-op fallback.
2151
2152 ```toml
2153 [notifications.event_sound]
2154 enabled = false # default: off (opt-in)
2155 events = ["turn-complete", "approval-needed"] # default allow-list
2156 min_interval_ms = 2000 # per-event rate limit
2157 quiet = false # true silences everything without editing the allow-list
2158 ```
2159
2160 The cue table is fixed — cues are functional BEL-based signals, not
2161 designed-for-pleasantness audio, and every cue is one or two `\x07` bytes
2162 (inert on terminals that ignore BEL, so this is a platform-safe no-op
2163 everywhere):
2164
2165 | Event | Cue |
2166 |---|---|
2167 | `turn-complete` | BEL (`\x07`) |
2168 | `subagent-terminal` | BEL (`\x07`) |
2169 | `approval-needed` | double BEL (`\x07\x07`) |
2170 | `input-needed` | BEL (`\x07`) |
2171 | `elevation-needed` | double BEL (`\x07\x07`) |
2172 | `model-notify` | BEL (`\x07`) |
2173
2174 Decision order: disabled → quiet mode → event not in `events` → `turn-complete`
2175 deferred to the `completion_sound` channel when that is active (so the two
2176 never double-ding) → per-event rate limit (`min_interval_ms` since the last
2177 play of that event) → play. Unknown strings in `events` are ignored.
2178
2179 #### What a notification can contain
2180
2181 A desktop notification is a glance surface: on macOS it can appear on the
2182 lock screen, and on every platform it is visible to anyone near the machine.
2183 Codewhale therefore builds notifications from a typed payload with a fixed
2184 per-event disclosure policy rather than from whatever text was on hand:
2185
2186 | Event | Shown | Never shown |
2187 |---|---|---|
2188 | Turn complete | status line (+ elapsed/cost when `include_summary`), preview of the assistant's reply | — |
2189 | Sub-agent finished | status line, agent id, preview of the child's summary line | — |
2190 | Approval needed | the tool name | the tool description, the command, the arguments |
2191 | Input needed | "Answer the question in the terminal to continue" | the question |
2192 | Sandbox elevation needed | the tool name and the denial reason | the command |
2193 | `notify` tool | model-supplied title and body | — |
2194
2195 Every field is capped (80 characters for the status line, 120 for the
2196 identifier, 200 for the preview), stripped of control bytes and escape
2197 sequences, and passed through a redactor that replaces credential-shaped
2198 strings with `[redacted]`, reduces absolute local paths to `…/basename`,
2199 and replaces raw tool JSON with `[details hidden]`. The redactor is
2200 deliberately over-eager: an unbroken 40-character run has no word
2201 structure, so it is redacted even when it is not a secret.
2202
2203 #### macOS: why the banner says "Script Editor"
2204
2205 On macOS terminals that provide no notification escape of their own —
2206 Apple Terminal, the VS Code and JetBrains embedded terminals, plain tmux
2207 without `LC_TERMINAL` — `method = "auto"` falls back to `osascript`'s
2208 `display notification`. That command posts on behalf of the *bundled*
2209 host process, and `/usr/bin/osascript` is unbundled, so macOS attributes
2210 the banner to `com.apple.ScriptEditor2`. That attribution supplies the
2211 Script Editor icon and owns the System Settings → Notifications entry
2212 (alert style, previews, Do Not Disturb). `display notification` has no
2213 icon parameter, so this cannot be fixed from the notification code; it
2214 needs Codewhale to ship a real `.app` bundle. Tracked in
2215 [#4834](https://github.com/Hmbown/CodeWhale/issues/4834). In the meantime,
2216 iTerm2, WezTerm, Ghostty, and kitty are matched first and use their own
2217 notification protocols, and `method = "osc9"` / `"bel"` / `"off"` opt out
2218 of the `osascript` path explicitly.
2219
2220 ## Tool Catalog
2221
2222 Codewhale loads a small core native tool catalog by default and leaves less
2223 common native tools discoverable through ToolSearch. To keep specific native
2224 tools loaded on every request, add them to `[tools].always_load`:
2225
2226 ```toml
2227 [tools]
2228 always_load = ["Git", "notify"]
2229 ```
2230
2231 ## Feature Flags
2232
2233 Feature flags live under the `[features]` table and are merged across profiles.
2234 Defaults are enabled for built-in tooling, so you only need to set entries you
2235 want to force on or off.
2236
2237 ```toml
2238 [features]
2239 shell_tool = true
2240 subagents = true
2241 web_search = true # enables deferred Web; the flag name is retained for config compatibility
2242 apply_patch = true
2243 mcp = true
2244 exec_policy = true
2245 ```
2246
2247 You can also override features for a single run:
2248
2249 - `codewhale-tui --enable web_search`
2250 - `codewhale-tui --disable subagents`
2251
2252 Use `codewhale-tui features list` to inspect known flags and their effective state.
2253 The native `/config` view also includes a read-only **Experimental** section
2254 for experimental feature flags. It shows each flag's effective enabled/disabled
2255 state and whether that state comes from the default or a configured override.
2256 Change feature flags in `[features]` or with `--enable` / `--disable`; the
2257 `/config` section is an audit surface, not a stability promise. Goal and
2258 Workflow preview rows may appear there as reserved entries until those workflows
2259 graduate behind real gated flags.
2260
2261 ## Web Search Provider
2262
2263 `web_search` uses DuckDuckGo by default and does not require an API key. The
2264 DuckDuckGo path keeps a Bing fallback when DDG returns a bot challenge or no
2265 parseable results. Bing remains selectable for users who explicitly want it,
2266 and Tavily, Bocha, Metaso, SearXNG, Baidu, Volcengine, or Sofya can be selected
2267 when an API-backed provider is preferred.
2268
2269 Configured API providers are attempted first. Runtime failure or an empty
2270 result visibly degrades through DuckDuckGo and then Bing; the structured search
2271 receipt records every hop. Missing configuration and network-policy denials
2272 fail closed without sending the query to another provider.
2273
2274 For a private/internal search service that serves DuckDuckGo-compatible HTML,
2275 keep `provider = "duckduckgo"` and set `base_url`; Codewhale appends the `q`
2276 query parameter to that endpoint and applies network policy to its host.
2277 Custom endpoints do not fall back to public Bing. `CODEWHALE_SEARCH_BASE_URL`
2278 can override this per process; `DEEPSEEK_SEARCH_BASE_URL` remains accepted as
2279 the legacy alias.
2280
2281 **SearXNG** ([docs](https://docs.searxng.org/dev/search_api.html)) uses the
2282 configured instance's JSON API. Set `provider = "searxng"` and
2283 `base_url = "https://your-searxng.example"`; Codewhale calls
2284 `/search?q=...&format=json`. Codewhale does not use a public SearXNG instance
2285 by default because public instances often disable JSON output or rate-limit API
2286 traffic.
2287
2288 **Metaso** ([metaso.cn](https://metaso.cn)) requires a user-supplied key. Set
2289 `METASO_API_KEY` or `[search] api_key`; Codewhale does not ship a shared key.
2290
2291 **Baidu** uses Baidu AI Search at
2292 `https://qianfan.baidubce.com/v2/ai_search/web_search`. Set
2293 `BAIDU_SEARCH_API_KEY` or `[search] api_key`. This is a search-tool backend
2294 only; it does not add a Baidu model provider.
2295
2296 **Sofya** ([sofya.co](https://sofya.co)) returns full extracted page content
2297 rather than snippets. Set `[search] api_key` to your `ay_live_...` key, or the
2298 `SOFYA_API_KEY` env var. This is a search-tool backend only; it does not add a
2299 Sofya model provider.
2300
2301 ```toml
2302 [search]
2303 provider = "searxng" # duckduckgo | bing | tavily | bocha | metaso | searxng | baidu | volcengine | sofya
2304 # base_url = "https://search.example/" # optional with provider = "duckduckgo"; required with "searxng"
2305 # api_key = "YOUR_KEY" # required for tavily, bocha, metaso, baidu, volcengine, and sofya; unused by searxng
2306 ```
2307
2308 ## Local Media Attachments
2309
2310 Use `@path/to/file` in the composer to add local text file or directory context
2311 to the next message. Use `/attach <path>` for local image/video media paths, or
2312 `Ctrl+V` to attach an image from a local clipboard or an explicitly forwarded
2313 X11/Wayland clipboard. SSH terminal paste without a forwarded graphical display
2314 is text-only; use the local terminal's paste command (`Cmd+V` on macOS or
2315 `Ctrl+Shift+V` on Linux/Windows), and use `/attach <path>` for remote image
2316 files. OpenSSH loopback X11 displays are detected automatically. For an
2317 explicitly forwarded Wayland or non-loopback X11 display, set
2318 `CODEWHALE_SSH_CLIPBOARD=graphical`; set it to `terminal` to force terminal
2319 transfer instead of an ambient remote display. DeepSeek's public Chat
2320 Completions API currently accepts text message
2321 content, so media attachments are sent as explicit local path references instead
2322 of native image/video payloads.
2323 Attachment rows appear above the composer before submit; move to the start of
2324 the composer, press `↑` to select an attachment row, then press `Backspace` or
2325 `Delete` to remove it without editing the sample text by hand.
2326
2327 ## Managed Configuration and Requirements
2328
2329 codewhale supports a policy layering model:
2330
2331 1. user config + profile + env overrides
2332 2. managed config (if present)
2333 3. requirements validation (if present)
2334
2335 By default on Unix:
2336 - managed config: `/etc/deepseek/managed_config.toml`
2337 - requirements: `/etc/deepseek/requirements.toml`
2338
2339 Requirements file shape:
2340
2341 ```toml
2342 allowed_approval_policies = ["on-request", "untrusted", "never"]
2343 allowed_sandbox_modes = ["read-only", "workspace-write"]
2344 ```
2345
2346 If configured values violate requirements, startup fails with a descriptive error.
2347
2348 ## Notes On `codewhale-tui doctor`
2349
2350 `codewhale-tui doctor` follows the same config resolution rules as the rest of the
2351 TUI. That means `--config`, `CODEWHALE_CONFIG_PATH`, and the legacy
2352 `DEEPSEEK_CONFIG_PATH` are respected, and MCP/skills
2353 checks use the resolved `mcp_config_path` / `skills_dir` (including env overrides).
2354
2355 To bootstrap missing MCP/skills paths, run `codewhale-tui setup --all`. You can
2356 also run `codewhale-tui setup --skills --local` to create a workspace-local
2357 `./skills` dir.
2358
2359 Both plain `codewhale-tui doctor` and `doctor --json` are structural and
2360 offline by default. They do not check the release service, hosted provider
2361 APIs, local provider endpoints, or MCP processes, and they do not load a
2362 workspace credential `.env`. Use `--check-updates`,
2363 `--probe-api`, `--probe-local`, or `--probe-mcp` to opt into the corresponding
2364 live boundary; `--probe-local` may start a desktop-managed service such as
2365 Ollama. Only the explicit API/local probe paths may load workspace credential
2366 `.env` values. Live flags conflict with `--json`, so machine-readable doctor
2367 output is always offline. Top-level keys include `version`, `paths`, `secret_backend`,
2368 `config_path`, `config_present`, `workspace`, `api_key.source`,
2369 `api_key.availability`, `base_url`,
2370 `default_text_model`, `mcp`, `skills`, `tools`, `plugins`, `sandbox`,
2371 `platform`, `api_connectivity`, and `capability`. CI consumers should rely on
2372 `api_key.source` (`config_declared`/`env_declared`/`external_auth_declared`/
2373 `secret_store_unprobed`/`secret_store_unavailable`/`oauth_unprobed`/
2374 `external_consent`/`none`/`local_runtime`/`unknown`) and
2375 `api_key.availability`
2376 (`present`/`not_required`/`not_probed`/`unavailable`/`unknown`) rather than parsing the
2377 human-readable `doctor` text. Source is declaration metadata, not proof that a
2378 credential exists or works. Only a non-empty, non-sentinel literal config value
2379 is structurally `present`; no-auth and local routes are `not_required`. Environment,
2380 external-auth, OAuth, consent, and secret-store declarations remain `not_probed`
2381 and cannot make structural Setup or Fleet readiness true. A secret-store sentinel
2382 on a named/custom endpoint that is prohibited from using the shared store is
2383 `secret_store_unavailable`/`unavailable`, while `unknown` remains reserved for
2384 the absence of a supported structural conclusion. Exact and whitespace-wrapped
2385 legacy sentinels are never treated as literal credentials. The structural
2386 loader still honors safe environment routing/model/policy fields, but it never
2387 materializes environment HTTP headers, sandbox API keys, or search API keys;
2388 only an explicit API/local probe switches to the normal credential-loading
2389 path. An opted-in update check also emits only typed generic failures: untrusted
2390 release metadata and transport errors are not echoed.
2391
2392 If configuration loading or validation fails, `doctor --json` returns nonzero
2393 and prints a bounded JSON error envelope with
2394 `status = "error"` and `error.kind = "config_validation"`. It does not emit a
2395 normal route or capability report—or the underlying possibly sensitive error—
2396 for an invalid configuration.
2397
2398 MCP entries are configuration diagnostics unless an explicit MCP command is
2399 run. `mcp.probe_scope` is `configuration`, `mcp.live_health_checked` is false,
2400 and each server separates `checks.configuration` / `checks.command` from
2401 `checks.process_reachable`, `checks.protocol_initialized`, and
2402 `checks.backend_tool_health`. The latter three remain `not_checked` in doctor
2403 output. Run `codewhale mcp validate` to explicitly start enabled servers and
2404 verify protocol initialization/discovery; backend health still requires an
2405 appropriate explicit tool call. Doctor reports only safe structural MCP fields:
2406 URL userinfo/path/query/fragment and raw command arguments, environment values,
2407 header values, and token material are omitted. Provider URLs follow the same
2408 rule and expose only `scheme://host[:explicit-port]`.
2409
2410 The `capability` key contains per-provider capability info derived from
2411 static knowledge (release docs, API guides) rather than live API probes.
2412 Top-level sub-keys: `resolved_provider`, `resolved_model`, `context_window`,
2413 `max_output`, `thinking_supported`, `cache_telemetry_supported`,
2414 and `request_payload_mode`.
2415
2416 Use `capability.context_window` and `capability.max_output` for model-limit
2417 checks in CI scripts; do not treat `capability.max_output` as the per-turn
2418 request budget. Use `capability.thinking_supported` to decide whether to
2419 configure reasoning effort.
2420
2421 ## Setup status, clean, and extension dirs
2422
2423 `codewhale-tui setup` accepts a few flags beyond the existing `--mcp`,
2424 `--skills`, `--local`, `--all`, and `--force`:
2425
2426 - `--status` — print a compact one-screen status (api key, base URL, model,
2427 MCP/skills/tools/plugins counts, sandbox, `.env` presence). Read-only and
2428 network-free; safe to run in CI. If `.env` is missing and `.env.example` is
2429 present in the workspace, the status output points at `cp .env.example .env`.
2430 - `--tools` — scaffold `~/.codewhale/tools/` with a `README.md` describing the
2431 self-describing frontmatter convention (`# name:` / `# description:` /
2432 `# usage:`) and an `example.sh` that follows it. The directory is
2433 intentionally not auto-loaded; wire individual scripts into the agent via
2434 MCP, hooks, or skills.
2435 - `--plugins` — scaffold `~/.codewhale/plugins/` with a `README.md` and an
2436 `example/plugin.toml` plus a namespaced example Skill. Bundles are discovered
2437 read-only, untrusted, and disabled; review them through `/plugin` before
2438 enabling. v0.9.1 activates only declared Skills and MCP servers. See
2439 [PLUGIN_BUNDLES.md](PLUGIN_BUNDLES.md).
2440 - `--all` now scaffolds MCP + skills + tools + plugins together.
2441 - `--clean` — list `~/.codewhale/sessions/checkpoints/latest.json` and
2442 `offline_queue.json` if they exist. Legacy
2443 `~/.deepseek/sessions/checkpoints/` files are not scanned automatically; set
2444 `CODEWHALE_HOME=~/.deepseek` for a one-off legacy cleanup. Pass `--force` to
2445 actually remove matched files. This never touches real session history or the
2446 task queue.
2447
2448 `--status` and `--clean` are mutually exclusive with the scaffold flags.
2449
2450 ## Why the engine strips XML/`[TOOL_CALL]` text
2451
2452 codewhale sends and receives tool calls only over the API tool channel
2453 (structured `tool_use` / `tool_call` items). The streaming loop in
2454 `crates/tui/src/core/engine.rs` recognizes a fixed set of fake-wrapper start
2455 markers — `[TOOL_CALL]`, `<codewhale:tool_call`, `<tool_call`, `<invoke `,
2456 `<function_calls>` — and scrubs them from visible assistant text without ever
2457 turning them into structured tool calls. When a wrapper is stripped, the loop
2458 emits one compact `status` notice per turn so the user can see why their
2459 visible text shrank. Treat any change that re-enables text-based tool
2460 execution as a regression; the protocol-recovery tests in
2461 `crates/tui/tests/protocol_recovery.rs` lock the contract.
2462
2462 lines MARKDOWN