MathTrail

Pour un public technique

Comment fonctionne le service

MathTrail se place entre le chat qu’une famille utilise déjà et le Google Drive du parent. Le modèle du chat écrit chaque problème, et le service décide s’il parvient jusqu’à l’enfant. C’est un seul programme : il n’appelle aucun modèle qui lui soit propre et ne garde rien d’une requête à l’autre.

  • Un binaire Go sur Cloud Run
  • Sans état
  • Sans base de données
  • Aucun appel LLM de sa part
  • MCP 2026-07-28 · MCP Apps

Client de chat1 · client

  • Claude
  • ChatGPT
  • autres hôtes MCP Apps

Le modèle du chat écrit le problème, et la carte le montre à l’enfant.

MCP · appels d’outils en HTTPS

MathTrail sur Google Cloud2 · exécution

Cloud Run · un binaire Go · sans état

Le cœur : du calcul pur

  • La règle
  • Scores
  • Vérifications
  • Solveur
  • Contenu

Autour de lui : OAuth, le serveur MCP, la carte, le scellement, les limites.

Drive API v3 · avec le propre jeton du parent

Google Drive3 · stockage

MathTrail/mathtrail-profile.json

Un fichier par enfant, dans le propre Drive du parent : la seule chose conservée longtemps.

01

Le plan

Qui parle à qui. Ni l’enfant ni l’adulte n’accèdent directement à MathTrail : tout passe par l’hôte du chat. Tout ce qui se trouve dans le bloc central fait partie d’un seul processus, et non d’un service distinct. Choisissez une partie pour voir ce qu’elle fait et à quoi elle est reliée.

Parties du service

Le chat de la famille

Claude dans un navigateur, sur ordinateur et sur téléphone · ChatGPT

Personnes

Hôte du chat

MathTrail

Un binaire Go · Cloud Run · son propre domaine

entre les parties, de simples appels de fonction

L’entrée

Outils : lire le profil, calculer, le réécrire

Calcul pur, sans réseau

Autour du cœur

Google et GitHub

Les seuls services hors du binaire

Le compte Google du parent

Google Cloud

GitHub

Choisissez une partie du plan : tout ce qui lui est relié reste bien visible, et le reste s’estompe.

Le chat de la famille

Adulte

Un parent ou un enseignant. Titulaire du compte du chat comme du compte Google : connecte MathTrail, se connecte une fois, crée le profil, mène la leçon dans le chat et suit les progrès. La seule personne qui se connecte, et la seule qui écrit.

Le chat de la famille

Enfant

Niveaux 1–6. Résout les problèmes sur la carte, dans le chat de l’adulte. N’a pas de connexion personnelle.

Hôte du chat

Le modèle de l’hôte

Claude ou ChatGPT. Écrit le problème, un solveur en Starlark, une explication pour chaque mauvaise réponse proposée et une autovérification ; mène la leçon avec des mots partout où les cartes ne s’affichent pas. MathTrail lui-même n’appelle aucun modèle.

Hôte du chat

Environnement d’exécution MCP Apps

Affiche la carte dans un cadre isolé, lui transmet la langue, la plateforme et la taille, et relaie les propres appels de la carte avec le jeton de l’hôte : la carte elle-même ne voit jamais le jeton.

Hôte du chat

Document de métadonnées du client

L’hôte se désigne par une adresse HTTPS. MathTrail récupère le document à cette adresse, avec une protection contre la SSRF, plutôt que de tenir un registre des clients.

MathTrail · l’entrée

Routeur HTTP

La seule entrée : /health, /mcp, les points de terminaison OAuth et .well-known. Des délais d’expiration, une limite de taille du corps des requêtes, un contrôle de l’en-tête Origin ; l’émetteur est son propre domaine, et tout autre Host reçoit une erreur 404.

MathTrail · l’entrée

Serveur d’autorisation

MathTrail est son propre serveur OAuth 2.1 avec PKCE : métadonnées, son propre écran de consentement, connexion avec Google, jetons, renouvellement et révocation. Tout ce qu’un serveur avec état conserverait est scellé dans des jetons et des cookies.

MathTrail · l’entrée

Serveur MCP

Le protocole : Streamable HTTP 2026-07-28, sans état, sur le SDK Go officiel. Il liste et appelle les outils, sert la carte comme ressource ui:// et remet au modèle ses instructions avec leur version.

MathTrail · l’entrée

Limites

