Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Наборы инструментов

Набор — имя, однострочное описание и список имён тулов и групп; всё в SQLite. Он же переносит режим работы — см. ниже: рабочий каталог, потолок итераций, краткость и озвучивание шагов. Список name: description висит в системном промпте, и когда род работы совпал, модель зовёт toolset — следующая итерация тура уезжает провайдеру уже с узкими спеками. Тот же progressive disclosure, что у навыков, только про инструменты.

Зачем: спеки всех инструментов едут провайдеру в каждом запросе и на каждой итерации агентного лупа. Это и вход в токенах, и — важнее — шум: чем шире список, тем труднее модели выбрать нужный вызов. Набор оставляет то, чем работают сейчас.

Что видно, когда набор активен

Активный набор задаёт спеки, уезжающие провайдеру. Всё остальное, что туру доступно по гейтам, попадает в блок Hidden tools — имя и назначение, без input_schema. Схемы и есть весь вес; знать, что инструмент существует, стоит почти ничего. Крупные семейства (MCP-серверы, модули) сворачиваются в строку на семейство — перечислять полсотни имён дороже, чем сказать, что это за семейство.

И список наборов, и реестр скрытого уложены в тот же бюджет, что и навыки, — долю окна модели (2%, но не больше 16000 символов) на все три блока разом: при нехватке первыми худеют описания, по символу с каждого по кругу, имена держатся до последнего, а выброшенное считается и уходит warning’ом в лог. Пометки режима ([no iteration cap] и прочие) переживают любое усечение — врать про режим нельзя даже в тесноте.

Понадобился скрытый инструмент — tool_load с его именем или именем семейства возвращает его в список на текущий тур, не трогая активный набор. Так узкий набор нигде не становится тупиком: Ева не отвечает «не умею» о том, что у неё есть.

Имени можно и не знать. tool_find ищет по тому, что инструмент должен делать («чем отправить сообщение в личку»), каскадом от дешёвого к дорогому:

  1. лексика — основы запроса против документа инструмента: имя, назначение, семейство и имена с описаниями параметров схемы — тул находится и по имени своего аргумента. Токенизация та же, что у памяти; отбор по доле совпавших основ, ранжирование — BM25. Ноль токенов, доли миллисекунды, а имена по построению настоящие: они из реестра, выдумать их нельзя;
  2. быстрая модель — и только если лексика ничего внятного не нашла. Реестр скрытого едет ей в промпт, она разбирает запрос и называет инструменты; ответ сверяется с реестром, так что несуществующее имя наружу не выйдет.

Вторая ступень нужна ровно для случая «запрос по-русски, описания по-английски», где лексика бессильна. В обычном случае до неё не доходит. Найденное сразу догружается на текущий тур.

toolset, tool_load, tool_find и skill видны при любом наборе — иначе из узкого набора не было бы выхода. Тот же по духу предохранитель, что пустой список tools, который считается «весь реестр», а не «ни одного».

Редкое — не в списке по умолчанию

Оператор может убрать семейство из списка до всяких наборов: tools.on_demand перечисляет имена и группы, которые в туре без набора живут в реестре скрытого — именем и назначением, без схемы. Это про работу по поводу: доменный модуль, реверс байтов, CRUD навыков и таймеров случаются редко, а схемы их тулов едут провайдеру в каждом запросе.

Названное явно — набором или белым списком чата — сильнее: раз внесли в список, значит осознанно. Догружается такое обычным tool_load, как и всё прочее скрытое.

В каталоге GET /v1/tools такой инструмент помечен on_demand: true — клиентское меню по этой метке честно говорит, что тул придёт набором или догрузкой, а не сию секунду.

Область действия

toolset принимает scope:

  • chat (по умолчанию) — набор пишется в чат и живёт между ходами;
  • turn — только до конца текущего хода.

Дефолт именно chat: род работы редко меняется внутри одного хода, а префикс кэша живёт дольше. Цена turn — лишний разрыв кэша на следующем ходу, когда набор откатится (см. Кэш промпта).

Клиент может задать набор на один тур полем toolset в POST /v1/chats/{id}/messages — настройка чата при этом не меняется. Так кодовая сессия стартует узкой, не переучивая чат.

Режим работы

Кодовый режим раньше включал клиент при старте (eva-tui --code DIR), и поэтому из телеграма, из крона и из любого другого тура без клиента его не существовало. На деле из шести его частей клиента требуют только две — клиентские операции и сам интерфейс; остальные четыре суть свойства разговора, а не программы. Они и переехали в набор:

