ТЗ: агент — скиллы + тулсеты (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