diff --git a/tasks/orchestrator/DEV_TASK_ORCH1_QUEUE.md b/tasks/orchestrator/DEV_TASK_ORCH1_QUEUE.md index 85a1aa0..5477645 100644 --- a/tasks/orchestrator/DEV_TASK_ORCH1_QUEUE.md +++ b/tasks/orchestrator/DEV_TASK_ORCH1_QUEUE.md @@ -151,6 +151,57 @@ queue_poll_interval: float = 2.0 # ORCH-1: worker poll seconds --- +## ДОПОЛНЕНИЕ (от Славы, 2026-06-02): preflight + 429/rate-limit + backoff + circuit breaker + +⚠️ **Важное расширение надёжности.** Сейчас агент падает при недоступности CLI/429 как обычный фейл — это неправильно. Два РАЗНЫХ класса проблем, лечатся по-разному. + +### A. Дешёвый preflight (CLI/сеть доступны?) — НЕ жжёт токены +- Перед тем как воркер claim'ит job — дешёвая проверка (`src/preflight.py` или функция в launcher): + - `os.path.exists(CLAUDE_BIN)` — мгновенно + - `claude --version` (subprocess, timeout 5с) — токены НЕ тратит + - (опционально) TCP-connect до endpoint Tokenator — «порт жив?», бесплатно +- **Кэшировать результат ~30-60с** (не дёргать на каждый тик). Конфиг: `preflight_cache_ttl: int = 45`. +- Если preflight FAIL → воркер **НЕ claim'ит job** (остаётся `queued`), логирует, ждёт следующий тик. Никто не падает впустую. +- 🚫 **НЕ делать prompt-ping (ping→pong) перед каждым job** — это трата лимита и латентность. Только local-проверки. + +### B. 429/rate-limit — детектить НА ВЫХОДЕ, не предсказывать +- Rate limit НЕЛЬЗЯ надёжно предсказать заранее — ловить по результату прогона. +- В `_monitor_agent` после завершения: распарсить log/stderr на паттерны: `429`, `rate limit`, `overloaded`, `rate_limit_error`, `Retry-After`, `quota`. Классификатор в `src/error_classifier.py` (или функция): вернёт `transient` (429/overload/сеть) или `permanent` (code-fault). +- **Разные ветки ретраев:** + - `transient` (429/недоступность) → backoff-ретрай, attempts НЕ инкрементить как code-fault (или отдельный счётчик `transient_attempts` с большим лимитом, напр. 5) + - `permanent` (code-fault) → обычные attempts < max_attempts (2), потом `failed` + +### C. Backoff + `available_at` в очереди +- Добавить в таблицу `jobs` колонку `available_at TEXT` (когда job снова можно брать) + `transient_attempts INTEGER DEFAULT 0`. +- `claim_next_job` → `WHERE status='queued' AND (available_at IS NULL OR available_at <= datetime('now'))`. +- При transient-фейле: `available_at = now + backoff`. **Exponential backoff:** напр. `min(2^transient_attempts * base, max_backoff)`, base=10с, max=600с. **Уважать `Retry-After`** если сервер прислал (распарсить из лога, если есть). + +### D. Circuit breaker (рубильник) +- Если подряд N (напр. 3) job падают с transient/недоступностью → **открыть breaker**: воркер паузит на M минут (напр. 5), ВООБЩЕ не дёргает CLI (не тратит попытки/лимит), шлёт Telegram-алерт. +- Через M мин → **half-open**: пробует ОДИН job. Ожил (exit 0) → закрыть breaker. Опять transient → снова пауза. +- Состояние breaker в памяти воркера (или лёгкая таблица/файл) + отражать в `/queue`. +- Конфиг: `breaker_threshold: int = 3`, `breaker_pause_seconds: int = 300`. + +### Конфиг (добавить в config.py) +```python +preflight_cache_ttl: int = 45 # кэш дешёвого preflight, сек +backoff_base_seconds: int = 10 # transient backoff base +backoff_max_seconds: int = 600 # потолок backoff +transient_max_attempts: int = 5 # ретраи для 429/недоступности +breaker_threshold: int = 3 # сколько transient подряд до открытия +breaker_pause_seconds: int = 300 # пауза при открытом breaker +``` + +### Доп-тесты (в test_queue.py или test_resilience.py) +- preflight FAIL → job остаётся queued, не спавнится +- preflight кэш (не дёргает `claude --version` чаще ttl) +- классификатор: лог с "429"/"rate limit"/"overloaded" → transient; обычная ошибка → permanent +- transient fail → `available_at` в будущем, claim его не берёт пока не наступит время +- backoff растёт экспоненциально; Retry-After уважается +- breaker: 3 transient подряд → open (воркер паузит, CLI не дёргается); half-open → ожил → closed + +--- + ## Acceptance (проверит Стрим) | # | Проверка | Ожидаемо | @@ -164,6 +215,10 @@ queue_poll_interval: float = 2.0 # ORCH-1: worker poll seconds | 7 | тесты | new green, 9 pre-existing не тронуты | | 8 | `/queue` | counts + последние jobs | | 9 | B-1/B-2/ORCH-2/ORCH-6 | не сломаны | +| 10 | preflight | local-only, кэш, не жжёт токены; FAIL → job queued, не падает | +| 11 | 429-классификатор | transient vs permanent разделены | +| 12 | backoff | transient → available_at в будущем, exp backoff, Retry-After | +| 13 | circuit breaker | N transient подряд → пауза+алерт, half-open → recover | ---