ORCH-044 closes two blind spots that let a single de-authenticated agent
stall the shared queue for all projects:
P1 — preflight auth gate. `claude --version` answers even when logged out,
so version-only preflight was blind to auth. Adds a token-free, network-free
check of <AGENT_HOME>/.claude/.credentials.json: missing/unreadable/no-oauth
or an expired `claudeAiOauth.expiresAt` (epoch ms, vs now + skew) => preflight
FAIL; absent expiry => OK (no false positives). Result is cached on the same
preflight_cache_ttl. Post-factum safety net: launcher detects auth markers
("not logged in" / "/login" / "unauthorized" / 401) in the run log and resets
the preflight cache so the next tick re-evaluates auth. Auth failure is a gate,
not a transient — it does not spin the circuit breaker. Emergency toggle
ORCH_PREFLIGHT_CHECK_AUTH=false restores version-only behaviour.
P3 — empty log / no result-JSON => job failed. exit_code==0 with an empty or
JSON-less run log no longer counts as success: a separate result_ok flag gates
stage advance + usage comments, fires a Telegram alert, and routes the job
through the normal transient/permanent failure path (exit_code integrity in
agent_runs preserved).
Scope: P2 (--effort) is intentionally excluded and tracked in ORCH-50.
New settings: ORCH_PREFLIGHT_CHECK_AUTH, ORCH_CLAUDE_CREDENTIALS_PATH,
ORCH_AUTH_EXPIRY_SKEW_SECONDS. Docs updated (INFRA.md, internals.md, CHANGELOG).
Refs: ORCH-044
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
13 KiB
INFRA.md — инфраструктура и эксплуатация оркестратора
RUNBOOK. Топология, контейнеры, порты, переменные окружения, границы. Секреты тут НЕ хранятся — только дескрипторы. Реальные значения — в
.envна хосте.
Топология
host: mva154 (slin@82.22.50.71), network_mode: host
┌──────────────────────────────────────────────────────────────────────┐
│ orchestrator (PROD) :8500 env_file .env │
│ БД: ./data/orchestrator.db (обслуживает ВСЕ прод-проекты) │
│ │
│ orchestrator-staging (STAGING) :8501 env_file .env.staging │
│ БД: ./data/staging/orchestrator.db (изолирована, только sandbox) │
│ profile: staging — НЕ стартует обычным `docker compose up` │
└──────────────────────────────────────────────────────────────────────┘
│ webhooks │ git
▼ ▼
Plane (ag_proj) Gitea (localhost:3000)
/repos/<project> ← общий каталог репозиториев (host: /home/slin/repos)
Контейнеры
| Контейнер | Роль | Порт | env_file | БД (хост) | Старт |
|---|---|---|---|---|---|
orchestrator |
прод | 8500 | .env |
./data/orchestrator.db |
docker compose up -d |
orchestrator-staging |
staging / песочница | 8501 | .env.staging |
./data/staging/orchestrator.db |
docker compose --profile staging up -d orchestrator-staging |
Оба: network_mode: host, init: true (tini как PID 1 — reaping зомби, B-2), restart: unless-stopped.
Тома (volumes)
./data→/app/data(БД; у staging —./data/staging)/home/slin/repos→/repos(рабочие репозитории проектов)/var/run/docker.sock(для docker-операций деплоя)- claude-code, node,
~/.claude*(CLI агентов, ro) ~/.orchestrator-ssh→/root/.ssh(ro, деплой по ssh)
Переменные окружения (карта; значения — в .env)
| Переменная | Назначение |
|---|---|
ORCH_PLANE_API_URL / _TOKEN / _WORKSPACE_SLUG |
доступ к Plane API |
ORCH_PLANE_WEB_URL |
внешний (браузерный) web-URL Plane для кликабельных ссылок на issue в уведомлениях (ORCH-017); пусто → фолбэк на ORCH_PLANE_API_URL, loopback-фолбэк → ссылка опускается |
ORCH_PLANE_WEBHOOK_SECRET |
HMAC-проверка вебхуков Plane |
ORCH_GITEA_URL / _TOKEN / _WEBHOOK_SECRET |
доступ к Gitea + HMAC |
ORCH_CLAUDE_BIN |
путь к claude CLI |
ORCH_REPOS_DIR / ORCH_HOST_REPOS_DIR |
каталог репозиториев (в контейнере / на хосте) |
ORCH_DB_PATH |
путь к SQLite БД |
ORCH_PROJECTS_JSON |
реестр проектов (Plane id → repo + prefix); пусто → дефолт из src/projects.py |
ORCH_AGENT_MODEL_DEFAULT |
LLM-модель агентов по умолчанию (ORCH-41); дефолт claude-opus-4-8 |
ORCH_AGENT_MODEL_<AGENT> |
per-agent модель (ANALYST/ARCHITECT/DEVELOPER/REVIEWER/TESTER/DEPLOYER); пусто → default |
ORCH_AGENT_EFFORT_DEFAULT |
режим работы --effort по умолчанию (ORCH-41): low|medium|high|xhigh|max; дефолт high |
ORCH_AGENT_EFFORT_<AGENT> |
per-agent effort; дефолт: думающие → high, tester/deployer → medium |
ORCH_AGENT_FALLBACK_MODEL |
опц. фолбэк-модель при overloaded (--fallback-model); пусто → без флага |
ORCH_PREFLIGHT_CHECK_AUTH |
вкл/выкл token-free auth-проверку preflight (ORCH-044); дефолт true. Аварийный тумблер: false → preflight как до ORCH-044 (только --version) |
ORCH_CLAUDE_CREDENTIALS_PATH |
явный путь к .credentials.json (ORCH-044); пусто → <AGENT_HOME>/.claude/.credentials.json, где AGENT_HOME=/home/slin — HOME, под которым launcher реально спавнит claude (не HOME процесса орка) |
ORCH_AUTH_EXPIRY_SKEW_SECONDS |
запас на рассинхрон часов при сравнении claudeAiOauth.expiresAt (ORCH-044); дефолт 0 |
DEPLOY_SSH_USER / _HOST / DEPLOY_HOOK_SCRIPT |
параметры деплой-хука |
Секреты — только в .env / .env.staging на хосте, в гит НЕ коммитятся. Канон — .env.example, .env.staging.example.
Реестр проектов (src/projects.py, ORCH-6)
Связывает Plane project id → gitea repo + work-item prefix. Источник: ORCH_PROJECTS_JSON, fallback — встроенный дефолт. Прод видит: enduro-trails (ET), orchestrator (ORCH). Staging видит ТОЛЬКО orchestrator-sandbox (SANDBOX) — изоляция.
Модель и effort агентов (src/config.py + src/agents/launcher.py, ORCH-41)
Модель LLM и режим работы (--effort) каждого агента конфигурируемы — глобально per-agent (env) и per-project (через ORCH_PROJECTS_JSON).
Приоритет резолвинга (resolve_agent_model / resolve_agent_effort):
- per-project override —
agent_models/agent_effortsв записиORCH_PROJECTS_JSON; - per-agent env —
ORCH_AGENT_MODEL_<AGENT>/ORCH_AGENT_EFFORT_<AGENT>(если непусто); - глобальный дефолт —
ORCH_AGENT_MODEL_DEFAULT(claude-opus-4-8) /ORCH_AGENT_EFFORT_DEFAULT(high); - пусто → флаг не передаётся, действует дефолт CLI.
Значения effort: low < medium < high < xhigh < max — рычаг «качество vs стоимость/время». Дефолтная раскладка: думающие агенты (analyst/architect/developer/reviewer) → high, механические (tester/deployer) → medium. Невалидное значение → лог-warning, флаг опускается.
Per-project override в ORCH_PROJECTS_JSON (поля agent_models / agent_efforts опциональны, старые записи работают):
{"plane_project_id":"...","repo":"orchestrator","work_item_prefix":"ORCH",
"agent_models":{"developer":"claude-opus-4-8","reviewer":"claude-sonnet-4-6"},
"agent_efforts":{"developer":"xhigh","tester":"low"}}
⚠️ Бюджет (ORCH-38):
claude-opus-4-8дефолт в коде; реальное переключение прод-env делается отдельно после согласования.
Preflight auth-гейт (src/preflight.py, ORCH-044)
claude --version отвечает успешно даже когда claude разлогинен (версия — локальная инфа), поэтому до ORCH-044 preflight был слеп к авторизации: разлогиненный инстанс клеймил job и тихо умирал с пустым логом, блокируя общую очередь всех проектов.
ORCH-044 добавляет token-free проверку (без сети, без prompt-ping — BR-1):
- Проактивно (основной гейт): после успешного
--versionчитается<AGENT_HOME>/.claude/.credentials.json(путь —ORCH_CLAUDE_CREDENTIALS_PATHили дефолт отAGENT_HOME=/home/slin, не HOME процесса орка). Нет файла / битый JSON / нетclaudeAiOauth.accessToken⇒check()=(False, …).claudeAiOauth.expiresAt(epoch ms)<= now + ORCH_AUTH_EXPIRY_SKEW_SECONDS⇒ протух ⇒ FAIL. НетexpiresAt⇒ OK (не плодим ложные срабатывания). Результат кешируется тем жеORCH_PREFLIGHT_CACHE_TTL, что и--version. - Постфактум (защитная сетка): если агент всё же стартовал при протухшей сессии, launcher детектит маркер (
not logged in/please run /login/unauthorized/401) в run-логе и сбрасывает preflight-кеш, чтобы следующий тик переоценил auth. Auth-провал не считается transient и не крутит circuit breaker — гейт здесь preflight.
При auth=fail job не клеймится (_drain_once уже корректен при ok=False), reason виден в /queue (preflight_reason). Аварийный тумблер ORCH_PREFLIGHT_CHECK_AUTH=false возвращает version-only поведение.
⚠️ Риск ложноположительного auth-fail (R-1): неверный путь к credentials заблокирует клейм всех проектов (общая очередь). Митигация: единый источник
AGENT_HOME, тумблер, обязательная проверка на staging (8501) перед прод-деплоем. ADR —docs/work-items/ORCH-044/06-adr/ADR-001-preflight-auth-and-empty-result-failure.md.
ℹ️
--effort(P2) в ORCH-044 не трогается — вынесен в ORCH-50.
⚠️ Self-hosting — оркестратор дорабатывает САМ СЕБЯ
Факт: прод-инстанс orchestrator (8500) — ОДИН на ВСЕ прод-проекты (enduro-trails + orchestrator), с ОБЩЕЙ БД ./data/orchestrator.db и общей очередью задач (ORCH-1).
Следствие — групповой риск: когда орк выполняет задачу из проекта ORCH (дорабатывает себя), он бежит в том же инстансе, что обслуживает enduro-trails.
- Рестарт / падение прод-контейнера орк-задачей → конвейер ВСЕХ проектов встаёт.
- Кривой self-деплой (ORCH-36, Вариант B) → лежат все проекты сразу.
- Общая очередь → орк-задача занимает concurrency-слоты других проектов.
Что изолировано (безопасно):
- Staging (8501) — отдельная БД (
./data/staging), отдельный реестр (ORCH_PROJECTS_JSON= только sandbox). Прод-проекты не видит. - Репозитории разделены, изоляция веток через git worktree (ORCH-2).
Страховки:
- Стадия
deploy-staging(порт 8501) — обязательный гейт перед прод-деплоем орка. Прод-деплой недостижим, пока staging-гейт не зелёный (см.STAGING.md, ORCH-35). Гейт условный: реален только для self-hosting (repo=orchestrator), для остальных проектов — no-op.
Правила для агентов при задачах ORCH:
- НЕ перезапускать / не ронять прод-контейнер
orchestratorв рамках задачи. - Все проверки деплоя — на staging (8501), боевой 8500 не трогать.
- Деплой self — только через хук с health-check + авто-rollback (
DEPLOY_HOOK.md).
Эксплуатация (быстрые команды)
# статус
docker ps --filter name=orchestrator
curl -s http://localhost:8500/health
curl -s http://localhost:8500/status # активные задачи
curl -s http://localhost:8500/queue # очередь
# поднять staging-песочницу
docker compose --profile staging up -d orchestrator-staging
curl -s http://localhost:8501/health
# логи
docker logs --tail 100 orchestrator
RUNBOOK 2026-06-05. Обновлять при изменении топологии/портов/переменных. См. CONTRIBUTING.md §8.