Un rythme par adresse avant la connexion et par compte après, et un plafond global, le tout en mémoire ; puis les plafonds quotidiens, que le profil compte. Au-delà d’une limite, une phrase simple que le modèle transmet.

MathTrail · outils

Outils

Douze outils de même forme : lire le profil, calculer, le réécrire. Le modèle appelle la plupart d’entre eux ; la carte appelle les siens, pour le problème, la réponse, les progrès et le formulaire du profil. Rien de secret n’entre jamais dans un résultat.

MathTrail · calcul pur

La règle

Choisit la consigne de façon déterministe : après une erreur, le même thème, pour le consolider ; sinon, le thème absent depuis le plus longtemps ; la difficulté, dans le couloir où la probabilité de réussite est de 70–85 %. Un thème où l’enfant est en retard revient deux fois plus souvent. Les thèmes trop difficiles pour l’instant restent en pause, et dès que le niveau général de l’enfant en atteint un, un problème sur six peut le ramener comme contrôle du thème.

MathTrail · calcul pur

Scores

P = 0,2 + 0,8·σ(θ + δ − β). Elo ne bouge que selon qu’une réponse est juste ou non, avec un pas qui diminue ; une échelle comme aux échecs et onze étapes ; un thème compte comme maîtrisé selon une estimation prudente.

MathTrail · calcul pur

Vérifications

Ce que doit passer un problème soumis : sa structure et cinq réponses proposées différentes, les lettres de la langue de la leçon, des explications distinctes, le solveur, l’autovérification, la lisibilité, les quasi-doublons et le dessin : son format, ses étiquettes confrontées à l’énoncé, et aucune réponse écrite dessus. Tous les échecs sont signalés en une seule fois.

MathTrail · calcul pur

Solveur

Exécute le programme Starlark du modèle, qui essaie les réponses proposées ; exactement une doit se révéler juste. Limites de pas et de temps, ni réseau ni fichiers.

MathTrail · calcul pur

Contenu

Tout ce qui est intégré au binaire sans être du code : les catalogues de thèmes, de pièges et de compétences, les problèmes de référence, les schémas, les modèles de solveurs, un exemple de chaque type de dessin et les instructions du modèle avec leur version.

MathTrail · autour du cœur

Widget

La carte : du Preact compilé en un seul fichier HTML qui ne charge rien d’ailleurs, avec les dictionnaires des langues à l’intérieur. La mise en page fonctionne dès 320 px, et les boutons sont à la taille d’un doigt.

MathTrail · autour du cœur

Stockage

Retrouve le profil grâce à une marque cachée, compare avant d’écrire, réconcilie deux onglets, restaure le fichier depuis les révisions épinglées de Drive quand le parent le demande, et s’en tient à un petit budget d’appels à Drive.

MathTrail · autour du cœur

Scellement

Un mécanisme, deux rôles : il scelle la connexion et les jetons de Google dans les jetons que détient l’hôte, et la réponse d’un problème (celle du problème en cours et celle du problème écrit à l’avance) dans le profil. Les clés viennent de Secret Manager et font l’objet d’une rotation par identifiant de clé.

MathTrail · autour du cœur

Journalisation

Un résumé structuré par appel : l’outil, l’issue, la durée, la version des instructions ; les tentatives et les motifs d’un refus, comme événements du problème. Aucune donnée personnelle, aucun texte de problème, aucune réponse.

MathTrail · autour du cœur

Conteneur et configuration

Assemble les parties et les ferme dans l’ordre inverse, fonctionne avec des délais d’expiration et un arrêt propre, lit toute sa configuration dans l’environnement, et refuse de démarrer sur Cloud Run avec des options de développement.

Le compte Google du parent

Google OAuth

La connexion du parent avec le seul champ d’application drive.file, son renouvellement et sa révocation. Révoquer n’importe quel jeton met fin à tout accès.

Le compte Google du parent

Drive API v3

Le seul stockage à long terme : un profil JSON dans un dossier MathTrail visible du Drive du parent, lu et écrit avec le propre jeton du parent.

Google Cloud

Secret Manager

Les clés de scellement et le secret client de Google : des versions épinglées, lues une seule fois au démarrage d’une instance.

Google Cloud

Artifact Registry

L’image de chaque révision, par son condensé ; les anciennes images sont supprimées.

Google Cloud

Cloud Logging

