Normal path: execute → tool → observe.
Проблема (з реального продакшену)
Це не full outage. Це гірше.
Один tool flaky:
- інколи 200
- інколи timeout
- інколи 502
Агент продовжує “доробити задачу”. Користувачі ретраять, бо бачать таймаути. Бюджети горять, бо кожен retry — це новий run.
Partial outages — це місце, де видно, агент у тебе інженер чи азартний гравець.
Чому це ламається в продакшені
Partial outages важкі, бо успіх інколи трапляється. Це провокує loops.
1) Агент сприймає інтермітентний успіх як “продовжуй”
LLM оптимістичні. Якщо вони отримали хоча б частковий результат, вони часто намагаються “дозбирати”.
У ноутбуці це ок. У проді це runaway spend.
2) Немає поняття tool health
Якщо агент не знає “tool X деградує”, він буде:
- викликати його знову
- ретраїти
- переплановувати й знову викликати
Потрібен спільний health signal:
- стан breaker’а
- recent error rate
- latency spikes
3) Немає safe-mode поведінки
Коли tool деградує, потрібен план, який не залежить від нього:
- кеш
- partial results
- stop reason і право користувача вирішити, що далі
4) “All or nothing” outputs змушують до поганої поведінки
Якщо контракт API: “завжди поверни повну відповідь” — агент буде thrash під час partial outage.
Кращий контракт:
- partial results + confidence + stop reason
- опційно: async continuation
Приклад реалізації (реальний код)
Патерн “health snapshot” на старті run. Якщо критичний tool деградований:
- вимикаємо його для run
- переключаємось у safe-mode
- повертаємо partial + явний stop reason
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class Health:
degraded_tools: set[str]
def snapshot_health() -> Health:
# In real code: breaker states + recent error rates.
return Health(degraded_tools=set(get_degraded_tools())) # (pseudo)
def safe_tools_for_run(health: Health) -> set[str]:
allow = {"search.read", "kb.read", "http.get"}
# During outages: be conservative.
for t in health.degraded_tools:
allow.discard(t)
return allow
def run(task: str) -> dict[str, Any]:
health = snapshot_health()
allow = safe_tools_for_run(health)
if "kb.read" not in allow:
return {
"status": "degraded",
"reason": "kb.read degraded",
"partial": "I can’t reliably read the KB right now. Here’s what I can do without it…",
}
# Normal loop would run here with a tool gateway allowlist = allow.
return agent_loop(task, allow=allow) # (pseudo)export function snapshotHealth() {
// Real code: breaker states + recent error rates.
return { degradedTools: new Set(getDegradedTools()) }; // (pseudo)
}
export function safeToolsForRun(health) {
const allow = new Set(["search.read", "kb.read", "http.get"]);
for (const t of health.degradedTools) allow.delete(t);
return allow;
}
export function run(task) {
const health = snapshotHealth();
const allow = safeToolsForRun(health);
if (!allow.has("kb.read")) {
return {
status: "degraded",
reason: "kb.read degraded",
partial: "I can’t reliably read the KB right now. Here’s what I can do without it…",
};
}
return agentLoop(task, { allow }); // (pseudo)
}Це навмисно консервативно. Під час partial outage мета не “успіх будь-якою ціною”. Мета — “не перетворити partial outage на full outage”.
Реальний інцидент (з цифрами)
У нас був агент, який відповідав на support‑питання через kb.read.
KB сервіс деградував (p95 latency ~300ms → 9s, інтермітентні таймаути). Агент продовжував, бо інколи працювало.
Impact:
- average run time: 8s → 52s
- клієнтські ретраї подвоїли трафік
- on-call пейджили “agent timeouts”, а не реальний KB інцидент
- витрати виросли ~$180/день лише на ретраї + більші промпти
Fix:
- health snapshot + degrade mode
- fail fast після відкриття breaker’а
- partial results + чіткий stop reason
- “retry later” підказка замість тихих таймаутів
Partial outages навчили нас: stop reasons, видимі користувачу, — це фіча.
Компроміси
- Degrade mode відповіді менш повні.
- Fail fast знижує success rate тут і зараз.
- Health signals можуть помилятись (false positives). Це краще, ніж thrash.
Коли НЕ варто
- Якщо потрібна строга повнота — запускай async і показуй прогрес, а не синхронні loops.
- Якщо ти не можеш визначити семантику partial output — ти будеш змушений таймаутити (погано).
- Якщо в тебе немає health signals — почни з budgets і breaker defaults.
Чекліст (можна копіювати)
- [ ] Tool health snapshot на старті run
- [ ] Degrade mode policy (tools disabled, read-only, cached)
- [ ] Fail fast, коли breaker open
- [ ] Partial results + явний stop reason
- [ ] Budgets (time/tool calls/spend) все ще діють
- [ ] Алерти по degraded runs vs normal runs
Безпечний дефолтний конфіг (JSON/YAML)
degrade_mode:
enabled: true
disable_tools_when_degraded: true
allow_partial_results: true
health:
breaker_open_means_degraded: true
budgets:
max_seconds: 60
max_tool_calls: 12
FAQ (3–5)
Використовується в патернах
Пов’язані відмови
Q: Чому не ретраїти, доки не спрацює?
A: Бо інтермітентні фейли + ретраї підсилюють аутедж. Агент стає генератором навантаження.
Q: Що повертати в degrade mode?
A: Partial results, cached data або чесне “не можу зараз” зі stop reason.
Q: Чи потрібен per-tool health?
A: Так для зовнішніх залежностей. Почни зі стану breaker’а і recent error rates.
Q: Як користувачі працюють із partial results?
A: Краще, ніж з таймаутами. Дай stop reason і опцію спробувати пізніше.
Пов’язані сторінки (3–6 лінків)
- Foundations: Production-ready агент · Чому агенти ламаються в продакшені
- Failure: Cascading failures · Tool spam loops
- Governance: Tool permissions (allowlists)
- Production stack: Production agent stack