返回 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 ## Reading and checking configuration from the CLI
19
20 `codewhale config get <key>` reads scalar keys, whole tables such as `tools`,
21 and nested paths such as `tools.user_input_timeout_seconds`. Displayed tables
22 and nested values apply the same recursive credential redaction as `config dump`.
23
24 `config set` supports its named scalar keys and the provider, route, and
25 notification commands. Other dotted writes fail before modifying the file and
26 name the TOML table to edit. For example, set a tools timeout in the file as:
27
28 ```toml
29 [tools]
30 user_input_timeout_seconds = 0
31 ```
32
33 `codewhale config doctor` checks credential presence and endpoint shape. Settings
34 preserved for other runtime readers are not classified as unsupported merely
35 because the CLI dispatcher does not own them. A clean result from this command
36 does not validate every runtime setting (#6083).
37
38 ## Constitution, project instructions, and repo authority
39
40 Codewhale has several instruction surfaces. They are deliberately separate so a
41 personal constitution, repo policy, project instructions, and runtime security
42 controls do not blur together.
43
44 - **Bundled global Constitution** — the compiled base law in the binary. It is
45 the default floor for every session.
46 - **User-global constitution** — the normal guided setup output. Manage it with
47 `/constitution` or `/setup`; Codewhale stores structured data at
48 `$CODEWHALE_HOME/constitution.json` (default `~/.codewhale/constitution.json`)
49 and renders it into a separate `<codewhale_user_constitution>` prose block.
50 This can express preferences and stop conditions, but it does not change
51 runtime approval policy, sandbox, shell, network, trust, or MCP permissions.
52 - **Repo-local constitution** — optional project policy in
53 `.codewhale/constitution.json`, described below.
54 - **`AGENTS.md`** — cross-agent **project instructions** (prose). This is the
55 canonical file for "how should an agent work in this repo." Run `/init` to
56 scaffold one. `CLAUDE.md` and `.claude/instructions.md` are read as
57 compatibility fallbacks.
58 - **Memory and handoffs** — recalled state. Useful, but lower authority than
59 constitutions and project instructions.
60
61 ### Managing the user-global constitution (`/setup` and `/constitution`)
62
63 The bundled **working agreement** is the safe default and no longer adds a
64 required first-run screen. Customize it later through `/constitution` or the
65 progressive `/setup` guide. Provider/model readiness, workspace trust, and
66 runtime posture stay separate from this guidance.
67
68 On the **Constitution** step:
69
70 - **`1`–`6`** tune the guided draft. **`G`** previews it, and **`G`** again
71 ratifies and saves a fresh structured `constitution.json`.
72 - **`A`** (shown only when a provider is configured) asks your first configured
73 model to draft the constitution. Drafting is **not** saving: the draft is
74 rendered through the same preview and you still press **`G`** to ratify
75 before anything persists.
76 - **`K`** keeps your existing loaded constitution unchanged (shown only when a
77 valid file is already present).
78 - **`U`** (or `/constitution bundled`) records the bundled/default law.
79
80 `/constitution` (alias `/law`) is the primary management surface once you are
81 set up. Subcommands: `status` (the default), `preview`, `review`, `repo` (the
82 repo-local law block), `explain`, `edit`/`guided`, `repair`, `posture`, and
83 `bundled`. Managing the constitution never changes runtime approval, sandbox,
84 shell, network, trust, default mode, or MCP authority — those stay in runtime
85 posture/config.
86
87 Each repo can carry two distinct, complementary files:
88
89 - **`AGENTS.md`** — ordinary project working instructions.
90 - **`.codewhale/constitution.json`** — Codewhale-specific **repo authority /
91 prioritization policy**: when local sources conflict, which should Codewhale
92 trust first, and what to verify before claiming a task is done. `.codewhale/`
93 lives inside the repo (like `.github/`). Example:
94
95 ```json
96 {
97 "schema_version": 1,
98 "authority": [
99 "current user request",
100 "live code and tests",
101 "GitHub issue/PR details",
102 "AGENTS.md",
103 "memory",
104 "old handoffs"
105 ],
106 "protected_invariants": [
107 "do not break old-session transcript replay"
108 ],
109 "branch_policy": "PRs target the integration branch, not main",
110 "verification_policy": {
111 "before_claiming_done": ["run focused tests", "read changed files back"]
112 },
113 "escalate_when": [
114 "a destructive action was not explicitly authorized"
115 ]
116 }
117 ```
118
119 All fields are optional. When present, the file is rendered into the system
120 prompt as concise prose in a higher-authority block. Legacy `WHALE.md` files
121 are ignored and reported as migration-only diagnostics.
122
123 Each `protected_invariants` entry may be either a plain string (advisory
124 prose, the historical shape) or an object carrying path globs, which is
125 additionally **mechanically enforced** in the tool gate. See
126 [Enforced repo-law invariants](#enforced-repo-law-invariants) below.
127
128 This is the **repo-local law** layer in Codewhale's hierarchy: *bundled global
129 Constitution* → *user-global constitution* (`$CODEWHALE_HOME/constitution.json`,
130 rendered as prose) → *repo constitution* (`.codewhale/constitution.json`, this
131 file) → *AGENTS/project instructions* → *memory and handoffs* → *current
132 request and live evidence for the active turn*. Runtime policy
133 (permissions/sandbox/cost limits enforced in code) is separate from all of
134 these prompt layers. The repo constitution gives project decision rules; it
135 does not replace the bundled Constitution, the user-global constitution, or
136 the current user request.
137
138 > **`WHALE.md` is deprecated.** It overlapped confusingly with `AGENTS.md`.
139 > Codewhale no longer reads `WHALE.md` as project or global context. If one is
140 > present, setup/context diagnostics report it as ignored so you can migrate it.
141 > Move ordinary instructions to `AGENTS.md` and Codewhale-specific authority
142 > policy to `.codewhale/constitution.json`. Personal standing guidance belongs
143 > in `/constitution` / `$CODEWHALE_HOME/constitution.json`. (The global
144 > Codewhale Constitution shipped in the model prompt is a separate thing and is
145 > unaffected.)
146
147 ### Enforced repo-law invariants
148
149 By default a `protected_invariants` entry is advisory prose: it is rendered into
150 the prompt as guidance the agent should honor, but nothing stops a write. An
151 entry written as an **object with `paths`** is different — it compiles into a
152 mechanical write hold that the engine's tool gate evaluates before the write
153 runs. The law becomes mechanism, not just a request.
154
155 An enforced entry has this shape:
156
157 ```json
158 {
159 "schema_version": 1,
160 "protected_invariants": [
161 "Keep DeepSeek support first-class.",
162 {
163 "text": "The wire format is frozen; protocol changes need a human.",
164 "paths": ["crates/protocol/**"],
165 "action": "block"
166 },
167 {
168 "text": "Release notes need human review.",
169 "paths": ["CHANGELOG.md"],
170 "action": "ask"
171 }
172 ]
173 }
174 ```
175
176 - `text` — required. The reason surfaced on the hold. An empty `text` is skipped.
177 - `paths` — workspace-relative globs (globset syntax, e.g. `crates/protocol/**`,
178 `**/secrets.toml`, `CHANGELOG.md`). An object with no usable `paths` stays
179 advisory-only despite the object shape.
180 - `action` — optional, defaults to `ask`. `ask` force-prompts in Ask and
181 Auto-Review; in Full Access it denies the protected write without opening a
182 modal. `block` **denies the write outright** in every posture.
183
184 Semantics:
185
186 - **Tighten-only.** The schema has no allow/widen shape, so law can only *add*
187 holds — a crafted constitution can never grant authority or weaken a gate
188 above it.
189 - **Not bypassable by mode.** Like the built-in safety floor, an `ask` hold
190 force-prompts in Ask and Auto-Review. Full Access never opens approval
191 modals, so the same hold fails closed as a hard block; `block` always denies.
192 Mode cannot turn a hold off.
193 - **Repo-local only.** Only the repo's `.codewhale/constitution.json`
194 participates. The user-global constitution stays advisory prose and never
195 reaches this mechanism.
196 - **Fails safe.** A missing file, parse error, or invalid glob degrades to
197 fewer or zero rules — never a hold on unprotected paths and never a poisoned
198 gate. Across matches the strongest action wins, so `block` outranks `ask`.
199 - **Leaves a receipt.** Every hold emits a `tool.repo_law_decision` tool-audit
200 event naming the invariant, the matched path, and the source file; the
201 approval/denial reason names the invariant too.
202
203 **Coverage is deliberately limited.** Holds are evaluated only for the write
204 tools `write_file`, `edit_file`, `apply_patch`, and `fim_edit`, and only
205 against the filesystem targets named in their inputs (`path`/`target`/
206 `destination`/`file_path`, `changes[].path`, and unified-diff /
207 `apply_patch`-envelope headers). A shell command that writes a protected path is **not** held by
208 repo law — those writes are still governed by the ordinary approval, sandbox,
209 and shell-write gates, not by this mechanism.
210
211 ### Expert full base-prompt override (#3638)
212
213 The global Constitution (the base system prompt, normally compiled in from
214 `crates/tui/src/prompts/text.rs` as `BASE_PROMPT`) can be replaced per-user
215 without rebuilding. This is
216 an expert escape hatch, not the normal `/constitution` guided setup output.
217 Because this is a prompt trust boundary, it takes **two deliberate steps** — a
218 file alone is not enough:
219
220 1. Drop the replacement at `~/.codewhale/prompts/constitution.md` (under
221 `$CODEWHALE_HOME` when set).
222 2. Set the explicit opt-in flag `CODEWHALE_ALLOW_BASE_PROMPT_OVERRIDE=1`
223 (`true`/`on`/`yes` also accepted).
224
225 If the file exists but the flag is unset, the override is **ignored** (with a
226 log line pointing to the flag) and the bundled Constitution stays in place.
227 This is intended for repurposing the TUI beyond software engineering — e.g.
228 long-form writing or document review — where the engineering-oriented base
229 prompt is a poor fit. It is loaded once at startup; a **missing or empty file
230 is a no-op**, so existing installs keep the bundled prompt.
231
232 Scope is deliberately narrow: only the byte-stable **base prompt segment** is
233 overridable. Mode deltas, the approval policy, the tool taxonomy, Context
234 Management, and the Compaction Relay are still owned by Codewhale's runtime
235 assembly, so an override **cannot remove safety-relevant guidance** (sandbox,
236 approvals) — it only swaps the task/voice framing. To customize ordinary
237 personal behavior, prefer `/constitution`; to customize per-repo behavior,
238 prefer `AGENTS.md` + `.codewhale/constitution.json` above.
239
240 ## Where It Looks
241
242 Default config path:
243
244 - `~/.codewhale/config.toml`
245 - Legacy fallback: `~/.deepseek/config.toml`
246
247 Overrides:
248
249 - CLI: `codewhale --config /path/to/config.toml`
250 - Env: `CODEWHALE_CONFIG_PATH=/path/to/config.toml`
251 - Legacy env alias: `DEEPSEEK_CONFIG_PATH=/path/to/config.toml`
252
253 If both are set, `--config` wins. Environment variable overrides are applied after the file is loaded.
254
255 ### TUI editability audit
256
257 Inside the TUI, run `/config audit` to see which documented keys can be changed
258 from the current session, which ones can also be persisted, and which ones stay
259 file-only or restart-only. The audit includes current values for the high-impact
260 runtime controls such as `approval_policy`, `allow_shell`,
261 `stream_chunk_timeout_secs`, `base_url`, `mcp_config_path`, and the
262 `[subagents]` concurrency/depth/timeout keys.
263
264 Use the command's "Command / reason" column as the source of truth before
265 editing by hand. For example, `/config approval_mode on-request --save` writes
266 top-level `approval_policy = "on-request"`, while provider base URLs are saved
267 but still require restarting the model client.
268
269 ### User workspace entries
270
271 Interactive Agent sessions expose shell tools by default with approval gating
272 unless you explicitly disable them. For a shell opt-in that should live in the
273 user's global config for noninteractive or durable-task profiles rather than in
274 the repository, add a workspace-scoped entry:
275
276 ```toml
277 [workspace.'/absolute/path/to/project']
278 allow_shell = true
279 ```
280
281 The entry applies only when the launched workspace path matches the table key.
282 The legacy `[projects."/absolute/path/to/project"]` table is also accepted for
283 this user-owned override.
284
285 In interactive mode, the per-project overlay
286 `<workspace>/.codewhale/config.toml` is applied after this user entry. A
287 project-level `allow_shell = false` can still tighten the session; project-level
288 `allow_shell = true` is ignored.
289
290 ### Per-project overlay (#485)
291
292 When the TUI starts in a workspace that contains a regular-file
293 `<workspace>/.codewhale/config.toml`, the safe values declared in that file are
294 merged on top of the global config. Legacy
295 `<workspace>/.deepseek/config.toml` files are still read when the Codewhale path
296 is absent. Symlinked project config files are rejected. This lets a repo suggest
297 a model or tighten local safety posture without touching the user's
298 `~/.codewhale/config.toml`. Pass `--no-project-config` to skip the overlay for
299 one launch.
300
301 Supported keys in the project overlay (top-level fields only):
302
303 | Key | Effect |
304 |---|---|
305 | `model` | override `default_text_model` |
306 | `reasoning_effort` | force `"high"` / `"max"` for a complex repo |
307 | `approval_policy` | only values that tighten the user's current permission posture |
308 | `sandbox_mode` | only values that tighten the user's current sandbox posture |
309 | `notes_path` | keep notes in-repo |
310 | `max_subagents` | clamp sub-agent concurrency for a constrained repo (clamped to 1..=128) |
311 | `allow_shell` | `false` can disable shell access; `true` is ignored |
312
313 The overlay is intentionally narrow — it covers the fields a repo
314 maintainer is most likely to want to standardize across contributors.
315 Credential, endpoint, provider-selection, MCP config, hooks, skills,
316 retry, hotbar bindings, and `instructions = [...]` settings stay user-global.
317 If a repo-local config declares `api_key`, `base_url`, `provider`,
318 `mcp_config_path`, `hotbar`, `allow_shell = true`, or `instructions`,
319 Codewhale ignores that key and keeps the user's global setting.
320
321 The consolidated `codewhale` runtime uses one config file for DeepSeek auth
322 and model defaults. `codewhale auth set --provider deepseek` saves
323 the key to `~/.codewhale/config.toml` (migrating legacy `~/.deepseek/config.toml`
324 on first launch when needed), and `codewhale --model deepseek-v4-flash` is
325 forwarded to the TUI as `DEEPSEEK_MODEL`.
326
327 `codewhale login` signs in to the Codewhale account — it is the same browser
328 device flow as `codewhale account login`, not a provider-key command. Provider
329 credentials are configured exclusively through `codewhale auth set
330 --provider <provider>`.
331
332 That provider credential is distinct from the optional managed-product
333 account. `codewhale account login` starts the Codewhale browser device flow;
334 `codewhale account status` and `codewhale account logout` inspect or remove the
335 session for the selected `--profile`. Account sessions prefer the OS
336 credential manager and fall back automatically to the private `0600`
337 Codewhale secrets file when no credential manager is available (headless
338 hosts, SSH, containers).
339 `codewhale account keys list|set|remove` manages the
340 signed-in account's BYOK vault without displaying secret values. The older
341 `codewhale cloud ...` spelling remains a command alias.
342
343 ### Portable config bundles
344
345 `codewhale config export --portable [--project] [--out FILE]` writes a
346 portable, secret-free bundle of your configuration: sorted TOML with
347 credential and machine-specific keys (API keys, base URLs, socket paths)
348 dropped, never a redacted placeholder in their place. Without `--out` the
349 bundle goes to stdout. Typed tables, arrays, numbers, booleans, and datetimes
350 remain typed. Machine-bound authority is deliberately non-portable: project
351 trust overlays, credential readers, auto-running hooks, executable LSP
352 definitions, and local path bindings are omitted rather than copied to a new
353 host.
354
355 `codewhale config import <FILE|HTTPS_URL|-> [--dry-run] [--yes] [--project]`
356 applies a bundle. The envelope is strict (`schema_version = 1`, kind
357 `codewhale.portable-config`; unknown fields fail). Import prints a
358 deterministic plan — added / changed / skipped / conflicting / rejected —
359 then asks for consent unless `--yes` is given; headless use requires it.
360 Credential-shaped entries are rejected by key name and by value shape;
361 rejections name the field, never a value. Remote bundles come from HTTPS
362 only (loopback http excepted) with a 5 MiB cap. Application backs up the
363 target document to `<config>.bundle-backup-<timestamp>-<random>`, rolls back on any
364 failure, and re-importing an applied bundle changes nothing.
365
366 Import also rejects the non-portable authority classes omitted by export,
367 including nested/camel/dotted credential keys and cookie headers. This keeps a
368 hand-authored or remote bundle from reintroducing machine trust, local
369 credential access, or automatically executable commands that a local export
370 would refuse to carry. Structured tables are deep-merged: a portable model or
371 preference update does not erase target-local provider credentials, endpoints,
372 hooks, or executable definitions that were deliberately omitted from the
373 bundle. Arrays and scalar values still replace the corresponding portable
374 value.
375
376 Sections map to scope: `[project]` entries land only in the workspace
377 document (`--project`, which must target an actual workspace config),
378 `[global]` only in the user-global one; `preferences`, `profiles`, and
379 `plugins` apply at either scope. A global bundle operation refuses a workspace
380 document just as a project operation refuses the user-global document.
381
382 ### Credential read precedence (#5197)
383
384 Credential reads are **folder-independent by default**: every layer below is
385 user-global or process-scoped, so a key saved in one repo resolves identically
386 in every other repo. Repo-local config never carries credential material — the
387 project overlay above reads only its allowlisted keys and ignores `api_key`,
388 and a credential write aimed at a workspace-scoped config is rescoped to the
389 user-global `~/.codewhale/config.toml` (#5045, #5193).
390
391 For the active provider, the runtime resolves the API key in this exact order
392 (first match wins):
393
394 1. **Route-specific auth contract.** Routes whose `auth_mode` disables API
395 keys stop here with no credential. OAuth routes use their explicitly
396 consented token: `openai-codex` reads `OPENAI_CODEX_ACCESS_TOKEN`, then
397 Codewhale-owned ChatGPT PKCE tokens from `codewhale auth chatgpt`, then the
398 consent-granted Codex CLI login (read-only, never refreshed or rewritten);
399 `[providers.xai] auth_mode = "oauth"` reads Codewhale's own xAI
400 device-login store (or a consent-granted Grok CLI file).
401 2. **Explicit CLI key.** `--api-key` forwarded with its source marker wins
402 over every saved slot; for `deepseek`/`deepseek-CN` it also wins over the
403 root `api_key`.
404 3. **Config file `api_key`.** The `[providers.<name>] api_key` table slot for
405 the active provider, plus the legacy root `api_key` for
406 `deepseek`/`deepseek-CN` and the literal `provider = "custom"` route.
407 File-owned keys stay bound to their file-owned endpoint: when the
408 environment replaces the route's base URL with a custom host, the saved
409 key is not sent there.
410 4. **`api_key_env` binding.** `[providers.<name>] api_key_env = "VAR"` reads
411 the named environment variable. For custom providers an unset or empty
412 binding is a loud error, not a silent fallback (#5104).
413 5. **Secret store.** The durable per-provider slot written by
414 `codewhale auth set` (file-backed under `~/.codewhale/secrets/` by
415 default; the OS keyring only when explicitly selected). Skipped for named
416 custom routes, self-hosted providers, custom endpoints other than an
417 explicitly authenticated loopback, and routes whose `auth_mode` needs no
418 key.
419 6. **Ambient environment.** The provider's own variable
420 (`DEEPSEEK_API_KEY`, `OPENROUTER_API_KEY`, `MOONSHOT_API_KEY`, …).
421 Ambient keys are only ever sent to the provider's official endpoint and
422 are skipped under the same conditions as the secret store.
423 7. **Keyless fallback.** Self-hosted providers and loopback endpoints may run
424 with no credential; every other route fails with provider-specific setup
425 guidance.
426
427 Legacy compatibility: `~/.deepseek/config.toml` is migrated into
428 `~/.codewhale/config.toml` on first launch, `DEEPSEEK_*` environment
429 variables remain accepted aliases for the `CODEWHALE_*` forms, and
430 `DEEPSEEK_SECRET_BACKEND` is the legacy alias for `CODEWHALE_SECRET_BACKEND`.
431
432 Run `codewhale auth status` to inspect the active provider's config
433 file, OS keyring backend, environment variable, winning source, and last-four
434 label without printing the key itself. The command only probes the active
435 provider's keyring entry.
436
437 For hosted, generic OpenAI-compatible, self-hosted, OpenAI Responses, or native
438 Anthropic providers, set `provider = "<id>"` or pass
439 `codewhale --provider <id>`. The canonical provider IDs are `deepseek`,
440 `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `volcengine`,
441 `openrouter`, `orcarouter`, `xiaomi-mimo`, `novita`, `fireworks`,
442 `siliconflow`, `arcee`, `siliconflow-CN`, `moonshot`, `sglang`, `vllm`,
443 `ollama`, `ollama-cloud`, `huggingface`, `modelscope`, `together`, `qianfan`, `openai-codex`,
444 `anthropic`, `openmodel`, `zai`, `stepfun`, `minimax`, `deepinfra`,
445 `sakana`, `longcat`, `opencode-go`, `opencode-zen`, `meta`, `xai`,
446 `mistral`, `telecomjs`, `modelstudio-token-plan`, `google`,
447 `edenai`, `concentrate`, `codewhale`, and `custom` (a user-defined OpenAI-compatible endpoint via
448 `[providers.<name>]`).
449 For the provider-by-provider registry, including wire protocol, auth variables,
450 default base URLs, model IDs, and capability metadata, see
451 [PROVIDERS.md](PROVIDERS.md).
452 The facade saves provider credentials to the shared user config and forwards
453 the resolved key, base URL, provider, and model to the TUI process. Use
454 `codewhale auth set --provider nvidia-nim --api-key "YOUR_NVIDIA_API_KEY"` or
455 `codewhale auth set --provider openai --api-key "YOUR_OPENAI_COMPATIBLE_API_KEY"` or
456 `codewhale auth set --provider atlascloud --api-key "YOUR_ATLASCLOUD_API_KEY"` or
457 `codewhale auth set --provider wanjie-ark --api-key "YOUR_WANJIE_API_KEY"` or
458 `codewhale auth set --provider xiaomi-mimo --api-key "YOUR_XIAOMI_KEY"` or
459 `codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"` or
460 `codewhale auth set --provider siliconflow --api-key "YOUR_SILICONFLOW_API_KEY"` or
461 `codewhale auth set --provider arcee --api-key "YOUR_ARCEE_API_KEY"` or the
462 matching provider ID from [PROVIDERS.md](PROVIDERS.md) to save provider keys
463 through the facade. The generic `openai` provider defaults
464 to `https://api.openai.com/v1`, accepts `OPENAI_BASE_URL`, and defaults to
465 `gpt-5.6`. A custom OpenAI-compatible gateway can still select its own model
466 explicitly. `atlascloud` defaults to
467 `https://api.atlascloud.ai/v1`, accepts `ATLASCLOUD_BASE_URL`, and uses
468 `deepseek-ai/deepseek-v4-flash` as its default model. `wanjie-ark` targets
469 Wanjie Ark's OpenAI-compatible endpoint at
470 `https://maas-openapi.wanjiedata.com/api/v1`, defaults to `deepseek-reasoner`,
471 and passes model IDs through unchanged because Wanjie model access is
472 account-scoped. SGLang, vLLM, and Ollama are
473 self-hosted and can run without an API key by default. Ollama defaults to
474 `http://localhost:11434/v1` and sends model tags such as `codewhale-coder:1.3b`
475 or `qwen2.5-coder:7b` unchanged. Self-hosted providers and loopback custom
476 URLs (`localhost`, `127.0.0.1`, `[::1]`, `0.0.0.0`) do not read the secret store
477 unless API-key auth is explicitly requested; use an env var or config-file key
478 when a local server does require bearer auth.
479 Ollama Cloud is the separate hosted `ollama-cloud` provider. It defaults to
480 `https://ollama.com/v1` and `gpt-oss:120b`; save its key with
481 `codewhale auth set --provider ollama-cloud`. Ambient auth reads
482 `OLLAMA_CLOUD_API_KEY` first, then Ollama's official `OLLAMA_API_KEY`.
483 SiliconFlow defaults to `https://api.siliconflow.com/v1`, accepts
484 `SILICONFLOW_BASE_URL`, and uses `deepseek-ai/DeepSeek-V4-Pro` by default.
485 `provider = "siliconflow-CN"` selects the China regional default
486 `https://api.siliconflow.cn/v1` with the `[providers.siliconflow_cn]` table and
487 `SILICONFLOW_API_KEY` credential slot.
488 Arcee AI defaults to `https://api.arcee.ai/api/v1`, accepts `ARCEE_BASE_URL`,
489 and uses `trinity-large-thinking` by default for Codewhale agent work.
490 `trinity-large-preview` is also listed as a direct Arcee API model; OpenRouter's
491 `arcee-ai/trinity-large-thinking` remains the OpenRouter namespaced form, while
492 the direct Arcee provider uses the bare `trinity-large-thinking` ID. Direct
493 Arcee large-model API calls are tracked as 256K-context BF16 serving; Thinking
494 is reasoning-capable, while Preview is not marked as a thinking model.
495
496 ### OpenRouter vendor pinning
497
498 OpenRouter serves each model through several upstream vendors, and Codewhale
499 can pin requests to a vendor with `[providers.openrouter] vendor` (#6007):
500
501 ```toml
502 provider = "openrouter"
503 [providers.openrouter]
504 model = "deepseek/deepseek-v4-pro"
505 vendor = "deepinfra" # copy the vendor slug from the model's OpenRouter page
506 ```
507
508 This sends `"provider": {"order": ["deepinfra"], "allow_fallbacks": false}`
509 on OpenRouter requests. A base slug can match multiple endpoint variants;
510 copy a full slug such as `deepinfra/turbo` to select one variant. An unavailable
511 pin fails at OpenRouter. Codewhale's separate `fallback_providers` setting can
512 still switch the whole route after a recoverable error.
513
514 The pin applies across OpenRouter models, including auxiliary requests on that
515 route. Set `vendor = ""` to clear it. Reload config or restart to apply edits;
516 requests already in flight keep their captured route. Other providers do not
517 inherit the pin. `/preview-request` shows the primary request's routing fields.
518
519 Model strings still pass through verbatim: `:floor` sorts by price, `:nitro`
520 sorts by throughput, and `@preset/my-team-preset` references an account preset.
521 See OpenRouter's [provider routing](https://openrouter.ai/docs/guides/routing/provider-selection)
522 and [presets](https://openrouter.ai/docs/guides/features/presets) documentation.
523 Codewhale does not fetch per-vendor endpoint prices or availability; pinned
524 usage reports a routing-dependent unknown cost instead of a catalog estimate.
525
526 ### Custom OpenAI-Compatible Gateways
527
528 For a single third-party service that implements the OpenAI Chat Completions
529 API, the simplest setup is the built-in `openai` provider name pointed at the
530 gateway:
531
532 ```toml
533 provider = "openai"
534 default_text_model = "your-model-id"
535
536 [providers.openai]
537 api_key = "YOUR_OPENAI_COMPATIBLE_API_KEY"
538 base_url = "https://your-gateway.example/v1"
539 ```
540
541 Put the endpoint under `[providers.openai]`, not the legacy top-level
542 `base_url`, so the OpenAI-compatible provider receives it. `default_text_model`
543 is the model ID sent to the gateway; `[providers.openai].model` can be used as
544 the OpenAI-provider-specific override.
545
546 If you keep several OpenAI-compatible gateways, or need a stable name for an
547 AgentProfile provider pin, define a user-named custom provider table:
548
549 ```toml
550 provider = "lm-studio"
551
552 [providers.lm-studio]
553 kind = "openai-compatible"
554 base_url = "http://127.0.0.1:1234/v1"
555 api_key = "lm-studio"
556 model = "qwen-2.5-7b"
557 ```
558
559 Custom provider names may be selected with `provider = "<name>"`,
560 `--provider <name>`, or an AgentProfile `provider = "<name>"` when the matching
561 `[providers.<name>]` table exists.
562
563 StepFun has a first-class provider entry, so keep Coding Plan credentials and
564 base URL scoped to `[providers.stepfun]`:
565
566 ```toml
567 provider = "stepfun"
568
569 [providers.stepfun]
570 api_key = "YOUR_STEPFUN_API_KEY"
571 base_url = "https://api.stepfun.ai/step_plan/v1"
572 model = "step-3.7-flash"
573 ```
574
575 `/provider` setup asks which StepFun billing route the key belongs to —
576 pay-as-you-go (`https://api.stepfun.ai/v1`) or a Step Plan subscription
577 (`https://api.stepfun.ai/step_plan/v1`) — and validates the key against the
578 endpoint you pick before saving it. The answer is written to
579 `[providers.stepfun].base_url` and nowhere else. If that key already holds a
580 base URL Codewhale does not recognize as one of those two routes, the question
581 is skipped and your value is left untouched.
582
583 Alibaba Bailian / Model Studio DashScope Qwen routes use the same OpenAI
584 provider shape:
585
586 ```toml
587 provider = "openai"
588
589 [providers.openai]
590 api_key = "YOUR_DASHSCOPE_API_KEY"
591 base_url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
592 model = "qwen-plus"
593 context_window = 1000000
594 ```
595
596 Use the regional DashScope `compatible-mode/v1` base URL that matches the
597 region of your API key. Codewhale keeps `qwen-plus` scoped to the `openai`
598 provider route and does not infer a different provider from the model prefix.
599 The same rule applies to all provider-prefixed model strings: a prefix such as
600 `deepseek-ai/...` or `deepseek/...` is a provider-owned wire ID under the
601 selected provider, not an automatic switch to the DeepSeek provider.
602 Set `context_window` to the gateway/model's real total context window when it
603 differs from Codewhale's static model metadata. See
604 [Context length (context window)](#context-length-context-window) for the full
605 resolution order and for how to check which value is in effect.
606
607 If the gateway accepts `POST /chat/completions` but rejects
608 `/v1/chat/completions`, set a provider-local `path_suffix`:
609
610 ```toml
611 [providers.openai]
612 base_url = "https://your-gateway.example/v1"
613 path_suffix = "/chat/completions"
614 ```
615
616 The suffix applies only to chat-completion requests. Model listing and
617 DeepSeek beta paths keep their built-in routing so a generic gateway override
618 does not accidentally rewrite `/models` or `/beta/completions`.
619
620 For private gateways with broken or intercepted certificates, use
621 `SSL_CERT_FILE` with a trusted CA bundle. The legacy provider-table key
622 `insecure_skip_tls_verify = true` is still parsed so `codewhale doctor` can
623 report stale configs, but provider clients reject it instead of disabling TLS
624 certificate verification.
625
626 Local HTTP endpoints such as Ollama, SGLang, and vLLM are allowed by default
627 when they use localhost or loopback addresses. For a non-local `http://`
628 gateway, launch with `DEEPSEEK_ALLOW_INSECURE_HTTP=1` only on a trusted network:
629
630 ```bash
631 DEEPSEEK_ALLOW_INSECURE_HTTP=1 codewhale
632 ```
633
634 Third-party OpenAI-compatible gateways that need extra request headers can set
635 `http_headers = { "X-Model-Provider-Id" = "your-model-provider" }` at the top
636 level or under a provider table such as `[providers.deepseek]`. When configured,
637 codewhale sends those custom headers on model API requests. The equivalent
638 environment override is `DEEPSEEK_HTTP_HEADERS`, using comma-separated
639 `name=value` pairs such as
640 `X-Model-Provider-Id=your-model-provider,X-Gateway-Route=dev`. `Authorization`
641 and `Content-Type` are managed by the client and are not overridden by this
642 setting.
643
644 ### Vision Model
645
646 Codewhale's chat provider and `image_analyze` tool are configured separately.
647 The main chat path remains the selected text/tool provider; image analysis runs
648 through `[vision_model]` when the `vision_model` feature is enabled.
649
650 Xiaomi's current image-understanding docs include `mimo-v2.5` for image input.
651 To use MiMo for `image_analyze`, configure the vision model explicitly:
652
653 ```toml
654 [features]
655 vision_model = true
656
657 [vision_model]
658 model = "mimo-v2.5"
659 api_key = "YOUR_XIAOMI_KEY"
660 base_url = "https://api.xiaomimimo.com/v1"
661 ```
662
663 The example above uses Xiaomi MiMo's pay-as-you-go OpenAI-compatible endpoint.
664 If you are using a Token Plan key (`tp-...`) for `[vision_model]`, you must set
665 `base_url` explicitly because this generic OpenAI-compatible block does not
666 auto-select MiMo endpoints. Use
667 `https://token-plan-sgp.xiaomimimo.com/v1` for Singapore accounts,
668 `https://token-plan-cn.xiaomimimo.com/v1` for China-region accounts, or
669 `https://token-plan-ams.xiaomimimo.com/v1` for Europe/Amsterdam accounts.
670
671 ### Auto Model Routing (`[auto.router]`)
672
673 With `model = "auto"`, Codewhale routes each turn between a strong and a cheap
674 model. The routing decision comes from a small classifier call, or from a local
675 heuristic when no classifier route is available.
676
677 **There is no default classifier.** With `[auto.router]` unset, Auto is local
678 and free: it uses the heuristic and makes no classifier call, whatever keys you
679 hold. Holding a DeepSeek key used to elect `deepseek-v4-flash` automatically;
680 that was removed because it spent tokens on a route the user never chose and
681 privileged one provider over the rest (`crates/tui/src/config.rs:2392-2402`).
682 Electing a network classifier is now something you write down.
683
684 Point the classifier at any configured provider with `[auto.router]`:
685
686 ```toml
687 [auto.router]
688 provider = "zai"
689 model = "glm-5-turbo"
690 thinking = "off" # optional; defaults to off
691 ```
692
693 A classifier call happens only when `[auto.router]` is set *and* that provider
694 has a key — `router_available = router_configured && has_api_key_for(...)`
695 (`crates/tui/src/model_inventory.rs:206-218`). Either condition failing means
696 the heuristic decides, not a failure. The turn's route receipt (`/status` →
697 Auto) records which one it was.
698
699 To bootstrap MCP and skills directories at their resolved paths, run `codewhale setup`.
700 To only scaffold MCP, run `codewhale mcp init`.
701
702 Note: `setup`, `doctor`, `mcp`, `features`, `sessions`, `resume`/`fork`, `exec`,
703 `review`, and `eval` are all available from the installed `codewhale` command.
704 The consolidated dispatcher also provides `auth`, `config`, `model`, `thread`, `sandbox`,
705 `app-server`, `mcp-server`, `completions`, `login`/`logout`, `account`,
706 `metrics`, `update`, `lane`, `workflow`, and `web`. Plain prompts enter the
707 in-process TUI runtime. Release installers expose the same bytes as `codew`.
708
709 ### Startup Update Checks
710
711 By default, the TUI starts a background check for the latest stable Codewhale
712 release and shows a short toast only when a newer release is available and the
713 official release assets are complete. The check never blocks startup, never
714 blocks a turn, and fails silently when offline.
715
716 Disable the startup check entirely for air-gapped, corporate-proxy, or managed
717 desktop environments:
718
719 ```toml
720 [update]
721 check_for_updates = false
722 ```
723
724 #### Throttling
725
726 The answer is cached in `~/.codewhale/update-check.json` and reused for
727 `check_interval_hours` (default `1`). Only the *network request* is throttled —
728 the notice still appears on every launch while an update is outstanding. Set `0`
729 to check on every launch.
730
731 ```toml
732 [update]
733 check_interval_hours = 1
734 ```
735
736 A failed check is not cached, so an outage does not suppress the notice until
737 the interval elapses.
738
739 #### Automatic suppression
740
741 Checks are skipped, without contacting the network, when any of these is set to
742 a non-falsey value:
743
744 | Variable | Why |
745 | --- | --- |
746 | `CODEWHALE_NO_UPDATE_CHECK` | Explicit opt-out. |
747 | `NO_UPDATE_NOTIFIER` | The cross-CLI convention, honored for compatibility. |
748 | `CI`, `CONTINUOUS_INTEGRATION`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`, `JENKINS_URL`, `TEAMCITY_VERSION`, `TF_BUILD` | Automated build; nobody is at the terminal. |
749
750 Values of `""`, `0`, `false`, `no`, and `off` do not count as set, so a
751 `CI=false` export does not disable checks for ordinary users.
752
753 #### Which update command is offered
754
755 Codewhale never installs anything on its own — it only tells you an update
756 exists. The command it names depends on how the running binary was installed,
757 detected from its path:
758
759 | Install | Command offered |
760 | --- | --- |
761 | GitHub release binary (including Termux) | `codewhale update` |
762 | npm (`node_modules` on the path) | `npm install -g codewhale@latest` |
763 | Homebrew (`Cellar` / `linuxbrew` prefix) | `brew upgrade codewhale` |
764 | `cargo install` (`~/.cargo/bin`) | `cargo install codewhale-cli --locked --force` |
765
766 For package-managed installs the notice also warns against `codewhale update`:
767 replacing a binary Homebrew or npm owns leaves the manager describing a version
768 that is no longer on disk, and the next upgrade silently reverts you.
769
770 Override the detection with `CODEWHALE_INSTALL_METHOD=npm|homebrew|cargo|binary`
771 if you relocated the binary somewhere the path heuristics cannot read.
772
773 To redirect the startup check, set `update_uri` to an internal endpoint that
774 returns GitHub-compatible latest-release JSON. Minimal mirror metadata with a
775 `tag_name` field is accepted; if `assets` are present, Codewhale requires the
776 same uploaded asset set as the official release before showing the toast.
777
778 ```toml
779 [update]
780 check_for_updates = true
781 update_uri = "https://internal.mirror.example/codewhale/releases/latest"
782 ```
783
784 When `update_uri` is not set, startup checks honor release mirror environment
785 variables such as `CODEWHALE_RELEASE_BASE_URL` before falling back to the
786 official GitHub API endpoint. If a configured `update_uri` cannot be fetched or
787 parsed and a release mirror env var is set, the TUI falls back to that mirror
788 instead of failing startup.
789
790 ## Workshop output budgets
791
792 `[workshop]` still routes oversized tool results through the synthesis
793 path when they exceed `large_output_threshold_tokens`. Two optional
794 byte ceilings (#5367) raise the model-visible floor after that routing
795 and never lower it:
796
797 - `read_result_max_bytes` — cap for a single `read` / `read_file`
798 result. Absent keeps the compile-time defaults (100000 bytes for
799 `read`, which has no line cap; 16KiB / 500 lines for `read_file`).
800 For `read` this is the middle of a three-layer budget: the model's
801 own per-call `max_bytes` (hard maximum 500000) raises the budget for
802 one call, this setting raises the floor for the whole process, and
803 either way 2MiB is the absolute ceiling. Highest wins; neither layer
804 can lower a budget the other granted.
805 - `tool_result_max_bytes` — cap for a generic tool result after
806 spillover. Absent keeps the 12K-character compact floor (48K on
807 windows ≥500K tokens). Hard cap is 2MiB.
808
809 ## Context length (context window)
810
811 Also called context size, context limit, max context, or window. This is the
812 total token window Codewhale budgets against, and it drives the header/footer
813 context percent, the auto-compaction trigger, context-pressure checks, and the
814 request output cap. If Codewhale compacts at 128K on a model you know serves a
815 1M window, this is the setting to change (#5134).
816
817 **See what is in effect, and where the value came from.** Every one of these
818 prints the resolved window *and* its source:
819
820 - `/status` — a `Context window:` row with the percent and token counts, and a
821 `Window source:` row naming the provenance and the exact key that overrides
822 it.
823 - `/config` → Provider — `Context window` (your override, or `(not set)`) and
824 `Effective context window` (`1048576 tokens · configured`). Typing
825 `context length` in the `/config` filter jumps straight to them.
826 - `/context report` — `Window: 1048576 tokens (12.4% used, ...; source: configured)`.
827 - `/context json` — machine-readable `context_window_tokens` and
828 `context_window_source`.
829
830 **Change it** with the provider-table key `context_window`:
831
832 ```toml
833 [providers.moonshot]
834 context_window = 1048576
835 ```
836
837 or from the CLI:
838
839 ```bash
840 codewhale config set providers.moonshot.context_window 1048576
841 codewhale config unset providers.moonshot.context_window # back to automatic
842 ```
843
844 Use the table for the provider you are actually on (`providers.openai`,
845 `providers.deepseek`, `providers.moonshot`, …); `/status` names it for you. The
846 value is a positive token count for the route's *total* window.
847
848 When one gateway fronts models with heterogeneous windows, scope the override
849 to an exact wire model id with `[providers.<name>.model_context_windows]`:
850
851 ```toml
852 [providers.command_code]
853 context_window = 204800
854
855 [providers.command_code.model_context_windows]
856 "MiniMaxAI/MiniMax-M2.5" = 204800
857 "google/gemini-3.1-flash-lite" = 1000000
858 ```
859
860 Keys are the exact wire model ids the route sends (dotted and `org/model`
861 spellings both work as TOML keys when quoted); each value must be a positive
862 token count. A matching entry beats the provider-level `context_window` for
863 that model only — every other model on the provider still resolves against
864 `context_window` and the rungs below. From the CLI:
865
866 ```bash
867 codewhale config set 'providers.command_code.model_context_windows."MiniMaxAI/MiniMax-M2.5"' 204800
868 codewhale config unset 'providers.command_code.model_context_windows."MiniMaxAI/MiniMax-M2.5"'
869 ```
870
871 ### How the effective window is resolved
872
873 First match wins, and the source label each surface prints is exactly this
874 rung:
875
876 1. `configured (per-model)` — a `[providers.<name>.model_context_windows]`
877 entry keyed by the route's exact wire model id. A hard override for that
878 model only; it never rewrites another model's window.
879 2. `configured` — `[providers.<name>] context_window` in `config.toml`. A hard
880 override for every model on the provider: nothing below it can raise or
881 lower the result. Read-time aliases:
882 `contextWindow`, `context_window_tokens`, `contextWindowTokens`,
883 `context_length`, `contextLength`.
884 3. `provider-reported` — route-scoped 1M metadata a provider actually reported
885 for the Kimi Code `k3` route, when it was observed within the last 24 hours.
886 4. `static Kimi Code safe floor` — 262,144 tokens for Kimi Code memberships,
887 because 1M access is plan-gated (Allegretto and above).
888 5. `catalog` — the bundled route catalog (hand-curated offerings first, then
889 the bundled Models.dev rows). For `openai-codex`, a fresh (under 24 hours)
890 `$CODEX_HOME` model roster corrects this rung.
891 6. `model-name hint` — an `_Nk` suffix parsed from the model name itself
892 (`qwen3-32b-256k` → 256,000), vendor-agnostic. A naming convention the
893 serving engine may not honor is not a fact about the route, so this rung
894 sits *below* the catalog: any catalog row for the same id beats it (#5441).
895 7. `fallback` — the static per-provider capability table: 200,000 for
896 Anthropic-wire routes, 128,000 for `openai-codex`, 8,192 for Ollama,
897 otherwise Codewhale's static per-model metadata, and finally 128,000 when
898 the model is unknown.
899
900 ### What "(unverified)" means
901
902 The `model-name hint` and `fallback` rungs still drive real budgets — the
903 compaction trigger, the context meter, and the output reservation all use the
904 number — but they are guesses, not capabilities anyone checked. Every surface
905 that renders one of these windows appends `(unverified)` to its source label
906 (the status line, the context-pressure message, `/status`, `/config`, and the
907 model picker chip), so a window you did not configure and no provider reported
908 can never read as a verified limit (#5239, #5441). The `context_window` and
909 `model_context_windows` provider-table keys above are the fix: a configured
910 window is a hard override and renders as `configured` (or
911 `configured (per-model)`) with no marker.
912
913 Output ceilings follow the same rule (#5440): an Anthropic-family model the
914 catalog does not describe keeps the 64K Messages floor as its clamp, and the
915 ChatGPT/Codex OAuth route keeps its long-standing 4K policy, but receipts and
916 pickers label those numbers `unverified` (or an "assumed floor") instead of
917 `documented`. Clamping to a defensible floor is a product choice; presenting
918 it as a documented fact is not.
919
920 There is no environment variable for the context window; the provider-table
921 `context_window` and per-model `model_context_windows` keys are the user
922 knobs. They are the right ones to set when a gateway or self-hosted runtime
923 serves a window Codewhale's catalog does not model — per-model when only some
924 of a provider's routes differ, provider-wide when they all do. Codewhale will
925 not invent a window it cannot justify — it falls back to a conservative value,
926 labels it `fallback`, and marks it `(unverified)` at every surface that shows
927 it.
928
929 ### Adjacent knobs
930
931 - `auto_compact_threshold_percent` (settings.toml; also accepted as
932 `auto_compact_threshold`; `10`–`100`, default `80`): the share of the full
933 route context window at which auto-compaction fires, clamped so it can never
934 cross the spendable input ceiling after output reservation and headroom.
935 Editable from `/config`. Raising the window without touching this raises the
936 absolute compaction point along with it.
937 - `auto_compact` (settings.toml, on/off): turns automatic compaction off
938 entirely; `/compact` and Ctrl+L stay available.
939 - `[compaction] summary_instructions` and
940 `[compaction] retained_user_message_tokens` (config.toml): standing
941 summarizer instructions and the verbatim user-message retention budget. See
942 the `compaction.*` entry in the config-key reference below.
943 - `CODEWHALE_MAX_OUTPUT_TOKENS` (environment variable; legacy alias
944 `DEEPSEEK_MAX_OUTPUT_TOKENS`): overrides the requested output cap. Without an
945 override, Codewhale starts at the safe `65536` request cap and intersects it
946 with any smaller documented model or route ceiling; a catalog `max_output`
947 such as DeepSeek V4's 384K remains a capability ceiling, not the amount every
948 response requests. Explicit overrides are preserved within the resolved
949 route context window and any route output ceiling, and preflight/emergency
950 budgeting reserves the same effective value that can reach the wire. A
951 separately documented route input ceiling also clamps preflight and
952 compaction even when the total context window is larger. A blank canonical
953 variable falls through to a nonblank legacy value; a nonblank invalid or
954 zero canonical value is authoritative and falls back to the safe automatic
955 default instead of activating a stale legacy setting. There is no
956 `max_output_tokens` key in `config.toml`.
957
958 Before compaction replaces conversation history, Codewhale durably saves the
959 original messages to the session's `artifacts/context-transfer-<id>.json` and,
960 when a model summary is produced, its handoff to the matching `.md` file.
961 These use the existing session artifact store and persistence redaction.
962 A failed write aborts compaction without replacing context. Pruning-only passes
963 save the original messages without making an extra model call. Pressure metadata
964 shows estimated input tokens and the configured trigger; it is an estimate, not
965 an exact promise about a provider's remaining context.
966
967 Compaction history is available through `codewhale metrics` (or `--json`) and
968 `audit.log` in the Codewhale home. Completed passes record their trigger,
969 summary/pruning path, message and estimated-token counts, effective threshold,
970 and summarizer token usage. Automatic refusals are recorded once per turn with
971 their reason. These are local diagnostics, not provider invoice totals; earlier
972 artifacts are not retroactively counted. A text-mode `exec` that attempts
973 compaction saves its owning session at turn completion so its recovery artifacts
974 remain discoverable. A killed process may leave artifacts without that final
975 session snapshot; the audit writer reports I/O failures instead of inventing data.
976
977 See [Settings File](#settings-file-persistent-ui-preferences) for the
978 compaction settings and [Token Quantities and
979 Drivers](#token-quantities-and-drivers) for what each displayed token number
980 actually measures.
981
982 ## Profiles
983
984 You can define multiple profiles in the same file:
985
986 ```toml
987 api_key = "PERSONAL_KEY"
988 default_text_model = "deepseek-flash"
989
990 [profiles.work]
991 api_key = "WORK_KEY"
992 base_url = "https://api.deepseek.com/beta"
993
994 [profiles.nvidia-nim]
995 provider = "nvidia-nim"
996 api_key = "NVIDIA_KEY"
997 base_url = "https://integrate.api.nvidia.com/v1"
998 default_text_model = "deepseek-ai/deepseek-v4-pro"
999
1000 [profiles.fireworks]
1001 provider = "fireworks"
1002 default_text_model = "accounts/fireworks/models/deepseek-v4-pro"
1003
1004 [profiles.siliconflow]
1005 provider = "siliconflow"
1006 default_text_model = "deepseek-ai/DeepSeek-V4-Pro"
1007
1008 [profiles.siliconflow.providers.siliconflow]
1009 base_url = "https://api.siliconflow.com/v1"
1010
1011 [profiles.openai-compatible]
1012 provider = "openai"
1013
1014 [profiles.openai-compatible.providers.openai]
1015 base_url = "https://openai-compatible.example/v4"
1016 model = "glm-5"
1017
1018 [profiles.atlascloud]
1019 provider = "atlascloud"
1020
1021 [profiles.atlascloud.providers.atlascloud]
1022 base_url = "https://api.atlascloud.ai/v1"
1023 model = "deepseek-ai/deepseek-v4-flash"
1024
1025 [profiles.sglang]
1026 provider = "sglang"
1027 base_url = "http://localhost:30000/v1"
1028 default_text_model = "deepseek-ai/DeepSeek-V4-Pro"
1029
1030 [profiles.vllm]
1031 provider = "vllm"
1032 base_url = "http://localhost:8000/v1"
1033 default_text_model = "deepseek-ai/DeepSeek-V4-Pro"
1034
1035 [profiles.ollama]
1036 provider = "ollama"
1037 base_url = "http://localhost:11434/v1"
1038 default_text_model = "codewhale-coder:1.3b"
1039
1040 [profiles.ollama-cloud]
1041 provider = "ollama-cloud"
1042
1043 [profiles.ollama-cloud.providers.ollama_cloud]
1044 base_url = "https://ollama.com/v1"
1045 model = "gpt-oss:120b"
1046 ```
1047
1048 Select a profile with:
1049
1050 - CLI: `codewhale --profile work`
1051 - Env: `DEEPSEEK_PROFILE=work`
1052
1053 If a profile is selected but missing, codewhale exits with an error listing available profiles.
1054
1055 ## Environment Variables
1056
1057 Most runtime environment variables override config values. API-key variables are
1058 fallbacks after saved config and keyring credentials.
1059
1060 The three user-facing slots — provider, model, base URL — expose `CODEWHALE_*`
1061 aliases. When both forms are set the `CODEWHALE_*` value wins; the
1062 `DEEPSEEK_*` form is kept for older shells:
1063
1064 - `CODEWHALE_PROVIDER` (preferred) / `DEEPSEEK_PROVIDER` (legacy alias) —
1065 `deepseek|deepseek-anthropic|nvidia-nim|openai|atlascloud|wanjie-ark|volcengine|openrouter|xiaomi-mimo|novita|fireworks|siliconflow|arcee|siliconflow-CN|moonshot|sglang|vllm|ollama|ollama-cloud|huggingface|modelscope|together|qianfan|openai-codex|anthropic|openmodel|zai|stepfun|minimax|deepinfra|mistral`
1066 - `CODEWHALE_MODEL` (preferred) / `DEEPSEEK_MODEL` (legacy alias) — default model for the active provider
1067 - `CODEWHALE_BASE_URL` (preferred) / `DEEPSEEK_BASE_URL` (legacy alias) — base URL for the active provider
1068
1069 `CODEWHALE_BASE_URL` applies to the **active** route only. A request pinned to
1070 another provider — a subagent or fleet child, a routed tool, the per-turn
1071 auto-router, a picker preview — resolves its endpoint from that provider's own
1072 `[providers.<table>]`, then its provider-scoped variable (`MOONSHOT_BASE_URL`,
1073 `OPENAI_BASE_URL`, …), then that provider's default. It never inherits the
1074 active session's host, and a custom route with no configured `base_url` fails
1075 closed on a loopback placeholder rather than borrowing another provider's
1076 endpoint. The legacy root `base_url` behaves the same way: written in your
1077 config file it stays shared by the DeepSeek and DeepSeek-CN identities as it
1078 always has, but a value the environment wrote belongs to the identity it was
1079 addressed to. A managed-config overlay that supplies or reselects the effective
1080 route's endpoint takes the generic override away from every route.
1081
1082 Remaining variables:
1083
1084 - `DEEPSEEK_API_KEY`
1085 - `DEEPSEEK_ANTHROPIC_BASE_URL`
1086 - `DEEPSEEK_HTTP_HEADERS` (custom model request headers, comma-separated `name=value` pairs)
1087 - `DEEPSEEK_DEFAULT_TEXT_MODEL` (extra legacy alias of `DEEPSEEK_MODEL`)
1088 - `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` (stream idle timeout in seconds; default `900`, clamped to `1..=3600`)
1089 - `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)
1090 - `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.
1091 - `NVIDIA_API_KEY` or `NVIDIA_NIM_API_KEY` (when provider is `nvidia-nim`)
1092 - `NVIDIA_NIM_BASE_URL`, `NIM_BASE_URL`, or `NVIDIA_BASE_URL`
1093 - `NVIDIA_NIM_MODEL`
1094 - `OPENAI_API_KEY`
1095 - `OPENAI_BASE_URL`
1096 - `OPENAI_MODEL`
1097 - `ATLASCLOUD_API_KEY`
1098 - `ATLASCLOUD_BASE_URL`
1099 - `ATLASCLOUD_MODEL`
1100 - `WANJIE_ARK_API_KEY`, `WANJIE_API_KEY`, or `WANJIE_MAAS_API_KEY`
1101 - `WANJIE_ARK_BASE_URL`, `WANJIE_BASE_URL`, or `WANJIE_MAAS_BASE_URL`
1102 - `WANJIE_ARK_MODEL`, `WANJIE_MODEL`, or `WANJIE_MAAS_MODEL`
1103 - `VOLCENGINE_API_KEY`, `VOLCENGINE_ARK_API_KEY`, or `ARK_API_KEY`
1104 - `VOLCENGINE_BASE_URL`, `VOLCENGINE_ARK_BASE_URL`, or `ARK_BASE_URL`
1105 - `VOLCENGINE_MODEL` or `VOLCENGINE_ARK_MODEL`
1106 - `OPENROUTER_API_KEY`
1107 - `OPENROUTER_BASE_URL`
1108 - `OPENROUTER_MODEL`
1109 - `XIAOMI_MIMO_TOKEN_PLAN_API_KEY`, `MIMO_TOKEN_PLAN_API_KEY`, `XIAOMI_MIMO_API_KEY`, `XIAOMI_API_KEY`, or `MIMO_API_KEY`
1110 - `XIAOMI_MIMO_BASE_URL` or `MIMO_BASE_URL`
1111 - `XIAOMI_MIMO_MODEL` or `MIMO_MODEL`
1112 - `XIAOMI_MIMO_MODE` or `MIMO_MODE` (`token-plan-sgp`, `token-plan-cn`,
1113 `token-plan-ams`, or `pay-as-you-go`)
1114 - `NOVITA_API_KEY`
1115 - `NOVITA_BASE_URL`
1116 - `NOVITA_MODEL`
1117 - `FIREWORKS_API_KEY`
1118 - `FIREWORKS_BASE_URL`
1119 - `FIREWORKS_MODEL`
1120 - `HUGGINGFACE_API_KEY` or `HF_TOKEN` (`HF_TOKEN` is a fallback alias accepted when provider is `huggingface`)
1121 - `MODELSCOPE_API_KEY`
1122 - `HUGGINGFACE_BASE_URL` or `HF_BASE_URL`
1123 - `HUGGINGFACE_MODEL` or `HF_MODEL`
1124 - `SILICONFLOW_API_KEY`
1125 - `SILICONFLOW_BASE_URL`
1126 - `SILICONFLOW_MODEL`
1127 - `ARCEE_API_KEY`
1128 - `ARCEE_BASE_URL`
1129 - `ARCEE_MODEL`
1130 - `TOGETHER_API_KEY`
1131 - `TOGETHER_BASE_URL`
1132 - `TOGETHER_MODEL`
1133 - `QIANFAN_API_KEY` or `BAIDU_QIANFAN_API_KEY`
1134 - `QIANFAN_BASE_URL` or `BAIDU_QIANFAN_BASE_URL`
1135 - `QIANFAN_MODEL` or `BAIDU_QIANFAN_MODEL`
1136 - `OPENAI_CODEX_ACCESS_TOKEN` or `CODEX_ACCESS_TOKEN`
1137 - `OPENAI_CODEX_BASE_URL` or `CODEX_BASE_URL`
1138 - `OPENAI_CODEX_MODEL` or `CODEX_MODEL`
1139 - `OPENAI_CODEX_ACCOUNT_ID` or `CODEX_ACCOUNT_ID`
1140 - `ANTHROPIC_API_KEY`
1141 - `ANTHROPIC_BASE_URL`
1142 - `ANTHROPIC_MODEL`
1143 - `ZAI_API_KEY` or `Z_AI_API_KEY`
1144 - `ZAI_BASE_URL` or `Z_AI_BASE_URL`
1145 - `ZAI_MODEL` or `Z_AI_MODEL`
1146 - `STEPFUN_API_KEY` or `STEP_API_KEY`
1147 - `STEPFUN_BASE_URL` or `STEP_BASE_URL`
1148 - `STEPFUN_MODEL` or `STEP_MODEL`
1149 - `MINIMAX_API_KEY`
1150 - `MINIMAX_BASE_URL`
1151 - `MINIMAX_MODEL`
1152 - `DEEPINFRA_API_KEY` or `DEEPINFRA_TOKEN`
1153 - `DEEPINFRA_BASE_URL`
1154 - `DEEPINFRA_MODEL`
1155 - `MISTRAL_API_KEY`
1156 - `MISTRAL_BASE_URL`
1157 - `MISTRAL_MODEL`
1158 - `MOONSHOT_API_KEY` or `KIMI_API_KEY`
1159 - `MOONSHOT_BASE_URL` or `KIMI_BASE_URL`
1160 - `MOONSHOT_MODEL`, `KIMI_MODEL_NAME`, or `KIMI_MODEL`
1161 - `SGLANG_BASE_URL`
1162 - `SGLANG_MODEL`
1163 - `SGLANG_API_KEY` (optional; many localhost SGLang servers do not require auth)
1164 - `VLLM_BASE_URL`
1165 - `VLLM_MODEL`
1166 - `VLLM_API_KEY` (optional; many localhost vLLM servers do not require auth)
1167 - `OLLAMA_BASE_URL`
1168 - `OLLAMA_MODEL`
1169 - `OLLAMA_API_KEY` (optional; many localhost Ollama servers do not require auth)
1170 - `OLLAMA_CLOUD_BASE_URL`
1171 - `OLLAMA_CLOUD_MODEL`
1172 - `OLLAMA_CLOUD_API_KEY` (preferred Cloud key; `OLLAMA_API_KEY` is the official fallback)
1173 For every product-level `CODEWHALE_*` variable below, the matching legacy
1174 `DEEPSEEK_*` name is still read as a compatibility fallback; when both are set,
1175 the `CODEWHALE_*` value wins.
1176
1177 - `CODEWHALE_LOG_LEVEL` or `RUST_LOG` (`info`/`debug`/`trace` enables lightweight verbose logs)
1178 - `CODEWHALE_SKILLS_DIR`
1179 - `CODEWHALE_MCP_CONFIG`
1180 - `CODEWHALE_NOTES_PATH`
1181 - `CODEWHALE_MEMORY` (`1|on|true|yes|y|enabled` turns user memory on)
1182 - `CODEWHALE_MEMORY_PATH`
1183 - `CODEWHALE_TELEMETRY` / `DEEPSEEK_TELEMETRY` (legacy alias) — anonymous usage
1184 counting is on by default in the current 0.9.12 source, with a disclosure
1185 naming Codewhale and PostHog and an easy durable opt-out. Prior explicit
1186 declines remain off. Accepts `0|1|true|false|yes|no|on|off|enabled|
1187 disabled`. An explicit "off" is a **floor**: it beats `--telemetry true` and
1188 `telemetry = true` in config, and a value this list cannot read also resolves
1189 to off, because a typo in a kill switch must never resolve to "on". See
1190 [`TELEMETRY.md`](TELEMETRY.md).
1191 - `CODEWHALE_TELEMETRY_ENDPOINT` / `DEEPSEEK_TELEMETRY_ENDPOINT` (legacy alias)
1192 — `https://`, or plain `http://` only for loopback. Overrides the config file.
1193 Unset selects the shipped default,
1194 `https://telemetry.codewhale.net/v1/telemetry`; setting it to the **empty
1195 string** routes batches to a local dry-run file and contacts nobody. Either
1196 way it only decides where a session sends — it cannot override an opt-out.
1197 - `CODEWHALE_ALLOW_SHELL` (`1`/`true` enables)
1198 - `CODEWHALE_APPROVAL_POLICY` (`on-request|untrusted|never`)
1199 - `CODEWHALE_SANDBOX_MODE` (`read-only|workspace-write|danger-full-access|external-sandbox`)
1200 - `CODEWHALE_NO_NEW_PRIVS` (`0`/`false`/`no`/`off`/`disabled` opts out) — Linux only. The
1201 TUI process sets the kernel's irreversible no-new-privileges flag at startup
1202 as defense-in-depth, which blocks `sudo`/`su`/setuid helpers for Codewhale's
1203 whole process tree. The flag is already skipped when the startup sandbox
1204 mode resolves to `danger-full-access` (#5723), so this variable is the
1205 explicit override in the remaining cases: set it to a falsey value before
1206 launching if you administer through Codewhale as a wheel-group user under a
1207 narrower posture and need escalation to work (#5413), or set a truthy value
1208 to force the flag on even under `danger-full-access`. Unset, the posture
1209 decides; the other startup hardening (no ptrace, no core dumps) always
1210 stays on.
1211 - `CODEWHALE_MANAGED_CONFIG_PATH`
1212 - `CODEWHALE_REQUIREMENTS_PATH`
1213 - `CODEWHALE_MAX_SUBAGENTS` (clamped to `1..=128`)
1214 - `CODEWHALE_TASKS_DIR` (runtime task queue/artifact storage, default
1215 `~/.codewhale/tasks`, with legacy `~/.deepseek/tasks` fallback when only the
1216 legacy directory exists)
1217 - `CODEWHALE_RUNTIME_DIR` (override the runtime thread store root). Interactive
1218 sessions default to `$CODEWHALE_HOME/sessions/<session-id>/runtime` so each
1219 Codewhale process owns its own store (#5630). The store is single-owner: a
1220 second process on the **same** root fails at startup. Set this variable to
1221 share one store across processes, or when the runtime API server should use a
1222 stable non-session path. Unset, the API/server path remains
1223 `$CODEWHALE_HOME/tasks/runtime`. Legacy alias: `DEEPSEEK_RUNTIME_DIR`.
1224 - `CODEWHALE_ALLOW_INSECURE_HTTP` (`1`/`true` allows non-local `http://` base URLs; default is reject)
1225 - `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)
1226 - `CODEWHALE_HOME` (override the base data directory; defaults to `~/.codewhale`).
1227 If you previously exported `DEEPSEEK_HOME`, rename it to `CODEWHALE_HOME`;
1228 the old env var is not used for new Codewhale state paths.
1229 - `CODEWHALE_RELEASE_BASE_URL` (release asset mirror used by `codewhale update`
1230 and by TUI startup update checks when `[update].update_uri` is not set, or as
1231 a fallback when that configured URI cannot be fetched)
1232 - `CODEWHALE_AUTOMATIONS_DIR` (override the automations storage directory; uses
1233 `~/.codewhale/automations` by default, with legacy `~/.deepseek/automations`
1234 fallback when only the legacy directory exists)
1235 - `NO_ANIMATIONS` (`1|true|yes|on` forces `low_motion = true` and
1236 `fancy_animations = false` at startup, regardless of the saved
1237 settings; see [`docs/ACCESSIBILITY.md`](./ACCESSIBILITY.md)).
1238 - `SSL_CERT_FILE` — corporate-proxy / TLS-inspecting MITM users
1239 point this at a PEM bundle (or single DER cert) and the cert(s)
1240 get added alongside the platform's system trust store. Failures
1241 log a warning and continue — the existing system roots still
1242 apply.
1243
1244 ### Instruction sources (`instructions = [...]`, #454)
1245
1246 Add a list of additional system-prompt sources that get
1247 concatenated, in declared order, alongside the auto-loaded
1248 `AGENTS.md`:
1249
1250 ```toml
1251 instructions = [
1252 "./AGENTS.md",
1253 "~/.codewhale/global.md",
1254 "~/team/agents-shared.md",
1255 ]
1256 ```
1257
1258 Rules:
1259
1260 - Paths run through `expand_path` so `~` and env vars work.
1261 - Each file is capped at 100 KiB; oversized files are
1262 truncated with a `[…elided]` marker rather than skipped.
1263 - Missing files are skipped with a tracing warning so a stale
1264 entry doesn't fail the launch.
1265 - Only user-owned config, profiles, and managed config may set this array.
1266 Project config (`<workspace>/.codewhale/config.toml`, or legacy
1267 `<workspace>/.deepseek/config.toml`) ignores `instructions` so a cloned repo
1268 cannot choose arbitrary local files to place into the prompt.
1269
1270 ### Hooks
1271
1272 Hooks are a **TUI runtime feature**. They fire from the interactive TUI and
1273 the engine turn loop it drives; `codewhale exec`, the CLI subcommands, the
1274 app-server / ACP surfaces, and the `workflow` tool do not fire them.
1275
1276 [`docs/HOOKS.md`](HOOKS.md) is the authoritative reference for all eleven hook
1277 events — their firing points, environment variables, stdin payloads, timeout
1278 and background semantics, and which three of them can steer Codewhale. The
1279 sections below cover the configuration surface and the steering contracts in
1280 more depth.
1281
1282 Two contract points worth reading there before writing a hook:
1283
1284 - `background = true` means **submitted and never awaited**. The hook still
1285 gets the documented stdin payload and the same timeout, but it has no exit
1286 code and cannot steer.
1287 - A condition that references context its event never carries (an `exit_code`
1288 condition outside `tool_call_after` / `on_error`, a `mode` condition on
1289 `shell_env`, a tool condition on a non-tool event) is **rejected at load**,
1290 logged, and shown in `/hooks list`. It does not silently never match.
1291 Rejection is per entry, so a broken hook never drops another one that merely
1292 shares its `name` or is likewise unnamed.
1293
1294 ### `/hooks` listing
1295
1296 Run `/hooks` (or `/hooks list`) inside the TUI to see every
1297 configured lifecycle hook grouped by event, including each
1298 hook's name, command preview, effective timeout, and condition. When
1299 `[hooks].default_timeout_secs` is set it replaces every per-hook
1300 `timeout_secs`, and the listing shows that effective value and names the
1301 override rather than echoing the per-hook number. A
1302 `default_timeout_secs = 0` is rejected at load — it would expire every hook
1303 in the config immediately — so the override is ignored, per-hook
1304 `timeout_secs` applies, the listing shows that per-hook value with no
1305 override provenance, and the rejection appears under `configuration
1306 problems`. The
1307 `[hooks].enabled` flag's state is shown at the top so it's
1308 obvious when hooks are globally suppressed, and any entry rejected
1309 at load is listed under `configuration problems` with the reason.
1310 Hooks are configured under `[[hooks.hooks]]` entries — see
1311 [`docs/HOOKS.md`](HOOKS.md) for the full schema.
1312
1313 ### Mutable `message_submit` hooks
1314
1315 `message_submit` hooks run before a submitted message is added to
1316 history or sent to the model. Unlike observer-only lifecycle hooks,
1317 non-background `message_submit` hooks can replace or block the
1318 submitted text.
1319
1320 ```toml
1321 [[hooks.hooks]]
1322 event = "message_submit"
1323 command = "~/.codewhale/hooks/inject-context.sh"
1324 timeout_secs = 2
1325 continue_on_error = true
1326 ```
1327
1328 The hook receives JSON on stdin:
1329
1330 ```json
1331 {
1332 "event": "message_submit",
1333 "text": "original user text",
1334 "text_bytes": 18,
1335 "text_original_bytes": 18,
1336 "text_truncated": false,
1337 "session_id": "sess_12345678",
1338 "workspace": "/path/to/workspace",
1339 "mode": "agent",
1340 "model": "deepseek-chat",
1341 "total_tokens": 1234
1342 }
1343 ```
1344
1345 The entire serialized document is capped at 32 KiB. Codewhale retains the
1346 largest UTF-8-safe `text` prefix that fits after JSON escaping and bounded
1347 metadata, and the three `text_*` fields make truncation explicit. Immediate
1348 messages, restored queue entries, merged steers, and prior-hook replacements
1349 all cross this same serialization boundary.
1350
1351 If the hook exits `0` and prints JSON with a non-empty string `text` field,
1352 that value replaces the submitted text:
1353
1354 ```json
1355 { "text": "replacement user text" }
1356 ```
1357
1358 Exit `0` with empty stdout, or stdout JSON without `text`, leaves
1359 the current text unchanged. A JSON `text` field must not be empty;
1360 `{"text":""}` is treated as invalid stdout and ignored. Exit `2`
1361 blocks the submission before the turn starts; a structured `reason` field can
1362 provide the bounded, redacted status message shown in the TUI. Raw stdout,
1363 stderr, and process-error text are not copied into denial receipts.
1364 Other non-zero exits follow the hook's `continue_on_error` setting.
1365 Timeouts and spawn failures are also surfaced as transient TUI status
1366 messages when `continue_on_error = true` lets submission continue.
1367
1368 Multiple `message_submit` hooks run in config order, and each hook
1369 receives the text produced by the previous hook. Hooks marked
1370 `background = true` are observer-only and cannot transform or block
1371 the message — they still receive the same stdin payload and the same
1372 environment, they are simply never awaited. Existing environment
1373 variables remain available.
1374 `shell_env` hooks keep their existing `KEY=VALUE` stdout contract;
1375 JSON stdout contracts exist for `message_submit` (above) and
1376 `tool_call_before` (below).
1377
1378 ### `tool_call_before` decision hooks
1379
1380 `tool_call_before` hooks run before each tool call executes. In
1381 addition to the legacy hard deny (exit code `2`, which always wins
1382 regardless of stdout), a foreground hook may print a JSON decision on
1383 stdout with exit code `0`:
1384
1385 ```json
1386 {
1387 "decision": "allow" | "deny" | "ask",
1388 "reason": "human-readable explanation (used for deny)",
1389 "updatedInput": { "command": "ls -la" },
1390 "additionalContext": "text appended to the tool result for the model"
1391 }
1392 ```
1393
1394 All fields are optional. Empty stdout, non-JSON stdout, and JSON
1395 without a `decision` field behave exactly as before (allow). An
1396 unrecognized `decision` string logs a fixed warning without echoing the
1397 untrusted value and is treated as allow.
1398
1399 - `deny` blocks the tool; the model receives a permission-denied tool
1400 result containing `reason`.
1401 - `ask` forces the interactive approval prompt in Ask and Auto-Review even for
1402 tools that would otherwise auto-run. Full Access does not open tool-approval
1403 prompts, so hook `ask` does not downgrade that posture.
1404 - `updatedInput` must be a JSON object; it replaces the tool input
1405 before execution. When several hooks supply it, the last hook wins.
1406 - `additionalContext` is appended to the tool result sent back to the
1407 model as `[hook context] ...`. Multiple hooks' contexts are
1408 concatenated.
1409
1410 When multiple hooks match, precedence is deny > ask > allow. Hooks
1411 marked `background = true` cannot steer tool calls — they are
1412 submitted and never awaited, so they have no verdict to contribute.
1413
1414 A foreground hook that produced no verdict at all — it hit its timeout, the
1415 process could not be started, or a strict process exited non-zero without an
1416 explicit JSON decision — is not treated as permission. If
1417 *that* hook is configured with `continue_on_error = false`, the outcome
1418 denies the tool call and the denial names the hook and a bounded reason.
1419 Strictness is read off the hooks that actually matched this call, so a
1420 strict gate scoped to another tool cannot deny it, and a lenient hook's
1421 timeout does not deny merely because a strict hook exists elsewhere in
1422 config. Under the default `continue_on_error = true` the outcome is
1423 logged and the call proceeds.
1424
1425 `reason` and `additionalContext` are capped (2 000 characters per field,
1426 8 000 for the concatenated context of one call) and stripped of control
1427 characters before they reach the TUI or the model.
1428
1429 Example deny hook:
1430
1431 ```toml
1432 [[hooks.hooks]]
1433 event = "tool_call_before"
1434 command = '''echo '{"decision":"deny","reason":"blocked by project policy"}' '''
1435 condition = { type = "tool_name", name = "exec_shell" }
1436 ```
1437
1438 Example ask hook (force approval for every MCP tool):
1439
1440 ```toml
1441 [[hooks.hooks]]
1442 event = "tool_call_before"
1443 command = '''echo '{"decision":"ask"}' '''
1444 condition = { type = "tool_name", name = "mcp__*" }
1445 ```
1446
1447 Example input rewrite:
1448
1449 ```toml
1450 [[hooks.hooks]]
1451 event = "tool_call_before"
1452 command = "~/.codewhale/hooks/clamp-shell-timeout.sh"
1453 condition = { type = "tool_name", name = "exec_shell" }
1454 ```
1455
1456 where the script reads the hook context, then prints
1457 `{"updatedInput": {...}}` with the adjusted arguments.
1458
1459 `tool_name` conditions support `*` globs: `mcp__*` matches every MCP
1460 tool (e.g. `mcp__github__create_issue`) but not built-ins like
1461 `read_file`; exact names keep matching exactly. Other regex
1462 metacharacters in the pattern are matched literally.
1463
1464 ### Project-local hooks
1465
1466 Repositories can ship policy in `<workspace>/.codewhale/hooks.toml`,
1467 using the same shape as the `[hooks]` table (top-level fields plus
1468 `[[hooks]]` entries). Project hooks are executable shell
1469 configuration, so Codewhale only loads them after the workspace has
1470 been trusted in user-owned config through the trust prompt or a
1471 `[projects."<workspace>"] trust_level = "trusted"` entry. Session
1472 `/trust on` mode does not enable repo-supplied hooks by itself, and
1473 repo-local legacy markers such as `.deepseek/trusted` do not enable
1474 project hooks. Once trusted, project hooks are appended after global
1475 hooks from `config.toml`, so they run last and, for `updatedInput`,
1476 win ties. A malformed trusted project file logs a warning and startup
1477 falls back to global hooks only.
1478
1479 ```toml
1480 # .codewhale/hooks.toml
1481 [[hooks]]
1482 event = "tool_call_before"
1483 command = '''echo '{"decision":"deny","reason":"no shell in this repo"}' '''
1484 condition = { type = "tool_name", name = "exec_shell" }
1485 ```
1486
1487 ### Turn-end observer hooks
1488
1489 `turn_end` hooks observe the end of each model turn after post-turn
1490 state, usage totals, cost accounting, notifications, receipts, and
1491 queue recovery have been updated. They receive JSON on stdin and are
1492 observer-only: stdout is ignored, failures are logged as warnings, and
1493 the hook cannot block user input, mutate the transcript, or change the
1494 next queued follow-up.
1495
1496 Observer-only UI events share one 32-entry queue and two persistent workers;
1497 the terminal loop uses non-blocking submission and does not create a thread per
1498 event. A full queue or unavailable dispatcher drops that observer event and is
1499 kept as an event-specific error toast, independent of ordinary agent/turn
1500 status text.
1501
1502 ```toml
1503 [[hooks.hooks]]
1504 event = "turn_end"
1505 command = "~/.codewhale/hooks/turn-audit.sh"
1506 timeout_secs = 2
1507 continue_on_error = true
1508 ```
1509
1510 The payload includes common hook metadata plus post-turn accounting:
1511
1512 ```json
1513 {
1514 "event": "turn_end",
1515 "session_id": "sess_12345678",
1516 "workspace": "/path/to/workspace",
1517 "mode": "agent",
1518 "created_at": "2026-07-12T10:30:00+00:00",
1519 "model_backed": true,
1520 "provider": "deepseek",
1521 "model": "deepseek-chat",
1522 "billing_surface": null,
1523 "turn_id": "turn_12345678",
1524 "status": "completed",
1525 "error": null,
1526 "duration_ms": 1834,
1527 "usage": {
1528 "input_tokens": 1200,
1529 "output_tokens": 180,
1530 "prompt_cache_hit_tokens": 900,
1531 "prompt_cache_miss_tokens": 300,
1532 "prompt_cache_write_tokens": 0,
1533 "reasoning_tokens": null,
1534 "reasoning_replay_tokens": null
1535 },
1536 "totals": {
1537 "session_tokens": 1380,
1538 "conversation_tokens": 1380,
1539 "input_tokens": 1200,
1540 "output_tokens": 180
1541 },
1542 "tool_count": 2,
1543 "queued_message_count": 1,
1544 "stop_hook_active": false
1545 }
1546 ```
1547
1548 `created_at` anchors time-window pricing; `provider` and `model` identify the
1549 effective route used for model-backed turns. `billing_surface` is an optional,
1550 non-secret classification derived from the endpoint that actually served the
1551 turn. Recognized StepFun routes emit `stepfun-payg` or `stepfun-plan`; the raw
1552 base URL is never written to hook or runtime records. Runtime `TurnRecord`
1553 exports call the same field `effective_billing_surface`, which `scorecard`
1554 accepts as an alias. This keeps subscription quota separate from token-priced
1555 usage. Unrecognized and custom endpoints remain `null` and unpriced.
1556
1557 Shell-only lifecycle completions set `model_backed` to `false` and may report a
1558 `null` provider; offline scorecards exclude those records from model token and
1559 cost totals. Completion-only shell, manual-compaction, and purge events that do
1560 not have a matching `TurnStarted` retain the observer notification with a
1561 synthetic `lifecycle_<uuid>` turn id and the time the completion was observed.
1562
1563 For `interrupted` or `failed` turns, `status` reflects that terminal
1564 state and `error` carries the engine error string when one is available.
1565 `stop_hook_active` is reserved for future re-entry protection and is
1566 currently always `false`.
1567
1568 ### Sub-agent lifecycle hooks
1569
1570 `subagent_spawn` and `subagent_complete` hooks observe sub-agent lifecycle
1571 events. They receive bounded JSON metadata on stdin and are observer-only:
1572 hook failures are logged as warnings and do not block sub-agent scheduling,
1573 change prompts, or change results. For these observer events,
1574 `continue_on_error` has no effect: later matching hooks still run even when an
1575 earlier hook exits non-zero.
1576
1577 ```toml
1578 [[hooks.hooks]]
1579 event = "subagent_complete"
1580 command = "~/.codewhale/hooks/subagent-audit.sh"
1581 timeout_secs = 2
1582 continue_on_error = true
1583 ```
1584
1585 `subagent_spawn` receives:
1586
1587 ```json
1588 {
1589 "event": "subagent_spawn",
1590 "agent_id": "agent_12345678",
1591 "session_id": "sess_12345678",
1592 "workspace": "/path/to/workspace",
1593 "mode": "agent",
1594 "model": "deepseek-chat",
1595 "total_tokens": 1234,
1596 "prompt_preview": "bounded prompt preview",
1597 "prompt_truncated": false
1598 }
1599 ```
1600
1601 `subagent_complete` receives the same common fields plus terminal metadata:
1602
1603 ```json
1604 {
1605 "event": "subagent_complete",
1606 "agent_id": "agent_12345678",
1607 "session_id": "sess_12345678",
1608 "workspace": "/path/to/workspace",
1609 "mode": "agent",
1610 "model": "deepseek-chat",
1611 "total_tokens": 1234,
1612 "status": "completed",
1613 "result_preview": "bounded result preview",
1614 "result_truncated": false
1615 }
1616 ```
1617
1618 Previews are capped before delivery so lifecycle hooks do not receive full
1619 sub-agent prompts, transcripts, or unbounded results. Use the transcript handle
1620 returned by `agent` when full sub-agent details are needed.
1621
1622 ### Running-turn input
1623
1624 Composer shortcuts keep the same role throughout a session:
1625
1626 - **Enter** sends when idle and queues a next-turn follow-up while busy. The
1627 behavior does not change before versus after the provider's first token.
1628 - With an empty composer and queued follow-ups visible, **Enter** sends the
1629 oldest queued follow-up into the active turn now.
1630 - **Ctrl+Enter** (or **Cmd+Enter** when the terminal forwards it) explicitly
1631 steers the active turn. It sends normally when idle.
1632 - By default, **Shift+Enter**, **Alt+Enter**, and **Ctrl+J** insert a newline.
1633 - Set `composer_multiline_mode = true` to make **Enter** insert a newline and
1634 **Shift+Enter** send instead. **Alt+Enter**, **Ctrl+J**, and supported
1635 **Ctrl+Enter** / **Cmd+Enter** behavior is unchanged.
1636 - **Ctrl+G** and **Ctrl+S** only stash drafts; they never send or steer.
1637
1638 ### Composer stash (`/stash`, Ctrl+G / Ctrl+S)
1639
1640 Press **Ctrl+G** in the composer to park the current draft to
1641 `~/.codewhale/composer_stash.jsonl`. `/stash list` shows parked
1642 drafts with one-line previews and timestamps; `/stash pop`
1643 restores the most recently parked draft (LIFO); `/stash clear`
1644 wipes the file. Capped at 200 entries; multiline drafts round-trip intact.
1645 **Ctrl+S** remains an alias in terminals that forward it; Cursor and VS Code
1646 reserve Ctrl+S for Save, so Ctrl+G is the portable default.
1647
1648 ## Settings File (Persistent UI Preferences)
1649
1650 codewhale also stores user preferences in:
1651
1652 - `~/.codewhale/settings.toml` on new installs
1653 - `~/.deepseek/settings.toml` or the legacy platform config-dir
1654 `deepseek/settings.toml` when an existing settings file is present
1655
1656 Notable settings include `auto_compact`, which uses a model-aware default-on
1657 policy for known context windows up to the 1M-token V4 class. Automatic
1658 compaction runs before the active model limit and carries the compacted summary
1659 forward into the next request. The trigger defaults to
1660 `auto_compact_threshold_percent = 80`. Users who prefer manual continuity can
1661 persist `auto_compact = false`; manual `/compact` / Ctrl+L remains available.
1662 You can inspect or update these from the TUI with `/settings` and `/config`
1663 (interactive editor).
1664
1665 Common settings keys:
1666
1667 - `theme` (`system`, `terminal`, `underwater`, `underwater-retro`,
1668 `dark`, `light`, `grayscale`, `catppuccin-mocha`, `tokyo-night`,
1669 `dracula`, `gruvbox-dark`, `claude`, `matrix`, `solarized-light`, `uwu`;
1670 default `system`): `system` follows terminal
1671 background detection, `dark`/`light` use the Codewhale Whale pair,
1672 `terminal` inherits the host terminal, `grayscale` is the low-opinion
1673 black/white theme, and the named community presets apply across the TUI.
1674 Aliases such as `whale`, `mono`, `black-white`, `tokyonight`, and `gruvbox`
1675 are accepted. In Whale, cobalt blue owns action/focus, seafoam owns live
1676 work, Signal Gold owns human decisions and the whale, coral owns warnings,
1677 rose owns danger, violet owns Operate, and green remains completed/verified.
1678 Text labels, markers, and motion policy carry the same states when color is
1679 unavailable; color is never the only cue.
1680 User-authored overlays live only at `~/.codewhale/themes/<name>.json` (or
1681 `$CODEWHALE_HOME/themes/<name>.json`) and are selected with
1682 `/theme custom:<name>`. The filename is a bounded slug, symlinks and files
1683 over 64 KiB are refused, colors must be `#RRGGBB`, and unknown fields fail
1684 validation. `/theme schema` prints the embedded JSON Schema and `/theme path`
1685 shows the exact directory. An overlay names one compiled `base` theme and
1686 changes only listed semantic colors; it cannot include or read another file.
1687 Open `/theme` to browse valid overlays, preview them live, and keep the
1688 active `custom:<name>` selector when the picker is opened without moving.
1689 - `auto_compact` (on/off, model-aware default on for known context windows
1690 unless explicitly configured)
1691 - `auto_compact_threshold_percent` (10-100, default `80`): pre-send
1692 auto-compaction threshold used only when `auto_compact` is enabled.
1693 - `paste_burst_detection` (on/off, default on): fallback rapid-key paste
1694 detection for terminals that do not emit bracketed-paste events. This is
1695 independent of terminal bracketed-paste mode.
1696 - `work_surface_placement` (`bottom`, `top`, `left`, `right`, or `off`;
1697 default `bottom`): places the workbar — Tasks / To-do / Workers — under the
1698 composer (the default bottom workbar), above the transcript, in a side
1699 workbar, or hides it entirely (`off`). Side choices fall back to the top
1700 layout on narrow terminals without changing the saved preference. Set it
1701 live with `/config work_surface_placement right --save` (or `left` / `top` /
1702 `bottom` / `off`).
1703 - `rail_panel` (`tasks`, `agents`, `background`, `files`, `notepad`,
1704 `context`, `git`, `price`; default `tasks`, alias key `rail`): which panel
1705 the workbar shows. Panel selection is orthogonal to placement. `tasks` is
1706 the full live work list (to-dos, then sub-agents); `agents` narrows to the
1707 sub-agent rows; `background` lists background shells and automations;
1708 `files` lists touched files; `notepad` shows the workspace notes; `context`
1709 is a read-only session-facts list; `git` shows branch status; `price`
1710 shows cost. In every panel except `context`, rows are selectable and
1711 clickable and open their detail surface. `Alt+!`/`Alt+@`/`Alt+#`/`Alt+$`
1712 switch panels live.
1713 - `work_surface_top_height` (2–16) and `work_surface_side_width` (26–80):
1714 ceilings for the top strip's height and the side workbar's width. Both are
1715 normally persisted by dragging the divider rather than edited by hand; the
1716 strip still auto-fits its content below the ceiling.
1717 - `focus_texture` (`off`, `scrim`, or `grain`; default `off`): focus-context
1718 texture for modal views. `scrim` dims the already-rendered background
1719 outside the focused modal toward the theme surface; `grain` sprinkles
1720 sparse dots over blank cells there. The texture is static (no time
1721 component, so it is unaffected by `low_motion`), never writes over a cell
1722 that carries text, and preserves the 4.5:1 body-text contrast floor
1723 wherever both colors are resolvable. It is skipped entirely on frames
1724 below the ambient-life minimum size and when the focused modal already
1725 covers 90% or more of the frame. Set it live with
1726 `/config focus_texture scrim --save`.
1727 - `mention_menu_limit` (integer, default `128`): maximum number of
1728 `@`-mention popup candidates retained before the composer renders the
1729 visible window. The visible rows still depend on terminal height.
1730 - `mention_walk_depth` (integer, default `10`): maximum workspace depth for
1731 `@`-mention completion walks. Set to `0` for unlimited depth in deeply
1732 nested workspaces; keep the default in very large repos unless needed.
1733 - `mention_menu_behavior` (`fuzzy`, `browser`; default `fuzzy`): controls how
1734 `@`-mention completions are populated. `fuzzy` searches the workspace and
1735 applies mention frecency. `browser` lists only the immediate children of the
1736 currently typed directory segment in deterministic alphabetical order.
1737 - `show_thinking` (on/off)
1738 - `thinking_default_expanded` (on/off, default off): renders thinking blocks
1739 expanded initially when `show_thinking` is enabled. Space still toggles the
1740 selected block, so setting this to `true` inverts the default without
1741 removing per-block folding. This is useful in SSH/tmux environments where
1742 the Space binding may be intercepted.
1743 - `thinking_preview_lines` (integer, default `2`): how many body rows a
1744 **collapsed** completed thought still shows. `0` is header-only; `10` is
1745 the older dump. Live streaming preview is unchanged. Expand a block with
1746 Space, or set `thinking_default_expanded` to open every block.
1747 - `help_expand_groups` (on/off, default off): start Help/shortcuts with every
1748 group expanded. Default folds the long tail (Grok-style); type-to-filter
1749 still unfolds matches.
1750 - `pin_last_prompt` (on/off, default on): pin the last user prompt at the top
1751 of the transcript viewport after it scrolls off.
1752 - `show_tool_details` (on/off)
1753 - `inline_diffs` (`full`, `summary`, or `off`; default `full`): controls the
1754 inline presentation of successful structured File mutations. `full` shows a
1755 bounded red/green diff and semantic statistics, `summary` keeps only the
1756 statistics, and `off` keeps the calm changed-file outcome. All three retain
1757 the exact applied change in the selected File receipt's Alt/Option+V detail.
1758 Failure and cancellation never render a successful diff. Save the choice
1759 with `/config inline_diffs <mode> --save`.
1760 - `locale` (`auto`, `en`, `ja`, `zh-Hans`, `zh-Hant`, `pt-BR`, `es-419`, `vi`,
1761 `ko`; default `auto`): UI chrome locale. `auto` checks `LC_ALL`,
1762 `LC_MESSAGES`, then `LANG`; unsupported locale selections resolve to English.
1763 Every shipped pack holds full `en.json` parity, so no string falls back
1764 to English. The runtime also exposes the resolved locale in the system
1765 prompt as the fallback natural language for V4 reasoning and replies when the
1766 latest user message is ambiguous. Clear user language still takes priority;
1767 Chinese turns should produce Chinese `reasoning_content` and Chinese final
1768 replies even when the resolved locale is English.
1769 - `background_color` (`#RRGGBB`, `RRGGBB`, or `default`): optional main TUI
1770 background color applied to the root, header, transcript, and footer
1771 surfaces while preserving panel contrast.
1772 - `cost_currency` (`usd`, `cny`; default `usd`): currency used by the footer,
1773 context panel, `/cost`, `/tokens`, and long-turn notification summaries. The
1774 aliases `rmb` and `yuan` normalize to `cny`.
1775 - `default_mode` (`agent`, `plan`, or `operate`; legacy values are accepted for migration but are not live mode vocabulary)
1776 - `launch_screen` (legacy, migration-only): this historical `on`/`off` value
1777 is still accepted when reading an existing settings file, but it no longer
1778 changes behavior and is omitted from new saves. A fresh interactive launch
1779 always opens Tideline Startup; only an explicit resume or an explicit
1780 initial prompt enters the live session directly.
1781 - `sidebar_focus` (legacy, migration-only): the classic right sidebar this key
1782 configured was removed in the 0.9.4 rail unification. The key is still read
1783 once so old settings carry forward, then folds into the live keys:
1784 `pinned`/`work`/`plan`/`todos` become `rail_panel = "pinned"`,
1785 `agents`/`subagents` become `rail_panel = "agents"`, `context`/`session`
1786 become `rail_panel = "context"`, `tasks`/`auto` (the old default) become the
1787 `tasks` panel, `sessions` enables `sessions_rail`, and `hidden` turns the
1788 workbar off via `work_surface_placement = "off"`. An explicit `rail_panel`
1789 in the file always wins over the migrated value. Configure the workbar with
1790 `rail_panel` and `work_surface_placement`, not this key.
1791 - `sessions_rail` (`on`/`off`; default `off`): show the persistent Sessions
1792 list in the workbar. Rows list this workspace's recent
1793 non-archived sessions, newest first, with the active one marked; activating a
1794 row opens the session picker preselected on it (`/sessions open <id>`), so
1795 resume keeps its single implementation. Rows are projected from cached
1796 session metadata — the list never reads a transcript per frame, and never
1797 contacts a provider.
1798 - `session_auto_resume` (`on`/`off`; default `off`): reattach to this
1799 workspace's most recent session when Codewhale starts. Off by default so
1800 plain `codewhale` keeps starting fresh. `--resume`, `--continue`, and
1801 `--fresh` always take precedence. When it is on, startup still refuses to
1802 resume a session that is archived, fails to load, or is recorded against a
1803 different workspace; each of those falls back to a fresh transcript and says
1804 which session was skipped and why. It applies to the interactive launch only
1805 — `codewhale "<prompt>"` and `codewhale exec` are never silently prefixed
1806 with a prior conversation.
1807 - `max_input_history` (number of submitted input history entries; cleared
1808 drafts are also kept locally for composer history search). Note the spelling:
1809 the serde field on disk is `max_input_history`
1810 (`crates/tui/src/settings.rs:426`, default 100). `max_history` is the key
1811 name accepted by `/config set` and `settings.set()` (`settings.rs:1388`), not
1812 a settings.toml key — writing `max_history` into the file is silently
1813 ignored.
1814 - `default_model` (model name override)
1815
1816 `/task digest` (alias `/tasks digest`) renders the canonical Work Graph
1817 operations and four-state To-do list as plain text, running work first. It
1818 reads the same snapshots as the styled Work surface and owns no parallel
1819 progress state.
1820
1821 Plan and Act are the everyday visible modes in the UI; Operate is an explicit
1822 preview entry while its Workflow control surface is still being built. Switch
1823 between them with `/mode`. For compatibility, older settings files with
1824 `default_mode = "normal"` still load as `agent`.
1825
1826 Localization scope is tracked in [LOCALIZATION.md](LOCALIZATION.md). The v0.7.6
1827 core pack covers high-visibility TUI chrome only; provider/tool schemas,
1828 personality prompts, and full documentation remain English unless explicitly
1829 translated later.
1830
1831 Readability semantics:
1832
1833 - Selection uses a unified style across transcript, composer menus, and modals.
1834 - Footer hints use a dedicated semantic role (`FOOTER_HINT`) so hint text stays readable across themes.
1835
1836 ### Token Quantities and Drivers
1837
1838 DeepSeek V4 prefix caching makes token labels matter. These quantities are kept
1839 separate:
1840
1841 | Quantity | Meaning | Allowed to drive |
1842 |---|---|---|
1843 | 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. |
1844 | Reserved response headroom | The effective request cap plus `1024` safety tokens on every route. Normal no-override requests start at `65536`; a smaller route/provider ceiling narrows that value, and an explicit output override raises it only within the resolved route window and output ceiling. The identical cap reaches the wire and drives preflight; reasoning effort does not add a second hidden reservation. A separately published route input ceiling independently clamps the spendable input budget. | Emergency overflow budget checks only. |
1845 | 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. |
1846 | 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. |
1847 | Context percent | Active request input estimate divided by the model context window. | Display only; it mirrors the active-input basis used by context safeguards. |
1848 | Cost estimate | Approximate spend from provider usage and configured DeepSeek rates. | Display only. |
1849
1850 For known context-window models, including 1M-class V4 models, replacement
1851 compaction is enabled by default unless the user explicitly configures
1852 `auto_compact = false`. It fires at the active model's compaction threshold and
1853 replaces old history with recent user context followed by one ordinary
1854 checkpoint message. The standing system prompt remains unchanged. Unknown model
1855 ids remain opt-in.
1856
1857 ### Command Migration Notes
1858
1859 If you are upgrading from older releases:
1860
1861 - Old: `/codewhale`
1862 New: `/links` (aliases: `/dashboard`, `/api`)
1863 - Old: `/set model deepseek-reasoner`
1864 New: `/config` and edit the `model` row to `deepseek-v4-pro` or `deepseek-v4-flash`
1865 - Old: visible `Normal` mode or `default_mode = "normal"`
1866 New: use `Agent` / `default_mode = "agent"`; legacy `normal` still maps to `agent`
1867 - Old: discover `/set` in slash UX/help
1868 New: use `/config` for editing and `/settings` for read-only inspection
1869
1870 ## Key Reference
1871
1872 ### Kimi Code membership model IDs
1873
1874 The exact `https://api.kimi.com/coding/v1` endpoint accepts `k3`, `k3-256k`,
1875 `kimi-for-coding`, and `kimi-for-coding-highspeed`. Use `k3-256k` for a fixed
1876 262,144-token K3 window; use bare `k3` with `context_window = 1048576` only
1877 when the membership plan includes the 1M entitlement. Both K3 ids use the same
1878 reasoning contract, and all four membership ids omit generic sampling fields.
1879
1880 ### Core keys (used by the TUI/engine)
1881
1882 - `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`, `ollama-cloud`, `huggingface`, `modelscope`, `together`, `qianfan`, `openai-codex`, `anthropic`, `openmodel`, `zai`, `stepfun`, `minimax`, `deepinfra`, `sakana`, `longcat`, `opencode-go`, `meta`, `mistral`, `telecomjs`, `xai`, `orcarouter`, `modelstudio-token-plan`, `google`, `edenai`, or `custom`. 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`; `modelscope` targets ModelScope's OpenAI-compatible inference API at `https://api-inference.modelscope.cn/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 model-aware route (Chat Completions, Responses, or Messages according to the documented model) at `https://opencode.ai/zen/go/v1`; `meta` targets Meta Model API; `mistral` targets Mistral AI's OpenAI-compatible endpoint at `https://api.mistral.ai/v1`; `telecomjs` targets TelecomJS TokenHub at `https://aigw.telecomjs.com/v1`; and `xai` targets xAI's API-key or OAuth route.
1883 - `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.6`, 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).
1884 - `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.
1885 - `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 local `ollama` can omit it. `ollama-cloud` requires a key saved for that provider or supplied by `OLLAMA_CLOUD_API_KEY`, then `OLLAMA_API_KEY`.
1886 - `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.
1887 - `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-inference.modelscope.cn/v1` for `modelscope`, `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`, `https://api.mistral.ai/v1` for `mistral`, `http://localhost:30000/v1` for `sglang`, `http://localhost:8000/v1` for `vllm`, `http://localhost:11434/v1` for `ollama`, and `https://ollama.com/v1` for `ollama-cloud`. 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. Mistral-specific reasoning fields and polymorphic replay are enabled only on the documented first-party HTTPS `/v1` hosts; a custom Mistral base URL keeps generic Chat semantics. Set `https://api.deepseek.com` or `https://api.deepseek.com/v1` explicitly to opt out of DeepSeek beta features.
1888 - `ollama-cloud` route: select `provider = "ollama-cloud"`, configure `[providers.ollama_cloud]` when overriding the default `https://ollama.com/v1` / `gpt-oss:120b` tuple, and save a key from [Ollama account settings](https://ollama.com/settings/keys) with `codewhale auth set --provider ollama-cloud`. Ambient precedence is `OLLAMA_CLOUD_API_KEY`, then `OLLAMA_API_KEY`; arbitrary Ollama model IDs pass through unchanged.
1889 - Legacy Ollama Cloud migration: a released `provider = "ollama"` config whose normalized `[providers.ollama].base_url` is exactly `https://ollama.com/v1` is upgraded to the `ollama-cloud` runtime identity in memory. Only that exact tuple may read its old `ollama` provider table and secret slot. The config and secrets are never rewritten, and neighboring paths, HTTP downgrades, lookalike hosts, or an explicit `ollama-cloud` selection never consume the fallback.
1890 - `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.
1891 - `edenai` gateway: select `provider = "edenai"`; `[providers.edenai]` defaults to `https://api.edenai.run/v3` and `deepseek/deepseek-v4-pro`. `EDENAI_API_KEY`, `EDENAI_BASE_URL`, and `EDENAI_MODEL` are accepted. Use `EDENAI_BASE_URL = "https://api.eu.edenai.run/v3"` for Eden AI's documented EU endpoint; the default `deepseek/deepseek-v4-pro` is only listed on the global catalog, so pair the EU endpoint with an EU-listed model such as `qwen/deepseek-v4-pro` via `EDENAI_MODEL` or `model`. The provider refreshes Eden AI's `/models` catalog, but leaves model-specific reasoning controls untouched because the gateway spans multiple model families.
1892 - `codewhale` (Codewhale API): select `provider = "codewhale"`; `[providers.codewhale]` defaults to `https://api.codewhale.net/v1` and `deepseek/deepseek-v4-pro`. The credential is a Codewhale account API key (`cwc_key_…`) with the `models:infer` scope, read from `CODEWHALE_API_KEY` or the `codewhale` secret-store slot; `codewhale account api-keys create --name <name> --use` mints one and saves it locally. `CODEWHALE_API_BASE` overrides the origin and must be HTTPS except on loopback. The model catalog is the account's own authenticated `GET /v1/models`: ids are `provider/model` and each row states its wire (`chat-completions` → `/v1/chat/completions`, `anthropic-messages` → `/v1/messages`, `responses` → `/v1/responses`). Connect the underlying provider keys with `codewhale account keys set <provider>`.
1893 - `concentrate` gateway: select `provider = "concentrate"`; `[providers.concentrate]` defaults to `https://api.concentrate.ai/v1` and `deepseek-v4-pro` over the OpenAI Responses wire. `CONCENTRATE_API_KEY`, `CONCENTRATE_BASE_URL`, and `CONCENTRATE_MODEL` are accepted. Model ids pass through verbatim (`gpt-5.6-sol`, `openai/gpt-5.6-sol`, or `concentrate/auto` for the gateway router). BYOK only; see [PROVIDERS.md](PROVIDERS.md#concentrate-notes).
1894 - `mistral` model and reasoning contract: `[providers.mistral]` defaults to `mistral-code-latest`; `MISTRAL_MODEL` overrides it and the generic `CODEWHALE_MODEL` override wins when both are set. The current picker also lists `mistral-medium-latest`, `mistral-small-latest`, and `mistral-large-latest`. On exact first-party HTTPS `/v1` routes, Medium and Small accept only `reasoning_effort = "none" | "high"` and replay polymorphic thinking blocks. Deprecated native Magistral IDs may still be configured explicitly, remain always-reasoning, and never receive the adjustable effort field.
1895 - `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).
1896 - `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.
1897 - `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.
1898 - `[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.
1899 - `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.
1900 - `default_text_model` (string, optional): defaults to `deepseek-flash` for DeepSeek and `deepseek-anthropic`, `gpt-5.6` for OpenAI, `grok-4.6` for xAI, `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.3` 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, `deepseek-v4-flash` for local Ollama, and `gpt-oss:120b` for Ollama Cloud. Hugging Face and Together AI both default to `deepseek-ai/DeepSeek-V4-Pro`; `openai-codex` defaults to `gpt-5.6`; `anthropic` defaults to `claude-sonnet-4-6`; `openmodel` defaults to `deepseek-v4-flash`. Current public DeepSeek IDs include `deepseek-v4-pro` and `deepseek-flash` (V4.1 Flash, shipped as the unversioned id), both with 1M context windows, 384K max output, and thinking mode enabled by default. DeepSeek's live pricing/model page now labels the Pro backend `DeepSeek-V4-Pro-0813`; the callable API ID remains `deepseek-v4-pro`, so Codewhale does not send the backend label or the Claude Code-specific `deepseek-v4-pro[1m]` selector. 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.3` (the default), `GLM-5.2`, `GLM-5.1`, 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` has been live on the Z.ai Coding Plan since 2026-08-13; it inherits its catalog metadata from `GLM-5.2` until Z.ai publishes distinct 5.3 numbers and carries no price, and an explicit `GLM-5.2` selection keeps its own id; 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`, local Ollama, and Ollama Cloud 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.
1901 - 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.
1902 - `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. Ollama Cloud's OpenAI-compatible Chat Completions route preserves its documented `none` / `low` / `medium` / `high` / `max` ladder (`off` is sent as `none`; `xhigh` and `ultracode` normalize to `max`). Direct xAI `grok-4.6` on exact `https://api.x.ai/v1` receives top-level `reasoning_effort = "low" | "medium" | "high" | "xhigh"`; `off` normalizes to `high`, `max`/`ultracode` to `xhigh`, and `auto` leaves the field omitted so xAI's documented default `high` applies. A custom xAI-compatible `base_url` does not inherit that dialect. 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`.
1903 - `verbosity` (string, optional): `normal` or `concise`. `normal` keeps the
1904 default conversational prompt. `concise` appends a prompt discipline block
1905 for direct, low-chatter output; CLI noninteractive commands (`exec` and
1906 `eval`) default to `concise` unless config/env/CLI overrides it.
1907 Override per process with `CODEWHALE_VERBOSITY` or the legacy
1908 `DEEPSEEK_VERBOSITY` alias.
1909 - `telemetry` (bool, optional): anonymous usage counting, **`true` by default
1910 in the current 0.9.12 source**. Notice version `5` names Codewhale and PostHog
1911 and describes the opt-out policy; no acceptance is invented for a default
1912 user. Existing explicit declines remain off. An explicit `false` here is the
1913 durable *opt-out*: it deletes the random install id, truncates buffered and
1914 dry-run events, and leaves a tombstone reasserted while the setting is false.
1915 It is a floor: `--telemetry true` and `CODEWHALE_TELEMETRY=1` lose to it.
1916 Use `/settings` or `codewhale config set telemetry true` to turn counting
1917 back on explicitly for new sessions through the existing privacy transition. `CODEWHALE_TELEMETRY`
1918 (legacy alias `DEEPSEEK_TELEMETRY`) and `--telemetry false` provide a run-scoped
1919 kill switch that stops collection and delivery without erasing the owner's
1920 state. A repo-local `.codewhale/config.toml` cannot set this preference.
1921 `codewhale config telemetry` shows the disclosure; `codewhale config get
1922 telemetry` reports preference and privacy status. Full schema and opt-out
1923 behavior: [`TELEMETRY.md`](TELEMETRY.md).
1924 - `telemetry_endpoint` (string, optional): where batches are POSTed. Leaving it
1925 unset selects the shipped default,
1926 **`https://telemetry.codewhale.net/v1/telemetry`** — the first-party ingest
1927 service described in [`TELEMETRY.md`](TELEMETRY.md), whose source is in
1928 `telemetry-ingest/`. This key decides only *where* a permitted session sends;
1929 it cannot override an opt-out. Setting it
1930 to the **empty string** is how you stay enabled and contact nobody: each batch
1931 is then written to `$CODEWHALE_HOME/telemetry/dryrun.jsonl` and no HTTP client
1932 is constructed at all, so you can read exactly what would have been sent. Any
1933 other value replaces the default outright. `https://` is required; plain
1934 `http://` is accepted only for loopback hosts, and there is no environment
1935 variable that overrides that refusal. A rejected endpoint turns telemetry off
1936 for the run rather than falling back to plaintext or to the default. Override
1937 per process with `CODEWHALE_TELEMETRY_ENDPOINT` (legacy alias
1938 `DEEPSEEK_TELEMETRY_ENDPOINT`), where an empty value means the same "contact
1939 nobody". A repo-local `.codewhale/config.toml` cannot set it.
1940 - `allow_shell` (bool, optional): in interactive TUI Agent sessions, omitting
1941 this keeps shell tools available with approval prompts; setting it to `false`
1942 hides shell tools. Headless, durable-task, and other noninteractive profiles
1943 keep the conservative omitted-field default and require `allow_shell = true`
1944 to expose shell. Plan mode always hides shell; Full Access enables shell and
1945 auto-approval.
1946 - `approval_policy` (string, optional): `on-request`, `untrusted`, or `never`. Runtime `approval_mode` editing in `/config` also accepts `on-request` and `untrusted` aliases.
1947 - `[approval] default_selection` (string, optional): which option an approval
1948 card highlights when it first appears — `deny` (default) or `allow_once`.
1949 `deny` means a reflexive Enter on a card you have not read refuses the call.
1950 Set `allow_once` to restore the pre-v0.9.6 Enter-to-approve muscle memory
1951 (#5293). It moves the highlight only: which calls are prompted for is still
1952 `approval_policy` plus the rules in `permissions.toml`.
1953
1954 ```toml
1955 [approval]
1956 default_selection = "allow_once"
1957 ```
1958 - `[approval] timeout_seconds` (integer, optional): bound how long an
1959 interactive approval card may wait. When the window elapses the card
1960 resolves to **deny** — fail-closed, matching the external approval path —
1961 and the transcript records that the bound denied the call, not the
1962 operator. Omitted or `0` waits indefinitely, which stays the interactive
1963 default; values above 24h clamp with a warning (#6101).
1964
1965 ```toml
1966 [approval]
1967 timeout_seconds = 300
1968 ```
1969 - `sandbox_mode` (string, optional): `read-only`, `workspace-write`, `danger-full-access`, `external-sandbox`.
1970 Platform support is not identical. macOS uses Seatbelt when its runtime
1971 probe succeeds. Linux uses bubblewrap only when `prefer_bwrap = true` and
1972 `/usr/bin/bwrap` is executable; without that opt-in it reports no OS command
1973 sandbox. Windows does not currently advertise an OS sandbox; its planned helper contract starts
1974 with process-tree containment only and must not be described as read-only
1975 filesystem isolation, workspace-write enforcement, network blocking,
1976 registry isolation, or AppContainer isolation until those are implemented.
1977 - The cross-layer relationship between mode admission, hooks, registered tool
1978 requirements, typed rules, auto-review, repo law, human approval, and the
1979 execution sandbox is defined in
1980 [Authorization order](AUTHORIZATION_ORDER.md).
1981 - **Read deny-list.** Every sandbox posture — `read-only` included — grants
1982 read access to the whole filesystem; the postures differ in what they may
1983 *write* and whether they may reach the network. The read deny-list narrows
1984 that:
1985 - `sandbox_read_denylist_defaults` (bool, default `true`): apply the built-in
1986 credential-store set — `~/.ssh`, `~/.gnupg`, cloud credential directories
1987 (`~/.aws`, `~/.config/gcloud`, `~/.azure`, `~/.kube`, …), `~/.netrc`,
1988 `~/.npmrc`, `~/.git-credentials`, macOS keychains, browser profiles,
1989 Codewhale's own secret stores, and `.env` files (but not `.env.example`
1990 and friends). Ordinary source, `Cargo.toml`, `~/.gitconfig`, `~/.cargo`,
1991 and `~/.npm` stay readable so builds and tests still work. Set `false` to
1992 restore the pre-0.9.12 full-disk-read behavior.
1993 - `sandbox_denied_read_paths` (list of paths): additional denied subpaths.
1994 `~` expands. These can never be exempted.
1995 - `sandbox_read_denylist_exempt` (list of paths): subtract a path from the
1996 *built-in defaults* when a project genuinely needs it. Deny wins over
1997 allow: this never reopens anything in `sandbox_denied_read_paths`.
1998
1999 Exemption granularity is **whole-rule**, not per-file. An exempt path
2000 removes a built-in rule only when the rule's own path is at or below it,
2001 so exempting `~/.ssh/config` does nothing: the `~/.ssh` rule still denies
2002 it, because `~/.ssh` does not lie within `~/.ssh/config`. To reopen that
2003 one file you must exempt `~/.ssh` itself — which also reopens the private
2004 keys next to it. That is the documented tradeoff: there is no shipped way
2005 to narrow a built-in rule to "everything except one file"; copy what you
2006 need out of the denied tree instead. The one name-shaped rule, `.env`
2007 files, is exempted by *name* rather than by path: any exempt entry whose
2008 file name is exactly `.env` — bare `.env`, `~/.env`,
2009 `some/project/.env` — disables the entire `.env` filename rule, i.e.
2010 every `.env` and `.env.<name>` on disk rather than one project's.
2011 (`.env.example` and friends are never denied, so they need no exemption.)
2012
2013 Enforced at two points: sandboxed shell commands (Seatbelt last-match-wins
2014 `deny file-read*` rules; bubblewrap masks each path) and Codewhale's own
2015 in-process tools, which the OS sandbox never wraps — `read_file` / `read` /
2016 `read_media` for contents, `list_dir` / `file_search` for *enumeration*
2017 (listing a denied directory, or searching one, is refused just as Seatbelt
2018 blocks its readdir; a name search rooted above a denied tree skips entries
2019 inside it). A refused read is always an explicit error, never an empty
2020 result, and the error names the path as the caller spelled it rather than a
2021 symlink target's real location.
2022
2023 **This is defense-in-depth, not a security boundary.** It does not stop a
2024 hardlink to a denied file, a secret already copied into the workspace, an
2025 indirect read (`ssh-agent`, `security find-generic-password`, `aws sts …`),
2026 reads under `danger-full-access` shell commands, reads by MCP servers or
2027 other unwrapped child processes, or exfiltration of anything that *was*
2028 read. Keep least-privilege credentials and short-lived tokens doing the real
2029 work.
2030 - `permissions.toml` (sibling file, optional): typed permission rule records
2031 loaded next to `config.toml`, for example `~/.codewhale/permissions.toml`.
2032 This active user file is the only permission-rule source today; project
2033 config overlays do not load a project-local `permissions.toml`. A rule's
2034 optional `workspace` field is its repository scope, not a second source.
2035 Manually authored `[[rules]]` entries accept `tool`, optional `command` or
2036 `path`, optional absolute `workspace`, optional `command_exact = true`, and
2037 optional `action = "deny" | "ask" | "allow"`; omitted `action` defaults to
2038 `"ask"`. `workspace` limits a rule to that repository, while
2039 `command_exact = true` changes a command rule from the historical
2040 arity-aware prefix match to a complete-command match. `deny` blocks matching
2041 invocations before mode-based
2042 approval handling, `allow` skips approval for matching invocations, and
2043 `ask` forces approval only in modes that can prompt. Outside the TUI
2044 auto-approve path, a matching `ask` rule under `approval_policy = "never"`
2045 is rejected because no prompt can be shown. In Full Access / auto-approval sessions,
2046 `ask` rules do not downgrade the session into prompting or blocking; explicit
2047 `deny` rules still block according to the current execution-policy logic.
2048
2049 In a supported approval card, press `S` to allow the request once and append
2050 exact `action = "ask"` rules to this file. For eligible safe requests, choose
2051 **Always allow this exact rule in this repo** (shortcut `P`) to append an
2052 `action = "allow"` rule with the current absolute `workspace` scope.
2053 Remembered shell grants set `command_exact = true`, so later commands with
2054 extra arguments do not inherit the grant. File and patch grants retain the
2055 exact workspace-relative paths produced by the existing validation path.
2056 Supported saves are intentionally narrow:
2057 `exec_shell` stores the exact approved command string; `write_file` and
2058 `edit_file` store the exact workspace-relative file path; `apply_patch`
2059 stores one exact workspace-relative `path` rule per validated touched file
2060 from apply-patch preflight. Existing exec command matching remains
2061 arity-aware for manually authored prefix rules; approval-card allow grants
2062 use complete-command matching. File paths are normalized to the same
2063 workspace-relative form used by runtime matching.
2064
2065 `read_file` rules can still be authored manually when you want future reads
2066 of a specific path to ask, allow, or deny, but the approval UI does not save
2067 `read_file` rules. Commands classified as requiring approval or dangerous,
2068 critical approval cards, and repo-law prompts cannot save allow grants and
2069 continue to require review.
2070
2071 `/permissions` (or `/permissions list`) is the narrow rule-management
2072 surface. It lists each numbered rule with the active user-file source, its
2073 exact effective matcher (tool-wide, command prefix, exact command, or exact
2074 normalized path), global or repository scope, and whether that scope
2075 applies in the current workspace. `/config ask-rules` remains a compatibility
2076 entry to the same list.
2077
2078 Deletion is review-gated: `/permissions remove <number>` only previews the
2079 selected rule and prints a confirmation command. That command carries an
2080 opaque token bound to the exact file bytes and rule index; if another writer
2081 changes `permissions.toml`, confirmation fails instead of deleting a rule
2082 that moved into the old position. Confirmed removal and approval-card appends
2083 share the adjacent `permissions.toml.lock`, preserve unrelated TOML comments
2084 and formatting, and atomically replace the file. The running TUI reloads the
2085 user ruleset without clearing session-only approvals.
2086
2087 This editor intentionally does not create or rewrite rules, persist deny
2088 choices from approval cards, expand globs, or create broad
2089 directory/recursive rules. Author those supported exact/prefix records
2090 manually when needed.
2091 - `[[hotbar]]` (array of tables, optional): user-owned 1-8 slot bindings for
2092 the TUI hotbar. Each entry has `slot`, `action`, and optional `label`.
2093 Omitting `hotbar` uses the built-in default eight slots. Setting
2094 `hotbar = []` disables all default slots. When one or more `[[hotbar]]`
2095 tables are present, that list replaces the defaults; missing slots stay
2096 empty. Invalid slots outside `1..=8` are skipped with a warning, duplicate
2097 slots use the later entry, and unknown action IDs are kept so the UI can show
2098 a disabled/unknown cell instead of silently deleting user config. Trusted
2099 user config, profiles, and managed config replace the whole list; project
2100 overlays cannot change hotbar bindings. Setup or wizard flows that persist
2101 hotbar bindings write this same schema to the resolved `~/.codewhale/config.toml`
2102 path, preserving legacy `~/.deepseek/config.toml` only when that fallback file
2103 is already the active config.
2104
2105 ```toml
2106 [[hotbar]]
2107 slot = 1
2108 action = "mode.plan"
2109 label = "Plan"
2110
2111 [[hotbar]]
2112 slot = 2
2113 action = "session.compact"
2114 ```
2115 - `[auto_review]` (table, optional): tool-call review policy — a deterministic floor plus a model guardian tier.
2116 This layer sits on top of the existing permission posture; it can hold or block a
2117 tool call, but it is not an auto-push, auto-merge, or hosted review service.
2118 Block rules are checked first, then the built-in safety floor, then allow
2119 rules. In Ask, a safety hold opens approval; in Auto-Review, Full Access,
2120 or a non-interactive `never` posture it fails closed as a hard block. The
2121 safety floor still covers publish-like actions and destructive
2122 background/headless actions even if an allow rule matches.
2123
2124 ```toml
2125 [[auto_review.allow]]
2126 id = "read-only-inspection"
2127 action_kind = "read"
2128 reason = "Read-only inspection is safe to run automatically."
2129
2130 [[auto_review.block]]
2131 id = "no-release-publish"
2132 action_kind = "publish"
2133 reason = "Release and publish actions require maintainer review."
2134 ```
2135
2136 Rule matchers are exact `tool` and/or `action_kind`. At least one matcher is
2137 required. `action_kind` accepts the six decision-relevant kinds `read`,
2138 `write`, `shell`, `external`, `publish`, and `destructive`. Invalid names
2139 fail config validation instead of silently broadening into another policy
2140 class. In block rules, the old names remain conservative compatibility
2141 aliases: `network`, `git`, `mcp_action`, `browser`, and `unknown` map to
2142 `external`; `secret` maps to `destructive`; and `mcp_read` maps to `read`.
2143 Retired narrow kinds in allow rules fail validation rather than widening to
2144 a broader class. The retired `text_contains` matcher likewise fails
2145 validation instead of silently broadening an old intent-dependent rule.
2146 Fallback holds in interactive Auto-Review escalate to one stateless guardian
2147 request. The request contains the exact held call and deterministic
2148 observations as separate JSON fields. Conversation history, skill
2149 instructions, attached file contents, and other expanded model context are
2150 excluded. The guardian does not infer user intent or compute an authorization
2151 score. It exposes no tools and returns a risk level, allow/deny, and a
2152 rationale. High or critical risk cannot auto-run even if the model says
2153 allow. An oversized exact call is denied rather than truncated. Exactly one
2154 reviewer request is made; incomplete or malformed output, timeout,
2155 cancellation, provider failure, or an empty rationale all fail closed. The
2156 deterministic floor is never model-reviewed, and headless adapters use the
2157 deterministic-only tier. The pinned Codex, Kimi, and DeepSeek source
2158 boundaries are linked from [Permission Posture](MODES.md#permission-posture).
2159 Reviewer outcomes emit `tool.auto_review` audit events with
2160 `gate = "guardian"`.
2161
2162 Auto-review decisions emit `tool.auto_review` audit events with
2163 `gate = "deterministic"` when tool
2164 audit logging is enabled. Future PreToolUse/PostToolUse hooks can add
2165 observer input around this layer, but the configured auto-review policy is
2166 evaluated before a tool call is allowed to proceed.
2167 - `managed_config_path` (string, optional): managed config file loaded after user/env config.
2168 - `requirements_path` (string, optional): requirements file used to enforce allowed approval/sandbox values.
2169 - `max_subagents` (int, optional): defaults to `64` and is clamped to `1..=128`.
2170 - `subagents.*` (optional compatibility table): manual per-role model pins
2171 for direct and Workflow `agent` starts. An explicit saved profile wins,
2172 then a manual role pin, then a unique saved role pin. Conflicting tool
2173 `model` or `model_strength` choices are refused before admission. Unpinned
2174 roles allow task model/strength choices before inherited defaults.
2175 `[subagents.roles.<role>] model = "provider/model"` folds into the existing override
2176 map and wins over `[subagents.models]`, then the convenience keys. Structured
2177 canonical role keys win over legacy aliases. Only this structured syntax
2178 separates the explicit provider from the model suffix; unknown providers
2179 fail before admission. Bare structured model ids inherit the active provider.
2180 Legacy scalar/map values preserve namespaced provider-owned ids unchanged.
2181 Supported convenience keys are
2182 `default_model`, `worker_model`, `scout_model`, `planner_model`,
2183 `reviewer_model`, `custom_model`, `max_concurrent`, `max_admitted`,
2184 `launch_concurrency`, `token_budget`, `api_timeout_secs`, and
2185 `heartbeat_timeout_secs`. The v0.9.x keys `explorer_model`, `awaiter_model`,
2186 and `review_model` remain accepted as aliases. The `[subagents]
2187 max_concurrent` value overrides
2188 top-level `max_subagents` and is also clamped to `1..=128`. `[subagents]
2189 max_admitted` (aliases: `max_total`, `admission_limit`) is the bounded total
2190 of queued plus running sub-agents; it defaults to `1024`
2191 (`MAX_SUBAGENT_ADMISSION`, `crates/tui/src/config/subagent_limits.rs:21`,
2192 applied at `config.rs:6400`) so high-fanout turns can queue and drain while
2193 runtime launch pressure remains bounded, and is clamped to
2194 `max_concurrent..=1024`. `[subagents]
2195 launch_concurrency` sets how many direct children start at once before the
2196 rest queue for a launch slot; it defaults to the resolved `max_subagents` cap
2197 and is clamped to `1..=max_subagents` (the deprecated
2198 `interactive_max_launch` key is accepted as an alias, with the new key
2199 winning when both are set). `[subagents] token_budget` is an optional
2200 aggregate token ceiling for each root `agent` run and its descendants; unset
2201 or `0` preserves unlimited legacy behavior. `[subagents] api_timeout_secs`
2202 controls the per-step API timeout for sub-agent model calls and is clamped to
2203 `1..=3600`, with `0` or unset preserving the 600 second default; a timed-out
2204 attempt is retried with exponential backoff (up to 5 retries) before the
2205 step interrupts with a preserved checkpoint.
2206 `[subagents] heartbeat_timeout_secs` controls stale running agent cleanup,
2207 defaults to `300`, and is clamped to `30..=3600` while staying above the
2208 resolved API timeout. `[subagents.providers.<provider>]` accepts the same
2209 fanout, depth, budget, and timeout knobs (`enabled`, `max_concurrent`,
2210 `max_admitted`, `launch_concurrency`, `max_depth`, `token_budget`,
2211 `api_timeout_secs`, `heartbeat_timeout_secs`) and inherits the global
2212 `[subagents]` value for any key you omit. Provider keys accept canonical
2213 names such as `deepseek`, `zai`, `openrouter`, `anthropic`, plus convenience
2214 aliases such as `glm` for Z.ai and `deepseek_api` for direct DeepSeek:
2215
2216 ```toml
2217 [subagents]
2218 max_concurrent = 20
2219 launch_concurrency = 20
2220 max_admitted = 200
2221 max_depth = 6
2222
2223 [subagents.providers.deepseek]
2224 max_concurrent = 20
2225 launch_concurrency = 20
2226 max_admitted = 200
2227
2228 [subagents.providers.glm]
2229 max_concurrent = 4
2230 launch_concurrency = 3
2231 max_admitted = 12
2232 max_depth = 2
2233
2234 [subagents.providers.openrouter]
2235 max_concurrent = 5
2236 launch_concurrency = 3
2237 max_admitted = 20
2238 ```
2239
2240 `/config subagents status` prints both global values and the active
2241 provider's resolved profile so rate-limit tuning is visible in the TUI.
2242 `[subagents.models]` accepts lower-case Fleet role keys such as `worker`,
2243 `scout`, `planner`, `reviewer`, `builder`, and `verifier`; legacy type keys
2244 remain accepted during v0.9.x. Values are validated
2245 against the active provider at spawn time; direct DeepSeek requires DeepSeek
2246 IDs, while OpenAI-compatible/custom provider routes pass explicit model IDs
2247 through to that provider. To route a child to a different provider than the
2248 parent session, save a Fleet/AgentProfile with explicit `provider` and
2249 `model` fields (including user-named custom providers such as `lm-studio`)
2250 and call `agent(profile: "...")`; see [SUBAGENTS.md](SUBAGENTS.md).
2251 - `skills_dir` (string, optional): defaults to `~/.codewhale/skills` (each skill is
2252 a directory containing `SKILL.md`). Workspace-local `.agents/skills` or
2253 `./skills` are preferred when present; the runtime also discovers global
2254 agentskills.io-compatible `~/.agents/skills` and the broader Claude-ecosystem
2255 `~/.claude/skills`. First launch installs versioned bundled skills for common
2256 workflows including skill creation, delegation, MCP/plugin scaffolding,
2257 documents, presentations, spreadsheets, PDFs, and Feishu/Lark. Only
2258 Codewhale-owned roots (`<workspace>/.codewhale/skills` and
2259 `~/.codewhale/skills`) are writable install/import targets; compatible harness
2260 roots stay read-only. Bare `/skills` opens the Skills Manager (owned-only,
2261 zero network). See [SKILLS.md](SKILLS.md) for the manager, audit statuses,
2262 provenance markers, and mutation rules, and
2263 [CLAUDE_PLUGIN_COMPAT.md](CLAUDE_PLUGIN_COMPAT.md) for the supported boundary
2264 between portable `SKILL.md` bundles and Claude Code plugin runtimes.
2265 - `[skills].scan_codewhale_only` (bool, default `false`): when `true`, session
2266 skill discovery ignores cross-tool roots such as `.claude/skills`,
2267 `.opencode/skills`, `.cursor/skills`, and `~/.agents/skills`. Codewhale still
2268 scans `<workspace>/.codewhale/skills`, `~/.codewhale/skills`, and any explicit
2269 `skills_dir` override. The Skills Manager can still toggle a local compatible
2270 audit scan independently of this runtime knob — see [SKILLS.md](SKILLS.md).
2271 - `[skills].registry_url` / `[skills].max_install_size_bytes` (optional): used by
2272 `/skills --remote`, `/skills suggest <task>`, `/skills sync`, and `/skill
2273 install|update`. The default manager open path does not contact the registry.
2274 - `[verifier].enabled` (bool, default `false`): enables automatic
2275 claim-of-done verifier preview once that runtime trigger is active. The
2276 manual `run_verifiers` tool is still available when this is false.
2277 - `mcp_config_path` (string, optional): defaults to `~/.codewhale/mcp.json`, with
2278 legacy `~/.deepseek/mcp.json` fallback when the Codewhale path is absent.
2279 Custom paths must be absolute; a relative value falls back to the user-global
2280 path so changing the launch directory cannot silently change the MCP pool.
2281 It is visible in `/config` and can be changed from the TUI. The new path is
2282 used immediately by `/mcp`, but rebuilding the model-visible MCP tool pool
2283 requires restarting the TUI.
2284 - `notes_path` (string, optional): defaults to `~/.codewhale/notes.txt`, with
2285 legacy `~/.deepseek/notes.txt` fallback when the Codewhale path is absent, and
2286 is used by the model-visible `note` tool.
2287 - `[memory].enabled` (bool, optional): defaults to `false`. When `true`,
2288 the TUI loads the user memory file into a `<user_memory>` prompt block,
2289 enables `# foo` quick-capture in the composer, surfaces the `/memory`
2290 slash command, and registers the `remember` tool. The same toggle is
2291 available via `DEEPSEEK_MEMORY=on`.
2292 - `memory_path` (string, optional): anchors the native memory store. The
2293 configured filename is **not** the file that is written. Under the Native
2294 backend (the only backend) the store is re-rooted to
2295 `<parent-of-memory_path>/memory/global/MEMORY.md` — so the default
2296 `~/.codewhale/memory.md` yields `~/.codewhale/memory/global/MEMORY.md`
2297 (plus workspace-scoped files and a rebuildable SQLite FTS5 index). See
2298 [`MEMORY.md`](MEMORY.md) for the full feature surface (`# foo` composer
2299 prefix, `/memory` slash command, `remember` tool, opt-in toggle).
2300 - `snapshots.*` (optional): side-git workspace snapshots for file rollback:
2301 - `[snapshots].enabled` (bool, default `true`)
2302 - `[snapshots].max_age_days` (int, default `7`)
2303 - snapshots live under
2304 `~/.codewhale/snapshots/<project_hash>/<worktree_hash>/.git`, with legacy
2305 `~/.deepseek/snapshots/...` fallback when only the legacy state exists, and
2306 never use the workspace's own `.git` directory
2307 - `context.*` (optional):
2308 - `[context].enabled` (bool, default `false`)
2309 - `[context].project_pack` (bool, default `false`): include a deterministic
2310 project context pack (a large pretty-printed directory listing) in the
2311 stable prompt prefix (#4781). Useful for weak tool-calling models; the
2312 model can rebuild the same information with one `File` call.
2313 - The former seam-manager keys (`verbatim_window_turns`, `l1_threshold`,
2314 `l2_threshold`, `l3_threshold`, `seam_model`) are **ignored** — parsed
2315 for backward compatibility but read nowhere since 2026-07-23.
2316 - `compaction.*` (optional, config.toml): how a compaction pass behaves once
2317 it fires. `auto_compact` / `auto_compact_threshold_percent` (settings.toml)
2318 still decide *when* it fires. Both keys are absent by default, and absent
2319 means the built-in behaviour, unchanged:
2320 - `[compaction].summary_instructions` (string, default empty): standing
2321 operator instructions appended to the summarizer prompt as a clearly
2322 delimited "Additional instructions from the operator" section on **every**
2323 pass, manual and automatic — the effort-free counterpart to a one-off
2324 `/compact <focus>`, which still composes after this text. Useful for
2325 "always list exact file paths and line numbers", "always restate open
2326 decisions and their trade-offs", "always write a TL;DR first". Truncated
2327 at 4 000 characters with a warning naming the key; whitespace-only reads
2328 as unset. The summarizer still runs with no system prompt and no tools —
2329 this suffix is the only operator-authored input it sees.
2330 - `[compaction].retained_user_message_tokens` (int, default `20000`, clamped
2331 to `2000`–`200000`; also accepted as `retained_user_message_max_tokens`):
2332 token budget for the recent plain user messages kept **verbatim** in the
2333 replacement history. Raising it keeps more of the user's own earlier
2334 messages instead of only whatever the lossy summary captured; the
2335 last-round survival contract still applies on top. The `/compact` receipt
2336 names the effective budget and whether operator instructions were applied,
2337 so you can tell the knob took effect.
2338 - `retry.*` (optional): retry/backoff settings for API requests:
2339 - `[retry].enabled` (bool, default `true`)
2340 - `[retry].max_retries` (int, default `3`)
2341 - `[retry].initial_delay` (float seconds, default `1.0`)
2342 - `[retry].max_delay` (float seconds, default `60.0`)
2343 - `[retry].exponential_base` (float, default `2.0`)
2344 - `[notifications]`: notification delivery, attention, categories and audio share one
2345 policy. `quiet = true`, `method = "off"`, `condition = "never"` and disabled
2346 categories suppress both the banner and Codewhale's selected sound.
2347 - `notifications.method`: `auto` (default), `osc9`, `kitty`, `ghostty`, `bel`, `off`.
2348 - `notifications.condition`: `unfocused` (default), `always`, `never`. When absent,
2349 the legacy `tui.notification_condition` remains the fallback. `always` also
2350 bypasses the duration threshold; `unfocused` requires two seconds away.
2351 - `notifications.threshold_secs`: nonnegative integer, default `30`.
2352 - `notifications.include_summary`: boolean, default `false`.
2353 - `notifications.sound`: optional `off`, `whale`, `bell`, `beep`, `file`.
2354 A selected value controls audio across enabled categories. Absent keeps legacy
2355 `completion_sound` and `event_sound` choices; `off` overrides both.
2356 - `notifications.sound_file`: custom local WAV path for `sound = "file"` or legacy
2357 `completion_sound = "file"`.
2358 - `notifications.subagent_completion`: `always`, `final-only` (default), `off`.
2359 - `notifications.quiet`: boolean, default `false`.
2360 - `notifications.events`: six boolean categories, all enabled by default; see below.
2361 - `notifications.completion_sound`: legacy completion cue, default `off`, with the
2362 same values as `sound`. Used only when `sound` is absent.
2363 - `notifications.event_sound`: legacy `enabled` (default `false`), `events`
2364 (default `["turn-complete", "approval-needed"]`), and `quiet` (default `false`).
2365 `min_interval_ms` (default `2000`) applies to each category's audio in both modes.
2366 - `tui.alternate_screen` (string, optional, default `auto`): which screen an interactive session starts on. `auto` and `always` start on the TUI-owned alternate screen; `never` starts in inline mode — a ratatui viewport the full height of the terminal with no alternate screen, so the shell's scrollback survives the session and stays scrollable after exit. `/fullscreen` and `/inline` switch it in-process; a switch that the terminal refuses rolls back and says why. Inline mode paints the whole transcript inside its viewport — nothing is written into the host scrollback while the session runs.
2367 - `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 the intersected cells, removes visual wrap-column line breaks from paragraphs, and keeps selection scoped to the transcript pane; the payload is Markdown source by default, see `tui.selection_copy_markdown` below. 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 workbar and include visual wraps because the terminal, not the TUI, owns the selection.
2368 On Linux, finishing a transcript or composer selection quietly copies text to
2369 PRIMARY, leaving the regular clipboard unchanged. Middle-click inside the
2370 composer pastes PRIMARY at the pointer without submitting it. This uses native
2371 X11 or Wayland data control; compositors must support PRIMARY selection. Over
2372 SSH without a forwarded graphical display, use your terminal's selection/paste
2373 gestures or `--no-mouse-capture`. Explicit Copy still uses the regular clipboard.
2374
2375 - `tui.selection_copy_markdown` (bool, optional, default `true`): copy TUI-owned
2376 transcript selections (drag release, context-menu Copy, and `Cmd+C`/`Ctrl+C`
2377 on an active selection) as Markdown source instead of rendered text. Every
2378 intersected cell serializes through the same canonical projection `Ctrl-Y`
2379 and `/copy` use — user and assistant cells keep their authored Markdown,
2380 other cells keep their full transcript form — partial intersections round out
2381 to whole cells, cells join with blank lines, and a toast names the copied
2382 cell count. Set `false` to copy the rendered text as displayed. Composer
2383 selections and the Linux PRIMARY auto-copy are unchanged; PRIMARY always
2384 carries rendered text.
2385
2386 - `tui.terminal_probe_timeout_ms` (int, optional): legacy setting, accepted for configuration compatibility but no longer used. Startup sets raw mode directly after checking terminal ownership; worker scheduling delays do not abort startup.
2387 - `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.
2388 - `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`.
2389 - `tui.max_model_steps` (int, optional, default uncapped): optional model-step ceiling for one ordinary turn. Omission or `0` leaves model steps uncapped; explicit positive values are clamped to `1..=100000`. Headless `exec` and Fleet workers also have no implicit model-step ceiling; `exec --max-turns N` and positive worker budgets still apply. At ~80% of an explicit step budget the model gets one soft-landing notice; at exhaustion the turn ends `Failed` with `Maximum model steps reached before completion (limit: N)` after one bounded final-report response when needed. Cumulative wall-clock and per-stream limits remain independent. Active interactive goal turns use `goal.max_steps` instead (default `1000`); see the Goal loop section below.
2390 - `tui.turn_wall_clock_secs` (int, optional, default `3600`): cumulative per-turn wall-clock budget in seconds, measured across every model step of one turn (not per request). Time blocked on a human approval is excluded. Clamped to `30..=86400` (24 hours is the documented ceiling); `0` resolves to the default. When exhausted the turn stops before authorizing another billable request with a message naming the limit and the key to raise.
2391 - `transcript.prose_measure` (positive integer, optional, default absent = full width): wrap cap, in columns, for prose cells — user messages, assistant answers, and reasoning/thinking blocks — in the live transcript (#5436). Absent (or `0`) spends the full content width, consistent with tool/status cells and the #5322 wide-frame decision; the former 105-column prose rail is gone. Set a positive whole number (e.g. `prose_measure = 120` under `[transcript]`) to restore a bounded reading measure on ultrawide terminals. Narrow terminals always keep their content width — the cap clamps from above only. Tool, diff, and status cells never inherit this cap. Invalid values (negative or non-integer) are rejected at startup with a `transcript.prose_measure` config error. Resolved once per render pass, so the main transcript cache and the full-screen overlay always agree on the effective width.
2392 - `hooks` (optional): lifecycle hooks configuration (see `config.example.toml`).
2393 - `features.*` (optional): feature flag overrides (see below).
2394
2395 ### Workspace notes
2396
2397 `/note` manages a simple notes file in the current workspace at
2398 `.codewhale/notes.md` (legacy `.deepseek/notes.md` is the fallback path when
2399 no `.codewhale/notes.md` exists yet).
2400 Existing `/note <text>` usage still appends a note.
2401 The management forms are:
2402
2403 | Command | Action |
2404 |---|---|
2405 | `/note <text>` | Append a note (legacy shorthand) |
2406 | `/note add <text>` | Append a note explicitly |
2407 | `/note list` | List notes with temporary 1-based numbers |
2408 | `/note show <n>` | Show the full note at number `n` |
2409 | `/note edit <n> <text>` | Replace note `n` with new text |
2410 | `/note remove <n>` | Delete note `n`; `rm` and `delete` are aliases |
2411 | `/note clear` | Empty the workspace notes file |
2412 | `/note path` | Show the resolved workspace notes path |
2413
2414 The numbers shown by `/note list` are not stored in the file; they are derived
2415 from the current order each time notes are read. This keeps the file format
2416 compatible with the existing `---`-separated notes.
2417
2418 ### User memory
2419
2420 User memory is split across one top-level path setting and one opt-in
2421 toggle table:
2422
2423 ```toml
2424 # Anchors the store only — actual writes go to
2425 # ~/.codewhale/memory/global/MEMORY.md (see MEMORY.md).
2426 memory_path = "~/.codewhale/memory.md"
2427
2428 [memory]
2429 enabled = true
2430 ```
2431
2432 Notes:
2433
2434 - `memory_path` stays at the top level beside `notes_path` and
2435 `skills_dir`; it is not nested under `[memory]`.
2436 - The configured path is an **anchor**: its parent directory gains
2437 `memory/global/MEMORY.md`, workspace-scoped files, and `index.db`.
2438 Pointing `memory_path` at the native layout path itself would double-nest
2439 (`…/memory/global/memory/global/MEMORY.md`); keep the legacy-style
2440 anchor filename.
2441 - `DEEPSEEK_MEMORY_PATH` overrides the anchor path from the environment.
2442 - `DEEPSEEK_MEMORY=on` (also `1`, `true`, `yes`, `y`, or `enabled`)
2443 flips the feature on without editing `config.toml`.
2444 - The feature is inert when disabled: no file is injected, `# foo`
2445 falls through to normal message submission, and the model does not
2446 see the `remember` tool.
2447 - See [`MEMORY.md`](MEMORY.md) for examples and the full `/memory`
2448 command surface.
2449
2450 ### Goal loop (`[goal]`)
2451
2452 Operate-mode goals run to their completion gate with no default token, time, or
2453 continuation ceiling (#5052). Token/time budgets, when explicitly supplied,
2454 are telemetry only and do not stop a goal. Users who want a circuit breaker can
2455 opt into one:
2456
2457 ```toml
2458 [goal]
2459 # Optional safety backstop on automatic goal continuation passes.
2460 # Default: 0 (unlimited). Set a positive value to opt into a ceiling.
2461 max_continuations = 100
2462
2463 # Optional cancellable quiet period between successful turns. This is useful
2464 # for coordinator goals that should poll on a cadence instead of keeping one
2465 # provider turn open. Default: 0 (continue immediately).
2466 continuation_delay_seconds = 300
2467
2468 # Per-turn step allowance while a goal is active (#5994). Goal turns get a
2469 # larger but still finite budget than an ordinary interactive turn.
2470 # Default: 1000 (0 or absent resolves to 1000, never unlimited). Range:
2471 # 1..=100,000. This bounds each provider turn, never the number of
2472 # continuation passes.
2473 max_steps = 1000
2474 ```
2475
2476 The effective delay is capped at 86,400 seconds (24 hours); use an automation
2477 for schedules that are less frequent than once per day.
2478
2479 When an explicit backstop fires, the goal pauses with a status message naming
2480 `[goal] max_continuations` and a warning is logged; resume the goal after
2481 inspecting progress, or raise/disable the backstop.
2482
2483 `[goal] max_steps` governs one engine turn at a time: the ordinary interactive
2484 turn has no implicit model-step ceiling. Explicit per-invocation
2485 ceilings — `exec --max-turns N`, child-worker caps — always win over it. At
2486 about 80% of the selected budget the model is told to land; at exhaustion it
2487 gets one bounded final report and the turn classifies as budget-exhausted. An
2488 unfinished goal then pauses with the BudgetLimit reason instead of re-arming
2489 another goal turn — resume it explicitly after reviewing the report. Wall-clock
2490 and stream protections are separate and still apply.
2491
2492 The delay starts only after a successful turn while an explicitly created goal
2493 is still active. `/goal pause`, `/goal done`, `/goal blocked`, `/goal clear`,
2494 Esc, or Ctrl+C cancels a pending continuation before another provider request
2495 starts. Failed turns and policy/route failures never schedule another turn.
2496 Only the numeric cadence is stored in config; no prompt, credential, or secret
2497 is persisted for the loop.
2498
2499 ### Reasoning-only recovery (`[reasoning_only]`)
2500
2501 When a reasoning model (thinking mode) finishes a response with only hidden
2502 reasoning and no answer text or tool call, the engine can automatically
2503 re-request the answer. Configure this behavior with the `[reasoning_only]`
2504 table:
2505
2506 ```toml
2507 [reasoning_only]
2508 # Maximum number of automatic re-requests. Default: 2.
2509 # Set to 0 to disable automatic recovery (fail immediately).
2510 max_reprompts = 10
2511
2512 # Optional custom message sent to the model on each re-request.
2513 # When set, overrides the built-in default message.
2514 # When unset (or commented out), the engine uses:
2515 # "So, what's up ? Keep running !"
2516 reprompt_message = "Allez, répond quelque chose !"
2517 ```
2518
2519 This only applies when the model returns a clean `stop` finish reason with
2520 only thinking content. An output-length stop (`length`/`max_tokens`) is never
2521 retried, and a persistently answerless model still fails honestly after the
2522 configured bound.
2523
2524 To disable the reprompt message entirely (silent retry), set it to an empty
2525 string:
2526
2527 ```toml
2528 [reasoning_only]
2529 reprompt_message = ""
2530 ```
2531
2532 ### Notifications
2533
2534 Notification controls are available in the existing `/config` settings view,
2535 from terminal commands, and through the CLI. All write the same `config.toml`
2536 keys. Terminal changes apply immediately; add `--save` to keep them. CLI writes
2537 apply when the next process loads its configuration.
2538
2539 ```sh
2540 codewhale config set notifications.sound whale
2541 codewhale config set notifications.events.approval-needed false
2542 codewhale config set notifications.quiet true
2543 codewhale config get notifications
2544 codewhale config unset notifications.quiet
2545 ```
2546
2547 ```text
2548 /config notifications sound whale --save
2549 /config notifications condition unfocused --save
2550 /config notifications quiet true
2551 /config notifications status
2552 ```
2553
2554 Nested CLI edits preserve TOML types, unrelated keys and comments. Unset removes
2555 only the selected leaf. `notifications.sound legacy` in the TUI, or CLI unset of
2556 `notifications.sound`, restores previous sound choices. Invalid values are
2557 rejected before file or session changes. In an active TUI profile, saved edits
2558 update its notification table when it owns one; otherwise they update the
2559 inherited root table. The settings detail keeps saved and current values distinct.
2560
2561 ```toml
2562 [notifications]
2563 method = "auto" # auto | osc9 | kitty | ghostty | bel | off
2564 condition = "unfocused" # unfocused | always | never
2565 threshold_secs = 30
2566 include_summary = false
2567 sound = "whale" # optional; sound is opt-in, not enabled by default
2568 quiet = false
2569
2570 [notifications.events]
2571 turn-complete = true
2572 subagent-terminal = true
2573 approval-needed = true
2574 input-needed = true
2575 elevation-needed = true
2576 model-notify = true
2577 ```
2578
2579 `quiet = true` mutes every category without changing saved choices. `method =
2580 "off"` also stops both banner and selected audio. Disabling a category stops its
2581 sound. Attention and duration gates apply before any sink runs. Title animation
2582 completion is silent; only the authorized notification event can request audio.
2583 Successful turn completion is a notification category; failed/cancelled turns do
2584 not create a success notification.
2585
2586 `auto` chooses a recognized terminal protocol or the existing macOS native
2587 fallback; unknown terminals remain unsupported and never invent a bell. `bel`
2588 is an audio-only transport: one selected cue is dispatched, without a second
2589 transport bell. With `sound = "off"`, that transport is silent. `osc9`, `kitty`
2590 and `ghostty` use their terminal notification protocols; tmux passthrough is
2591 preserved. Terminal/OS notification preferences still govern display, attribution
2592 and any sound the host itself adds.
2593
2594 By default the terminal must stay unfocused for two seconds. `condition =
2595 "always"` allows foreground notifications and bypasses the duration threshold;
2596 `"never"` suppresses all delivery. The canonical condition takes precedence over
2597 legacy `[tui].notification_condition`.
2598
2599 The bundled `whale` is a 1.55-second original whale-inspired cue, with no
2600 third-party recording. It remains an opt-in candidate pending listening approval.
2601 WAV playback uses a background worker: macOS `/usr/bin/afplay`, Linux `aplay`
2602 from [ALSA utilities](https://github.com/alsa-project/alsa-utils), or Windows
2603 `PlaySoundW`. A missing player/file or unsupported platform has no fallback bell.
2604 Only one WAV plays at a time. A worker-start receipt is a dispatch attempt, not
2605 proof of audible playback or OS acceptance. The existing macOS `osascript`
2606 banner retains Script Editor attribution; this Core change does not provide a
2607 branded native Apps banner.
2608
2609 #### Previous sound settings
2610
2611 When `notifications.sound` is absent, completion uses a non-off
2612 `completion_sound` selection, and other events use the existing event allow-list.
2613 These are compatibility inputs to the same audio decision, not separate playback
2614 paths. When the global sound is selected, it takes precedence over the previous
2615 completion/event choices. The default remains silent unless a legacy sound or
2616 explicit `bel` transport was already selected.
2617
2618 ```toml
2619 [notifications.event_sound]
2620 enabled = false
2621 events = ["turn-complete", "approval-needed"]
2622 min_interval_ms = 2000
2623 quiet = false
2624 ```
2625
2626 Legacy event cues use one bell for completion, subagent completion, input and
2627 model notices; approval/elevation cues use two. The per-category repeat interval
2628 survives settings refreshes. Old unknown event names are ignored on load; new
2629 CLI/TUI edits require names from the six categories above.
2630
2631 Local approval/input/elevation prompts and error receipts remain available when
2632 external notifications are muted. Action prompts use the selected UI language
2633 and retire when that request settles. Repeated live notices keep their first
2634 expiry; a later routine update does not hide an unresolved warning at completion.
2635 Optional plugin suggestion toasts and contextual tips share one session guidance
2636 budget. `/config contextual_tips off` hides those toasts while preserving
2637 required notices and explicit plugin review requests.
2638
2639 #### What a notification can contain
2640
2641 A desktop notification is a glance surface: on macOS it can appear on the
2642 lock screen, and on every platform it is visible to anyone near the machine.
2643 Codewhale therefore builds notifications from a typed payload with a fixed
2644 per-event disclosure policy rather than from whatever text was on hand:
2645
2646 | Event | Shown | Never shown |
2647 |---|---|---|
2648 | Turn complete | status line (+ elapsed/cost when `include_summary`), preview of the assistant's reply | — |
2649 | Sub-agent finished | status line, agent id, preview of the child's summary line | — |
2650 | Approval needed | the tool name | the tool description, the command, the arguments |
2651 | Input needed | "Answer the question in the terminal to continue" | the question |
2652 | Sandbox elevation needed | the tool name and the denial reason | the command |
2653 | `notify` tool | model-supplied title and body | — |
2654
2655 Every field is capped (80 characters for the status line, 120 for the
2656 identifier, 200 for the preview), stripped of control bytes and escape
2657 sequences, and passed through a redactor that replaces credential-shaped
2658 strings with `[redacted]`, reduces absolute local paths to `…/basename`,
2659 and replaces raw tool JSON with `[details hidden]`. The redactor is
2660 deliberately over-eager: an unbroken 40-character run has no word
2661 structure, so it is redacted even when it is not a secret.
2662
2663 #### macOS: why the banner says "Script Editor"
2664
2665 On macOS terminals that provide no notification escape of their own —
2666 Apple Terminal, the VS Code and JetBrains embedded terminals, plain tmux
2667 without `LC_TERMINAL` — `method = "auto"` falls back to `osascript`'s
2668 `display notification`. That command posts on behalf of the *bundled*
2669 host process, and `/usr/bin/osascript` is unbundled, so macOS attributes
2670 the banner to `com.apple.ScriptEditor2`. That attribution supplies the
2671 Script Editor icon and owns the System Settings → Notifications entry
2672 (alert style, previews, Do Not Disturb). `display notification` has no
2673 icon parameter, so this cannot be fixed from the notification code; it
2674 needs Codewhale to ship a real `.app` bundle. Tracked in
2675 [#4834](https://github.com/Hmbown/CodeWhale/issues/4834). In the meantime,
2676 iTerm2, WezTerm, Ghostty, and kitty are matched first and use their own
2677 notification protocols, and `method = "osc9"` / `"bel"` / `"off"` opt out
2678 of the `osascript` path explicitly.
2679
2680 ## Automations in the terminal
2681
2682 Open `/automation`, then choose **New automation** (`n`) or **Edit automation**
2683 (`e`). The form edits the name, multiline prompt, schedule, model, workspace,
2684 and enabled status. `Tab` moves between fields; `Enter` inserts a newline in
2685 the prompt. Schedule presets include daily, weekly, hourly, once, and a custom
2686 RRULE. Time fields accept `HH:MM`; their arrow controls change the time by
2687 15 minutes. Weekly day buttons and model search support mouse and keyboard.
2688
2689 The next-run preview uses the scheduler's local time zone, shown with its UTC
2690 offset. An enabled automation can run after **Save** (`Ctrl+S`); a paused one
2691 has no scheduled next run. **Cancel** (`Esc`) discards the draft. Editing keeps
2692 existing permission settings, custom schedules, and additional workspace
2693 entries unless the corresponding supported field is explicitly changed.
2694
2695 Schedules are evaluated in the machine's local time zone against the wall
2696 clock. A wall time that does not exist on a spring-forward day is skipped and
2697 an ambiguous fall-back time fires once. Occurrences missed while Codewhale was
2698 closed, asleep, or still running the previous occurrence are coalesced: the
2699 next start runs one catch-up occurrence and then continues from the next
2700 future slot, never replaying every missed slot. An occurrence never starts
2701 while an earlier run of the same automation is still queued or running. Each
2702 run is recorded durably with its status, timing and error; a run that needs a
2703 tool approval has no operator to ask and fails once the approval wait expires.
2704
2705 Choosing a concrete model pins both the model and its exact configured
2706 provider, including named custom routes. Later changes to the active provider
2707 do not move that automation's pin. The default-model choice and legacy
2708 definitions without a provider pin keep the runtime's existing default
2709 behavior. New automation records use schema v2 and tasks use v3 so older
2710 runtimes reject records whose provider pins they cannot preserve.
2711
2712 ## Lifecycle Outbox (`[lifecycle_outbox]`)
2713
2714 The lifecycle outbox is an opt-in, machine-readable stream of session,
2715 turn, and sub-agent lifecycle events. With a path configured, Codewhale
2716 appends one JSON line per event to that file — for interactive TUI
2717 sessions *and* headless `codewhale exec` runs — so a supervisor
2718 (terminal multiplexer wrapper, automation harness, alerting setup) can
2719 react to what happened without scraping the screen or installing per-hook
2720 shell commands. Unset or empty `path` = the feature is **off** and
2721 behavior is unchanged.
2722
2723 ```toml
2724 [lifecycle_outbox]
2725 path = "~/.codewhale/notifications/outbox.jsonl" # unset/empty = OFF
2726 webhook_url = "" # optional; POSTs events as JSON when set
2727 webhook_token = "" # optional bearer token for webhook_url
2728 ```
2729
2730 ### Events emitted
2731
2732 | Event | Kind | Fired at |
2733 |---|---|---|
2734 | `turn_start` | `turn.started` | a new turn begins (TUI TurnStarted; `exec` at message dispatch) |
2735 | `turn_end` | `turn.completed` / `turn.failed` / `turn.interrupted` | turn completion, kind projected from the turn status |
2736 | `turn_stalled` | `turn.stalled` | the stall watchdog recovers a wedged turn |
2737 | `subagent_spawn` | `subagent.spawned` | a sub-agent is spawned |
2738 | `subagent_complete` | `subagent.completed` | a sub-agent reaches a terminal state |
2739 | `session_start` | `session.started` | interactive session start |
2740 | `session_end` | `session.ended` | interactive session end |
2741
2742 ### File contract
2743
2744 Each line is a `RuntimeEventEnvelope`:
2745
2746 ```json
2747 {"schema_version": 1, "seq": 3, "event": "turn_start", "kind": "turn.started",
2748 "thread_id": "…", "turn_id": "…", "item_id": null, "timestamp": "…",
2749 "created_at": "…", "payload": {…}}
2750 ```
2751
2752 - `seq` is monotonic per outbox file and recovers from the last written
2753 line when a new process opens the file.
2754 - Lines are written one complete JSON line per append, serialized by an
2755 internal writer task and flushed before the next event; concurrent
2756 sessions writing the same path do not interleave bytes mid-line, but
2757 separate processes each continue from their own recovered `seq`, so seqs
2758 can repeat across processes sharing one file — prefer one file per
2759 process for strict uniqueness.
2760 - Parent directories are created lazily on the first event.
2761 - Payloads are constructed from bounded, pre-redacted fields only — never
2762 raw tool arguments, environment, or full transcript text. Free-form
2763 fields (error messages, previews) are capped at the notification limits
2764 (80 headline / 120 detail / 200 preview characters) and stripped of
2765 control bytes.
2766
2767 ### Webhook delivery
2768
2769 With `webhook_url` set, every event is additionally POSTed as
2770 `{"at": "<ISO 8601 timestamp>", "event": {…}}` with
2771 `Authorization: Bearer <webhook_token>` when a token is configured.
2772 Delivery is best-effort: failures are logged and dropped, never retried
2773 into the agent loop, and a failing webhook never blocks the local file
2774 append.
2775
2776 ## Control Socket (`[control_socket]`)
2777
2778 Per-session control surface for supervised operation: with the feature
2779 enabled, the interactive TUI binds one unix domain socket per *running*
2780 session at `<sessions-dir>/<session-id>/control.sock` (mode `0600`;
2781 `<sessions-dir>` is the same directory the session store uses, typically
2782 `~/.codewhale/sessions`). The socket is removed with the session's
2783 artifact directory, and a stale socket left by a crashed process is taken
2784 over by the next launch. Unset or `enabled = false` = the feature is
2785 **off** (the default) and behavior is unchanged. Unix-only; on other
2786 platforms the key parses but no socket is bound.
2787
2788 ```toml
2789 [control_socket]
2790 enabled = false # default: OFF
2791 ```
2792
2793 The socket speaks newline-framed JSON-RPC, one request per connection:
2794 write one request line, read one response line, close.
2795
2796 ```json
2797 {"id":"1","method":"message","params":{"text":"hello"}}
2798 {"id":"2","method":"interrupt","params":{}}
2799 {"id":"3","method":"relaunch","params":{}}
2800 {"id":"4","method":"status","params":{}}
2801 ```
2802
2803 - `message` — delivers `text` as a structured user message through the
2804 ordinary composer dispatch path; dispatched immediately when idle,
2805 queued when a turn is in flight (the response's `delivery` field says
2806 which).
2807 - `interrupt` — the Esc-shaped cancel of the active turn; `cancelled`
2808 reports whether active work was in flight.
2809 - `relaunch` — routed through the `/relaunch` slash-command path (same
2810 save-and-resume handoff, no separate mechanics).
2811 - `status` — answers `turn_state` (`idle` / `in_progress` / `waiting`) and
2812 `goal` (`objective`, `status`, `paused`).
2813
2814 Success responses echo the request id with a `type`-tagged result;
2815 failures carry `error.code` (`invalid_request`, `command_error`,
2816 `timeout`, `server_unavailable`). Requests are bounded at 1 MiB per line
2817 and a handler that does not answer within 5 s is reported as `timeout`.
2818
2819 ## Tool Catalog
2820
2821 Codewhale loads a small core native tool catalog by default and leaves less
2822 common native tools discoverable through ToolSearch. To keep specific native
2823 tools loaded on every request, add them to `[tools].always_load`:
2824
2825 ```toml
2826 [tools]
2827 always_load = ["Git", "notify"]
2828 ```
2829
2830 ### `request_user_input` limits
2831
2832 `request_user_input` asks the user a short batch of multiple-choice questions.
2833 Both ceilings are configurable (#5949): raise `user_input_max_questions` when a
2834 research or planning workflow legitimately needs more clarifications, lower it
2835 when interactive triage should stay terse.
2836
2837 ```toml
2838 [tools]
2839 user_input_max_questions = 6 # default 6, clamped to 1..=10
2840 user_input_max_options = 4 # default 4, clamped to 2..=10
2841 ```
2842
2843 The effective values are applied in three places at once: the tool's JSON
2844 schema (`minItems` / `maxItems`), its model-visible description, and the
2845 payload validator. A rejected payload names the key to raise, so the model can
2846 either resize the batch or tell the user which setting to change.
2847
2848 ### User-input / approval wait timeout
2849
2850 Questions from `request_user_input` and approval decisions wait a bounded
2851 time and then cancel with a timeout (#6003). The default is 300 seconds.
2852 Raise it when you step away or read carefully, or set `0` to wait forever
2853 (overnight automation, long human review). Headless `exec` runs have no
2854 responder, so `request_user_input` is withheld there by default:
2855 the model reports the tool absent and finishes instead of stalling.
2856
2857 ```toml
2858 [tools]
2859 user_input_timeout_seconds = 300 # default 300; 0 disables the timeout; clamped to 86400 (24h)
2860 ```
2861
2862 The one key governs both the interactive question wait and the Runtime
2863 approval-decision wait, and the wait is this table's only user-facing clock —
2864 wall-clock and stream protections elsewhere are unaffected.
2865
2866 ## Feature Flags
2867
2868 Feature flags live under the `[features]` table and are merged across profiles.
2869 Defaults are enabled for built-in tooling, so you only need to set entries you
2870 want to force on or off.
2871
2872 ```toml
2873 [features]
2874 shell_tool = true
2875 subagents = true
2876 web_search = true # enables deferred Web; the flag name is retained for config compatibility
2877 apply_patch = true
2878 mcp = true
2879 exec_policy = true
2880 ```
2881
2882 You can also override features for a single run:
2883
2884 - `codewhale --enable web_search`
2885 - `codewhale --disable subagents`
2886
2887 Use `codewhale features list` to inspect known flags and their effective state.
2888 The native `/config` view also includes a read-only **Experimental** section
2889 for experimental feature flags. It shows each flag's effective enabled/disabled
2890 state and whether that state comes from the default or a configured override.
2891 Change feature flags in `[features]` or with `--enable` / `--disable`; the
2892 `/config` section is an audit surface, not a stability promise. Goal and
2893 Workflow preview rows may appear there as reserved entries until those workflows
2894 graduate behind real gated flags.
2895
2896 ## Web Search Provider
2897
2898 `web_search` uses keyless Firecrawl by default. Runtime failure or an exhausted
2899 keyless quota degrades visibly through DuckDuckGo and Bing. China deployments
2900 can explicitly select Baidu, Metaso, Volcengine, or a trusted SearXNG endpoint;
2901 Codewhale does not guess geography from locale or model provider.
2902
2903 Configured API providers are attempted first. Runtime failure or an empty
2904 result visibly degrades through DuckDuckGo and then Bing; the structured search
2905 receipt records every hop. Missing configuration and network-policy denials
2906 fail closed without sending the query to another provider.
2907
2908 For a private/internal search service that serves DuckDuckGo-compatible HTML,
2909 keep `provider = "duckduckgo"` and set `base_url`; Codewhale appends the `q`
2910 query parameter to that endpoint and applies network policy to its host.
2911 Custom endpoints do not fall back to public Bing. `CODEWHALE_SEARCH_BASE_URL`
2912 can override this per process; `DEEPSEEK_SEARCH_BASE_URL` remains accepted as
2913 the legacy alias.
2914
2915 **SearXNG** ([docs](https://docs.searxng.org/dev/search_api.html)) uses the
2916 configured instance's JSON API. Set `provider = "searxng"` and
2917 `base_url = "https://your-searxng.example"`; Codewhale calls
2918 `/search?q=...&format=json`. Codewhale does not use a public SearXNG instance
2919 by default because public instances often disable JSON output or rate-limit API
2920 traffic.
2921
2922 Self-host it as a separate process (Docker is fine); Codewhale never bundles or
2923 manages the search engine itself:
2924
2925 - Enable JSON on the instance (`settings.yml`, `search.formats` must include
2926 `json`) and restart it. An HTML-only instance answers the API with HTTP 403;
2927 Codewhale reports that as a JSON/API-access problem on the SearXNG hop rather
2928 than silently returning no results.
2929 - Bind it to loopback, or to a host and port your network policy allows. The
2930 instance keeps its own engine list, limiter, and limits.
2931 - Point Codewhale at it with `[search] provider = "searxng"` and `base_url`
2932 (required; either the root URL or the `/search` endpoint). No instance ships
2933 as a default, and none is discovered automatically.
2934 - `codewhale doctor --probe-search` sends a transport-only `HEAD` to that
2935 origin — no `q=`, no credentials, no redirects, no audit receipt — so a green
2936 probe proves reachability and network-policy admission, not that JSON is on.
2937
2938 Confirm the JSON API itself before assuming a Codewhale bug:
2939
2940 ```sh
2941 curl -sS "$BASE/search?q=codewhale&format=json" | jq '.results[0] | {title,url,score}'
2942 ```
2943
2944 Codewhale ranks the returned rows by `score`, highest first, and applies
2945 `max_results` to that ranking; rows an instance reports without a usable score
2946 keep their original relative order.
2947
2948 **Metaso** ([metaso.cn](https://metaso.cn)) requires a user-supplied key. Set
2949 `METASO_API_KEY` or `[search] api_key`; Codewhale does not ship a shared key.
2950
2951 **Firecrawl** ([docs](https://docs.firecrawl.dev/sdks/cli)) searches Firecrawl
2952 Cloud without a key using its bounded per-IP daily quota. Set
2953 `FIRECRAWL_API_KEY` or `[search] api_key` for authenticated limits. Codewhale
2954 sends no `Authorization` header in keyless mode.
2955
2956 **Baidu** uses Baidu AI Search at
2957 `https://qianfan.baidubce.com/v2/ai_search/web_search`. Set
2958 `BAIDU_SEARCH_API_KEY` or `[search] api_key`. This is a search-tool backend
2959 only; it does not add a Baidu model provider.
2960
2961 **Sofya** ([sofya.co](https://sofya.co)) returns full extracted page content
2962 rather than snippets. Set `[search] api_key` to your `ay_live_...` key, or the
2963 `SOFYA_API_KEY` env var. This is a search-tool backend only; it does not add a
2964 Sofya model provider.
2965
2966 **Serply** ([serply.io](https://serply.io)) returns Google organic results with
2967 title, URL, and snippet. Set `[search] api_key` to your Serply key, or the
2968 `SERPLY_API_KEY` env var. This is a search-tool backend only; it does not add a
2969 Serply model provider.
2970
2971 **Tavily** ([tavily.com](https://tavily.com)) is selected automatically when a
2972 Tavily key is present and no provider is pinned: `TAVILY_API_KEY` set, or
2973 `[search] api_key` / `CODEWHALE_SEARCH_API_KEY` in the `tvly-` family. Doctor
2974 reports that as `source: tavily key`. Autodetect is runtime-only — Codewhale
2975 never writes `[search] provider` for it, and `TAVILY_API_KEY` is never merged
2976 into `[search] api_key`. An explicit `[search] provider` or
2977 `CODEWHALE_SEARCH_PROVIDER` always wins, so `provider = "firecrawl"` keeps
2978 Firecrawl even with a Tavily key in the environment. Pinned `tavily` accepts
2979 any non-empty `[search] api_key` and is configured by that key or
2980 `TAVILY_API_KEY`; with both empty it fails closed.
2981
2982 ```toml
2983 [search]
2984 provider = "firecrawl" # also duckduckgo | bing | tavily | bocha | metaso | searxng | baidu | volcengine | sofya | serply
2985 # base_url = "https://search.example/" # optional with provider = "duckduckgo"; required with "searxng"
2986 # api_key = "YOUR_KEY" # optional for firecrawl; required by the other API providers
2987 ```
2988
2989 ## Local Media Attachments
2990
2991 Use `@path/to/file` in the composer to add local text file or directory context
2992 to the next message. Use `/attach <path>` for local image/video media paths, or
2993 `Ctrl+V` to attach an image from a local clipboard or an explicitly forwarded
2994 X11/Wayland clipboard. SSH terminal paste without a forwarded graphical display
2995 is text-only; use the local terminal's paste command (`Cmd+V` on macOS or
2996 `Ctrl+Shift+V` on Linux/Windows), and use `/attach <path>` for remote image
2997 files. OpenSSH loopback X11 displays are detected automatically. For an
2998 explicitly forwarded Wayland or non-loopback X11 display, set
2999 `CODEWHALE_SSH_CLIPBOARD=graphical`; set it to `terminal` to force terminal
3000 transfer instead of an ambient remote display. DeepSeek's public Chat
3001 Completions API currently accepts text message
3002 content, so media attachments are sent as explicit local path references instead
3003 of native image/video payloads.
3004 Attachment rows appear above the composer before submit; move to the start of
3005 the composer, press `↑` to select an attachment row, then press `Backspace` or
3006 `Delete` to remove it without editing the sample text by hand.
3007
3008 ## Managed Configuration and Requirements
3009
3010 codewhale supports a policy layering model:
3011
3012 1. user config + profile + env overrides
3013 2. managed config (if present)
3014 3. requirements validation (if present)
3015
3016 By default on Unix:
3017 - managed config: `/etc/deepseek/managed_config.toml`
3018 - requirements: `/etc/deepseek/requirements.toml`
3019
3020 Requirements file shape:
3021
3022 ```toml
3023 allowed_approval_policies = ["on-request", "untrusted", "never"]
3024 allowed_sandbox_modes = ["read-only", "workspace-write"]
3025 ```
3026
3027 If configured values violate requirements, startup fails with a descriptive error.
3028
3029 ## Notes On `codewhale doctor`
3030
3031 `codewhale doctor` follows the same config resolution rules as the rest of the
3032 TUI. That means `--config`, `CODEWHALE_CONFIG_PATH`, and the legacy
3033 `DEEPSEEK_CONFIG_PATH` are respected, and MCP/skills
3034 checks use the resolved `mcp_config_path` / `skills_dir` (including env overrides).
3035
3036 To bootstrap missing MCP/skills paths, run `codewhale setup --all`. You can
3037 also run `codewhale setup --skills --local` to create a workspace-local
3038 `./skills` dir.
3039
3040 Both plain `codewhale doctor` and `codewhale doctor --json` are structural and
3041 offline by default. They do not check the release service, hosted provider
3042 APIs, local provider endpoints, or MCP processes, and they do not load a
3043 workspace credential `.env`. Use `--check-updates`,
3044 `--probe-api`, `--probe-local`, or `--probe-mcp` to opt into the corresponding
3045 live boundary; `--probe-local` may start a desktop-managed service such as
3046 Ollama. Only the explicit API/local probe paths may load workspace credential
3047 `.env` values. Live flags conflict with `--json`, so machine-readable doctor
3048 output is always offline. Top-level keys include `version`, `paths`, `secret_backend`,
3049 `config_path`, `config_present`, `workspace`, `api_key.source`,
3050 `api_key.availability`, `base_url`,
3051 `default_text_model`, `mcp`, `skills`, `tools`, `plugins`, `sandbox`,
3052 `platform`, `api_connectivity`, and `capability`. CI consumers should rely on
3053 `api_key.source` (`config_declared`/`env_declared`/`external_auth_declared`/
3054 `secret_store_unprobed`/`secret_store_unavailable`/`oauth_unprobed`/
3055 `external_consent`/`none`/`local_runtime`/`unknown`) and
3056 `api_key.availability`
3057 (`present`/`not_required`/`not_probed`/`unavailable`/`unknown`) rather than parsing the
3058 human-readable `doctor` text. Source is declaration metadata, not proof that a
3059 credential exists or works. Only a non-empty, non-sentinel literal config value
3060 is structurally `present`; no-auth and local routes are `not_required`. Environment,
3061 external-auth, OAuth, consent, and secret-store declarations remain `not_probed`
3062 and cannot make structural Setup or Fleet readiness true. A secret-store sentinel
3063 on a named/custom endpoint that is prohibited from using the shared store is
3064 `secret_store_unavailable`/`unavailable`, while `unknown` remains reserved for
3065 the absence of a supported structural conclusion. Exact and whitespace-wrapped
3066 legacy sentinels are never treated as literal credentials. The structural
3067 loader still honors safe environment routing/model/policy fields, but it never
3068 materializes environment HTTP headers, sandbox API keys, or search API keys;
3069 only an explicit API/local probe switches to the normal credential-loading
3070 path. An opted-in update check also emits only typed generic failures: untrusted
3071 release metadata and transport errors are not echoed.
3072
3073 If configuration loading or validation fails, `doctor --json` returns nonzero
3074 and prints a bounded JSON error envelope with
3075 `status = "error"` and `error.kind = "config_validation"`. It does not emit a
3076 normal route or capability report—or the underlying possibly sensitive error—
3077 for an invalid configuration.
3078
3079 MCP entries are configuration diagnostics unless an explicit MCP command is
3080 run. `mcp.probe_scope` is `configuration`, `mcp.live_health_checked` is false,
3081 and each server separates `checks.configuration` / `checks.command` from
3082 `checks.process_reachable`, `checks.protocol_initialized`, and
3083 `checks.backend_tool_health`. The latter three remain `not_checked` in doctor
3084 output. Run `codewhale mcp validate` to explicitly start enabled servers and
3085 verify protocol initialization/discovery; backend health still requires an
3086 appropriate explicit tool call. Doctor reports only safe structural MCP fields:
3087 URL userinfo/path/query/fragment and raw command arguments, environment values,
3088 header values, and token material are omitted. Provider URLs follow the same
3089 rule and expose only `scheme://host[:explicit-port]`.
3090
3091 The `capability` key contains per-provider capability info derived from
3092 static knowledge (release docs, API guides) rather than live API probes.
3093 Top-level sub-keys: `resolved_provider`, `resolved_model`, `context_window`,
3094 `max_output`, `thinking_supported`, `cache_telemetry_supported`,
3095 and `request_payload_mode`.
3096
3097 Use `capability.context_window` and `capability.max_output` for model-limit
3098 checks in CI scripts; do not treat `capability.max_output` as the per-turn
3099 request budget. Use `capability.thinking_supported` to decide whether to
3100 configure reasoning effort.
3101
3102 ## Setup status, clean, and extension dirs
3103
3104 `codewhale setup` accepts a few flags beyond the existing `--mcp`,
3105 `--skills`, `--local`, `--all`, and `--force`:
3106
3107 - `--status` — print a compact one-screen status (api key, base URL, model,
3108 MCP/skills/tools/plugins counts, sandbox, `.env` presence). Read-only and
3109 network-free; safe to run in CI. If `.env` is missing and `.env.example` is
3110 present in the workspace, the status output points at `cp .env.example .env`.
3111 - `--tools` — scaffold `~/.codewhale/tools/` with a `README.md` describing the
3112 self-describing frontmatter convention (`# name:` / `# description:` /
3113 `# usage:`) and an `example.sh` that follows it. The directory is
3114 intentionally not auto-loaded; wire individual scripts into the agent via
3115 MCP, hooks, or skills.
3116 - `--plugins` — scaffold `~/.codewhale/plugins/` with a `README.md` and an
3117 `example/plugin.toml` plus a namespaced example Skill. Bundles are discovered
3118 read-only, untrusted, and disabled; review them through `/plugin` before
3119 enabling. v0.9.1 activates only declared Skills and MCP servers. See
3120 [PLUGIN_BUNDLES.md](PLUGIN_BUNDLES.md).
3121 - `--all` now scaffolds MCP + skills + tools + plugins together.
3122 - `--clean` — list `~/.codewhale/sessions/checkpoints/latest.json` and
3123 `offline_queue.json` if they exist. Legacy
3124 `~/.deepseek/sessions/checkpoints/` files are not scanned automatically; set
3125 `CODEWHALE_HOME=~/.deepseek` for a one-off legacy cleanup. Pass `--force` to
3126 actually remove matched files. This never touches real session history or the
3127 task queue.
3128
3129 `--status` and `--clean` are mutually exclusive with the scaffold flags.
3130
3131 ## Why the engine strips XML/`[TOOL_CALL]` text
3132
3133 codewhale sends and receives tool calls only over the API tool channel
3134 (structured `tool_use` / `tool_call` items). The streaming loop in
3135 `crates/tui/src/core/engine.rs` recognizes a fixed set of fake-wrapper start
3136 markers — `[TOOL_CALL]`, `<codewhale:tool_call`, `<tool_call`, `<invoke `,
3137 `<function_calls>` — and scrubs them from visible assistant text without ever
3138 turning them into structured tool calls. When a wrapper is stripped, the loop
3139 emits one compact `status` notice per turn so the user can see why their
3140 visible text shrank. Treat any change that re-enables text-based tool
3141 execution as a regression; the protocol-recovery tests in
3142 `crates/tui/tests/integration/protocol_recovery.rs` lock the contract.
3143
3144 ## Model-bound redaction (`[redaction] model_bound`)
3145
3146 Codewhale masks credential-looking values in tool output **before it is sent
3147 to an upstream model** — the "model boundary". A file read by a tool can
3148 contain a configured API key, a bare provider token, or a credential-shaped
3149 opaque string, and the model must not see those bytes. This backstop is
3150 separate from the display/export scrubbers: it decides what the model itself
3151 can quote back, and it is deliberately conservative (`CredentialShaped`
3152 policy, see `crates/config/src/persistence.rs`), so ordinary code and config
3153 stay byte-exact while keys, JWTs, bearer tokens, PEM blocks, and long opaque
3154 runs are masked.
3155
3156 Turning that masking **off** is a security decision, so it is not a plain
3157 boolean:
3158
3159 ```toml
3160 [redaction]
3161 model_bound = "disabled" # "enabled" (default) | "disabled"
3162 ```
3163
3164 Setting `"disabled"` only records a *request*. It takes effect only when all
3165 of these are true:
3166
3167 1. You restart the interactive TUI.
3168 2. The startup gate appears and you press `1`/`Y` on its first stage
3169 ("confirm and disable"). This only advances to a second, final-confirmation
3170 stage - the gate repeats the red warning and asks "are you really sure?".
3171 3. On that second stage you press `1`/`Y` again. The gate is rendered with the
3172 same explicit-key discipline as workspace trust - `Enter` never confirms by
3173 reflex, and `2`/`U` on the second stage steps back.
3174 4. Only that second confirmation persists a receipt to
3175 `~/.codewhale/redaction-state.json` (next to `config.toml`) and rebuilds
3176 the engine with masking off for the rest of this launch and future ones.
3177
3178 The receipt is bound to the config it was made against and is valid only
3179 while that config still requests `"disabled"`. Setting `model_bound` back
3180 to `"enabled"` - or rewriting `config.toml` in any way after the
3181 confirmation - invalidates it, so requesting `"disabled"` again later
3182 asks for a fresh confirmation on the next launch. The receipt checks both the
3183 config contents and modification time; missing, unreadable, or malformed config
3184 and older receipts without this binding keep masking enabled. Legacy-home
3185 installs store the receipt beside their resolved config file.
3186
3187 Until a confirmation exists, the effective mode is always `"enabled"`:
3188
3189 - Choosing `2`/`U` ("keep masking on") leaves the config field untouched, so
3190 the next launch asks again. Edit the field back to `"enabled"` to stop being
3191 asked.
3192 - Non-interactive entry points (`codewhale exec`, hooks, automations, headless
3193 agents) never confirm anything and never apply an unconfirmed request.
3194 - Routing/classification summaries and durable goal-state text keep their own
3195 always-on redaction regardless of this switch; the opt-out exists so the
3196 model can quote file bytes for exact edits, not to relax stored state.
3197
3198 The config value itself is forgiving: `true`/`false`, `"on"`/`"off"`, and
3199 `"enabled"`/`"disabled"` (any casing) all parse, with `false`/`"off"` meaning
3200 `"disabled"`.
3201
3202 A confirmed opt-out still sends your configured API keys to the provider you
3203 are already talking to. Only use it when the model must read and edit files
3204 that contain real credentials.
3205
3205 lines MARKDOWN