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>
18 KiB
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-determinization-roadmap.md.
0. Три ортогональных факта (как читать карту)
Карта явно разводит три раздельных факта — их смешение было корнем блокеров R3→R5:
- Ось 1 — consultation ≠ transport/slot. «LLM-консультация» = точка, где решение/артефакт
конвейера потребляет суждение LLM. Транспорт (
_spawn) — реализация, не определение. Слот агента (job-ролиD1/D2) делает site LLM-capable, но консультация гейтится потоком управления (перехват до_spawn) → capability ≠ consultation. - Ось 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 не входит.
- (C) control-path — LLM эмитит machine-verdict, на котором ветвится
- Ось 3 — деривируемость вердикта. Вердикт C-консультации либо есть детерминированная функция
tool-сигналов (exit-code
pytest/smoke/staging_check.py/деплоя), которые оркестратор уже вычисляет сам, либо требует настоящего суждения, не сводимого к exit-коду.
Avoidable LLM control path (нормативное определение —
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-determinization-roadmap.md.