Replace the LLM `deployer` agent on the `deploy-staging` stage (self-hosting orchestrator) with a deterministic staging-runner intercepted in launch_job BEFORE _spawn (the deploy-finalizer / post-deploy-monitor reserved-agent precedent). The runner executes the SAME staging suite, maps the exit-code to `staging_status:` via the existing self_deploy.map_exit_code_to_status contract, writes 15-staging-log.md, and initiates the UNCHANGED check_staging_status gate exactly as a finished LLM-deployer would. Invariant (NFR-1): this replaces only the *producer* of the artifact — the artifact contract, the gate / _parse_staging_status / check_staging_status name, STAGE_TRANSITIONS, the machine-verdict key `staging_status:` and the DB schema are byte-for-byte unchanged. Additive, under a kill-switch + repo-scope CSV, never-raise, fail-safe back to the LLM path. Two-level outcome (D5, anti ORCH-110): suite executed -> verdict -> advance (FAILED -> the existing deploy-staging -> development rollback + developer-retry, same as a FAILED LLM verdict); tool-error (suite did not execute) -> bounded DEFER -> fail-closed FAILED + alert on exhaustion (infra != code fault; never a silent advance / false green). First implemented slice of the LLM determinization roadmap (ORCH-118 A6, replace-deterministic-now). - New leaf src/staging_runner.py (never-raise; proc_group tree-kill + timeout) - launch_job intercept + _run_staging_runner_job (mirror _run_deploy_finalizer_job) - config: ORCH_STAGING_RUNNER_* keys (enabled/repos/timeout/infra-retry budget) - GET /queue staging_runner observability block - docs: llm-call-sites/roadmap/usage-policy (A6 implemented; machine blocks + single-transport invariant intact), deployer.md (LLM branch -> fallback), CLAUDE.md, CHANGELOG.md, overview (tech-pipeline/tech-agents/tech-quality-security), .env.example - tests/test_orch115_staging_runner.py (TC-01..TC-13); LLM anti-drift green (TC-14) Refs: ORCH-115 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
102 lines
9.2 KiB
Markdown
102 lines
9.2 KiB
Markdown
# LLM usage policy (ORCH-118)
|
||
|
||
> **Нормативный durable-документ.** Формулирует принцип использования LLM в оркестраторе, критерии
|
||
> «keep vs replace» через **control-path-ось**, и нормативное **определение «avoidable LLM control
|
||
> path»**. Применяется ко **всем** будущим правкам control-path'ов. Сопутствующие артефакты —
|
||
> карта [`llm-call-sites.md`](llm-call-sites.md) и roadmap [`llm-determinization-roadmap.md`](llm-determinization-roadmap.md).
|
||
|
||
---
|
||
|
||
## 1. Принцип
|
||
|
||
**LLM — только там, где нужно настоящее суждение.** Если решение/вердикт control-path'а есть
|
||
**детерминированная функция tool-сигналов**, которые оркестратор уже вычисляет (exit-code `pytest`,
|
||
smoke, `staging_check.py`, статус деплоя, наличие файлов, CI-статус), — оно должно приниматься
|
||
**детерминированно**, а не консультацией LLM. LLM сохраняется там, где требуется суждение, **не
|
||
сводимое** к tool-сигналу (анализ требований, архитектурное решение, написание кода, приемлемость
|
||
ревью).
|
||
|
||
Это защищает **автономность** (NFR-2): меньше точек, где недетерминизм/стоимость/латентность LLM
|
||
встроены в поток управления, и меньше класса инцидентов «LLM-агент принял решение, которое на деле
|
||
есть исполнение фиксированных команд и маппинг результата» (RCA-трек ORCH-110/111/112/113/114/117).
|
||
|
||
---
|
||
|
||
## 2. Три оси решения (ground-truth — код)
|
||
|
||
1. **consultation ≠ transport/slot.** «LLM консультируется» ⇔ решение/артефакт конвейера **потребляет
|
||
суждение LLM**. Существование транспорта (`_spawn`) или слота агента (job-роли с перехватом до
|
||
`_spawn`) — это **capability**, не консультация.
|
||
2. **control-path (C) ≠ artifact-producer (P)** — определяется **кодом-потребителем** вывода роли:
|
||
- **(C)** LLM эмитит machine-verdict, на котором **ветвится `check_*`-гейт** → суждение входит в
|
||
поток управления.
|
||
- **(P)** LLM производит артефакт, а продвижение решает **детерминированный гейт** независимо
|
||
(наличие файлов / CI) → суждение в control flow не входит.
|
||
3. **деривируемость вердикта** — вердикт C-консультации либо детерминированная функция tool-сигналов,
|
||
либо настоящее суждение, не сводимое к exit-коду.
|
||
|
||
---
|
||
|
||
## 3. Нормативное определение «avoidable LLM control path»
|
||
|
||
Это **двухбитный проверяемый предикат над `src/qg/checks.py`**, а не «удобство на глаз».
|
||
|
||
<!-- ORCH-118-AVOIDABLE-DEFINITION-BLOCK:START -->
|
||
Call-site является **avoidable LLM control path** тогда и только тогда, когда выполнены **оба** условия:
|
||
- **(i)** это **C (control-path)** консультация — её LLM-вердикт потребляется потоком управления
|
||
(`check_*`-гейт ветвится на нём: PASS → дальше / FAIL → откат);
|
||
- **(ii)** вердикт **деривируем** (derivable) из tool-сигналов, которые оркестратор уже вычисляет сам —
|
||
exit-code `pytest` / smoke / `staging_check.py` / статус деплоя.
|
||
|
||
Если оба условия выполнены, суждение LLM не добавляет информации → консультацию можно снять без потери
|
||
смысла (заменить детерминированным раннером или гибридом с LLM-фолбэком только на не-деривируемую часть).
|
||
<!-- ORCH-118-AVOIDABLE-DEFINITION-BLOCK:END -->
|
||
|
||
**Поимённый целевой набор** (сверен с кодом, прибит тестами TC-13/TC-14):
|
||
|
||
- **avoidable LLM control paths = `{tester, deployer}`** — C **и** вердикт деривируем
|
||
(`result:` = exit-code `pytest`+smoke; `staging_status:` = маппинг exit-кода `staging_check.py`).
|
||
- **`reviewer`** — C, но **keep**: вердикт «приемлемость кода/решения» **НЕ деривируем** из exit-кода
|
||
(настоящее суждение). Это control-path-но-keep, **не** avoidable.
|
||
- **`analyst` / `architect` / `developer`** — **не** control path (**P**, artifact-producer):
|
||
детерминированный гейт судит артефакт независимо.
|
||
|
||
---
|
||
|
||
## 4. Критерии решения: keep vs replace
|
||
|
||
| Ситуация (по осям §2) | Решение | Класс |
|
||
|-----------------------|---------|-------|
|
||
| **P** — artifact-producer (детерминированный гейт судит артефакт) | **keep** LLM | `keep-LLM` |
|
||
| **C**, вердикт **НЕ деривируем** (настоящее суждение) | **keep** LLM (назвать суждение) | `keep-LLM` |
|
||
| **C**, вердикт **деривируем**, замена безопасна сейчас | **replace** | `replace-deterministic-now` |
|
||
| **C**, вердикт деривируем, но замена позже / с предпосылками | **replace later** | `replace-later/risky` |
|
||
| **C**, ядро деривируемо, но часть требует суждения | **hybrid** (детерм. ядро + LLM-фолбэк) | `needs-hybrid-fallback` |
|
||
|
||
> **keep-LLM требует обоснования:** любая `keep-LLM`-запись обязана **назвать конкретное суждение**;
|
||
> для C-keep — явно зафиксировать **не-деривируемость** вердикта (почему не сводится к exit-коду).
|
||
|
||
---
|
||
|
||
## 5. Требование к новым/изменённым control-path'ам (норматив)
|
||
|
||
- **Обоснование против политики.** Любой **новый** или изменённый control-path, который консультирует
|
||
LLM, обязан в своём ADR обосновать это против настоящей политики: показать, что он **P** (artifact
|
||
judged independently) **или** **C с не-деривируемым** вердиктом. C-консультация с деривируемым
|
||
вердиктом — это `avoidable`; её ввод без обоснования reviewer ловит как finding ≥P1.
|
||
- **Reviewer-ось (как ORCH-079) — требование, не реализация гейта.** Политика **рекомендует**
|
||
reviewer'у проверять соответствие новых control-path'ов настоящей политике; ORCH-118 **не** вводит
|
||
новый Quality Gate (`QG_CHECKS`/`check_*` не меняются) — это нормативное требование процесса.
|
||
- **Норматив сопровождения.** Меняешь место вызова LLM или потребителя вердикта в `src/qg/checks.py` →
|
||
обнови карту [`llm-call-sites.md`](llm-call-sites.md) и эту политику **в том же PR** (анти-дрейф
|
||
держат TC-13/TC-14).
|
||
- **Единственный транспорт.** Единственный разрешённый транспорт LLM-консультации в `src/**` — это
|
||
`launcher._spawn` (S0). Ввод второго транспорта (новый `_spawn`, импорт `anthropic`/`openai`/иного
|
||
LLM-SDK, прямой HTTP Anthropic/Claude, второй model-invoking subprocess) запрещён без явного ADR;
|
||
прибито тестами TC-01/TC-12.
|
||
- **Реализованный срез (ORCH-115).** Снятие C-консультации с деривируемым вердиктом — это разрешённое
|
||
`replace-deterministic-now`, а не ввод новой LLM-консультации. ORCH-115 снял A6/staging-status:
|
||
детерминированный `src/staging_runner.py` производит `staging_status:` без `_spawn` (перехват до
|
||
него, как `D1`/`D2`) — раннер **LLM не зовёт** и **второй транспорт не вводит**, поэтому инвариант
|
||
«единственный транспорт S0» соблюдён (TC-12 зелёный). Это образец для последующих срезов roadmap'а.
|