From 62b4d1f7d1afaf495ba1c4d6ff4f652a5ad62711 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 8 Jun 2026 22:36:50 +0300 Subject: [PATCH] architect(ET): auto-commit from architect run_id=400 --- docs/architecture/README.md | 4 +- .../06-adr/ADR-001-effort-resolution-floor.md | 129 ++++++++++++++++++ docs/work-items/ORCH-081/10-tech-risks.md | 17 +++ 3 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 docs/work-items/ORCH-081/06-adr/ADR-001-effort-resolution-floor.md create mode 100644 docs/work-items/ORCH-081/10-tech-risks.md diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 0020af1..7bb26b9 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -42,13 +42,13 @@ created → analysis → architecture → development → review → testing → **Канон гейтов:** машинные вердикты читаются ТОЛЬКО из YAML-frontmatter, никогда из прозы. Лог-файлы мержатся в `origin/main` отдельным PR; гейт читает из `origin/main`. ### Модель и эффорт по ролям (ORCH-41, валидация ORCH-74) -Модель и `--effort` каждого агента берутся из config (`src/config.py`), резолвятся `launcher.resolve_agent_model` / `resolve_agent_effort` по приоритету **project-override (`projects_json` `agent_models`/`agent_efforts`) > `ORCH_AGENT_MODEL_`/`ORCH_AGENT_EFFORT_` > `*_default` > CLI-дефолт (без флага)**. frontmatter `model:` в `.openclaw/agents/*.md` **удалён** (ORCH-74 G1) — он был мёртвой/лживой декларацией (launcher его не читает); config — единственный источник правды о модели. Model-routing (G3) НЕ включён — все 6 агентов на `claude-opus-4-8`. +Модель и `--effort` каждого агента берутся из config (`src/config.py`), резолвятся `launcher.resolve_agent_model` / `resolve_agent_effort` по приоритету **project-override (`projects_json` `agent_models`/`agent_efforts`) > `ORCH_AGENT_MODEL_`/`ORCH_AGENT_EFFORT_` > `*_default` > CLI-дефолт (без флага)**. **Эффорт (ORCH-081):** ниже `*_default` добавлен непустой **per-role floor** — class-default поля `agent_effort_` из `config.py` (его пустой env перебить не может). Floor — строго последний уровень (ниже default) и срабатывает ТОЛЬКО когда все уровни пусты, поэтому пустые прод-`ORCH_AGENT_EFFORT_*=` (которые pydantic трактует как явное `''` и обнуляют дефолт) больше не приводят к запуску без `--effort`: каждая роль получает свой канонический пол (developer=`xhigh`, tester/deployer=`medium`, прочие=`high`). Непустой явный конфиг по-прежнему побеждает floor; опечатка вне `VALID_EFFORTS` дропается валидацией ДО floor (never-break, не маскируется). См. `docs/work-items/ORCH-081/06-adr/ADR-001-effort-resolution-floor.md`. frontmatter `model:` в `.openclaw/agents/*.md` **удалён** (ORCH-74 G1) — он был мёртвой/лживой декларацией (launcher его не читает); config — единственный источник правды о модели. Model-routing (G3) НЕ включён — все 6 агентов на `claude-opus-4-8`. | Агент | Модель | Эффорт | |-------|--------|--------| | analyst | claude-opus-4-8 | high | | architect | claude-opus-4-8 | high | -| developer | claude-opus-4-8 | high | +| developer | claude-opus-4-8 | xhigh | | reviewer | claude-opus-4-8 | high | | tester | claude-opus-4-8 | medium | | deployer | claude-opus-4-8 | medium | diff --git a/docs/work-items/ORCH-081/06-adr/ADR-001-effort-resolution-floor.md b/docs/work-items/ORCH-081/06-adr/ADR-001-effort-resolution-floor.md new file mode 100644 index 0000000..7a52b7b --- /dev/null +++ b/docs/work-items/ORCH-081/06-adr/ADR-001-effort-resolution-floor.md @@ -0,0 +1,129 @@ +# ADR-001: Per-role floor для резолва `--effort`, устойчивый к пустому env + +**Work Item:** ORCH-081 (ORCH-52h) · **Эпик:** ORCH-052 (после ORCH-074) +**Связанные:** ORCH-41 (резолв model/effort), ORCH-074 (валидация модели, `is_valid_model`) + +## Статус +Accepted + +## Контекст + +В проде `resolve_agent_effort()` возвращает `''` для всех 6 агентов, хотя в +`src/config.py` заданы осмысленные дефолты (`high`/`medium`). Итог: флаг `--effort` +не передаётся в Claude CLI, каждый агент бежит на встроенном CLI-дефолте, а не на +заявленном уровне. Для Opus 4.8 reasoning-эффорт сильнее влияет на качество, чем у +прежних моделей, → прямой удар по предсказуемости качества всего конвейера (включая +enduro-trails из общего инстанса). + +### Корень (точная механика) +Pydantic Settings трактует **присутствующую** env-переменную — даже пустую +(`ORCH_AGENT_EFFORT_DEVELOPER=` без значения) — как явное значение и **перебивает** +дефолт класса: поле `= ''`. В проде пусты И per-agent (`ORCH_AGENT_EFFORT_=`), +И default (`ORCH_AGENT_EFFORT_DEFAULT=`). Цепочка резолва (`_resolve_agent_attr`): + +``` +project-override (agent_efforts) → пусто +per-agent env ('') → falsy → skip +default ('') → falsy → skip +→ '' (уровень 4: без флага) +``` + +Привычный откат «per-agent пуст → взять default» не спасает: откатываться не на что — +default тоже пуст. Нужен непустой **per-role** «пол» (floor) ниже default. + +### Дополнительное ограничение (урок 08.06) +Хост-правки env, положенные в git-managed файл, **не переживают деплой**. Источник +правды реальных значений — `.env` на хосте (gitignored). Значит, фикс обязан быть +**code-side robust**: даже если прод-`.env` снова окажется с пустыми +`ORCH_AGENT_EFFORT_*`, эффорт всё равно резолвится в целевые значения. + +## Рассмотренные варианты + +### Вариант A — `field_validator` в `config.py` (coerce пустой → дефолт на уровне поля) +Валидатор каждого `agent_effort_*` конвертирует пустую строку в канонический дефолт +поля. +**Отклонён:** ломает приоритет FR-2. Если per-agent поле всегда непустое, оно ВСЕГДА +бьёт `default` (уровень 3 становится мёртвым для роли с пустым env). Сценарий: оператор +ставит `ORCH_AGENT_EFFORT_DEFAULT=max`, per-agent оставляет пустыми — намерение «все +роли на max», но coercion на уровне поля даст каждой роли её per-role дефолт, а не +`max`. Floor обязан стоять **строго ниже** default, а это видно только в резолвере, +где доступна вся цепочка приоритетов. + +### Вариант B — explicit hardcoded map `{analyst: high, …}` в `launcher.py` +Отдельная константа-карта per-role floor. +**Отклонён как первичный:** вводит **второй источник правды** рядом с дефолтами +`config.py`. Баг, который мы чиним, — это и есть дрейф/рассинхрон конфигурации; +заводить новую поверхность дрейфа концептуально неверно (карту и config надо вручную +держать в синхроне). + +### Вариант C — floor в резолвере, значение = class-default поля (ПРИНЯТО) +Floor применяется как **последний** уровень в `resolve_agent_effort`, ниже `default`, +а его значение берётся из **декларированного class-default** соответствующего поля +`Settings` (через `model_fields`), который пустой env НЕ может перебить. + +## Решение + +Фикс кладётся в `resolve_agent_effort` (`src/agents/launcher.py`), `_resolve_agent_attr` +остаётся общим с model-резолвом и **не трогается** (floor — effort-специфичен). + +### Цепочка резолва (новая, уровень 4 — floor) +``` +1. project-override (projects_json.agent_efforts[agent]) — непустой побеждает +2. per-agent env (settings.agent_effort_) — непустой побеждает +3. global default (settings.agent_effort_default) — непустой побеждает +4. per-role FLOOR (class-default поля agent_effort_) — НОВОЕ, непустой пол + ↓ (только если все 1–3 пусты) +5. валидация VALID_EFFORTS → невалидное дропается в '' (ORCH-41, never-break) +``` + +### Ключевые инварианты реализации +- **Floor = class-default поля, а не instance-значение.** `type(settings).model_fields[f"agent_effort_{agent}"].default` возвращает декларированный дефолт (`high`/`medium`/`xhigh`), который пустой env не клобберит. Это восстанавливает значение, которое pydantic дал бы, не будь спурьозного `VAR=`. **Единый источник правды — `config.py`**: developer-апгрейд на `xhigh` делается одной правкой поля, floor подтягивается автоматически. +- **Floor применяется ДО валидации и ТОЛЬКО при пустом резолве.** Порядок критичен для FR-3: явная опечатка (`turbo`) — непустая, поэтому floor НЕ применяется, и значение штатно дропается валидацией в `''`. Floor не маскирует мусор. +- **Floor — строго уровень 4 (ниже default).** Непустой явный env/override/`default` по-прежнему побеждает floor (FR-2). Floor срабатывает лишь когда сконфигурировать эффорт забыли/занулили на всех уровнях. +- **Unknown-agent fallback:** если поля `agent_effort_` нет (имя не из 6 ролей), floor деградирует на class-default `agent_effort_default` (`high`) — непустой безопасный пол, never-break. + +### Сопутствующая правка config (FR-4) +`config.py`: `agent_effort_developer` `high → xhigh` (канон Opus 4.8: coding/agentic роль). +Это единственное изменение значений; остальные (`analyst/architect/reviewer=high`, +`tester/deployer=medium`) подтверждаются и фиксируются устойчиво. Поскольку floor = +class-default, апгрейд автоматически становится и новым floor для developer. + +### Целевые значения (floor при полностью пустом env) +| agent | floor | +|-------|-------| +| analyst | high | +| architect | high | +| developer | **xhigh** | +| reviewer | high | +| tester | medium | +| deployer | medium | + +## Последствия + +**Плюсы** +- Code-side robust: пустой прод-`.env` больше не обнуляет эффорт; целевые уровни + гарантированы без зависимости от хост-правок, которые не переживают деплой. +- Единый источник правды (`config.py`); нулевой риск дрейфа floor-карты. +- Приоритет резолва и контракт ORCH-41 сохранены 1:1; непустой явный конфиг работает + как раньше (полная обратная совместимость). +- Валидация ORCH-41 не регрессирует — опечатки по-прежнему дропаются, never-break. + +**Минусы / ограничения** +- Лёгкая зависимость от pydantic-v2 API (`model_fields[...].default`) — публичный + стабильный атрибут, но это связь с внутренним устройством Settings. Замокать в тестах + тривиально. +- «CLI-дефолт без флага» как исход для 6 штатных ролей становится недостижим — это + намеренно: для известных ролей всегда есть непустой пол. Unknown-agent сохраняет + безопасный непустой fallback. + +**Не затрагивается** +- API endpoints — нет. Схема БД — нет. QG checks / гейты конвейера — нет. + Model-резолв (ORCH-074) — нет. Путь проброса `--effort` в `_spawn` (стр. ~434) — нет + (только верификация тестом, FR-3/FR-5). + +## Деплой (self-hosting) +Правка касается инструмента, обслуживающего в проде и другие проекты. Прод-контейнер +`orchestrator` не ронять в рамках задачи; деплой — штатно `deploy-staging` (8501) → +`Confirm Deploy`. Рекомендуется привести прод-`.env` к каноне `.env.example` +(developer=xhigh, остальные непустые), НО фикс обязан работать и без этого (FR-1). +Проверка в проде (AC-6) фиксируется в `14-deploy-log.md`. diff --git a/docs/work-items/ORCH-081/10-tech-risks.md b/docs/work-items/ORCH-081/10-tech-risks.md new file mode 100644 index 0000000..19a40cd --- /dev/null +++ b/docs/work-items/ORCH-081/10-tech-risks.md @@ -0,0 +1,17 @@ +# 10 — Технические риски: ORCH-081 (ORCH-52h) + +| ID | Риск | Вероятн. | Влияние | Митигация | +|----|------|----------|---------|-----------| +| R-1 | **Floor маскирует опечатку.** Если floor применить ПОСЛЕ/ВМЕСТО валидации, мусорное `turbo` подменится на floor вместо дропа → регрессия never-break ORCH-41. | низк. | средн. | Floor строго ДО валидации и ТОЛЬКО при пустом резолве (значение `turbo` непустое → floor не трогается → дроп). Покрыть тестом FR-3 (опечатка → `''`). | +| R-2 | **Floor перебивает явный конфиг.** Ошибка порядка → floor встанет выше default/per-agent и `ORCH_AGENT_EFFORT_DEFAULT=max` перестанет применяться. | низк. | средн. | Floor — строго уровень 4 (ниже default). Тест FR-2: непустой default/per-agent/override побеждает floor. | +| R-3 | **Зависимость от pydantic-internal** `model_fields[...].default`. Будущий мажор pydantic может сменить API → floor отвалится. | низк. | низк. | Публичный стабильный атрибут pydantic v2. Тест AC-1/AC-2 поймает регрессию сразу (floor вернёт не то/пусто). Фиксируется версией pydantic в зависимостях. | +| R-4 | **Дрейф floor vs config** при выборе hardcoded-карты. | — | — | Снят архитектурно: floor = class-default поля, единый источник правды (см. ADR-001, вариант B отклонён). | +| R-5 | **Self-hosting:** правка резолва эффорта затрагивает запуск ВСЕХ агентов всех проектов общего инстанса; ошибка ломает конвейер enduro-trails тоже. | низк. | высок. | Обязательный `deploy-staging` (8501) перед прод-деплоем; прод-контейнер не ронять вне штатного хука; `Confirm Deploy`-гейт. Post-deploy проверка AC-6 по логам запуска агента. | +| R-6 | **Прод-`.env` снова с пустыми `ORCH_AGENT_EFFORT_*`** после деплоя (урок 08.06: git-managed env не переживает). | средн. | низк. | Именно это и закрывает фикс (FR-1, code-side robust): эффорт резолвится в floor независимо от состояния `.env`. Приведение `.env` к каноне — рекомендация, не зависимость. | +| R-7 | **`xhigh` не принимается CLI-слоем.** developer-апгрейд бессмыслен, если `xhigh ∉ VALID_EFFORTS`. | очень низк. | средн. | `xhigh` уже в `VALID_EFFORTS` (`launcher.py:22`); добавления не требуется — только верификация тестом (FR-5). | + +## Сводный вывод +Изменение локализовано в `resolve_agent_effort` + один дефолт `config.py`; не трогает +API, схему БД, QG-гейты, model-резолв и путь проброса `--effort`. Главный остаточный +риск — операционный (R-5, self-hosting), снимается штатным staging-гейтом. Контракт +ORCH-41/ORCH-074 сохранён, обратная совместимость полная.