auto-sync: 2026-06-03 00:00:01
This commit is contained in:
@@ -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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user