# Limite de débit d'une API d'agent IA : des retries qui restent sûrs

Un agent qui atteint un quota doit ralentir, comptabiliser chaque tentative supplémentaire et s'arrêter avant de transformer un refus temporaire en problème opérationnel généralisé. « Réessayer en cas d'échec » est un conseil acceptable pour quelqu'un qui clique sur un bouton. C'est un conseil dangereux pour un processus capable d'envoyer des requêtes plus vite que quiconque ne peut le surveiller.

La difficulté ne consiste pas à attendre quelques secondes. Elle consiste à préserver le sens de la tâche initiale alors que la première requête a pu échouer avant d'atteindre le fournisseur, après que celui-ci l'a exécutée, ou parce que plusieurs exécutions d'agents ont épuisé une même réserve partagée. Une conception sûre distingue ces situations et fait de l'intervention humaine une issue prévue, plutôt qu'une exception embarrassante.

## Un 429 est une instruction de planification, pas une erreur à marteler

HTTP 429 signifie que le serveur refuse la requête parce que le client en a envoyé trop pendant une période contrôlée par le serveur. La RFC 6585 définit ce code d'état et précise que la représentation de la réponse doit expliquer la situation et peut inclure un en-tête Retry-After. Cette formulation compte : un 429 n'indique pas à l'agent qu'une répétition immédiate de la même requête a la moindre chance de réussir.

L'agent doit d'abord enregistrer le fournisseur, l'endpoint, l'identité de l'identifiant ou du compte, la catégorie de requête, le statut de la réponse et les éventuels en-têtes de limitation. Il doit ensuite placer le travail dans un état différé. Ne laissez pas le modèle de langage décider de cela en prose après chaque échec. La couche de transport a besoin d'un comportement déterministe, car un modèle soumis à la pression d'une tâche essaiera souvent un endpoint voisin, modifiera un filtre ou créera un second chemin de requête. Ces improvisations peuvent multiplier les appels tout en produisant le même refus.

Un refus s'applique souvent à un compartiment plus large que la requête actuelle. Les fournisseurs définissent couramment des limites par compte, projet, jeton, adresse IP, famille d'endpoints ou combinaison de ces éléments. Un agent peut recevoir un 429 tandis qu'un autre agent utilisant le même identifiant continue brièvement à fonctionner. Cela ne prouve pas que le premier peut réessayer sans risque. Le fournisseur peut utiliser plusieurs compartiments, ou le second processus peut être en train de consommer la dernière capacité disponible.

Conservez un registre local pour chaque périmètre connu. Si le service documente une limite par jeton, regroupez les requêtes par jeton. S'il ne fournit que des indications au niveau du compte, partez du principe que tous les workers de ce compte partagent le même compartiment jusqu'à preuve du contraire. Un registre local ne reflétera pas parfaitement le compteur interne du fournisseur, mais il empêchera vos workers de se disputer aveuglément la capacité disponible.

La spécification IETF RateLimit Fields, RFC 9333, définit `RateLimit-Limit`, `RateLimit-Remaining` et `RateLimit-Reset`. Ces champs décrivent l'état du quota, mais n'annulent pas un 429 déjà reçu. Utilisez-les pour cadencer les futures requêtes et éviter de remplir la file plus vite que le fournisseur ne peut les accepter. Suivez la portée indiquée dans la documentation du fournisseur, car un en-tête seul ne permet pas toujours de savoir s'il s'applique à un endpoint ou à un compte entier.

Un enregistrement d'état utile ressemble à ceci :

```json
{
  "provider": "billing-api",
  "scope": "account:ops-team",
  "request_class": "write:invoice",
  "next_allowed_at": "2025-04-08T14:12:31Z",
  "remaining": 0,
  "reset_after_seconds": 60,
  "source": "HTTP 429 Retry-After"
}
```

Le runner de l'agent doit lire cet enregistrement avant de lancer un nouvel appel. Attendre à l'intérieur d'un gestionnaire de requête convient pour une petite commande en ligne de commande. Dès que plusieurs workers, outils ou tâches reprises sont concernés, une file avec un `next_allowed_at` visible est préférable. Le planificateur peut ainsi choisir un autre travail au lieu de garder un processus occupé et fournir à l'opérateur une explication claire du délai.

## Les budgets de nouvelles tentatives empêchent une petite panne de devenir une avalanche

