`ConfigStore.reload()` re-parsed every section except `[skills]`, so
`/model --persist` and any other in-place reload kept serving the skill config
the process started with — including `[skills].enabled`, which is what decides
whether the skill tools exist at all.
`[trash]` owns the one policy `delete_path` needs from the human: whether a
delete is a move to the desktop's trash (`enabled = true`, the default) or a
removal that cannot be undone (`false`, which is what the tool did before).
Unknown keys degrade to the defaults, the way `[mcp]` does, so a typo cannot
silently switch a destructive policy on. `reload()` re-reads the section.
`[notify]` decides what the CLI does when a prompt is waiting on the human:
`grace_seconds` (default 5s, roughly Claude Code's permission notice) before
anything is shown, `min_interval_seconds` between two notifications,
`channels` (`auto` = desktop then terminal), `actions` for the allow/deny/review
buttons, `include_reason` for the one-line reason, and `protocol` to pin the
terminal escape protocol.
The reason stays out by default because it names commands, argv and URLs, and a
notification outlives the prompt and may be mirrored to a lock screen. Unknown
keys degrade to the defaults, the way `[mcp]` does, and `reload()` re-reads the
section.
Desktop notifications need the optional `notify` extra (`desktop-notifier`),
which is the only new dependency and stays off the default install.
``search_sources`` (a provider list, ``["deepseek", "bing"]`` by default,
``[]`` to not offer the tool at all) names what the local ``web_search`` tool
searches with, in fallback order: ``search_in_order`` asks each source in turn
and keeps the first answer, so a source that errors — a rate limit, a rejected
key — or finds nothing hands over to the next instead of failing the tool. The
handler reports the failures only when every source came up empty.
The Bing leg answers with its RSS results (``?format=rss``), which need no key
and carry a description — the snippet DeepSeek's own index does not hand out —
and it goes through the ``fetch`` client, so the SSRF and host-grant policy
applies to it too (a host that binds tool instance state, as the CLI does).
``provider_tools`` reached the effective provider only for the OpenAI preset,
so a DeepSeek ``provider_tools`` in TOML was dropped on the way — and the preset
defaulted to none, leaving search unavailable for every convention.
DeepSeek now defaults to ``[{type="web_search"}]`` like OpenAI does (``[]``
turns it off) and its list reaches the agent: the Anthropic convention runs the
hosted search inside the response, the Responses and chat conventions get the
local ``web_search`` tool (see ``config.routing.local_web_search``).
Only DeepSeek's Anthropic-compatible surface runs a hosted ``web_search``: the
Responses and chat-completions surfaces answer "I can't browse the web" with
``{"type": "web_search"}`` and with the server-tool shape alike, so a DeepSeek
provider that talks them had no search at all.
``config.routing.local_web_search`` decides per provider: a DeepSeek provider
whose ``provider_tools`` ask for ``web_search`` and whose convention is not
``anthropic`` gets the local tool (``tools/net/search.py``), which asks the
search-capable endpoint behind the scenes and hands the model one ``title —
url`` line per hit — read a page with ``fetch``, the bodies stay server-side.
DeepSeek's preset asks for no hosted tool by default, so this stays opt-in.
DeepSeek dropped the `v4` prefix from its flash model, and the old id stays
valid only as a compatibility alias. Seed the default catalog with the current
name so a provider that omits `models` no longer offers a deprecated id, and
follow the rename in the wire fixtures and the minimal config template. An
explicit `deepseek-v4-flash` in TOML still passes through unchanged.
Skills are discovered from user-level directories by default: an explicit
``paths`` list wins, then the built-in sources (our own user config dir, and
other harnesses' skill dirs) with the project-scope dirs behind their own
switch. A name found twice shadows the lower-priority copy without hiding it.
``inject_catalog`` / ``max_catalog_skills`` bound what reaches the system
prompt, ``enabled = false`` registers nothing, and ``allow_write`` is the
standing grant for editing skills (default: ask every time). A malformed
section degrades to the defaults, like [agent] and [mcp].
Reject providers with no models into bad_providers and warn with reasons
on chat/providers. When /provider changes and the current model is not
listed, prompt for a new model (keep it when still supported).