ADR 0083: settings.toml — дискриминант ai.mode и вложенные секции (local / acp / mcp_only / cloud)¶
Статус: Accepted · Implemented
Дата: 2026-04-20
Связанные ADR¶
| ADR | Роль |
|---|---|
| 0028 | путь и секреты |
| 0038 | фасад провайдеров и инструментов |
| 0048 | ACP, MCP в сессии |
| 0016 | ACP как внешний агент |
| 0081 | параметрические Intent Melody :start:end для редактора — смежная тема, не часть ai.mode |
| ## Резюме |
- Секция
[ai]вsettings.toml:mode= local | acp | mcp_only | cloud. - Вложенные секции; без обратной совместимости со старым
provider.
Контекст¶
Секция [ai] в пользовательском TOML сейчас смешивает несколько независимых осей:
- Какой контур отвечает за текст ассистента (локальный Ollama, облачный API, Cursor ACP).
- Флаг «не звать встроенный LLM, ждать ответ только из MCP» (
chat_mcp_only) — по смыслу это отдельный режим взаимодействия, а не «ещё один провайдер».
Читаемость файла и документации страдает: приходится держать в голове комбинации provider + булевых флагов.
Решение¶
1. Дискриминант верхнего уровня¶
В [ai] вводится обязательное (для канона) поле:
| TOML-ключ | Смысл |
|---|---|
mode |
Один из: local, acp, mcp_only, cloud. |
Почему не голое mcp: слово совпадает с протоколом и подключёнными MCP-серверами (инструменты в сессии ACP, внешние stdio-серверы и т.д.). Значение режима чата должно читаться как источник ответа ассистента, а не как «MCP включён». Каноническое имя режима — mcp_only: встроенный LLM после отправки пользователя не вызывается; текст ассистента приходит только из контура внешнего MCP (например send_chat с ролью assistant). Семантически то же выражали бы assistant_via_mcp или no_builtin_provider; в TOML выбираем одно короткое mcp_only, синонимы в коде/доках не обязательны.
Семантика:
| Значение | Назначение |
|---|---|
local |
Встроенный локальный провайдер: [ai.local] с backend = "ollama" (и далее другие значения) плюс [ai.local.ollama] с полями модели/endpoint. Вложенная таблица даёт короткие ключи на листе (model, base_url). Другие локальные бэкенды — отдельные backend и [ai.local.<name>]. |
acp |
Чат через Cursor Agent / ACP (cursor-agent): пути, model_id, политика подмешивания MCP-серверов в сессию — в подсекции ACP; это не то же самое, что режим mcp_only. |
mcp_only |
Режим только ответа через MCP для текста ассистента: встроенный провайдер (Ollama/облако) в этом смысле не используется. Подключённые MCP-сервера как инструменты могут существовать и в local / acp / cloud — это ортогонально. |
cloud |
Облачные провайдеры с API-ключами (Anthropic, OpenAI-совместимые, DeepSeek и т.д.): выбор провайдера и учётные данные — во вложенных таблицах. |
2. Вложенные таблицы (направление)¶
Ключи и точная вложенность фиксируются при реализации; нормативный скелет:
[ai]
mode = "local" # local | acp | mcp_only | cloud
# Поля, общие для любого режима (пример): размещение UI настроек, флаги истории — по мере необходимости.
# Локальный режим: явный backend и вложенная таблица с тем же именем (v1 — ollama).
[ai.local]
backend = "ollama" # ollama | openai_compatible | … (канон значений фиксируется при реализации)
[ai.local.ollama]
# model — идентификатор модели в API Ollama; base_url, request_timeout — при необходимости.
[ai.acp]
# cursor_acp_path, cursor_acp_model_id, политика acp_auto_inject_ide_mcp (подмешивание MCP-серверов в сессию агента).
[ai.mcp_only]
# Лимиты/флаги, специфичные для режима «без встроенного LLM, ответ ассистента только из MCP».
[ai.cloud]
# Поле active_provider = "anthropic" | "openai" | "deepseek" (или иной канон списка).
# Предпочтительно: фиксированные вложенные таблицы по провайдеру (не массив [[...]]):
[ai.cloud.anthropic]
# model, base_url (если отличается), ссылки на ключ в ai-keys.toml — при реализации.
[ai.cloud.openai]
[ai.cloud.deepseek]
Плагинируемый каталог облачных провайдеров (массив [[...]], динамическая загрузка и т.п.) не входит в scope этого ADR и отложена; v1 — только фиксированный набор вложенных таблиц выше.
Именование: в TOML — snake_case, согласовано с CascadeTomlSerializer / 0028. Вложенность [ai.local.ollama] / [ai.cloud.*] задаёт контекст: на листе достаточно model, base_url, без длинных префиксов в имени ключа. Для выбора локального сервиса/API в [ai.local] каноническое поле — backend; синоним engine не используем (путается с рантаймом инференса и с model).
Локальный режим: «тип» vs модель. Поле вроде model_type в смысле формата весов (GGUF, SafeTensors, …) в пользовательский settings.toml не выносим — это не контракт IDE. Нужны два уровня:
backendв[ai.local]— строковый дискриминант:ollama, позжеopenai_compatibleи т.д. Должен совпадать с активной подтаблицей[ai.local.<backend>](напримерbackend = "ollama"↔[ai.local.ollama]). Так читаемость в корне локальной секции не зависит только от пути; вложенная таблица по-прежнему несёт короткие ключи (model, …).model— строковый идентификатор модели в API выбранного движка (напримерqwen2.5-coder:7bдля Ollama), не путать с «типом файла модели».
Что кроме Ollama (продуктово, не обязательно v1): тот же абстрактный OpenAI-compatible HTTP (как у облака), но с base_url на localhost — типично LM Studio, llama.cpp в режиме совместимости с OpenAI API, vLLM, LocalAI и т.д. В схеме это отдельная подтаблица, например [ai.local.openai_compatible] с model, base_url, при необходимости ссылкой на ключ в ai-keys.toml (редко для локали). Реализация и тесты — после v1/Ollama; плагинируемый реестр движков — по-прежнему отложен.
3. Секреты¶
По-прежнему не хранить ключи API в settings.toml: отдельный ai-keys.toml (см. 0028). В ADR только какие поля ссылаются на ключи по имени/слоту.
Обратная совместимость¶
Не требуется. Старые ключи вида provider, chat_mcp_only, плоские default_ollama_model, облачные URL в корне [ai] снимаются с канона: миграция — ручная правка файла и обновление сэмплов в репозитории (docs/samples/settings*.toml). При желании в коде допускается одноразовый скрипт/док «как переписать старый файл» вне обязательной автомиграции в рантайме.
Последствия¶
AiSettingsи связанный VM перестраиваются под дискриминант + вложенные типы; сериализация Tomlyn должна воспроизводить вложенные таблицы предсказуемо.- UI настроек AI (ComboBox провайдера и т.д.) маппится на
modeи подсекции вместо плоского спискаOllama|…|CursorACP. - Документация и сэмплы — единый пример с
[ai].modeи вложенными блоками. - Тесты — контракт десериализации TOML для новой формы; удаление тестов на старые ключи.
Отклонённые / отложенные альтернативы¶
- Оставить только
provider+ флаги — отклонено как менее читаемое для пользователя и для ADR. - Режим с именем
mcp— отклонено: неоднозначно с MCP как протоколом и списком подключённых серверов; канон —mcp_only(см. пояснение выше;assistant_via_mcp/no_builtin_provider— смысловые синонимы, не отдельные значения в TOML). - Автомиграция при загрузке — не делаем по требованию; снижает сложность кода и скрытые сюрпризы.
- Плагины и расширяемый реестр облачных провайдеров — отложено; канон v1 — фиксированные
[ai.cloud.anthropic]/openai/deepseek(см. скелет выше).
Статус реализации¶
Реализовано в коде (модели, сериализация Tomlyn, VM, панель «AI и чата», сэмплы). Расширения (например openai_compatible в [ai.local], редактирование облачных моделей из UI) — по мере необходимости.