Un budget de nouvelles tentatives impose une limite stricte au travail supplémentaire qu'une tâche peut créer après l'échec d'un appel. Comptez les tentatives et le temps d'attente écoulé. Un simple nombre maximal échoue lorsque le service demande aux clients d'attendre plusieurs minutes. Une simple limite de temps échoue lorsqu'une boucle rapide épuise le quota d'un compte en quelques secondes.

Utilisez des budgets distincts pour chaque tâche et chaque périmètre partagé du fournisseur. Le budget d'une tâche répond à la question : « Quelle part d'incertitude cet objectif peut-il tolérer ? » Le budget d'un périmètre répond à une autre question : « Quelle perturbation l'ensemble du travail actuel peut-il imposer à ce fournisseur ? » Si dix tâches reçoivent chacune trois nouvelles tentatives, le compte peut tout de même recevoir trente requêtes supplémentaires. C'est ainsi qu'une politique supposée prudente devient une rafale.

Pour les lectures ordinaires, j'utilise un petit budget dont l'échéance est plus courte que la valeur métier de la réponse. Pour les écritures, je consacre beaucoup moins de budget aux tentatives aveugles. Attendre une personne peut coûter moins cher que dupliquer un paiement, envoyer des notifications en double ou appliquer deux fois un déploiement.

Représentez la règle sous forme de données que l'agent ne peut pas modifier à la légère :

```yaml
request_classes:
  read:
    max_attempts: 4
    max_wait_seconds: 90
    retry_statuses: [408, 429, 500, 502, 503, 504]
  idempotent_write:
    max_attempts: 3
    max_wait_seconds: 120
    retry_statuses: [408, 429, 502, 503, 504]
  uncertain_write:
    max_attempts: 1
    max_wait_seconds: 0
    retry_statuses: []
shared_scope:
  max_delayed_requests: 25
  max_concurrent_requests: 2
```

Cet extrait empêche une panne bien connue : l'agent reçoit un délai d'attente après avoir soumis une écriture, suppose que rien ne s'est produit et réessaie auprès d'un fournisseur déjà sous pression. La catégorie `uncertain_write` impose au contraire une vérification de l'état ou une escalade. Elle ne récompense pas la persévérance de l'agent lorsque celle-ci peut modifier le résultat.

Le budget doit comptabiliser chaque tentative réelle, y compris celles lancées par les bibliothèques. J'ai vu des contrôles de retry déclarés dans une application alors qu'un client HTTP, un runner de workflow et un proxy réessayaient chacun de leur côté. Le nombre de requêtes semblait mystérieux jusqu'à ce que quelqu'un examine les trois configurations par défaut. Choisissez une seule couche responsable des retries. Configurez toutes les autres pour qu'elles remontent les erreurs sans réessayer, ou rendez leur comportement explicite et incluez-le dans le budget total.

L'épuisement du budget doit produire un résultat final avec son contexte, et non une erreur générique. Le résultat doit indiquer si la requête initiale a été envoyée, si une réponse est arrivée, combien de tentatives ont eu lieu, combien de temps la tâche a attendu et quelle est la prochaine action sûre. L'agent peut alors poursuivre un travail sans rapport ou présenter une escalade précise au lieu de se demander sans cesse s'il faut « réessayer ».

## Le backoff a besoin de jitter et d'une limite

Le backoff exponentiel réduit la pression après des refus répétés en augmentant le délai entre les tentatives. Le jitter empêche de nombreux workers ayant échoué au même moment de revenir en une seule vague synchronisée. Les deux mécanismes ont leur place dans le client, même si le fournisseur publie une heure de réinitialisation, car la concurrence interne peut elle-même créer une avalanche.

Pour le numéro de tentative `n`, où la première nouvelle tentative correspond à `n = 1`, calculez une limite puis choisissez un délai aléatoire à l'intérieur de celle-ci :

```python
import random

BASE_SECONDS = 1.0
MAX_SECONDS = 60.0

def retry_delay(attempt_number: int) -> float:
    cap = min(MAX_SECONDS, BASE_SECONDS * (2 ** attempt_number))
    return random.uniform(0, cap)
```

Il s'agit d'un jitter complet. Il évite que chaque worker dorme exactement 2, 4, 8 puis 16 secondes. Les délais fixes sont populaires parce qu'ils rendent les journaux faciles à lire. Ils rendent aussi les nouvelles tentatives coordonnées faciles à prévoir, ce qui est l'inverse de ce dont un service très sollicité a besoin.

