ТЗ: агент — скиллы + тулсеты (TASK-agent-tools.md)
Архитектура (договорено 2026-08-22): - скиллы (знания) ≠ тулсеты (руки) - ядро 5 тул: время, show_text, read_skill, enable/disable_toolset - префиксы тул по набору (media_*) - активность на сессию, в хиппе, таймаут 10 мин - enable/disable невидимы (не в истории), скилл исчезает при активном наборе - бесшовная подмена вызова на активацию - история не вычищается
This commit is contained in:
@@ -0,0 +1,147 @@
|
|||||||
|
# Агент «Порфирий» на телефоне: скиллы + тулсеты — архитектура (договорено 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 — невидимые тулы (вызовы не в истории)
|
||||||
|
- Скилл исчезает из промпта при активном наборе
|
||||||
|
- История не вычищается
|
||||||
|
- Подмена вызова на активацию — с явной пометкой «вызов ещё не выполнен»
|
||||||
Reference in New Issue
Block a user