ПолеЧто делает
workdirкаталог, от которого считаются относительные пути и в котором идут команды: cd в каждой команде больше не нужен
uncappedснимает потолок итераций тура — длинная правка не обрывается на середине
narrateкороткая строка между вызовами о том, что делаю (исключение из общего «говори один раз»)
no_brevityне навязывать краткость: это работа, а не реплика в мессенджере

Все части необязательны: набор может остаться просто набором инструментов. Применяются они только к турам Господина — настраивал их он, чужому туру они не полагаются.

Надевает режим сама Ева: попросили накодить — она зовёт toolset и говорит, что включила; закончили — снимает. Закрепить за чатом можно и руками. Каталог, пришедший с набором, ложится на чат, а дальше меняется тулом workdir или клиентом (PUT /v1/chats/{id}/workdir) независимо от набора — по нему же клиент считает относительные пути (поле cwd в событии client_op).

Крон от этого выигрывает не меньше: у крон-джобы клиента нет вовсе, а режим живёт на её чате — значит длинная работа по расписанию перестаёт упираться в потолок итераций.

Как набор сочетается с гейтами

Порядок жёсткий и проверяется в реестре, до исполнения:

  1. Гейты личности (master/trusted/privileged/client_ops) — поверх всего и всегда. Набор не выдаёт чужому туру master-only тул.
  2. В доверенном туре (TUI, Android, личка Господина) набор заменяет базовый доступ: Ева сама решает, чем работать, и может вернуть себе что угодно из реестра.
  3. В недоверенном (публичный чат) набор может только сузить: белый список настроек чата остаётся потолком. Иначе команда, внедрённая в чужое сообщение, открывала бы себе то, что Господин выключил.

Стартовые наборы

На первом запуске база засевается пятью наборами — coding, ops, social, research, money, — чтобы фича не лежала мёртвой. Посев одноразовый (защёлка в kernel_meta): удалённый набор не воскреснет на следующем старте. Наборы перечисляют группы, а не имена, поэтому семейство, которого в этой сборке нет, просто ни с чем не совпадёт.

Это стартовая точка, а не канон: правятся они дальше на ходу.

Управление

Заводит и правит наборы только Господин: toolset_create, toolset_edit (пропущенные поля не трогаются, tools заменяется целиком, пустой hint снимается), toolset_delete (чаты с этим набором возвращаются ко всему реестру).

У набора есть необязательный hint — рабочая инструкция, которая едет ответом на активацию и висит в промпте, пока набор активен.

Каталог наборов клиентам — GET /v1/toolsets. Активный набор чата виден в поле toolset чата и переключается ручкой PUT /v1/chats/{id}/toolset — у клиента это кнопка рядом с выбором модели.

Сон: курирование наборов

Раз в сутки, этапом tools сна (секция sleep.tools конфига), модель этапа смотрит на все наборы, весь реестр (имена, назначения, семейства — без схем) и статистику ношения (сколько чатов носит каждый набор) и отвечает планом: добавить/убрать тулы в наборе (дельтой, не заменой списка), создать новый набор под устойчивый род работы, переписать расплывчатое описание — набор выбирается из списка в промпте именно по нему. Как и в памяти, действовать во сне нельзя: единственный инструмент — sleep_finish, которым отдаётся план, а проверяет и применяет его ядро. Удалять и переименовывать наборы, трогать их режимные флаги (hint/workdir/uncapped/…) и наборы с нечитаемым списком этап не может — это ручная работа Господина; набор не может опустеть (пустой список открывает весь реестр), каждый добавляемый тул обязан существовать в реестре, потолок — sleep.tools.max_ops правок за ночь. План виден в том же журнале GET /v1/sleep/runs, общий sleep.dry_run действует и здесь, о применённых правках Господину уходит уведомление — они меняют, чем Ева работает днём.

Назначение семейств

Строка свёрнутого семейства берётся, по убыванию приоритета:

  1. description в секции сервера/модуля в конфиге — ручной override;
  2. чем сервер представился сам: _meta["dev.eva/about"] в ответе initialize (на eva-sdk — Module::about(...) / Module(about=...));
  3. у встроенных семейств — таблица в ядре;
  4. не сказал никто — первые имена тулов семейства. Понятнее, чем пусто, но хуже, чем фраза по делу.

Штатные instructions протокола сюда не идут, хотя соблазн есть: это правила обращения с сервером, а не ответ на вопрос «что это за семейство». Первой фразой там обычно стоит частность («Issues and PRs share one numbering»), и в реестре скрытого она бесполезна.

Практика: _meta умеют наши модули на eva-sdk. Серверы на FastMCP его выставить не могут — в InitializationOptions такого поля нет, — поэтому им назначение задаётся полем description в конфиге. Строчка на сервер, и трогать чужой код не нужно.