Files
orchestrator/docs/architecture/llm-call-sites.md
claude-bot b50cf1dd08
All checks were successful
CI / test (push) Successful in 1m8s
CI / test (pull_request) Successful in 1m8s
feat(staging): deterministic staging-runner replacing LLM deployer on deploy-staging (ORCH-115)
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>
2026-06-16 01:59:43 +03:00

18 KiB
Raw Blame History

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. Ось 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): 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-brd04-test-plan check_analysis_complete:33 (наличие файлов) ~80200k / 520 мин да (через S0) P keep-LLM анализ требований / BRD/ТЗ — настоящее суждение; гейт судит лишь наличие артефактов
A2 .openclaw/agents/architect.md стадия architecture architect 06-adr/, 07 check_architecture_done:62 (наличие 06-adr/07) ~80200k / 520 мин да (через S0) P keep-LLM архитектурное решение / ADR — настоящее суждение
A3 .openclaw/agents/developer.md стадия development developer код + PR check_ci_green:82 (+ check_branch_mergeable:657) — CI/merge ~150400k / 1040 мин да (через S0) P keep-LLM написание кода — настоящее суждение; гейт судит CI/merge, не самоотчёт
A4 .openclaw/agents/reviewer.md стадия review reviewer 12-review.md verdict: check_reviewer_verdict:336 (verdict:) ~100300k / 525 мин да (через 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:) ~60150k / 520 мин да (через 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:) ~40120k / 315 мин да (через 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.