Merge pull request 'fix(effort): per-role floor for --effort + developer→xhigh (ORCH-081)' (#80) from feature/ORCH-081-orch-52h-env-config into main
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit was merged in pull request #80.
This commit is contained in:
@@ -37,12 +37,15 @@ ORCH_AGENT_MODEL_DEVELOPER=
|
||||
ORCH_AGENT_MODEL_REVIEWER=
|
||||
ORCH_AGENT_MODEL_TESTER=
|
||||
ORCH_AGENT_MODEL_DEPLOYER=
|
||||
# Effort split: thinking agents (analyst/architect/developer/reviewer) -> high;
|
||||
# mechanical agents (tester/deployer) -> medium.
|
||||
# Effort split (ORCH-081/ORCH-52h): thinking agents (analyst/architect/reviewer)
|
||||
# -> high; developer -> xhigh (coding/agentic role, Opus 4.8 canon); mechanical
|
||||
# agents (tester/deployer) -> medium. NB: an empty ORCH_AGENT_EFFORT_*= no longer
|
||||
# zeroes the effort — the launcher falls back to a per-role floor (= the config.py
|
||||
# class-default) so each role still runs at its canonical level (ORCH-081).
|
||||
ORCH_AGENT_EFFORT_DEFAULT=high
|
||||
ORCH_AGENT_EFFORT_ANALYST=high
|
||||
ORCH_AGENT_EFFORT_ARCHITECT=high
|
||||
ORCH_AGENT_EFFORT_DEVELOPER=high
|
||||
ORCH_AGENT_EFFORT_DEVELOPER=xhigh
|
||||
ORCH_AGENT_EFFORT_REVIEWER=high
|
||||
ORCH_AGENT_EFFORT_TESTER=medium
|
||||
ORCH_AGENT_EFFORT_DEPLOYER=medium
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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_<AGENT>`/`ORCH_AGENT_EFFORT_<AGENT>` > `*_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_<AGENT>`/`ORCH_AGENT_EFFORT_<AGENT>` > `*_default` > CLI-дефолт (без флага)**. **Эффорт (ORCH-081):** ниже `*_default` добавлен непустой **per-role floor** — class-default поля `agent_effort_<role>` из `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 |
|
||||
|
||||
7
docs/work-items/ORCH-081/00-business-request.md
Normal file
7
docs/work-items/ORCH-081/00-business-request.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Business Request: ORCH-52h: эффорт агентов резолвится в пустую строку в проде (env перебивает config)
|
||||
|
||||
Work Item ID: ORCH-081
|
||||
|
||||
## Description
|
||||
|
||||
TBD
|
||||
82
docs/work-items/ORCH-081/01-brd.md
Normal file
82
docs/work-items/ORCH-081/01-brd.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# 01 — BRD: ORCH-081 (ORCH-52h)
|
||||
|
||||
**Work Item:** ORCH-081
|
||||
**Эпик:** ORCH-052 (продолжение ORCH-52a / ORCH-074)
|
||||
**Тип:** Багфикс (конфигурация эффорта агентов)
|
||||
**Приоритет:** HIGH
|
||||
**Repo:** orchestrator (self-hosting)
|
||||
|
||||
## 1. Контекст и проблема
|
||||
|
||||
При проверке ORCH-074 (08.06) обнаружено: `resolve_agent_effort()` для **всех 6 агентов
|
||||
в проде** возвращает пустую строку `''`, хотя в `src/config.py` заданы осмысленные
|
||||
дефолты (`agent_effort_default="high"`, per-agent `high`/`medium`). Итог: флаг
|
||||
`--effort` **не передаётся** в Claude CLI, и каждый агент бежит на встроенном
|
||||
CLI-дефолте эффорта, а **не** на заявленном `high`/`medium`.
|
||||
|
||||
### Корень (диагностика)
|
||||
В проде env-переменные `ORCH_AGENT_EFFORT_DEFAULT` и
|
||||
`ORCH_AGENT_EFFORT_{ANALYST,ARCHITECT,DEVELOPER,REVIEWER,TESTER,DEPLOYER}` выставлены в
|
||||
**пустую строку** (`VAR=` без значения). Pydantic Settings трактует присутствующую
|
||||
env-переменную (даже пустую) как явное значение и **перебивает** дефолт класса:
|
||||
`agent_effort_* = ''`. В цепочке резолва (`launcher._resolve_agent_attr`):
|
||||
- per-agent `''` → falsy → пропуск (уровень 2);
|
||||
- default `''` → falsy → пропуск (уровень 3);
|
||||
- → возврат `''` (уровень 4, «без флага»).
|
||||
|
||||
Поскольку **и default тоже пуст**, привычный откат «per-agent пуст → взять default»
|
||||
не спасает: откатываться не на что. Это ключевой нюанс — фикс обязан давать каждой
|
||||
роли непустой «пол» (floor) даже когда И per-agent, И default env пусты.
|
||||
|
||||
## 2. Бизнес-ценность / зачем важно
|
||||
|
||||
Для Opus 4.8 (канон Anthropic) уровень reasoning-эффорта влияет на качество вывода
|
||||
**сильнее**, чем у прежних моделей. Coding/agentic роли (особенно `developer`) должны
|
||||
идти минимум на `high`, а `developer` — кандидат на `xhigh`. Сейчас фактически работает
|
||||
неконтролируемый CLI-дефолт → прямой удар по стратегии надёжности и предсказуемости
|
||||
качества всего конвейера (включая enduro-trails из общего инстанса).
|
||||
|
||||
## 3. Решение (бизнес-уровень)
|
||||
|
||||
Принят **вариант (c)** (решение Славы, 08.06): пустая строка эффорта трактуется как
|
||||
«не задано» и откатывается на осмысленный per-role дефолт (а не на CLI-дефолт),
|
||||
**устойчиво** к пустым env. Дополнительно — зафиксировать целевые дефолты в `config.py`
|
||||
и `.env.example`.
|
||||
|
||||
### Целевые значения эффорта (единственный апгрейд — `developer`)
|
||||
| Агент | Эффорт | Обоснование |
|
||||
|-------|--------|-------------|
|
||||
| analyst | high | intelligence-роль |
|
||||
| architect | high | intelligence-роль |
|
||||
| **developer** | **xhigh** | coding/agentic, канон Opus 4.8 → апгрейд с `high` |
|
||||
| reviewer | high | intelligence-роль |
|
||||
| tester | medium | механическая роль |
|
||||
| deployer | medium | механическая роль |
|
||||
|
||||
`developer → xhigh` — единственное изменение относительно текущих config-дефолтов;
|
||||
остальные значения подтверждают текущий замысел и фиксируются устойчиво.
|
||||
|
||||
## 4. Грабли / ограничения (из бизнес-запроса)
|
||||
|
||||
- **Хост-репо / env-правки НЕ переживают деплой**, если положены в git-managed файл
|
||||
(урок 08.06 про docker-compose + TZ). Источник правды для реальных значений —
|
||||
`.env` на хосте (gitignored), канон-шаблон — `.env.example`. Фикс обязан быть
|
||||
**code-side robust**: даже если прод-`.env` снова окажется с пустыми
|
||||
`ORCH_AGENT_EFFORT_*`, эффорт всё равно резолвится в целевые значения.
|
||||
- **Self-hosting:** правка касается инструмента, который сейчас в проде обслуживает и
|
||||
другие проекты. Прод-контейнер `orchestrator` не ронять в рамках задачи; деплой —
|
||||
через штатный `deploy-staging` → `Confirm Deploy`.
|
||||
|
||||
## 5. Не-цели
|
||||
|
||||
- НЕ трогать model-резолв (`resolve_agent_model` — сделан в ORCH-074).
|
||||
- НЕ включать G3 model-routing — все 6 агентов остаются на `claude-opus-4-8`.
|
||||
- НЕ менять значения эффорта сверх согласованных (`high`/`medium`/`xhigh` для
|
||||
developer). Иные значения — отдельное взвешенное решение.
|
||||
|
||||
## 6. Затронутые стороны
|
||||
|
||||
- Все агенты конвейера (analyst → deployer) во всех проектах общего инстанса.
|
||||
- Операторы (правка прод-`.env`), документация (README таблица, `.env.example`).
|
||||
</content>
|
||||
</invoke>
|
||||
110
docs/work-items/ORCH-081/02-trz.md
Normal file
110
docs/work-items/ORCH-081/02-trz.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 02 — ТЗ: ORCH-081 (ORCH-52h)
|
||||
|
||||
**Work Item:** ORCH-081 · **Тип:** багфикс конфигурации · **Repo:** orchestrator
|
||||
|
||||
Документ описывает ТРЕБУЕМОЕ ПОВЕДЕНИЕ и затронутые модули. Конкретный механизм
|
||||
(field_validator vs изменение резолвера) — на усмотрение архитектора; ниже зафиксированы
|
||||
инварианты, которым любая реализация обязана удовлетворять.
|
||||
|
||||
## 1. Задействованные модули
|
||||
|
||||
| Модуль | Роль в задаче |
|
||||
|--------|----------------|
|
||||
| `src/config.py` (`Settings`) | дефолты эффорта; устойчивость к пустому env (ядро фикса) |
|
||||
| `src/agents/launcher.py` | `resolve_agent_effort` / `_resolve_agent_attr` (цепочка резолва), `VALID_EFFORTS`, сборка `--effort` в `_spawn` |
|
||||
| `.env.example` | канон-шаблон значений эффорта по ролям |
|
||||
| `docs/architecture/README.md` | таблица «Модель и эффорт по ролям» (строки ~47–54) |
|
||||
| `CHANGELOG.md` | запись о фиксе |
|
||||
| `tests/test_resolve_agent_effort.py` | расширить кейсами пустого env |
|
||||
|
||||
## 2. Корень бага (точная механика)
|
||||
|
||||
`launcher._resolve_agent_attr` (строки ~104–114):
|
||||
```
|
||||
per_agent = getattr(settings, f"agent_effort_{agent}", "") # '' в проде -> falsy -> skip
|
||||
default = getattr(settings, "agent_effort_default", "") # '' в проде -> falsy -> skip
|
||||
return "" # уровень 4: без флага
|
||||
```
|
||||
Pydantic: `ORCH_AGENT_EFFORT_*=` (пустая строка в env) перебивает дефолт класса →
|
||||
поле `= ''`. Поскольку пустым оказывается **и** `agent_effort_default`, у резолва нет
|
||||
непустого «пола» для отката → `''` → `--effort` не передаётся.
|
||||
|
||||
## 3. Требования к фиксу (вариант c)
|
||||
|
||||
### FR-1. Непустой floor на каждую роль при пустом env
|
||||
При ЛЮБОЙ комбинации пустых `ORCH_AGENT_EFFORT_*` (включая `ORCH_AGENT_EFFORT_DEFAULT=`)
|
||||
`resolve_agent_effort(agent)` обязан вернуть целевое непустое значение для каждой из 6
|
||||
ролей:
|
||||
|
||||
| agent | результат |
|
||||
|-------|-----------|
|
||||
| analyst | `high` |
|
||||
| architect | `high` |
|
||||
| developer | `xhigh` |
|
||||
| reviewer | `high` |
|
||||
| tester | `medium` |
|
||||
| deployer | `medium` |
|
||||
|
||||
Замечание для реализации: floor должен быть **per-role**, а не единым на default —
|
||||
иначе пустой `ORCH_AGENT_EFFORT_TESTER=` снапнется на `high` вместо `medium`. Т.е.
|
||||
«пустая строка трактуется как не-задано» применяется так, чтобы каждая роль получала
|
||||
СВОЙ канонический дефолт, а не общий.
|
||||
|
||||
### FR-2. Приоритет резолва сохраняется
|
||||
Порядок не меняется: project-override (`projects_json.agent_efforts`) > per-agent env >
|
||||
default > floor. Непустой явный env/override по-прежнему ПОБЕЖДАЕТ floor (оператор может
|
||||
осознанно задать, напр., `ORCH_AGENT_EFFORT_DEVELOPER=high`, и это применится).
|
||||
|
||||
### FR-3. Валидация невалидного значения не регрессирует
|
||||
Значение вне `VALID_EFFORTS` (`low|medium|high|xhigh|max`) по-прежнему логируется
|
||||
(`logger.warning`) и **дропается** → `''` (без флага). Floor НЕ должен «спасать» явную
|
||||
опечатку (`turbo`/`ultra`) — поведение ORCH-41 сохраняется (never-break, мусор не
|
||||
уезжает в CLI).
|
||||
|
||||
### FR-4. `developer → xhigh` зафиксирован явно
|
||||
`config.py`: `agent_effort_developer` со значением `xhigh` (сейчас `high`).
|
||||
`.env.example`: `ORCH_AGENT_EFFORT_DEVELOPER=xhigh` (сейчас `high`) + правка комментария
|
||||
про split (developer теперь xhigh, не в группе «thinking → high»).
|
||||
|
||||
### FR-5. `xhigh` принимается CLI-слоем
|
||||
Подтвердить, что `xhigh` присутствует в `VALID_EFFORTS`
|
||||
(`src/agents/launcher.py:22` — уже `frozenset({"low","medium","high","xhigh","max"})`,
|
||||
**присутствует**; добавления не требуется, только верификация тестом). Эффорт реально
|
||||
собирается в команду: `_spawn` строит `effort_flag = f"--effort {effort} "` при непустом
|
||||
`effort` (строка ~434) — путь проброса не менять, только убедиться тестом сборки флага.
|
||||
|
||||
## 4. Изменения API / схемы БД
|
||||
|
||||
- **API endpoints:** нет.
|
||||
- **Схема БД:** нет.
|
||||
- **Конфиг (env-контракт):** значения `ORCH_AGENT_EFFORT_*` неизменны по ИМЕНАМ;
|
||||
меняется лишь дефолт `developer` (high → xhigh) и устойчивость к пустым значениям.
|
||||
Обратная совместимость: непустой явный env работает 1:1 как раньше.
|
||||
|
||||
## 5. Требования к QG checks
|
||||
|
||||
Новых QG checks не требуется. Гейты конвейера не затрагиваются.
|
||||
|
||||
## 6. Артефакты pipeline (обновить в ТОМ ЖЕ PR)
|
||||
|
||||
- `src/config.py` — дефолт developer + устойчивость к пустому env.
|
||||
- `src/agents/launcher.py` — если фикс кладётся в резолвер (на усмотрение архитектора).
|
||||
- `.env.example` — `ORCH_AGENT_EFFORT_DEVELOPER=xhigh` + правка комментария split.
|
||||
- `docs/architecture/README.md` — таблица эффорта: developer `high` → `xhigh`; при
|
||||
необходимости — ремарка про floor/устойчивость к пустому env.
|
||||
- `CHANGELOG.md` — запись (`fix:`).
|
||||
- `tests/test_resolve_agent_effort.py` — новые кейсы (см. 04-test-plan.yaml).
|
||||
|
||||
## 7. Операционная часть (вне PR-кода, для деплой-лога)
|
||||
|
||||
- Реальные значения — в прод-`.env` на хосте (gitignored). Рекомендуется привести
|
||||
прод-`.env` к каноне `.env.example` (developer=xhigh, остальные непустые), НО фикс
|
||||
обязан работать и без этого (FR-1). Не коммитить секреты/хост-env в git.
|
||||
- Деплой — через `deploy-staging` (8501) → `Confirm Deploy`. Прод-контейнер не ронять
|
||||
вне штатного хука.
|
||||
|
||||
## 8. Definition of Done
|
||||
|
||||
AC-1…AC-5 из `03-acceptance-criteria.md` выполнены; `pytest -q` зелёный; документация
|
||||
(README + `.env.example` + CHANGELOG) синхронизирована в том же PR; never-break соблюдён.
|
||||
</content>
|
||||
60
docs/work-items/ORCH-081/03-acceptance-criteria.md
Normal file
60
docs/work-items/ORCH-081/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 03 — Критерии приёмки: ORCH-081 (ORCH-52h)
|
||||
|
||||
Каждый критерий — чёткое условие PASS/FAIL. Пустой env моделируется в unit-тестах
|
||||
(установка `agent_effort_* = ""`), проверка «в проде» — операционная (post-deploy).
|
||||
|
||||
## AC-1 — осмысленный непустой эффорт для всех 6 агентов
|
||||
**PASS:** `resolve_agent_effort(agent)` возвращает целевое непустое значение для каждой
|
||||
роли при канонической конфигурации:
|
||||
|
||||
| agent | ожидаемое |
|
||||
|-------|-----------|
|
||||
| analyst | `high` |
|
||||
| architect | `high` |
|
||||
| developer | `xhigh` |
|
||||
| reviewer | `high` |
|
||||
| tester | `medium` |
|
||||
| deployer | `medium` |
|
||||
|
||||
**FAIL:** любой агент возвращает `''` или значение, отличное от таблицы.
|
||||
|
||||
## AC-2 — пустой env НЕ приводит к пустому эффорту (вариант c)
|
||||
**PASS:** при `agent_effort_default = ""` И всех `agent_effort_<role> = ""`
|
||||
(моделирование прод-env, где `ORCH_AGENT_EFFORT_*=` пусты) `resolve_agent_effort` для
|
||||
каждой из 6 ролей возвращает значение по таблице AC-1 (floor per-role срабатывает:
|
||||
developer=`xhigh`, tester/deployer=`medium`, остальные=`high`), а **не** `''`.
|
||||
**FAIL:** хотя бы одна роль при полностью пустом env даёт `''`.
|
||||
|
||||
## AC-3 — эффорт реально пробрасывается в запуск агента
|
||||
**PASS:** в `launcher._spawn` (или эквивалентной сборке) при непустом резолвнутом
|
||||
эффорте формируется `--effort <value> ` во флагах команды; при пустом — флаг
|
||||
отсутствует. Тест сборки флага подтверждает наличие `--effort xhigh ` для developer и
|
||||
`--effort medium ` для tester.
|
||||
**FAIL:** `--effort` отсутствует при непустом значении ИЛИ присутствует при пустом.
|
||||
|
||||
## AC-4 — документация синхронизирована
|
||||
**PASS:** `.env.example` содержит `ORCH_AGENT_EFFORT_DEVELOPER=xhigh` и корректный
|
||||
комментарий про split; таблица «Модель и эффорт по ролям» в
|
||||
`docs/architecture/README.md` показывает developer = `xhigh` (остальные без изменений);
|
||||
`CHANGELOG.md` содержит запись о фиксе.
|
||||
**FAIL:** любой из трёх артефактов рассинхронизирован с фактическими дефолтами config.
|
||||
|
||||
## AC-5 — never-break, тесты зелёные
|
||||
**PASS:**
|
||||
- `pytest -q` целиком зелёный (включая существующие
|
||||
`tests/test_resolve_agent_effort.py` и новые кейсы).
|
||||
- Невалидное значение эффорта (`turbo`/`ultra`/`bogus`) по-прежнему логируется и
|
||||
дропается в `''` (floor его НЕ маскирует) — регрессии валидации ORCH-41 нет.
|
||||
- Непустой явный per-agent env / project-override по-прежнему побеждает floor
|
||||
(приоритет резолва сохранён).
|
||||
- `xhigh ∈ VALID_EFFORTS` (подтверждено тестом).
|
||||
|
||||
**FAIL:** падение любого теста, регрессия валидации/приоритета, либо `xhigh`
|
||||
отвергается как невалидный.
|
||||
|
||||
## AC-6 (операционный, для деплой-стадии) — проверка в проде
|
||||
**PASS:** после деплоя на проде `resolve_agent_effort` для 6 агентов даёт значения
|
||||
AC-1 (проверяется в рантайме прод-инстанса / по логам запуска агента — наличие
|
||||
`--effort` с верным уровнем). Фиксируется в `14-deploy-log.md`.
|
||||
**FAIL:** в проде хотя бы один агент бежит без `--effort` или с неверным уровнем.
|
||||
</content>
|
||||
86
docs/work-items/ORCH-081/04-test-plan.yaml
Normal file
86
docs/work-items/ORCH-081/04-test-plan.yaml
Normal file
@@ -0,0 +1,86 @@
|
||||
work_item: ORCH-081
|
||||
description: >
|
||||
Тест-план фикса ORCH-52h — устойчивость резолва эффорта к пустому env (вариант c) +
|
||||
фиксация целевых дефолтов (developer -> xhigh). Расширяет существующий
|
||||
tests/test_resolve_agent_effort.py. Пустой прод-env моделируется установкой
|
||||
agent_effort_* = "" на settings (через monkeypatch), как уже делают текущие тесты.
|
||||
tests:
|
||||
- id: TC-01
|
||||
type: unit
|
||||
description: >
|
||||
Канонические дефолты: resolve_agent_effort для всех 6 ролей даёт
|
||||
analyst/architect/reviewer=high, developer=xhigh, tester/deployer=medium.
|
||||
module: tests/test_resolve_agent_effort.py
|
||||
covers: [AC-1, FR-4]
|
||||
expected: PASS
|
||||
|
||||
- id: TC-02
|
||||
type: unit
|
||||
description: >
|
||||
Пустой env (вариант c): при agent_effort_default="" И всех
|
||||
agent_effort_<role>="" каждая из 6 ролей возвращает целевое значение по AC-1
|
||||
(НЕ ""). Ключевой кейс бага: developer -> xhigh, tester/deployer -> medium,
|
||||
analyst/architect/reviewer -> high.
|
||||
module: tests/test_resolve_agent_effort.py
|
||||
covers: [AC-2]
|
||||
expected: PASS
|
||||
|
||||
- id: TC-03
|
||||
type: unit
|
||||
description: >
|
||||
Floor НЕ маскирует опечатку: невалидное значение (default/per-agent/override =
|
||||
'turbo'/'ultra'/'bogus') по-прежнему логируется и дропается в "" (валидация
|
||||
ORCH-41 не регрессирует). Проверить, что floor не подменяет невалидный явный ввод
|
||||
на дефолт.
|
||||
module: tests/test_resolve_agent_effort.py
|
||||
covers: [AC-5, FR-3]
|
||||
expected: PASS
|
||||
|
||||
- id: TC-04
|
||||
type: unit
|
||||
description: >
|
||||
Приоритет сохранён: непустой per-agent env побеждает floor/ default
|
||||
(ORCH_AGENT_EFFORT_DEVELOPER=high -> "high", не "xhigh"); project-override
|
||||
побеждает per-agent (agent_efforts={"developer":"xhigh"}).
|
||||
module: tests/test_resolve_agent_effort.py
|
||||
covers: [AC-5, FR-2]
|
||||
expected: PASS
|
||||
|
||||
- id: TC-05
|
||||
type: unit
|
||||
description: >
|
||||
xhigh валиден: xhigh ∈ VALID_EFFORTS и resolve_agent_effort с developer-дефолтом
|
||||
xhigh не дропается.
|
||||
module: tests/test_resolve_agent_effort.py
|
||||
covers: [AC-5, FR-5]
|
||||
expected: PASS
|
||||
|
||||
- id: TC-06
|
||||
type: unit
|
||||
description: >
|
||||
Сборка флага: при resolve developer=xhigh во флагах присутствует "--effort xhigh ",
|
||||
при tester=medium — "--effort medium "; при пустом эффорте "--effort" отсутствует
|
||||
(mirror логики _spawn, как существующие test_flags_* кейсы).
|
||||
module: tests/test_resolve_agent_effort.py
|
||||
covers: [AC-3]
|
||||
expected: PASS
|
||||
|
||||
- id: TC-07
|
||||
type: integration
|
||||
description: >
|
||||
Документация синхронизирована: .env.example содержит
|
||||
ORCH_AGENT_EFFORT_DEVELOPER=xhigh; README таблица эффорта показывает developer
|
||||
xhigh. (Проверяется ревьюером/тестером по diff; опционально — текстовая ассерта.)
|
||||
module: tests/test_resolve_agent_effort.py
|
||||
covers: [AC-4]
|
||||
expected: PASS
|
||||
|
||||
- id: TC-08
|
||||
type: unit
|
||||
description: >
|
||||
Регрессия существующего набора: весь tests/test_resolve_agent_effort.py +
|
||||
tests/test_resolve_agent_model.py остаются зелёными (never-break ORCH-41/074).
|
||||
module: tests/test_resolve_agent_effort.py
|
||||
covers: [AC-5]
|
||||
expected: PASS
|
||||
</content>
|
||||
@@ -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_<ROLE>=`),
|
||||
И 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_<agent>) — непустой побеждает
|
||||
3. global default (settings.agent_effort_default) — непустой побеждает
|
||||
4. per-role FLOOR (class-default поля agent_effort_<agent>) — НОВОЕ, непустой пол
|
||||
↓ (только если все 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_<agent>` нет (имя не из 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`.
|
||||
17
docs/work-items/ORCH-081/10-tech-risks.md
Normal file
17
docs/work-items/ORCH-081/10-tech-risks.md
Normal file
@@ -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 сохранён, обратная совместимость полная.
|
||||
57
docs/work-items/ORCH-081/12-review.md
Normal file
57
docs/work-items/ORCH-081/12-review.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
type: review
|
||||
work_item_id: ORCH-081
|
||||
verdict: APPROVED
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Review ORCH-081 (ORCH-52h) — устойчивость резолва `--effort` к пустому env + developer→xhigh
|
||||
|
||||
## Summary
|
||||
Фикс конфигурационного бага: в проде `resolve_agent_effort()` возвращал `''` для всех 6 агентов (пустые `ORCH_AGENT_EFFORT_*=` перебивают class-default pydantic), `--effort` не доходил до Claude CLI. Решение — вариант C по ADR-001: непустой **per-role floor** уровня 4 в `resolve_agent_effort`, значение = декларированный class-default поля `agent_effort_<agent>` через `model_fields[...].default`. `developer` поднят `high→xhigh` в `config.py` (единый источник правды, floor подтягивается автоматически).
|
||||
|
||||
Реализация полностью соответствует ТЗ и ADR; вся документация синхронизирована в том же бранче; `pytest -q` — **1031 passed**.
|
||||
|
||||
## Соответствие ТЗ (FR-1…FR-5)
|
||||
- **FR-1** per-role floor при пустом env → каждая роль получает свой канон (`_agent_effort_floor`, TC-02). ✓
|
||||
- **FR-2** приоритет резолва сохранён: явный env/override/default побеждают floor (TC-04: `test_explicit_env_beats_floor`, `test_default_beats_floor`, `test_project_override_beats_floor`). ✓
|
||||
- **FR-3** валидация не регрессирует: непустая опечатка (`turbo`) не доходит до floor → дропается в `''` (TC-03 `test_floor_does_not_mask_typo`). ✓
|
||||
- **FR-4** `agent_effort_developer = "xhigh"` в `config.py`; `ORCH_AGENT_EFFORT_DEVELOPER=xhigh` + правка комментария split в `.env.example`. ✓
|
||||
- **FR-5** `xhigh ∈ VALID_EFFORTS`; сборка флага `--effort xhigh `/`--effort medium ` подтверждена (TC-05/TC-06). ✓
|
||||
|
||||
## Соответствие ADR-001
|
||||
- Floor как **строго уровень 4** ниже default, в резолвере — ✓ (вариант C, не field_validator/не hardcoded map).
|
||||
- Floor = **class-default поля** (`type(settings).model_fields[...].default`), который пустой env перебить не может — ✓.
|
||||
- `_resolve_agent_attr` (общий с model-резолвом) **не тронут** — ✓.
|
||||
- Floor применяется **ДО валидации и только при пустом резолве** — ✓.
|
||||
- Unknown-agent деградирует на class-default `agent_effort_default` (`high`) — ✓ (`test_empty_env_unknown_agent_floor_is_default`).
|
||||
- Никаких изменений API / схемы БД / QG / model-резолва / пути проброса в `_spawn` — ✓.
|
||||
|
||||
## Качество кода и тестов
|
||||
- Чистый leaf-helper, подробные docstrings, контракт never-raise соблюдён.
|
||||
- Тесты содержательные, покрывают все AC/FR (канон-дефолты, floor per-role, не-маскирование опечатки, приоритет на 3 уровнях, `xhigh`-валидность, сборка флага + негативные кейсы).
|
||||
|
||||
## Findings
|
||||
|
||||
### P0 — Blocker
|
||||
- (нет)
|
||||
|
||||
### P1 — Must fix
|
||||
- (нет)
|
||||
|
||||
### P2 — Should fix
|
||||
- (нет)
|
||||
|
||||
### P3 — Nice-to-have
|
||||
- `tests/test_resolve_agent_effort.py:218-219` — продублирована строка `assert "--fallback-model" not in flags` в `test_flags_absent_when_model_empty`. Безвредно, можно убрать при случае.
|
||||
|
||||
## Документация
|
||||
Изменён `src/` → документация обновлена в том же бранче (доку-гейт пройден):
|
||||
- `docs/architecture/README.md` — таблица «Модель и эффорт по ролям»: developer = `xhigh`; добавлена ремарка про per-role floor / устойчивость к пустому env (AC-4). ✓
|
||||
- `.env.example` — `ORCH_AGENT_EFFORT_DEVELOPER=xhigh` + комментарий split/floor (AC-4). ✓
|
||||
- `CHANGELOG.md` — запись `fix:` с разбором корня/фикса. ✓
|
||||
- `docs/work-items/ORCH-081/06-adr/ADR-001-effort-resolution-floor.md` — присутствует (Accepted). ✓
|
||||
|
||||
## Примечание (вне scope ревью)
|
||||
- AC-6 — операционная проверка в проде после деплоя, фиксируется в `14-deploy-log.md` на стадии deploy. К коду PR не относится.
|
||||
- `git diff main...HEAD` показывает также код ORCH-074 (`is_valid_model`/`resolve_agent_model`) из-за устаревшего локального `main`; собственно изменения ORCH-081 — коммит `56bf303` (+ README обновлён в линии бранча). На ревью это не влияет: HEAD-состояние корректно по всем осям.
|
||||
61
docs/work-items/ORCH-081/13-test-report.md
Normal file
61
docs/work-items/ORCH-081/13-test-report.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
type: test-report
|
||||
work_item_id: ORCH-081
|
||||
result: PASS
|
||||
---
|
||||
|
||||
# Test Report — ORCH-081 (ORCH-52h)
|
||||
|
||||
Устойчивость резолва `--effort` к пустому env (вариант c) + фиксация целевых
|
||||
дефолтов (developer → xhigh).
|
||||
|
||||
## Окружение
|
||||
- Python: 3.12.13
|
||||
- pytest: 8.3.3
|
||||
- Repo/branch: orchestrator @ `feature/ORCH-081-orch-52h-env-config` (worktree)
|
||||
- prod `/health`: ok (8500) · staging `/health`: ok (8501) — не трогались
|
||||
- Дата: 2026-06-08
|
||||
|
||||
## Результаты по тест-плану (04-test-plan.yaml)
|
||||
|
||||
| TC ID | Описание | Покрытие | Результат |
|
||||
|-------|----------|----------|-----------|
|
||||
| TC-01 | Канонические дефолты: 6 ролей дают high/high/xhigh/high/medium/medium | AC-1, FR-4 | PASS |
|
||||
| TC-02 | Пустой env (вариант c): per-role floor, developer→xhigh, tester/deployer→medium, остальные→high (НЕ "") | AC-2 | PASS |
|
||||
| TC-03 | Floor НЕ маскирует опечатку: `turbo`/`ultra`/`bogus` логируется и дропается в "" | AC-5, FR-3 | PASS |
|
||||
| TC-04 | Приоритет сохранён: непустой per-agent env / project-override побеждают floor/default | AC-5, FR-2 | PASS |
|
||||
| TC-05 | `xhigh ∈ VALID_EFFORTS` и не дропается | AC-5, FR-5 | PASS |
|
||||
| TC-06 | Сборка флага: `--effort xhigh ` (developer), `--effort medium ` (tester); пустой → флаг отсутствует | AC-3 | PASS |
|
||||
| TC-07 | Документация синхронизирована: `.env.example` DEVELOPER=xhigh, README таблица developer=xhigh | AC-4 | PASS |
|
||||
| TC-08 | Регрессия: весь набор test_resolve_agent_effort.py + полный регресс зелёные | AC-5 | PASS |
|
||||
|
||||
### Сопоставление с критериями приёмки
|
||||
- **AC-1** — `test_canonical_effort_all_roles[*]` (6 параметров) → PASS.
|
||||
- **AC-2** — `test_empty_env_falls_back_to_per_role_floor[*]` (6 параметров) + `test_empty_env_unknown_agent_floor_is_default` → PASS.
|
||||
- **AC-3** — `test_flags_present_when_configured`, `test_flags_effort_per_role`, `test_flags_absent_when_effort_empty` → PASS.
|
||||
- **AC-4** — verified по diff: `src/config.py:108` `agent_effort_developer = "xhigh"`; `.env.example:48` `ORCH_AGENT_EFFORT_DEVELOPER=xhigh`; `docs/architecture/README.md` таблица developer=`xhigh`; `CHANGELOG.md` содержит запись `fix:` → PASS.
|
||||
- **AC-5** — `test_floor_does_not_mask_typo`, `test_*_beats_floor`, `test_xhigh_is_valid`, `test_invalid_*_dropped` + полный регресс зелёный → PASS.
|
||||
- **AC-6** — операционный, вне scope стадии testing: проверяется в рантайме прода на стадии `deploy`, фиксируется в `14-deploy-log.md`.
|
||||
|
||||
## Smoke test API (prod 8500)
|
||||
- `GET /health` → `{"status":"ok","service":"orchestrator"}`
|
||||
- `GET /status` → HTTP 200
|
||||
- `GET /queue` → HTTP 200
|
||||
|
||||
## Вывод pytest
|
||||
|
||||
Целевой файл задачи:
|
||||
```
|
||||
tests/test_resolve_agent_effort.py ... 29 passed, 1 warning in 0.36s
|
||||
```
|
||||
|
||||
Полный регресс:
|
||||
```
|
||||
........................................................................ [ 97%]
|
||||
....................... [100%]
|
||||
1031 passed, 1 warning in 27.02s
|
||||
```
|
||||
(единственный warning — PydanticDeprecatedSince20 в `src/config.py:5`, не относится к задаче, предсуществующий.)
|
||||
|
||||
## Итог
|
||||
**PASS** — все 8 TC пройдены, критерии AC-1…AC-5 выполнены (AC-6 операционный, для стадии deploy), полный регресс `1031 passed`, smoke API зелёный. Прод/staging-контейнеры не затрагивались.
|
||||
12
docs/work-items/ORCH-081/14-deploy-log.md
Normal file
12
docs/work-items/ORCH-081/14-deploy-log.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
deploy_status: SUCCESS
|
||||
work_item: ORCH-081
|
||||
hook_exit_code: 0
|
||||
deployed_by: deploy-finalizer
|
||||
---
|
||||
|
||||
# Deploy log — ORCH-036 executable self-deploy
|
||||
|
||||
Прод-деплой завершён хост-хуком с exit-code `0` -> `deploy_status: SUCCESS`.
|
||||
|
||||
Вердикт зафиксирован детерминированным finalizer'ом (Фаза C), не LLM.
|
||||
@@ -158,12 +158,50 @@ def resolve_agent_model(agent: str, project_id: str = None) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _agent_effort_floor(agent: str) -> str:
|
||||
"""ORCH-081 (ORCH-52h): per-role non-empty floor for --effort resolution.
|
||||
|
||||
Returns the DECLARED class-default of the ``agent_effort_<agent>`` field on
|
||||
Settings (e.g. developer -> ``xhigh``, tester/deployer -> ``medium``, the rest
|
||||
-> ``high``). This is the value pydantic WOULD have used were it not clobbered
|
||||
by a spurious empty env var (``ORCH_AGENT_EFFORT_<ROLE>=``): the class-default
|
||||
is fixed in the class body and a present-but-empty env value cannot override it,
|
||||
so it is a robust floor even when the host ``.env`` zeroes every effort var.
|
||||
|
||||
config.py is the single source of truth: upgrading developer to ``xhigh`` there
|
||||
automatically raises the floor here — no second map to keep in sync (ADR-001).
|
||||
|
||||
Unknown agent (a name outside the 6 roles) has no ``agent_effort_<agent>``
|
||||
field; we degrade to the class-default of ``agent_effort_default`` (``high``),
|
||||
a safe non-empty floor. Never raises.
|
||||
"""
|
||||
fields = type(settings).model_fields
|
||||
for key in (f"agent_effort_{agent}", "agent_effort_default"):
|
||||
field = fields.get(key)
|
||||
if field is not None and field.default:
|
||||
return field.default
|
||||
return ""
|
||||
|
||||
|
||||
def resolve_agent_effort(agent: str, project_id: str = None) -> str:
|
||||
"""ORCH-41: resolve the --effort level for an agent (optionally per-project).
|
||||
|
||||
Same priority as resolve_agent_model. The resolved value is validated against
|
||||
VALID_EFFORTS; an invalid value is logged and dropped (returns "") so a typo
|
||||
in env/projects_json can never pass a bad flag to the CLI.
|
||||
Same priority as resolve_agent_model, with one extra level below the global
|
||||
default (ORCH-081 / ADR-001):
|
||||
1. project-override (projects_json.agent_efforts[agent])
|
||||
2. per-agent env (settings.agent_effort_<agent>)
|
||||
3. global default (settings.agent_effort_default)
|
||||
4. per-role FLOOR (class-default of agent_effort_<agent>) — NEW
|
||||
|
||||
The floor only kicks in when levels 1-3 are all empty (the prod bug: a present
|
||||
but empty ``ORCH_AGENT_EFFORT_*=`` clobbers every default to ''), guaranteeing
|
||||
a non-empty target effort for the 6 known roles regardless of host .env state.
|
||||
|
||||
The floor is applied BEFORE validation and ONLY to an empty resolve, so it
|
||||
never masks a typo: an explicit invalid value (e.g. ``turbo``) is non-empty,
|
||||
skips the floor, and is logged + dropped to "" exactly as in ORCH-41 (the
|
||||
resolved value is validated against VALID_EFFORTS; an invalid value can never
|
||||
pass a bad flag to the CLI). Never raises.
|
||||
"""
|
||||
value = _resolve_agent_attr(
|
||||
agent, project_id,
|
||||
@@ -171,6 +209,11 @@ def resolve_agent_effort(agent: str, project_id: str = None) -> str:
|
||||
env_attr_prefix="agent_effort_",
|
||||
default_attr="agent_effort_default",
|
||||
)
|
||||
if not value:
|
||||
# Levels 1-3 all empty (typically a prod .env with empty ORCH_AGENT_EFFORT_*):
|
||||
# fall through to the per-role floor (class-default). Applied before
|
||||
# validation but only here, so a typo (non-empty) never reaches this branch.
|
||||
value = _agent_effort_floor(agent)
|
||||
if value and value not in VALID_EFFORTS:
|
||||
logger.warning(
|
||||
f"Invalid effort '{value}' for agent '{agent}' "
|
||||
|
||||
@@ -97,13 +97,15 @@ class Settings(BaseSettings):
|
||||
agent_model_deployer: str = ""
|
||||
|
||||
# ORCH-41: per-agent effort / reasoning level: low|medium|high|xhigh|max.
|
||||
# Empty -> agent_effort_default. Same resolution order as model. Default split:
|
||||
# thinking agents (analyst/architect/developer/reviewer) -> high; mechanical
|
||||
# agents (tester/deployer) -> medium.
|
||||
# Empty -> agent_effort_default. Same resolution order as model. Default split
|
||||
# (ORCH-081/ORCH-52h): thinking agents (analyst/architect/reviewer) -> high;
|
||||
# developer -> xhigh (coding/agentic role, Opus 4.8 canon); mechanical agents
|
||||
# (tester/deployer) -> medium. These class-defaults are ALSO the per-role floor
|
||||
# used by resolve_agent_effort when the env is empty (single source of truth).
|
||||
agent_effort_default: str = "high"
|
||||
agent_effort_analyst: str = "high"
|
||||
agent_effort_architect: str = "high"
|
||||
agent_effort_developer: str = "high"
|
||||
agent_effort_developer: str = "xhigh"
|
||||
agent_effort_reviewer: str = "high"
|
||||
agent_effort_tester: str = "medium"
|
||||
agent_effort_deployer: str = "medium"
|
||||
|
||||
@@ -26,13 +26,22 @@ from src.projects import ProjectConfig, reload_projects
|
||||
ORCH_PLANE_ID = "8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a"
|
||||
|
||||
|
||||
# ORCH-081/ORCH-52h: canonical effort per role (developer upgraded high -> xhigh).
|
||||
CANON_EFFORT = {
|
||||
"analyst": "high",
|
||||
"architect": "high",
|
||||
"developer": "xhigh",
|
||||
"reviewer": "high",
|
||||
"tester": "medium",
|
||||
"deployer": "medium",
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_settings(monkeypatch):
|
||||
monkeypatch.setattr(settings, "agent_effort_default", "high")
|
||||
for a in ("analyst", "architect", "developer", "reviewer"):
|
||||
monkeypatch.setattr(settings, f"agent_effort_{a}", "high")
|
||||
for a in ("tester", "deployer"):
|
||||
monkeypatch.setattr(settings, f"agent_effort_{a}", "medium")
|
||||
for a, e in CANON_EFFORT.items():
|
||||
monkeypatch.setattr(settings, f"agent_effort_{a}", e)
|
||||
monkeypatch.setattr(P.settings, "projects_json", "")
|
||||
reload_projects()
|
||||
yield
|
||||
@@ -50,19 +59,40 @@ def _install_registry(monkeypatch, agent_efforts):
|
||||
monkeypatch.setattr(P, "_BY_REPO", {p.repo: p for p in reg})
|
||||
|
||||
|
||||
# ---- default split ----------------------------------------------------------
|
||||
# ---- TC-01: canonical defaults (AC-1 / FR-4) --------------------------------
|
||||
def test_default_split():
|
||||
assert resolve_agent_effort("developer") == "high"
|
||||
assert resolve_agent_effort("developer") == "xhigh"
|
||||
assert resolve_agent_effort("architect") == "high"
|
||||
assert resolve_agent_effort("tester") == "medium"
|
||||
assert resolve_agent_effort("deployer") == "medium"
|
||||
|
||||
|
||||
# ---- level 4: nothing -> "" -------------------------------------------------
|
||||
def test_no_config_returns_empty(monkeypatch):
|
||||
@pytest.mark.parametrize("agent,expected", list(CANON_EFFORT.items()))
|
||||
def test_canonical_effort_all_roles(agent, expected):
|
||||
assert resolve_agent_effort(agent) == expected
|
||||
|
||||
|
||||
# ---- TC-02: empty env -> per-role floor (variant c, AC-2) -------------------
|
||||
@pytest.mark.parametrize("agent,expected", list(CANON_EFFORT.items()))
|
||||
def test_empty_env_falls_back_to_per_role_floor(monkeypatch, agent, expected):
|
||||
"""Models the prod bug: ORCH_AGENT_EFFORT_*= present-but-empty -> every level
|
||||
resolves to '' on the instance; the per-role floor (config class-default) must
|
||||
still yield the canonical level (NOT '')."""
|
||||
monkeypatch.setattr(settings, "agent_effort_default", "")
|
||||
for a in CANON_EFFORT:
|
||||
monkeypatch.setattr(settings, f"agent_effort_{a}", "")
|
||||
result = resolve_agent_effort(agent)
|
||||
assert result == expected
|
||||
assert result != ""
|
||||
|
||||
|
||||
# ---- unknown agent floor degrades to default (high), never '' ---------------
|
||||
def test_empty_env_unknown_agent_floor_is_default(monkeypatch):
|
||||
monkeypatch.setattr(settings, "agent_effort_default", "")
|
||||
monkeypatch.setattr(settings, "agent_effort_tester", "")
|
||||
assert resolve_agent_effort("tester") == ""
|
||||
# An agent with no agent_effort_<name> field falls back to the
|
||||
# agent_effort_default class-default (high), a safe non-empty floor.
|
||||
assert resolve_agent_effort("nonexistent_role") == "high"
|
||||
|
||||
|
||||
# ---- level 2: per-agent env beats default -----------------------------------
|
||||
@@ -103,6 +133,45 @@ def test_all_valid_efforts_pass(monkeypatch):
|
||||
assert resolve_agent_effort("developer") == e
|
||||
|
||||
|
||||
# ---- TC-03: floor does NOT mask a typo (FR-3 / AC-5) ------------------------
|
||||
def test_floor_does_not_mask_typo(monkeypatch):
|
||||
"""An explicit invalid value is non-empty, so the floor is NOT applied: the
|
||||
value is validated and dropped to '' (never-break ORCH-41), even though the
|
||||
developer floor (xhigh) exists."""
|
||||
monkeypatch.setattr(settings, "agent_effort_default", "")
|
||||
monkeypatch.setattr(settings, "agent_effort_developer", "turbo")
|
||||
assert resolve_agent_effort("developer") == ""
|
||||
|
||||
|
||||
# ---- TC-04: priority preserved — explicit config beats floor (FR-2) ---------
|
||||
def test_explicit_env_beats_floor(monkeypatch):
|
||||
"""Operator may deliberately downgrade developer to high; the explicit
|
||||
non-empty env wins over the xhigh floor."""
|
||||
monkeypatch.setattr(settings, "agent_effort_developer", "high")
|
||||
assert resolve_agent_effort("developer") == "high"
|
||||
|
||||
|
||||
def test_default_beats_floor(monkeypatch):
|
||||
"""A non-empty global default wins over the per-role floor (floor is strictly
|
||||
below default): default=max with empty per-agent -> max, not the xhigh floor."""
|
||||
monkeypatch.setattr(settings, "agent_effort_developer", "")
|
||||
monkeypatch.setattr(settings, "agent_effort_default", "max")
|
||||
assert resolve_agent_effort("developer") == "max"
|
||||
|
||||
|
||||
def test_project_override_beats_floor(monkeypatch):
|
||||
monkeypatch.setattr(settings, "agent_effort_developer", "")
|
||||
_install_registry(monkeypatch, {"developer": "high"})
|
||||
assert resolve_agent_effort("developer", ORCH_PLANE_ID) == "high"
|
||||
|
||||
|
||||
# ---- TC-05: xhigh is a valid effort (FR-5) ----------------------------------
|
||||
def test_xhigh_is_valid():
|
||||
assert "xhigh" in VALID_EFFORTS
|
||||
# developer canonical xhigh resolves (is not dropped by validation)
|
||||
assert resolve_agent_effort("developer") == "xhigh"
|
||||
|
||||
|
||||
# ---- flag assembly (mirror of launcher cmd construction) --------------------
|
||||
def _build_flags(model, effort, fb):
|
||||
model_flag = f"--model {model} " if model else ""
|
||||
@@ -111,6 +180,7 @@ def _build_flags(model, effort, fb):
|
||||
return f"{model_flag}{effort_flag}{fb_flag}"
|
||||
|
||||
|
||||
# ---- TC-06: flag assembly (AC-3) --------------------------------------------
|
||||
def test_flags_present_when_configured(monkeypatch):
|
||||
monkeypatch.setattr(settings, "agent_fallback_model", "claude-sonnet-4-6")
|
||||
model = resolve_agent_model("developer")
|
||||
@@ -118,21 +188,32 @@ def test_flags_present_when_configured(monkeypatch):
|
||||
fb = settings.agent_fallback_model
|
||||
flags = _build_flags(model, effort, fb)
|
||||
assert "--model claude-opus-4-8 " in flags
|
||||
assert "--effort high " in flags
|
||||
assert "--effort xhigh " in flags
|
||||
assert "--fallback-model claude-sonnet-4-6 " in flags
|
||||
|
||||
|
||||
def test_flags_absent_when_empty(monkeypatch):
|
||||
def test_flags_effort_per_role(monkeypatch):
|
||||
"""developer -> --effort xhigh; tester -> --effort medium (mirrors _spawn)."""
|
||||
assert "--effort xhigh " in _build_flags("", resolve_agent_effort("developer"), "")
|
||||
assert "--effort medium " in _build_flags("", resolve_agent_effort("tester"), "")
|
||||
|
||||
|
||||
def test_flags_absent_when_effort_empty():
|
||||
"""When the resolved effort is empty, --effort is omitted entirely. Mirrors the
|
||||
`f"--effort {effort} " if effort else ""` branch in _spawn (AC-3 negative case)."""
|
||||
flags = _build_flags("", "", "")
|
||||
assert flags == ""
|
||||
assert "--effort" not in flags
|
||||
|
||||
|
||||
def test_flags_absent_when_model_empty(monkeypatch):
|
||||
monkeypatch.setattr(settings, "agent_model_default", "")
|
||||
monkeypatch.setattr(settings, "agent_model_developer", "")
|
||||
monkeypatch.setattr(settings, "agent_effort_default", "")
|
||||
monkeypatch.setattr(settings, "agent_effort_developer", "")
|
||||
monkeypatch.setattr(settings, "agent_fallback_model", "")
|
||||
model = resolve_agent_model("developer")
|
||||
effort = resolve_agent_effort("developer")
|
||||
fb = settings.agent_fallback_model
|
||||
flags = _build_flags(model, effort, fb)
|
||||
flags = _build_flags(model, "", fb)
|
||||
assert flags == ""
|
||||
assert "--model" not in flags
|
||||
assert "--effort" not in flags
|
||||
assert "--fallback-model" not in flags
|
||||
assert "--fallback-model" not in flags
|
||||
|
||||
Reference in New Issue
Block a user