Files
view-mate/TASK-agent-tools.md
T
subochev 22369670cb ТЗ: агент — скиллы + тулсеты (TASK-agent-tools.md)
Архитектура (договорено 2026-08-22):
- скиллы (знания) ≠ тулсеты (руки)
- ядро 5 тул: время, show_text, read_skill, enable/disable_toolset
- префиксы тул по набору (media_*)
- активность на сессию, в хиппе, таймаут 10 мин
- enable/disable невидимы (не в истории), скилл исчезает при активном наборе
- бесшовная подмена вызова на активацию
- история не вычищается
2026-08-22 20:15:48 +03:00

11 KiB

Агент «Порфирий» на телефоне: скиллы + тулсеты — архитектура (договорено 2026-08-22)

Зафиксировано по итогам обсуждения. Это и память, и ТЗ для разработки (opencode). Модель сейчас: Qwen3.8-27B-NVFP4 через llm.binom.pw. План: позже перейти на Gemma (маленькая, быстрая, on-device) — архитектура должна это пережить без переделки.


1. Ключевое разделение: СКИЛЛЫ ≠ ТУЛСЕТЫ

  • Скиллы = знания. «Как пользоваться», рецепты, шаги, питфоллы. Пассивные, лежат в файлах, читаются по требованию через read_skill. Отвечают на вопрос «КАК?».
  • Тулсеты = доступные руки. «Какие функции сейчас можно дёргать». Активные, живут в промпте, меняются динамически. Отвечают на «ЧТО МОЖНО?».
  • Пересечение одно: скилл говорит «включи вот этот набор» — дальше механика делает своё.

2. Ядро (всегда в промпте, 5 тул, компактно)

  1. get_current_time — время/дата/пояс, ISO 8601 (уже есть)
  2. show_text — показать текст на очках (уже есть, ShowText)
  3. read_skill|имя — вернуть полное содержимое скилла в историю
  4. enable_toolset|имя — активировать набор тул
  5. disable_toolset|имя — снять набор тул

Все тулы — идемпотентные и прощающие:

  • повторный enable — не ошибка, «уже активен, продлеваю таймаут»
  • disable без активного — «нет активных наборов», не ошибка
  • неизвестное имя набора — «набора X нет, доступны: media, ...»

3. Структура промпта (пересобирается на КАЖДЫЙ запрос)

1. ЯДРО (всегда): 5 тул с описаниями
2. КАТАЛОГ ТУЛСЕТОВ (всегда, компактно, по строке на набор):
   media — управление медиа и воспроизведением (search/info/play)
   web   — работа в вебе (когда появится)
3. АКТИВНЫЕ ТУЛСЕТЫ (динамически, только активные):
   media: активен (+ описания тул media_search, media_info, media_play...)
4. ИНДЕКС СКИЛЛОВ (всегда, имя + триггер, по строке на скилл)
5. Содержимое скиллов — НЕ в промпте, читается через read_skill в историю

4. Префиксы тул — по имени набора

Все тулы набора имеют префикс: media_search, media_info, media_play. Префикс = маршрутизация: система видит media_* → знает набор, сигнатуры, может проверить аргументы без реестров-в-сторону.

5. Активность тулсета — на СЕССИЮ, в хиппе, по таймауту

  • Хранится в памяти (Map): сессия → (набор → deadline)
  • Не персистится: перезапуск приложения = всё сброшено (это правильно: перезапустил — значит, начал заново; 10-минутные наборы переживать рестарт не должны)
  • Таймаут: 10 минут с последнего вызова тулы набора (enable продлевает)
  • Проверка при сборке промпта: now > deadline → набора нет, запись подчистить
  • Никаких фоновых таймеров — только проверка при сборке промпта

6. Три состояния промпта

Состояние 1 — набор не активен, тема не медиа:

ядро + каталог тулсетов + индекс скиллов
(скилл media в индексе: «для фильмов вызови enable_toolset|media»)