Là où va le stdout du service. Les lignes qui servent à compter les enfants sont conservées 62 jours dans un bucket à part.

Google Cloud

BigQuery → Data Studio

Les décomptes d’utilisation sont conservés pendant des années et recalculés chaque nuit ; aucune table ne contient le nom sous lequel on compte chaque enfant. Un rapport privé et un rapport public, qui ne montre aucun groupe de moins de dix enfants.

GitHub

Actions

La compilation, les vérifications, la publication de l’image et le déploiement via la fédération d’identité de charge de travail, sans clé de compte de service.

GitHub

Pages

Ce site : les pages des thèmes, ainsi que la politique de confidentialité et les conditions d’utilisation vers lesquelles renvoie l’écran de consentement.

02

Une leçon, appel par appel

Le modèle et la carte appellent les mêmes outils et reposent sur une seule source de vérité : le fichier du profil dans le Drive du parent. Pour que la leçon se poursuive, ils n’ont rien à se dire.

Scénario
  • un appel
  • sa réponse
  • dans l’hôte

Problème

  • Adulte
  • Carte
  • Le modèle de l’hôte
  • MathTrail
  • Drive
  1. 1Adulte → Le modèle de l’hôte: « un nouveau problème, s’il te plaît »

  2. 2Le modèle de l’hôte → MathTrail: next_task

  3. L’hôte affiche la carte dès que l’appel commence : « Préparation du prochain problème… »

  4. limites · la règle choisit le thème, le niveau et la difficulté

  5. 3MathTrail → Drive: écrire la demande ouverte

  6. 4MathTrail → Le modèle de l’hôte: la demande est ouverte : récupérer le paquet

  7. 5Le modèle de l’hôte → MathTrail: get_package

  8. 6MathTrail → Le modèle de l’hôte: consigne · 3 problèmes de référence · pièges · modèles · exemples de dessins

  9. Le modèle écrit le problème, un solveur Starlark et une autovérification

  10. 7Le modèle de l’hôte → MathTrail: submit_task : jusqu’à 3 tentatives

  11. vérifications · le solveur doit trouver exactement une bonne réponse

  12. 8MathTrail → Drive: le problème devient le problème en cours, sa réponse scellée

  13. 9Carte → MathTrail: read_task : la carte interroge toutes les 4 s

  14. 10MathTrail → Carte: texte · dessin · cinq réponses proposées · indice, sans la réponse

Chaque appel lit aussi le profil dans Drive ; ces lectures ne sont pas représentées. La carte qui attend ne voit jamais le paquet : ses problèmes de référence contiennent leurs réponses. Pendant que l’enfant résout le problème, le modèle écrit à l’avance le suivant avec prepare_task, et quand on en demande un autre, next_task le place aussitôt sur une nouvelle carte, sans cette attente.

Réponse

  • Adulte
  • Enfant
  • Carte
  • Le modèle de l’hôte
  • MathTrail
  • Drive
  1. 1Enfant → Carte: appuie sur C

  2. 2Carte → MathTrail: submit_answer : le chat ne consomme aucun tour

  3. la première réponse seulement · le sceau s’ouvre maintenant · θ et δ ne bougent que selon qu’elle est juste ou non

  4. 3MathTrail → Drive: écrire les scores et l’historique

  5. 4MathTrail → Carte: juste ou non · le piège derrière C · le dessin de la solution · les étapes · le score avant → après

  6. La carte devient l’analyse de la réponse, à la place du problème

  7. 5Carte → Le modèle de l’hôte: ui/update-model-context : une ligne, lue avec le prochain message de l’adulte

  8. 6Adulte → Le modèle de l’hôte: « pourquoi un poteau de plus ? »

  9. 7Le modèle de l’hôte → Adulte: répond sur ce problème précis, en complément de la carte

L’appui enregistre la réponse avant toute explication, et la carte devient aussitôt l’analyse de la réponse. Le chat ne consomme aucun tour, et le modèle ne dessine rien. Il lit la ligne de la carte avec le prochain message de l’adulte, et répond à une question sur le problème sans redire ce que montre la carte. Si la ligne envoyée par l’hôte au modèle se perd, le modèle se rattrape à son appel suivant.

