Panne partielle : quand une partie du système d’agents tombe en panne

Une panne partielle apparaît quand seule une partie du système d’agents échoue tandis que le reste continue à fonctionner. Causes et impact.
Sur cette page
  1. Le problème
  2. Pourquoi ça arrive
  3. Pannes les plus fréquentes
  4. Piège du succès intermittent (Intermittent success trap)
  5. Retry amplification entre couches
  6. Queue bloquée par des runs "bruyants" (Queue starvation)
  7. Attente d'une réponse "parfaite" sans degrade path
  8. Comment détecter ces problèmes
  9. Comment distinguer partial outage de full outage
  10. Comment stopper ces pannes
  11. Où c'est implémenté dans l'architecture
  12. Auto-vérification
  13. FAQ
  14. Pages liées

Le problème

La demande paraît simple : vérifier le statut de paiement et donner une réponse courte au client.

Dans les traces, c'est autre chose : en 11 minutes, un run a fait 33 appels, 10 ont renvoyé 200, 14 ont renvoyé timeout, et 9 ont renvoyé 502/503. Pour une tâche de cette classe, cela peut être autour de ~$1.90 au lieu des ~$0.14 habituels.

Le service est formellement "vivant" : une partie des appels passe, il n'y a pas de panne totale. Mais la queue des runs grossit, la latency saute, et les utilisateurs reçoivent un résultat instable.

Le système ne tombe pas.

Il se bloque simplement, lentement, entre succès rares et échecs répétés.

Analogie : imagine une caisse où le terminal accepte parfois la carte puis se fige. Le magasin n'est pas fermé, mais la file grandit chaque minute. Une panne partielle dans les systèmes d'agents fonctionne pareil : l'infrastructure semble disponible, mais il n'existe plus de chemin stable vers la réponse.

Pourquoi ça arrive

En production, cela se passe généralement ainsi :

  1. une dépendance devient instable (timeout, 5xx, parfois 200) ;
  2. des retries démarrent dans plusieurs couches en même temps ;
  3. le run retient les workers plus longtemps, la queue grossit ;
  4. d'autres workflows ralentissent aussi à cause de ressources partagées ;
  5. sans fail-fast ni safe-mode, le système multiplie les coûts au lieu d'isoler l'échec.

Dans le trace, cela apparaît comme un pattern mixte : tool_2xx_rate tient encore, mais timeout_rate, retry_attempts_per_run et queue_backlog montent en même temps.

Le problème n'est pas un seul timeout.

Runtime ne bascule pas la dépendance instable en degraded mode, tant que la panne reste locale.

Pannes les plus fréquentes

En production, quatre patterns de partial outage reviennent le plus souvent.

Piège du succès intermittent (Intermittent success trap)

Le tool renvoie parfois 200, ce qui masque la dégradation. L'agent continue d'insister sur le même canal, au lieu d'un basculement contrôlé.

Cause typique : pas de seuil de "santé" de dépendance au niveau run.

Retry amplification entre couches

Le client HTTP, le gateway et le runtime font chacun leurs retries. Même une petite hausse d'erreurs devient vite une vague d'appels inutiles.

Cause typique : retry policy non centralisée.

Queue bloquée par des runs "bruyants" (Queue starvation)

Les runs problématiques restent longtemps bloqués, occupent le worker pool et évincent les tâches saines.

Cause typique : absence de limites de durée de run et de budget-gates.

Attente d'une réponse "parfaite" sans degrade path

Le système essaie d'attendre un résultat "idéal", alors que la dépendance est clairement dégradée.

Cause typique : pas de contrat partial/fallback côté utilisateur.

Comment détecter ces problèmes

Une panne partielle est visible via la combinaison des métriques health, runtime et queue.

MétriqueSignal de partial outageAction
degraded_dependency_rateune dépendance renvoie souvent timeout/5xxactiver degraded mode et réduire le fan-out
tool_2xx_with_high_timeout_rate200 et forte part de timeout apparaissent ensembleajouter un seuil de health, ne pas se baser sur 200 seul
retry_attempts_per_runtrop de répétitions pour un runcentraliser les retries et limiter le retry budget
run_duration_p95runs longs qui "pendent"ajouter fail-fast timeout et stop reasons
queue_backlogla queue grossit avec trafic normalisoler le chemin dégradé et activer fallback