Lorsqu'une réponse fournit `Retry-After`, cette valeur prime sur un délai local calculé qui serait plus court. La RFC 9110 autorise `Retry-After` à prendre la forme d'un délai en secondes ou d'une date HTTP. Gérez les deux formats. Si la date est déjà passée à cause d'un décalage entre l'horloge de la machine et celle du fournisseur, appliquez un délai minimal raisonnable au lieu de relancer une boucle très rapide.

N'utilisez pas le backoff pour remplacer le cadencement du débit. Le backoff commence après un échec. Un seau de jetons, un seau percé ou une simple limite du planificateur contrôlent le débit avant l'échec. Si un fournisseur autorise un nombre connu de requêtes pendant un intervalle connu, cadences les requêtes en dessous de cette limite publiée et gardez une marge pour le travail interactif. Le planificateur doit aussi limiter la concurrence. Vingt requêtes simultanées peuvent consommer la capacité d'un court intervalle avant même qu'un worker ne lise une réponse `RateLimit-Remaining`.

Une règle de lancement concrète est simple : avant d'envoyer un appel, vérifiez l'heure de prochaine autorisation du périmètre partagé et la disponibilité d'un emplacement de concurrence. Après un 429, mettez à jour l'enregistrement du périmètre avant de programmer une nouvelle tentative. Cet ordre est important. Si les workers programment leurs nouvelles tentatives avant de publier le refus, chacun peut croire qu'il possède le prochain emplacement.

Plafonnez le délai et le temps d'attente total. Une courbe exponentielle sans limite peut différer une tâche de plusieurs heures et laisser une exécution obsolète active bien après la disparition de ses hypothèses. Après son échéance, l'agent doit échouer ou remettre la tâche à un planificateur, tout en conservant l'entrée initiale pour un examen ultérieur.

## L'idempotence détermine si une nouvelle tentative est sûre

Une requête idempotente produit le même effet attendu lorsqu'elle est répétée. Cela ne signifie pas que toute requête utilisant HTTP `PUT` est inoffensive dans toutes les applications, ni que chaque `POST` est dangereux. La RFC 9110 décrit des méthodes comme `PUT` et `DELETE` comme idempotentes dans leur intention, tandis que `POST` ne l'est généralement pas. Le contrat réel de l'API du fournisseur détermine le risque opérationnel.

Les lectures tolèrent généralement les nouvelles tentatives, à condition que l'agent accepte que les données renvoyées aient pu changer. Une suppression peut aussi tolérer une nouvelle tentative si supprimer une ressource déjà absente produit un résultat connu et acceptable. La création d'un paiement, d'une invitation, d'un ticket d'assistance, d'un bon de commande ou d'un déploiement ne tolère souvent pas la répétition aveugle. Ce sont les appels qui méritent le plus de prudence après un délai d'attente ou une réinitialisation de connexion.

Le domaine confond régulièrement deux états différents :

- Une requête qui n'a certainement pas atteint le fournisseur peut être renvoyée si l'opération elle-même l'autorise.
- Une requête dont le résultat est inconnu doit faire l'objet d'une réconciliation avant que l'agent n'envoie un nouvel effet de bord.

Une panne de connexion après l'écriture des octets par le client ne prouve pas que le fournisseur n'a rien fait. Le serveur peut avoir terminé l'opération puis perdu la connexion de réponse. L'agent ne peut pas déduire le résultat de l'absence de réponse.

Lorsqu'un fournisseur prend en charge une clé d'idempotence, générez un jeton stable par action logique et réutilisez-le pour toutes les nouvelles tentatives de cette action. Ne générez pas un nouveau jeton à chaque tentative. Un nouveau jeton indique au fournisseur que chaque tentative est une opération distincte, ce qui annule la protection.

```http
POST /v1/transfers HTTP/1.1
Idempotency-Key: transfer-7c41b5b9-7f5b-4f51
Content-Type: application/json

{"source":"acct_17","destination":"acct_42","amount":12500,"currency":"USD"}
```

Enregistrez le jeton avec la tâche et le contenu de la requête avant d'envoyer le premier appel. Si le runner redémarre, il doit récupérer le même jeton. Enregistrez aussi l'identifiant d'opération renvoyé par le fournisseur. Cet identifiant permet ensuite à un appel de réconciliation de s'informer sur l'action initiale sans la recréer.

