Частковий збій: коли ламається частина агентної системи

Partial outage виникає, коли перестає працювати лише частина агентної системи, а решта залишається доступною. Як це ламає пайплайни і UX.
На цій сторінці
  1. Проблема
  2. Чому це стається
  3. Які збої трапляються найчастіше
  4. Пастка інтермітентного успіху (Intermittent success trap)
  5. Retry amplification по шарах
  6. Черга забивається "шумними" run'ами (Queue starvation)
  7. Очікування "ідеальної" відповіді без degrade path
  8. Як виявляти ці проблеми
  9. Як відрізнити partial outage від full outage
  10. Як зупиняти такі збої
  11. Де це реалізується в архітектурі
  12. Самоперевірка
  13. FAQ
  14. Пов'язані сторінки

Проблема

Запит виглядає простим: перевірити статус оплати і дати коротку відповідь клієнту.

У трейсах видно інше: за 11 хвилин один run зробив 33 виклики, з них 10 повернули 200, 14 — timeout, ще 9 — 502/503. Для задачі такого класу це може бути ~$1.90 замість звичних ~$0.14.

Сервіс формально "живий": частина викликів проходить, повного падіння немає. Але черга run'ів росте, latency стрибає, а користувачі отримують нестабільний результат.

Система не падає.

Вона просто повільно застрягає між рідкими успіхами і повторними збоями.

Аналогія: уяви касу, де термінал то приймає картку, то зависає. Магазин не закритий, але черга росте щохвилини. Partial outage в агентних системах працює так само: інфраструктура ніби доступна, але стабільного шляху до відповіді вже немає.

Чому це стається

У production зазвичай так:

  1. залежність починає працювати нестабільно (timeout, 5xx, інколи 200);
  2. retries запускаються у кількох шарах одночасно;
  3. run тримає воркери довше, черга росте;
  4. інші workflow теж сповільнюються через спільні ресурси;
  5. без fail-fast і safe-mode система множить витрати замість ізоляції збою.

У trace це видно як змішаний патерн: tool_2xx_rate ще тримається, але одночасно ростуть timeout_rate, retry_attempts_per_run і queue_backlog.

Проблема не в одному timeout.

Runtime не переводить нестабільну залежність у degraded mode, поки збій ще лишається локальним.

Які збої трапляються найчастіше

У production найчастіше видно чотири патерни partial outage.

Пастка інтермітентного успіху (Intermittent success trap)

Інструмент інколи повертає 200, і це маскує деградацію. Агент продовжує тиснути в той самий канал, замість контрольованого переключення.

Типова причина: немає порогу "здоров'я" залежності на рівні run.

Retry amplification по шарах

HTTP-клієнт, gateway і runtime кожен робить свої retries. Навіть невеликий сплеск помилок швидко перетворюється на хвилю зайвих викликів.

Типова причина: retry policy не централізована.

Черга забивається "шумними" run'ами (Queue starvation)

Проблемні run'и довго висять у виконанні, займають worker pool і витісняють здорові задачі.

Типова причина: відсутні ліміти тривалості run і budget-gates.

Очікування "ідеальної" відповіді без degrade path

Система намагається дочекатися "ідеального" результату, хоча залежність явно деградує.

Типова причина: немає partial/fallback контракту для користувача.

Як виявляти ці проблеми

Partial outage добре видно по комбінації health-, runtime- і queue-метрик.

МетрикаСигнал partial outageЩо робити
degraded_dependency_rateодна залежність часто дає timeout/5xxувімкнути degraded mode і знизити fan-out
tool_2xx_with_high_timeout_rateодночасно є і 200, і висока частка timeoutдодати health threshold, не орієнтуватись лише на 200
retry_attempts_per_runпідозріло багато повторів на один runцентралізувати retries і обмежити retry budget
run_duration_p95довгі "висячі" run'иввести fail-fast timeout і stop reasons
queue_backlogчерга росте при звичному трафікуізолювати деградований шлях і вмикати fallback

Як відрізнити partial outage від full outage

Не кожна деградація — це повний outage. Ключове питання: чи є стабільний шлях виконання, чи лише випадкові "успіхи".

Нормально для full outage, якщо:

  • майже всі виклики падають одноманітно (5xx або повна недоступність);
  • система швидко переходить у fail-fast;
  • немає ілюзії "інколи працює".

Небезпечно для partial outage, якщо:

  • у тому самому run змішані 200, timeout і 5xx;
  • агент повторює виклики, бо бачить рідкі успішні відповіді;
  • queue/latency ростуть навіть без явного global incident.

Як зупиняти такі збої

Практично це виглядає так:

  1. фіксуєш health snapshot залежностей на старті run;
  2. при порушенні порогу одразу вмикаєш degraded mode для workflow;
  3. тримаєш retries в одному tool gateway з жорстким budget;
  4. повертаєш partial/fallback і явний stop reason, замість "вічного" очікування.

Мінімальний guard для partial outage:

PYTHON
from dataclasses import dataclass
import time


RETRYABLE = {408, 429, 500, 502, 503, 504}


@dataclass(frozen=True)
class OutageLimits:
    max_retry_per_call: int = 2
    max_retry_total: int = 6
    max_run_seconds: int = 45
    max_tool_calls: int = 14
    degraded_error_threshold: float = 0.35
    min_sample_size: int = 5