Comment distinguer partial outage de full outage

Toute dégradation n'est pas une panne totale. Question clé : y a-t-il un chemin d'exécution stable, ou seulement des "succès" aléatoires.

Normal pour full outage si :

  • presque tous les appels échouent de façon uniforme (5xx ou indisponibilité totale) ;
  • le système passe rapidement en fail-fast ;
  • il n'y a pas l'illusion du "ça marche parfois".

Dangereux pour partial outage si :

  • dans le même run, on mélange 200, timeout et 5xx ;
  • l'agent répète les appels car il voit de rares succès ;
  • queue/latency montent sans incident global explicite.

Comment stopper ces pannes

En pratique :

  1. figer un health snapshot des dépendances au début du run ;
  2. dès que le seuil est dépassé, basculer le workflow en degraded mode ;
  3. garder les retries dans un seul tool gateway avec budget strict ;
  4. renvoyer partial/fallback avec stop reason explicite, au lieu d'attendre "indéfiniment".

Guard minimal pour 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

Dans cette version, retry_count compte toutes les réponses retryable dans le run, et attempt est le nombre de répétitions pour un appel précis.

C'est un guard de base. En production, on l'étend en général avec des per-tool health probes, un circuit breaker et un chemin safe-mode dédié pour les runs dégradés. before_tool_call(...) et on_tool_result(...) sont appelés dans le tool gateway, pour que la décision de dégradation soit centralisée, et non dupliquée dans chaque couche.

Où c'est implémenté dans l'architecture

En production, le contrôle du partial outage est presque toujours réparti entre trois couches du système.

Tool Execution Layer fournit les health signals : error rate, timeout patterns, retry budget et circuit breaker. C'est là qu'on voit qu'une dépendance est déjà instable, même si une partie des appels retourne encore 200.

Agent Runtime prend les décisions au niveau run : bascule en degraded mode, stop reasons et fin contrôlée avec fallback. Sans cette couche, le système continue d'attendre "encore un appel réussi".

Orchestration Topologies définit comment isoler un workflow dégradé du reste du système (bulkheads, queues, priorités). Cela évite qu'une dégradation locale devienne un incident partagé.

Auto-vérification

Vérification rapide avant release. Coche les points et regarde le statut ci-dessous.
C'est un sanity-check court, pas un audit formel.

Progression: 0/8

⚠ Il y a des signaux de risque

Il manque des contrôles de base. Fermez les points clés de la checklist avant release.

FAQ

Q : Pourquoi partial outage est souvent pire que full outage ?
R : Parce qu'il est masqué par "ça marche parfois". Le système ne s'arrête pas et continue de brûler du temps, des tokens et du worker pool.

Q : Faut-il désactiver immédiatement le tool dégradé ?
R : Pas toujours. En général, on active degraded mode : limiter retries, réduire fan-out, et passer sur un chemin partial/fallback.

Q : Où prendre les décisions sur retries et dégradation ?
R : Dans un seul tool gateway. Sinon chaque couche fait ses propres retries et partial outage s'amplifie vite.

Q : Que montrer à l'utilisateur quand la dépendance dégrade ?
R : Un stop reason explicite, ce qui a échoué, et une prochaine étape contrôlée : réponse partielle ou nouvelle tentative après rétablissement.


Partial outage ressemble rarement à un crash bruyant. C'est une dégradation silencieuse : le système avance encore, mais ne tient plus la qualité ni le rythme. C'est pourquoi les agents de production ont besoin non seulement de retries, mais aussi d'un mode de dégradation strict et d'une isolation des dépendances.

Pages liées

Si ce problème apparaît en production, ces pages sont aussi utiles :

⏱️ 8 min de lectureMis à jour 12 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

Nick — ingénieur qui construit une infrastructure pour des agents IA en production.

Focus : patterns d’agents, modes de défaillance, contrôle du runtime et fiabilité des systèmes.

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


Note éditoriale

Cette documentation est assistée par l’IA, avec une responsabilité éditoriale humaine pour l’exactitude, la clarté et la pertinence en production.

Le contenu s’appuie sur des défaillances réelles, des post-mortems et des incidents opérationnels dans des systèmes d’agents IA déployés.