---
title: "Applied AI — Avancé S4 : Architecture multi-agents"
author: "Yann Isola"
theme: applied-ai
palette: ["#1A2230", "#0F7A6C", "#B4612A", "#E9F6F3", "#F4F7F6"]
---

# Slide 1 — Titre

## Architecture multi-agents
### Applied AI — Niveau avancé · Session 4

**Yann Isola** — Préparation *Claude Certified Architect*

Décomposition · Escalade & HITL · Gestion d'erreurs

---

# Slide 2 — Objectifs de la session

À la fin de cette session, vous saurez :

1. **Choisir** une stratégie de décomposition (verticale / horizontale / récursive) et la **justifier**
2. **Concevoir** une politique d'escalade Human-in-the-Loop complète (déclencheurs, patrons, SLA)
3. **Implémenter** les patrons d'erreurs : retry idempotent, circuit breaker, dégradation gracieuse, DLQ
4. **Instrumenter** un système multi-agents (traçage distribué, correlation IDs)

> Fil rouge : **l'architecture des mauvais jours** — un système se juge quand tout casse, pas quand tout va bien.

---

# Slide 3 — Pourquoi décomposer ? Les trois murs

Un agent unique face à une tâche complexe :

- 🧱 **Mur du contexte** — la fenêtre de contexte est finie ; la saturer dégrade la qualité
- 🧱 **Mur de la cohérence** — plus la tâche est longue, plus la dérive augmente
- 🧱 **Mur de la spécialisation** — un prompt généraliste < des prompts spécialisés

**Mais :** chaque agent ajouté = latence + tokens + complexité opérationnelle.

> La question n'est jamais « peut-on décomposer ? » mais « **le gain dépasse-t-il le coût ?** »

---

# Slide 4 — Décomposition verticale (par profondeur)

Étapes **séquentielles** de **natures différentes** :

```
Planification  →  Exécution  →  Vérification
 (le plan =        (outils,       (audit contre les
  contrat           artefacts)     critères de succès)
  auditable)
```

