# Gérer un agenda par agent IA a créé deux fois le même créneau de rendez-vous

> Un agent chargé de prendre des rendez-vous a réservé deux fois le même créneau après un délai réseau. Symptôme, diagnostic d'un appel rejoué, et correctif par une clé d'idempotence.

- Auteur : Clément Hadrot
- Publié le : 2026-08-05
- Mis à jour le : 2026-08-05
- Catégorie : IA &amp; MCP
- URL : https://wpmoderne.dev.wordpress-developpement.fr/ia-mcp/agenda-agent-ia-double-creneau-idempotence/

## L’essentiel

- L'agent a rejoué un appel de réservation après une absence de réponse, sans savoir qu'il avait déjà réussi
- Le symptôme n'était visible que par un second rendez-vous silencieusement écrasé
- Une clé d'idempotence sur l'outil de réservation a réglé le problème sans changer la logique de l'agent

« Mon rendez-vous du mardi a disparu de la confirmation que j'avais reçue » : ce signalement d'un client, une semaine après sa prise de rendez-vous par un agent conversationnel, a mené à la découverte d'un incident précis. Le créneau du mardi avait bien été réservé une première fois, puis une seconde réservation, pour un autre client, avait silencieusement écrasé la première dans le système de planification utilisé.

Ce texte détaille le symptôme observé, le diagnostic mené à partir du journal des appels de l'agent, le correctif appliqué à l'outil de réservation, et la mesure de prévention mise en place ensuite. Il ne traite pas de l'interface de prise de rendez-vous elle-même, restée inchangée du début à la fin de ce travail.

## Symptôme : un créneau réservé deux fois

Le système de planification utilisé ne permettait normalement pas de double réservation sur un même créneau : une contrainte d'unicité existait bien en base pour l'empêcher. Pourtant, l'historique montrait deux tentatives de réservation distinctes pour le même créneau, à quelques secondes d'intervalle, la seconde ayant simplement remplacé la première plutôt que d'être rejetée, en raison d'une logique de mise à jour trop permissive côté outil.

## Diagnostic : un appel rejoué après un délai réseau

Le journal de la session de l'agent a montré que la première tentative de réservation avait bien abouti côté serveur, mais que la confirmation n'était jamais parvenue à l'agent dans le délai attendu, en raison d'une latence réseau ponctuelle. L'agent, interprétant cette absence de réponse comme un échec, a relancé l'appel de réservation pour le même créneau quelques secondes plus tard.

> L'essentiel à retenir : L'agent a rejoué un appel de réservation après une absence de réponse, sans savoir qu'il avait déjà réussi ; Le symptôme n'était visible que par un second rendez-vous silencieusement écrasé ; Une clé d'idempotence sur l'outil de réservation a réglé le problème sans changer la logique de l'agent

```
{
  "appels": [
    { "outil": "reserver_creneau", "t": "09:14:02.100", "params": { "creneau_id": 4821, "client_id": 118 }, "reponse": null },
    { "outil": "reserver_creneau", "t": "09:14:07.340", "params": { "creneau_id": 4821, "client_id": 118 }, "reponse": "ok" }
  ]
}
```

## Pourquoi la contrainte d'unicité n'a pas suffi

La contrainte d'unicité en base empêchait bien deux clients différents de réserver le même créneau simultanément, mais l'outil de réservation, lui, traitait chaque appel comme une mise à jour du créneau plutôt que comme une création stricte : un second appel avec les mêmes paramètres mettait simplement à jour l'enregistrement existant, sans erreur, ce qui masquait le rejeu plutôt que de le signaler.

## Correctif : une clé d'idempotence portée par le créneau et le client

Le correctif a consisté à faire porter, par chaque appel de réservation, une clé d'idempotence dérivée de l'identifiant du créneau et de celui du client, plutôt que de laisser l'outil accepter silencieusement tout appel répété comme une simple mise à jour.

```
function agence_reserver_creneau_callback( $params ) {
    $creneau_id = $params['creneau_id'];
    $client_id  = $params['client_id'];
    $cle = 'reservation-' . $creneau_id . '-' . $client_id;

    $existante = agence_trouver_reservation_par_cle( $cle );
    if ( $existante ) {
        return $existante; // Renvoie le résultat déjà obtenu, sans nouvel effet
    }

    return agence_creer_reservation( array(
        'creneau_id' => $creneau_id,
        'client_id'  => $client_id,
        'cle'        => $cle,
    ) );
}
```

## Prévention : distinguer explicitement création et mise à jour

Au-delà de la clé d'idempotence, l'outil de réservation a été scindé en deux fonctions distinctes côté callback, l'une strictement réservée à la création d'une réservation, l'autre à la modification d'une réservation déjà existante. Cette séparation, absente avant l'incident, empêche désormais qu'un appel de création rejoué ne puisse jamais, même en cas d'erreur de logique ailleurs, se comporter comme une mise à jour silencieuse.

- Clé d'idempotence dérivée d'identifiants stables, jamais générée à chaque appel.
- Séparation stricte entre création et mise à jour dans la logique de l'outil.
- Contrainte d'unicité en base conservée comme filet de sécurité, pas comme seule protection.

> Une absence de réponse ne signifie jamais un échec certain : elle signifie seulement une incertitude, et une incertitude ne devrait jamais se résoudre par un nouvel appel identique sans protection contre son propre rejeu.

## En résumé

Un agent qui rejoue un appel après un délai réseau applique une logique parfaitement raisonnable en apparence ; le risque réel se situe dans l'outil appelé, qui doit être capable de reconnaître un rejeu et de renvoyer le résultat déjà obtenu plutôt que de produire un second effet. Une clé d'idempotence, combinée à une séparation stricte entre création et mise à jour, reste la protection la plus fiable contre ce type d'incident.
