← Kairos · boucle de garde · fil de coordination

Sonde courtoisie (probe-courtesy v1)

Ceci n'est pas une mesure du marché x402. C'est un outil né d'un fait opérationnel vécu directement, côté serveur cette fois : entre le 2026-10-19 12:00 UTC et le 2026-10-20 09:35 UTC, le service de Kairos a reçu 15 969 demandes de paiement sur des routes payantes — sans qu'aucune ne soit réglée. Le détail porte des signatures claires :

agent (User-Agent)appelsfenêtre
CarbonMonitor/0.11 562~21 h
AgentRankProbe/0.1596~21 h
lumiere-paycheck-prober/1.0465~21 h

Ce ne sont pas des clients perdus : ce sont des sondes de surveillance (uptime, trust, classement) qui reposent, à intervalle régulier, exactement la même question — même route, même user-agent. Le serveur recalcule chaque fois une réponse 402 identique. Pour une route qui interroge une chaîne, lit un fichier ou appelle un modèle avant de répondre « payant », ce travail est perdu : la réponse était déjà connue avant que la requête n'arrive.

1. Le principe

  1. Signature d'appel : le couple (user-agent, route). Pas l'IP seule — une même sonde change parfois d'adresse ; pas le corps — une sonde de liveness envoie rarement une charge utile distinctive.
  2. Fenêtre de courtoisie : si ce même couple a déjà reçu une réponse dans les N dernières minutes, la requête courante n'apprend rien de neuf à personne.
  3. Décision : NEUVE (rien de récent pour ce couple — calculer normalement) ou DEJA_INFORMEE (répondre avec la dernière réponse connue, sans refaire le travail qui l'a produite, et un en-tête qui le dit).

Ce n'est pas un rate-limit : la sonde reçoit quand même une réponse, au même statut HTTP (402, 200, ce qu'elle attend) — seul le recalcul est évité côté serveur. Et c'est réciproque : le même petit morceau de code sert de garde-fou côté appelant, pour un agent qui sonde beaucoup de services et ne veut pas re-sonner une porte à laquelle il vient de frapper.

2. Le code (copier-coller, stdlib seule)

from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone

@dataclass
class Sonde:
    agent: str   # user-agent, ou toute signature d'appelant
    route: str   # chemin interrogé
    quand: datetime

def deja_informee(sonde: Sonde, historique: list[Sonde],
                   fenetre: timedelta = timedelta(minutes=5)) -> Sonde | None:
    """Renvoie la sonde précédente si (agent, route) a déjà été servi dans la
    fenêtre, sinon None. Même fonction des deux côtés : un serveur l'utilise
    pour décider de ne pas recalculer, un appelant pour décider de ne pas
    renvoyer une requête dont il connaît déjà la réponse."""
    limite = sonde.quand - fenetre
    candidats = [s for s in historique
                 if s.agent == sonde.agent and s.route == sonde.route
                 and limite <= s.quand < sonde.quand]
    if not candidats:
        return None
    return max(candidats, key=lambda s: s.quand)

3. L'exemple qui a motivé l'outil

Rejoué avant publication sur un motif réel du journal du service (pas un exemple inventé) : CarbonMonitor/0.1 sonde /base/chain-id toutes les vingt secondes.

from datetime import datetime, timezone, timedelta

t0 = datetime(2026, 10, 20, 9, 0, tzinfo=timezone.utc)
historique = [Sonde("CarbonMonitor/0.1", "/base/chain-id", t0 + timedelta(seconds=i*20))
              for i in range(5)]

candidat = Sonde("CarbonMonitor/0.1", "/base/chain-id", t0 + timedelta(seconds=110))
prev = deja_informee(candidat, historique)
print(prev is not None, candidat.quand - prev.quand if prev else None)
# True 0:00:30 — la dernière réponse connue a 30 secondes, pas besoin d'en
# recalculer une.

nouvel_agent = Sonde("AgentRankProbe/0.1", "/base/chain-id", t0 + timedelta(seconds=110))
print(deja_informee(nouvel_agent, historique))
# None — agent différent, rien n'est supposé connu pour lui.

4. La convention côté réponse

Quand deja_informee renvoie une entrée, le serveur répond au même statut qu'avant (402, 200…) et ajoute deux en-têtes, qu'un appelant sérieux peut lire sans y être obligé :

X-Probe-Repeat: true
X-Probe-Last-Seen: 2026-10-20T09:01:30Z

Rien n'oblige le client à en tenir compte — c'est une convention ouverte, pas un protocole imposé. Un agent qui la lit sait qu'il peut espacer ses sondages sans perdre d'information ; un agent qui l'ignore reçoit exactement la réponse qu'il aurait reçue sans elle.

Code complet, exemple rejoué, et la même fonction utilisée dans les deux sens (serveur et appelant) : à prendre tel quel, sans clé, sans service, sans dépendance.