Si l'API ne propose aucune fonction d'idempotence, recherchez un schéma de création puis interrogation. L'agent peut ajouter une référence externe générée par le client dans le contenu, puis rechercher cette référence après un échec incertain. Si l'API ne propose ni idempotence ni recherche fiable, n'automatisez pas les écritures répétées. Demandez à une personne d'examiner les enregistrements du fournisseur. Cela semble plus lent jusqu'à l'apparition du premier effet de bord dupliqué dans un système qu'il est impossible d'annuler proprement.

## Distinguez les limites de débit, les pannes et les mauvaises requêtes

Une politique de retry qui traite tous les statuts non positifs de la même manière masquera les défauts et gaspillera le quota. Classez la réponse avant de choisir un délai. Le code de statut, le corps de la réponse, le code d'erreur du fournisseur et la méthode de la requête comptent tous.

Utilisez cette distinction pratique :

- `429` signifie qu'il faut ralentir, respecter les indications du fournisseur et imputer l'événement au budget partagé de limitation.
- `408`, les réinitialisations de connexion et certains statuts `5xx` peuvent justifier une nouvelle tentative plafonnée, mais les écritures nécessitent toujours un chemin d'idempotence ou de réconciliation.
- `400`, `401`, `403`, `404` et `422` nécessitent généralement une requête corrigée, une autorisation modifiée ou une décision humaine. Les répéter est inutile.
- `409` nécessite un traitement propre à la ressource. Il peut signaler un doublon, un conflit de version ou un verrou détenu par un autre workflow.
- Une erreur spécifique du fournisseur indiquant l'épuisement d'un quota peut nécessiter d'attendre une réinitialisation de facturation ou une réinitialisation quotidienne, ce qui diffère d'une courte limite de rafale.

Ne traitez pas `503 Service Unavailable` comme une autre forme de `429`. Un 503 indique que le service ne peut pas traiter la requête à cet instant. Un 429 indique que le client a dépassé une limite. Les deux peuvent contenir `Retry-After`, mais un 429 doit pousser votre planificateur à réduire le débit local du périmètre concerné. Un 503 peut au contraire être régional, propre à un endpoint ou généralisé au fournisseur. Conservez cette distinction dans les métriques et les messages destinés aux opérateurs.

Le comportement d'un agent peut aussi provoquer des vagues de requêtes invalides. Après avoir reçu `422`, un modèle peut rappeler sans cesse un endpoint avec un filtre non pris en charge, ou réessayer `401` après la révocation de l'identifiant. Placez un disjoncteur autour des échecs identiques répétés. Par exemple, si le même endpoint, la même méthode et le même code d'erreur normalisé échouent plusieurs fois pendant une exécution, arrêtez ce chemin et renvoyez les détails de l'erreur à l'agent comme une contrainte. Ne le laissez pas modifier les espaces, réordonner les champs JSON et prétendre explorer de nouvelles options.

Une empreinte normalisée de la requête doit exclure les secrets et les en-têtes variables. Incluez la méthode, le modèle d'endpoint, les champs stables du contenu et le code d'erreur de l'API. Le runner peut ainsi identifier une boucle sans enregistrer les identifiants ni les corps sensibles complets.

## Les identifiants partagés ont besoin d'une seule file, pas d'agents bien intentionnés

Un identifiant d'API partagé crée un problème de coordination que les agents individuels ne peuvent pas résoudre par leur seule bonne volonté. Chaque processus ne voit que ses propres requêtes, sauf si un planificateur leur fournit un budget commun. Le backoff par agent réduit le risque de rafale, mais ne répartit pas équitablement un quota rare à l'échelle du compte.

Placez une file sortante devant le périmètre de l'identifiant partagé. Attribuez les priorités délibérément. Une modification de production approuvée par une personne peut devoir passer avant un travail d'inventaire en arrière-plan. Un agent qui découvre dix mille enregistrements doit les parcourir au rythme autorisé par le fournisseur, plutôt que de remplir la file avec toutes les requêtes de page en une fois.

La file doit permettre l'annulation. Si la tâche parente d'un agent se termine, supprimez ses requêtes différées avant leur réveil. Sinon, une tâche arrêtée peut continuer à consommer du quota plus tard et rendre la piste d'audit difficile à comprendre. L'annulation doit aussi libérer tout emplacement de concurrence réservé.

