# Агент «Порфирий» на телефоне: скиллы + тулсеты — архитектура (договорено 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 — невидимые тулы (вызовы не в истории) - Скилл исчезает из промпта при активном наборе - История не вычищается - Подмена вызова на активацию — с явной пометкой «вызов ещё не выполнен»