hermes-hub/agents/inbox/2026-08-31-A52-local-model-supervisor.md
Hermes Team 8b67f0dadb docs(agents): вернуть в репозиторий постановки A42-A56 и отчёт A30
Тринадцать файлов существовали только на диске ПК владельца, в рабочей
копии, отставшей от origin/main на 122 коммита. Их реализация и тесты
давно влиты: tests/test_a42_provider_connect.py,
test_a49_subagents_skills_memory.py, test_a51_hub_controls_hermes.py,
test_a52_local_models_supervisor_dual.py, test_a55_account_connection.py,
test_a56_context_compression.py и другие. Постановок, объясняющих, что
эти тесты обязаны доказывать, в репозитории не было.

Правило записано в agents/AGENTS.md: задание живёт в репозитории, а не в
переписке и не в личных папках на диске.

A48, A50 и A54 не переносятся: они уже есть на origin под другими именами,
содержимое совпадает с точностью до перевода строки в конце файла.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 18:16:35 +07:00

295 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Задание A52: локальные модели — замена, надзиратель, пара кодеров с облачным судьёй
## Дата поступления
2026-08-31 (переработано в тот же день: добавлены части 1 и 3)
## База
`origin/main` (`17b368a`).
```
git fetch origin --prune
git checkout -b antigravity/a52-local-models origin/main
```
В `main` напрямую не пушить.
## Порядок исполнения
Два прохода: **Flash** исполняет, **Pro** проводит аудит. Пункт **P0-10** написан для аудитора.
Задание из трёх частей, и они идут **строго по порядку**:
```
Часть 1 замена моделей и замеры сдаётся отдельно, дальше по её числам
Часть 2 надзиратель локальных моделей
Часть 3 пара кодеров и облачный судья
```
Часть 3 планировать **по измеренным числам части 1**, а не заранее: без замера видеопамяти схема не проверяема.
Связано с A49 (расстановка субагентов и память) и A51 (аккаунт реально используется). Выполнять после них: надзирателю нужны и место в графе, и работающее назначение.
---
## Что проверено ревьюером — заново не выяснять
### Видеопамять занята почти полностью
```
Qwen3.8-27B кодер @196K 25 488 МиБ
qwen3-4b компрессор @32K 5 368 МиБ
──────────
30 856 из 32 768 свободно 1 912
```
### Измерено у кандидатов (A45, файлы и контрольные суммы сверены по диску)
```
Qwen3-Coder-30B-A3B @64K 21 368 МиБ 109,6 ток/с 83,3% MoE 30B/3B
Qwen3-Coder-30B-A3B @32K 19 640 МиБ 110,2 ток/с
Qwen2.5-Coder-32B @64K 27 938 МиБ 29,3 ток/с 83,3% плотная
```
Расход контекста у Qwen3-Coder — около 54 КиБ на токен.
### Качество на стенде из 12 задач (A40, признано)
```
Phi-4-14B 83,3% файл 8,28 ГиБ
Qwen2.5-Coder-14B 75,0% файл 8,37 ГиБ
Qwen3-4B-2507 83,3% файл 2,33 ГиБ
Granite-4.2-8B 66,7% файл 5,16 ГиБ
```
**Расход видеопамяти у 14B при 64К не измерен ни разу.** В A45 их не было, столбец VRAM из A40 непригоден: он собрал занятость всей карты, а не процесса.
### Процессор годится для служебных ролей
Замер ревьюера, 32 потока из 72, AVX2:
```
LFM2.5-2.6B промпт 1150,8 ток/с генерация 13,4 ток/с
Qwen3-4B промпт 854,2 ток/с генерация 8,7 ток/с
```
Обработка промпта на процессоре быстрая, генерация медленная: она упирается в память и идёт последовательно.
### Сервер отдаёт всё нужное для честной работы
Проверено на живом `127.0.0.1:8081`:
```
GET /props → default_generation_settings.n_ctx = 196608, total_slots = 1
POST /tokenize → точное число токенов («def add(a, b): return a + b» = 10)
```
Предел контекста и размер задания **измеряются**, а не прикидываются.
### Чего в коде нет
```
разбиения задачи на части — нет
подсчёта токенов для планирования — нет; format_token_count только для показа
model_registry.context_window = 128000 — статическое умолчание,
а у модели владельца 196608
llama-swap — только ttl, групп нет: держать две модели резидентно не станет
```
### Поправка к постановке владельца
Оркестратор **не переставал** давать задачи локальной модели:
```
local_adapter.classify_error: "timeout", "timed out", "502", "503", "504"
→ ErrorCategory.TRANSIENT, retry_delay_seconds=2
```
Профиль не помечается исчерпанным и из цепочки не выбывает. Происходит другое: на каждом запросе локальная модель забирает отведённые Hermes 180 секунд, не успевает, и работу доделывает следующий в цепочке — платный. Чинить надо **подачу работы**, а не возврат в цепочку.
### Физика, которую нельзя обойти
**Две модели на одной V100 не работают вдвое быстрее.** Генерация упирается в пропускную способность памяти; две модели делят одну полосу. Вместе они выдадут примерно столько же, сколько одна.
Значит «параллельно» здесь означает **два независимых решения**, а не выигрыш во времени. Ускорение в интерфейсе обещать нельзя.
Оговорка: у MoE активны три миллиарда из тридцати, полосу они едят иначе. Два MoE могут ужиться лучше — **это гипотеза, её измеряют, а не закладывают**.
---
# Часть 1. Замена моделей и замеры
## P0-1. Заменить кодер
Поставить `Qwen3-Coder-30B-A3B-Instruct-Q4_K_M` вместо `Qwen3.8-27B`.
Основание измерено: **109,6 против 30,3 ток/с**, то же качество 83,3%, и на 4 ГиБ меньше.
1. Контекст **не ниже 65536** — порог отбора у Hermes.
2. Условия запуска взять у нынешнего юнита: `--flash-attn on`, `--cache-type-k/v q8_0`, `--reasoning off`, `--parallel 1`.
3. **Прежний юнит сохранить**; откат одной командой описать и проверить.
4. Скорость и видеопамять замерить **на живой службе**, а не переносить из A45.
## P0-2. Заменить компрессор
Поставить `LFM2.5-2.6B` вместо `Qwen3-4B` на порт 8082.
1. **Сначала замерить качество сжатия** на том же наборе, что у нынешнего. Быстрее — не значит лучше; сожмёт хуже, замену не делать и так и написать.
2. Рассмотреть запуск **на процессоре** (`-ngl 0`): освобождает видеопамять, а сжатие — чтение многого и запись малого, где процессор силён. Замерить оба варианта и дать владельцу числа для решения.
## P0-3. Измерить кандидатов в пару
Замерить **расход видеопамяти по процессу при 64К** для `Phi-4-14B`, `Qwen2.5-Coder-14B`, `Qwen3-4B-2507`, `Granite-4.2-8B` — через `nvidia-smi --query-compute-apps=pid,used_memory`, а не по занятости карты.
Затем **проверить запуском**, какие пары помещаются вместе с компрессором в 32 768 МиБ. Не расчётом.
Отдельно замерить, **что происходит со скоростью при одновременной работе двух моделей**: суммарная выработка против одиночной. Это проверка утверждения о полосе памяти, и её результат решает, имеет ли смысл держать пару резидентно.
**Часть 1 сдаётся отдельно.**
---
# Часть 2. Надзиратель локальных моделей
Владелец: «если выбирается локальная модель, должен появляться субагент, который мониторит подачу работы. Если у модели не хватает контекста, он разбивает задачу на куски и подаёт, пока не заработает. Потом формирует память, какой объём давать модели».
## P0-4. Роль надзирателя
1. **Новая каноническая роль** `local-supervisor` в реестре.
2. **Включается автоматически**, когда выбранный профиль локальный (`local`, `llama.cpp`, `ollama`, `vllm`). Вручную назначать не нужно.
3. **Не встаёт между ролью и платным провайдером.**
4. **Надзиратель — не модель, а распорядитель.** Считает, режет, подаёт, наблюдает; работу делает локальная модель. Тратить на него платный вызов нельзя.
Основная его работа — счёт и разбор, а не рассуждение: токены считает `/tokenize`, предел даёт `/props`, границы кусков определяются разбором кода. Ставить сюда слабую модель значит сделать надзирателя менее надёжным.
## P0-5. Замер перед подачей, а не догадка
1. **Предел контекста брать у живого сервера** через `/props`. Умолчание `model_registry` = 128000 к модели владельца отношения не имеет.
2. **Размер задания считать через `/tokenize`** — точно. Оценка по символам допустима только запасным путём и должна быть помечена как оценка.
3. **Учитывать место под ответ**: в контекст входят задание, история и ожидаемый ответ. Запас обосновать.
4. **Не выдумывать пределы.** Сервер не ответил — так и записать.
## P0-6. Разбиение и подача
1. **Помещается — подавать целиком.** Резать без нужды вредно: теряется связность.
2. **Не помещается — резать по смысловым границам**: файл, функция, класс, раздел. Посреди выражения — нельзя.
3. **Подавать последовательно**, передавая накопленный результат, и собирать ответ.
4. **Неделимая задача — честный отказ**, а не разрез наугад.
5. **Число попыток ограничено** и настраивается. Бесконечный цикл на единственной видеокарте недопустим.
6. **После исчерпания попыток** — отказ с причиной, дальше обычная отказоустойчивость. Надзиратель **не прячет неудачу**, удерживая работу на локальной модели любой ценой.
## P0-7. Наблюдение за ходом
1. **Видеть, что модель работает, а не висит**: поток ответа или тайминги сервера.
2. **Различать три исхода**: успел, не успел, ответил ошибкой. Сейчас всё сваливается в «таймаут».
3. **Показывать ход** на «Обзоре»: какой кусок из скольких, сколько токенов подано.
4. **Отдельно ловить случай A39**: весь лимит ушёл на рассуждения, ответа нет. Измерено: 1500 токенов за 111 секунд и **ноль символов ответа**; с `enable_thinking: false` — ответ за 11 секунд. Признак — пустой ответ при полном расходе лимита; лечится `request_options`, механизм есть после A39.
## P0-8. Память: какой объём модель тянет
1. **Записывать по каждой модели**: при каком размере получался ответ, при каком нет, сколько занимало, какой кусок оказался рабочим.
2. **Хранить в общей памяти** (`/srv/projects/AI-Memory`, структура после A47). Запись подписывается: модель, когда, по какому заданию.
3. **Использовать при следующей подаче**: начинать с размера, который уже работал.
4. **Привязывать к имени сборки из метаданных GGUF**, а не к порту или имени профиля. Урок Tiel-Coder: в файле оказалась `Ornith-1.5-35B`.
5. **Показывать во вкладке «Память»** надзирателя, что он усвоил.
---
# Часть 3. Пара кодеров и облачный судья
Планировать **по числам части 1**.
## P0-9. Схема и её цена
```
задание → Кодер A (локальный) ┐
→ Кодер B (локальный) ┘→ судья (облачная модель)
├ принято → дальше по конвейеру
└ не принято → обоим на доработку
```
1. **Кодеры не видят работу друг друга** до суда. Иначе второе решение не независимо и смысл теряется.
2. **Судья облачный**, видеопамяти не занимает. Это роль `developer-2` существующего конвейера; модель задаёт владелец в интерфейсе — **зашивать имя модели или провайдера в код нельзя**.
3. **Судья получает задание и оба решения**, возвращает: какое принято либо что доработать каждому.
4. **Ревьюер остаётся на своём месте** после судьи; конвейер не переделывать.
5. **Пара включается настройкой**; возврат к одному кодеру возможен.
**Предел итераций обязателен.** Круг «пока не сделают правильно» тратит платную квоту судьи на каждом обороте.
6. **Предел кругов** настраивается, умолчание обосновать. Механизм ограничения итераций в конвейере уже есть — использовать его.
7. **Показывать номер круга.**
8. **Круги исчерпаны — честный отказ** с последним состоянием обеих работ и мнением судьи. Частичный результат за готовый не выдавать.
9. **Считать расход**: сколько вызовов судьи ушло на задачу.
10. **Круг без изменений — застревание.** Оба вернули то же, что и в прошлый раз — прекратить и сказать.
---
## P0-10. Аудит вторым проходом
1. **Числа части 1 сняты на живой службе**, а не перенесены из A45.
2. **Откат к прежнему кодеру** выполнен и проверен.
3. **Пара проверена запуском**, а не расчётом: обе модели подняты, памяти хватило, обе отвечают.
4. **Утверждение о полосе памяти** проверено: суммарная выработка двух моделей против одиночной. Результат записать, каким бы он ни был.
5. **Заведомо большая задача** разбита, подана и собрана; **неделимая** дала честный отказ.
6. **Предел попыток и предел кругов** проверены задачей, которая не выполнится никогда.
7. **Предел контекста взят у сервера**, счёт токенов сверен с `/tokenize` независимо.
8. **Независимость кодеров**: решение одного не попадает в контекст другого.
9. **Модель судьи задаётся из интерфейса**, а не зашита.
10. **Для платных провайдеров путь не изменился**, надзиратель туда не лезет.
11. **Службы владельца вернуть в рабочее состояние.**
12. **Побочные изменения** объяснить.
13. **Пропущенный пункт назвать пропущенным.**
---
## Ограничения
- Видеокарта одна, сервер рабочий: окна для замеров согласовать, службы возвращать в строй.
- Прежние юниты сохранять, откат описывать и проверять.
- Учётные данные и `~/.hermes/agy_profiles/` не трогать.
- Имена моделей и провайдеров в код не зашивать.
- Конфигурацию Hermes не править.
- Версию `0.1.1` не поднимать.
- Правило честности без исключений: ни одного числа без замера; ускорение не обещать без подтверждения.
## Критерии приёмки
**Часть 1**
1. Кодер заменён на Qwen3-Coder-30B-A3B, контекст не ниже 65536; скорость и видеопамять замерены на живой службе; откат проверен.
2. Компрессор замерен в обоих вариантах; замена сделана либо обоснованно отклонена.
3. Видеопамять четырёх кандидатов при 64К измерена по процессу.
4. Проверено запуском, какие пары помещаются; измерено, что со скоростью при одновременной работе.
**Часть 2**
5. Роль `local-supervisor` включается автоматически для локальных профилей; для остальных путь не изменился.
6. Предел контекста берётся через `/props`; размер задания считается через `/tokenize`; проверено.
7. Большая задача разбивается по смысловым границам и собирается; неделимая даёт отказ.
8. Число попыток ограничено; бесконечного цикла нет.
9. Ход виден владельцу; случай «весь лимит на рассуждения» распознаётся отдельно от таймаута.
10. Рабочий объём записан в общую память с привязкой к имени сборки GGUF и используется при следующей подаче.
**Часть 3**
11. Пара работает независимо; облачный судья сравнивает и возвращает на доработку.
12. Модель судьи задаётся из интерфейса.
13. Предел кругов работает; застревание распознаётся; расход вызовов судьи показан.
14. Пара включается и отключается настройкой.
**Общее**
15. Неизмеренное показано как `Н/Д` с причиной; неудачи не скрываются.
16. `ruff check .` чисто; релизный гейт 10/10; тестов не меньше **517**.
17. Службы владельца работают.
18. Память проекта в AI-Memory обновлена.
19. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`.
## Главное
Сейчас локальная модель получает задачу целиком, не успевает за отведённое время и отдаёт работу платному провайдеру. Так на каждом запросе: она не выбывает из цепочки, она просто всякий раз проигрывает.
Замена кодера окупается сама по себе — вчетверо быстрее при том же качестве и на четыре гигабайта меньше. Надзиратель делает подачу работы соразмерной модели: измеряет, а не предполагает, режет по смыслу и запоминает рабочий объём. Пара кодеров с облачным судьёй добавляет вторую независимую попытку — но её ценность в разных ошибках, а не в скорости, и каждый круг доработки стоит платного вызова.
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`.