---
name: avtero_llm_alert_opsdash
description: avtero — алерт в ops-dashboard когда LLM-чейн падает (exec-healthcheck → degraded → TG); как устроено и как тестировать без рестарта
metadata: 
  node_type: memory
  type: project
  originSessionId: 4eea915e-3b47-4cd5-8a0e-bfa29b78a87c
---

**Задача (2026-06-30): получать TG-алерт, когда у avtero падает LLM-чейн.** Раньше не приходил, т.к. дашборд мониторил avtero по http `/healthz`, а тот отдаёт 200 даже при полном крахе чейна (502 llm_failed) → avtero «зелёный» → пуша нет.

**Решение (только конфиг + 1 скрипт, app не трогали — его правит параллельная сессия):**
- Скрипт `~/Services/ops_dashboard/checks/avtero_llm_health.py` (stdlib, py3.9 — нужен `from __future__ import annotations`): exit0=healthy, exit2=API down (/healthz≠200), exit1=в `api.err.log` есть «All models in fallback chain failed» за последние **300с** (парсит таймстемп, старая ошибка НЕ держит degraded вечно). Env-оверрайды `AVTERO_ERR_LOG`/`AVTERO_HEALTHZ` для тестов.
- В `dashboard_config.json` сервис `avtero`: `health_check` тип `http`→`exec`, `command: python3 .../avtero_llm_health.py`, timeout 15. Бэкап `dashboard_config.json.bak-llm-alert-*`.
- Дашборд: `infer_service_status` (pid+wd→running) → downgrade `if running and health_check not ok → degraded`. Транзишн running→degraded → `notify_telegram` шлёт в чат `1005954706` (токен env `OPS_DASHBOARD_BOT_TOKEN`, прокси :2081, events=down/running/degraded).

**Тестировать ТОЛЬКО правкой конфига на месте, БЕЗ рестарта дашборда:**
- uptime-sampler крутится раз в **60с** + 1 итерация лага → переход виден через ~120с. Жди ≥135с.
- Рестарт дашборда (`kickstart com.cebers.ops-dashboard`) **сбрасывает last_status → первый осмотр = baseline без алерта** (prev is None). Поэтому тест с рестартами НЕ покажет переход — правь только `health_check.command` в конфиге (loop перечитывает `read_json(config)` каждую итерацию).
- Тест-приём: `cp api.err.log /tmp/t.log; echo "<now-ts> ... All models in fallback chain failed: TEST" >> /tmp/t.log`, временно `command="AVTERO_ERR_LOG=/tmp/t.log python3 ..."`, ждать 135с → проверить `sqlite3 ~/Services/ops_dashboard/_state/dashboard.sqlite "SELECT ts,status,prev_status FROM state_events WHERE target_id='avtero' ORDER BY rowid DESC"`. **Проверено живьём 2026-06-30: running→degraded зафиксирован** → пуш ушёл; откат конфига → recovery.

**Латентность алерта ~2 мин — это норма** (60с-сэмплер). Богаче (пер-модельный статус/consec_errors/last_error) умеет встроенный `_check_ai_provider_alerts` дашборда, читающий `AI_METRICS_SNAPSHOT_PATHS` (json с `ai_providers`) — но туда avtero пришлось бы ПИСАТЬ метрики (app-код). Отложено. См. [[avtero_llm_chain_incident]] [[ops_dashboard_tailscale_funnel]].


**ОБНОВЛЕНИЕ 2026-07-05 — ops-grep ослеп, добавлен прямой алерт.** После ввода graceful no-result (codex-primary, `ALLOW_CATALOG_FALLBACK=0`) полный провал цепочки ЛОВИТСЯ в orchestrator.py:6101 и логируется как «LLM unavailable (…)», а НЕ «All models in fallback chain failed» → ops-dashboard exec-греп по старой фразе больше не матчит → юзер не получал алерт «все модели упали». **Фикс:** `app/services/outage_alert.py::alert_llm_outage()` — прямой TG-алерт из самой ветки провала (fire-and-forget, throttle `LLM_OUTAGE_ALERT_MIN_INTERVAL_SEC` деф.900с; токен/админы/прокси из plist API). Проверено тест-алертом. Это авторитетный «Автеро не смог ответить», ops-греп теперь второстепенен. См. [[avtero_paired_sides]] (в той же сессии) и D-llm-alerts в Obsidian.