Gestion d’outage partielle (degrade mode + code)

  • Repère la panne tôt, avant que la facture grimpe.
  • Comprends ce qui casse en prod, et pourquoi.
  • Copie des garde-fous : budgets, stop reasons, validation.
  • Sache quand ce n’est pas la vraie cause.
Signaux de détection
  • Tool calls/run explosent (ou se répètent avec args hash).
  • Spend/tokens montent sans amélioration des outputs.
  • Retries passent de rares à constants (429/5xx).
Un tool est down, d’autres marchent. Sans degrade mode, l’agent thrash et brûle le budget. Voilà comment renvoyer du partiel avec une stop reason claire.
Sur cette page
  1. Le problème (côté prod)
  2. Pourquoi ça casse en prod
  3. 1) Intermittent = tentation de loop
  4. 2) Pas de signal de santé tool
  5. 3) Pas de safe-mode
  6. 4) API “all or nothing”
  7. Exemple d’implémentation (code réel)
  8. Incident réel (avec chiffres)
  9. Compromis
  10. Quand NE PAS l’utiliser
  11. Checklist (copier-coller)
  12. Config par défaut sûre (JSON/YAML)
  13. FAQ (3–5)
  14. Pages liées (3–6 liens)
Flux interactif
Scénario:
Étape 1/2: Execution

Normal path: execute → tool → observe.

Le problème (côté prod)

Ce n’est pas une outage totale. C’est intermittent.

Un tool :

  • parfois 200
  • parfois timeout
  • parfois 502

L’agent insiste pour “terminer”. Le user refresh parce qu’il voit des timeouts. Chaque refresh = un nouveau run = du budget brûlé.

Pourquoi ça casse en prod

1) Intermittent = tentation de loop

“Ça marche parfois” pousse l’agent à réessayer.

2) Pas de signal de santé tool

Sans health/breaker, l’agent ne sait pas qu’il doit arrêter.

3) Pas de safe-mode

Safe-mode = comportement dégradé : cache/partiel/read-only.

4) API “all or nothing”

Si ton contrat exige “réponse complète”, tu forces la boucle.

Exemple d’implémentation (code réel)

Snapshot health au début du run :

  • tool critique dégradé → désactivé pour ce run
  • safe-mode + partial
PYTHON
from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True)
class Health:
  degraded_tools: set[str]


def snapshot_health() -> Health:
  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"}
  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": "KB pas fiable en ce moment. Voilà ce que je peux faire sans…",
      }

  return agent_loop(task, allow=allow)  # (pseudo)
JAVASCRIPT
export function snapshotHealth() {
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: "KB pas fiable en ce moment. Voilà ce que je peux faire sans…",
  };
}

return agentLoop(task, { allow }); // (pseudo)
}

En outage partielle, l’objectif n’est pas “réussir à tout prix”. C’est “ne pas transformer une petite panne en grosse panne”.

Incident réel (avec chiffres)

Agent support basé sur kb.read.

Service KB dégradé (p95 ~300ms → 9s, timeouts). L’agent insistait “parce que parfois ça marche”.

Impact :

  • durée moyenne d’un run : 8s → 52s
  • retries côté client → trafic doublé
  • pager sur “agent timeouts” au lieu de la vraie cause
  • spend ~+$180/jour

Fix :

  1. health snapshot + degrade mode
  2. fail-fast quand breaker open
  3. partial + stop reason
  4. message “retry later” au lieu d’un timeout silencieux

Compromis

  • Réponses dégradées moins complètes.
  • Fail-fast baisse le taux de succès à court terme.
  • Health peut faire des faux positifs.

Quand NE PAS l’utiliser

  • Besoin de complétude stricte : async + progress.
  • Pas de sémantique “partial” : tu finis en timeouts.
  • Pas de health signals : commence par budgets + breaker.

Checklist (copier-coller)

  • [ ] Health snapshot au début du run
  • [ ] Degrade mode policy
  • [ ] Fail-fast sur breaker open
  • [ ] Partial + stop reason
  • [ ] Budgets toujours actifs
  • [ ] Alerting: runs dégradés vs normaux

Config par défaut sûre (JSON/YAML)

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)

Pourquoi ne pas retry jusqu’à ce que ça marche ?
Parce que intermittent + retries amplifie l’outage. Ton agent devient un générateur de charge.
Que renvoyer en degrade mode ?
Du partiel, du cache, ou un ‘pas possible maintenant’ avec stop reason.
Il faut une health par tool ?
Oui pour les dépendances externes. Commence par breaker + error rate.
Les users gèrent le partiel ?
Mieux que les timeouts. Donne une stop reason et une option de retry.

Q : Pourquoi ne pas retry jusqu’à ce que ça marche ?
R : Parce que intermittent + retries amplifie l’outage. Ton agent devient un générateur de charge.

Q : Que renvoyer en degrade mode ?
R : Du partiel, du cache, ou un “pas possible maintenant” avec stop reason.

Q : Il faut une health par tool ?
R : Oui pour les dépendances externes. Commence par breaker + error rate.

Q : Les users gèrent le partiel ?
R : Mieux que les timeouts. Donne une stop reason et une option de retry.

Pages liées (3–6 liens)

Pas sur que ce soit votre cas ?

Concevez votre agent ->
⏱️ 5 min de lectureMis à jour Mars, 2026Difficulté: ★★☆
Implémenter dans OnceOnly
Guardrails for loops, retries, and spend escalation.
Utiliser dans 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 }
Intégré : contrôle en productionOnceOnly
Ajoutez des garde-fous aux agents tool-calling
Livrez ce pattern avec de la gouvernance :
  • Budgets (steps / plafonds de coût)
  • Kill switch & arrêt incident
  • Audit logs & traçabilité
  • Idempotence & déduplication
  • Permissions outils (allowlist / blocklist)
Mention intégrée : OnceOnly est une couche de contrôle pour des systèmes d’agents en prod.
Exemple de policy (concept)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}
Auteur

Cette documentation est organisée et maintenue par des ingénieurs qui déploient des agents IA en production.

Le contenu est assisté par l’IA, avec une responsabilité éditoriale humaine quant à l’exactitude, la clarté et la pertinence en production.

Les patterns et recommandations s’appuient sur des post-mortems, des modes de défaillance et des incidents opérationnels dans des systèmes déployés, notamment lors du développement et de l’exploitation d’une infrastructure de gouvernance pour les agents chez OnceOnly.