auto-sync: 2026-06-03 00:00:01

This commit is contained in:
Stream
2026-06-03 00:00:01 +03:00
parent c6d347b09c
commit 65dba7eea7

View File

@@ -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 |
---