✅ Prompts courts et ciblés · vérificateur indépendant (anti-biais d'auto-évaluation)
⚠ Latence cumulée · plan qui peut devenir obsolète → boucle de replanification

---

# Slide 5 — Verticale : le code

```python
plan   = planner.plan(task)              # plan + critères de succès
result = executor.execute(plan)
for attempt in range(max_repairs):       # boucle de réparation bornée
    report = verifier.check(result, plan.success_criteria)
    if report.ok:
        return Result("success", result)
    result = executor.repair(result, report.issues)
return Result("needs_human_review", result)   # → escalade (chap. 9)
```

**À noter :** la sortie `needs_human_review` — la décomposition s'articule *nativement* avec l'escalade.

---

# Slide 6 — Décomposition horizontale (par domaine)

Chaque agent **possède un domaine**, traité de bout en bout :

```
              Orchestrateur
      ┌──────────┼──────────┬──────────┐
      ▼          ▼          ▼          ▼
  Juridique   Fiscal     Risque    (parallèle !)
      └──────────┴──────────┴──────► Synthèse (fan-in)
```

✅ Latence ≈ agent le plus lent (pas la somme) · spécialisation max · isolation des pannes
⚠ Contradictions inter-domaines → étape de **réconciliation** obligatoire

---

# Slide 7 — Décomposition récursive (arbre)

Chaque agent peut **décomposer sa propre sous-tâche** :

```
Racine ── Agent A ── Sous-agent A1
      │           └─ Sous-agent A2 ── A2a
      └── Agent B ── Sous-agent B1
```

**Idéal pour :** structures imprévisibles ou fractales (monorepos, arborescences documentaires)

**Garde-fous OBLIGATOIRES :**
1. `max_depth` (2–3 niveaux ⚠)
2. Budget de tokens **hérité** par niveau (ex. : 30 % du parent)
3. **Contrat de résultat** structuré : `{statut, artefacts, coût}` — jamais du texte libre

---

# Slide 8 — Quand décomposer ? Les 3 critères

| Critère | Question | Seuil indicatif ⚠ |
|---|---|---|
| **Complexité** | > 5–7 étapes hétérogènes ? | Oui → décomposer |
| **Budget tokens** | Contexte > ~60–70 % de la fenêtre ? | Oui → décomposer |
| **Spécialisation** | Gain qualité mesurable par domaine ? | Gain > surcoût → décomposer |

> 🥇 Règle d'or : **l'architecture la plus robuste est celle qui n'existe pas.**
> Si un agent suffit, un agent suffit.

---

# Slide 9 — ⚠ Piège de certification n°1

**« La tâche est exécutée 1 000 fois par jour, faut-il décomposer ? »**

# NON.

- Volume élevé = problème de **parallélisation d'instances** (N copies du même agent)
- Décomposition = problème de **structure de la tâche**

Les deux se combinent, mais ne se confondent **jamais**.
Discriminant vertical/horizontal : *séquence de natures différentes* vs *parallélisme de domaines*.

---

# Slide 10 — Escalade : une exigence d'architecture

Aucun système d'agents n'atteint 100 % d'autonomie fiable.

**HITL** (*Human-in-the-Loop* — humain dans la boucle) : mécanismes par lesquels un humain **valide, corrige ou reprend** le travail d'un agent.

> Un système sans escalade explicite escalade quand même —
> mais en mode chaos : tickets, incidents, perte de confiance.
> **Concevez les chemins de sortie dès le jour 1.**

---

# Slide 11 — Déclencheur 1 : confiance sous le seuil

```python
CONFIDENCE_THRESHOLD = 0.75   # ⚠ à calibrer sur données réelles

if judge_agent.evaluate(answer).confidence < CONFIDENCE_THRESHOLD:
    escalate(task, reason="low_confidence")
```

**3 bonnes pratiques :**
- Agent **juge distinct** (l'auto-évaluation est complaisante)
- Seuil **calibré** sur jeu de validation étiqueté (courbe précision / taux d'escalade)
- **Surveiller la dérive** du taux d'escalade en production

---

# Slide 12 — Déclencheur 2 : hors périmètre

L'agent reconnaît que la demande **sort de son domaine autorisé** :
sujet non couvert · conseil réglementé (juridique, fiscal, médical) · contournement

**Défense en profondeur (2 couches) :**
1. **Classifieur de périmètre** en amont (petit modèle rapide)
2. Consigne d'auto-détection dans le prompt système

> ⚠ **Piège n°2 :** une hallucination assurée a une confiance auto-déclarée **élevée**.
> Le seuil de confiance seul ne l'attrape pas → d'où ce déclencheur indépendant.

---

# Slide 13 — Déclencheur 3 : niveau de risque

Certaines actions escaladent **par nature**, quelle que soit la confiance.

**Matrice de risque :**

| Impact \ Réversibilité | Réversible | Irréversible |
|---|---|---|
| **Faible** | Autonome | Autonome + logs renforcés |
| **Moyen** | Autonome + revue a posteriori | Approbation préalable |
| **Élevé** | Approbation préalable | Approbation + double validation |

Exemples : virements au-delà d'un montant, suppression de données, modification de production.

---

# Slide 14 — Patron 1 : Pause-and-ask

L'agent **suspend** son exécution → question précise → attend → **reprend**.

- 🎯 Usage : ambiguïté bloquante, décision ponctuelle en cours de tâche
- 🔧 Exigence : état de l'agent **sérialisable** (l'humain peut répondre dans 4 h)
- ⚠ Risque : accumulation de tâches suspendues → **timeout obligatoire**

Chronologie : `travail → ⏸ pause → humain répond → ▶ reprise → fin`

---

# Slide 15 — Patron 2 : Queue-for-review

L'agent **termine** son travail → le résultat attend dans une **file de validation** avant publication/exécution. L'agent est libéré immédiatement.

- 🎯 Usage : contenu à enjeu, préparation d'actions (ex. : instructions de retrait)
- ✅ L'humain traite **par lots** avec une interface de revue (diff, approuver/rejeter)
- 📊 Métrique clé : taux d'approbation sans modification > ~98 % ⚠ durablement → envisager l'autonomie (avec échantillonnage)

Chronologie : `travail complet → 📥 file → humain valide → publication`

---

# Slide 16 — Patron 3 : Fallback-to-human

L'agent **abandonne** et transfère **intégralement** à un humain. Le filet terminal.

**Règle d'or : l'humain ne repart JAMAIS de zéro.**

Le paquet de transfert contient :
- la demande initiale
- l'historique des actions de l'agent
- les hypothèses testées
- le motif **précis** de l'escalade

> Un transfert sans contexte détruit la valeur du travail agent.

---

# Slide 17 — Pause-and-ask vs Queue-for-review

**L'erreur la plus fréquente de l'examen :**

| | Pause-and-ask | Queue-for-review |
|---|---|---|
| L'agent a-t-il fini ? | ❌ Non — suspendu **pendant** | ✅ Oui — validation **après** |
| Synchrone ? | Oui (pour la tâche) | Non (asynchrone) |
| Cas type | Ambiguïté bloquante | Approbation avant exécution |

Exemple discriminant : *signaux de fraude pendant une récupération de compte* → **pause-and-ask** (surtout pas finir la procédure puis la faire relire !).

---

# Slide 18 — La boucle de feedback humain

Chaque correction humaine = **une donnée d'apprentissage**.

```
1. CAPTURE     {task_id, sortie_agent, action_humaine, correction, motif}
2. AGRÉGATION  quelles catégories sont le plus corrigées ? quels motifs ?
3. INJECTION   prompt système → exemples/RAG → fine-tuning (⚠ coûteux, en dernier)
4. MESURE      le taux de correction baisse-t-il ? sinon = théâtre de feedback
```

**Question d'audit :** « Montrez-moi la dernière modification de prompt motivée par une correction humaine — et sa date. »

---

# Slide 19 — SLA d'escalade

Une escalade sans SLA (*Service Level Agreement*) = escalade vers un **trou noir**.

| Priorité | Exemple | Délai cible ⚠ | Au timeout |
|---|---|---|---|
| **P1** | Fraude, client en détresse, risque légal | < 15 min | Ré-escalade + fail-safe (geler) |
| **P2** | Validations, questions réglementées | < 4 h | Ré-escalade n+1 |
| **P3** | Revues d'échantillonnage | < 48 h | Fail-operational (défaut conservateur) |

+ **Routage par compétence** (le juridique va au juriste) + **tableaux de bord** (taux d'escalade, temps de 1ʳᵉ réponse, dépassements).

---

# Slide 20 — Erreurs : l'effet domino

```
Sous-agent C échoue
  → Agent B attend C → timeout → B échoue
    → L'orchestrateur reçoit 2 échecs → abandonne TOUT
      → 45 min de travail de A, D, E : perdues
```

**3 principes anti-cascade :**
1. **Isolation** (bulkheads) : une branche qui tombe n'invalide pas les autres
2. **Timeouts systématiques** sur tout appel inter-agents
3. **Typologie** : erreurs *réessayables* (timeout, rate limit) ≠ *fatales* (entrée invalide, policy)

---

# Slide 21 — Retry : l'idempotence d'abord

Un timeout est **ambigu** : la 1ʳᵉ tentative a peut-être abouti côté serveur.
Rejouer « créer une facture » sans précaution = **doublon**.

```python
idem_key = f"inv-{uuid.uuid4()}"        # UNE clé par INTENTION
for attempt in range(3):
    try:
        return api.post("/invoices", json=payload,
                        headers={"Idempotency-Key": idem_key})
    except TransientError:
        sleep(backoff(attempt))          # exponentiel + jitter
```

**Règle d'architecte :** chaque outil documente `idempotent: true/false`. Retry auto **uniquement** sur les outils idempotents. Backoff avec **jitter** (anti *thundering herd*).

---

# Slide 22 — Checkpointing & rollback partiel

**Checkpointing** — sauvegarder l'état à chaque jalon → reprise sans tout rejouer :

```python
if step.id in state.completed_steps: continue   # déjà fait
result = execute(step); state.record(step.id, result)
store.save(mission_id, state)                    # après CHAQUE jalon
```

**Rollback partiel (patron Saga)** — chaque action a sa **compensation** :

```
réserver stock → débiter compte → créer expédition ✗
   compensations en ordre INVERSE : recréditer, puis libérer le stock
```

---

# Slide 23 — Circuit breaker : les 3 états

```
              N échecs consécutifs
  ┌────────┐ ───────────────────► ┌────────┐
  │ FERMÉ  │                      │ OUVERT │  rejet immédiat
  │        │ ◄── succès ──┐       │        │  (fail fast)
  └────────┘              │       └───┬────┘
  appels normaux          │           │ après cooldown
                    ┌─────┴───────────▼──┐
                    │    SEMI-OUVERT     │── échec ──► OUVERT
                    │ (1 appel d'essai)  │
                    └────────────────────┘
```

Seuil : 5 échecs ⚠ · Cooldown : 30 s ⚠ (à calibrer)
**Multi-agents : un disjoncteur PAR dépendance** — jamais global. Ouverture → l'orchestrateur active la dégradation gracieuse.

---

# Slide 24 — Dégradation gracieuse

**Réduire la capacité plutôt qu'échouer complètement.**

| Niveau | Situation | Comportement |
|---|---|---|
| 0 | Nominal | Service complet |
| 1 | Outil d'enrichissement KO | Réponse sans, **manque mentionné** |
| 2 | Sous-agent spécialisé KO | Réponse générique + avertissement |
| 3 | Modèle principal KO | Modèle de secours, périmètre restreint ⚠ |
| 4 | Tout KO | Refus honnête + capture pour différé |

**2 règles absolues :** transparence (jamais présenter du dégradé comme complet) · on dégrade la **capacité**, jamais le **contrôle** (garde-fous intouchables).

---

# Slide 25 — Dead Letter Queue (DLQ)

Une tâche qui a épuisé ses retries n'est **jamais supprimée en silence** → file de lettres mortes, avec tout le contexte de diagnostic :

```json
{ "task_id": "task-8842", "correlation_id": "corr-a1b9f3",
  "attempts": 3, "last_error": {"type": "ToolTimeoutError"},
  "agent_chain": ["orchestrator", "agent-juridique", "sub-registre"],
  "state_checkpoint_ref": "ckpt://missions/8842/step-3" }
```

**Exploitation :** alerte au dépôt (DLQ qui grossit en silence = anti-patron) · **rejeu** après correction (grâce aux clés d'idempotence + checkpoints) · analyse de tendance (80 % du même timeout = problème d'infra, pas d'agents).

---

# Slide 26 — Observabilité : correlation ID

Un identifiant unique généré **à l'entrée**, propagé partout : agents, outils, files, logs.

```python
cid = headers.get("X-Correlation-ID") or f"corr-{uuid4().hex[:8]}"
log.info("request.received", cid=cid)
orchestrator.run(request, cid=cid)   # propagation explicite
```

**Test décisif :** `grep corr-a1b9f3 logs/*` doit reconstituer **l'intégralité** du parcours.
Un composant qui « perd » l'ID = observabilité cassée à cet endroit précis.

---

# Slide 27 — Traçage distribué (OpenTelemetry)

Chaque opération = un **span** (segment chronométré) dans une **trace** (arbre de la demande) :

```
Trace corr-a1b9f3                          [42,3 s]
├── planner.plan                           [3,1 s]  ✓
├── agent-juridique.analyze                [18,2 s] ✓
│   └── tool.search_registry               [12,0 s] ← goulot !
├── agent-fiscal.analyze                   [17,8 s] ✗ TIMEOUT ← cause racine
└── redactor.merge                         [2,4 s]  ✓ (dégradé niv. 1)
```

**Attributs spécifiques LLM :** modèle, tokens in/out, coût, escalades, état du disjoncteur, **version du prompt**. → outil d'optimisation coûts/qualité, pas seulement de débogage.

---

# Slide 28 — La grille de l'architecte

| Domaine | Question de conception | Livrable |
|---|---|---|
| Décomposition | Verticale / horizontale / récursive — pourquoi ? | Diagramme + 3 critères |
| Escalade | Déclencheurs, patrons, SLA ? | Politique + matrice de risque |
| Erreurs | Que se passe-t-il quand X tombe ? | Carte des pannes + échelle de dégradation |
| Retry | Quels outils sont idempotents ? | Spécification annotée |
| Observabilité | Peut-on rejouer l'histoire d'une demande ? | Schéma de propagation du CID |

---

# Slide 29 — Les pièges d'examen à retenir

1. **Volume ≠ décomposition** → instances parallèles, pas de multi-agents
2. **Confiance ≠ périmètre** → l'hallucination assurée passe le filtre de confiance
3. **Pause-and-ask ≠ queue-for-review** → suspendu *pendant* vs validé *après*
4. **Retry sans idempotence = doublons** → clé d'idempotence d'abord
5. **Half-open** : un échec en semi-ouvert → retour direct en OUVERT
6. **Dégrader la capacité, jamais le contrôle** → garde-fous intouchables
7. Questions croisées : « 5 échecs d'un sous-agent ? » → **circuit breaker PUIS escalade**

---

# Slide 30 — Prochaines étapes

**Aujourd'hui :**
- 🧪 3 exercices : choix de décomposition · politique d'escalade · implémentation erreurs
- 🖥 Démo interactive : visualiseur de décomposition, constructeur d'escalade, simulateur de circuit breaker
- ✅ Quiz de validation (10 questions — objectif 8/10)

**Session 5 :** évaluation et benchmarking des systèmes agentiques

> **À retenir en une phrase :** un système multi-agents se juge sur
> **l'architecture de ses mauvais jours.**

*Applied AI — Yann Isola*
