Каскадні збої: коли одна помилка агента поширюється далі

Cascading failures виникають, коли помилка одного tool, сервісу або агента запускає ширший ланцюг збоїв. Чому агентні системи до цього вразливі.
На цій сторінці
  1. Проблема
  2. Чому це стається
  3. Які збої трапляються найчастіше
  4. Множення ретраїв між шарами (Retry amplification)
  5. Сатурація спільного пулу (Shared pool saturation)
  6. Доміно таймаутів у суміжних сервісах (Timeout domino)
  7. Каскад витрат поверх технічного збою (Cost cascade)
  8. Як виявляти ці проблеми
  9. Як відрізнити каскадний збій від локальної помилки інструмента
  10. Як зупиняти такі збої
  11. Де це реалізується в архітектурі
  12. Самоперевірка
  13. FAQ
  14. Пов'язані сторінки

Проблема

Запит виглядає звичайним: зібрати профіль клієнта і підготувати коротку відповідь.

У трейсах видно інше: один зовнішній tool почав повертати timeout, агент перейшов у retries, через 4 хвилини перевантажив worker pool, а ще через 7 хвилин почали деградувати вже не пов'язані workflow і сервіси.

Початковий збій був локальним. Але через цикл агента він став системним.

Система не падає одразу.

Вона поступово тягне за собою все більше залежностей.

Аналогія: уяви затор на одній смузі мосту. Спочатку гальмує лише одна смуга. Потім хвиля зупинки доходить до всіх під'їздів до мосту. Каскадний збій в агенті працює так само: локальна проблема без обмежень швидко стає спільною проблемою системи.

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

Каскадний збій виникає не через одну "погану" відповідь інструмента, а через підсилення помилки в кількох шарах одночасно.

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

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

Проблема не в одному нестабільному сервісі. Runtime не зупиняє хвилю, поки вона ще локальна.

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

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

Множення ретраїв між шарами (Retry amplification)

Один збій повторюється в HTTP-клієнті, tool gateway і reasoning loop агента. Кількість викликів росте геометрично. Мініприклад: 1 failure -> 3 retries у SDK -> 3 retries у gateway -> 3 retries у agent loop = 27 викликів.

Типова причина: retry policy розмазана по кількох місцях.

Сатурація спільного пулу (Shared pool saturation)

Проблемний tool займає більшість worker-ів. Інші run'и стоять у черзі, хоча їхні залежності здорові.

Типова причина: немає per-tool bulkhead limits.

Доміно таймаутів у суміжних сервісах (Timeout domino)

Коли черга росте, збільшується wait time. Через це upstream/downstream сервіси частіше виходять у timeout.

Типова причина: немає жорстких max_seconds і fail-fast при деградації залежності.

Каскад витрат поверх технічного збою (Cost cascade)

Каскад піднімає і вартість run'а: більше retries, більше токенів, довший життєвий цикл. Навіть "успішні" завершення стають занадто дорогими.

Типова причина: відсутні execution budgets (max_tool_calls, max_retries, max_usd).

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

Каскадні збої добре видно по комбінації gateway-, runtime- і queue-метрик.

МетрикаСигнал cascading failureЩо робити
retry_amplification_rateодин збій дає багато дубльованих retriesцентралізувати retries в одному gateway
circuit_open_rateчасто відкривається breaker на одному toolвмикати safe-mode і знижувати fan-out
queue_backlogчерга росте при звичному вхідному трафікуввести bulkhead limits і timeout на run
cross_service_timeout_rateтаймаути з'являються в не пов'язаних сервісахізолювати проблемний tool і обмежити конкуренцію
cascading_stop_reason_rateчасті cascade:* stop reasonsперевірити breaker/bulkhead і fallback-стратегію

Як відрізнити каскадний збій від локальної помилки інструмента

Не кожен tool_timeout означає cascade. Ключове питання: чи збій лишається локальним, чи вже впливає на інші частини системи.

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

  • збій ізольований в одному інструменті;
  • черга і latency інших run'ів залишаються стабільними;
  • після короткого cooldown система повертається до baseline.

Небезпечно, якщо:

  • помилка одного tool піднімає queue_backlog глобально;
  • таймаути з'являються у не пов'язаних workflow;
  • вартість і тривалість run'ів ростуть навіть там, де цей tool не використовується.

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

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

  1. тримаєш retries лише в одному choke point (tool gateway);
  2. ставиш per-tool circuit breaker + cooldown + bulkhead limits;
  3. вводиш execution budgets на retries, tool calls, час і вартість;
  4. при деградації перемикаєш run у safe-mode (partial/fallback), а не "тиснеш далі".

Мінімальний guard проти cascade:

PYTHON
from dataclasses import dataclass
import time


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