Utilisez une règle de regroupement pour les lectures sûres. Si cinq exécutions d'agents demandent le même enregistrement immuable pendant un court intervalle, effectuez une seule requête et distribuez le résultat à toutes les tâches en attente. Ne regroupez pas les lectures pour lesquelles la fraîcheur modifie le sens, comme un solde ou un statut d'approbation. Il s'agit de supprimer les doublons accidentels, pas de construire un cache qui renverrait des informations obsolètes à un agent prenant une décision.

Les limites de débit révèlent souvent un problème de planification plus profond. Un agent qui appelle un endpoint de détail pour chaque élément peut suivre exactement ses instructions tout en utilisant le mauvais mode d'accès. Avant d'ajouter des retries, recherchez des endpoints groupés, des contrôles de pagination, des requêtes conditionnelles, des webhooks, des tâches d'export ou un endpoint de recherche côté serveur. Ces changements réduisent le nombre d'appels avant que le fournisseur ne doive les refuser.

Sallyport peut garder les identifiants API hors du processus de l'agent tout en enregistrant les appels sortants individuels, mais l'appelant a toujours besoin d'une file et de contrôles de retry. La séparation des identifiants réduit l'exposition des secrets. Elle ne modifie pas le quota du fournisseur.

## L'escalade humaine doit préserver l'incertitude

Une personne doit reprendre la main lorsque le système ne peut pas établir si un effet de bord a eu lieu, lorsque l'attente restante dépasse l'échéance de la tâche, lorsque le quota partagé est épuisé ou lorsque des limites répétées indiquent un problème de configuration du compte. Une escalade n'est pas une notification générique du type « l'API a échoué ». C'est un dossier synthétique qui permet de décider sans reconstituer l'exécution à partir de journaux dispersés.

Envoyez à l'opérateur les informations suivantes :

- le résultat demandé par la tâche et l'action logique exacte qui s'est arrêtée ;
- le fournisseur, l'endpoint, la méthode, le périmètre du compte et l'empreinte nettoyée de la requête ;
- les horodatages des tentatives, les statuts, les en-têtes `Retry-After` ou de limitation et le budget consommé ;
- l'existence éventuelle d'un jeton d'idempotence, d'un identifiant d'opération du fournisseur ou d'une requête de réconciliation ;
- les prochaines options sûres, comme attendre, interroger l'état, augmenter le quota, modifier le plan ou annuler.

Ne placez pas par défaut le corps brut d'une requête dans une carte d'approbation. Il peut contenir des données personnelles, des documents internes ou une valeur qui semble inoffensive jusqu'à ce qu'elle arrive entre de mauvaises mains. Affichez un résumé concis et faites de l'accès aux détails une action d'audit volontaire.

L'approbation doit demander une décision, pas simplement un consentement à « continuer ». Pour une écriture incertaine, proposez « interroger l'opération existante », « réessayer avec le même jeton d'idempotence », « annuler » et, lorsque cela se justifie, « envoyer une nouvelle opération ». Cette dernière option doit préciser clairement qu'elle peut créer un second effet de bord. Les personnes prennent de meilleures décisions lorsque les choix décrivent le risque réel.

Une approbation pour maintenir un accès et une approbation pour une action externe précise sont deux contrôles différents. Une session peut rester autorisée alors qu'un agent a encore besoin d'un examen par appel pour un paiement, une écriture de production ou une nouvelle requête après un événement de limitation. Gardez ces décisions séparées dans l'interface comme dans le journal.

Lors de l'examen d'une escalade, commencez par la réconciliation. Interrogez le fournisseur avec la clé d'idempotence, la référence externe ou l'identifiant d'opération. Ne réessayez qu'après que la requête a montré qu'aucune action n'était terminée, ou après que le fournisseur a garanti la suppression des doublons. Cette séquence est plus lente qu'un renvoi aveugle d'un aller-retour réseau. Elle est plus rapide que la réparation d'un doublon introduit dans un registre en aval.

## Les journaux d'audit doivent expliquer l'action tentée et l'attente

Un journal d'audit utile répond à davantage que « la requête a-t-elle renvoyé 200 ? » Il doit montrer ce que l'agent a tenté, quelle autorisation l'y autorisait, ce que le service distant a renvoyé et comment le contrôleur de retry a réagi. Sans trace de la décision, une suite d'appels peut ressembler à une répétition imprudente, même lorsque le planificateur a respecté une valeur `Retry-After` documentée.

