# LLM call-site map — inventory, control-path axis & classification (ORCH-118) > **Что это.** Доказательная карта **каждого места**, где control-path оркестратора > потребляет (или способен потребить) суждение LLM. Единица инвентаря — **LLM-консультация** > (control-path потребляет суждение LLM), **не** «спавн процесса / существование Claude CLI» > (capability ≠ consultation, BRD §0 / R4). > > **Снимок, прибитый к коду.** Карта — *снимок*; её инварианты держат структурные тесты > `tests/test_llm_call_site_inventory.py` (анти-дрейф). Меняешь место вызова LLM или потребителя > вердикта в `src/qg/checks.py` → **обнови эту карту и политику в том же PR** (норматив сопровождения). > > **Источник истины** содержательной классификации — ADR > `docs/work-items/ORCH-118/06-adr/ADR-001-llm-call-site-map-and-determinization-roadmap.md` > (D2/D3/D4) и сквозной `docs/architecture/adr/adr-0047-llm-usage-policy-and-call-site-map.md`. > Нормативное определение «avoidable LLM control path» и критерии keep/replace — в > [`llm-usage-policy.md`](llm-usage-policy.md); порядок замен — в > [`llm-determinization-roadmap.md`](llm-determinization-roadmap.md). --- ## 0. Три ортогональных факта (как читать карту) Карта **явно** разводит три раздельных факта — их смешение было корнем блокеров R3→R5: 1. **Ось 1 — consultation ≠ transport/slot.** «LLM-консультация» = точка, где решение/артефакт конвейера **потребляет суждение LLM**. Транспорт (`_spawn`) — реализация, не определение. Слот агента (job-роли `D1`/`D2`) делает site LLM-**capable**, но консультация гейтится потоком управления (перехват **до** `_spawn`) → **capability ≠ consultation**. 2. **Ось 2 — control-path (C) ≠ artifact-producer (P).** Определяется **кодом-потребителем** вывода роли в `src/qg/checks.py`: - **(C) control-path** — LLM эмитит machine-verdict, на котором **ветвится `check_*`-гейт** (PASS → дальше / FAIL → откат). Суждение LLM **входит** в поток управления. - **(P) artifact-producer** — LLM производит артефакт, а продвижение решает **детерминированный гейт**, судящий артефакт **независимо** (наличие файлов / CI-статус). Суждение LLM в control flow **не входит**. 3. **Ось 3 — деривируемость вердикта.** Вердикт C-консультации либо есть **детерминированная функция tool-сигналов** (exit-code `pytest`/smoke/`staging_check.py`/деплоя), которые оркестратор **уже вычисляет сам**, либо требует **настоящего суждения**, не сводимого к exit-коду. > **Avoidable LLM control path** (нормативное определение — [`llm-usage-policy.md`](llm-usage-policy.md)): > call-site, для которого выполнены **оба** условия — **(i)** это C-консультация (её LLM-вердикт > потребляется потоком управления) **и** **(ii)** вердикт **деривируем** из tool-сигналов. Тогда > суждение LLM не добавляет информации → консультацию можно снять без потери смысла. --- ## 1. Инвентарь LLM-консультаций (полный, привязан к коду) Каждая запись несёт: `id` · `location (file:line)` · `trigger` · `stage/owner` · `output artifact` · `machine-verdict key` · `output consumer` (кто потребляет вывод роли) · `est. tokens/runtime` (оценка из `agent_runs`) · `consults-LLM` · `axis` (C/P) · `classification` · `rationale`. | id | location (file:line) | trigger | stage / owner | output artifact | machine-verdict key | output consumer (`src/qg/checks.py`) | est. tokens/runtime (оценка) | consults-LLM | axis | classification | rationale | |----|----------------------|---------|---------------|-----------------|---------------------|--------------------------------------|------------------------------|--------------|------|----------------|-----------| | **S0** | `src/agents/launcher.py:472` (`_spawn`; CLI-сборка `610-614`; парс токенов `_monitor_agent:838`) | `launch_job` → `_spawn` для любой из 6 ролей | — (транспорт) | — | — | — | — | **транспорт** (capability) | — | — | Единственный транспорт LLM-консультации в `src/**`; не call-site решения | | **A1** | `.openclaw/agents/analyst.md` | стадия `analysis` | analyst | `01-brd` … `04-test-plan` | — | `check_analysis_complete:33` (наличие файлов) | ~80–200k / 5–20 мин | да (через S0) | **P** | `keep-LLM` | анализ требований / BRD/ТЗ — настоящее суждение; гейт судит лишь наличие артефактов | | **A2** | `.openclaw/agents/architect.md` | стадия `architecture` | architect | `06-adr/`, `07` | — | `check_architecture_done:62` (наличие 06-adr/07) | ~80–200k / 5–20 мин | да (через S0) | **P** | `keep-LLM` | архитектурное решение / ADR — настоящее суждение | | **A3** | `.openclaw/agents/developer.md` | стадия `development` | developer | код + PR | — | `check_ci_green:82` (+ `check_branch_mergeable:657`) — CI/merge | ~150–400k / 10–40 мин | да (через S0) | **P** | `keep-LLM` | написание кода — настоящее суждение; гейт судит CI/merge, не самоотчёт | | **A4** | `.openclaw/agents/reviewer.md` | стадия `review` | reviewer | `12-review.md` | `verdict:` | `check_reviewer_verdict:336` (`verdict:`) | ~100–300k / 5–25 мин | да (через S0) | **C** | `keep-LLM` | control path, но вердикт «приемлемость кода/решения» **НЕ деривируем** из exit-кода — настоящее суждение | | **A5** | `.openclaw/agents/tester.md` | стадия `testing` | tester | `13-test-report.md` | `result:` | `check_tests_passed:182` → `_parse_tests_verdict:226` (`result:`) | ~60–150k / 5–20 мин | да (через S0) | **C** | `needs-hybrid-fallback` | **avoidable**: PASS/FAIL = exit-code `pytest`+smoke (деривируем); LLM нужен лишь на триаж падений / маппинг TC↔критерии | | **A6** | `.openclaw/agents/deployer.md` | стадии `deploy-staging` / `deploy` | deployer | `15-staging-log.md` / `14-deploy-log.md` | `staging_status:` / `deploy_status:` | `check_staging_status:599` → `_parse_staging_status:538` (`staging_status:`); `check_deploy_status:473` → `_parse_deploy_status:413` (`deploy_status:`) | ~40–120k / 3–15 мин | да (через S0; для in-scope репо на `deploy-staging` — **нет**, перехват до `_spawn`) | **C** | `replace-deterministic-now` | **avoidable, СРЕЗ РЕАЛИЗОВАН (ORCH-115):** на `deploy-staging` для self-hosting `orchestrator` вердикт `staging_status:` производит детерминированный `src/staging_runner.py` (перехват в `launch_job` до `_spawn`, как D1/D2) — маппинг exit-кода `staging_check.py`; прод уже детерминирован Phase A/B/C (ORCH-036). LLM-ветвь остаётся fallback'ом под выключенным флагом / для не-self репо | | **D1** | `src/agents/launcher.py:389` (перехват в `launch_job` **до** `_spawn`; «Not an LLM spawn» `407`) | post-deploy edge | deploy-finalizer | jobs-row | — | — | — (детерминированный) | **нет** (слот, перехват до `_spawn`) | — | `already-deterministic` (эталон) | Занимает слот агента, но LLM не консультируется — рабочий прецедент замены | | **D2** | `src/agents/launcher.py:394` (перехват в `launch_job` **до** `_spawn`; «Not an LLM spawn» `428`) | post-deploy observation | post-deploy-monitor | jobs-row | — | — | — (детерминированный) | **нет** (слот, перехват до `_spawn`) | — | `already-deterministic` (эталон) | Тик наблюдения; LLM не консультируется | > **Итог (поимённо).** `avoidable LLM control paths = {tester, deployer}`; control-path-но-keep = > `{reviewer}`; не-control-path (P) = `{analyst, architect, developer}`; already-deterministic-эталон = > `{deploy-finalizer, post-deploy-monitor}`. ### 1.1 Машинно-читаемый блок инвентаря > Стабильный заголовок таблицы (`id | role | location | output_consumer | consults_llm | axis | > avoidable | classification`) парсится `tests/test_llm_call_site_inventory.py` (split по `|`, без > новых зависимостей) и сверяется с кодом (TC-03/04/05/13/14). **Не менять заголовок и значения без > синхронной правки кода/тестов.** | id | role | location | output_consumer | consults_llm | axis | avoidable | classification | |----|------|----------|-----------------|--------------|------|-----------|----------------| | S0 | _spawn | src/agents/launcher.py:472 | - | transport | - | - | - | | A1 | analyst | .openclaw/agents/analyst.md | check_analysis_complete | yes | P | no | keep-LLM | | A2 | architect | .openclaw/agents/architect.md | check_architecture_done | yes | P | no | keep-LLM | | A3 | developer | .openclaw/agents/developer.md | check_ci_green | yes | P | no | keep-LLM | | A4 | reviewer | .openclaw/agents/reviewer.md | check_reviewer_verdict | yes | C | no | keep-LLM | | A5 | tester | .openclaw/agents/tester.md | _parse_tests_verdict | yes | C | yes | needs-hybrid-fallback | | A6 | deployer | .openclaw/agents/deployer.md | _parse_staging_status | yes | C | yes | replace-deterministic-now | | D1 | deploy-finalizer | src/agents/launcher.py:389 | - | no | - | - | already-deterministic | | D2 | post-deploy-monitor | src/agents/launcher.py:394 | - | no | - | - | already-deterministic | ### 1.2 keep-LLM — названное суждение (обоснование) > Для каждой `keep-LLM`-записи назван **конкретный** вид суждения, ради которого LLM сохраняется. > Для C-keep (`reviewer`) обоснование явно фиксирует **НЕ-деривируемость** вердикта (почему не сводится > к exit-коду). Парсится TC-05 (`- role: текст`). - analyst: анализ требований и написание BRD/ТЗ — настоящее суждение; детерминированный гейт `check_analysis_complete` судит лишь наличие файлов, не их содержательное качество. - architect: архитектурное решение и ADR — настоящее суждение о компромиссах/инвариантах; `check_architecture_done` судит лишь наличие 06-adr/07. - developer: написание кода — настоящее суждение; гейт `check_ci_green` судит CI/merge, а не самоотчёт агента. - reviewer: «приемлемость кода/решения» — настоящее суждение, которое НЕ деривируемо (not derivable) из exit-кода `pytest`/smoke/деплоя; в отличие от tester/deployer вердикт reviewer'а не сводится к tool-сигналу, поэтому это control-path-но-keep, а не avoidable. --- ## 2. Таксономия классификации (4 класса, выведена из осей) Четыре взаимоисключающих класса; класс **выводится** из осей §0 (а не постулируется): - **`keep-LLM`** — нужно настоящее суждение (обязательно **назвать** конкретное суждение, §1.2). - **`replace-deterministic-now`** — безопасная детерминированная замена сейчас. - **`replace-later/risky`** — замена возможна позже / с предпосылками. - **`needs-hybrid-fallback`** — детерминированное ядро + LLM-фолбэк только на суждение. **Правило вывода:** `P → keep-LLM`; `C + не-деривируемый вердикт → keep-LLM`; `C + деривируемый вердикт → replace-* / needs-hybrid-fallback` (**= avoidable**). `deploy-finalizer`/`post-deploy-monitor` помечены `already-deterministic` — вне таксономии замен (эталон: LLM не консультируется, перехват до `_spawn`). --- ## 3. Детерминизм не-агентских control-path'ов (доказательство, FR-3 / AC-3) Эти пути **не консультируют LLM** (ни через `_spawn`-транспорт, ни альтернативным транспортом). ⚠️ Они **спавнят subprocess'ы** (`git`/`pytest`/`docker`/`ssh`/сканеры/`staging_check.py`) — это детерминированные **инструменты**, не LLM: доказательство детерминизма — **отсутствие *LLM*-транспорта**, а не отсутствие *subprocess* (дискриминатор §0). Проверяется TC-02. | Путь / модуль | `file:line` (якорь) | Природа | |---------------|---------------------|---------| | Маршрутизация стадий | `src/stages.py::STAGE_TRANSITIONS:12`, `advance_stage` в `src/stage_engine.py` | статический словарь + детерминированная функция | | Реестр Quality Gate | `src/qg/checks.py::QG_CHECKS:812` (14 имён) | словарь имя→функция | | Все `check_*` | `src/qg/checks.py` (`33/62/82/182/336/473/599/657`, …) | файловые/HTTP/exit-code проверки | | Парсеры вердиктов `_parse_*` | `src/qg/checks.py:226/413/538` через `src/frontmatter.py::parse_frontmatter` | YAML-frontmatter парс (читают, **не производят** вердикт) | | Классификатор ошибок | `src/error_classifier.py` | regex по строкам | | Под-гейты | `src/{security_gate,merge_gate,coverage_gate,staging_verdict}.py` | сканеры/`pytest --cov`/git/маппинг exit-кодов | | Self-deploy Phase A/B/C | `src/self_deploy.py` | детерминированный detached-деплой (ORCH-036) | | Сериализация / владение | `src/{serial_gate,transition_lease,reconciler,job_reaper}.py` | FIFO-гейт / durable-lease / CAS / reaper | Любая найденная **неожиданная** LLM-консультация в этих путях добавляется в инвентарь §1 и классифицируется §2 (тогда TC-02 станет красным — точка дрейфа). --- ## 4. Скоуп и анти-дрейф - **Docs + tests only (ORCH-118).** `STAGE_TRANSITIONS` / реестр и имена `QG_CHECKS`/`check_*` / machine-verdict-ключи (`verdict:`/`result:`/`staging_status:`/`deploy_status:`/`security_status:`/ `coverage_status:`) / схема БД — **байт-в-байт не тронуты** (это инвариант самой карты). - **Реализованные срезы.** Первый срез roadmap'а — **deployer (staging-status)** — реализован **ORCH-115** (`src/staging_runner.py`, перехват в `launch_job` до `_spawn`): на `deploy-staging` для in-scope репо вердикт `staging_status:` производит детерминированный код, не LLM. Это `replace-deterministic-now` без ввода второго транспорта (раннер LLM не зовёт) — карта/инвариант «единственный транспорт S0» соблюдены. Машинный `ORCH-118-INVENTORY-BLOCK` сохраняет deployer как `avoidable=yes`/`axis=C` (LLM-ветвь жива как fallback под выключенным флагом / для не-self репо). Второй кандидат (`tester`) остаётся follow-up'ом по роли. - **Анти-дрейф тесты:** `tests/test_llm_call_site_inventory.py` (TC-01…TC-06, TC-09, TC-12, TC-13, TC-14) и `tests/test_llm_determinization_docs.py` (TC-07/08/11). Дискриминатор всех проверок — **«консультирует LLM», а не «спавнит subprocess»**. - **Связанные документы:** [`llm-usage-policy.md`](llm-usage-policy.md), [`llm-determinization-roadmap.md`](llm-determinization-roadmap.md).