Connexion

  • Le navigateur du parent
  • Hôte
  • MathTrail
  • Google
  • Drive
  1. 1Hôte → MathTrail: POST /mcp, sans jeton

  2. 2MathTrail → Hôte: 401 · où sont les métadonnées

  3. 3Hôte → Le navigateur du parent: ouvre /oauth/authorize · PKCE S256

  4. 4Le navigateur du parent → MathTrail: authorize

  5. Son propre écran de consentement : quel client, et où il renverra le parent

  6. 5Le navigateur du parent → Google: connexion avec Google · drive.file seulement

  7. 6Google → Le navigateur du parent: retour, avec le code de Google

  8. 7Le navigateur du parent → MathTrail: /oauth/callback

  9. 8MathTrail → Google: échanger le code

  10. 9MathTrail → Drive: /about : à qui est ce Drive ?

  11. id utilisateur = HMAC du compte Drive · jetons de Google scellés dans notre code

  12. 10MathTrail → Le navigateur du parent: 302 · code = mt1.c.…

  13. 11Le navigateur du parent → Hôte: rend le code

  14. 12Hôte → MathTrail: /oauth/token · code + vérificateur

  15. 13MathTrail → Hôte: accès 15 min · actualisation 30 jours

Le parent se connecte une seule fois. Le service ne garde rien de cet échange : la requête voyage scellée dans le paramètre state, et les jetons de Google dans un code scellé.

Le problème en cours comme machine à états

Chaque règle « une seule fois » est un champ du profil, vérifié au sein d’une même lecture, d’un même calcul et d’une même écriture. Rien d’autre ne retient quoi que ce soit.

nonenext_taskconsigne prêterequestedsubmit_task ✓scellé, remisissuedsubmit_answerenregistrée une foisanswered

  • requested ↺refusé, tentatives restantes : pas plus de trois par demande
  • requested → nonetrois refus, ou 15 minutes depuis l’ouverture de la demande ; aucun problème du jour n’est consommé, et trois refus comptent comme une demande échouée de la journée
  • issued → requested« Un autre problème » avant une réponse : le problème est enregistré sans réponse, le score reste inchangé
  • answered ↺toute réponse ultérieure (depuis un deuxième onglet ou depuis le modèle) reçoit le premier résultat et n’écrit rien
  • answered → requestedle problème suivant, sur une nouvelle carte ; celui auquel on a répondu n’est plus en cours
  • answered → issuedle problème suivant était écrit à l’avance : next_task le place aussitôt sur une nouvelle carte, en sautant requested
  • issued → nonele sceau ne peut plus être ouvert, car la clé a subi deux rotations ; rien n’est enregistré

03

Où vit l’état

Tout ce qu’un serveur avec état garderait dans une base de données est gardé par d’autres : dans un jeton scellé que détient le chat, dans un cookie du navigateur du parent, ou dans le propre Drive du parent. Redémarrez le service ou lancez-en une deuxième copie : rien n’est perdu.

MathTrail lui-même

Rien

Ni comptes, ni sessions, ni enregistrements de clients, ni jetons Google. Chaque requête porte tout ce dont elle a besoin.

Le stockage des jetons du chat

Jetons scellés, opaques pour l’hôte

Jeton d’accès
15 min
Jeton d’actualisation
30 jours, ≤ 90

À l’intérieur sont scellés les jetons de Google, un identifiant utilisateur opaque pour les limites, et le pays depuis lequel le parent se connecte.

Le navigateur du parent

Deux cookies, pas de session

Clients approuvés
180 jours
Protection de la connexion (CSRF)
10 min

Les perdre coûte un écran de consentement de plus, ou une nouvelle connexion.

Le Drive du parent

La seule chose conservée longtemps

mathtrail-profile.json
jusqu’à sa suppression

Le profil, les scores et le problème en cours, avec sa réponse scellée. Le parent peut ouvrir le fichier, et Drive en garde l’historique.

Secret Manager

Lus une fois, au démarrage d’une instance

Clés de scellementaprès une rotation, la clé précédente reste acceptée
90 jours
Secret client de Google
épinglé

La mémoire de l’instance

Rien que des caches : perdus au redémarrage, sans rien casser

Compteurs du rythme des requêtes
minutes
Où se trouve le fichier du profil
10 min
Documents des clients
de 5 min à 24 h

Ce que la v1 écarte volontairement

  • Base de données
  • Files d’attente
  • Stockage de sessions
  • Cache partagé
  • Banque de problèmes tout prêts
  • Client d’API LLM
  • REST et OpenAPI

04

Le fichier du profil

