docs(agents): задание A26 — аккаунты без слотов, распределение и пороги квот

Владелец сформулировал, ради чего задумывался хаб: вручную распределять
аккаунты по агентам и следить за квотами, подставляя другой аккаунт там, где
лимит подходит к концу. Модель слотов этому мешает — она навязывает
внутреннее устройство конфигурации и уже привела к тому, что подключённый
аккаунт не появился в маршрутизации.

Задание опирается на три факта, снятых исполнением, чтобы агенты не
переписывали работающее: один профиль уже может обслуживать все шесть ролей;
произвольный идентификатор профиля регистрируется, то есть потолок в три
аккаунта Codex держит только зашитый список в auto_assigner; порогов квот в
коде нет вовсе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-25 14:40:37 +07:00
parent d4b99a49ef
commit 8cb57dd6a1

View file

@ -0,0 +1,171 @@
# Задание A26: аккаунты без слотов, распределение по агентам и пороги квот
## Дата поступления
2026-08-25
## База
Проверочный HEAD на момент выдачи: **`d4b99a4`**.
## Ветка
`antigravity/accounts-without-slots`
## Порядок исполнения
Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-6** написан для аудитора.
---
## Порядок работы с git
```
cd <каталог репозитория>; git fetch origin --prune; git status
git checkout main; git pull --ff-only origin main
git checkout -b antigravity/accounts-without-slots
git commit -m "..." <- сначала коммит
git push -u origin antigravity/accounts-without-slots
```
В `main` напрямую не пушить. В конце — push и проверка `git log --oneline -1`, `git status` чистый.
---
## Ради чего это всё
Дословно от владельца:
> «вот для этого хаб и делался. чтобы вручную правильно распределять аккаунты и играться с лимитами. где подзаканчиваются, там ставить другой аккаунт»
Сейчас продукт этого не даёт. Модель «слотов» — десять предсозданных ячеек Antigravity, три Codex, три OpenCode — навязывает пользователю внутреннее устройство конфигурации. Владелец выбирает слот, ничего не зная о последствиях, и получает результат, которого не ожидал: аккаунт подключён, а на экранах его нет, потому что слот не входит ни в одну цепочку. Это уже случилось вживую.
Нужен обратный порядок: **сначала аккаунты, потом назначение**.
---
## Что уже проверено исполнением — заново не выясняйте
Три факта, снятые ревьюером на `d4b99a4`. Они сильно сокращают работу.
**1. Один профиль может обслуживать все шесть ролей.** Движок это держит уже сейчас:
```
persist_role_chain(role, ['ag-w1']) для всех шести ролей -> True
конфигурация: все шесть цепочек равны ['ag-w1']
```
Значит требование «если есть только Antigravity, он встаёт во всех агентах» **не требует изменений в маршрутизаторе**. Не переписывайте `router_engine`.
**2. Произвольный идентификатор профиля регистрируется.**
```
ensure_profile_definition('openai-codex', 'codex-worker-9') -> True
'codex-worker-9' in load_router_config().profiles -> True
```
Значит потолок «три аккаунта Codex» — это **только** зашитый список `provider_slots` в `auto_assigner.py:144`, а не структурное ограничение. Снятие потолка не требует переделки формата конфигурации.
**3. Порогов квот в коде нет вообще.** Поиск по `threshold|min_quota|quota_floor|switch_at` в `router/` не даёт ни одного совпадения. Это делается с нуля.
---
## P0-1. Снять потолок на число аккаунтов
Дословно: «например у меня есть 4 кодекса, и их все я хочу добавить. или 5 опенкодов».
Сейчас `AutoAssigner.find_free_slot` (`auto_assigner.py:140`) перебирает жёсткий список и возвращает `None`, когда список кончился. Четвёртый Codex подключить невозможно.
Требуется: число аккаунтов провайдера **ничем не ограничено**. Когда предопределённые имена кончились, идентификатор выдаётся автоматически по понятной схеме (`codex-4`, `codex-5`, …), с проверкой, что такого ещё нет.
Идентификатор — деталь реализации, и в интерфейсе он не должен быть главным. Владелец мыслит аккаунтами и почтами, а не слотами.
## P0-2. «Аккаунты» показывают только настоящие аккаунты
Дословно: «надо убрать все ячейки со вкладки аккаунты. как добавляю аккаунт, тогда он там появляется. не надо захламлять страницу».
Сейчас в умолчаниях предсозданы **24 профиля**, и страница показывает их все, включая никогда не подключённые: «Аккаунт не добавлен», «Холодный резерв». У владельца это 24 карточки при одном реальном аккаунте.
Требуется показывать **только подключённые**.
Осторожно: не удаляйте профили из конфигурации молча — на них ссылаются цепочки ролей. Речь о том, что показывать, а не о том, что хранить. Решите чистить и конфигурацию — обоснуйте в отчёте и сохраните работоспособность цепочек.
## P0-3. Назначение аккаунта агенту — в «Обзоре»
Дословно: «просто загрузка аккаунтов, а уже в обзоре прикреплять нужный аккаунт к агенту».
На «Обзоре» уже есть схема ролей и выбор модели в узле (A24). Добавить туда выбор **аккаунта**: какой обслуживает эту роль и в каком порядке резервирования.
Действия существуют — `assign_role`, `save_chain`, `reorder_chain`; второй реализации не заводить.
Ключевое требование, вытекающее из факта №1: **один аккаунт можно назначить сразу нескольким агентам**, вплоть до всех шести. Интерфейс не должен этому препятствовать и не должен считать это ошибкой.
## P0-4. Кнопка «Авто»
Дословно: «или при нажатии авто, сам распределяет аккаунты согласно маршрутизации».
Действие `auto_assign_all` существует (`action_handler.py:509`), но что оно делает и совпадает ли с ожиданием владельца — **проверьте исполнением и опишите в отчёте**. Отчёт без запуска не принимается.
Ожидаемое поведение:
- **Один провайдер.** Есть только Antigravity — он встаёт во все шесть ролей. Владелец дальше сам меняет модель у каждого агента. Это нормальный режим, а не вырожденный случай.
- **Несколько провайдеров.** Распределение идёт по назначению роли: «оркестратор у меня первый кодекс, а кодер антигравити» — у каждой роли есть предпочтительный провайдер, и авто-распределение ему следует, а не раскладывает аккаунты подряд.
- **Несколько аккаунтов одного провайдера** разводятся по разным ролям, чтобы не жечь квоту одного на всё сразу.
Порядок предпочтений по ролям возьмите из текущего `config/router_profiles.example.yaml` — он и выражает замысел владельца. Решите его менять — сначала спросите в отчёте, молча не меняйте.
## P0-5. Пороги квот: предупреждение или переключение
Дословно: «выставлять минимальные лимиты (например 10% или 5%) и при достижении этих лимитов или оповещение или автоматом переключает на резервный аккаунт».
Это то, ради чего хаб задумывался. Требуется:
1. **Настраиваемый порог** — общий и, если несложно, отдельный для аккаунта. Значения владельца: 10% и 5%. **В код их не зашивать**, это умолчание, а не константа.
2. **Выбор поведения** при достижении: только оповестить либо переключиться на следующий аккаунт в цепочке. Решает владелец, не код.
3. **Оповещение видно в интерфейсе** — в «Состоянии» и в журнале событий. Достаточно ли тоста — решите и обоснуйте.
4. **Переключение обратимо.** Квоты восстанавливаются по `reset_at`; отставленный по порогу аккаунт обязан вернуться в строй сам. Урок A23 уже оплачен: у `AUTH_REQUIRED` не было выхода, и шесть профилей выпали навсегда. **Повторять нельзя.**
Данные есть: `quota_collector` собирает проценты и `reset_at`, они видны в карточках.
Честность обязательна: порог срабатывает по **измеренной** квоте. Неизвестная квота («Н/Д») — **не** повод считать её нулевой и переключаться. Нет данных — так и сказать, поведение не менять.
## P0-6. Аудит вторым проходом
Для проверяющего. Список составлен из дефектов, уже проходивших мимо первого прохода.
1. **Выдуманные значения.** Находились захардкоженные коды устройства `GRK-7842`, `CDX-9104`, запасной коммит `fb23bff` в манифесте. Здесь особый риск в P0-5: соблазн подставить «разумный» процент при отсутствии данных.
2. **Тесты, закрепляющие дефект.** `test_headless_server_auth_matrix` дважды защищал заглушку: сначала требовал неверный адрес `x.ai/device`, затем слова «Headless» и «agy», хотя вход через веб уже работал. Новые тесты должны описывать желаемое поведение.
3. **Запустить изменённое.** Импорт ничего не доказывает. На днях вложенная функция оказалась не видна части точек вызова и давала `NameError` внутри обработки успеха — поймал `ruff`, а не тест.
4. **Кэш состояния.** Только что чинилось: учётные данные сохранялись, но `refresh(force_scan=False)` состояние не обновлял, и подключённый аккаунт не появлялся никогда. Любое изменение состава аккаунтов обязано быть видно **сразу**, без перезапуска.
5. **Побочные изменения** в файлах, которых задание не касалось, — объяснить каждое.
6. **Пропущенный пункт назвать пропущенным.**
---
## Ограничения
- Десктоп (`router/ui/**`) не трогать: он выводится из обращения.
- Правило честности без исключений. Нет данных — «Н/Д» и причина.
- Действия только через существующий `action_handler`; второй реализации `assign_role`, `save_chain`, `set_model` быть не должно.
- Меняете контракт — правьте `docs/web-api/CONTRACT.md` и скажите в отчёте.
- Конфигурацию владельца литералами не править.
- Тег `v0.1.1` не создавать.
## Критерии приёмки
1. Ветка в `origin`, `git status` чист.
2. Подключается **пятый** аккаунт одного провайдера; проверено исполнением, вывод в отчёте.
3. «Аккаунты» показывают только подключённые; при нуле подключённых — внятное пустое состояние, а не 24 пустые карточки.
4. Аккаунт назначается агенту из «Обзора»; один аккаунт назначается **всем шести** ролям; изменение доходит до `router_profiles.yaml` и переживает перезапуск.
5. «Авто» описано по факту запуска: что делает при одном провайдере, при двух, при нескольких аккаунтах одного провайдера.
6. Порог настраивается, значение не зашито; при достижении срабатывает выбранное поведение; при неизвестной квоте не срабатывает.
7. Аккаунт, отставленный по порогу, возвращается в строй после восстановления квоты **без ручного вмешательства**; проверено тестом.
8. Изменения состава аккаунтов видны в интерфейсе сразу, без перезапуска.
9. Ни одного выдуманного значения; проверено отдельно и описано в отчёте.
10. `ruff check .` чисто; релизный гейт не ухудшен.
11. **Скриншоты:** «Аккаунты» с одним аккаунтом, «Обзор» с назначением, настройка порога.
12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. На `main` сейчас **432 passed, 2 skipped**.
## Главное
Владелец хочет управлять аккаунтами, а не слотами: загрузил аккаунты, распределил по агентам, следит за квотами и переставляет, когда они подходят к концу. Всё остальное в задании обслуживает это.
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`. Сдано только после появления коммита в `origin`.