返回 CodeWhale
config.example.toml
根目录 / config.example.toml
1 # ╔══════════════════════════════════════════════════════════════════════════════╗
2 # ║ Codewhale Configuration ║
3 # ║ ║
4 # ║ Terminal coding agent for any model — open models first. ║
5 # ╚══════════════════════════════════════════════════════════════════════════════╝
6
7 # See `docs/CONFIGURATION.md` for how config is loaded (profiles, env overrides, etc.).
8
9 # ─────────────────────────────────────────────────────────────────────────────────
10 # Active provider + DeepSeek defaults
11 # ─────────────────────────────────────────────────────────────────────────────────
12 # Choose which provider to use by default. Per-provider credentials live in the
13 # `[providers.*]` sections near the bottom of
14 # this file — keeping both stored at once means `/provider deepseek` and
15 # `/provider nvidia-nim` (or `--provider openai`, `--provider wanjie-ark`,
16 # `--provider volcengine`, `--provider openrouter`, `--provider xiaomi-mimo`,
17 # `--provider fireworks`, `--provider siliconflow`, `--provider siliconflow-CN`,
18 # `/provider arcee`, `/provider moonshot`, `/provider qianfan`, `/provider sglang`, `/provider vllm`,
19 # `/provider ollama`, `/provider huggingface`, `/provider stepfun`, `/provider openmodel`,
20 # `/provider opencode-go`, `/provider opencode-zen`, `/provider meta`, `/provider xai`) toggle without having to re-enter keys. Keys and
21 # endpoints live in each provider's `[providers.<name>]` table. An older
22 # top-level `api_key` / `base_url` is still read: it belongs to
23 # `[providers.deepseek]` (or to the vendor whose official host it names), and
24 # `codewhale config migrate` moves it there in the file.
25 provider = "deepseek" # deepseek | deepseek-cn | deepseek-anthropic | nvidia-nim | openai | atlascloud | wanjie-ark | volcengine | openrouter | orcarouter | xiaomi-mimo | novita | fireworks | siliconflow | siliconflow-CN | arcee | moonshot | zai | stepfun | minimax | sglang | vllm | ollama | ollama-cloud | huggingface | modelscope | together | qianfan | openai-codex | anthropic | openmodel | deepinfra | sakana | longcat | opencode-go | opencode-zen | meta | xai | mistral | telecomjs | google | edenai | zenmux | csdn | concentrate | modelstudio-token-plan | modelstudio-coding-plan | codewhale
26 # provider = "deepseek-cn" # legacy alias (official host is still https://api.deepseek.com)
27 # Optional custom model request headers for OpenAI-compatible gateways.
28 # Authorization and Content-Type are managed by the client and cannot be overridden here.
29 # http_headers = { "X-Model-Provider-Id" = "your-model-provider" }
30
31 # ─────────────────────────────────────────────────────────────────────────────────
32 # Default Models
33 # ─────────────────────────────────────────────────────────────────────────────────
34 # DeepSeek V4 family:
35 # deepseek-v4-pro — flagship reasoning model on DeepSeek Platform
36 # deepseek-v4-flash — fast, cost-efficient (legacy aliases: deepseek-chat, deepseek-reasoner)
37 # deepseek-ai/deepseek-v4-pro — NVIDIA NIM-hosted Pro model ID
38 # deepseek-ai/deepseek-v4-flash — NVIDIA NIM-hosted Flash model ID
39 # deepseek/deepseek-v4-pro — default OpenRouter DeepSeek model ID
40 # arcee-ai/trinity-large-thinking — OpenRouter Arcee Trinity Large Thinking
41 # xiaomi/mimo-v2.5-pro — OpenRouter Xiaomi MiMo 2.5 Pro
42 # xiaomi/mimo-v2.5 — OpenRouter Xiaomi MiMo 2.5
43 # z-ai/glm-5.1 — OpenRouter Z.AI GLM 5.1
44 # z-ai/glm-5.2 — OpenRouter Z.AI GLM 5.2
45 # z-ai/glm-5.3 — OpenRouter Z.AI GLM 5.3 (live on Z.ai since 2026-08-13;
46 # metadata inherited from 5.2, unpriced)
47 # z-ai/glm-5.3-flash — OpenRouter Z.AI GLM 5.3 Flash (1M multimodal; $0.15/$0.50 list)
48 # z-ai/glm-5-turbo — OpenRouter Z.AI GLM 5 Turbo (scout fast sibling of 5.2)
49 # GLM-5.3 — default direct Z.AI Coding Plan model (live since 2026-08-13;
50 # metadata inherited from 5.2, unpriced)
51 # GLM-5.3-Flash — direct Z.AI GLM 5.3 Flash (faster/explore sibling of 5.3)
52 # GLM-5.2 — direct Z.AI GLM 5.2 (previous default; explicit selections keep it)
53 # GLM-5.1 — direct Z.AI smaller model
54 # GLM-5-Turbo — direct Z.AI fast model (scout fast sibling of 5.2)
55 # step-3.7-flash — default direct StepFun / StepFlash model ID
56 # kimi-k3 — direct Moonshot K3 model ID (1M context)
57 # kimi-k2.7-code — default direct Moonshot/Kimi K2.7 model ID
58 # k3 — Kimi Code membership K3 API model ID
59 # kimi-for-coding — Kimi Code membership K2.7 compatibility ID
60 # kimi-for-coding-highspeed — Kimi Code membership high-speed roster ID
61 # gpt-4.1 — default generic OpenAI-compatible model ID
62 # deepseek-ai/deepseek-v4-flash — default AtlasCloud model ID
63 # deepseek-reasoner — default Wanjie Ark model ID
64 # mimo-v2.5-pro — default Xiaomi MiMo model ID
65 # mimo-v2.5-pro-ultraspeed — Xiaomi MiMo V2.5 Pro UltraSpeed chat model ID
66 # mimo-v2.5 — Xiaomi MiMo V2.5 Omni model ID
67 # mimo-v2.5-tts — Xiaomi MiMo speech/TTS model ID
68 # mimo-v2.5-tts-voicedesign — Xiaomi MiMo voice-design TTS model ID
69 # mimo-v2.5-tts-voiceclone — Xiaomi MiMo voice-clone TTS model ID
70 # accounts/fireworks/models/deepseek-v4-pro — Fireworks AI Pro model ID
71 # deepseek-ai/DeepSeek-V4-Pro — SiliconFlow hosted Pro model ID
72 # deepseek-ai/DeepSeek-V4-Flash — SiliconFlow hosted Flash model ID
73 # trinity-large-thinking — default direct Arcee AI API model ID
74 # trinity-large-preview — direct Arcee AI API model ID
75 # deepseek-ai/DeepSeek-V4-Pro — SGLang self-hosted Pro model ID
76 # deepseek-ai/DeepSeek-V4-Flash — SGLang self-hosted Flash model ID
77 # auto — auto-select between flash and pro based on task complexity.
78 # Complex tasks (debugging, refactoring, architecture) → pro;
79 # simple tasks (lookups, formatting, Q&A) → flash.
80 default_text_model = "deepseek-flash"
81
82 # ─────────────────────────────────────────────────────────────────────────────────
83 # Thinking Mode (DeepSeek V4 reasoning effort)
84 # ─────────────────────────────────────────────────────────────────────────────────
85 # "off" — disables chain-of-thought (thinking.type = disabled)
86 # "low" — compat-maps to "high" server-side
87 # "medium" — compat-maps to "high" server-side
88 # "high" — reasoning_effort = high (DeepSeek default)
89 # "max" — reasoning_effort = max (deepest reasoning)
90 #
91 # Ctrl+T in the TUI cycles the reasoning tier. The header shows the current
92 # tier as a ⚡ chip. (Shift+Tab cycles the permission posture — Ask /
93 # Auto-Review / Full Access — not the reasoning tier.)
94 reasoning_effort = "max"
95
96 # NOTE: `show_thinking`, `thinking_default_expanded`, `thinking_preview_lines`,
97 # `help_expand_groups`, `pin_last_prompt`, and `cost_currency` live in
98 # `~/.codewhale/settings.toml`, not here — `Config` has no such fields and
99 # unknown keys are ignored. See crates/tui/src/settings.rs.
100 #
101 # Density (Grok-like compact defaults; turn these up if you want more shown):
102 # thinking_preview_lines = 2 # 0 header-only, 10 older dump
103 # thinking_default_expanded = false
104 # help_expand_groups = false # true = F1 starts fully expanded
105 # pin_last_prompt = true
106 # show_tool_details = false
107
108 # Root-level keys must appear before the first `[table]` header: TOML assigns
109 # every key after a header to that table. Keep new top-level keys in this
110 # block (scripts/check-config-example.py enforces it in CI).
111
112 # ─────────────────────────────────────────────────────────────────────────────────
113 # Paths
114 # ─────────────────────────────────────────────────────────────────────────────────
115 # New installs write product state under ~/.codewhale/. Existing ~/.deepseek/
116 # files are still read as compatibility fallbacks when the .codewhale file is
117 # absent.
118 skills_dir = "~/.codewhale/skills"
119 mcp_config_path = "~/.codewhale/mcp.json"
120 notes_path = "~/.codewhale/notes.txt"
121
122 # Anchors the native memory store. The filename itself is not written: under
123 # the Native backend (the only backend) the store is re-rooted to
124 # `<parent-of-this-path>/memory/global/MEMORY.md`. With the default below that
125 # resolves to `~/.codewhale/memory/global/MEMORY.md` (plus workspace-scoped
126 # files and a rebuildable SQLite FTS5 index). See docs/MEMORY.md.
127 memory_path = "~/.codewhale/memory.md"
128
129 # instructions = ["./AGENTS.md", "~/.codewhale/global.md"]
130 #
131 # Optional list of additional instruction files concatenated into the
132 # system prompt in declared order (#454). Useful for layering
133 # repo-specific rules on top of a global preferences file. Each entry
134 # is expanded so `~` and env vars work; missing files are skipped with
135 # a tracing warning. Files are capped at 100 KiB per entry.
136 #
137 # Project-level config (.codewhale/config.toml in the workspace) replaces
138 # the user-level array wholesale rather than merging — list `~/global.md`
139 # inside the project array if you want both. An explicit empty array
140 # (`instructions = []`) clears the user list for the current repo.
141
142 # ─────────────────────────────────────────────────────────────────────────────────
143 # Security
144 # ─────────────────────────────────────────────────────────────────────────────────
145 allow_shell = true
146 approval_policy = "on-request" # on-request | untrusted | never
147 sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access | external-sandbox
148
149 # Whether a workspace-write sandbox also lets shell commands reach the
150 # network. Default false: being allowed to edit this repository is not a
151 # reason to be allowed to open outbound connections, so `curl`, package
152 # installs, and `git fetch` are denied by the OS sandbox unless you opt in.
153 # When a command is denied, Codewhale offers an elevation prompt that grants
154 # network for that call only; set this to `true` to grant it for the whole
155 # session instead. `danger-full-access` and `yolo` are unsandboxed and
156 # unaffected by this key. Note that on platforms with no OS sandbox backend
157 # (default Linux without bubblewrap, and Windows) nothing is enforced either
158 # way -- `/status` and `doctor` both say so.
159 # sandbox_network_access = false
160
161 # Which other agents' instruction files to import as project instructions.
162 # Empty by default. Codewhale reads AGENTS.md (the cross-agent standard) and
163 # its own .codewhale/instructions.md without being asked; a CLAUDE.md,
164 # .cursorrules, or .github/copilot-instructions.md written as law for a
165 # different tool is not treated as law here until you say so. Codewhale's own
166 # files always outrank anything imported.
167 # Accepts: "claude", "cursor", "cline", "windsurf", "gemini", "copilot",
168 # "muse", or "all". Env: CODEWHALE_PROJECT_INSTRUCTION_IMPORTS (comma-separated).
169 # project_instruction_imports = ["claude"]
170 #
171 # Everything that reaches the model as standing project instruction authority —
172 # the repository-root -> workspace AGENTS.md chain, the global fallback layer,
173 # .codewhale/rules/*.md, and any imported foreign files — shares one 48 KiB
174 # aggregate budget. Instructions claim it first and are trimmed from the
175 # broadest scope inward, so the nearest-scope file is the last thing dropped.
176 # prompt_suggestion = true # opt-in: show ghost-text follow-up question in composer after each turn
177
178 # Optional tab/window title shown as `[title] …` in front of the terminal
179 # window title (the `Codewhale` / `reasoning…` / `using tool…` / `done`
180 # states). Multi-window setups can give each workspace its own `--config`
181 # file (or profile) so alt-tabbed sessions are identifiable at a glance.
182 # The `/title` command overrides this per session; `/config title … --save`
183 # persists a new default here. Run `/title off` to drop a session override.
184 # title = "workspace-x"
185
186 # Typed permission rules live in a sibling `permissions.toml` file, not in
187 # config.toml. Each `[[rules]]` entry accepts `tool`, optional `command`
188 # or `path`, optional absolute `workspace`, optional `command_exact = true`,
189 # and an `action` field: `"deny"`, `"ask"` (default), or `"allow"`.
190 # Deny always wins over ask, which wins over allow. The active user's sibling
191 # file is the only rule source today; project config overlays do not load a
192 # project-local permissions.toml. `workspace` is a repo scope, not a source.
193 # Globs and broad directory rules remain future work.
194 #
195 # In supported approval cards, press `S` to allow once and save an exact ask
196 # rule. Eligible safe calls also offer `P` / "Always allow this exact rule in
197 # this repo", which saves an exact `allow` rule scoped to the current absolute
198 # workspace. Dangerous/critical calls and repo-law prompts cannot save allow
199 # grants. The approval UI still does not save deny rules:
200 # exec_shell -> exact approved command string
201 # write_file -> exact workspace-relative target path
202 # edit_file -> exact workspace-relative target path
203 # apply_patch -> one exact workspace-relative path per validated touched file
204 # `read_file` rules can be written manually, but the approval UI does not save
205 # them.
206 #
207 # `/permissions list` shows the active source, effective matcher, repo/global
208 # scope, and whether each rule applies in the current workspace.
209 # `/permissions remove <number>` previews a deletion and prints a confirmation
210 # command bound to the current file snapshot. If the file changes first, the
211 # confirmation fails safely. Removal preserves unrelated comments/formatting,
212 # uses the same lock as approval-card appends, and atomically replaces the file.
213 # `/config ask-rules` remains a compatibility entry to the same list.
214 #
215 # Example ~/.codewhale/permissions.toml:
216 #
217 # [[rules]]
218 # tool = "exec_shell"
219 # command = "cargo test"
220 # action = "ask"
221 #
222 # # Block dangerous commands
223 # [[rules]]
224 # tool = "exec_shell"
225 # command = "sed"
226 # action = "deny"
227 #
228 # [[rules]]
229 # tool = "exec_shell"
230 # command = "awk"
231 # action = "deny"
232 #
233 # # Allow trusted commands without asking
234 # [[rules]]
235 # tool = "exec_shell"
236 # command = "git status"
237 # command_exact = true
238 # workspace = "/absolute/path/to/project"
239 # action = "allow"
240 #
241 # # Path-based deny
242 # [[rules]]
243 # tool = "write_file"
244 # path = "src/main.rs"
245 # action = "deny"
246 #
247 # [[rules]]
248 # tool = "edit_file"
249 # path = "src/lib.rs"
250 # action = "ask"
251 #
252 # [[rules]]
253 # tool = "apply_patch"
254 # path = "src/patch-target.rs"
255 #
256 # [[rules]]
257 # tool = "read_file"
258 # path = "secrets/api_key.txt"
259 # action = "deny"
260 # ─────────────────────────────────────────────────────────────────────────────────
261 # External Sandbox Backend (pluggable remote execution)
262 # ─────────────────────────────────────────────────────────────────────────────────
263 # When sandbox_backend is set to "opensandbox", all exec_shell calls are
264 # routed through an external OpenSandbox-compatible HTTP API instead of
265 # spawning a local process. The backend sends `POST {sandbox_url}/v1/sandbox/run`
266 # with `{"cmd": "...", "env": {...}}` and expects
267 # `{"stdout": "...", "stderr": "...", "exit_code": 0}`.
268 #
269 # sandbox_backend = "none" # "none" (default) or "opensandbox"
270 # sandbox_url = "http://localhost:8080" # OpenSandbox-compatible API base URL
271 # sandbox_api_key = "YOUR_API_KEY" # Optional Bearer token sent with requests
272 #
273 # Env-var overrides:
274 # DEEPSEEK_SANDBOX_BACKEND → sandbox_backend
275 # DEEPSEEK_SANDBOX_URL → sandbox_url
276 # DEEPSEEK_SANDBOX_API_KEY → sandbox_api_key
277 #
278 # Example OpenSandbox setup:
279 #
280 # sandbox_backend = "opensandbox"
281 # sandbox_url = "http://localhost:8080"
282 # sandbox_api_key = "sk-opensandbox-secret"
283 #
284 # The backend uses a 30-second HTTP timeout. Background, interactive, and
285 # TTY modes are not supported with external backends — all commands run
286 # synchronously via HTTP.
287 # ─────────────────────────────────────────────────────────────────────────────────
288 # Bubblewrap (Linux only, additional filesystem isolation)
289 # ─────────────────────────────────────────────────────────────────────────────────
290 # When set to true and `/usr/bin/bwrap` is present, exec_shell commands are
291 # routed through bubblewrap instead of relying solely on Landlock. Bubblewrap
292 # creates a read-only view of the root filesystem with write access limited to
293 # the working directory. Install separately:
294 #
295 # Ubuntu/Debian: apt install bubblewrap
296 # Fedora: dnf install bubblewrap
297 # Arch: pacman -S bubblewrap
298 #
299 # prefer_bwrap = false # default — use Landlock only
300 #
301 # Env override: CODEWHALE_PREFER_BWRAP=true
302 # Legacy alias (deprecated until 0.10.0): DEEPSEEK_PREFER_BWRAP=true
303 #
304 # With prefer_bwrap = true, the sandbox gets a private /dev and /proc plus a
305 # writable isolated /tmp by default, so toolchains work without widening the
306 # filesystem policy (#5410). Two optional escape hatches:
307 #
308 # bwrap_ro_roots = [] # extra host paths bind-mounted read-only, e.g.
309 # # ["/usr/lib", "/usr/lib64"] for system-library
310 # # linking when the root bind is narrowed
311 # bwrap_dev_roots = [] # host device nodes bind-mounted read-write, e.g.
312 # # ["/dev/null"] for redirection against the host
313 # # node — character/block devices only, never
314 # # directories, so this cannot widen file writes
315
316 # There is no config.toml `auto_allow` key. To pre-approve a command, add an
317 # `action = "allow"` rule to permissions.toml `[[rules]]` (see the
318 # permissions.toml example above).
319 max_subagents = 64 # optional (default 64, clamped to 1-128)
320
321 # Optional managed policy paths (defaults to /etc/deepseek/*.toml on unix):
322 # managed_config_path = "/etc/deepseek/managed_config.toml"
323 # requirements_path = "/etc/deepseek/requirements.toml"
324
325 # ─────────────────────────────────────────────────────────────────────────────────
326 # Product telemetry — optional, enabled by default with a startup disclosure
327 # ─────────────────────────────────────────────────────────────────────────────────
328 # Anonymous usage analytics are enabled when this key is omitted, unless an
329 # existing opt-out or a run-scoped kill switch disables them. No first-run
330 # affirmative opt-in is required. See docs/TELEMETRY.md for the complete schema.
331 # Local session history and usage diagnostics work with telemetry disabled;
332 # they require no account or hosted analytics service.
333 #
334 # Setting it to `false` here is an answer, not just a flag: it deletes the
335 # random install id, truncates every buffered event, and leaves a tombstone
336 # that a session already running re-checks before it sends anything. Every
337 # later run re-reads this key and re-asserts that tombstone, so it stands for
338 # as long as the `false` does — and nothing outranks it, not `--telemetry true`
339 # and not `CODEWHALE_TELEMETRY=1`. Turning telemetry back on means writing
340 # `telemetry = true` here.
341 #
342 # The environment variable and the flag are different: they stop the run and
343 # erase nothing, so a harness that disables telemetry for one command does not
344 # discard the machine owner's install id and dry-run records.
345 #
346 # codewhale config set telemetry false # opt out: stops it and erases state
347 # CODEWHALE_TELEMETRY=0 codewhale # kill switch: stops it, erases nothing
348 #
349 # What is never collected: prompts, completions, tool arguments, diffs, file
350 # contents, filenames, paths, git remotes, repo or branch names, memory
351 # entries, chat history, API keys or tokens (not even a boolean saying one
352 # exists), model ids, custom provider table names, MCP server names, error or
353 # panic message bodies, per-event timestamps, keystrokes, clipboard,
354 # screenshots, or location. The complete schema is `docs/TELEMETRY.md`, and a
355 # test asserts this file and the serializer agree.
356 #
357 # A repo-local `.codewhale/config.toml` can set neither key: someone else's
358 # repository cannot turn your telemetry on or aim it at a host of their choosing.
359 # telemetry = false
360
361 # Where batches are POSTed. Leaving this unset selects the shipped default,
362 # the first-party ingest service:
363 #
364 # https://telemetry.codewhale.net/v1/telemetry
365 #
366 # That default is only ever consulted for a session that is already enabled —
367 # it decides where a batch goes, never whether one exists. Nothing is sent
368 # until `telemetry` is on AND the first-run notice was answered with Enable.
369 #
370 # Two overrides, both of which beat the default:
371 #
372 # telemetry_endpoint = "https://collector.internal/v1/batch" # your own sink
373 # telemetry_endpoint = "" # contact nobody
374 #
375 # The empty string is the local dry-run sink: batches are serialized exactly as
376 # a real endpoint would see them, appended to
377 # `$CODEWHALE_HOME/telemetry/dryrun.jsonl`, and no HTTP client is ever
378 # constructed. Read that file to see precisely what would have been sent.
379 # `CODEWHALE_TELEMETRY_ENDPOINT` overrides this file, and setting it to the
380 # empty string means the same "contact nobody".
381 #
382 # `https://` is required; plain `http://` is accepted only for loopback, and no
383 # environment variable overrides that refusal.
384 # telemetry_endpoint = "https://telemetry.codewhale.net/v1/telemetry"
385
386 # Signed catalog overlays are optional and inactive without approved trust keys.
387 # Explicit route/model selections and provider-owned rosters keep priority.
388 # CODEWHALE_DISABLE_CLOUD_FACTS=1 disables cache, local files and network too.
389 [cloud_facts]
390 enabled = false
391 channel = "stable"
392 ttl_hours = 6
393 # url = "https://codewhale.net/api/facts/v1/{channel}"
394
395 # ─────────────────────────────────────────────────────────────────────────────────
396 # Startup update check
397 # ─────────────────────────────────────────────────────────────────────────────────
398 # The TUI checks for newer Codewhale releases in the background at startup.
399 # Set check_for_updates = false in managed or air-gapped environments.
400 # The result is cached in ~/.codewhale/update-check.json, so the network is
401 # touched at most once per check_interval_hours while the notice still shows
402 # on every launch. Set 0 to check on every launch.
403 # Checks are skipped entirely in CI, and when CODEWHALE_NO_UPDATE_CHECK or
404 # NO_UPDATE_NOTIFIER is set.
405 # update_uri may point at a GitHub-compatible latest-release JSON endpoint.
406 [update]
407 check_for_updates = true
408 check_interval_hours = 24
409 # update_uri = "https://internal.mirror.example/codewhale/releases/latest"
410
411 # ─────────────────────────────────────────────────────────────────────────────────
412 # Hotbar slots (#2061 / #2064)
413 # ─────────────────────────────────────────────────────────────────────────────────
414 # Optional hotbar bindings for slots 1-8. Since #3807 the Hotbar is hidden by
415 # default: an omitted `hotbar` key and an explicit `hotbar = []` both mean no
416 # hotbar. It shows only when [[hotbar]] tables are configured here, and
417 # `/hotbar on` writes the eight default slots as explicit [[hotbar]] tables:
418 # 1 slash.workflow 2 slash.goal 3 slash.auto 4 mode.plan
419 # 5 mode.agent 6 mode.operate 7 palette.open 8 sidebar.toggle
420 #
421 # Invalid slots are skipped with a warning, duplicate slots use the last entry,
422 # and unknown actions are preserved so the UI can show a disabled entry.
423 # Slash commands can be bound as slash.<name>, for example slash.workflow.
424 # Commands that require arguments pre-fill the composer instead of running
425 # incomplete.
426 #
427 # [[hotbar]]
428 # slot = 1
429 # label = "voice"
430 # action = "voice.toggle"
431 #
432 # [[hotbar]]
433 # slot = 2
434 # action = "session.compact"
435 #
436 # [[hotbar]]
437 # slot = 3
438 # label = "mode"
439 # action = "slash.mode"
440
441 # ─────────────────────────────────────────────────────────────────────────────────
442 # User memory (#489) — opt-in. When enabled, the TUI loads the native store
443 # derived from memory_path (see above), injects a bounded recall block into
444 # the system prompt, intercepts `# foo` in the composer, and registers the
445 # `remember` / `memory_search` / `memory_get` tools.
446 # ─────────────────────────────────────────────────────────────────────────────────
447 [memory]
448 # enabled = true # turn the feature on (default: false)
449 # Override the env-var equivalent: `DEEPSEEK_MEMORY=on`
450
451 # Xiaomi MiMo speech/TTS defaults. Also configurable with
452 # XIAOMI_MIMO_SPEECH_OUTPUT_DIR / MIMO_SPEECH_OUTPUT_DIR.
453 [speech]
454 # output_dir = "./speech"
455
456 # ─────────────────────────────────────────────────────────────────────────────────
457 # Reasoning-only recovery
458 # ─────────────────────────────────────────────────────────────────────────────────
459 # When a reasoning model returns only hidden thinking without any answer text
460 # or tool call, the engine can re-request the answer automatically.
461 # Set max_reprompts = 0 to disable automatic recovery entirely.
462 # [reasoning_only]
463 # max_reprompts = 2
464 # reprompt_message = "So, what's up ? Keep running !"
465
466 # ───────────────────────────────────────────────────────────────────────────
467 # Model-bound key redaction ([redaction])
468 # ───────────────────────────────────────────────────────────────────────────
469 # Codewhale masks credential-looking values in tool output before it reaches
470 # the model (the "model boundary"), so a file that contains a configured API
471 # key, a bare provider token, or a credential-shaped opaque string never leaks
472 # those bytes to the model. Leave this enabled unless the model must read and
473 # edit files that contain real credentials.
474 #
475 # Disabling is a security decision, so it is never a plain flag:
476 # * Set model_bound = "disabled" here, restart Codewhale, and the startup
477 # gate asks twice - a first confirmation, then a red "are you really
478 # sure?" stage. Only the second confirmation takes effect, and it applies
479 # on later launches while model_bound stays "disabled".
480 # * Going back to "enabled" - or rewriting config.toml after the
481 # confirmation - invalidates it: requesting "disabled" again always
482 # asks for a fresh confirmation.
483 # * Until a confirmation exists - including in non-interactive/headless
484 # runs, which never confirm anything - masking stays on regardless of
485 # this key. Choosing "keep masking on" on the gate leaves the key
486 # untouched, so the next launch asks again.
487 # * The value is forgiving: false/"off" mean "disabled"; true/"on" mean
488 # "enabled".
489 # [redaction]
490 # model_bound = "enabled" # mask keys before they reach the model (default)
491 # model_bound = "disabled" # request the opt-out (restart + confirm required)
492
493 # Native tool catalog controls (#2076). By default only the core tool surface
494 # is loaded into the model context; less common native tools are discoverable
495 # through ToolSearch and loaded on first use.
496 # [tools]
497 # always_load = ["git_show", "notify"]
498 #
499 # `request_user_input` payload ceilings (#5949). The model may ask up to
500 # `user_input_max_questions` questions per call, each offering up to
501 # `user_input_max_options` options. Out-of-range values are clamped to the
502 # supported ranges (1..=10 and 2..=10) with a warning.
503 # user_input_max_questions = 6
504 # user_input_max_options = 4
505 #
506 # Seconds a question or an approval decision waits before it cancels (#6003).
507 # 0 disables the timeout entirely; values above 86400 (24h) are clamped.
508 # user_input_timeout_seconds = 300
509 #
510 # Bound an interactive approval card's wait (#6101). When the window elapses
511 # the card resolves to deny (fail-closed) and the transcript says the bound
512 # denied the call; omitted or 0 waits indefinitely.
513 # [approval]
514 # timeout_seconds = 300
515
516 # Optional sub-agent tuning. max_concurrent overrides top-level max_subagents.
517 # [subagents]
518 # max_concurrent = 10
519 # api_timeout_secs = 600 # per-step API timeout, clamped to 1..=3600
520 #
521 # How many levels of nested sub-agents the `agent` tool may spawn:
522 # max_depth = 0 # opt out completely — the agent never spawns sub-agents
523 # max_depth = 1 # the agent may spawn sub-agents, but those may not spawn more
524 # max_depth = 2 # one more level of nesting, etc.
525 # Unset defaults to 3; any value is clamped to the hard ceiling (8). The depth
526 # limit is enforced in code, not requested of the model — a sub-agent past the
527 # limit cannot be spawned regardless of what the model decides.
528 # max_depth = 3
529
530 # ─────────────────────────────────────────────────────────────────────────────────
531 # Per-provider credentials (peer providers — NIM is first-class, not a flag)
532 # ─────────────────────────────────────────────────────────────────────────────────
533 # Providers can be stored at once; `provider = "..."` (top of file) or
534 # `/provider deepseek` / `/provider nvidia-nim` / `--provider openai` /
535 # `--provider wanjie-ark` / `/provider volcengine` / `/provider fireworks` /
536 # `--provider siliconflow` / `/provider arcee` / `/provider moonshot`
537 # switches between them without having to re-enter keys. Env vars override anything set here:
538 # DeepSeek: DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL
539 # DeepSeek Anthropic-compatible: DEEPSEEK_API_KEY, DEEPSEEK_ANTHROPIC_BASE_URL
540 # NIM: NVIDIA_API_KEY (or NVIDIA_NIM_API_KEY), NIM_BASE_URL
541 # (or NVIDIA_NIM_BASE_URL / NVIDIA_BASE_URL), NVIDIA_NIM_MODEL
542 # OpenAI-compatible: OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL
543 # Wanjie Ark: WANJIE_ARK_API_KEY (or WANJIE_API_KEY), WANJIE_ARK_BASE_URL, WANJIE_ARK_MODEL
544 # Volcengine Ark: VOLCENGINE_API_KEY (or VOLCENGINE_ARK_API_KEY / ARK_API_KEY), VOLCENGINE_BASE_URL, VOLCENGINE_MODEL
545 # OpenRouter: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL
546 # Xiaomi MiMo: XIAOMI_MIMO_API_KEY (or XIAOMI_API_KEY / MIMO_API_KEY), XIAOMI_MIMO_BASE_URL, XIAOMI_MIMO_MODEL
547 # Token Plan: XIAOMI_MIMO_TOKEN_PLAN_API_KEY (or MIMO_TOKEN_PLAN_API_KEY), XIAOMI_MIMO_MODE/MIMO_MODE
548 # Novita: NOVITA_API_KEY, NOVITA_BASE_URL, NOVITA_MODEL
549 # Fireworks: FIREWORKS_API_KEY, FIREWORKS_BASE_URL
550 # SiliconFlow: SILICONFLOW_API_KEY, SILICONFLOW_BASE_URL, SILICONFLOW_MODEL
551 # Arcee: ARCEE_API_KEY, ARCEE_BASE_URL, ARCEE_MODEL
552 # Moonshot/Kimi: MOONSHOT_API_KEY (or KIMI_API_KEY), MOONSHOT_BASE_URL, MOONSHOT_MODEL
553 # SGLang: SGLANG_BASE_URL, SGLANG_MODEL, optional SGLANG_API_KEY
554 # vLLM: VLLM_BASE_URL, VLLM_MODEL, optional VLLM_API_KEY
555 # Ollama: OLLAMA_BASE_URL, OLLAMA_MODEL, optional OLLAMA_API_KEY
556 # Hugging Face: HUGGINGFACE_API_KEY (or HF_TOKEN), HUGGINGFACE_BASE_URL (or HF_BASE_URL),
557 # HUGGINGFACE_MODEL (or HF_MODEL)
558 # Meta Model API: META_MODEL_API_KEY (or MODEL_API_KEY), META_MODEL_API_BASE_URL
559 # (or MODEL_API_BASE_URL), META_MODEL_API_MODEL (or MODEL_API_MODEL)
560 #
561 # Custom DeepSeek-compatible APIs usually do not need a new provider table:
562 # set `provider = "deepseek"` and override [providers.deepseek].base_url/model.
563 # For generic OpenAI-compatible gateways, use `provider = "openai"` and the
564 # [providers.openai] table below. Keep provider/api_key/base_url in user config
565 # or environment variables; project overlays are not allowed to set them.
566 #
567 # Provider is the route/account/endpoint; model is the ID on that route.
568 # Common DeepSeek routes:
569 # provider = "deepseek" model = "deepseek-v4-pro"
570 # provider = "nvidia-nim" model = "deepseek-ai/deepseek-v4-pro"
571 # provider = "openrouter" model = "deepseek/deepseek-v4-pro"
572 # provider = "fireworks" model = "accounts/fireworks/models/deepseek-v4-pro"
573 # provider = "siliconflow" model = "deepseek-ai/DeepSeek-V4-Pro"
574
575 # DeepSeek Platform (https://platform.deepseek.com)
576 [providers.deepseek]
577 api_key = "YOUR_DEEPSEEK_API_KEY" # must be non-empty; deepseek-cn reads it too
578 base_url = "https://api.deepseek.com/beta"
579 # base_url = "https://api.deepseek.com" # opt out of DeepSeek beta features
580 # model = "deepseek-v4-pro"
581 # Custom DeepSeek-compatible example:
582 # base_url = "https://your-provider.example/v1"
583 # model = "deepseek-ai/DeepSeek-V4-Pro"
584 # http_headers = { "X-Model-Provider-Id" = "your-model-provider" } # optional custom request headers
585 # path_suffix = "/chat/completions" # override the API path; skips /v1 versioning when set
586 # reasoning_stream_style = "inline_tags" # route <think>...</think> content into Thinking cells
587
588 # DeepSeek Anthropic-compatible Messages route (opt-in)
589 # [providers.deepseek_anthropic]
590 # api_key = "YOUR_DEEPSEEK_API_KEY"
591 # base_url = "https://api.deepseek.com/anthropic"
592 # model = "deepseek-v4-pro"
593 # [providers.deepseek.auth] # provider-scoped auth source metadata; command execution lands in a follow-up slice
594 # source = "command"
595 # command = ["secret-tool", "lookup", "service", "codewhale-deepseek"]
596 # timeout_ms = 2000
597 # insecure_skip_tls_verify = true # last resort for private gateways; prefer SSL_CERT_FILE
598
599 # NVIDIA NIM-hosted DeepSeek V4 (https://build.nvidia.com)
600 [providers.nvidia_nim]
601 # api_key = "YOUR_NVIDIA_API_KEY"
602 # base_url = "https://integrate.api.nvidia.com/v1"
603 # model = "deepseek-ai/deepseek-v4-pro" # or deepseek-ai/deepseek-v4-flash
604
605 # Generic OpenAI-compatible endpoint. Use the built-in `openai` provider for
606 # third-party gateways; do not invent a custom provider name. For non-local
607 # http:// gateways, launch with DEEPSEEK_ALLOW_INSECURE_HTTP=1 only on a
608 # trusted network.
609 [providers.openai]
610 # api_key = "YOUR_OPENAI_COMPATIBLE_API_KEY"
611 # base_url = "https://api.openai.com/v1"
612 # model = "gpt-4.1"
613 # Gateway example:
614 # base_url = "https://gateway.example/v1"
615 # model = "your-deepseek-compatible-model"
616 # Alibaba Bailian / Model Studio DashScope OpenAI-compatible example:
617 # base_url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
618 # model = "qwen-plus"
619 # context_window = 1000000 # set the gateway/model's real total context window
620 # insecure_skip_tls_verify = true # last resort for private gateways; prefer SSL_CERT_FILE
621
622 # AtlasCloud OpenAI-compatible endpoint (https://www.atlascloud.ai/docs/models/llm)
623 [providers.atlascloud]
624 # api_key = "YOUR_ATLASCLOUD_API_KEY"
625 # base_url = "https://api.atlascloud.ai/v1"
626 # model = "deepseek-ai/deepseek-v4-flash"
627
628 # Wanjie Ark / 万界方舟 OpenAI-compatible endpoint
629 [providers.wanjie_ark]
630 # api_key = "YOUR_WANJIE_API_KEY"
631 # base_url = "https://maas-openapi.wanjiedata.com/api/v1"
632 # model = "deepseek-reasoner" # or the exact model ID enabled on your Wanjie account
633
634 # Volcengine / Volcano Engine Ark Coding API
635 [providers.volcengine]
636 # api_key = "YOUR_VOLCENGINE_API_KEY"
637 # base_url = "https://ark.cn-beijing.volces.com/api/coding/v3"
638 # model = "DeepSeek-V4-Pro" # or DeepSeek-V4-Flash
639
640 # OpenRouter — multi-provider gateway (https://openrouter.ai)
641 [providers.openrouter]
642 # vendor = "deepinfra" # exact upstream slug; disables OpenRouter fallbacks
643 # api_key = "YOUR_OPENROUTER_API_KEY"
644 # base_url = "https://openrouter.ai/api/v1"
645 # model = "deepseek/deepseek-v4-pro"
646 # OpenRouter-compatible gateways can reuse this provider so reasoning/cache
647 # parsing stays on the OpenRouter-compatible path instead of generic OpenAI:
648 # base_url = "https://openrouter-compatible.example/v1"
649 # model = "deepseek/deepseek-v4-pro"
650 # Recent large model IDs also accepted here include arcee-ai/trinity-large-thinking,
651 # minimax/minimax-m3, minimax/minimax-m2.7, xiaomi/mimo-v2.5-pro, qwen/qwen3.6-flash,
652 # qwen/qwen3.6-35b-a3b, qwen/qwen3.6-max-preview, qwen/qwen3.6-27b, qwen/qwen3.6-plus,
653 # qwen/qwen3.7-max, google/gemma-4-31b-it, z-ai/glm-5.1, z-ai/glm-5.2,
654 # moonshotai/kimi-k2.6,
655 # nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free, and nvidia/nemotron-3-ultra.
656
657 # OrcaRouter — OpenAI-compatible aggregation gateway (https://www.orcarouter.ai)
658 [providers.orcarouter]
659 # api_key = "YOUR_ORCAROUTER_API_KEY"
660 # base_url = "https://api.orcarouter.ai/v1"
661 # model = "deepseek/deepseek-v4-pro"
662 # Namespaced wire models pass through verbatim (deepseek/deepseek-v4-pro,
663 # deepseek/deepseek-v4-flash); OrcaRouter's own auto-routing model is
664 # selectable as "orcarouter/auto".
665
666 # Xiaomi MiMo OpenAI-compatible endpoint (https://platform.xiaomimimo.com)
667 [providers.xiaomi_mimo]
668 # api_key = "YOUR_XIAOMI_KEY"
669 # base_url = "https://token-plan-sgp.xiaomimimo.com/v1" # Token Plan / tp- keys
670 # # base_url = "https://token-plan-ams.xiaomimimo.com/v1" # Token Plan Europe / Amsterdam
671 # # base_url = "https://api.xiaomimimo.com/v1" # Pay-as-you-go / sk- keys
672 # model = "mimo-v2.5-pro" # chat/reasoning
673 # Chat model IDs: mimo-v2.5-pro, mimo-v2.5-pro-ultraspeed, mimo-v2.5
674 # Token Plan subscriptions use separate tp-* API keys plus api-key auth.
675 # mode = "token-plan-sgp" # default Token Plan endpoint
676 # mode = "token-plan-cn" # China cluster
677 # mode = "token-plan-ams" # Europe cluster
678 # mode = "pay-as-you-go" # standard API / sk- keys
679 # TTS aliases are also accepted by `codewhale speech`: tts, voice-design, voice-clone
680 # TTS model IDs: mimo-v2.5-tts, mimo-v2.5-tts-voicedesign, mimo-v2.5-tts-voiceclone, mimo-v2-tts
681
682 # Novita AI-hosted inference (https://novita.ai)
683 [providers.novita]
684 # api_key = "YOUR_NOVITA_API_KEY"
685 # base_url = "https://api.novita.ai/openai/v1"
686 # model = "deepseek/deepseek-v4-pro" # or deepseek/deepseek-v4-flash
687
688 # Fireworks AI-hosted DeepSeek V4 (https://fireworks.ai)
689 [providers.fireworks]
690 # api_key = "YOUR_FIREWORKS_API_KEY"
691 # base_url = "https://api.fireworks.ai/inference/v1"
692 # model = "accounts/fireworks/models/deepseek-v4-pro"
693
694 # SiliconFlow-hosted DeepSeek V4 (https://siliconflow.com)
695 [providers.siliconflow]
696 # api_key = "YOUR_SILICONFLOW_API_KEY"
697 # base_url = "https://api.siliconflow.com/v1"
698 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
699
700 # SiliconFlow China-hosted DeepSeek V4 (https://siliconflow.cn)
701 # Falls back to [providers.siliconflow] for api_key / base_url / model when unset.
702 [providers.siliconflow-CN]
703 # api_key = "YOUR_SILICONFLOW_API_KEY"
704 # base_url = "https://api.siliconflow.cn/v1"
705 # model = "deepseek-ai/DeepSeek-V4-Pro"
706
707 # Arcee AI direct OpenAI-compatible endpoint (https://docs.arcee.ai)
708 [providers.arcee]
709 # api_key = "YOUR_ARCEE_API_KEY"
710 # base_url = "https://api.arcee.ai/api/v1"
711 # model = "trinity-large-thinking" # or trinity-large-preview
712
713 # Moonshot/Kimi OpenAI-compatible endpoint (https://platform.kimi.ai)
714 [providers.moonshot]
715 # api_key = "YOUR_MOONSHOT_API_KEY" # or KIMI_API_KEY
716 # base_url = "https://api.moonshot.ai/v1" # or KIMI_BASE_URL
717 # model = "kimi-k3" # direct Moonshot K3 wire ID
718 # Direct K3 is always-thinking: off -> low, medium -> high, and max remains max.
719 # The exact route sends top-level reasoning_effort, max_completion_tokens, and
720 # omits temperature/top_p per https://platform.kimi.ai/docs/guide/kimi-k3-quickstart.
721 # Kimi Code membership path (key: https://www.kimi.com/code/console):
722 # api_key = "YOUR_KIMI_CODE_API_KEY"
723 # base_url = "https://api.kimi.com/coding/v1"
724 # model = "k3" # Kimi Code K3 wire ID
725 # K3 membership off -> enabled/low; dispatched auto selects a concrete tier.
726 # Only an omitted reasoning setting leaves the provider default in control.
727 # Moderato plans are capped at 262144; Allegretto and above unlock up to 1048576.
728 # context_window = 262144 # manually cap k3 to the Moderato/256K window
729 # context_window = 1048576 # Allegretto+ only; do not claim an unavailable entitlement
730 # Alternatively use model = "k3-256k" with context_window = 262144 for the fixed 256K route.
731 # Omit context_window to keep Codewhale's safe 262144-token bare-k3 baseline.
732 # `k3[1m]` is a Claude Code-only convention, not an API model ID; Codewhale rejects it.
733 # Kimi Code K2.7 remains available to all members as model = "kimi-for-coding".
734 # Kimi OAuth is not supported. Legacy auth_mode = "kimi_oauth" fails closed
735 # to the API-key guidance above without probing Kimi CLI credential files.
736
737 # Z.AI GLM Coding Plan endpoint (https://docs.z.ai)
738 [providers.zai]
739 # api_key = "YOUR_ZAI_API_KEY" # or Z_AI_API_KEY
740 # base_url = "https://api.z.ai/api/coding/paas/v4"
741 # # General API endpoint, if you are not using the Coding Plan:
742 # # base_url = "https://api.z.ai/api/paas/v4"
743 # model = "GLM-5.3" # default; GLM-5.3-Flash is the fast sibling, GLM-5.2 the previous default, GLM-5.1 the smaller model, GLM-5-Turbo the 5.2 fast sibling
744 # # GLM-5.3 is live on the Z.ai Coding Plan (2026-08-13). Its catalog metadata
745 # # (limits, reasoning options) is inherited from GLM-5.2 until Z.ai publishes
746 # # distinct 5.3 numbers, and it carries no price. GLM-5.3-Flash (2026-08-26)
747 # # is the 1M multimodal picker row (`model = "GLM-5.3-Flash"`). An explicit
748 # # model = "GLM-5.2" keeps sending GLM-5.2; only the default moved. Accounts
749 # # not provisioned for 5.3 can still see a 429 with entitlement code 1311.
750
751 # StepFun / StepFlash direct OpenAI-compatible endpoint (https://platform.stepfun.ai)
752 [providers.stepfun]
753 # api_key = "YOUR_STEPFUN_API_KEY" # or STEP_API_KEY
754 # base_url = "https://api.stepfun.ai/v1" # or STEP_BASE_URL
755 # # Coding Plan endpoint:
756 # # base_url = "https://api.stepfun.ai/step_plan/v1"
757 # model = "step-3.7-flash" # or STEPFUN_MODEL / STEP_MODEL
758
759 # MiniMax direct OpenAI-compatible endpoint (https://platform.minimax.io)
760 [providers.minimax]
761 # api_key = "YOUR_MINIMAX_API_KEY"
762 # base_url = "https://api.minimax.io/v1"
763 # model = "MiniMax-M3" # or MiniMax-M2.7, MiniMax-M2.7-highspeed
764 # # MiniMax also publishes Anthropic-compatible endpoints:
765 # # global https://api.minimax.io/anthropic, China https://api.minimaxi.com/anthropic.
766
767 # Self-hosted SGLang OpenAI-compatible server
768 [providers.sglang]
769 # api_key = "OPTIONAL_SGLANG_TOKEN"
770 # base_url = "http://localhost:30000/v1"
771 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
772
773 # Self-hosted vLLM OpenAI-compatible server
774 [providers.vllm]
775 # api_key = "OPTIONAL_VLLM_TOKEN"
776 # base_url = "http://localhost:8000/v1"
777 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
778
779 # Self-hosted Ollama OpenAI-compatible server
780 [providers.ollama]
781 # api_key = "OPTIONAL_OLLAMA_TOKEN"
782 # base_url = "http://localhost:11434/v1"
783 # model = "deepseek-v4-flash" # or any local Ollama tag
784
785 # Hugging Face Inference Providers (https://huggingface.co/docs/api-inference)
786 # Provider aliases: huggingface, hugging-face, hugging_face, hf
787 # Env var aliases: HUGGINGFACE_API_KEY / HF_TOKEN, HUGGINGFACE_BASE_URL / HF_BASE_URL,
788 # HUGGINGFACE_MODEL / HF_MODEL
789 [providers.huggingface]
790 # api_key = "YOUR_HF_TOKEN"
791 # base_url = "https://router.huggingface.co/v1"
792 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
793
794 # DeepInfra — AI inference cloud (https://deepinfra.com)
795 [providers.deepinfra]
796 # api_key = "YOUR_DEEPINFRA_TOKEN"
797 # base_url = "https://api.deepinfra.com/v1/openai"
798 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
799
800 # ─────────────────────────────────────────────────────────────────────────────────
801 # Sakana AI Fugu Provider (https://api.sakana.ai)
802 # Provider aliases: sakana, sakana-ai, sakana_ai, fugu
803 # Env var aliases: FUGU_API_KEY, SAKANA_API_KEY
804 [providers.sakana]
805 # api_key = "YOUR_FUGU_API_KEY"
806 # base_url = "https://api.sakana.ai/v1"
807 # model = "fugu" # or fugu-ultra-20260615
808
809 # Meituan LongCat Provider (https://longcat.chat/platform)
810 # OpenAI-compatible curated gateway for Meituan's LongCat models.
811 # Provider aliases: longcat, long-cat, meituan-longcat, meituan
812 # Env var aliases: LONGCAT_API_KEY
813 [providers.longcat]
814 # api_key = "YOUR_LONGCAT_API_KEY"
815 # base_url = "https://api.longcat.chat/openai/v1"
816 # model = "LongCat-2.0"
817
818 # OpenCode Go (https://opencode.ai/docs/go/)
819 # Subscription-backed OpenAI-compatible Chat Completions route.
820 # Env vars: OPENCODE_GO_API_KEY, OPENCODE_GO_BASE_URL, OPENCODE_GO_MODEL
821 # Chat Completions models: deepseek-v4-pro, grok-4.5, glm-5.2, glm-5.1,
822 # kimi-k3, kimi-k2.7-code, kimi-k2.6, deepseek-v4-flash, mimo-v2.5,
823 # mimo-v2.5-pro.
824 # Models documented only on OpenCode Go's Anthropic `/messages` endpoint are
825 # intentionally not advertised by this provider yet.
826 [providers.opencode_go]
827 # api_key = "YOUR_OPENCODE_GO_API_KEY"
828 # base_url = "https://opencode.ai/zen/go/v1"
829 # model = "deepseek-v4-pro"
830
831 # OpenCode Zen (https://opencode.ai/docs/zen/)
832 # Model-aware gateway: GPT/Muse Spark use Responses, Claude/Qwen use Anthropic
833 # Messages, and DeepSeek/MiniMax/GLM/Kimi/Grok/free models use Chat Completions.
834 # Gemini uses a Google-specific protocol that Codewhale does not implement and
835 # therefore fails closed instead of being sent with the wrong request shape.
836 # Env vars: OPENCODE_ZEN_API_KEY (preferred), OPENCODE_API_KEY,
837 # OPENCODE_ZEN_BASE_URL, OPENCODE_ZEN_MODEL
838 [providers.opencode_zen]
839 # api_key = "YOUR_OPENCODE_ZEN_API_KEY"
840 # base_url = "https://opencode.ai/zen/v1"
841 # model = "gpt-5.5" # Responses
842 # model = "muse-spark-1.2-contributor-free" # Responses (free tier, auto-routed to Responses — no wire needed)
843 # model = "claude-sonnet-4-6" # Anthropic Messages example
844 # model = "deepseek-v4-pro" # Chat Completions example
845 # Custom gateway equivalent (when not using the opencode_zen provider):
846 # [providers.my_opencode]
847 # kind = "openai-compatible"
848 # base_url = "https://opencode.ai/zen/v1"
849 # model = "muse-spark-1.2-contributor-free"
850 # wire = "responses"
851 # api_key_env = "OPENCODE_ZEN_API_KEY"
852
853 # Meta Model API / Muse Spark (https://developer.meta.com/ai/)
854 # OpenAI-compatible Chat Completions route.
855 # Provider aliases: meta, meta-ai, meta-model-api, muse, muse-spark
856 # Env var aliases: META_MODEL_API_KEY / MODEL_API_KEY,
857 # META_MODEL_API_BASE_URL / MODEL_API_BASE_URL,
858 # META_MODEL_API_MODEL / MODEL_API_MODEL
859 [providers.meta]
860 # api_key = "YOUR_META_MODEL_API_KEY"
861 # base_url = "https://api.meta.ai/v1"
862 # model = "muse-spark-1.1"
863
864 # xAI / Grok Provider (https://console.x.ai/)
865 # OpenAI-compatible Chat Completions route.
866 # Provider aliases: xai, x-ai, x_ai, grok
867 # Env var aliases: XAI_API_KEY, XAI_BASE_URL, XAI_MODEL
868 #
869 # Auth modes:
870 # api_key (default) — console.x.ai pay-per-use key via api_key / XAI_API_KEY / keyring
871 # oauth — `codewhale auth xai-device` uses Codewhale-owned storage.
872 # Reading an existing Grok CLI file requires explicit
873 # `codewhale auth external-consent --provider xai --mode read-only`.
874 [providers.xai]
875 # api_key = "YOUR_XAI_API_KEY"
876 # auth_mode = "oauth" # or "device_code" / "grok_cli"
877 # base_url = "https://api.x.ai/v1"
878 # model = "grok-4.6" # or grok-4.5, grok-4.3, grok-build
879
880 # Mistral AI — la Plateforme (https://console.mistral.ai/)
881 # OpenAI-compatible Chat Completions route.
882 # Provider aliases: mistral, mistral-ai, mistralai, la-plateforme
883 # Env var aliases: MISTRAL_API_KEY, MISTRAL_BASE_URL, MISTRAL_MODEL
884 [providers.mistral]
885 # api_key = "YOUR_MISTRAL_API_KEY"
886 # base_url = "https://api.mistral.ai/v1"
887 # model = "mistral-code-latest" # or mistral-medium-latest, mistral-small-latest, mistral-large-latest
888
889 # Google Gemini — Google AI Studio (https://aistudio.google.com/apikey)
890 # OpenAI-compatible Chat Completions route on the official Gemini endpoint;
891 # this is the supported Gemini path (see docs/PROVIDERS.md).
892 # Provider aliases: google, gemini, google-gemini, ai-studio
893 # Env var aliases: GEMINI_API_KEY, GOOGLE_API_KEY, GEMINI_BASE_URL, GOOGLE_BASE_URL
894 [providers.google]
895 # api_key = "YOUR_GEMINI_API_KEY"
896 # base_url = "https://generativelanguage.googleapis.com/v1beta/openai/"
897 # model = "gemini-3.1-pro-preview"
898
899 # ─────────────────────────────────────────────────────────────────────────────────
900 # Alibaba Cloud Model Studio — Token Plan
901 # (https://bailian.console.aliyun.com/)
902 #
903 # Token Plan Personal and Team share the same AP-Southeast (Singapore) endpoint.
904 # Available text/coding models: qwen3.8-max, qwen3.8-max-preview, qwen3.7-plus,
905 # qwen3.7-max, qwen3.6-flash, deepseek-v4-pro, deepseek-v4-flash-0731, glm-5.2
906 #
907 # Provider aliases: modelstudio-token-plan, modelstudio_token_plan,
908 # alibaba-token-plan, dashscope-token-plan
909 # Env var aliases: MODELSTUDIO_API_KEY (preferred), DASHSCOPE_API_KEY,
910 # MODELSTUDIO_TOKEN_PLAN_BASE_URL, MODELSTUDIO_TOKEN_PLAN_MODEL
911 [providers.modelstudio_token_plan]
912 # api_key = "YOUR_MODELSTUDIO_API_KEY"
913 # base_url = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
914 # model = "qwen3.8-max"
915 # # Anthropic-compatible dialect (same key, /apps/anthropic path):
916 # # provider = "modelstudio-token-plan-anthropic"
917 # # base_url = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic"
918
919 # ─────────────────────────────────────────────────────────────────────────────────
920 # Alibaba Cloud Model Studio — Coding Plan
921 # (https://bailian.console.aliyun.com/)
922 #
923 # Coding Plan uses a separate international endpoint (coding-intl.dashscope).
924 # Same model catalog as the Token Plan.
925 #
926 # Provider aliases: modelstudio-coding-plan, modelstudio_coding_plan,
927 # alibaba-coding-plan, dashscope-coding-plan
928 # Env var aliases: MODELSTUDIO_API_KEY (preferred), DASHSCOPE_API_KEY,
929 # MODELSTUDIO_CODING_PLAN_BASE_URL, MODELSTUDIO_CODING_PLAN_MODEL
930 [providers.modelstudio_coding_plan]
931 # api_key = "YOUR_MODELSTUDIO_API_KEY"
932 # base_url = "https://coding-intl.dashscope.aliyuncs.com/v1"
933 # model = "qwen3.8-max"
934 # # Anthropic-compatible dialect (same key, /apps/anthropic path):
935 # # provider = "modelstudio-coding-plan-anthropic"
936 # # base_url = "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"
937
938 # ─────────────────────────────────────────────────────────────────────────────────
939 # Together AI Provider (https://www.together.ai/)
940 # Env var aliases: TOGETHER_API_KEY, TOGETHER_BASE_URL, TOGETHER_MODEL
941 [providers.together]
942 # api_key = "YOUR_TOGETHER_API_KEY"
943 # base_url = "https://api.together.xyz/v1"
944 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
945
946 # ─────────────────────────────────────────────────────────────────────────────────
947 # Baidu Qianfan Provider (https://intl.cloud.baidu.com/product/qianfan.html)
948 # Provider aliases: qianfan, baidu-qianfan, baidu_qianfan, baidu
949 # Env var aliases: QIANFAN_API_KEY / BAIDU_QIANFAN_API_KEY,
950 # QIANFAN_BASE_URL / BAIDU_QIANFAN_BASE_URL,
951 # QIANFAN_MODEL / BAIDU_QIANFAN_MODEL
952 [providers.qianfan]
953 # api_key = "YOUR_QIANFAN_API_KEY"
954 # base_url = "https://api.baiduqianfan.ai/v1"
955 # model = "ernie-4.0-turbo-8k" # or your Qianfan service/model id
956
957 # ─────────────────────────────────────────────────────────────────────────────────
958 # OpenAI Codex (ChatGPT) Provider — EXPERIMENTAL
959 # Run `codex login`, then explicitly grant read-only access to that exact file:
960 # codewhale auth external-consent --provider openai-codex --mode read-only
961 # Codewhale never refreshes or rewrites the Codex CLI file. No API key is
962 # stored here. Talks to the OpenAI Responses API at /codex/responses.
963 # Env var aliases: OPENAI_CODEX_ACCESS_TOKEN / CODEX_ACCESS_TOKEN (token override),
964 # OPENAI_CODEX_BASE_URL / CODEX_BASE_URL, OPENAI_CODEX_MODEL / CODEX_MODEL,
965 # OPENAI_CODEX_ACCOUNT_ID / CODEX_ACCOUNT_ID, OPENAI_CODEX_AUTH_FILE, CODEX_HOME
966 [providers.openai_codex]
967 # base_url = "https://chatgpt.com/backend-api"
968 # model = "gpt-5.6"
969 # The CLI writes this table after informed consent; do not copy it between
970 # providers or machines. Absence means disabled.
971 # [providers.openai_codex.external_credentials]
972 # access = "read_only"
973 # provider = "openai-codex"
974 # source = "codex_cli"
975 # path = "/absolute/path/to/.codex/auth.json"
976 # consent_version = 1
977
978 # ─────────────────────────────────────────────────────────────────────────────────
979 # Anthropic Provider (native Messages API)
980 # Talks to https://api.anthropic.com/v1/messages with x-api-key auth — not an
981 # OpenAI-compatible route. Models: claude-opus-4-8, claude-sonnet-4-6 (default),
982 # claude-haiku-4-5. Env vars: ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL,
983 # ANTHROPIC_MODEL.
984 [providers.anthropic]
985 # api_key = "sk-ant-..."
986 # base_url = "https://api.anthropic.com"
987 # model = "claude-sonnet-4-6"
988
989 # OpenModel Provider (Anthropic-compatible Messages API)
990 # Talks to https://api.openmodel.ai/v1/messages with Bearer auth. OpenModel
991 # routes DeepSeek, DashScope, Xiaomi, Claude, and other models by model id.
992 # Env vars: OPENMODEL_API_KEY, OPENMODEL_BASE_URL, OPENMODEL_MODEL.
993 [providers.openmodel]
994 # api_key = "om-..."
995 # base_url = "https://api.openmodel.ai"
996 # model = "deepseek-v4-flash"
997
998 # Codewhale API (https://app.codewhale.net/settings?section=api)
999 # Account-backed model access over the provider keys connected to the
1000 # customer's Codewhale account. The account's authenticated `GET /v1/models`
1001 # listing is the catalog authority: each `provider/model` row states its wire
1002 # (`chat-completions` → `/v1/chat/completions`, `anthropic-messages` →
1003 # `/v1/messages`, `responses` → `/v1/responses`). Every protocol authenticates
1004 # the account key with `Authorization: Bearer`, never `x-api-key`.
1005 # Provider aliases: codewhale-api, cw-api, codewhale-cloud
1006 # Env vars: CODEWHALE_API_KEY, CODEWHALE_API_BASE (HTTPS except loopback);
1007 # the generic CODEWHALE_MODEL override also applies.
1008 [providers.codewhale]
1009 # api_key = "cwc_key_..." # or `codewhale account api-keys create --name <name> --use`
1010 # base_url = "https://api.codewhale.net/v1"
1011 # model = "deepseek/deepseek-v4-pro" # provider/model exactly as the account catalog returns it
1012
1013 # ─────────────────────────────────────────────────────────────────────────────────
1014 # Web Search Provider
1015 # ─────────────────────────────────────────────────────────────────────────────────
1016 # Choose which backend the Web tool's search uses. Default is keyless Firecrawl
1017 # (bounded per-IP quota; `[search] api_key` or FIRECRAWL_API_KEY raises it).
1018 # Bing and DuckDuckGo HTML scraping need no API key. Tavily, Bocha, Metaso,
1019 # Baidu, Volcengine, Sofya and Serply need an api_key; SearXNG needs base_url.
1020 # API runtime failures and empty responses visibly degrade through DuckDuckGo
1021 # then Bing. Missing configuration and network-policy denials fail closed.
1022 #
1023 # [search]
1024 # provider = "firecrawl" # firecrawl | bing | duckduckgo | tavily | bocha | metaso | searxng | baidu | volcengine | sofya | serply
1025 # # firecrawl: Firecrawl Cloud search, keyless with a bounded per-IP quota
1026 # # duckduckgo: HTML scrape with Bing fallback
1027 # # bing: HTML scrape, no API key
1028 # # tavily: https://tavily.com — AI search, needs api_key
1029 # # TAVILY_API_KEY set + provider unset auto-selects tavily
1030 # # at runtime; it is never written to config.toml
1031 # # bocha: https://bochaai.com — 博查AI搜索,国内友好,需api_key
1032 # # metaso: https://metaso.cn — 秘塔AI搜索,需 api_key
1033 # # 设置 METASO_API_KEY 或 [search] api_key
1034 # # searxng: https://docs.searxng.org — trusted/self-hosted JSON API,
1035 # # set base_url; no public instance is used by default
1036 # # instance must enable JSON (settings.yml search.formats); HTML-only instances 403
1037 # # baidu: 百度 AI Search via qianfan.baidubce.com,需 api_key
1038 # # volcengine: 火山引擎 Ark web_search (免费 2 万次/月), 需 api_key
1039 # # 也回退到 VOLCENGINE_API_KEY / VOLCENGINE_ARK_API_KEY / ARK_API_KEY 环境变量
1040 # # sofya: https://sofya.co — AI search returning full page
1041 # # content (not snippets), needs api_key (ay_live_...);
1042 # # also falls back to the SOFYA_API_KEY env var
1043 # # serply: https://serply.io Google organic results with
1044 # # snippets, needs api_key;
1045 # # also falls back to the SERPLY_API_KEY env var
1046 # base_url = "https://search.example/" # optional DuckDuckGo-compatible HTML endpoint;
1047 # # required SearXNG root or /search endpoint
1048 # api_key = "YOUR_SEARCH_KEY" # required for tavily, bocha, metaso, baidu, volcengine, sofya, and serply; optional for firecrawl (raises the keyless quota); unused by searxng
1049 # # WARNING: treat config.toml like a secret file when
1050 # # storing API keys. Prefer env vars for local smoke tests.
1051 #
1052 # Env-var overrides:
1053 # CODEWHALE_SEARCH_PROVIDER → search.provider
1054 # DEEPSEEK_SEARCH_PROVIDER → search.provider (legacy alias)
1055 # CODEWHALE_SEARCH_API_KEY → search.api_key
1056 # DEEPSEEK_SEARCH_API_KEY → search.api_key (legacy alias)
1057 # CODEWHALE_SEARCH_BASE_URL → search.base_url
1058 # DEEPSEEK_SEARCH_BASE_URL → search.base_url (legacy alias)
1059 # FIRECRAWL_API_KEY → firecrawl key fallback
1060 # METASO_API_KEY → metaso key fallback
1061 # BAIDU_SEARCH_API_KEY → baidu key fallback
1062 # VOLCENGINE_API_KEY / VOLCENGINE_ARK_API_KEY / ARK_API_KEY → volcengine key fallback
1063 # SOFYA_API_KEY → sofya key fallback
1064 # SERPLY_API_KEY → serply key fallback
1065 # TAVILY_API_KEY → tavily autodetect when provider is unset (explicit
1066 # provider = "..." or CODEWHALE_SEARCH_PROVIDER still
1067 # wins; the autodetect is never persisted)
1068
1069 # ─────────────────────────────────────────────────────────────────────────────────
1070 # Network Policy (#135)
1071 # ─────────────────────────────────────────────────────────────────────────────────
1072 # Per-domain allow/deny rules for outbound network calls made by the TUI's
1073 # tools (`fetch_url`, `web_search`) and the MCP HTTP transport. Stdio MCP
1074 # servers and direct LLM API calls are unaffected.
1075 #
1076 # Precedence: deny wins. A host listed in both `allow` and `deny` is denied.
1077 #
1078 # Host-matching rules:
1079 # - Exact match: `api.deepseek.com` matches only `api.deepseek.com`.
1080 # - Subdomain wildcard: an entry starting with `.` (e.g. `.example.com`)
1081 # matches `api.example.com` and `a.b.example.com` but not the apex
1082 # `example.com`. To cover both, list both. `*.example.com` is also accepted.
1083 #
1084 # Defaults are intentionally conservative: when this section is absent, no
1085 # policy is enforced (mirrors pre-v0.7.0 behavior). To opt in:
1086 #
1087 # [network]
1088 # default = "prompt" # allow | deny | prompt
1089 # allow = ["api.deepseek.com", "github.com", ".githubusercontent.com"]
1090 # deny = []
1091 # proxy = ["github.com", ".githubusercontent.com"]
1092 # proxy_fake_ip_cidrs = ["198.18.0.0/15"] # requires both matching host and address
1093 # audit = true # one line per call to ~/.codewhale/audit.log
1094
1095 # ─────────────────────────────────────────────────────────────────────────────────
1096 # Verifier preview (#2093)
1097 # ─────────────────────────────────────────────────────────────────────────────────
1098 # Enables automatic claim-of-done verifier preview once the runtime trigger is
1099 # active. Manual `run_verifiers` remains available even when this is false.
1100 #
1101 # [verifier]
1102 # enabled = false
1103
1104 # ─────────────────────────────────────────────────────────────────────────────────
1105 # Advisor / Watcher (#3982)
1106 # ─────────────────────────────────────────────────────────────────────────────────
1107 # Optional background watcher that fires after each turn that contains tool
1108 # calls. It reads a bounded slice of recent tool interactions, makes a concise
1109 # LLM advisory call, and emits a brief note into the status area.
1110 #
1111 # Off by default. Enable with `[advisor] enabled = true` or toggle per-session
1112 # with `/advisor on` / `/advisor off`.
1113 #
1114 # Options (the full set — `AdvisorConfigToml` in crates/config/src/lib.rs):
1115 # enabled — master switch (default: false)
1116 # max_tool_calls — number of recent tool call/result pairs to read
1117 # (default: 10, clamped to 1–50)
1118 # rate_limit_secs — minimum seconds between successive advisor notes
1119 # (default: 60, clamped to 5–3600)
1120 # dedup_window_secs — notes whose content hash matches the previous note
1121 # within this window are dropped (default: 300)
1122 # model — model override for the advisory call; when absent the
1123 # advisor reuses the session's current model
1124 #
1125 # Unknown keys under [advisor] are silently ignored, not rejected — a typo
1126 # leaves the default in place with no error.
1127 #
1128 # [advisor]
1129 # enabled = false
1130 # max_tool_calls = 10
1131 # rate_limit_secs = 60
1132 # dedup_window_secs = 300
1133 # model = "deepseek-v4-flash"
1134
1135 # ─────────────────────────────────────────────────────────────────────────────────
1136 # Skills (#140)
1137 # ─────────────────────────────────────────────────────────────────────────────────
1138 # Settings for the `/skill install <spec>` community-skill installer.
1139 # * registry_url — curated index.json that resolves bare names to
1140 # `github:owner/repo` specs. Override to point at
1141 # a private fork or internal mirror.
1142 # * max_install_size_bytes — per-skill uncompressed size cap. Tarballs that
1143 # exceed this limit are rejected during validation.
1144 # Default: 5 MiB.
1145 #
1146 # `/skill install` is gated by `[network]`. Make sure `github.com` and
1147 # `raw.githubusercontent.com` are reachable (default `prompt` is fine — you'll
1148 # be asked once and can persist) before running it.
1149 #
1150 # [skills]
1151 # registry_url = "https://raw.githubusercontent.com/Hmbown/deepseek-skills/main/index.json"
1152 # max_install_size_bytes = 5_242_880
1153 # scan_codewhale_only = false # true: ignore Claude/OpenCode/Cursor/agentskills.io skill dirs
1154 # flat_workspace_root = false # true: opt in to trusted <workspace>/skills content
1155
1156 # Model-stream budgets and HTTP transport. `config dump` shows effective values.
1157 # Legacy [tui] stream_* keys remain fallbacks when a canonical field is absent.
1158 [stream]
1159 open_timeout_secs = 45 # response headers, including connect (0 = env/default, 5-300)
1160 chunk_timeout_secs = 900 # per-SSE-chunk idle bound (0 = default, 1-3600)
1161 max_resumes = 3 # whole-request reissues after failed streams (0 = off, 0-10)
1162 max_transparent_retries = 2 # retry before any content (0 = off, 0-10)
1163 max_stream_errors = 5 # recoverable errors per stream (0 = default, 1-50)
1164 max_duration_secs = 1800 # one stream, not the whole turn (0 = default, 10-86400)
1165 max_content_mb = 10 # one stream's content (0 = default, 1-512)
1166 connect_timeout_secs = 30 # TCP/TLS setup (0 = default, 1-300)
1167 force_http1 = false # a truthy CODEWHALE_FORCE_HTTP1 still pins HTTP/1.1
1168 # Keepalive settings apply to newly built model/catalog/fallback clients.
1169 tcp_keepalive_secs = 30 # idle TCP probes (0 = off, 1-3600)
1170 http2_keep_alive_interval_secs = 15 # active HTTP/2 PINGs (0 = off, 1-3600)
1171 http2_keep_alive_timeout_secs = 20 # PING ACK deadline (0 = default, 1-3600)
1172
1173 # ─────────────────────────────────────────────────────────────────────────────────
1174 # TUI
1175 # ─────────────────────────────────────────────────────────────────────────────────
1176 [tui]
1177 alternate_screen = "auto" # auto/always start on the alternate screen; never starts inline (keeps terminal scrollback). /fullscreen and /inline switch at runtime
1178 mouse_capture = true # true: TUI-owned mouse selection (payload set by selection_copy_markdown); false: raw terminal selection/copy
1179 selection_copy_markdown = true # true: copy transcript drag selections as Markdown source; false: rendered text
1180 # Model steps are uncapped by default. Uncomment to install an explicit
1181 # ceiling; omission or 0 means no step limit, positive values clamp to 1-100000.
1182 # max_model_steps = 1000
1183 # A turn has no wall-clock limit unless you set one; omission or 0 means none.
1184 # turn_wall_clock_secs = 7200 # cumulative wall clock for one turn, excluding
1185 # time blocked on a human approval (30-86400)
1186 osc8_links = true # emit OSC 8 escapes around URLs (Cmd+click in iTerm2/Ghostty/Kitty/WezTerm/Terminal.app 13+); set false for terminals that misrender
1187 # What the bottom chrome shows. Each key is one thing on screen: `mode` is the
1188 # posture bar's plan/act/operate chip, everything else is a segment of the
1189 # metrics line under it. Omit the key to use the built-in default; set [] to
1190 # strip the metrics line down to the help hint. You can also edit this
1191 # interactively with `/statusline`.
1192 # Supported keys: mode, model, context_percent, cost,
1193 # balance (prepaid providers only: DeepSeek, DeepSeekCN, OpenRouter,
1194 # SiliconFlow), cache, tokens, ttft, output_rate, workspace, git_branch.
1195 # Legacy session_metrics enables both ttft and output_rate.
1196 # Retired in 0.9.13: status, agents, reasoning_replay, prefix_stability,
1197 # last_tool_elapsed, rate_limit — they drove nothing. Old files
1198 # keep loading; the retired keys are ignored.
1199 # status_items = ["mode", "model", "context_percent", "cost", "tokens"]
1200 # Size presets for the two rows themselves (#5950) — composition stays in
1201 # status_items; these only decide how much of a row paints:
1202 # posture_bar = "full" # full | compact | hidden (default full)
1203 # # compact keeps the posture chips (and the cap
1204 # # warning) and drops the clocks, counts and hint;
1205 # # hidden gives the row to the transcript.
1206 # metrics_line = "compact" # full | compact | hidden (default compact)
1207 # # compact keeps the route, context reading, cost
1208 # # and balance plus selected TTFT/rate when they fit.
1209 # # It drops secondary counts and help; hidden gives the row to the transcript.
1210 # # Also settable at runtime: /config posture_bar compact
1211 # notification_condition = "unfocused" # unfocused | always | never
1212 # "unfocused" = notify only after this terminal has been
1213 # in the background for two seconds (default);
1214 # "always" = allow configured notifications while focused;
1215 # successful turns skip the threshold;
1216 # "never" = suppress all operator notifications.
1217 # locale = "auto" # UI chrome language: auto | en | ja | zh-Hans | zh-Hant | pt-BR | es-419
1218 # # | vi | ko | ca | de | fr | id | hi | ru | uk
1219 # # "auto" reads LC_ALL → LC_MESSAGES → LANG; falls back to English.
1220 # # Override: `locale = "zh-Hans"` for Simplified Chinese regardless of OS locale.
1221 # # Also settable at runtime: /config locale zh-Hans
1222 # # Note: this only affects TUI labels/chrome — it does NOT change model output language.
1223 # mention_menu_behavior = "fuzzy" # fuzzy | browser; browser lists immediate directory children for @-mentions.
1224
1225 # ─────────────────────────────────────────────────────────────────────────────────
1226 # Transcript
1227 # ─────────────────────────────────────────────────────────────────────────────────
1228 # Prose — user messages, assistant answers, and reasoning/thinking — fills the
1229 # full content width, consistent with tool/status cells and the wide-frame
1230 # decision in #5322 (#5436). Owners who want a bounded reading measure on
1231 # ultrawide terminals can cap it in columns.
1232 [transcript]
1233 # prose_measure = 120 # positive integer: cap prose wrap at N columns.
1234 # 0 or absent = full content width (default).
1235 # Must be a positive whole number; tool, diff, and
1236 # status cells always keep the full content width.
1237
1238 # ─────────────────────────────────────────────────────────────────────────────────
1239 # Feature Flags
1240 # ─────────────────────────────────────────────────────────────────────────────────
1241 [features]
1242 shell_tool = true
1243 subagents = true
1244 web_search = true # enables canonical web.run plus the compatibility web_search alias
1245 apply_patch = true
1246 mcp = true
1247 exec_policy = true
1248 code_mode = true # execute_tools composes MCP/plugin/native calls; false defers it behind tool_search
1249 # vision_model = false # enable vision model for image_analyze tool
1250 # verify_tool = false # disable the agent-callable `verify` self-critique tool
1251 # (#4196). On by default; the agent decides when to spend
1252 # the extra reasoning, so cost is only incurred on demand.
1253 # Set false to remove it from the model's tool catalog.
1254 # extension_host = false # EXPERIMENTAL. Run reviewed plugins' `native` host code
1255 # (TypeScript/JavaScript, Cordis/DSH plugin model) in a
1256 # separate Node.js (or, opt-in, Bun) process. Tools and
1257 # slash commands in this phase. Every
1258 # extension tool is always Required and never
1259 # read-only; like any Required tool it runs without
1260 # a prompt under Full Access, under Bypass, or with a
1261 # matching session grant, which is bound to the
1262 # plugin's reviewed build (an update asks again).
1263 # Sandbox: on macOS the host runs under Seatbelt, and
1264 # on Linux under bubblewrap (/usr/bin/bwrap) when a
1265 # launch-time probe shows it works. Either way: no
1266 # direct network, no reads of ~/.codewhale secrets/
1267 # credentials/config/sessions, and writes only to its
1268 # data dir and temp dirs (plus, on macOS, the Darwin
1269 # user cache, ~/.cargo/registry, ~/.cargo/git and the
1270 # npm cache, which every Seatbelt-sandboxed command
1271 # gets). If bwrap is missing or cannot start (for
1272 # example blocked unprivileged user namespaces), the
1273 # host runs UNSANDBOXED with your user permissions and
1274 # /plugin, `codewhale doctor` and the start diagnostic
1275 # say so with bwrap's own error. On Windows it always
1276 # runs with your user permissions.
1277 # Toggling this changes the plugin activation policy,
1278 # so every plugin must be re-reviewed afterwards.
1279
1280 # [extension_host]
1281 # runtime = "node" # "node" (default), "bun" or "auto". Bun is an
1282 # # opt-in, qualified on macOS only. "auto" uses
1283 # # Bun >= 1.4.0 when one is found and starts,
1284 # # else Node. "bun"/"node" never fall back.
1285 # # Unset with only `bun` set: "bun".
1286 # bun = "~/.bun/bin/bun" # Bun >= 1.4.0. When set, the only Bun tried; a
1287 # # broken one fails instead of searching PATH and
1288 # # $BUN_INSTALL/bin or ~/.bun/bin.
1289 # node = "/usr/local/bin/node" # Node.js ^22.19 || >=24. When set, the only Node
1290 # # tried. Unset, every `node` on PATH is tried in
1291 # # order, skipping any inside node_modules or the
1292 # # working directory.
1293
1294 # Per-plugin settings for the extension host, keyed by the plugin's manifest
1295 # name. The table is delivered to the plugin's `apply(ctx, config)` and checked
1296 # against the plugin's own `Config` schema, if it declares one. User config
1297 # only: a project's .codewhale/config.toml cannot set it. Read at start and at
1298 # `/plugin reload`, which re-activates a plugin whose table changed. At most
1299 # 16 KiB of plain TOML values; `/plugin show <name>` lists the keys, not the
1300 # values. Do not store secrets here: the plugin's code reads every value.
1301 #
1302 # [plugins."hello-extension".config]
1303 # greeting = "Howdy"
1304
1305 # ─────────────────────────────────────────────────────────────────────────────────
1306 # Vision Model Configuration (optional)
1307 # ─────────────────────────────────────────────────────────────────────────────────
1308 # Uses an OpenAI-compatible vision model API for the `image_analyze` tool.
1309 # api_key inherits from the main config if not specified.
1310 #
1311 # [vision_model]
1312 # model = "gemini-3.1-flash-lite-preview" # Required: vision-capable model ID
1313 # api_key = "YOUR_API_KEY" # Optional: defaults to main api_key
1314 # base_url = "https://generativelanguage.googleapis.com/v1beta/openai/" # Optional
1315 #
1316 # Xiaomi MiMo image understanding can be configured through the same tool:
1317 # model = "mimo-v2.5"
1318 # api_key = "YOUR_XIAOMI_KEY"
1319 # base_url = "https://token-plan-sgp.xiaomimimo.com/v1" # Token Plan / tp- keys
1320
1321 # ─────────────────────────────────────────────────────────────────────────────────
1322 # Retry Configuration
1323 # ─────────────────────────────────────────────────────────────────────────────────
1324 [retry]
1325 enabled = true
1326 max_retries = 3
1327 initial_delay = 1.0
1328 max_delay = 60.0
1329 exponential_base = 2.0
1330 # jitter = true # randomize each backoff delay
1331 # jitter_factor = 0.1 # ±10% spread (0.0-1.0)
1332 # respect_retry_after = true # honor a server Retry-After header
1333
1334 # ─────────────────────────────────────────────────────────────────────────────────
1335 # Goal loop (`[goal]`) — operate-mode persistent goals
1336 # ─────────────────────────────────────────────────────────────────────────────────
1337 # Operate-mode goals run to their completion gate with no default token, time,
1338 # or continuation ceiling. Token/time budgets, when supplied, are telemetry
1339 # only and do not stop a goal. The keys below are the opt-in circuit breakers.
1340 # [goal]
1341 # Optional safety backstop on automatic goal continuation passes.
1342 # Default: 0 (unlimited). Set a positive value to opt into a ceiling.
1343 # max_continuations = 100
1344 # Optional cancellable quiet period between successful turns, useful for
1345 # coordinator goals that poll on a cadence instead of keeping one provider
1346 # turn open. Default: 0 (continue immediately). Cap: 86400 (24h).
1347 # continuation_delay_seconds = 300
1348 # Per-turn step allowance while a goal is active (#5994): larger but still
1349 # finite. Default: 1000 (0/absent resolves to 1000). Range: 1..=100,000.
1350 # Bounds each turn, never the number of continuation passes; explicit
1351 # per-invocation ceilings (exec --max-turns, worker caps) always win.
1352 # max_steps = 1000
1353
1354 # ─────────────────────────────────────────────────────────────────────────────────
1355 # Context Compaction
1356 # ─────────────────────────────────────────────────────────────────────────────────
1357 # Auto-compaction is a saved UI setting edited with `/config` (`auto_compact`).
1358 # The optional saved threshold setting is `auto_compact_threshold_percent`
1359 # (default 80). There is no config-file
1360 # `[compaction]` table yet; runtime compaction budgets are chosen by the TUI
1361 # from the active model/context window.
1362
1363 # [context] supports one key, `project_pack` (default false, #4781). The
1364 # removed seam-manager keys (enabled, verbatim_window_turns, l1/l2/l3_threshold,
1365 # seam_model) are ignored if an older config still carries them.
1366
1367 # ─────────────────────────────────────────────────────────────────────────────────
1368 # Workshop / Tool-Output Budgets
1369 # ─────────────────────────────────────────────────────────────────────────────────
1370 # By default an oversized tool result uses bounded spillover: a result under the
1371 # byte threshold stays inline, a larger one gets a head/tail preview plus a
1372 # session artifact the model can read back. There is no synthesis sub-agent and
1373 # no per-call `raw = true` escape.
1374 #
1375 # `large_output_threshold_tokens` and `[workshop.per_tool_thresholds]` only take
1376 # effect when the process opts in to adaptive evidence routing with
1377 # `CODEWHALE_ADAPTIVE_OUTPUT_ROUTING=1`; they set where a result stops being
1378 # inline and becomes handle-only evidence. Per-tool keys are exact tool names as
1379 # the model sees them (`bash`, `fetch_url`, ...). The two byte ceilings below
1380 # apply either way.
1381 #
1382 # [workshop]
1383 # large_output_threshold_tokens = 4096
1384 # # Optional model-visible byte ceilings (#5367). Absent keeps the
1385 # # compile-time defaults (read 50KiB / read_file 16KiB, then the
1386 # # compact 12K-char floor). Values raise the floor; they never lower
1387 # # it. Hard cap is 2MiB.
1388 # # read_result_max_bytes = 102400
1389 # # tool_result_max_bytes = 102400
1390 # [workshop.per_tool_thresholds] # adaptive routing only
1391 # bash = 2048 # shell output becomes handle-only evidence sooner
1392 # fetch_url = 8192 # web pages can be large; give them more room
1393
1394 # ─────────────────────────────────────────────────────────────────────────────────
1395 # Profile Example (for multiple environments)
1396 # ─────────────────────────────────────────────────────────────────────────────────
1397 # Select a profile with `deepseek --profile <name>` or `DEEPSEEK_PROFILE=<name>`.
1398 # A profile's provider tables override the same fields of the base tables.
1399 [profiles.work.providers.deepseek]
1400 api_key = "WORK_DEEPSEEK_API_KEY"
1401 base_url = "https://api.deepseek.com/beta"
1402
1403 [profiles.dev]
1404 allow_shell = true
1405
1406 [profiles.dev.providers.deepseek]
1407 api_key = "DEV_DEEPSEEK_API_KEY"
1408
1409 [profiles.nvidia-nim]
1410 provider = "nvidia-nim"
1411 default_text_model = "deepseek-ai/deepseek-v4-pro"
1412
1413 [profiles.nvidia-nim.providers.nvidia_nim]
1414 api_key = "YOUR_NVIDIA_API_KEY"
1415 base_url = "https://integrate.api.nvidia.com/v1"
1416
1417 # ─────────────────────────────────────────────────────────────────────────────────
1418 # Desktop Notifications (OSC 9 / BEL on long agent-turn completion)
1419 # ─────────────────────────────────────────────────────────────────────────────────
1420 # Emits an escape sequence to the terminal when a turn **completes successfully**
1421 # and took longer than `threshold_secs`. Failed or cancelled turns are
1422 # intentionally silent. Useful when you tab away from the TUI and want an alert
1423 # for "your task is ready".
1424 #
1425 # method = "auto" # auto | osc9 | kitty | ghostty | bel | off
1426 # auto: native/terminal banner for known terminals.
1427 # On macOS, the native banner is silent; on Linux,
1428 # unknown terminals stay silent instead of ringing BEL.
1429 # osc9: \x1b]9;<msg>\x07 (iTerm2-style; shows macOS notification)
1430 # bel: explicit \x07 beep (MessageBeep on Windows)
1431 # off: disable banners; an explicit completion sound is independent
1432 # threshold_secs = 30 # only notify when the turn took >= this many seconds
1433 # include_summary = false # include elapsed time + cost in the notification body
1434 # subagent_completion = "final-only" # always | final-only | off — notices when
1435 # background work finishes: sub-agents, background
1436 # shells and durable tasks. final-only (default) sends
1437 # one notice naming everything that finished once no
1438 # agent, workflow or durable task is still running (a
1439 # running shell such as a dev server never holds it
1440 # back); off silences them entirely.
1441 # completion_sound = "off" # off | beep | bell | file — opt-in turn-completion sound
1442 # sound_file = "E:\\google\\downloads\\notify.wav" # WAV used when completion_sound = "file" (Windows)
1443 [notifications]
1444 # method = "auto"
1445 # threshold_secs = 30
1446 # include_summary = false
1447 # subagent_completion = "final-only"
1448 # completion_sound = "off"
1449 # sound_file = "E:\\google\\downloads\\notify.wav"
1450
1451 # Lifecycle event outbox: opt-in JSONL stream of session/turn/subagent
1452 # lifecycle events for supervisors and automation harnesses. One JSON line
1453 # per event (RuntimeEventEnvelope schema), appended and flushed on every
1454 # emit; seq is monotonic per file and recovers from the last line on open.
1455 # UNCOMMENT `path` TO ENABLE — unset/empty = OFF = behavior unchanged.
1456 # Fires for interactive TUI sessions AND headless `codewhale exec` runs.
1457 # See docs/CONFIGURATION.md → Lifecycle Outbox for the file contract.
1458 # [lifecycle_outbox]
1459 # path = "~/.codewhale/notifications/outbox.jsonl"
1460 # webhook_url = "https://example.com/hooks/codewhale" # optional: POST {"at", "event"} JSON per event
1461 # webhook_token = "" # optional: sent as `Authorization: Bearer <token>`
1462 # # delivery is best-effort: failures are logged and dropped
1463
1464 # Opt-in per-event sound cues (#4817): deterministic, terminal-bell level
1465 # (BEL bytes only — functional signals, platform-safe no-op when the terminal
1466 # ignores BEL). Off by default. When completion_sound is active, turn-complete
1467 # is left to that channel so the two never double-ding.
1468 # [notifications.event_sound]
1469 # enabled = false # master switch, default off
1470 # events = ["turn-complete", "approval-needed"] # allow-list; unknown names ignored
1471 # min_interval_ms = 2000 # per-event rate limit
1472 # quiet = false # true silences all event sounds
1473
1474 # ─────────────────────────────────────────────────────────────────────────────────
1475 # Workspace Snapshots (#137)
1476 # ─────────────────────────────────────────────────────────────────────────────────
1477 # Each turn the TUI takes a `pre-turn:<seq>` and `post-turn:<seq>` snapshot of
1478 # your workspace into a side-git repo at:
1479 #
1480 # ~/.codewhale/snapshots/<project_hash>/<worktree_hash>/.git
1481 #
1482 # Your own `.git` is never touched — `--git-dir` and `--work-tree` are always
1483 # set together when shelling out to git. Use `/restore N` (slash command) or
1484 # the `revert_turn` tool to roll the working tree back. Conversation history
1485 # is unaffected.
1486 #
1487 # Disk footprint: ~1-2 GB worst case for a 100 MB workspace × 12 turns/day,
1488 # typically far less thanks to git's content-addressed storage. The session
1489 # boot prunes anything older than `max_age_days` (default 7).
1490 #
1491 # [snapshots]
1492 # enabled = true # Snapshot workspace pre/post each turn for /restore
1493 # max_age_days = 7 # Older snapshots pruned at session start
1494 # max_workspace_gb = 2 # Snapshots self-disable on first init when the
1495 # # non-excluded workspace exceeds this size in GB
1496 # # (v0.8.32). Default 2 GB protects against running
1497 # # codewhale in directories with hundreds of GB
1498 # # of datasets / model weights / docker dumps where
1499 # # `git add -A` would hang the TUI for hours. Set
1500 # # to 0 to disable the cap (v0.8.31 behaviour);
1501 # # raise to a higher number for legitimate large
1502 # # monorepos.
1503
1504 # ─────────────────────────────────────────────────────────────────────────────────
1505 # LSP Diagnostics (post-edit) (#136)
1506 # ─────────────────────────────────────────────────────────────────────────────────
1507 # After every successful file edit (`edit_file`, `apply_patch`, `write_file`),
1508 # the engine asks an LSP server for diagnostics on the file and injects them
1509 # as a synthetic system message before the next API call. This lets the agent
1510 # see compile breaks immediately without round-tripping through the user.
1511 #
1512 # Enabled by default. Failure modes are non-blocking: a missing LSP binary,
1513 # a crashed server, or a timeout simply skips the post-edit hook for that
1514 # turn — the agent's work is never blocked.
1515 #
1516 # Built-in language → server defaults:
1517 # rust → rust-analyzer
1518 # go → gopls serve
1519 # python → pyright-langserver --stdio
1520 # typescript → typescript-language-server --stdio
1521 # java → jdtls
1522 # php → intelephense --stdio
1523 # vue → vue-language-server --stdio
1524 # c, cpp → clangd
1525 #
1526 # Java support uses Eclipse JDT LS via the `jdtls` command. IntelliJ IDEA is
1527 # not required, and installing IntelliJ IDEA alone does not install `jdtls`.
1528 #
1529 # Override the defaults via the `servers` table below.
1530 #
1531 # For languages not in the built-in list (Ruby, C#, Swift, etc.), use
1532 # `[lsp.custom.<ext>]` to register a language server:
1533 #
1534 # [lsp.custom.rb]
1535 # command = "ruby-lsp"
1536 # args = ["--stdio"]
1537 # language_id = "ruby"
1538 #
1539 # [lsp.custom.cs]
1540 # command = "csharp-ls"
1541 # args = []
1542 # language_id = "csharp"
1543 #
1544 # [lsp.custom.swift]
1545 # command = "sourcekit-lsp"
1546 # language_id = "swift"
1547 [lsp]
1548 # enabled = true
1549 # poll_after_edit_ms = 5000
1550 # max_diagnostics_per_file = 20
1551 # include_warnings = false
1552 # [lsp.servers]
1553 # rust = ["rust-analyzer"]
1554 # go = ["gopls", "serve"]
1555 # java = ["jdtls"]
1556 # php = ["intelephense", "--stdio"]
1557 # vue = ["vue-language-server", "--stdio"]
1558
1559 # ─────────────────────────────────────────────────────────────────────────────────
1560 # Hooks (optional)
1561 # ─────────────────────────────────────────────────────────────────────────────────
1562 # Hooks run shell commands on lifecycle events (session start/end, tool calls, etc.).
1563 # Configure as `[[hooks.hooks]]` under a `[hooks]` table.
1564 #
1565 # SCOPE: hooks are a TUI runtime feature. They fire from the interactive TUI
1566 # and the engine turn loop it drives. `codewhale exec`, the CLI subcommands,
1567 # the app-server / ACP surfaces, and the `workflow` tool do NOT fire them.
1568 #
1569 # Available events (all 11): session_start, session_end, message_submit,
1570 # tool_call_before, tool_call_after, mode_change, on_error, turn_end,
1571 # subagent_spawn, subagent_complete, shell_env.
1572 # See docs/HOOKS.md for the per-event payload, env var, and steering contract.
1573 #
1574 # `message_submit`, `tool_call_before`, and `shell_env` are the only events
1575 # whose result can change what Codewhale does. The rest are observer-only —
1576 # which means their RESULT is ignored, not that the command is harmless. Every
1577 # hook is an arbitrary shell command running with your credentials.
1578 #
1579 # Note: `default_timeout_secs` below OVERRIDES each hook's own `timeout_secs`.
1580 # Leave it unset if you want per-hook timeouts to apply. `/hooks list` shows the
1581 # effective value and names the override. `default_timeout_secs = 0` is
1582 # REJECTED at load — it would expire every hook immediately — and per-hook
1583 # `timeout_secs` applies instead. The timeout applies to background hooks too:
1584 # on expiry the whole process group is killed and then reaped, best-effort,
1585 # with a bounded reap wait (see docs/HOOKS.md → Timeouts).
1586 #
1587 # `background = true` means submitted and never awaited. The hook still gets
1588 # the documented stdin payload, environment, and timeout — it just has no exit
1589 # code, so it cannot steer. `shell_env` ignores the flag and always runs in the
1590 # foreground because its stdout is the contract.
1591 #
1592 # A condition that references context its event never carries is REJECTED at
1593 # load, logged, and shown by `/hooks list` — for example an `exit_code`
1594 # condition outside `tool_call_after` / `on_error`, or a `mode` condition on
1595 # `shell_env`. Rejection is per entry: a broken hook never drops another one
1596 # that happens to share its name. `on_error` fires for tool failures with the
1597 # tool name, call id, and reported exit code attached, so tool-scoped and
1598 # exit-code-scoped `on_error` hooks are supported.
1599 #
1600 # `shell_env` (#456) is special: the hook runs immediately before each
1601 # `exec_shell` invocation and its stdout is parsed as `KEY=VALUE\n` lines.
1602 # Those vars are applied on top of `exec_shell`'s environment. For LOCAL
1603 # execution that environment is built from a fixed allowlist of parent
1604 # variables (PATH, HOME, LANG, TERM, …) — an ambient secret exported in your
1605 # terminal is NOT forwarded to `exec_shell` by itself, so this hook is the
1606 # supported way to supply one. If an external sandbox backend is configured,
1607 # that allowlist does NOT apply: the backend owns its base environment and your
1608 # `shell_env` values are TRANSMITTED to it. Later hooks override
1609 # earlier ones. Use this for ephemeral credentials, per-skill PATH adjustments,
1610 # or short-lived tokens. The resolved KEY names (NEVER values) are written to
1611 # `~/.codewhale/audit.log` so each session can be reconciled later. Hook
1612 # failure / timeout simply contributes no vars — it does not abort the shell
1613 # call.
1614 #
1615 # [hooks]
1616 # enabled = true
1617 # default_timeout_secs = 30
1618 #
1619 # [[hooks.hooks]]
1620 # event = "session_start"
1621 # command = "echo 'Codewhale session started'"
1622 #
1623 # # Inject ephemeral creds into every shell call. Output one
1624 # # KEY=VALUE per line on stdout (export prefix optional).
1625 # [[hooks.hooks]]
1626 # name = "aws-creds"
1627 # event = "shell_env"
1628 # command = "aws-vault export my-profile --format=env"
1629 # # Optionally limit to specific tool names / categories:
1630 # # condition = { type = "tool_category", category = "shell" }
1631 #
1632 # # Observe sub-agent lifecycle events. These hooks receive bounded JSON
1633 # # metadata on stdin and are warn-only: failures do not affect sub-agent
1634 # # scheduling, prompts, or results. continue_on_error has no effect for
1635 # # these observer events; later matching hooks always continue.
1636 # [[hooks.hooks]]
1637 # name = "subagent-audit"
1638 # event = "subagent_complete"
1639 # command = "~/.codewhale/hooks/subagent-audit.sh"
1640
1641 # ─────────────────────────────────────────────────────────────────────────────────
1642 # Runtime API (`deepseek serve --http`) (#561)
1643 # ─────────────────────────────────────────────────────────────────────────────────
1644 # Tuning knobs for the local HTTP/SSE daemon. The server binds to 127.0.0.1
1645 # by default and is intended for local UIs (whalescale-desktop, dashboards,
1646 # automation scripts). Today this section only controls the CORS allow-list;
1647 # host/port/workers stay on `--host`, `--port`, and `--workers` flags.
1648 #
1649 # Built-in defaults always include:
1650 # http://localhost:3000 http://127.0.0.1:3000
1651 # http://localhost:1420 http://127.0.0.1:1420
1652 # tauri://localhost
1653 #
1654 # Use `cors_origins` to add extra dev origins (e.g. Vite's default `:5173`).
1655 # User entries STACK on top of the defaults — they do not replace them. The
1656 # CLI flag `--cors-origin URL` (repeatable) and env var
1657 # `DEEPSEEK_CORS_ORIGINS=url1,url2` resolve to the same merged list.
1658 #
1659 # [runtime_api]
1660 # cors_origins = ["http://localhost:5173", "http://127.0.0.1:5173"]
1661
1662 # ─────────────────────────────────────────────────────────────────────────────────
1663 # Tool Overrides & Plugins ([tools])
1664 # ─────────────────────────────────────────────────────────────────────────────────
1665 # The `[tools]` table lets you disable any built-in tool, or add your own
1666 # tool backed by a script or command — without forking or recompiling the
1667 # binary. A script or command cannot replace a built-in: an override keyed by
1668 # a built-in name is refused (logged) and the built-in stays active.
1669 #
1670 # Plugin scripts dropped in the plugin directory are auto-discovered and
1671 # registered as model-visible tools alongside the built-in ones. A script may
1672 # declare `# approval: suggest` (the default) or `# approval: required`;
1673 # `# approval: auto` is no longer supported and falls back to the default.
1674 #
1675 # Scripts receive the tool's JSON input on **stdin** and must return a
1676 # JSON `ToolResult` (`{"content": "...", "success": true}`) on **stdout**.
1677 #
1678 # [tools]
1679 # # Custom plugin directory (defaults to `~/.codewhale/tools/`)
1680 # plugin_dir = "~/.codewhale/tools"
1681 #
1682 # [tools.overrides]
1683 # # Disable a tool entirely — removes it from the model-visible catalog.
1684 # "Web" = { type = "disabled" }
1685 #
1686 # # Add a tool backed by a script. Relative paths resolve against plugin_dir.
1687 # "audited_shell" = { type = "script", path = "audit-shell.sh" }
1688 #
1689 # # Add a tool backed by a command (binary on PATH or absolute path).
1690 # "bat_reader" = { type = "command", command = "bat", args = ["--paging=never"] }
1691 #
1692 # # Scripts can also accept static arguments before the JSON input:
1693 # "cached_fetch" = { type = "script", path = "cached-fetch.sh", args = ["--ttl", "300"] }
1694
1695 # ──────────── Enterprise example: audit-logging shell wrapper ──────────────
1696 # Drop `audit-shell.sh` (below) in `~/.codewhale/tools/`, where it registers
1697 # itself as `audited_shell`, and turn the built-in shell off (`Bash` and its
1698 # lowercase `bash` form). Other shell-capable tools, such as the terminal
1699 # tools, stay available; disable them too if every command must pass the
1700 # wrapper.
1701 #
1702 # [tools.overrides]
1703 # "Bash" = { type = "disabled" }
1704 # "bash" = { type = "disabled" }
1705 #
1706 # The wrapper logs every request to `~/.codewhale/audit/shell.log`, then
1707 # delegates to your own approved shell executor. Do not pipe the raw JSON
1708 # request into `sh -s`; parse the command field and enforce your policy first.
1709 #
1710 # ```sh
1711 # #!/usr/bin/env sh
1712 # # name: audited_shell
1713 # # description: Audit-logging shell wrapper
1714 # # approval: required
1715 # LOGDIR="${HOME}/.codewhale/audit"
1716 # mkdir -p "$LOGDIR"
1717 # LOGFILE="$LOGDIR/shell.log"
1718 # input=$(cat)
1719 # echo "[$(date -Iseconds)] $input" >> "$LOGFILE"
1720 # printf '%s\n' '{"content":"audit wrapper dry run: configure an executor","success":false}'
1721 # ```
1722
1723 # ─────────────────────────────────────────────────────────────────────────────────
1724 # Workflow automatic launch, approval, isolation, and activity (#4128)
1725 # ─────────────────────────────────────────────────────────────────────────────────
1726 # First-class knobs for automatic Workflow orchestration. When the table is
1727 # omitted entirely, the runtime uses these product defaults. Later launch,
1728 # approval, and activity-persistence paths all read through this one model.
1729 # [workflow]
1730 # # Allow the parent agent to auto-launch Workflow for multi-agent work.
1731 # # Set false to require an explicit `/workflow` opt-in.
1732 # automatic = true
1733 # # Auto-start read-only plans without an approval card when automatic is on.
1734 # auto_start_read_only = true
1735 # # Require an approval card before write/shell/network/high-budget launches.
1736 # require_approval_for_writes = true
1737 # # Hard ceiling on agents in one Workflow run (matches VM lifetime cap).
1738 # max_children = 1000
1739 # # Maximum concurrently live agents inside one run (others wait for a slot).
1740 # max_concurrent = 16
1741 # # Maximum structural nesting depth accepted for Workflow IR (default 5).
1742 # # This is separate from Runtime child delegation below.
1743 # max_depth = 5
1744 # # Default shared token budget for a Workflow run and its children.
1745 # default_token_budget = 120000
1746
1747 # ─────────────────────────────────────────────────────────────────────────────────
1748 # Agent Fleet roster, role registry, and execution requests (#3165, #3167)
1749 # ─────────────────────────────────────────────────────────────────────────────────
1750 # [fleet]
1751 # # Fleet stores member identity and route intent. Project trust, host identity,
1752 # # secrets, approvals, sandboxing, filesystem/network reach, and tool authority
1753 # # are Runtime policy and are deliberately not configured here. Pre-0.9.11
1754 # # trust keys are accepted only as ignored migration input.
1755 #
1756 # # Headless worker execution hardening (#3027)
1757 # [fleet.exec]
1758 # # Tools always allowed regardless of role
1759 # allowed_tools = []
1760 # # Tools always disallowed (overrides role and task spec)
1761 # disallowed_tools = ["exec_shell"]
1762 # # Optional hard ceiling on worker steps (tool calls + model turns).
1763 # # Omit or set 0 for the unbounded default; use a positive value to opt in.
1764 # max_turns = 500
1765 # # Runtime child-agent depth for fleet workers. Shares ONE recursion axis with
1766 # # standalone sub-agents (a fleet worker IS a headless sub-agent). This is an
1767 # # execution request, not Fleet member identity. 0 blocks child agents (the
1768 # # root worker still runs); 3 is the default and 8 is the opt-in hard ceiling.
1769 # max_spawn_depth = 3
1770 # # Extra system prompt injected into every headless worker
1771 # append_system_prompt = "Never modify .git/config or change remotes."
1772 # # Output format: "text" (default) or "stream-json" for ndjson events
1773 # output_format = "text"
1774 #
1775 # # Fleet profiles define named agent configurations the roster can dispatch.
1776 # # Built-in profiles are always available: manager, operator, scout, builder,
1777 # # reviewer, verifier, synthesizer, general. User-defined profiles under
1778 # # [fleet.profiles] override or extend the built-in set by id. Precedence
1779 # # is Workspace (.codewhale/agents/*.toml) > Config ([fleet.profiles]) > BuiltIn.
1780 # # See /fleet setup for an in-app profile-authoring wizard.
1781 # [fleet.profiles.ci-linter]
1782 # slot = "verifier"
1783 # loadout = "fast"
1784 # model = "deepseek-v4-pro"
1785 #
1786 # [fleet.profiles.ci-linter.role]
1787 # name = "CI Linter"
1788 # description = "Runs linters and formatters"
1789 # instructions = "Run cargo fmt --check and cargo clippy; never apply fixes."
1790 #
1791 # [fleet.profiles.pr-reviewer]
1792 # slot = "reviewer"
1793 # loadout = "inherit"
1794 #
1795 # [fleet.profiles.pr-reviewer.role]
1796 # name = "PR Reviewer"
1797 # description = "Reviews PRs with GitHub access"
1798 # instructions = "Review diffs for correctness, regressions, and missing tests."
1799
1800 # ─────────────────────────────────────────────────────────────────────────────────
1801 # Named fleets live in files, not inline tables (0.9.14).
1802 #
1803 # Inline [fleets.<name>] tables were removed: define named fleets as
1804 # `fleets/<name>.toml` under $CODEWHALE_HOME or the workspace instead
1805 # (see docs/FLEET.md). Old inline tables still parse but are ignored.
1806 # ─────────────────────────────────────────────────────────────────────────────────
1807
1808 # ─────────────────────────────────────────────────────────────────────────────────
1809 # Requirements (admin constraints) example file
1810 # ─────────────────────────────────────────────────────────────────────────────────
1811 # allowed_approval_policies = ["on-request", "untrusted", "never"]
1812 # allowed_sandbox_modes = ["read-only", "workspace-write"]
1813
1813 lines TOML