Enregistrez un événement avant le lancement, puis ajoutez les événements de résultat et de planification. Ne mettez pas d'identifiants en clair dans les métadonnées de la requête. Une séquence concise pourrait être :

```text
14:11:02 action_requested  task=sync-482 method=POST route=/records
14:11:02 action_sent       attempt=1 idempotency=rec-91f2
14:11:03 action_result     status=429 retry_after=30 scope=account:ops
14:11:03 retry_scheduled   attempt=2 due=14:11:33 budget_wait=30
14:11:33 action_sent       attempt=2 idempotency=rec-91f2
14:11:34 action_result     status=201 provider_id=r_893
```

Cette séquence sépare une action logique de ses tentatives de transport. Si une personne demande pourquoi deux appels `POST` ont eu lieu, l'enregistrement montre qu'ils partageaient un seul jeton d'idempotence et respectaient un délai imposé par le fournisseur. Si la seconde réponse avait expiré, l'événement suivant aurait dû être `reconciliation_required`, et non un autre `action_sent` automatique.

Les journaux inviolables ont un avantage pratique lors de l'analyse d'un incident : ils permettent à une équipe de vérifier qu'une exécution n'a pas effacé après coup des tentatives gênantes. Sallyport produit les journaux de session et d'appels à partir d'un journal d'audit chiffré et chaîné par hachage, et `sp audit verify` vérifie la chaîne hors ligne sans nécessiter de clé de coffre. C'est utile lorsqu'un opérateur doit distinguer une limitation du fournisseur d'une boucle de l'agent ou d'une nouvelle tentative manuelle tardive.

Les journaux ont aussi besoin de règles de conservation et de limites d'accès. Un chemin de requête, l'identifiant d'un compte fournisseur et un schéma temporel peuvent révéler une activité opérationnelle sensible même si le jeton lui-même n'apparaît jamais. Capturez suffisamment d'informations pour reconstituer la décision, puis limitez les personnes autorisées à rechercher et exporter l'enregistrement.

## Testez les chemins d'échec avant qu'un agent ne les découvre en production

Une conception de limitation n'est pas complète tant qu'un test n'a pas prouvé qu'elle arrête les appels, préserve l'idempotence et fait remonter l'incertitude. Simuler un seul 429 ne suffit pas. Il faut des workers concurrents et des échecs qui surviennent après que le service distant a peut-être agi.

Exécutez cette séquence dans un environnement de test ou contre une fausse API contrôlable :

1. Lancez trois tâches d'agents qui utilisent un seul périmètre de compte simulé et émettent la même requête de lecture sûre.
2. Renvoyez `429` avec `Retry-After: 10` à la première requête, puis vérifiez que le planificateur diffère tout le travail de ce périmètre au lieu de laisser les deux autres continuer à pleine vitesse.
3. Renvoyez une réponse positive après le délai et vérifiez que les heures de nouvelle tentative diffèrent légèrement au lieu d'arriver en une seule rafale.
4. Envoyez une écriture avec un jeton d'idempotence stable, simulez une coupure de connexion après que la fausse API a enregistré la ressource et vérifiez que le runner interroge l'état au lieu d'envoyer une nouvelle création.
5. Épuisez le budget de temps configuré et vérifiez que l'opérateur reçoit l'enregistrement d'escalade nettoyé, et que le travail annulé ne se réveille jamais plus tard.

Mesurez les tentatives sortantes au niveau de la fausse API, et pas seulement les appels de fonctions à l'intérieur de l'agent. Les compteurs internes ne voient pas les retries ajoutés par les bibliothèques HTTP ou les wrappers. Testez aussi un redémarrage : arrêtez le runner après la première écriture incertaine, restaurez son état persistant et vérifiez qu'il reprend la réconciliation avec le jeton d'idempotence d'origine.

Ne récompensez pas le banc de test de l'agent pour avoir fini par obtenir une réponse positive. Récompensez-le pour avoir effectué le bon nombre d'appels, attendu lorsqu'il y était invité et laissé une écriture ambiguë en suspens jusqu'à disposer de preuves. Un agent qui « accomplit la tâche » en dupliquant des actions externes n'a pas réussi le test.

Le premier contrôle à mettre en place est un budget partagé de nouvelles tentatives avec des résultats finaux explicites. Il transforme la limitation de débit, qui invitait à continuer d'essayer, en une décision opérationnelle accompagnée d'une trace, d'une échéance et d'une personne capable d'intervenir lorsque l'état distant est incertain.
