diff --git a/docs/work-items/ORCH-074/01-brd.md b/docs/work-items/ORCH-074/01-brd.md new file mode 100644 index 0000000..b8add45 --- /dev/null +++ b/docs/work-items/ORCH-074/01-brd.md @@ -0,0 +1,77 @@ +# BRD — ORCH-074: фикс модели агентов (мёртвый frontmatter → валидация имени) + +Work Item ID: ORCH-074 +Эпик: ORCH-052 (слой 3), под-задача ORCH-52a +Приоритет: **urgent** +Тип: доработка механизма выбора модели агентов (self-modifying). + +## 1. Контекст и проблема + +Каркас выбора модели агентов реализован в ORCH-041 и **работает корректно**: +`src/agents/launcher.py::resolve_agent_model(agent, project_id)` резолвит модель +по приоритету project-override → `ORCH_AGENT_MODEL_` → `agent_model_default` +→ CLI-дефолт. Все 6 агентов сейчас резолвятся в `claude-opus-4-8` (через +`agent_model_default`). + +Аудит кода (08.06) выявил два дефекта данных/валидации (НЕ дефект механизма): + +- **P1. Лживый/мёртвый `model:` во frontmatter `.openclaw/agents/*.md`.** + Все 6 промптов содержат `model:` в YAML-frontmatter: + `claude-sonnet-4-6` (analyst, developer, tester, deployer) и + `claude-opus-4-7` (architect, reviewer). launcher **НЕ читает** frontmatter + `model:` — это мёртвая декларация, которая лжёт о реально используемой модели + и нарушает принцип «документация = golden source». Мина: если кто-то «починит» + launcher читать frontmatter → все агенты молча упадут на устаревшие модели. + +- **P2. Нет валидации ИМЕНИ модели.** В отличие от effort (есть `VALID_EFFORTS`-гард, + невалидный effort логируется и дропается), имя модели не валидируется. Опечатка + в `agent_model_*` / project-override → `--model <мусор>` → CLI падает или тихо + деградирует. Нарушение принципа never-break. + +## 2. Решение Славы (08.06) — фиксированный скоп + +> G3 model-routing **НЕ включаем** — ВСЕ 6 агентов остаются на `claude-opus-4-8`. +> Скоп: **G1** (убрать лживый `model:` из frontmatter) + **G2** (валидация имени +> модели, never-break) + **опц. G4** (`fallback_model` — на усмотрение архитектора, +> НЕ routing). **Эффорт НЕ трогать.** AC-4 (routing) снят. + +## 3. Бизнес-цели + +| ID | Цель | Драйвер | +|----|------|---------| +| G1 | Устранить лживый frontmatter: убрать `model:` из всех 6 `.openclaw/agents/*.md`. config — единственный источник правды модели. | Наблюдаемость (frontmatter не лжёт) | +| G2 | Добавить валидацию имени модели: невалидное имя → лог + откат на default, никогда не передаётся в `--model`. | Надёжность (never-break) | +| G4 | (опц., решает архитектор) Задать `agent_fallback_model` для страховки доступности. | Надёжность (availability) | + +## 4. Не-цели (явно вне скоупа) + +- **G3 routing НЕ включаем.** Все 6 агентов остаются `claude-opus-4-8`. AC-4 снят. +- **Эффорт НЕ трогать** — уже корректно настроен (`thinking → high`, `tester/deployer → medium`). +- **Не менять resolve-механизм ORCH-041** — он корректен. Меняются только данные + (frontmatter, опц. config) + добавляется валидация. +- **Не трогать non-self поведение** — per-project override (`projects.py agent_models`) + для enduro-trails остаётся рабочим. + +## 5. Заинтересованные стороны + +- **Owner (Слава)** — зафиксировал скоп; деплой через штатный «Confirm Deploy». +- **Агенты оркестратора** — потребители resolve-механизма (self-hosting). +- **Проект enduro-trails** — НЕ должен пострадать (общий инстанс/БД/очередь). + +## 6. Риски и инварианты + +- **Self-hosting:** изменение применяется к БУДУЩИМ запускам агентов. НЕ ломать + текущий конвейер; не ронять прод-контейнер. Деплой только через «Confirm Deploy». +- **never-break:** невалидная модель/эффорт НЕ должны ронять запуск агента — + деградация на default/CLI-дефолт + лог. +- **frontmatter автогенерация:** убедиться, что инструмент (если автогенерит + frontmatter) не вернёт `model:` обратно. Frontmatter остаётся описательным + (`name`/`description`/`tools`). +- **enduro per-project override** не должен сломаться валидацией (валидные имена + проходят без изменения поведения). + +## 7. Бизнес-эффект + +- Frontmatter перестаёт лгать → меньше риск «починки», ломающей агентов. +- Опечатка в имени модели больше не роняет/деградирует запуск агента. +- (опц.) fallback повышает доступность при перегрузке основной модели. diff --git a/docs/work-items/ORCH-074/02-trz.md b/docs/work-items/ORCH-074/02-trz.md new file mode 100644 index 0000000..cf0152d --- /dev/null +++ b/docs/work-items/ORCH-074/02-trz.md @@ -0,0 +1,99 @@ +# ТЗ — ORCH-074: убрать мёртвый frontmatter `model:` + валидация имени модели + +Work Item ID: ORCH-074 +Базируется на: BRD `01-brd.md`. Скоп фиксирован решением Славы (08.06): +**G1 + G2 + опц. G4. G3 (routing) НЕ включаем. Эффорт НЕ трогать.** + +## 1. Задействованные модули `src/` и файлы + +| Файл | Изменение | +|------|-----------| +| `.openclaw/agents/analyst.md` | **G1:** удалить строку `model: claude-sonnet-4-6` из frontmatter | +| `.openclaw/agents/architect.md` | **G1:** удалить строку `model: claude-opus-4-7` | +| `.openclaw/agents/developer.md` | **G1:** удалить строку `model: claude-sonnet-4-6` | +| `.openclaw/agents/reviewer.md` | **G1:** удалить строку `model: claude-opus-4-7` | +| `.openclaw/agents/tester.md` | **G1:** удалить строку `model: claude-sonnet-4-6` | +| `.openclaw/agents/deployer.md` | **G1:** удалить строку `model: claude-sonnet-4-6` | +| `src/agents/launcher.py` | **G2:** добавить валидацию имени модели в `resolve_agent_model` (или helper), по образцу `VALID_EFFORTS`-гарда в `resolve_agent_effort` | +| `src/config.py` | **G4 (опц.):** задать `agent_fallback_model` (если архитектор решит). При G2 — возможно добавить константу/настройку валидного формата модели | +| `docs/architecture/README.md` | **AC-6:** таблица «модель/эффорт по ролям» актуализирована; нет упоминаний sonnet/opus-4-7 как «модели агента» | +| `.env.example` | **AC-3/AC-6:** добавить блок `ORCH_AGENT_MODEL_*` / `ORCH_AGENT_EFFORT_*` / `ORCH_AGENT_FALLBACK_MODEL` (сейчас в `.env.example` их НЕТ) | +| `CLAUDE.md` | **AC-6:** при необходимости — отметить, что модель агента берётся ТОЛЬКО из config (frontmatter описательный) | +| `CHANGELOG.md` | запись о доработке | +| `tests/test_resolve_agent_model.py` | **AC-2:** добавить кейсы валидации мусорного имени | + +## 2. G1 — убрать мёртвый frontmatter `model:` + +Удалить **только** строку `model: …` из YAML-frontmatter каждого из 6 файлов +`.openclaw/agents/*.md`. Остальные ключи (`name`, `description`, `tools`/`model`-comment) +не трогать. frontmatter остаётся валидным YAML и описательным. + +Проверка (AC-1): +``` +grep -L "^model:" .openclaw/agents/*.md # должны вернуться ВСЕ 6 файлов +``` +(`grep -L` печатает файлы БЕЗ совпадения — все 6 не должны содержать `^model:`.) + +## 3. G2 — валидация имени модели (never-break) + +Требование (НЕ предписывает архитектуру — выбор предиката за архитектором): + +- Резолвенное имя модели валидируется ПЕРЕД возвратом из `resolve_agent_model` + (либо в общем helper). Невалидное имя → `logger.warning(...)` + откат на + следующий валидный уровень (в пределе — `agent_model_default`, а если и он + невалиден → `""`, т.е. без флага `--model`, CLI-дефолт). **Никогда** не вернуть + мусор, который попадёт в `--model`. +- Поведение — точная аналогия `resolve_agent_effort` (`VALID_EFFORTS`): валидный → + как есть; невалидный → лог + дроп. +- Предикат валидности (на усмотрение архитектора, рекомендация аналитика): + формат-чек `claude-*` (forward-compatible — новые версии моделей не требуют + правки allowlist) ЛИБО явный `VALID_MODELS` allowlist (строже, но требует + поддержки при выходе новых моделей). **Выбор и обоснование — в ADR.** +- Инвариант обратной совместимости: ВСЕ ныне используемые валидные имена + (`claude-opus-4-8`, а также enduro per-project override) проходят валидацию + без изменения поведения. Невалидным считается только мусор (опечатка, + `gpt-4`, пустая строка после strip и т.п.). +- Контракт уровней резолва ORCH-041 сохраняется: валидация добавляется поверх, + механизм приоритетов не меняется. + +## 4. G4 — fallback_model (опционально, решает архитектор) + +- `src/config.py::agent_fallback_model` сейчас `""` (флаг не прокидывается). +- Если архитектор решит включить — задать каноничное имя модели; launcher уже + прокидывает его в `--fallback-model` (строки 374-375 launcher.py). Имя fallback + ТОЖЕ должно проходить валидацию G2 (или быть гарантированно валидным). +- Если архитектор решит НЕ включать — оставить `""`, AC-5 помечается N/A в ADR. + +## 5. Изменения API / схемы БД + +- **API (HTTP):** нет. +- **Схема БД:** нет миграций. +- **CLI-команда агента:** формируется в `launcher._spawn` (строки 384-392). + Меняется только КАЧЕСТВО значения `--model` (валидное/дроп), сама структура + команды не меняется. + +## 6. Требования к QG checks + +- Новых QG-чеков НЕ требуется. Валидация — это runtime-гард в launcher, не + отдельный quality-gate. + +## 7. Артефакты pipeline + +Должны быть созданы/обновлены в ЭТОМ PR (golden source = код + доки): +- `docs/architecture/README.md` — таблица «модель/эффорт по ролям». +- `.env.example` — блок переменных моделей/эффорта/fallback. +- `CHANGELOG.md` — запись. +- `06-adr/ADR-NNN-*.md` — решение по предикату валидации (G2) и по G4 (fallback вкл/выкл). +- ADR архитектора фиксирует: выбран вариант G1 «убрать» (не «читать frontmatter»). + +## 8. Эффорт — НЕ ТРОГАТЬ + +`agent_effort_*` корректны (`thinking → high`, `tester/deployer → medium`). +Менять только при явном отдельном обосновании (вне скоупа этой задачи). + +## 9. Грабли + +- Имена моделей — каноничные строки Claude CLI; сверить с тем, что реально + принимает CLI на проде (`ORCH_CLAUDE_BIN`). НЕ хардкодить версию вне `config.py`. +- Если frontmatter автогенерится инструментом — убедиться, что `model:` не вернётся. +- Self-hosting: НЕ ронять прод-контейнер; деплой через «Confirm Deploy». diff --git a/docs/work-items/ORCH-074/03-acceptance-criteria.md b/docs/work-items/ORCH-074/03-acceptance-criteria.md new file mode 100644 index 0000000..139d744 --- /dev/null +++ b/docs/work-items/ORCH-074/03-acceptance-criteria.md @@ -0,0 +1,78 @@ +# Критерии приёмки — ORCH-074 + +Work Item ID: ORCH-074 +Скоп (Слава 08.06): G1 + G2 + опц. G4. **G3 routing снят — AC-4 не применяется.** + +Каждый критерий: чёткое условие PASS/FAIL. + +--- + +## AC-1 — frontmatter `model:` убран из всех 6 промптов (G1) + +- **PASS:** ни один файл `.openclaw/agents/*.md` не содержит строки `^model:` в + frontmatter. Команда `grep -L "^model:" .openclaw/agents/*.md` возвращает все 6 + файлов (analyst, architect, developer, reviewer, tester, deployer). +- **FAIL:** хотя бы в одном файле осталась строка `model:`. +- Доп. инвариант: frontmatter остаётся валидным YAML; ключи `name`/`description`/`tools` + сохранены. + +## AC-2 — валидация имени модели, never-break (G2) + +- **PASS:** при невалидном `agent_model_*` / project-override (мусорное имя) + `resolve_agent_model` возвращает откат на default (или `""`), пишет + `logger.warning`, и мусор **никогда** не попадает в `--model`. Покрыто + unit-тестом с мусорным именем (см. `04-test-plan.yaml`, TC-03..TC-05). +- **FAIL:** мусорное имя проходит насквозь в `--model`, или валидация роняет + запуск агента (исключение вместо graceful-деградации). + +## AC-3 — resolve_agent_model осмыслен для всех 6 агентов + +- **PASS:** для каждого из 6 агентов `resolve_agent_model(agent)` (без + project_id) возвращает `claude-opus-4-8` (routing G3 выключен → intelligence- + модель для всех). Значение документировано в README (таблица env) и `.env.example`. +- **FAIL:** хотя бы один агент резолвится в пустую/невалидную/устаревшую модель, + либо документация не отражает фактическую модель. + +## AC-4 — routing (G3) — **СНЯТ (N/A)** + +- Routing НЕ включается в этой задаче. Критерий не применяется. ADR фиксирует + отказ от G3 как осознанное решение Славы (08.06). + +## AC-5 — fallback_model (G4, опционально) + +- **PASS (если G4 включён):** `agent_fallback_model` задан каноничным именем, + проходит валидацию G2, прокидывается в `--fallback-model` (launcher 374-375), + задокументирован. +- **PASS (если G4 НЕ включён):** `agent_fallback_model = ""`, ADR явно фиксирует + отказ; AC-5 помечен N/A. +- **FAIL:** fallback задан невалидным именем, ИЛИ включён без документации/ADR. + +## AC-6 — синхронизация документации + +- **PASS:** `docs/architecture/README.md`, `CLAUDE.md`, `.env.example` + синхронизированы — таблица «модель по ролям» актуальна (все = `claude-opus-4-8`); + НЕТ упоминаний `claude-sonnet-4-6` / `claude-opus-4-7` как «модели агента» + (если они не используются). `.env.example` содержит блок + `ORCH_AGENT_MODEL_*` / `ORCH_AGENT_EFFORT_*` / `ORCH_AGENT_FALLBACK_MODEL`. +- **FAIL:** документация противоречит config, или остались мёртвые упоминания + sonnet/opus-4-7 как модели агента. + +## AC-7 — pytest зелёный + never-break + +- **PASS:** `pytest tests/ -q` зелёный. Невалидная модель/эффорт НЕ роняет запуск + агента (graceful-деградация подтверждена тестами). +- **FAIL:** падают тесты, или невалидный вход роняет запуск. + +## AC-8 — enduro per-project override не сломан + +- **PASS:** валидный per-project override (`projects.py agent_models`) для не-self + проекта (enduro) резолвится и проходит валидацию без изменения поведения + (покрыто существующими тестами `test_resolve_agent_model.py`). +- **FAIL:** валидация ломает корректный per-project override. + +## AC-9 — ADR зафиксирован + +- **PASS:** ADR в `06-adr/` фиксирует: (а) выбран вариант G1 «убрать frontmatter» + (не «читать»); (б) предикат валидации G2 (формат-чек vs allowlist) с обоснованием; + (в) решение по G4 (вкл/выкл) и по отказу от G3. +- **FAIL:** ADR отсутствует или не покрывает эти решения. diff --git a/docs/work-items/ORCH-074/04-test-plan.yaml b/docs/work-items/ORCH-074/04-test-plan.yaml new file mode 100644 index 0000000..d1effdb --- /dev/null +++ b/docs/work-items/ORCH-074/04-test-plan.yaml @@ -0,0 +1,92 @@ +work_item: ORCH-074 +# Скоп (Слава 08.06): G1 + G2 + опц. G4. G3 routing снят (no routing tests). +# Эффорт не трогаем (no new effort tests beyond never-break regression). + +tests: + # ---- G1: frontmatter `model:` убран из всех 6 промптов (AC-1) ---- + - id: TC-01 + type: integration + description: > + Ни один .openclaw/agents/*.md не содержит строки `^model:` во frontmatter. + Тест итерирует по 6 файлам, ассертит отсутствие model:-строки. + module: tests/test_agent_frontmatter_no_model.py + expected: PASS + + - id: TC-02 + type: integration + description: > + frontmatter каждого из 6 промптов остаётся валидным YAML и сохраняет ключи + name/description (парсинг между первыми двумя '---' без ошибок). + module: tests/test_agent_frontmatter_no_model.py + expected: PASS + + # ---- G2: валидация имени модели, never-break (AC-2, AC-7) ---- + - id: TC-03 + type: unit + description: > + Мусорное имя в agent_model_ (напр. 'gpt-4' или 'claud-opus-typo') + -> resolve_agent_model откатывается на default (claude-opus-4-8) и НЕ + возвращает мусор. Проверяется также warning в логах (caplog). + module: tests/test_resolve_agent_model.py + expected: PASS + + - id: TC-04 + type: unit + description: > + Мусорное имя в project-override (agent_models) -> resolve_agent_model + откатывается на следующий валидный уровень (default), мусор не передаётся. + module: tests/test_resolve_agent_model.py + expected: PASS + + - id: TC-05 + type: unit + description: > + Невалиден И override, И default -> resolve_agent_model возвращает "" + (без флага --model, CLI-дефолт). never-break: исключение НЕ бросается. + module: tests/test_resolve_agent_model.py + expected: PASS + + - id: TC-06 + type: unit + description: > + Валидное каноничное имя (claude-opus-4-8) проходит валидацию без изменения: + resolve_agent_model('developer') == 'claude-opus-4-8'. Регрессия ORCH-041. + module: tests/test_resolve_agent_model.py + expected: PASS + + # ---- AC-3: все 6 агентов резолвятся в осмысленную модель ---- + - id: TC-07 + type: unit + description: > + Для всех 6 агентов (analyst/architect/developer/reviewer/tester/deployer) + resolve_agent_model(agent) == 'claude-opus-4-8' (routing выключен). + module: tests/test_resolve_agent_model.py + expected: PASS + + # ---- AC-8: enduro per-project override не сломан валидацией ---- + - id: TC-08 + type: unit + description: > + Валидный per-project override (agent_models у не-self проекта) резолвится и + проходит валидацию без изменения поведения (регрессия ORCH-041). + module: tests/test_resolve_agent_model.py + expected: PASS + + # ---- G4: fallback_model (опц.) — условный тест ---- + - id: TC-09 + type: unit + description: > + ЕСЛИ G4 включён архитектором: agent_fallback_model задан валидным именем и + проходит валидацию G2. ЕСЛИ выключен: agent_fallback_model == "" (тест + подтверждает дефолт). Финальная форма теста зависит от решения в ADR. + module: tests/test_resolve_agent_model.py + expected: PASS + + # ---- AC-7: общий зелёный прогон / never-break regression ---- + - id: TC-10 + type: integration + description: > + Полный pytest зелёный; невалидная модель/эффорт не роняет запуск агента + (graceful-деградация). Регрессия resolve_agent_effort (VALID_EFFORTS) цела. + module: tests/ + expected: PASS