Reproductibilité
docs.stripe.com · audité le 2026-07-27 · AgentFit v3.4.0 · rubrique v4 · Claude Opus 5
Une note que l'on ne peut pas reproduire n'est pas une mesure. Voici donc le test que nous vous devons : nous avons audité le même site six fois en une journée — trois fois en demandant à un modèle de langage d'appliquer une grille de documentation, trois fois en lançant AgentFit — et nous publions ce que nous avons enregistré, y compris ce qui nous dérange.
Demander à un modèle · 3 exécutions
78 · 80 · 65 15 points d'écart
Trois agents indépendants, le même prompt public, le même modèle, le même jour. Aucun ne savait rien des autres, ni de la raison pour laquelle on l'interrogeait.
Lancer AgentFit · 3 exécutions
57 · 57 · 57
0 points d'écart
eabbb2f6abcdea473b600dc4ee8e25282fd002344f1e8425519caa32ff858b01
un seul sha256, trois fichiers
Trois audits en direct à la suite, trois fichiers JSON, une seule somme de contrôle. Pas de cache : chaque exécution est repartie interroger le site.
Lisez les colonnes de haut en bas, pas en travers. La grille de gauche compte 26 critères, AgentFit en compte 28, et les pondérations diffèrent : une spécification lisible par une machine vaut 25 points à gauche et 17 ici. 57 n'est pas « la bonne réponse » et 78 n'est pas « faux ». L'affirmation de cette page est plus étroite, et c'est la seule que les données soutiennent : l'une de ces deux procédures rend le même nombre à chaque fois, l'autre non.
Là où les exécutions ont divergé
Les trois exécutions du modèle ont fait le travail sérieusement : elles ont récupéré des URL en direct, parcouru les critères un par un et rapporté ce qu'elles trouvaient. D'après notre propre décompte dans les transcriptions — que nous ne publions pas — chaque exécution a effectué entre 36 et 47 appels d'outils, et aucune n'a inventé de fait. Prenez ces deux affirmations comme notre lecture des exécutions, pas comme quelque chose que les fichiers ci-dessous vous permettent de vérifier. Elles ont tout de même terminé à 15 points d'écart, dont onze dans une seule catégorie.
| Catégorie | Max | A | B | C | Écart |
|---|---|---|---|---|---|
| A — Découverte | 18 | 13 | 13 | 12 | 1 |
| B — Artefacts de page | 22 | 12 | 13 | 12 | 1 |
| C — Spécification lisible par une machine | 25 | 20 | 21 | 10 | 11 |
| D — Contenu | 20 | 18 | 19 | 18 | 1 |
| E — Rendu et hygiène | 15 | 15 | 14 | 13 | 2 |
| Total | 100 | 78 | 80 | 65 | 15 |
Deux sortes de désaccords sont mélangées ici. Les petits — A5, B3, B5, B6, D1, D3, D6, un point par-ci par-là, dans les deux sens — sont ce que l'on attendrait de trois lecteurs attentifs de la même page : l'un a trouvé les onglets SDK dans le DOM, les deux autres ont conclu qu'ils n'existent qu'à l'intérieur d'une charge JavaScript. S'agit-il d'un bruit de mesure ordinaire ? Trois exécutions ne peuvent pas le dire ; sur ce point nous supposons. Seul le désaccord sur D1 est visible dans les résumés publiés — le reste vient de notre propre comparaison des trois transcriptions. Le grand désaccord est d'une autre nature, et c'est la raison d'être de cette page.
Les autres critères de la catégorie C ont obtenu exactement 10 points dans chacune des trois exécutions. Tout l'écart de onze points tient à deux critères.
C1a : les mêmes faits, trois notes différentes
Le critère C1a — issu de la grille à 26 critères du modèle ; AgentFit fusionne découverte et validité en un seul C1 — demande si l'API dispose d'une spécification lisible par machine qu'un agent peut atteindre. Les trois exécutions ont établi exactement les deux mêmes faits à propos de docs.stripe.com :
- les emplacements conventionnels — /openapi.json et ses voisins — renvoient 404 ;
- une spécification OpenAPI valide et complète est publiée dans le dépôt GitHub de Stripe.
Personne n'a eu tort. Personne n'a halluciné. Les notes : 0 sur 8, 3 sur 8 et 4 sur 8.
| Critère | A | B | C |
|---|---|---|---|
C1a · spec discoverable | 3/8 | 4/8 | 0/8 |
C1b · spec valid | 7/7 | 7/7 | 0/7 |
E5 · terms of use | 3/3 | 2/3 | 1/3 |
Le désaccord ne porte pas sur le site. Il porte sur une question que la grille n'a jamais posée : une spécification compte-t-elle comme publiée si elle réside sur le domaine de quelqu'un d'autre et demande un saut de plus ? C'est un véritable embranchement sans réponse évidente, et chaque exécution s'y est présentée seule, au milieu d'une longue tâche, sans règle à consulter. Trois exécutions, trois politiques.
À partir de là, la divergence se propage. C1b note la qualité de la spécification trouvée. L'exécution qui a décidé que GitHub ne comptait pas n'avait rien à valider et a mis 0 sur 7. Les deux qui l'ont récupérée ont mis 7 sur 7 — puis ont annoncé un nombre de chemins différent, 414 et 136, parce que le dépôt de Stripe contient plus d'un fichier de spécification et qu'elles n'ont pas pris le même (spec3.json et spec3.yaml). Deux exécutions d'accord sur la note pour des raisons opposées.
E5 — « les conditions d'utilisation sont-elles indiquées ? » — a donné 3/3, 2/3 et 1/3. Une exécution a trouvé en pied de page un lien vers stripe.com/legal/ssa. Une autre a rejeté ce lien et a plutôt validé la directive Content-Signal du robots.txt. La troisième a cherché /terms, /legal et /privacy sur l'hôte de la documentation, a obtenu trois 404 et a noté ce qu'elle voyait. Trois lectures défendables d'une même ligne de grille.
L'autocontrôle a varié lui aussi
Le prompt embarque une ancre de calibration : un tableau de sites déjà mesurés où Stripe figure à 56, mesuré le 13 mai 2026. Il est là précisément pour repérer la dérive. C'est aussi ce tableau qui explique que le site ici soit docs.stripe.com — Stripe figurait déjà dans l'ancre de calibration de quelqu'un d'autre avant que nous ne lancions quoi que ce soit : le site a été choisi pour nous, et choisi avant l'expérience.
Les trois exécutions s'en sont servies. Les trois ont remarqué leur propre écart — +22, +24, +9. Les trois ont ensuite réexaminé leurs critères, raisonné sur ce qui avait pu changer depuis mai, et conclu que l'ancrage était périmé et que leur propre nombre valait mieux. Trois fois, indépendamment, dans le même sens, avec trois nombres différents.
C'est le passage que nous trouvons le plus instructif. Ce n'est pas de la négligence : c'est à quoi ressemble un travail consciencieux quand la procédure comporte encore un paramètre libre. Un autocontrôle dont le verdict change d'une exécution à l'autre n'est pas un autocontrôle.
L'ancrage se trouve à un point du 57 d'AgentFit. N'y lisez rien : c'est une mesure manuelle, sur une autre grille, d'un autre mois, et ce n'est pas une preuve que 57 est la note véritable.
Pourquoi le code répond la même chose
Rien d'astucieux. Chaque critère est une requête plus une règle, et les deux sont écrites avant que l'audit ne commence.
| Critère | Résultat | Requête | Enregistré |
|---|---|---|---|
| A1 · index llms.txt | présent 3/3 | GET /llms.txt → 200 | Stripe Documentation |
| C1 · découverte de la spécification | absent 0/8 | liste d'URL fixe, aucune réponse | — |
| D2 · réalisme des exemples | présent 5/5 | page d'endpoint échantillonnée → 200 | placeholder_ratio = 0.00, 0/3 blocks |
| E6 · accessibilité | partiel 1/4 | page d'accueil → 200 | a11y: 3 violations (e.g. button-name) |
D2 est celui qui fait appel à l'apprentissage automatique. Deux petits classifieurs tournent à l'intérieur de l'audit : l'un note le réalisme d'un exemple de code, l'autre juge la complétude d'une page d'endpoint. Ce sont des graphes ONNX à poids figés, compilés dans le binaire. La même entrée donne le même tenseur et le même flottant, à chaque fois. Le déterminisme n'exige pas de renoncer aux modèles ; il exige de renoncer aux modèles dont on ne peut pas figer la sortie.
C1 est précisément la bifurcation que les trois exécutions ont franchie différemment, et AgentFit y met 0 sur 8. Notre règle n'est pas « GitHub ne compte pas » : c'est une liste fixe d'endroits où regarder — dix-sept chemins conventionnels sur l'hôte audité, un lien api-catalog RFC 9727, et tout lien vers une spécification trouvé sur la page d'accueil que nous avons récupérée. Sur docs.stripe.com, aucun de ces chemins n'a renvoyé de spécification et cette page ne portait aucun lien de ce type : rien n'a donc été trouvé. Une règle qui suivrait un saut de plus jusqu'au dépôt d'un éditeur ne serait pas moins correcte. La nôtre est simplement fixée avant le début de l'audit, appliquée à l'identique à chaque site du corpus, et son barème est publié sur la page de la grille. Toute la différence est là : pas une meilleure réponse, une réponse figée. Comment C1 est noté.
Comparez-le à une exécution en direct
Le dépôt est privé et nous ne publions aucun binaire : nous n’allons donc pas vous dire de le compiler — vous ne le pouvez pas. Ce qui reste vérifiable sans nous est plus étroit, et nous préférons le dire exactement. Les fichiers ci-dessus ont été écrits par une compilation en ligne de commande, sur notre machine, le 27 juillet 2026. Le service public est une autre compilation, tournant ailleurs, et il vous remettra son propre rapport pour le même site. Si la procédure est bien celle que nous décrivons, les deux s’accordent sur chaque note. Voici comment faire cette comparaison, et ce qu’elle vaut.
Sans rien exécuter (aucun quota)
Le jeu de données public porte l’identifiant d’exécution du dernier audit valide de chaque hôte. Prenez celui de Stripe, relisez cette exécution depuis l’API, réduisez les deux documents aux champs notés, puis comparez-les :
$ RUN=$(curl -s https://agentfit.dev/dataset.csv \
| awk -F, '$1=="docs.stripe.com" && $3=="true" {print $7; exit}')
$ curl -s https://agentfit.dev/static/reproducibility/agentfit-run1.json \
| jq -S '{total_score, max_score, categories,
criteria: [.criteria[] | {id, status, score, max}]}' > ours.json
$ curl -s https://agentfit.dev/api/public/audit/$RUN \
| jq -S '.report | {total_score, max_score, categories,
criteria: [.criteria[] | {id, status, score, max}]}' > theirs.json
$ diff ours.json theirs.json
Le 27 juillet 2026, ce diff n’a rien imprimé : 200 lignes, aucune sortie. Deux compilations différentes, deux machines différentes, les mêmes 28 verdicts.
L’exécuter vous-même (une fois par site et par jour)
Collez https://docs.stripe.com dans le formulaire de la page d’accueil. À la fin, l’adresse affiche /r/<identifiant>. Mettez cet identifiant à la place de $RUN et lancez les deux mêmes commandes. Notez ce que cela ajoute et ce que cela n’ajoute pas : l’audit part toujours de nos serveurs, donc ce qui est à vous, c’est la requête et l’instant, pas le point de sortie.
Un audit du même site par réseau et par 24 heures. Une deuxième tentative renvoie 429 et aucun identifiant d’exécution — délibérément, pour qu’on ne puisse pas braquer le service sur la documentation d’autrui comme un marteau. Si vous butez dessus, la méthode ci-dessus n’exige aucune soumission. Le serveur MCP partage ce quota : demander à un agent de réauditer le même site le même jour se heurte au même mur. Et devant celui-là s’en dresse un second, qui ne vous appartient pas : deux audits du même hôte en dix minutes, par n’importe qui, épuisent une réserve partagée, et l’appelant suivant reçoit 429 avec le code rate_limited plutôt que site_quota. Celui-là se dissipe tout seul en dix minutes.
Sans curl ni jq
Ouvrez agentfit-run1.json dans un onglet et la page de rapport de l’exécution, /r/<identifiant>, dans un autre. Sept nombres tranchent : les six totaux de catégorie et le total général. Ils sont imprimés ici, pour que cette page suffise.
| Catégorie | Note | Max |
|---|---|---|
| A — Découverte | 11 | 14 |
| B — Éléments de page | 11 | 21 |
| C — Contrat d'API | 4 | 17 |
| D — Contenu | 16 | 23 |
| E — Rendu et hygiène | 15 | 21 |
| F — Capacités d'agent | 0 | 4 |
| Total | 57 | 100 |
Trois choses différeront, et c’est normal
- auditor — l’étiquette de compilation de ce qui a écrit le rapport. Chez nous elle dit agentfit/v3.4.0, parce que c’était la compilation locale du jour ; un rapport frais dira ce qui est déployé aujourd’hui. Elle nomme le binaire, pas la mesure — c’est précisément pourquoi la comparaison ci-dessus l’écarte.
- evidence_snippet — les mots que le site a servis à la machine venue les chercher. Nos trois exécutions partaient d’une adresse à laquelle Stripe répondait en allemand ; l’exécution du service, elle, a reçu de l’anglais. Le 27 juillet, tout l’écart était là : B1, B3 et E1 portaient un texte différent sous une URL identique, un code de réponse identique et une note identique. L’extrait consigne l’entrée, pas le verdict.
- L’ordre des clés. Nos fichiers sont ce que l’outil a écrit ; l’API renvoie le même objet après un aller-retour par Postgres, qui range les clés de la plus courte à la plus longue. Comparer ces deux documents octet à octet est sans espoir et ne dirait rien des notes — jq -S met les deux dans le même ordre, et c’est ce qui donne un sens au diff.
Une correspondance signifie une seule chose : la même procédure, sur le même site, a rendu le même verdict depuis une autre compilation, sur une autre machine. Elle ne signifie pas que 57 est la bonne note — rien sur cette page ne le prétend. Et elle dépend du site : si docs.stripe.com a changé depuis le 27 juillet 2026, les nombres bougent, et c’est l’instrument qui fonctionne, pas qui casse. Regardez la date audited_on dans la ligne du jeu de données avant de conclure quoi que ce soit d’un écart.
Ces fichiers sont gelés et nous ne les régénérons pas à chaque version. Deux raisons : la somme de contrôle affichée sur cette page est prise sur ces octets précis, et chaque nombre de la prose ci-dessus en a été extrait — une pièce à conviction qui se rafraîchit toute seule ne pourrait porter ni l’un ni l’autre. Le fil d’alarme est du côté vivant, pas dans le fichier gelé : la colonne 9 de la ligne du jeu de données que vous venez de lire est rubric_version, et tant que cette page tient, elle doit valoir 4. La pièce à conviction est antérieure à cette colonne et ne la porte pas ; le nombre auquel la comparer est donc celui imprimé dans cette phrase — nous l’affirmons au lieu de vous le faire trouver, c’est une garantie plus faible, et c’est pourquoi elle est écrite noir sur blanc. Si cette colonne affiche un jour autre chose, c’est que la règle a changé après l’écriture de cette page : la comparaison champ à champ est nulle, et vous avez sous les yeux un document historique. Nous préférons que la page le dise tout haut plutôt qu’elle se rejoue en silence jusqu’à tomber d’accord.
Deux des 28 critères, D2 et C3, sont tranchés par de petits classifieurs plutôt que par une règle. Ils sont compilés dans le binaire et versionnés avec lui — il n’y a pas de serveur de modèles, et le réentraînement de personne ne déplace une note déjà publiée.
Ce que nous n'affirmons pas
- Nous ne prétendons pas que 57 soit la bonne note. Nous ne publions pas de score vrai pour docs.stripe.com, parce que personne n'en a un. Nous publions une procédure et son résultat. Et la reproductibilité est une condition nécessaire, pas suffisante : echo 57 est lui aussi parfaitement reproductible — ce qu'il n'est pas, c'est sensible au site. Pour juger si la nôtre l'est, allez voir /browse : la même procédure y répartit des sites réels sur toute l'échelle.
- Nous ne comparons pas deux nombres. Les grilles diffèrent : nombre de critères différent, pondérations différentes. La comparaison a du sens à l'intérieur de chaque colonne, pas entre elles.
- Il s'agit d'un site, d'un jour, d'un modèle, de trois exécutions. C'est une démonstration, pas une étude, et nous ne promettons pas de la relancer périodiquement.
- Le modèle est Claude Opus 5 — celui-là même qui a aidé à construire cette page et une bonne part de ce service. Nous montrons une variabilité que nous avons reproduite dans l'outil dont nous nous servons nous-mêmes, tous les jours ; lisez ces lignes en le sachant. Nous ne disons pas que les modèles de langage sont mauvais à cet exercice. Nous disons que nous n'avons pas réussi à en obtenir deux fois le même nombre.
- Le déterminisme est une propriété de la procédure, pas une promesse sur l'internet. Nos trois exécutions sont parties d'une seule machine un seul jour, et le site a servi à cette adresse des corps de page en allemand — visible dans les preuves de B1 et E1, et une exécution du modèle a enregistré la même chose. Changez le point de sortie et vous changez l'entrée. Même entrée, même sortie : toute la garantie tient là.
Ce qu'un modèle réussit vraiment ici
Rien de tout cela ne rend les exécutions du modèle inutiles — cela en fait un instrument d'une autre nature. Le diagnostic de chacune des trois exécutions est bon en soi : pourquoi une spécification manquante pénalise un agent, quoi publier à la place, lesquelles des pages de Stripe survivraient à une lecture sans navigateur. C'est de l'explication, et un modèle explique à merveille.
Un modèle de langage excelle à expliquer quoi corriger. Il fait un mauvais instrument pour mesurer si vous l'avez corrigé. Nous, c'est le second. AgentFit est l'instrument ; portez son nombre à un modèle et demandez-lui quoi en faire. C'est la bonne division du travail, et c'est celle que nous pratiquons.
Les données
Les six exécutions sont servies depuis ce site. Les trois rapports AgentFit sont les fichiers écrits par l'outil, octet pour octet — c'est sur eux qu'est calculée la somme de contrôle. Les trois fichiers du modèle sont les nôtres :
Les transcriptions brutes ne sont pas publiées. Pour la colonne du modèle, vous obtenez des résumés structurés que nous avons rédigés après coup à partir de ces transcriptions : la note finale, les totaux par catégorie et les remarques par critère citées plus haut. Les notes et les totaux sont le résultat propre de chaque exécution ; la formulation des remarques est la nôtre.
- trois rapports AgentFit et la somme de contrôle —
run1.json·run2.json·run3.json·sha256.txt - trois exécutions du modèle, sous forme de résumés rédigés par nous d'après les transcriptions —
runA.json·runB.json·runC.json - le prompt de la colonne modèle, épinglé au commit que nous avons exécuté —
ai-ready-audit
Rien n'a été écarté — ni une exécution, ni un site. Trois audits du modèle et trois audits de l'outil, sur le seul site que le tableau de calibration du prompt nommait déjà ; nous n'en avons lancé aucun autre et n'avons rien jeté. Si une quatrième exécution était revenue à 57, nous l'aurions publiée aussi et cette page serait plus courte.
Gratuit, sans inscription, environ 30 secondes. 28 critères, une preuve HTTP sous chacun, un lien de rapport partageable — et le même nombre demain si rien n'a changé.