Files
orchestrator/docs/architecture/llm-call-sites.md
claude-bot 9d16ee473a feat(testing): deterministic test-runner replacing LLM tester on the testing stage (ORCH-116)
Second realised slice of the determinization-roadmap (ORCH-118 A5,
needs-hybrid-fallback): on the `testing` stage for the self-hosting
`orchestrator` repo the LLM `tester` agent is replaced by a deterministic
test-runner (src/test_runner.py), intercepted in launch_job BEFORE _spawn
(deploy-finalizer / post-deploy-monitor / staging-runner precedent).

It runs the regression `python -m pytest <target>` in the task worktree via
proc_group (tree-kill) + an optional read-only smoke (/health, /status, /queue
+ serial_gate), maps the exit-code -> result: PASS|FAIL via the existing
self_deploy.map_exit_code_to_status contract, writes 13-test-report.md and
initiates the EXISTING check_tests_passed gate exactly as a finished LLM-tester.

Invariant (NFR-1): only the *producer* changes — the artifact contract
(13-test-report.md / result:), the gate check_tests_passed / _parse_tests_verdict,
STAGE_TRANSITIONS and the DB schema are byte-for-byte UNCHANGED. Additive, under
a kill-switch (test_runner_enabled), never-raise, fail-closed, self-hosting scope,
two-level outcome (tool-error DEFER, anti ORCH-110), hybrid (LLM strictly
off-control-path). 52c-`status:` is aligned with the verdict (D6.1) so the
three-field _parse_tests_verdict never false-negatives a PASS.

Docs (ORCH-118 NFR-6, atomic with code): llm-call-sites.md (A5 implemented),
llm-determinization-roadmap.md (rank 2 implemented), llm-usage-policy.md,
README/internals/overview, tester.md, CLAUDE.md, CHANGELOG.md. Coverage:
tests/test_orch116_test_runner.py (TC-01..TC-14); LLM anti-drift tests green.
Full suite: 2137 passed.

Refs: ORCH-116
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 09:37:40 +03:00

19 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; для in-scope репо на testingнет, перехват до _spawn) C needs-hybrid-fallback avoidable, СРЕЗ РЕАЛИЗОВАН (ORCH-116): на testing для self-hosting orchestrator (репо с тест-контрактом) вердикт result: производит детерминированный src/test_runner.py (перехват в launch_job до _spawn, как D1/D2) — exit-код pytest в worktree + read-only smoke. LLM-ветвь остаётся fallback'ом под выключенным флагом / для репо без контракта / как будущий off-control-path триаж падений (маппинг TC↔критерии), который НЕ выносит и НЕ переопределяет result: (гибрид-природа needs-hybrid-fallback сохранена)
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 (result:) — реализован ORCH-116 (src/test_runner.py, тем же перехватом до _spawn): на testing для in-scope репо вердикт result: производит детерминированный код (exit-код pytest в worktree + read-only smoke), не LLM. Это needs-hybrid-fallback, тоже без второго транспорта (раннер LLM не зовёт). Машинный ORCH-118-INVENTORY-BLOCK сохраняет tester как avoidable=yes/axis=C/ classification=needs-hybrid-fallback — LLM-ветвь жива как fallback (выключенный флаг / репо без контракта) и как будущий off-control-path триаж, который не выносит result:.
  • Анти-дрейф тесты: 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.