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.
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.
Problème
- Adulte
- Carte
- Le modèle de l’hôte
- MathTrail
- Drive
1Adulte → Le modèle de l’hôte: « un nouveau problème, s’il te plaît »
2Le modèle de l’hôte → MathTrail: next_task
L’hôte affiche la carte dès que l’appel commence : « Préparation du prochain problème… »
limites · la règle choisit le thème, le niveau et la difficulté
3MathTrail → Drive: écrire la demande ouverte
4MathTrail → Le modèle de l’hôte: la demande est ouverte : récupérer le paquet
5Le modèle de l’hôte → MathTrail: get_package
6MathTrail → Le modèle de l’hôte: consigne · 3 problèmes de référence · pièges · modèles · exemples de dessins
Le modèle écrit le problème, un solveur Starlark et une autovérification
7Le modèle de l’hôte → MathTrail: submit_task : jusqu’à 3 tentatives
vérifications · le solveur doit trouver exactement une bonne réponse
8MathTrail → Drive: le problème devient le problème en cours, sa réponse scellée
9Carte → MathTrail: read_task : la carte interroge toutes les 4 s
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
1Enfant → Carte: appuie sur C
2Carte → MathTrail: submit_answer : le chat ne consomme aucun tour
la première réponse seulement · le sceau s’ouvre maintenant · θ et δ ne bougent que selon qu’elle est juste ou non
3MathTrail → Drive: écrire les scores et l’historique
4MathTrail → Carte: juste ou non · le piège derrière C · le dessin de la solution · les étapes · le score avant → après
La carte devient l’analyse de la réponse, à la place du problème
5Carte → Le modèle de l’hôte: ui/update-model-context : une ligne, lue avec le prochain message de l’adulte
6Adulte → Le modèle de l’hôte: « pourquoi un poteau de plus ? »
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
- Drive
1Hôte → MathTrail: POST /mcp, sans jeton
2MathTrail → Hôte: 401 · où sont les métadonnées
3Hôte → Le navigateur du parent: ouvre /oauth/authorize · PKCE S256
4Le navigateur du parent → MathTrail: authorize
Son propre écran de consentement : quel client, et où il renverra le parent
5Le navigateur du parent → Google: connexion avec Google · drive.file seulement
6Google → Le navigateur du parent: retour, avec le code de Google
7Le navigateur du parent → MathTrail: /oauth/callback
8MathTrail → Google: échanger le code
9MathTrail → Drive: /about : à qui est ce Drive ?
id utilisateur = HMAC du compte Drive · jetons de Google scellés dans notre code
10MathTrail → Le navigateur du parent: 302 · code = mt1.c.…
11Le navigateur du parent → Hôte: rend le code
12Hôte → MathTrail: /oauth/token · code + vérificateur
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 demanderequested → 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éeissued → 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 rienanswered → requestedle problème suivant, sur une nouvelle carte ; celui auquel on a répondu n’est plus en coursanswered → issuedle problème suivant était écrit à l’avance : next_task le place aussitôt sur une nouvelle carte, en sautant requestedissued → 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
| Bloc | Ce qu’il contient | Qui l’écrit | Taille | |
|---|---|---|---|---|
| Empreintes | Jusqu’à 200 esquisses des problèmes remis, pour la vérification des quasi-doublons, sans texte, sans thème, sans date | next_tasksubmit_task | 27 Ko / 29 Ko | |
| Historique des scores | Où 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éponses | submit_answer | 14 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 niveau | next_tasksubmit_tasksubmit_answer | 8 Ko / 14 Ko | |
| Dernières réponses | Les 20 derniers problèmes, avec ou sans réponse, et jamais moins que les 5 réponses que lit la règle | next_tasksubmit_answer | 4 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 opaque | next_tasksubmit_tasksubmit_answer | 4 Ko / 8 Ko | |
| Problème en cours, en clair | Ce 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ême | next_tasksubmit_tasksubmit_answer | 2 Ko / 4 Ko | |
| Demande ouverte | Pendant l’écriture du problème : son identifiant, la consigne, les tentatives utilisées et qui a choisi le thème | next_taskprepare_tasksubmit_task | 1,5 Ko / 2 Ko | |
| Service et enfant | La 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 jour | save_profileedit_profilechaque écriture | 0,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.