Состояние 2 — нейронка решила активировать (enable_toolset):

  • Вызов enable НЕ пишется в историю — система активирует набор в реестре, пересобирает промпт → следующий запрос уже в состоянии 3
  • Это «невидимая системная тула»: её вызовы — часть механики, не диалога

Состояние 3 — набор активен:

ядро + описания тул media_* + «Активные тулсеты: media» + скилл media УБРАН
  • Скилл media исчезает из индекса/промпта, пока набор активен — иначе нейронка видит «надо вызвать enable» и соблазняется дёрнуть его повторно. Искушение удаляется, а не запрещается.
  • Секция «Активные тулсеты» нужна: объясняет нейронке, что тулы media_* легитимны и их можно звать (снимает паралич выбора). 5 токенов.

Деактивация: нейронка вызвала disable → выкидываем вызов из истории, снимаем набор, пересобираем промпт. Симметрично enable.

7. Бесшовная подмена вызова на активацию (защита от тупки)

Нейронка вызвала media_search при НЕактивном наборе (по привычке/призраку из истории):

  • Система НЕ выполняет вслепую и НЕ ругается
  • Подменяет вызов: как будто нейронка вызвала enable_toolset|media
  • Отвечает: «Тулсет media активирован. Реестр: media_search(query), media_info(id)... Поиск ещё НЕ выполнялся. Для поиска вызови media_search с аргументом query.»
  • Ключевое условие: явно сказать, что вызов ещё не выполнен — иначе нейронка решит, что тула сработала, и выдумает ответ (галлюцинация)
  • Нейронка видит чистую историю: запрос → активация → реестр → search. Никаких швов.

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

8. История диалога — НЕ вычищается

  • Результаты тулов остаются в истории (это память диалога: «а что там с Ларой Крофт?»)
  • Призраки тулов из прошлого нейтрализуются НЕ чисткой, а: а) бесшовной подменой (см. п.7) — модель получит реестр и повторит правильно б) секцией активных тулсетов — «сейчас активен media, зови его тулы»
  • Вычистка истории = амнезия («что ты мне показывал про Лару Крофт?» — не знаю) — НЕ ДЕЛАЕМ

9. Скиллы — файловая структура

  • Папка skills/ (на телефоне, внутренние файлы приложения)
  • Формат: name + описание-триггер в одну строку + тело (шаги, команды, питфоллы)
  • SkillRepository — объединяет источники (встроенные assets + пользовательские)
  • SkillRegistry — индекс (имя + триггер), идёт в промпт
  • Полное содержимое — только через read_skill|имя (лениво)

Скилл media (пример содержания):

Триггер: поиск фильмов, детали, запуск/управление просмотром.
Вызови enable_toolset|media, дождись ответа — в нём реестр тул:
media_search — поиск по названию (аргумент query)
media_info — детали фильма (аргумент id)
media_play — запуск/управление (аргументы...)

10. Требования к тулам набора (для маленькой модели)

  • Описания ЗЛЫЕ И КОРОТКИЕ: имя + одна строка + аргументы в одну строку
  • Активный набор ≈ 400 токенов, не гора
  • Валидация аргументов на исполнителе: невалидные → «аргументы неверны, вот схема»

11. Тестирование

  • Чистая логика — JVM-юнит-тесты (без Android): сборка промпта (3 состояния), подмена вызова, таймауты, идемпотентность, каталог/реестр, префиксная маршрутизация
  • На железе (142, adb): полный цикл — вопрос → read_skill → enable → search → ответ; переключение сессий (активность на сессию); таймаут 10 мин (ускорить для теста); подмена вызова при неактивном наборе; двойной enable/disable

12. Известные решения (не пересматривать)

  • Протокол вызова: TOOL_CALL: имя|аргументы (строка, не JSON — маленькая модель)
  • Активность тулсета: на сессию, в хиппе, 10 мин от последнего вызова
  • enable/disable — невидимые тулы (вызовы не в истории)
  • Скилл исчезает из промпта при активном наборе
  • История не вычищается
  • Подмена вызова на активацию — с явной пометкой «вызов ещё не выполнен»