# ╔══════════════════════════════════════════════════════════════════════════════╗ # ║ Codewhale Configuration ║ # ║ ║ # ║ Terminal coding agent for any model — open models first. ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # See `docs/CONFIGURATION.md` for how config is loaded (profiles, env overrides, etc.). # ───────────────────────────────────────────────────────────────────────────────── # Active provider + DeepSeek defaults # ───────────────────────────────────────────────────────────────────────────────── # Choose which provider to use by default. Per-provider credentials live in the # `[providers.*]` sections near the bottom of # this file — keeping both stored at once means `/provider deepseek` and # `/provider nvidia-nim` (or `--provider openai`, `--provider wanjie-ark`, # `--provider volcengine`, `--provider openrouter`, `--provider xiaomi-mimo`, # `--provider fireworks`, `--provider siliconflow`, `--provider siliconflow-CN`, # `/provider arcee`, `/provider moonshot`, `/provider qianfan`, `/provider sglang`, `/provider vllm`, # `/provider ollama`, `/provider huggingface`, `/provider stepfun`, `/provider openmodel`, # `/provider opencode-go`, `/provider opencode-zen`, `/provider meta`, `/provider xai`) toggle without having to re-enter keys. Keys and # endpoints live in each provider's `[providers.]` table. An older # top-level `api_key` / `base_url` is still read: it belongs to # `[providers.deepseek]` (or to the vendor whose official host it names), and # `codewhale config migrate` moves it there in the file. 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 # provider = "deepseek-cn" # legacy alias (official host is still https://api.deepseek.com) # Optional custom model request headers for OpenAI-compatible gateways. # Authorization and Content-Type are managed by the client and cannot be overridden here. # http_headers = { "X-Model-Provider-Id" = "your-model-provider" } # ───────────────────────────────────────────────────────────────────────────────── # Default Models # ───────────────────────────────────────────────────────────────────────────────── # DeepSeek V4 family: # deepseek-v4-pro — flagship reasoning model on DeepSeek Platform # deepseek-v4-flash — fast, cost-efficient (legacy aliases: deepseek-chat, deepseek-reasoner) # deepseek-ai/deepseek-v4-pro — NVIDIA NIM-hosted Pro model ID # deepseek-ai/deepseek-v4-flash — NVIDIA NIM-hosted Flash model ID # deepseek/deepseek-v4-pro — default OpenRouter DeepSeek model ID # arcee-ai/trinity-large-thinking — OpenRouter Arcee Trinity Large Thinking # xiaomi/mimo-v2.5-pro — OpenRouter Xiaomi MiMo 2.5 Pro # xiaomi/mimo-v2.5 — OpenRouter Xiaomi MiMo 2.5 # z-ai/glm-5.1 — OpenRouter Z.AI GLM 5.1 # z-ai/glm-5.2 — OpenRouter Z.AI GLM 5.2 # z-ai/glm-5.3 — OpenRouter Z.AI GLM 5.3 (live on Z.ai since 2026-08-13; # metadata inherited from 5.2, unpriced) # z-ai/glm-5.3-flash — OpenRouter Z.AI GLM 5.3 Flash (1M multimodal; $0.15/$0.50 list) # z-ai/glm-5-turbo — OpenRouter Z.AI GLM 5 Turbo (scout fast sibling of 5.2) # GLM-5.3 — default direct Z.AI Coding Plan model (live since 2026-08-13; # metadata inherited from 5.2, unpriced) # GLM-5.3-Flash — direct Z.AI GLM 5.3 Flash (faster/explore sibling of 5.3) # GLM-5.2 — direct Z.AI GLM 5.2 (previous default; explicit selections keep it) # GLM-5.1 — direct Z.AI smaller model # GLM-5-Turbo — direct Z.AI fast model (scout fast sibling of 5.2) # step-3.7-flash — default direct StepFun / StepFlash model ID # kimi-k3 — direct Moonshot K3 model ID (1M context) # kimi-k2.7-code — default direct Moonshot/Kimi K2.7 model ID # k3 — Kimi Code membership K3 API model ID # kimi-for-coding — Kimi Code membership K2.7 compatibility ID # kimi-for-coding-highspeed — Kimi Code membership high-speed roster ID # gpt-4.1 — default generic OpenAI-compatible model ID # deepseek-ai/deepseek-v4-flash — default AtlasCloud model ID # deepseek-reasoner — default Wanjie Ark model ID # mimo-v2.6-pro — default Xiaomi MiMo model ID # mimo-v2.5-pro-ultraspeed — Xiaomi MiMo V2.5 Pro UltraSpeed chat model ID # mimo-v2.6-flash — Xiaomi MiMo V2.6 Flash model ID # mimo-v2.5-tts — Xiaomi MiMo speech/TTS model ID # mimo-v2.5-tts-voicedesign — Xiaomi MiMo voice-design TTS model ID # mimo-v2.5-tts-voiceclone — Xiaomi MiMo voice-clone TTS model ID # accounts/fireworks/models/deepseek-v4-pro — Fireworks AI Pro model ID # deepseek-ai/DeepSeek-V4-Pro — SiliconFlow hosted Pro model ID # deepseek-ai/DeepSeek-V4-Flash — SiliconFlow hosted Flash model ID # trinity-large-thinking — default direct Arcee AI API model ID # trinity-large-preview — direct Arcee AI API model ID # deepseek-ai/DeepSeek-V4-Pro — SGLang self-hosted Pro model ID # deepseek-ai/DeepSeek-V4-Flash — SGLang self-hosted Flash model ID # auto — auto-select between flash and pro based on task complexity. # Complex tasks (debugging, refactoring, architecture) → pro; # simple tasks (lookups, formatting, Q&A) → flash. default_text_model = "deepseek-flash" # ───────────────────────────────────────────────────────────────────────────────── # Thinking Mode (DeepSeek V4 reasoning effort) # ───────────────────────────────────────────────────────────────────────────────── # "auto" — the route chooses per request (default) # "off" — disables chain-of-thought (thinking.type = disabled) # "low" — compat-maps to "high" server-side # "medium" — compat-maps to "high" server-side # "high" — reasoning_effort = high (DeepSeek default) # "max" — reasoning_effort = max (deepest reasoning) # # Ctrl+T in the TUI cycles the reasoning tier. The header shows the current # tier as a ⚡ chip. (Shift+Tab cycles the permission posture — Ask / # Auto-Review / Full Access — not the reasoning tier.) reasoning_effort = "auto" # NOTE: `show_thinking`, `thinking_default_expanded`, `thinking_preview_lines`, # `help_expand_groups`, `pin_last_prompt`, and `cost_currency` live in # `~/.codewhale/settings.toml`, not here — `Config` has no such fields and # unknown keys are ignored. See crates/tui/src/settings.rs. # # Density (Grok-like compact defaults; turn these up if you want more shown): # thinking_preview_lines = 2 # 0 header-only, 10 older dump # thinking_default_expanded = false # help_expand_groups = false # true = F1 starts fully expanded # pin_last_prompt = true # show_tool_details = false # Root-level keys must appear before the first `[table]` header: TOML assigns # every key after a header to that table. Keep new top-level keys in this # block (scripts/check-config-example.py enforces it in CI). # ───────────────────────────────────────────────────────────────────────────────── # Paths # ───────────────────────────────────────────────────────────────────────────────── # New installs write product state under ~/.codewhale/. Existing ~/.deepseek/ # files are still read as compatibility fallbacks when the .codewhale file is # absent. skills_dir = "~/.codewhale/skills" mcp_config_path = "~/.codewhale/mcp.json" notes_path = "~/.codewhale/notes.txt" # Anchors the native memory store. The filename itself is not written: under # the Native backend (the only backend) the store is re-rooted to # `/memory/global/MEMORY.md`. With the default below that # resolves to `~/.codewhale/memory/global/MEMORY.md` (plus workspace-scoped # files and a rebuildable SQLite FTS5 index). See docs/MEMORY.md. memory_path = "~/.codewhale/memory.md" # instructions = ["./AGENTS.md", "~/.codewhale/global.md"] # # Optional list of additional instruction files concatenated into the # system prompt in declared order (#454). Useful for layering # repo-specific rules on top of a global preferences file. Each entry # is expanded so `~` and env vars work; missing files are skipped with # a tracing warning. Files are capped at 100 KiB per entry. # # Project-level config (.codewhale/config.toml in the workspace) replaces # the user-level array wholesale rather than merging — list `~/global.md` # inside the project array if you want both. An explicit empty array # (`instructions = []`) clears the user list for the current repo. # ───────────────────────────────────────────────────────────────────────────────── # Security # ───────────────────────────────────────────────────────────────────────────────── allow_shell = true approval_policy = "on-request" # on-request | untrusted | never sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access | external-sandbox # Whether a workspace-write sandbox also lets shell commands reach the # network. Default false: being allowed to edit this repository is not a # reason to be allowed to open outbound connections, so `curl`, package # installs, and `git fetch` are denied by the OS sandbox unless you opt in. # When a command is denied, Codewhale offers an elevation prompt that grants # network for that call only; set this to `true` to grant it for the whole # session instead. `danger-full-access` and `yolo` are unsandboxed and # unaffected by this key. Note that on platforms with no OS sandbox backend # (default Linux without bubblewrap, and Windows) nothing is enforced either # way -- `/status` and `doctor` both say so. # sandbox_network_access = false # Which other agents' instruction files to import as project instructions. # Empty by default. Codewhale reads AGENTS.md (the cross-agent standard) and # its own .codewhale/instructions.md without being asked; a CLAUDE.md, # .cursorrules, or .github/copilot-instructions.md written as law for a # different tool is not treated as law here until you say so. Codewhale's own # files always outrank anything imported. # Accepts: "claude", "cursor", "cline", "windsurf", "gemini", "copilot", # "muse", or "all". Env: CODEWHALE_PROJECT_INSTRUCTION_IMPORTS (comma-separated). # project_instruction_imports = ["claude"] # # Standing instructions are budgeted per scope, in estimated tokens, and every # trim or skip is reported to the model and in /context. # `global` covers ~/.codewhale/AGENTS.md (or ~/.agents/AGENTS.md), the global # instructions.md fallbacks, ~/.claude/CLAUDE.md (last resort, claude_fallback # only) and the `instructions = [...]` array. `project` covers the # repository-root -> workspace AGENTS.md chain, co-read foreign files, and # .codewhale/rules/*.md. A scope is capped at the smaller of its absolute token # budget and its share of the usable window (context window minus the output # reservation and a 10% safety reserve). Each file keeps at most its first # 100 KiB. User config only: a cloned repository cannot change it. # [instruction_budget] # global_budget_tokens = 100000 # positive; absolute cap for the global scope # project_budget_tokens = 100000 # positive; absolute cap for the project scope # global_budget_percent = 25 # 1..=100, share of the usable window # project_budget_percent = 25 # 1..=100, share of the usable window # static_prefix_guard_percent = 80 # 1..=100; guard must leave >=10 points # file_max_bytes = 8388608 # 1..=8388608; larger files are skipped # policy = "trim" # trim | refuse # claude_fallback = false # opt in: AGENTS.md wins; else CLAUDE.md # prompt_suggestion = true # opt-in: show ghost-text follow-up question in composer after each turn # Optional tab/window title shown as `[title] …` in front of the terminal # window title (the `Codewhale` / `reasoning…` / `using tool…` / `done` # states). Multi-window setups can give each workspace its own `--config` # file (or profile) so alt-tabbed sessions are identifiable at a glance. # The `/title` command overrides this per session; `/config title … --save` # persists a new default here. Run `/title off` to drop a session override. # title = "workspace-x" # Typed permission rules live in a sibling `permissions.toml` file, not in # config.toml. Each `[[rules]]` entry accepts `tool`, optional `command` # or `path`, optional absolute `workspace`, optional `command_exact = true`, # and an `action` field: `"deny"`, `"ask"` (default), or `"allow"`. # Deny always wins over ask, which wins over allow. The active user's sibling # file is the only rule source today; project config overlays do not load a # project-local permissions.toml. `workspace` is a repo scope, not a source. # Globs and broad directory rules remain future work. # # In supported approval cards, press `S` to allow once and save an exact ask # rule. Eligible safe calls also offer `P` / "Always allow this exact rule in # this repo", which saves an exact `allow` rule scoped to the current absolute # workspace. Dangerous/critical calls and repo-law prompts cannot save allow # grants. The approval UI still does not save deny rules: # exec_shell -> exact approved command string # write_file -> exact workspace-relative target path # edit_file -> exact workspace-relative target path # apply_patch -> one exact workspace-relative path per validated touched file # `read_file` rules can be written manually, but the approval UI does not save # them. # # `/permissions list` shows the active source, effective matcher, repo/global # scope, and whether each rule applies in the current workspace. # `/permissions remove ` previews a deletion and prints a confirmation # command bound to the current file snapshot. If the file changes first, the # confirmation fails safely. Removal preserves unrelated comments/formatting, # uses the same lock as approval-card appends, and atomically replaces the file. # `/config ask-rules` remains a compatibility entry to the same list. # # Example ~/.codewhale/permissions.toml: # # [[rules]] # tool = "exec_shell" # command = "cargo test" # action = "ask" # # # Block dangerous commands # [[rules]] # tool = "exec_shell" # command = "sed" # action = "deny" # # [[rules]] # tool = "exec_shell" # command = "awk" # action = "deny" # # # Allow trusted commands without asking # [[rules]] # tool = "exec_shell" # command = "git status" # command_exact = true # workspace = "/absolute/path/to/project" # action = "allow" # # # Path-based deny # [[rules]] # tool = "write_file" # path = "src/main.rs" # action = "deny" # # [[rules]] # tool = "edit_file" # path = "src/lib.rs" # action = "ask" # # [[rules]] # tool = "apply_patch" # path = "src/patch-target.rs" # # [[rules]] # tool = "read_file" # path = "secrets/api_key.txt" # action = "deny" # ───────────────────────────────────────────────────────────────────────────────── # External Sandbox Backend (pluggable remote execution) # ───────────────────────────────────────────────────────────────────────────────── # When sandbox_backend is set to "opensandbox", all exec_shell calls are # routed through an external OpenSandbox-compatible HTTP API instead of # spawning a local process. The backend sends `POST {sandbox_url}/v1/sandbox/run` # with `{"cmd": "...", "env": {...}}` and expects # `{"stdout": "...", "stderr": "...", "exit_code": 0}`. # # sandbox_backend = "none" # "none" (default) or "opensandbox" # sandbox_url = "http://localhost:8080" # OpenSandbox-compatible API base URL # sandbox_api_key = "YOUR_API_KEY" # Optional Bearer token sent with requests # # Env-var overrides: # DEEPSEEK_SANDBOX_BACKEND → sandbox_backend # DEEPSEEK_SANDBOX_URL → sandbox_url # DEEPSEEK_SANDBOX_API_KEY → sandbox_api_key # # Example OpenSandbox setup: # # sandbox_backend = "opensandbox" # sandbox_url = "http://localhost:8080" # sandbox_api_key = "sk-opensandbox-secret" # # The backend uses a 30-second HTTP timeout. Background, interactive, and # TTY modes are not supported with external backends — all commands run # synchronously via HTTP. # ───────────────────────────────────────────────────────────────────────────────── # Bubblewrap (Linux only, OS-level filesystem isolation) # ───────────────────────────────────────────────────────────────────────────────── # On by default: when `/usr/bin/bwrap` is installed and a probe shows it can # create its namespaces on this host, sandboxed exec_shell commands are routed # through it. Bubblewrap creates a read-only view of the root filesystem with # write access limited to the sandbox mode's writable roots. Install separately: # # Ubuntu/Debian: apt install bubblewrap # Fedora: dnf install bubblewrap # Arch: pacman -S bubblewrap # # prefer_bwrap = false # opt out — Linux commands run unwrapped # # Env override: CODEWHALE_PREFER_BWRAP=true|false # Legacy alias (deprecated until 0.10.0): DEEPSEEK_PREFER_BWRAP=true|false # # With prefer_bwrap = true, the sandbox gets a private /dev and /proc plus a # writable isolated /tmp by default, so toolchains work without widening the # filesystem policy (#5410). Two optional escape hatches: # # bwrap_ro_roots = [] # extra host paths bind-mounted read-only, e.g. # # ["/usr/lib", "/usr/lib64"] for system-library # # linking when the root bind is narrowed # bwrap_dev_roots = [] # host device nodes bind-mounted read-write, e.g. # # ["/dev/null"] for redirection against the host # # node — character/block devices only, never # # directories, so this cannot widen file writes # There is no config.toml `auto_allow` key. To pre-approve a command, add an # `action = "allow"` rule to permissions.toml `[[rules]]` (see the # permissions.toml example above). max_subagents = 64 # optional (default 64, clamped to 1-128) # Optional managed policy paths (defaults to /etc/deepseek/*.toml on unix): # managed_config_path = "/etc/deepseek/managed_config.toml" # requirements_path = "/etc/deepseek/requirements.toml" # ───────────────────────────────────────────────────────────────────────────────── # Product telemetry — optional, enabled by default with a startup disclosure # ───────────────────────────────────────────────────────────────────────────────── # Anonymous usage analytics are enabled when this key is omitted, unless an # existing opt-out or a run-scoped kill switch disables them. No first-run # affirmative opt-in is required. See docs/TELEMETRY.md for the complete schema. # Local session history and usage diagnostics work with telemetry disabled; # they require no account or hosted analytics service. # # Setting it to `false` here is an answer, not just a flag: it deletes the # random install id, truncates every buffered event, and leaves a tombstone # that a session already running re-checks before it sends anything. Every # later run re-reads this key and re-asserts that tombstone, so it stands for # as long as the `false` does — and nothing outranks it, not `--telemetry true` # and not `CODEWHALE_TELEMETRY=1`. Turning telemetry back on means writing # `telemetry = true` here. # # The environment variable and the flag are different: they stop the run and # erase nothing, so a harness that disables telemetry for one command does not # discard the machine owner's install id and dry-run records. # # codewhale config set telemetry false # opt out: stops it and erases state # CODEWHALE_TELEMETRY=0 codewhale # kill switch: stops it, erases nothing # # What is never collected: prompts, completions, tool arguments, diffs, file # contents, filenames, paths, git remotes, repo or branch names, memory # entries, chat history, API keys or tokens (not even a boolean saying one # exists), model ids, custom provider table names, MCP server names, error or # panic message bodies, per-event timestamps, keystrokes, clipboard, # screenshots, or location. The complete schema is `docs/TELEMETRY.md`, and a # test asserts this file and the serializer agree. # # A repo-local `.codewhale/config.toml` can set neither key: someone else's # repository cannot turn your telemetry on or aim it at a host of their choosing. # telemetry = false # Where batches are POSTed. Leaving this unset selects the shipped default, # the first-party ingest service: # # https://telemetry.codewhale.net/v1/telemetry # # That default is only ever consulted for a session that is already enabled — # it decides where a batch goes, never whether one exists. Nothing is sent # until `telemetry` is on AND the first-run notice was answered with Enable. # # Two overrides, both of which beat the default: # # telemetry_endpoint = "https://collector.internal/v1/batch" # your own sink # telemetry_endpoint = "" # contact nobody # # The empty string is the local dry-run sink: batches are serialized exactly as # a real endpoint would see them, appended to # `$CODEWHALE_HOME/telemetry/dryrun.jsonl`, and no HTTP client is ever # constructed. Read that file to see precisely what would have been sent. # `CODEWHALE_TELEMETRY_ENDPOINT` overrides this file, and setting it to the # empty string means the same "contact nobody". # # `https://` is required; plain `http://` is accepted only for loopback, and no # environment variable overrides that refusal. # telemetry_endpoint = "https://telemetry.codewhale.net/v1/telemetry" # Signed catalog overlays are optional and inactive without approved trust keys. # Explicit route/model selections and provider-owned rosters keep priority. # CODEWHALE_DISABLE_CLOUD_FACTS=1 disables cache, local files and network too. [cloud_facts] enabled = false channel = "stable" ttl_hours = 6 # url = "https://codewhale.net/api/facts/v1/{channel}" # ───────────────────────────────────────────────────────────────────────────────── # Startup update check # ───────────────────────────────────────────────────────────────────────────────── # The TUI checks for newer Codewhale releases in the background at startup. # Set check_for_updates = false in managed or air-gapped environments. # The result is cached in ~/.codewhale/update-check.json, so the network is # touched at most once per check_interval_hours while the notice still shows # on every launch. Set 0 to check on every launch. # Checks are skipped entirely in CI, and when CODEWHALE_NO_UPDATE_CHECK or # NO_UPDATE_NOTIFIER is set. # update_uri may point at a GitHub-compatible latest-release JSON endpoint. # show_whats_new = false turns off the one-time "Updated to " notice # and release-notes link shown on the first start after an update. [update] check_for_updates = true check_interval_hours = 24 show_whats_new = true # update_uri = "https://internal.mirror.example/codewhale/releases/latest" # ───────────────────────────────────────────────────────────────────────────────── # Hotbar slots (#2061 / #2064) # ───────────────────────────────────────────────────────────────────────────────── # Optional hotbar bindings for slots 1-8. Since #3807 the Hotbar is hidden by # default: an omitted `hotbar` key and an explicit `hotbar = []` both mean no # hotbar. It shows only when [[hotbar]] tables are configured here, and # `/hotbar on` writes the eight default slots as explicit [[hotbar]] tables: # 1 slash.workflow 2 slash.goal 3 slash.auto 4 mode.plan # 5 mode.agent 6 mode.operate 7 palette.open 8 sidebar.toggle # # Invalid slots are skipped with a warning, duplicate slots use the last entry, # and unknown actions are preserved so the UI can show a disabled entry. # Slash commands can be bound as slash., for example slash.workflow. # Commands that require arguments pre-fill the composer instead of running # incomplete. # # [[hotbar]] # slot = 1 # label = "voice" # action = "voice.toggle" # # [[hotbar]] # slot = 2 # action = "session.compact" # # [[hotbar]] # slot = 3 # label = "mode" # action = "slash.mode" # ───────────────────────────────────────────────────────────────────────────────── # User memory (#489) — opt-in. When enabled, the TUI loads the native store # derived from memory_path (see above), injects a bounded recall block into # the system prompt, intercepts `# foo` in the composer, and registers the # `remember` / `memory_search` / `memory_get` tools. # ───────────────────────────────────────────────────────────────────────────────── [memory] # enabled = true # turn the feature on (default: false) # Override the env-var equivalent: `DEEPSEEK_MEMORY=on` # Xiaomi MiMo speech/TTS defaults. Also configurable with # XIAOMI_MIMO_SPEECH_OUTPUT_DIR / MIMO_SPEECH_OUTPUT_DIR. [speech] # output_dir = "./speech" # ───────────────────────────────────────────────────────────────────────────────── # Reasoning-only recovery # ───────────────────────────────────────────────────────────────────────────────── # When a reasoning model returns only hidden thinking without any answer text # or tool call, the engine can re-request the answer automatically. # Set max_reprompts = 0 to disable automatic recovery entirely. # [reasoning_only] # max_reprompts = 2 # reprompt_message = "So, what's up ? Keep running !" # ─────────────────────────────────────────────────────────────────────────── # Model-bound key redaction ([redaction]) # ─────────────────────────────────────────────────────────────────────────── # Codewhale masks credential-looking values in tool output before it reaches # the model (the "model boundary"), so a file that contains a configured API # key, a bare provider token, or a credential-shaped opaque string never leaks # those bytes to the model. Leave this enabled unless the model must read and # edit files that contain real credentials. # # Disabling is a security decision, so it is never a plain flag: # * Set model_bound = "disabled" here, restart Codewhale, and the startup # gate asks twice - a first confirmation, then a red "are you really # sure?" stage. Only the second confirmation takes effect, and it applies # on later launches while model_bound stays "disabled". # * Going back to "enabled" - or rewriting config.toml after the # confirmation - invalidates it: requesting "disabled" again always # asks for a fresh confirmation. # * Until a confirmation exists - including in non-interactive/headless # runs, which never confirm anything - masking stays on regardless of # this key. Choosing "keep masking on" on the gate leaves the key # untouched, so the next launch asks again. # * The value is forgiving: false/"off" mean "disabled"; true/"on" mean # "enabled". # [redaction] # model_bound = "enabled" # mask keys before they reach the model (default) # model_bound = "disabled" # request the opt-out (restart + confirm required) # Native tool catalog controls (#2076). By default only the core tool surface # is loaded into the model context; less common native tools are discoverable # through ToolSearch and loaded on first use. # [tools] # always_load = ["git_show", "notify"] # # `request_user_input` payload ceilings (#5949). The model may ask up to # `user_input_max_questions` questions per call, each offering up to # `user_input_max_options` options. Out-of-range values are clamped to the # supported ranges (1..=10 and 2..=10) with a warning. # user_input_max_questions = 6 # user_input_max_options = 4 # # Seconds a question or an approval decision waits before it cancels (#6003). # 0 disables the timeout entirely; values above 86400 (24h) are clamped. # user_input_timeout_seconds = 300 # # Fail-safe ceiling, in seconds, for how long a durable task (runtime API # server) may sit on an unanswered approval or question before it fails with # human_wait_timeout. 0 or omitted uses 1800; values are clamped to 1..=86400. # task_human_wait_cap_seconds = 1800 # # Bound an interactive approval card's wait (#6101). When the window elapses # the card resolves to deny (fail-closed) and the transcript says the bound # denied the call; omitted or 0 waits indefinitely. # [approval] # timeout_seconds = 300 # Optional sub-agent tuning. max_concurrent overrides top-level max_subagents. # [subagents] # max_concurrent = 10 # api_timeout_secs = 600 # per-step API timeout, clamped to 1..=3600 # # How many levels of nested sub-agents the `agent` tool may spawn: # max_depth = 0 # opt out completely — the agent never spawns sub-agents # max_depth = 1 # the agent may spawn sub-agents, but those may not spawn more # max_depth = 2 # one more level of nesting, etc. # Omit to leave delegation depth uncapped. Explicit owner limits are enforced # by the runtime and cannot be widened by descendants. # max_depth = 3 # ───────────────────────────────────────────────────────────────────────────────── # Per-provider credentials (peer providers — NIM is first-class, not a flag) # ───────────────────────────────────────────────────────────────────────────────── # Providers can be stored at once; `provider = "..."` (top of file) or # `/provider deepseek` / `/provider nvidia-nim` / `--provider openai` / # `--provider wanjie-ark` / `/provider volcengine` / `/provider fireworks` / # `--provider siliconflow` / `/provider arcee` / `/provider moonshot` # switches between them without having to re-enter keys. Env vars override anything set here: # DeepSeek: DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL # DeepSeek Anthropic-compatible: DEEPSEEK_API_KEY, DEEPSEEK_ANTHROPIC_BASE_URL # NIM: NVIDIA_API_KEY (or NVIDIA_NIM_API_KEY), NIM_BASE_URL # (or NVIDIA_NIM_BASE_URL / NVIDIA_BASE_URL), NVIDIA_NIM_MODEL # OpenAI-compatible: OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL # Wanjie Ark: WANJIE_ARK_API_KEY (or WANJIE_API_KEY), WANJIE_ARK_BASE_URL, WANJIE_ARK_MODEL # Volcengine Ark: VOLCENGINE_API_KEY (or VOLCENGINE_ARK_API_KEY / ARK_API_KEY), VOLCENGINE_BASE_URL, VOLCENGINE_MODEL # OpenRouter: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL # Xiaomi MiMo: XIAOMI_MIMO_API_KEY (or XIAOMI_API_KEY / MIMO_API_KEY), XIAOMI_MIMO_BASE_URL, XIAOMI_MIMO_MODEL # Token Plan: XIAOMI_MIMO_TOKEN_PLAN_API_KEY (or MIMO_TOKEN_PLAN_API_KEY), XIAOMI_MIMO_MODE/MIMO_MODE # Novita: NOVITA_API_KEY, NOVITA_BASE_URL, NOVITA_MODEL # Fireworks: FIREWORKS_API_KEY, FIREWORKS_BASE_URL # SiliconFlow: SILICONFLOW_API_KEY, SILICONFLOW_BASE_URL, SILICONFLOW_MODEL # Arcee: ARCEE_API_KEY, ARCEE_BASE_URL, ARCEE_MODEL # Moonshot/Kimi: MOONSHOT_API_KEY (or KIMI_API_KEY), MOONSHOT_BASE_URL, MOONSHOT_MODEL # SGLang: SGLANG_BASE_URL, SGLANG_MODEL, optional SGLANG_API_KEY # vLLM: VLLM_BASE_URL, VLLM_MODEL, optional VLLM_API_KEY # Ollama: OLLAMA_BASE_URL, OLLAMA_MODEL, optional OLLAMA_API_KEY # Hugging Face: HUGGINGFACE_API_KEY (or HF_TOKEN), HUGGINGFACE_BASE_URL (or HF_BASE_URL), # HUGGINGFACE_MODEL (or HF_MODEL) # Meta Model API: META_MODEL_API_KEY (or MODEL_API_KEY), META_MODEL_API_BASE_URL # (or MODEL_API_BASE_URL), META_MODEL_API_MODEL (or MODEL_API_MODEL) # # Custom DeepSeek-compatible APIs usually do not need a new provider table: # set `provider = "deepseek"` and override [providers.deepseek].base_url/model. # For generic OpenAI-compatible gateways, use `provider = "openai"` and the # [providers.openai] table below. Keep provider/api_key/base_url in user config # or environment variables; project overlays are not allowed to set them. # # Provider is the route/account/endpoint; model is the ID on that route. # Common DeepSeek routes: # provider = "deepseek" model = "deepseek-v4-pro" # provider = "nvidia-nim" model = "deepseek-ai/deepseek-v4-pro" # provider = "openrouter" model = "deepseek/deepseek-v4-pro" # provider = "fireworks" model = "accounts/fireworks/models/deepseek-v4-pro" # provider = "siliconflow" model = "deepseek-ai/DeepSeek-V4-Pro" # DeepSeek Platform (https://platform.deepseek.com) [providers.deepseek] api_key = "YOUR_DEEPSEEK_API_KEY" # must be non-empty; deepseek-cn reads it too base_url = "https://api.deepseek.com/beta" # base_url = "https://api.deepseek.com" # opt out of DeepSeek beta features # model = "deepseek-v4-pro" # Custom DeepSeek-compatible example: # base_url = "https://your-provider.example/v1" # model = "deepseek-ai/DeepSeek-V4-Pro" # http_headers = { "X-Model-Provider-Id" = "your-model-provider" } # optional custom request headers # path_suffix = "/chat/completions" # override the API path; skips /v1 versioning when set # reasoning_stream_style = "inline_tags" # route ... content into Thinking cells # DeepSeek Anthropic-compatible Messages route (opt-in) # [providers.deepseek_anthropic] # api_key = "YOUR_DEEPSEEK_API_KEY" # base_url = "https://api.deepseek.com/anthropic" # model = "deepseek-v4-pro" # [providers.deepseek.auth] # provider-scoped auth source metadata; command execution lands in a follow-up slice # source = "command" # command = ["secret-tool", "lookup", "service", "codewhale-deepseek"] # timeout_ms = 2000 # insecure_skip_tls_verify = true # last resort for private gateways; prefer SSL_CERT_FILE # NVIDIA NIM-hosted DeepSeek V4 (https://build.nvidia.com) [providers.nvidia_nim] # api_key = "YOUR_NVIDIA_API_KEY" # base_url = "https://integrate.api.nvidia.com/v1" # model = "deepseek-ai/deepseek-v4-pro" # or deepseek-ai/deepseek-v4-flash # Generic OpenAI-compatible endpoint. Use the built-in `openai` provider for # third-party gateways; do not invent a custom provider name. For non-local # http:// gateways, launch with DEEPSEEK_ALLOW_INSECURE_HTTP=1 only on a # trusted network. [providers.openai] # api_key = "YOUR_OPENAI_COMPATIBLE_API_KEY" # base_url = "https://api.openai.com/v1" # model = "gpt-4.1" # Gateway example: # base_url = "https://gateway.example/v1" # model = "your-deepseek-compatible-model" # Alibaba Bailian / Model Studio DashScope OpenAI-compatible example: # base_url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1" # model = "qwen-plus" # context_window = 1000000 # set the gateway/model's real total context window # insecure_skip_tls_verify = true # last resort for private gateways; prefer SSL_CERT_FILE # AtlasCloud OpenAI-compatible endpoint (https://www.atlascloud.ai/docs/models/llm) [providers.atlascloud] # api_key = "YOUR_ATLASCLOUD_API_KEY" # base_url = "https://api.atlascloud.ai/v1" # model = "deepseek-ai/deepseek-v4-flash" # Wanjie Ark / 万界方舟 OpenAI-compatible endpoint [providers.wanjie_ark] # api_key = "YOUR_WANJIE_API_KEY" # base_url = "https://maas-openapi.wanjiedata.com/api/v1" # model = "deepseek-reasoner" # or the exact model ID enabled on your Wanjie account # Volcengine / Volcano Engine Ark Coding API [providers.volcengine] # api_key = "YOUR_VOLCENGINE_API_KEY" # base_url = "https://ark.cn-beijing.volces.com/api/coding/v3" # model = "DeepSeek-V4-Pro" # or DeepSeek-V4-Flash # OpenRouter — multi-provider gateway (https://openrouter.ai) [providers.openrouter] # vendor = "deepinfra" # exact upstream slug; disables OpenRouter fallbacks # api_key = "YOUR_OPENROUTER_API_KEY" # base_url = "https://openrouter.ai/api/v1" # model = "deepseek/deepseek-v4-pro" # OpenRouter-compatible gateways can reuse this provider so reasoning/cache # parsing stays on the OpenRouter-compatible path instead of generic OpenAI: # base_url = "https://openrouter-compatible.example/v1" # model = "deepseek/deepseek-v4-pro" # Recent large model IDs also accepted here include arcee-ai/trinity-large-thinking, # minimax/minimax-m3, minimax/minimax-m2.7, xiaomi/mimo-v2.5-pro, qwen/qwen3.6-flash, # qwen/qwen3.6-35b-a3b, qwen/qwen3.6-max-preview, qwen/qwen3.6-27b, qwen/qwen3.6-plus, # qwen/qwen3.7-max, google/gemma-4-31b-it, z-ai/glm-5.1, z-ai/glm-5.2, # moonshotai/kimi-k2.6, # nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free, and nvidia/nemotron-3-ultra. # OrcaRouter — OpenAI-compatible aggregation gateway (https://www.orcarouter.ai) [providers.orcarouter] # api_key = "YOUR_ORCAROUTER_API_KEY" # base_url = "https://api.orcarouter.ai/v1" # model = "deepseek/deepseek-v4.1-flash-1017" # built-in default for this provider # Namespaced wire models pass through verbatim (deepseek/deepseek-v4-pro, # deepseek/deepseek-v4-flash); OrcaRouter's own auto-routing model is # selectable as "orcarouter/auto". # Xiaomi MiMo OpenAI-compatible endpoint (https://platform.xiaomimimo.com) [providers.xiaomi_mimo] # api_key = "YOUR_XIAOMI_KEY" # base_url = "https://token-plan-sgp.xiaomimimo.com/v1" # Token Plan / tp- keys # # base_url = "https://token-plan-ams.xiaomimimo.com/v1" # Token Plan Europe / Amsterdam # # base_url = "https://api.xiaomimimo.com/v1" # Pay-as-you-go / sk- keys # model = "mimo-v2.6-pro" # chat/reasoning # Chat model IDs: mimo-v2.6-pro, mimo-v2.6-flash, mimo-v2.5-pro-ultraspeed # Token Plan subscriptions use separate tp-* API keys plus api-key auth. # mode = "token-plan-sgp" # default Token Plan endpoint # mode = "token-plan-cn" # China cluster # mode = "token-plan-ams" # Europe cluster # mode = "pay-as-you-go" # standard API / sk- keys # TTS aliases are also accepted by `codewhale speech`: tts, voice-design, voice-clone # TTS model IDs: mimo-v2.5-tts, mimo-v2.5-tts-voicedesign, mimo-v2.5-tts-voiceclone, mimo-v2-tts # Novita AI-hosted inference (https://novita.ai) [providers.novita] # api_key = "YOUR_NOVITA_API_KEY" # base_url = "https://api.novita.ai/openai/v1" # model = "deepseek/deepseek-v4-pro" # or deepseek/deepseek-v4-flash # Fireworks AI-hosted DeepSeek V4 (https://fireworks.ai) [providers.fireworks] # api_key = "YOUR_FIREWORKS_API_KEY" # base_url = "https://api.fireworks.ai/inference/v1" # model = "accounts/fireworks/models/deepseek-v4-pro" # SiliconFlow-hosted DeepSeek V4 (https://siliconflow.com) [providers.siliconflow] # api_key = "YOUR_SILICONFLOW_API_KEY" # base_url = "https://api.siliconflow.com/v1" # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash # SiliconFlow China-hosted DeepSeek V4 (https://siliconflow.cn) # Falls back to [providers.siliconflow] for api_key / base_url / model when unset. [providers.siliconflow-CN] # api_key = "YOUR_SILICONFLOW_API_KEY" # base_url = "https://api.siliconflow.cn/v1" # model = "deepseek-ai/DeepSeek-V4-Pro" # Arcee AI direct OpenAI-compatible endpoint (https://docs.arcee.ai) [providers.arcee] # api_key = "YOUR_ARCEE_API_KEY" # base_url = "https://api.arcee.ai/api/v1" # model = "trinity-large-thinking" # or trinity-large-preview # Moonshot/Kimi OpenAI-compatible endpoint (https://platform.kimi.ai) [providers.moonshot] # api_key = "YOUR_MOONSHOT_API_KEY" # or KIMI_API_KEY # base_url = "https://api.moonshot.ai/v1" # or KIMI_BASE_URL # model = "kimi-k3" # direct Moonshot K3 wire ID # Direct K3 is always-thinking: off -> low, medium -> high, and max remains max. # The exact route sends top-level reasoning_effort, max_completion_tokens, and # omits temperature/top_p per https://platform.kimi.ai/docs/guide/kimi-k3-quickstart. # Kimi Code membership path (key: https://www.kimi.com/code/console): # api_key = "YOUR_KIMI_CODE_API_KEY" # base_url = "https://api.kimi.com/coding/v1" # model = "k3" # Kimi Code K3 wire ID # K3 membership off -> enabled/low; dispatched auto selects a concrete tier. # Only an omitted reasoning setting leaves the provider default in control. # Moderato plans are capped at 262144; Allegretto and above unlock up to 1048576. # context_window = 262144 # manually cap k3 to the Moderato/256K window # context_window = 1048576 # Allegretto+ only; do not claim an unavailable entitlement # Alternatively use model = "k3-256k" with context_window = 262144 for the fixed 256K route. # Omit context_window to keep Codewhale's safe 262144-token bare-k3 baseline. # `k3[1m]` is a Claude Code-only convention, not an API model ID; Codewhale rejects it. # Kimi Code K2.7 remains available to all members as model = "kimi-for-coding". # Kimi OAuth is not supported. Legacy auth_mode = "kimi_oauth" fails closed # to the API-key guidance above without probing Kimi CLI credential files. # Z.AI GLM Coding Plan endpoint (https://docs.z.ai) [providers.zai] # api_key = "YOUR_ZAI_API_KEY" # or Z_AI_API_KEY # base_url = "https://api.z.ai/api/coding/paas/v4" # # General API endpoint, if you are not using the Coding Plan: # # base_url = "https://api.z.ai/api/paas/v4" # 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 # # GLM-5.3 is live on the Z.ai Coding Plan (2026-08-13). Its catalog metadata # # (limits, reasoning options) is inherited from GLM-5.2 until Z.ai publishes # # distinct 5.3 numbers, and it carries no price. GLM-5.3-Flash (2026-08-26) # # is the 1M multimodal picker row (`model = "GLM-5.3-Flash"`). An explicit # # model = "GLM-5.2" keeps sending GLM-5.2; only the default moved. Accounts # # not provisioned for 5.3 can still see a 429 with entitlement code 1311. # StepFun / StepFlash direct OpenAI-compatible endpoint (https://platform.stepfun.ai) [providers.stepfun] # api_key = "YOUR_STEPFUN_API_KEY" # or STEP_API_KEY # base_url = "https://api.stepfun.ai/v1" # or STEP_BASE_URL # # Coding Plan endpoint: # # base_url = "https://api.stepfun.ai/step_plan/v1" # model = "step-3.7-flash" # or STEPFUN_MODEL / STEP_MODEL # MiniMax direct OpenAI-compatible endpoint (https://platform.minimax.io) [providers.minimax] # api_key = "YOUR_MINIMAX_API_KEY" # base_url = "https://api.minimax.io/v1" # model = "MiniMax-M3" # or MiniMax-M2.7, MiniMax-M2.7-highspeed # # MiniMax also publishes Anthropic-compatible endpoints: # # global https://api.minimax.io/anthropic, China https://api.minimaxi.com/anthropic. # Self-hosted SGLang OpenAI-compatible server [providers.sglang] # api_key = "OPTIONAL_SGLANG_TOKEN" # base_url = "http://localhost:30000/v1" # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash # Self-hosted vLLM OpenAI-compatible server [providers.vllm] # api_key = "OPTIONAL_VLLM_TOKEN" # base_url = "http://localhost:8000/v1" # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash # Self-hosted Ollama OpenAI-compatible server [providers.ollama] # api_key = "OPTIONAL_OLLAMA_TOKEN" # base_url = "http://localhost:11434/v1" # model = "deepseek-v4-flash" # or any local Ollama tag # Hugging Face Inference Providers (https://huggingface.co/docs/api-inference) # Provider aliases: huggingface, hugging-face, hugging_face, hf # Env var aliases: HUGGINGFACE_API_KEY / HF_TOKEN, HUGGINGFACE_BASE_URL / HF_BASE_URL, # HUGGINGFACE_MODEL / HF_MODEL [providers.huggingface] # api_key = "YOUR_HF_TOKEN" # base_url = "https://router.huggingface.co/v1" # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash # DeepInfra — AI inference cloud (https://deepinfra.com) [providers.deepinfra] # api_key = "YOUR_DEEPINFRA_TOKEN" # base_url = "https://api.deepinfra.com/v1/openai" # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash # ───────────────────────────────────────────────────────────────────────────────── # Sakana AI Fugu Provider (https://api.sakana.ai) # Provider aliases: sakana, sakana-ai, sakana_ai, fugu # Env var aliases: FUGU_API_KEY, SAKANA_API_KEY [providers.sakana] # api_key = "YOUR_FUGU_API_KEY" # base_url = "https://api.sakana.ai/v1" # model = "fugu" # or fugu-ultra-20260615 # Meituan LongCat Provider (https://longcat.chat/platform) # OpenAI-compatible curated gateway for Meituan's LongCat models. # Provider aliases: longcat, long-cat, meituan-longcat, meituan # Env var aliases: LONGCAT_API_KEY [providers.longcat] # api_key = "YOUR_LONGCAT_API_KEY" # base_url = "https://api.longcat.chat/openai/v1" # model = "LongCat-2.0" # OpenCode Go (https://opencode.ai/docs/go/) # Subscription-backed OpenAI-compatible Chat Completions route. # Env vars: OPENCODE_GO_API_KEY, OPENCODE_GO_BASE_URL, OPENCODE_GO_MODEL # Chat Completions models: deepseek-v4-pro, grok-4.5, glm-5.2, glm-5.1, # kimi-k3, kimi-k2.7-code, kimi-k2.6, deepseek-v4-flash, mimo-v2.5, # mimo-v2.5-pro. # Models documented only on OpenCode Go's Anthropic `/messages` endpoint are # intentionally not advertised by this provider yet. [providers.opencode_go] # api_key = "YOUR_OPENCODE_GO_API_KEY" # base_url = "https://opencode.ai/zen/go/v1" # model = "deepseek-v4-pro" # OpenCode Zen (https://opencode.ai/docs/zen/) # Model-aware gateway: GPT/Muse Spark use Responses, Claude/Qwen use Anthropic # Messages, and DeepSeek/MiniMax/GLM/Kimi/Grok/free models use Chat Completions. # Gemini uses a Google-specific protocol that Codewhale does not implement and # therefore fails closed instead of being sent with the wrong request shape. # Env vars: OPENCODE_ZEN_API_KEY (preferred), OPENCODE_API_KEY, # OPENCODE_ZEN_BASE_URL, OPENCODE_ZEN_MODEL [providers.opencode_zen] # api_key = "YOUR_OPENCODE_ZEN_API_KEY" # base_url = "https://opencode.ai/zen/v1" # model = "gpt-5.5" # Responses # model = "muse-spark-1.2-contributor-free" # Responses (free tier, auto-routed to Responses — no wire needed) # model = "claude-sonnet-4-6" # Anthropic Messages example # model = "deepseek-v4-pro" # Chat Completions example # Custom gateway equivalent (when not using the opencode_zen provider): # [providers.my_opencode] # kind = "openai-compatible" # base_url = "https://opencode.ai/zen/v1" # model = "muse-spark-1.2-contributor-free" # wire = "responses" # api_key_env = "OPENCODE_ZEN_API_KEY" # Meta Model API / Muse Spark (https://developer.meta.com/ai/) # OpenAI-compatible Chat Completions route. # Provider aliases: meta, meta-ai, meta-model-api, muse, muse-spark # Env var aliases: META_MODEL_API_KEY / MODEL_API_KEY, # META_MODEL_API_BASE_URL / MODEL_API_BASE_URL, # META_MODEL_API_MODEL / MODEL_API_MODEL [providers.meta] # api_key = "YOUR_META_MODEL_API_KEY" # base_url = "https://api.meta.ai/v1" # model = "muse-spark-1.1" # xAI / Grok Provider (https://console.x.ai/) # OpenAI-compatible Chat Completions route. # Provider aliases: xai, x-ai, x_ai, grok # Env var aliases: XAI_API_KEY, XAI_BASE_URL, XAI_MODEL # # Auth modes: # api_key (default) — console.x.ai pay-per-use key via api_key / XAI_API_KEY / keyring # oauth — `codewhale auth xai-device` uses Codewhale-owned storage. # Reading an existing Grok CLI file requires explicit # `codewhale auth external-consent --provider xai --mode read-only`. [providers.xai] # api_key = "YOUR_XAI_API_KEY" # auth_mode = "oauth" # or "device_code" / "grok_cli" # base_url = "https://api.x.ai/v1" # model = "grok-4.7" # or grok-4.6, grok-4.5, grok-4.3, grok-build # Mistral AI — la Plateforme (https://console.mistral.ai/) # OpenAI-compatible Chat Completions route. # Provider aliases: mistral, mistral-ai, mistralai, la-plateforme # Env var aliases: MISTRAL_API_KEY, MISTRAL_BASE_URL, MISTRAL_MODEL # Vibe Code subscription setup: docs/MISTRAL_VIBE.md [providers.mistral] # api_key = "YOUR_MISTRAL_API_KEY" # base_url = "https://api.mistral.ai/v1" # model = "mistral-code-latest" # or mistral-medium-latest, mistral-small-latest, mistral-large-latest # Google Gemini — Google AI Studio (https://aistudio.google.com/apikey) # OpenAI-compatible Chat Completions route on the official Gemini endpoint; # this is the supported Gemini path (see docs/PROVIDERS.md). # Provider aliases: google, gemini, google-gemini, ai-studio # Env var aliases: GEMINI_API_KEY, GOOGLE_API_KEY, GEMINI_BASE_URL, GOOGLE_BASE_URL [providers.google] # api_key = "YOUR_GEMINI_API_KEY" # base_url = "https://generativelanguage.googleapis.com/v1beta/openai/" # model = "gemini-3.1-pro-preview" # ───────────────────────────────────────────────────────────────────────────────── # Alibaba Cloud Model Studio — Token Plan # (https://bailian.console.aliyun.com/) # # Token Plan Personal and Team share the same AP-Southeast (Singapore) endpoint. # Available text/coding models: qwen3.8-max, qwen4-max-preview, qwen3.7-plus, # qwen3.7-max, qwen3.6-flash, deepseek-v4-pro, deepseek-v4-flash-0731, glm-5.2 # # Provider aliases: modelstudio-token-plan, modelstudio_token_plan, # alibaba-token-plan, dashscope-token-plan # Env var aliases: MODELSTUDIO_API_KEY (preferred), DASHSCOPE_API_KEY, # MODELSTUDIO_TOKEN_PLAN_BASE_URL, MODELSTUDIO_TOKEN_PLAN_MODEL [providers.modelstudio_token_plan] # api_key = "YOUR_MODELSTUDIO_API_KEY" # base_url = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1" # model = "qwen3.8-max" # # Anthropic-compatible dialect (same key, /apps/anthropic path): # # provider = "modelstudio-token-plan-anthropic" # # base_url = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic" # ───────────────────────────────────────────────────────────────────────────────── # Alibaba Cloud Model Studio — Coding Plan # (https://bailian.console.aliyun.com/) # # Coding Plan uses a separate international endpoint (coding-intl.dashscope). # Same model ids as the Token Plan except qwen4-max-preview, which is # Token Plan only. # # Provider aliases: modelstudio-coding-plan, modelstudio_coding_plan, # alibaba-coding-plan, dashscope-coding-plan # Env var aliases: MODELSTUDIO_API_KEY (preferred), DASHSCOPE_API_KEY, # MODELSTUDIO_CODING_PLAN_BASE_URL, MODELSTUDIO_CODING_PLAN_MODEL [providers.modelstudio_coding_plan] # api_key = "YOUR_MODELSTUDIO_API_KEY" # base_url = "https://coding-intl.dashscope.aliyuncs.com/v1" # model = "qwen3.8-max" # # Anthropic-compatible dialect (same key, /apps/anthropic path): # # provider = "modelstudio-coding-plan-anthropic" # # base_url = "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic" # ───────────────────────────────────────────────────────────────────────────────── # Together AI Provider (https://www.together.ai/) # Env var aliases: TOGETHER_API_KEY, TOGETHER_BASE_URL, TOGETHER_MODEL [providers.together] # api_key = "YOUR_TOGETHER_API_KEY" # base_url = "https://api.together.xyz/v1" # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash # ───────────────────────────────────────────────────────────────────────────────── # Baidu Qianfan Provider (https://intl.cloud.baidu.com/product/qianfan.html) # Provider aliases: qianfan, baidu-qianfan, baidu_qianfan, baidu # Env var aliases: QIANFAN_API_KEY / BAIDU_QIANFAN_API_KEY, # QIANFAN_BASE_URL / BAIDU_QIANFAN_BASE_URL, # QIANFAN_MODEL / BAIDU_QIANFAN_MODEL [providers.qianfan] # api_key = "YOUR_QIANFAN_API_KEY" # base_url = "https://api.baiduqianfan.ai/v1" # model = "ernie-4.0-turbo-8k" # or your Qianfan service/model id # ───────────────────────────────────────────────────────────────────────────────── # OpenAI Codex (ChatGPT) Provider — EXPERIMENTAL # Run `codex login`, then explicitly grant read-only access to that exact file: # codewhale auth external-consent --provider openai-codex --mode read-only # Codewhale never refreshes or rewrites the Codex CLI file. No API key is # stored here. Talks to the OpenAI Responses API at /codex/responses. # Env var aliases: OPENAI_CODEX_ACCESS_TOKEN / CODEX_ACCESS_TOKEN (token override), # OPENAI_CODEX_BASE_URL / CODEX_BASE_URL, OPENAI_CODEX_MODEL / CODEX_MODEL, # OPENAI_CODEX_ACCOUNT_ID / CODEX_ACCOUNT_ID, OPENAI_CODEX_AUTH_FILE, CODEX_HOME [providers.openai_codex] # base_url = "https://chatgpt.com/backend-api" # model = "gpt-6.1-sol" # The CLI writes this table after informed consent; do not copy it between # providers or machines. Absence means disabled. # [providers.openai_codex.external_credentials] # access = "read_only" # provider = "openai-codex" # source = "codex_cli" # path = "/absolute/path/to/.codex/auth.json" # consent_version = 1 # ───────────────────────────────────────────────────────────────────────────────── # Anthropic Provider (native Messages API) # Talks to https://api.anthropic.com/v1/messages with x-api-key auth — not an # OpenAI-compatible route. Models: claude-opus-5-5, claude-sonnet-5-5 (default), # claude-haiku-5-5. Env vars: ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, # ANTHROPIC_MODEL. [providers.anthropic] # api_key = "sk-ant-..." # base_url = "https://api.anthropic.com" # model = "claude-sonnet-5-5" # OpenModel Provider (Anthropic-compatible Messages API) # Talks to https://api.openmodel.ai/v1/messages with Bearer auth. OpenModel # routes DeepSeek, DashScope, Xiaomi, Claude, and other models by model id. # Env vars: OPENMODEL_API_KEY, OPENMODEL_BASE_URL, OPENMODEL_MODEL. [providers.openmodel] # api_key = "om-..." # base_url = "https://api.openmodel.ai" # model = "deepseek-v4-flash" # Codewhale API (https://app.codewhale.net/settings?section=api) # Account-backed model access over the provider keys connected to the # customer's Codewhale account. The account's authenticated `GET /v1/models` # listing is the catalog authority: each `provider/model` row states its wire # (`chat-completions` → `/v1/chat/completions`, `anthropic-messages` → # `/v1/messages`, `responses` → `/v1/responses`). Every protocol authenticates # the account key with `Authorization: Bearer`, never `x-api-key`. # Provider aliases: codewhale-api, cw-api, codewhale-cloud # Env vars: CODEWHALE_API_KEY, CODEWHALE_API_BASE (HTTPS except loopback); # the generic CODEWHALE_MODEL override also applies. [providers.codewhale] # api_key = "cwc_key_..." # or `codewhale account api-keys create --name --use` # base_url = "https://api.codewhale.net/v1" # model = "deepseek/deepseek-v4-pro" # provider/model exactly as the account catalog returns it # ───────────────────────────────────────────────────────────────────────────────── # Web Search Provider # ───────────────────────────────────────────────────────────────────────────────── # Choose which backend the Web tool's search uses. Default is keyless Firecrawl # (bounded per-IP quota; `[search] api_key` or FIRECRAWL_API_KEY raises it). # Bing and DuckDuckGo HTML scraping need no API key. Tavily, Bocha, Metaso, # Baidu, Volcengine, Sofya, Serply and DeepSeek need an api_key; SearXNG needs # base_url. # API runtime failures and empty responses visibly degrade through DuckDuckGo # then Bing. Missing configuration and network-policy denials fail closed. # # [search] # provider = "firecrawl" # firecrawl | bing | duckduckgo | tavily | bocha | metaso | searxng | baidu | volcengine | sofya | serply | deepseek # # firecrawl: Firecrawl Cloud search, keyless with a bounded per-IP quota # # duckduckgo: HTML scrape with Bing fallback # # bing: HTML scrape, no API key # # tavily: https://tavily.com — AI search, needs api_key # # TAVILY_API_KEY set + provider unset auto-selects tavily # # at runtime; it is never written to config.toml # # bocha: https://bochaai.com — 博查AI搜索,国内友好,需api_key # # metaso: https://metaso.cn — 秘塔AI搜索,需 api_key # # 设置 METASO_API_KEY 或 [search] api_key # # searxng: https://docs.searxng.org — trusted/self-hosted JSON API, # # set base_url; no public instance is used by default # # instance must enable JSON (settings.yml search.formats); HTML-only instances 403 # # baidu: 百度 AI Search via qianfan.baidubce.com,需 api_key # # volcengine: 火山引擎 Ark web_search (免费 2 万次/月), 需 api_key # # 也回退到 VOLCENGINE_API_KEY / VOLCENGINE_ARK_API_KEY / ARK_API_KEY 环境变量 # # sofya: https://sofya.co — AI search returning full page # # content (not snippets), needs api_key (ay_live_...); # # also falls back to the SOFYA_API_KEY env var # # serply: https://serply.io Google organic results with # # snippets, needs api_key; # # also falls back to the SERPLY_API_KEY env var # # deepseek: https://api-docs.deepseek.com/guides/search — # # api.deepseek.com/v1/tools/search, needs api_key; # # also falls back to the DEEPSEEK_API_KEY env var # # (the saved model-provider key is not reused) # base_url = "https://search.example/" # optional DuckDuckGo-compatible HTML endpoint; # # required SearXNG root or /search endpoint # api_key = "YOUR_SEARCH_KEY" # required for tavily, bocha, metaso, baidu, volcengine, sofya, serply and deepseek; optional for firecrawl (raises the keyless quota); unused by searxng # # WARNING: treat config.toml like a secret file when # # storing API keys. Prefer env vars for local smoke tests. # # Env-var overrides: # CODEWHALE_SEARCH_PROVIDER → search.provider # DEEPSEEK_SEARCH_PROVIDER → search.provider (legacy alias) # CODEWHALE_SEARCH_API_KEY → search.api_key # DEEPSEEK_SEARCH_API_KEY → search.api_key (legacy alias) # CODEWHALE_SEARCH_BASE_URL → search.base_url # DEEPSEEK_SEARCH_BASE_URL → search.base_url (legacy alias) # FIRECRAWL_API_KEY → firecrawl key fallback # METASO_API_KEY → metaso key fallback # BAIDU_SEARCH_API_KEY → baidu key fallback # VOLCENGINE_API_KEY / VOLCENGINE_ARK_API_KEY / ARK_API_KEY → volcengine key fallback # SOFYA_API_KEY → sofya key fallback # SERPLY_API_KEY → serply key fallback # TAVILY_API_KEY → tavily autodetect when provider is unset (explicit # provider = "..." or CODEWHALE_SEARCH_PROVIDER still # wins; the autodetect is never persisted) # ───────────────────────────────────────────────────────────────────────────────── # Network Policy (#135) # ───────────────────────────────────────────────────────────────────────────────── # Per-domain allow/deny rules for outbound network calls made by the TUI's # tools (`fetch_url`, `web_search`) and the MCP HTTP transport. Stdio MCP # servers and direct LLM API calls are unaffected. # # Precedence: deny wins. A host listed in both `allow` and `deny` is denied. # # Host-matching rules: # - Exact match: `api.deepseek.com` matches only `api.deepseek.com`. # - Subdomain wildcard: an entry starting with `.` (e.g. `.example.com`) # matches `api.example.com` and `a.b.example.com` but not the apex # `example.com`. To cover both, list both. `*.example.com` is also accepted. # # Defaults are intentionally conservative: when this section is absent, no # policy is enforced (mirrors pre-v0.7.0 behavior). To opt in: # # [network] # default = "prompt" # allow | deny | prompt # allow = ["api.deepseek.com", "github.com", ".githubusercontent.com"] # deny = [] # proxy = ["github.com", ".githubusercontent.com"] # proxy_fake_ip_cidrs = ["198.18.0.0/15"] # requires both matching host and address # audit = true # one line per call to ~/.codewhale/audit.log # ───────────────────────────────────────────────────────────────────────────────── # Verifier preview (#2093) # ───────────────────────────────────────────────────────────────────────────────── # Enables automatic claim-of-done verifier preview once the runtime trigger is # active. Manual `run_verifiers` remains available even when this is false. # # [verifier] # enabled = false # ───────────────────────────────────────────────────────────────────────────────── # Advisor / Watcher (#3982) # ───────────────────────────────────────────────────────────────────────────────── # Optional background watcher that fires after each turn that contains tool # calls. It reads a bounded slice of recent tool interactions, makes a concise # LLM advisory call, and emits a brief note into the status area. # # Off by default. Enable with `[advisor] enabled = true` or toggle per-session # with `/advisor on` / `/advisor off`. # # Options (the full set — `AdvisorConfigToml` in crates/config/src/lib.rs): # enabled — master switch (default: false) # max_tool_calls — number of recent tool call/result pairs to read # (default: 10, clamped to 1–50) # rate_limit_secs — minimum seconds between successive advisor notes # (default: 60, clamped to 5–3600) # dedup_window_secs — notes whose content hash matches the previous note # within this window are dropped (default: 300) # model — model override for the advisory call; when absent the # advisor reuses the session's current model # # Unknown keys under [advisor] are silently ignored, not rejected — a typo # leaves the default in place with no error. # # [advisor] # enabled = false # max_tool_calls = 10 # rate_limit_secs = 60 # dedup_window_secs = 300 # model = "deepseek-v4-flash" # ───────────────────────────────────────────────────────────────────────────────── # Skills (#140) # ───────────────────────────────────────────────────────────────────────────────── # Settings for the `/skill install ` community-skill installer. # * registry_url — curated index.json that resolves bare names to # `github:owner/repo` specs. Override to point at # a private fork or internal mirror. # * max_install_size_bytes — per-skill uncompressed size cap. Tarballs that # exceed this limit are rejected during validation. # Default: 5 MiB. # # `/skill install` is gated by `[network]`. Make sure `github.com` and # `raw.githubusercontent.com` are reachable (default `prompt` is fine — you'll # be asked once and can persist) before running it. # # [skills] # registry_url = "https://raw.githubusercontent.com/Hmbown/deepseek-skills/main/index.json" # max_install_size_bytes = 5_242_880 # scan_codewhale_only = false # true: ignore Claude/OpenCode/Cursor/agentskills.io skill dirs # flat_workspace_root = false # true: opt in to trusted /skills content # Model-stream budgets and HTTP transport. `config dump` shows effective values. # Legacy [tui] stream_* keys remain fallbacks when a canonical field is absent. [stream] open_timeout_secs = 45 # response headers, including connect (0 = env/default, 5-300) chunk_timeout_secs = 900 # per-SSE-chunk idle bound (0 = default, 1-3600) max_resumes = 3 # proven pre-dispatch connection recovery (0 = off, 0-10) max_transparent_retries = 2 # compatibility value; interrupted streams never reissue max_stream_errors = 5 # compatibility value; uncertain stream errors end the stream max_duration_secs = 1800 # one stream, not the whole turn (0 = default, 10-86400) max_content_mb = 10 # one stream's content (0 = default, 1-512) connect_timeout_secs = 30 # TCP/TLS setup (0 = default, 1-300) force_http1 = false # a truthy CODEWHALE_FORCE_HTTP1 still pins HTTP/1.1 # Keepalive settings apply to newly built model/catalog/fallback clients. tcp_keepalive_secs = 30 # idle TCP probes (0 = off, 1-3600) http2_keep_alive_interval_secs = 15 # active HTTP/2 PINGs (0 = off, 1-3600) http2_keep_alive_timeout_secs = 20 # PING ACK deadline (0 = default, 1-3600) # ───────────────────────────────────────────────────────────────────────────────── # TUI # ───────────────────────────────────────────────────────────────────────────────── [tui] alternate_screen = "auto" # auto/always start on the alternate screen; never starts inline (keeps terminal scrollback). /fullscreen and /inline switch at runtime mouse_capture = true # true: TUI-owned mouse selection (payload set by selection_copy_markdown); false: raw terminal selection/copy selection_copy_markdown = true # true: copy transcript drag selections as Markdown source; false: rendered text # Model steps are uncapped by default. Uncomment to install an explicit # ceiling; omission or 0 means no step limit, positive values clamp to 1-100000. # max_model_steps = 1000 # A turn has no wall-clock limit unless you set one; omission or 0 means none. # turn_wall_clock_secs = 7200 # cumulative wall clock for one turn, excluding # time blocked on a human approval (30-86400) 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 # What the bottom chrome shows. Each key is one thing on screen: `mode` is the # posture bar's plan/act/operate chip, everything else is a segment of the # metrics line under it. Omit the key to use the built-in default; set [] to # strip the metrics line down to the help hint. You can also edit this # interactively with `/statusline`. # Supported keys: mode, model, context_percent, cost, # balance (prepaid providers only: DeepSeek, DeepSeekCN, OpenRouter, # SiliconFlow), cache, tokens, ttft, output_rate, workspace, git_branch. # Legacy session_metrics enables both ttft and output_rate. # Retired in 0.9.13: status, agents, reasoning_replay, prefix_stability, # last_tool_elapsed, rate_limit — they drove nothing. Old files # keep loading; the retired keys are ignored. # status_items = ["mode", "model", "context_percent", "cost", "tokens"] # Size presets for the two rows themselves (#5950) — composition stays in # status_items; these only decide how much of a row paints: # posture_bar = "full" # full | compact | hidden (default full) # # compact keeps the posture chips (and the cap # # warning) and drops the clocks, counts and hint; # # hidden gives the row to the transcript. # metrics_line = "compact" # full | compact | hidden (default compact) # # compact keeps the route, context reading, cost # # and balance plus selected TTFT/rate when they fit. # # It drops secondary counts and help; hidden gives the row to the transcript. # # Also settable at runtime: /config posture_bar compact # notification_condition = "unfocused" # unfocused | always | never # "unfocused" = notify only after this terminal has been # in the background for two seconds (default); # "always" = allow configured notifications while focused; # successful turns skip the threshold; # "never" = suppress all operator notifications. # locale = "auto" # UI chrome language: auto | en | ja | zh-Hans | zh-Hant | pt-BR | es-419 # # | vi | ko | ca | de | fr | id | hi | ru | uk # # "auto" reads LC_ALL → LC_MESSAGES → LANG; falls back to English. # # Override: `locale = "zh-Hans"` for Simplified Chinese regardless of OS locale. # # Also settable at runtime: /config locale zh-Hans # # Note: this only affects TUI labels/chrome — it does NOT change model output language. # mention_menu_behavior = "fuzzy" # fuzzy | browser; browser lists immediate directory children for @-mentions. # ───────────────────────────────────────────────────────────────────────────────── # Transcript # ───────────────────────────────────────────────────────────────────────────────── # Prose — user messages, assistant answers, and reasoning/thinking — fills the # full content width, consistent with tool/status cells and the wide-frame # decision in #5322 (#5436). Owners who want a bounded reading measure on # ultrawide terminals can cap it in columns. [transcript] # prose_measure = 120 # positive integer: cap prose wrap at N columns. # 0 or absent = full content width (default). # Must be a positive whole number; tool, diff, and # status cells always keep the full content width. # ───────────────────────────────────────────────────────────────────────────────── # Feature Flags # ───────────────────────────────────────────────────────────────────────────────── [features] shell_tool = true subagents = true web_search = true # enables canonical web.run plus the compatibility web_search alias apply_patch = true mcp = true exec_policy = true code_mode = true # execute_tools composes MCP/plugin/native calls; false defers it behind tool_search # vision_model = false # enable vision model for image_analyze tool # verify_tool = false # disable the agent-callable `verify` self-critique tool # (#4196). On by default; the agent decides when to spend # the extra reasoning, so cost is only incurred on demand. # Set false to remove it from the model's tool catalog. # extension_host = false # EXPERIMENTAL. Run reviewed plugins' `native` host code # (TypeScript/JavaScript, Cordis/DSH plugin model) in a # separate Node.js (or, opt-in, Bun) process. Tools and # slash commands in this phase. Every # extension tool is always Required and never # read-only; like any Required tool it runs without # a prompt under Full Access, under Bypass, or with a # matching session grant, which is bound to the # plugin's reviewed build (an update asks again). # Native code requires a verified OS sandbox: Seatbelt # on macOS, bubblewrap on Linux, and LPAC/Job on Windows. # An unavailable Native sandbox refuses activation. # Unix reads are limited to the selected runtime and # libraries, canonical host bundle, captured Core-managed # plugin stores and Native data. Writes stay in Native # data and private scratch; repository reads and direct # network are denied. Native plugins share one trusted # cohort, not separate per-plugin OS sandboxes. # On macOS a detached child can outlive process-group # cancellation while retaining the sandbox restrictions. # Builtin host policy is separate and diagnosed separately. # Toggling this changes the plugin activation policy, # so every plugin must be re-reviewed afterwards. # [extension_host] # runtime = "node" # "node" (default), "bun" or "auto". Bun is an # # opt-in, qualified on macOS only. "auto" uses # # Bun >= 1.4.0 when one is found and starts, # # else Node. "bun"/"node" never fall back. # # Unset with only `bun` set: "bun". # bun = "~/.bun/bin/bun" # Bun >= 1.4.0. When set, the only Bun tried; a # # broken one fails instead of searching PATH and # # $BUN_INSTALL/bin or ~/.bun/bin. # node = "/usr/local/bin/node" # Node.js ^22.19 || >=24. When set, the only Node # # tried. Unset, every `node` on PATH is tried in # # order, skipping any inside node_modules or the # # working directory. # Per-plugin settings for the extension host, keyed by the plugin's manifest # name. The table is delivered to the plugin's `apply(ctx, config)` and checked # against the plugin's own `Config` schema, if it declares one. User config # only: a project's .codewhale/config.toml cannot set it. Read at start and at # `/plugin reload`, which re-activates a plugin whose table changed. At most # 16 KiB of plain TOML values; `/plugin show ` lists the keys, not the # values. Do not store secrets here: the plugin's code reads every value. # # [plugins."hello-extension".config] # greeting = "Howdy" # ───────────────────────────────────────────────────────────────────────────────── # Vision Model Configuration (optional) # ───────────────────────────────────────────────────────────────────────────────── # Uses an OpenAI-compatible vision model API for the `image_analyze` tool. # api_key inherits from the main config if not specified. # # [vision_model] # model = "gemini-3.1-flash-lite-preview" # Required: vision-capable model ID # api_key = "YOUR_API_KEY" # Optional: defaults to main api_key # base_url = "https://generativelanguage.googleapis.com/v1beta/openai/" # Optional # # Xiaomi MiMo image understanding can be configured through the same tool: # model = "mimo-v2.6-flash" # api_key = "YOUR_XIAOMI_KEY" # base_url = "https://token-plan-sgp.xiaomimimo.com/v1" # Token Plan / tp- keys # ───────────────────────────────────────────────────────────────────────────────── # Retry Configuration # ───────────────────────────────────────────────────────────────────────────────── [retry] enabled = true max_retries = 3 initial_delay = 1.0 max_delay = 60.0 exponential_base = 2.0 # jitter = true # randomize each backoff delay # jitter_factor = 0.1 # ±10% spread (0.0-1.0) # respect_retry_after = true # honor a server Retry-After header # ───────────────────────────────────────────────────────────────────────────────── # Goal loop (`[goal]`) — operate-mode persistent goals # ───────────────────────────────────────────────────────────────────────────────── # Operate-mode goals run to their completion gate with no default token, time, # or continuation ceiling. Token/time budgets, when supplied, are telemetry # only and do not stop a goal. The keys below are the opt-in circuit breakers. # [goal] # Optional safety backstop on automatic goal continuation passes. # Default: 0 (unlimited). Set a positive value to opt into a ceiling. # max_continuations = 100 # Optional cancellable quiet period between successful turns, useful for # coordinator goals that poll on a cadence instead of keeping one provider # turn open. Default: 0 (continue immediately). Cap: 86400 (24h). # continuation_delay_seconds = 300 # Per-turn step allowance while a goal is active (#5994): larger but still # finite. Default: 1000 (0/absent resolves to 1000). Range: 1..=100,000. # Bounds each turn, never the number of continuation passes; explicit # per-invocation ceilings (exec --max-turns, worker caps) always win. # max_steps = 1000 # ───────────────────────────────────────────────────────────────────────────────── # Context Compaction # ───────────────────────────────────────────────────────────────────────────────── # Auto-compaction is a saved UI setting edited with `/config` (`auto_compact`). # The optional saved threshold setting is `auto_compact_threshold_percent` # (default 80). There is no config-file # `[compaction]` table yet; runtime compaction budgets are chosen by the TUI # from the active model/context window. # [context] supports one key, `project_pack` (default false, #4781). The # removed seam-manager keys (enabled, verbatim_window_turns, l1/l2/l3_threshold, # seam_model) are ignored if an older config still carries them. # ───────────────────────────────────────────────────────────────────────────────── # Workshop / Tool-Output Budgets # ───────────────────────────────────────────────────────────────────────────────── # By default an oversized tool result uses bounded spillover: a result under the # byte threshold stays inline, a larger one gets a head/tail preview plus a # session artifact the model can read back. There is no synthesis sub-agent and # no per-call `raw = true` escape. # # `large_output_threshold_tokens` and `[workshop.per_tool_thresholds]` only take # effect when the process opts in to adaptive evidence routing with # `CODEWHALE_ADAPTIVE_OUTPUT_ROUTING=1`; they set where a result stops being # inline and becomes handle-only evidence. Per-tool keys are exact tool names as # the model sees them (`bash`, `fetch_url`, ...). The two byte ceilings below # apply either way. # # [workshop] # large_output_threshold_tokens = 4096 # # Optional model-visible byte ceilings (#5367). Absent keeps the # # compile-time defaults (read 50KiB / read_file 16KiB, then the # # compact 12K-char floor). Values raise the floor; they never lower # # it. Hard cap is 2MiB. # # read_result_max_bytes = 102400 # # tool_result_max_bytes = 102400 # [workshop.per_tool_thresholds] # adaptive routing only # bash = 2048 # shell output becomes handle-only evidence sooner # fetch_url = 8192 # web pages can be large; give them more room # ───────────────────────────────────────────────────────────────────────────────── # Profile Example (for multiple environments) # ───────────────────────────────────────────────────────────────────────────────── # Select a profile with `deepseek --profile ` or `DEEPSEEK_PROFILE=`. # A profile's provider tables override the same fields of the base tables. [profiles.work.providers.deepseek] api_key = "WORK_DEEPSEEK_API_KEY" base_url = "https://api.deepseek.com/beta" [profiles.dev] allow_shell = true [profiles.dev.providers.deepseek] api_key = "DEV_DEEPSEEK_API_KEY" [profiles.nvidia-nim] provider = "nvidia-nim" default_text_model = "deepseek-ai/deepseek-v4-pro" [profiles.nvidia-nim.providers.nvidia_nim] api_key = "YOUR_NVIDIA_API_KEY" base_url = "https://integrate.api.nvidia.com/v1" # ───────────────────────────────────────────────────────────────────────────────── # Desktop Notifications (OSC 9 / BEL on long agent-turn completion) # ───────────────────────────────────────────────────────────────────────────────── # Emits an escape sequence to the terminal when a turn **completes successfully** # and took longer than `threshold_secs`. Failed or cancelled turns are # intentionally silent. Useful when you tab away from the TUI and want an alert # for "your task is ready". # # method = "auto" # auto | osc9 | kitty | ghostty | bel | off # auto: native/terminal banner for known terminals. # On macOS, the native banner is silent; on Linux, # unknown terminals stay silent instead of ringing BEL. # osc9: \x1b]9;\x07 (iTerm2-style; shows macOS notification) # bel: explicit \x07 beep (MessageBeep on Windows) # off: disable banners; an explicit completion sound is independent # threshold_secs = 30 # only notify when the turn took >= this many seconds # include_summary = false # include elapsed time + cost in the notification body # subagent_completion = "final-only" # always | final-only | off — notices when # background work finishes: sub-agents, background # shells and durable tasks. final-only (default) sends # one notice naming everything that finished once no # agent, workflow or durable task is still running (a # running shell such as a dev server never holds it # back); off silences them entirely. # completion_sound = "off" # off | beep | bell | file — opt-in turn-completion sound # sound_file = "E:\\google\\downloads\\notify.wav" # WAV used when completion_sound = "file" (Windows) [notifications] # method = "auto" # threshold_secs = 30 # include_summary = false # subagent_completion = "final-only" # completion_sound = "off" # sound_file = "E:\\google\\downloads\\notify.wav" # Lifecycle event outbox: opt-in JSONL stream of session/turn/subagent # lifecycle events for supervisors and automation harnesses. One JSON line # per event (RuntimeEventEnvelope schema), appended and flushed on every # emit; seq is monotonic per file and recovers from the last line on open. # UNCOMMENT `path` TO ENABLE — unset/empty = OFF = behavior unchanged. # Fires for interactive TUI sessions AND headless `codewhale exec` runs. # See docs/CONFIGURATION.md → Lifecycle Outbox for the file contract. # [lifecycle_outbox] # path = "~/.codewhale/notifications/outbox.jsonl" # webhook_url = "https://example.com/hooks/codewhale" # optional: POST {"at", "event"} JSON per event # webhook_token = "" # optional: sent as `Authorization: Bearer ` # # delivery is best-effort: failures are logged and dropped # Opt-in per-event sound cues (#4817): deterministic, terminal-bell level # (BEL bytes only — functional signals, platform-safe no-op when the terminal # ignores BEL). Off by default. When completion_sound is active, turn-complete # is left to that channel so the two never double-ding. # [notifications.event_sound] # enabled = false # master switch, default off # events = ["turn-complete", "approval-needed"] # allow-list; unknown names ignored # min_interval_ms = 2000 # per-event rate limit # quiet = false # true silences all event sounds # ───────────────────────────────────────────────────────────────────────────────── # Workspace Snapshots (#137) # ───────────────────────────────────────────────────────────────────────────────── # Each turn the TUI takes a `pre-turn:` and `post-turn:` snapshot of # your workspace into a side-git repo at: # # ~/.codewhale/snapshots///.git # # Your own `.git` is never touched — `--git-dir` and `--work-tree` are always # set together when shelling out to git. Use `/restore N` (slash command) or # the `revert_turn` tool to roll the working tree back. Conversation history # is unaffected. # # Disk footprint: ~1-2 GB worst case for a 100 MB workspace × 12 turns/day, # typically far less thanks to git's content-addressed storage. The session # boot prunes anything older than `max_age_days` (default 7). # # [snapshots] # enabled = true # Snapshot workspace pre/post each turn for /restore # max_age_days = 7 # Older snapshots pruned at session start # max_workspace_gb = 2 # Snapshots self-disable on first init when the # # non-excluded workspace exceeds this size in GB # # (v0.8.32). Default 2 GB protects against running # # codewhale in directories with hundreds of GB # # of datasets / model weights / docker dumps where # # `git add -A` would hang the TUI for hours. Set # # to 0 to disable the cap (v0.8.31 behaviour); # # raise to a higher number for legitimate large # # monorepos. # ───────────────────────────────────────────────────────────────────────────────── # LSP Diagnostics (post-edit) (#136) # ───────────────────────────────────────────────────────────────────────────────── # After every successful file edit (`edit_file`, `apply_patch`, `write_file`), # the engine asks an LSP server for diagnostics on the file and injects them # as a synthetic system message before the next API call. This lets the agent # see compile breaks immediately without round-tripping through the user. # # Enabled by default. Failure modes are non-blocking: a missing LSP binary, # a crashed server, or a timeout simply skips the post-edit hook for that # turn — the agent's work is never blocked. # # Built-in language → server defaults: # rust → rust-analyzer # go → gopls serve # python → pyright-langserver --stdio # typescript → typescript-language-server --stdio # java → jdtls # php → intelephense --stdio # vue → vue-language-server --stdio # c, cpp → clangd # # Java support uses Eclipse JDT LS via the `jdtls` command. IntelliJ IDEA is # not required, and installing IntelliJ IDEA alone does not install `jdtls`. # # Override the defaults via the `servers` table below. # # For languages not in the built-in list (Ruby, C#, Swift, etc.), use # `[lsp.custom.]` to register a language server: # # [lsp.custom.rb] # command = "ruby-lsp" # args = ["--stdio"] # language_id = "ruby" # # [lsp.custom.cs] # command = "csharp-ls" # args = [] # language_id = "csharp" # # [lsp.custom.swift] # command = "sourcekit-lsp" # language_id = "swift" [lsp] # enabled = true # poll_after_edit_ms = 5000 # max_diagnostics_per_file = 20 # include_warnings = false # [lsp.servers] # rust = ["rust-analyzer"] # go = ["gopls", "serve"] # java = ["jdtls"] # php = ["intelephense", "--stdio"] # vue = ["vue-language-server", "--stdio"] # ───────────────────────────────────────────────────────────────────────────────── # Hooks (optional) # ───────────────────────────────────────────────────────────────────────────────── # Hooks run shell commands on lifecycle events (session start/end, tool calls, etc.). # Configure as `[[hooks.hooks]]` under a `[hooks]` table. # # SCOPE: hooks are a TUI runtime feature. They fire from the interactive TUI # and the engine turn loop it drives. `codewhale exec`, the CLI subcommands, # the app-server / ACP surfaces, and the `workflow` tool do NOT fire them. # # Available events (all 11): session_start, session_end, message_submit, # tool_call_before, tool_call_after, mode_change, on_error, turn_end, # subagent_spawn, subagent_complete, shell_env. # See docs/HOOKS.md for the per-event payload, env var, and steering contract. # # `message_submit`, `tool_call_before`, and `shell_env` are the only events # whose result can change what Codewhale does. The rest are observer-only — # which means their RESULT is ignored, not that the command is harmless. Every # hook is an arbitrary shell command running with your credentials. # # Note: `default_timeout_secs` below OVERRIDES each hook's own `timeout_secs`. # Leave it unset if you want per-hook timeouts to apply. `/hooks list` shows the # effective value and names the override. `default_timeout_secs = 0` is # REJECTED at load — it would expire every hook immediately — and per-hook # `timeout_secs` applies instead. The timeout applies to background hooks too: # on expiry the whole process group is killed and then reaped, best-effort, # with a bounded reap wait (see docs/HOOKS.md → Timeouts). # # `background = true` means submitted and never awaited. The hook still gets # the documented stdin payload, environment, and timeout — it just has no exit # code, so it cannot steer. `shell_env` ignores the flag and always runs in the # foreground because its stdout is the contract. # # A condition that references context its event never carries is REJECTED at # load, logged, and shown by `/hooks list` — for example an `exit_code` # condition outside `tool_call_after` / `on_error`, or a `mode` condition on # `shell_env`. Rejection is per entry: a broken hook never drops another one # that happens to share its name. `on_error` fires for tool failures with the # tool name, call id, and reported exit code attached, so tool-scoped and # exit-code-scoped `on_error` hooks are supported. # # `shell_env` (#456) is special: the hook runs immediately before each # `exec_shell` invocation and its stdout is parsed as `KEY=VALUE\n` lines. # Those vars are applied on top of `exec_shell`'s environment. For LOCAL # execution that environment is built from a fixed allowlist of parent # variables (PATH, HOME, LANG, TERM, …) — an ambient secret exported in your # terminal is NOT forwarded to `exec_shell` by itself, so this hook is the # supported way to supply one. If an external sandbox backend is configured, # that allowlist does NOT apply: the backend owns its base environment and your # `shell_env` values are TRANSMITTED to it. Later hooks override # earlier ones. Use this for ephemeral credentials, per-skill PATH adjustments, # or short-lived tokens. The resolved KEY names (NEVER values) are written to # `~/.codewhale/audit.log` so each session can be reconciled later. Hook # failure / timeout simply contributes no vars — it does not abort the shell # call. # # [hooks] # enabled = true # default_timeout_secs = 30 # # [[hooks.hooks]] # event = "session_start" # command = "echo 'Codewhale session started'" # # # Inject ephemeral creds into every shell call. Output one # # KEY=VALUE per line on stdout (export prefix optional). # [[hooks.hooks]] # name = "aws-creds" # event = "shell_env" # command = "aws-vault export my-profile --format=env" # # Optionally limit to specific tool names / categories: # # condition = { type = "tool_category", category = "shell" } # # # Observe sub-agent lifecycle events. These hooks receive bounded JSON # # metadata on stdin and are warn-only: failures do not affect sub-agent # # scheduling, prompts, or results. continue_on_error has no effect for # # these observer events; later matching hooks always continue. # [[hooks.hooks]] # name = "subagent-audit" # event = "subagent_complete" # command = "~/.codewhale/hooks/subagent-audit.sh" # ───────────────────────────────────────────────────────────────────────────────── # Runtime API (`deepseek serve --http`) (#561) # ───────────────────────────────────────────────────────────────────────────────── # Tuning knobs for the local HTTP/SSE daemon. The server binds to 127.0.0.1 # by default and is intended for local UIs (whalescale-desktop, dashboards, # automation scripts). Today this section only controls the CORS allow-list; # host/port/workers stay on `--host`, `--port`, and `--workers` flags. # # Built-in defaults always include: # http://localhost:3000 http://127.0.0.1:3000 # http://localhost:1420 http://127.0.0.1:1420 # tauri://localhost # # Use `cors_origins` to add extra dev origins (e.g. Vite's default `:5173`). # User entries STACK on top of the defaults — they do not replace them. The # CLI flag `--cors-origin URL` (repeatable) and env var # `DEEPSEEK_CORS_ORIGINS=url1,url2` resolve to the same merged list. # # [runtime_api] # cors_origins = ["http://localhost:5173", "http://127.0.0.1:5173"] # ───────────────────────────────────────────────────────────────────────────────── # Tool Overrides & Plugins ([tools]) # ───────────────────────────────────────────────────────────────────────────────── # The `[tools]` table lets you disable any built-in tool, or add your own # tool backed by a script or command — without forking or recompiling the # binary. A script or command cannot replace a built-in: an override keyed by # a built-in name is refused (logged) and the built-in stays active. # # Plugin scripts dropped in the plugin directory are auto-discovered and # registered as model-visible tools alongside the built-in ones. A script may # declare `# approval: suggest` (the default) or `# approval: required`; # `# approval: auto` is no longer supported and falls back to the default. # # Scripts receive the tool's JSON input on **stdin** and must return a # JSON `ToolResult` (`{"content": "...", "success": true}`) on **stdout**. # # [tools] # # Custom plugin directory (defaults to `~/.codewhale/tools/`) # plugin_dir = "~/.codewhale/tools" # # [tools.overrides] # # Disable a tool entirely — removes it from the model-visible catalog. # "Web" = { type = "disabled" } # # # Add a tool backed by a script. Relative paths resolve against plugin_dir. # "audited_shell" = { type = "script", path = "audit-shell.sh" } # # # Add a tool backed by a command (binary on PATH or absolute path). # "bat_reader" = { type = "command", command = "bat", args = ["--paging=never"] } # # # Scripts can also accept static arguments before the JSON input: # "cached_fetch" = { type = "script", path = "cached-fetch.sh", args = ["--ttl", "300"] } # ──────────── Enterprise example: audit-logging shell wrapper ────────────── # Drop `audit-shell.sh` (below) in `~/.codewhale/tools/`, where it registers # itself as `audited_shell`, and turn the built-in shell off (`Bash` and its # lowercase `bash` form). Other shell-capable tools, such as the terminal # tools, stay available; disable them too if every command must pass the # wrapper. # # [tools.overrides] # "Bash" = { type = "disabled" } # "bash" = { type = "disabled" } # # The wrapper logs every request to `~/.codewhale/audit/shell.log`, then # delegates to your own approved shell executor. Do not pipe the raw JSON # request into `sh -s`; parse the command field and enforce your policy first. # # ```sh # #!/usr/bin/env sh # # name: audited_shell # # description: Audit-logging shell wrapper # # approval: required # LOGDIR="${HOME}/.codewhale/audit" # mkdir -p "$LOGDIR" # LOGFILE="$LOGDIR/shell.log" # input=$(cat) # echo "[$(date -Iseconds)] $input" >> "$LOGFILE" # printf '%s\n' '{"content":"audit wrapper dry run: configure an executor","success":false}' # ``` # ───────────────────────────────────────────────────────────────────────────────── # Workflow automatic launch, approval, isolation, and activity (#4128) # ───────────────────────────────────────────────────────────────────────────────── # First-class knobs for automatic Workflow orchestration. When the table is # omitted entirely, the runtime uses these product defaults. Later launch, # approval, and activity-persistence paths all read through this one model. # [workflow] # # Allow the parent agent to auto-launch Workflow for multi-agent work. # # Set false to require an explicit `/workflow` opt-in. # automatic = true # # Auto-start read-only plans without an approval card when automatic is on. # auto_start_read_only = true # # Require an approval card before write/shell/network/high-budget launches. # require_approval_for_writes = true # # Optional owner-selected total child limit. Zero leaves work uncapped. # max_children = 0 # # Maximum concurrently live agents inside one run (others wait for a slot). # max_concurrent = 64 # # Optional structural Workflow depth limit; zero leaves it uncapped. # # This is separate from Runtime child delegation below. # max_depth = 0 # # Legacy token hint retained in receipts; not metered or enforced. # default_token_budget = 0 # ───────────────────────────────────────────────────────────────────────────────── # Agent Fleet roster, role registry, and execution requests (#3165, #3167) # ───────────────────────────────────────────────────────────────────────────────── # [fleet] # # Fleet stores member identity and route intent. Project trust, host identity, # # secrets, approvals, sandboxing, filesystem/network reach, and tool authority # # are Runtime policy and are deliberately not configured here. Pre-0.9.11 # # trust keys are accepted only as ignored migration input. # # # Headless worker execution hardening (#3027) # [fleet.exec] # # Tools always allowed regardless of role # allowed_tools = [] # # Tools always disallowed (overrides role and task spec) # disallowed_tools = ["exec_shell"] # # Optional hard ceiling on worker steps (tool calls + model turns). # # Omit or set 0 for the unbounded default; use a positive value to opt in. # max_turns = 500 # # Runtime child-agent depth for fleet workers. Shares ONE recursion axis with # # standalone sub-agents (a fleet worker IS a headless sub-agent). This is an # # execution request, not Fleet member identity. 0 blocks child agents (the # # root worker still runs); omit to leave depth uncapped. # max_spawn_depth = 3 # # Extra system prompt injected into every headless worker # append_system_prompt = "Never modify .git/config or change remotes." # # Output format: "text" (default) or "stream-json" for ndjson events # output_format = "text" # # # Fleet profiles define named agent configurations the roster can dispatch. # # Built-in profiles are always available: manager, operator, scout, builder, # # reviewer, verifier, synthesizer, general. User-defined profiles under # # [fleet.profiles] override or extend the built-in set by id. Precedence # # is Workspace (.codewhale/agents/*.toml) > Config ([fleet.profiles]) > BuiltIn. # # See /fleet setup for an in-app profile-authoring wizard. # [fleet.profiles.ci-linter] # slot = "verifier" # loadout = "fast" # model = "deepseek-v4-pro" # # [fleet.profiles.ci-linter.role] # name = "CI Linter" # description = "Runs linters and formatters" # instructions = "Run cargo fmt --check and cargo clippy; never apply fixes." # # [fleet.profiles.pr-reviewer] # slot = "reviewer" # loadout = "inherit" # # [fleet.profiles.pr-reviewer.role] # name = "PR Reviewer" # description = "Reviews PRs with GitHub access" # instructions = "Review diffs for correctness, regressions, and missing tests." # ───────────────────────────────────────────────────────────────────────────────── # Named fleets live in files, not inline tables (0.9.14). # # Inline [fleets.] tables were removed: define named fleets as # `fleets/.toml` under $CODEWHALE_HOME or the workspace instead # (see docs/FLEET.md). Old inline tables still parse but are ignored. # ───────────────────────────────────────────────────────────────────────────────── # ───────────────────────────────────────────────────────────────────────────────── # Requirements (admin constraints) example file # ───────────────────────────────────────────────────────────────────────────────── # allowed_approval_policies = ["on-request", "untrusted", "never"] # allowed_sandbox_modes = ["read-only", "workspace-write"]