@dataclass(frozen=True)
class CascadeLimits:
    max_steps: int = 25
    max_seconds: int = 90
    max_tool_calls: int = 18
    max_retries: int = 4
    max_in_flight_per_tool: int = 8
    open_circuit_after: int = 3
    circuit_cooldown_s: int = 30


class CascadeGuard:
    def __init__(self, limits: CascadeLimits = CascadeLimits()):
        self.limits = limits
        self.steps = 0
        self.tool_calls = 0
        self.retries = 0
        self.in_flight: dict[str, int] = {}
        self.fail_streak: dict[str, int] = {}
        self.circuit_open_until: dict[str, float] = {}
        self.started_at = time.time()

    def on_step(self) -> str | None:
        self.steps += 1
        if self.steps > self.limits.max_steps:
            return "cascade:budget_max_steps"
        if (time.time() - self.started_at) > self.limits.max_seconds:
            return "cascade:budget_timeout"
        return None

    def before_tool_call(self, tool: str) -> str | None:
        if time.time() < self.circuit_open_until.get(tool, 0.0):
            return "cascade:circuit_open"

        current = self.in_flight.get(tool, 0)
        if current >= self.limits.max_in_flight_per_tool:
            return "cascade:bulkhead_full"

        self.tool_calls += 1
        if self.tool_calls > self.limits.max_tool_calls:
            return "cascade:budget_tool_calls"

        self.in_flight[tool] = current + 1
        return None

    def after_tool_call(self, tool: str, status_code: int) -> str | None:
        self.in_flight[tool] = max(0, self.in_flight.get(tool, 1) - 1)

        if status_code in RETRYABLE:
            self.retries += 1
            if self.retries > self.limits.max_retries:
                return "cascade:retry_budget"

            streak = self.fail_streak.get(tool, 0) + 1
            self.fail_streak[tool] = streak
            if streak >= self.limits.open_circuit_after:
                self.circuit_open_until[tool] = time.time() + self.limits.circuit_cooldown_s
                return "cascade:circuit_open"
            return "cascade:retry_allowed"

        self.fail_streak[tool] = 0
        return None

Це базовий guard. У цій версії tool_calls рахує саме спроби виклику, а не лише успішно допущені виклики. У production його зазвичай доповнюють пріоритезацією запитів, окремими лімітами для критичних tool і явним safe-mode маршрутом. before_tool_call(...) викликають до зовнішнього виклику, а after_tool_call(...) одразу після відповіді, щоб cascade гасився якомога раніше.

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

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

Tool Execution Layer — перший бар'єр: retry policy, circuit breaker, bulkhead, timeout і нормалізація помилок. Якщо цей шар слабкий, локальний збій швидко стає хвилею.

Agent Runtime керує бюджетами, stop reasons (cascade:*) і safe-mode переходами. Саме тут важливо зупинити run до системної сатурації.

Orchestration Topologies визначає, як ізолювати проблемні гілки workflow і не дати одному деградованому шляху заблокувати весь workflow.

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

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

Прогрес: 0/8

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

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

FAQ

Q: Ретраї ж корисні. Чому вони можуть ламати систему?
A: Корисні лише з backoff, caps і єдиною точкою керування. Коли retries дублюються між шарами, вони множать навантаження швидше, ніж система відновлюється.

Q: Чому агентні системи більш схильні до cascade, ніж звичайні API?
A: Бо агент має reasoning loop і може повторювати ті самі tool_call багато разів. Збій залежності множиться кожним кроком run.

Q: Timeout недостатньо? Навіщо ще breaker і bulkhead?
A: Timeout лише обмежує один виклик. Breaker зупиняє хвилю повторів, а bulkhead не дає одному tool забрати всі воркери.

Q: Safe-mode не псує якість відповіді?
A: Частково так, але це контрольована деградація. Краще віддати коректний частковий результат, ніж дочекатися повного outage.


Cascading failure майже ніколи не виглядає як одна велика помилка. Частіше це ланцюг дрібних збоїв, який система сама підсилює. Ключовий принцип: agent loops підсилюють збої (agents amplify failures). Тому production-агентам потрібні не лише сильні моделі, а й жорсткі межі на рівні runtime та gateway.

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

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

  • Чому AI агенти ламаються — загальна карта збоїв у production.
  • Tool failure — як локальна відмова інструмента переходить у cascade.
  • Tool spam — як повторні виклики прискорюють деградацію.
  • Budget explosion — як cascade перетворюється на фінансовий інцидент.
  • Partial outage — як працювати в режимі часткової деградації залежностей.
  • Agent Runtime — де реалізувати budgets, stop reasons і safe-mode.
  • Tool Execution Layer — де тримати retries, breaker і bulkhead.
⏱️ 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-агентних системах.