Un fichier JSON par enfant : non pas le cache d’une vérité stockée sur un serveur, mais la vérité elle-même. Chaque bloc a un plafond, respecté à chaque écriture, si bien que le fichier ne grossit jamais sans limite : un fichier ordinaire reste sous la cible, et un fichier proche de ses plafonds la dépasse. Les problèmes passés sont conservés sous forme d’empreintes, pas de texte.

Taille du fichier

≈ 61 Ko / ≈ 79 Ko

Quelle taille afficher
BlocCe qu’il contientQui l’écritTaille
EmpreintesJusqu’à 200 esquisses des problèmes remis, pour la vérification des quasi-doublons, sans texte, sans thème, sans datenext_tasksubmit_task27 Ko / 29 Ko
Historique des scoresOù se trouvaient θ et le δ du thème avant chaque réponse récente, ainsi que θ et chaque thème au début de chacun des 7 derniers jours avec des réponsessubmit_answer14 Ko / 15 Ko
Scores et thèmesθ et le point de départ de l’enfant ; pour chaque thème : δ, les réponses, les pièges où l’enfant s’est fait prendre, la maîtrise et son niveaunext_tasksubmit_tasksubmit_answer8 Ko / 14 Ko
Dernières réponsesLes 20 derniers problèmes, avec ou sans réponse, et jamais moins que les 5 réponses que lit la règlenext_tasksubmit_answer4 Ko / 5 Ko
Problème en cours, scelléLa réponse, le piège derrière chaque mauvaise réponse proposée, la solution et le solveur, en une seule chaîne opaquenext_tasksubmit_tasksubmit_answer4 Ko / 8 Ko
Problème en cours, en clairCe que montre la carte : le texte, le dessin, cinq réponses proposées, l’indice et, après la réponse, la réponse elle-mêmenext_tasksubmit_tasksubmit_answer2 Ko / 4 Ko
Demande ouvertePendant l’écriture du problème : son identifiant, la consigne, les tentatives utilisées et qui a choisi le thèmenext_taskprepare_tasksubmit_task1,5 Ko / 2 Ko
Service et enfantLa version du schéma, un UUID aléatoire, la révision ; le pseudonyme, le niveau scolaire, les centres d’intérêt, les remarques ; les compteurs du joursave_profileedit_profilechaque écriture0,6 Ko / 1,5 Ko

Un problème écrit à l’avance se trouve à côté du problème en cours et pèse à peu près autant. Ce que le fichier ne contient pas : les textes des problèmes passés, la réponse en clair tant que l’enfant n’a pas répondu, quoi que ce soit qui désigne le parent, et l’étape (les étapes sont calculées au moment de l’affichage). Drive n’a pas d’écriture conditionnelle, donc le fichier est relu avant un enregistrement, et l’écriture est refusée s’il a changé : deux onglets ne s’écrasent jamais l’un l’autre en silence.

05

Ce qui est scellé, et pour combien de temps

Un seul mécanisme remplit deux rôles : il scelle la connexion dans les jetons que détient le chat, et la réponse d’un problème (celle du problème en cours et celle du problème écrit à l’avance) dans le profil. Chaque sceau porte sa finalité, si bien qu’un jeton d’accès ne peut pas passer pour un jeton d’actualisation, ni une réponse scellée pour quoi que ce soit d’autre.

Un jeton, décortiqué

mt1.a.kQ7fWx.5rJ0Yb2mQ8vK1nT4pXc9…

mt1
version du format
finalité
c code · a accès · r actualisation · d client · s state · k consentement · t réponse d’un problème
id de clé
tiré de la clé elle-même, si bien qu’une rotation passe inaperçue
texte chiffré
XChaCha20-Poly1305, un nonce aléatoire de 192 bits

Pas un JWT : personne d’autre que le service ne le lit, et aucun claim ne peut fuir d’une chaîne opaque vers un journal.

Durées de vie, sur une échelle logarithmique

  • Code d’autorisation
    60 s
  • Connexion en cours
    10 min
  • Jeton d’accès
    15 min
  • Jeton d’actualisation
    30 jours glissants, 90 au plus
  • Clé de scellement
    rotation tous les 90 jours
  • Clients approuvés
    180 jours

Un serveur sans état ne peut pas marquer un code comme utilisé, c’est pourquoi la fenêtre du code reste courte. Après une rotation, la clé précédente est encore acceptée, si bien qu’aucun jeton ni aucune réponse scellée ne se perd quand la clé change.