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>
8.5 KiB
LLM determinization roadmap (ORCH-118)
Что это. Упорядоченный план детерминированных замен avoidable LLM control paths (
{tester, deployer}— см.llm-call-sites.md). Это транзиентный план: он обновляется по мере закрытия follow-up'ов. ORCH-118 раннеры не реализует (FR-7); старт каждого кандидата гейтится утверждением карты.Кандидаты названы ПО РОЛИ. Конкретные follow-up Plane-ID не фиксируются ни в одном артефакте (R3 / NFR-6): этих work item нет в подтверждённом backlog; ID присваивается при заведении задачи. Анти-фабрикация прибита тестом
TC-11(tests/test_llm_determinization_docs.py).Оценки экономии — «оценка до фактического замера» (NFR-5). Источник — существующие колонки
agent_runs(model/effort/токены/стоимость/время); точные числа снимаются при заведении follow-up'а черезGET /metrics/ запрос кagent_runs. Здесь — порядок величины, а не контракт.
1. Рекомендованный первый срез — deployer (staging-status) — ✅ РЕАЛИЗОВАН (ORCH-115)
Статус: реализовано. Срез выполнен в ORCH-115 —
src/staging_runner.py(перехват вlaunch_jobдо_spawn, какD1/D2): на стадииdeploy-stagingдля self-hostingorchestratorвердиктstaging_status:производит детерминированный код (маппинг exit-кодаstaging_check.pyчерезself_deploy.map_exit_code_to_status), а не LLM. Под kill-switchstaging_runner_enabled+ скоупstaging_runner_repos(пусто → self-hosting only); LLM-ветвь остаётся fallback'ом. Контракт артефакта/гейтаcheck_staging_status/STAGE_TRANSITIONS/схема БД — не тронуты. Детали —docs/work-items/ORCH-115/06-adr/ADR-001-deterministic-staging-runner.md, сквознойdocs/architecture/adr/adr-0048-deterministic-staging-runner.md. Записьrank 1в машинном блоке §4 сохраняется (first_slice = yes, инвариант карты) как историческая фиксация первого среза.
Обоснование (самый низкорисковый «чисто деривируемый» control path):
- Деривируемость максимальна. Вердикт
staging_status:— чистый маппинг exit-кодаscripts/staging_check.py; готовый leafsrc/staging_verdict.py::compute_staging_verdict(ORCH-061) уже считает этот вердикт детерминированно. - Прод уже детерминирован. Ребро
deploy(deploy_status:) для self-hostingorchestratorпроизводит детерминированный finalizer (Phase A/B/C, ORCH-036) — LLM в критическом self-restart-пути нет. Срез не трогает критический путь → минимальная поверхность риска. - Опирается на прецедент
D1/D2(launch_job-перехват до_spawn) — архитектурный риск замены агента уже снят рабочим паттерном. replace-deterministic-now, без hybrid-fallback (в отличие от tester).
2. Второй кандидат — tester (гибрид) — ✅ РЕАЛИЗОВАН (ORCH-116)
Статус: реализовано. Срез выполнен в ORCH-116 —
src/test_runner.py(перехват вlaunch_jobдо_spawn, какD1/D2/ORCH-115): на стадииtestingдля self-hostingorchestratorвердиктresult:производит детерминированный код (exit-кодpytest tests/в worktree ветки + read-only smoke/health//status//queue+serial_gate, маппинг черезself_deploy.map_exit_code_to_statusв токенахPASS/FAIL), а не LLM. Под kill-switchtest_runner_enabled+ скоупtest_runner_repos(пусто → self-hosting only) + резолв тест-контракта (репо без контракта → LLM-tester, fail-safe). Контракт артефакта/гейтаcheck_tests_passed/STAGE_TRANSITIONS/схема БД — не тронуты. Записьrank 2в машинном блоке §4 сохраняется (first_slice = no,hybrid_needed = yes— инвариант карты) как фиксация второго среза.
Детерминированное ядро (pytest + smoke даёт PASS/FAIL по exit-коду) покрывает основной вердикт;
LLM-фолбэк сохраняется только на суждение, не сводимое к exit-коду: триаж падений и маппинг
TC ↔ критерии приёмки. Поэтому needs-hybrid-fallback, а не replace-deterministic-now: поверхность
суждения шире и объём работы больше. В Phase 1 (ORCH-116) детерминированное ядро вынесено в раннер;
off-control-path LLM-триаж (он не выносит и не переопределяет result:, не добавляет ребро
в STAGE_TRANSITIONS) зафиксирован как Phase 2 follow-up по роли и в этом срезе не реализуется.
3. Общие требования к каждому follow-up'у
Каждый кандидат при заведении задачи несёт: kill-switch + обратимость (паттерн
ORCH-022/027/043/089/090 — флаг *_enabled, пустой CSV *_repos → self-hosting only), скоуп-гард
(не трогать STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/схему БД), а замена-агента —
перехват в launch_job до _spawn (как D1/D2). Свежесть прецедента — инцидент-трек
ORCH-110/111/112/113/114/117 (единое детерминированное владение side-effectful путями).
4. Машинно-читаемый блок roadmap
Заголовок (
rank | role | dependencies | savings_estimate_source | security_risk | hybrid_needed | followup_type | first_slice) парситсяtests/test_llm_determinization_docs.py::test_tc07_*.followup_type— по роли, без Plane-ID. Ровно одинfirst_slice = yes.
| rank | role | dependencies | savings_estimate_source | security_risk | hybrid_needed | followup_type | first_slice |
|---|---|---|---|---|---|---|---|
| 1 | deployer | staging_verdict.compute_staging_verdict (ORCH-061) + launch_job pre-spawn precedent (D1/D2) | agent_runs (deployer rows; estimate pending GET /metrics) | low (prod already deterministic via Phase A/B/C ORCH-036) | no | deployer-replacement (staging-status mapping) | yes |
| 2 | tester | deterministic pytest+smoke core; LLM fallback for failure triage / TC-to-criteria mapping | agent_runs (tester rows; estimate pending GET /metrics) | medium (failure-triage judgment must stay correct) | yes | tester-hybrid (deterministic core + LLM fallback) | no |
5. Вне scope
reviewer— C, но keep (вердикт «приемлемость кода/решения» не деривируем): не в roadmap'е.analyst/architect/developer— P (artifact-producer, не control path): не в roadmap'е.- Реализация раннеров — отдельные follow-up задачи (по роли), стартуют после утверждения карты.
Связанные документы: llm-call-sites.md, llm-usage-policy.md.