class PartialOutageGuard:
    def __init__(self, limits: OutageLimits = OutageLimits()):
        self.limits = limits
        self.started_at = time.time()
        self.tool_calls = 0
        self.retry_count = 0
        self.total_calls = 0
        self.error_calls = 0

    def before_tool_call(self) -> str | None:
        self.tool_calls += 1
        if self.tool_calls > self.limits.max_tool_calls:
            return "partial_outage:tool_call_budget"
        if (time.time() - self.started_at) > self.limits.max_run_seconds:
            return "partial_outage:run_timeout"
        return None

    def on_tool_result(self, status_code: int, attempt: int) -> str | None:
        self.total_calls += 1

        if status_code in RETRYABLE:
            self.error_calls += 1
            error_rate = self.error_calls / max(1, self.total_calls)
            if (
                self.total_calls >= self.limits.min_sample_size
                and error_rate >= self.limits.degraded_error_threshold
            ):
                return "partial_outage:degraded_mode"
            self.retry_count += 1
            if self.retry_count > self.limits.max_retry_total:
                return "partial_outage:retry_budget"
            if attempt >= self.limits.max_retry_per_call:
                return "partial_outage:retry_exhausted"
            return "partial_outage:retry_allowed"

        error_rate = self.error_calls / max(1, self.total_calls)
        if (
            self.total_calls >= self.limits.min_sample_size
            and error_rate >= self.limits.degraded_error_threshold
        ):
            return "partial_outage:degraded_mode"

        return None

У цій версії retry_count рахує всі retryable відповіді в межах run, а attempt — кількість повторів конкретного виклику.

Це базовий guard. У production його зазвичай доповнюють per-tool health probes, circuit breaker і окремим safe-mode шляхом для degraded run'ів. before_tool_call(...) і on_tool_result(...) викликають у tool gateway, щоб рішення про деградацію приймалось централізовано, а не в кожному шарі окремо.

Де це реалізується в архітектурі

У production контроль partial outage майже завжди розкладений між трьома шарами системи.

Tool Execution Layer дає health signals: error rate, timeout patterns, retry budget і circuit breaker. Саме тут видно, що залежність вже нестабільна, навіть якщо частина викликів ще повертає 200.

Agent Runtime приймає рішення по run: перехід у degraded mode, stop reasons і контрольоване завершення з fallback. Без цього шару система продовжує чекати "ще один вдалий виклик".

Orchestration Topologies визначає, як ізолювати деградований workflow від решти системи (bulkheads, черги, пріоритети). Це не дає локальній деградації перетворитись на спільний інцидент.

Самоперевірка

Швидка перевірка перед релізом. Відмічайте пункти і дивіться статус нижче.
Це короткий sanity-check, а не формальний аудит.

Прогрес: 0/8

⚠ Є сигнали ризику

Бракує базових контролів. Закрийте ключові пункти цього чекліста перед релізом.

FAQ

Q: Чому partial outage часто гірший за full outage?
A: Бо він маскується під "інколи працює". Система не зупиняється, а довго спалює час, токени і worker pool.

Q: Чи треба одразу вимикати деградований tool?
A: Не завжди. Зазвичай вмикають degraded mode: обмежують retries, зменшують fan-out і переходять на partial/fallback шлях.

Q: Де краще приймати рішення про retries і деградацію?
A: В одному tool gateway. Інакше кожен шар робить свої retries, і partial outage швидко масштабується.

Q: Що показувати користувачу, коли залежність деградує?
A: Явний stop reason, що саме не спрацювало, і контрольований наступний крок: часткова відповідь або повторна спроба після відновлення залежності.


Partial outage майже ніколи не виглядає як гучна аварія. Це тиха деградація, де система ще рухається, але вже не тримає якість і темп. Тому production-агентам потрібні не лише retries, а й жорсткий режим деградації та ізоляції залежностей.

Пов'язані сторінки

Якщо ця проблема виникла у production, корисно також подивитися:

  • Чому AI агенти ламаються — загальна карта збоїв у production.
  • Tool failure — як локальна помилка інструмента стає інцидентом.
  • Deadlocks — як waiting-стани ростуть під час деградації залежностей.
  • Cascading failures — як partial outage поширюється на інші workflow.
  • Agent Runtime — де керувати degraded mode, stop reasons і fallback.
  • Tool Execution Layer — де тримати retries, health signals і circuit breaker.
⏱️ 7 хв читанняОновлено 12 березня 2026 р.Складність: ★★☆
Реалізувати в OnceOnly
Guardrails for loops, retries, and spend escalation.
Використати в OnceOnly
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
controls:
  loop_detection:
    enabled: true
    dedupe_by: [tool, args_hash]
  retries:
    max: 2
    backoff_ms: [200, 800]
stop_reasons:
  enabled: true
logging:
  tool_calls: { enabled: true, store_args: false, store_args_hash: true }
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Kill switch та аварійна зупинка
  • Audit logs та трасування
  • Ідемпотентність і dedupe
  • Дозволами на інструменти (allowlist / blocklist)
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.
Приклад policy (концепт)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}

Автор

Микола — інженер, який будує інфраструктуру для продакшн AI-агентів.

Фокус: патерни агентів, режими відмов, контроль рантайму та надійність систем.

🔗 GitHub: https://github.com/mykolademyanov


Редакційна примітка

Ця документація підготовлена з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Контент базується на реальних відмовах, постмортемах та операційних інцидентах у розгорнутих AI-агентних системах.