Ctrl K
ComunidadGuías…

config.example.toml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║                         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.<name>]` 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
# `<parent-of-this-path>/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 <number>` 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 <version>" 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.<name>, 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 <think>...</think> 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 <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 <spec>` 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 <workspace>/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 <name>` 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 <name>` or `DEEPSEEK_PROFILE=<name>`.
# 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;<msg>\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 <token>`
#                               # 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:<seq>` and `post-turn:<seq>` snapshot of
# your workspace into a side-git repo at:
#
#     ~/.codewhale/snapshots/<project_hash>/<worktree_hash>/.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.<ext>]` 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.<name>] tables were removed: define named fleets as
# `fleets/<name>.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"]