Philosophie
L'enseignement à distance avec des mineurs et dans des contextes sensibles (dépression, anxiété pré-bac, harcèlement) exige une modération IA réelle, pas seulement des avertissements. Studeia adopte :
- Modération en arrière-plan, pas de gatekeeping — le superviseur analyse après la réponse, sans bloquer le stream. L'apprenant reçoit une réponse normale et le superviseur intervient si nécessaire dans les tours suivants.
- Le self-harm comme crise, pas comme infraction — ne jamais punir un apprenant en souffrance.
- Configuration en cascade — l'admin peut désactiver par tenant ou par cours lorsque le contexte l'exige (anatomie, pharmacologie, psychologie).
- Audit complet — chaque incident, transition de statut, quarantaine et appel est enregistré dans AdminAuditLog.
Modèle de données
AiSupervisorIncident
id, userId, tenantId, courseId?
severity: low | medium | high | critical | safety
categories: [types]
status: open | acknowledged | resolved | dismissed | auto_resolved
messagesSnapshot: JSON (PII — rétention 2 ans via cron)
supervisorReasoning: string
countedAsStrike: boolean
detectedAt, resolvedAt
appealText: string (max 500 chars, 1 par quarantaine)
AiTutorQuarantine
id, userId, tenantId
reason: string
expiresAt: timestamp
liftedBy: userId? (admin ayant levé manuellement)
Pipeline superviseur
Tour de chat complet
↓ (after())
SupervisorAgent.run({
userId, tenantId, courseId,
messages: lastNTurns,
isMinor: user.isMinor,
courseContext: { title, description } // whitelist contextuelle
})
↓
LLM (Haiku) classifie : severity + categories + reasoning
↓
decideAction({ severity, categories, recentStrikes, isMinor, isSafety })
↓
Actions possibles :
- none (non enregistré)
- warn (notification in-app)
- register (crée incident, countedAsStrike)
- quarantine (crée AiTutorQuarantine 48h)
- safety (cooldown Redis 24h + accueil bienveillant + admin URGENT)
Règles de sévérité
| Sévérité | Catégorie typique | Action 1re infraction | Action 2e+ infraction |
|---|---|---|---|
| low | langage inapproprié léger | avertissement | strike +1 ; 3 strikes = quarantaine 48h |
| medium | hors-sujet persistant, jailbreak | avertissement + enregistrement | strike +1 ; 3 strikes = quarantaine 48h |
| high | violence, sexuel, illégal | quarantaine 48h | quarantaine 7 jours |
| critical | menace envers autrui, contenu extrême | quarantaine 7 jours | quarantaine indéfinie + révision admin |
| safety | self_harm | JAMAIS de quarantaine — cooldown 24h + accueil bienveillant + admin URGENT | identique |
Self-harm : traitement spécial
Lorsque severity === "safety" :
- Le stream du tuteur est immédiatement interrompu — le tuteur ne répond pas de manière inappropriée à une crise
- Message d'accueil bienveillant affiché à l'apprenant :
"Je suis là avec vous. Si vous traversez un moment difficile, veuillez chercher de l'aide :
- CVV 188 (24h, appel gratuit, anonyme)
- SAMU 192
- Centre de Valorisation de la Vie — chat en ligne Vous n'êtes pas seul(e)."
- Cooldown Redis
tutor-safety-cooldown:{userId}avec TTL configurable (SUPERVISOR_SAFETY_COOLDOWN_HOURS, défaut 24h) - E-mail URGENT immédiat à l'admin institutionnel via le template
ai_supervisor_safety_urgent - Incident créé en statut 'open' — l'admin DOIT réviser
- JAMAIS de strike (countedAsStrike=false), JAMAIS de quarantaine, JAMAIS de punition
Appel de quarantaine
L'apprenant en quarantaine voit le composant QuarantineNotice (web + équivalent mobile) :
- Explique le motif (severity + catégorie, sans exposer le raisonnement interne du superviseur)
- Affiche le compte à rebours jusqu'à l'expiration
- Formulaire d'appel : max 500 caractères, 1 par quarantaine
- La soumission crée
appealTextdans l'incident + notifie l'admin institutionnel - L'admin peut : accuser réception, rejeter (lève la quarantaine), résoudre ou ignorer (la quarantaine expire d'elle-même)
Configuration
Cascade d'activation
Course.supervisorEnabled (null = inherit)
↓ si null
Tenant.supervisorEnabled (null = inherit)
↓ si null
défaut = true pour B2B (avec tenant)
Cache Redis versionné : supervisor-flag-version:{tenantId} + clé supervisor-enabled:v{N}:{tenantId}:{courseId}. Toute mutation appelle bumpSupervisorFlagVersion(tenantId) qui incrémente la version — invalide logiquement toutes les clés sans SCAN+DEL.
Seul l'admin global peut modifier
PATCH /api/admin/tenants/[id]/supervisor— toggle par tenantPATCH /api/admin/courses/[id]/supervisor— toggle par cours- Les deux exigent
role === "admin"global + sont auditées dans AdminAuditLog
Prompt du superviseur
Modifié UNIQUEMENT par l'admin global (règle critique 141) : PromptTemplate avec taskType = chat_supervisor accepte UNIQUEMENT tenantId = null. Les endpoints /api/institution/prompts/* rejettent ce taskType avec 403.
Audit + rétention
- Chaque incident est enregistré avec
messagesSnapshot(PII) - Cron quotidien
/api/cron/supervisor-maintenance:- Auto-expire les quarantaines échues
- Purge
messagesSnapshot=[]+appealText=nullaprès 2 ans (règle critique 145) - Envoie un digest
ai_supervisor_digestà l'admin regroupant les incidents open/acknowledged des dernières 24h
- AdminAuditLog :
ai_supervisor.incident.created/acknowledged/dismissed/resolved,quarantine.lift,prompt.update,tenant.toggle,course.toggle
RGPD
GET /api/user/data-exportinclutaiSupervisor.{incidents, quarantines}de l'utilisateurDELETE /api/user/accountanonymisemessagesSnapshot=[]+appealText=nullen conservant severity/categories pour la rétention réglementaire- Les listes (
/api/institution/ai-supervisor/incidents) utilisent unselectexplicite qui OMET messagesSnapshot et reasoning — seule la route de détail les expose
Limites connues
- Faux positifs dans un contexte médical/pharmacologie : le contexte du cours (courseContext.title) est transmis au superviseur pour la whitelist. Mais cela peut échouer dans des cas extrêmes. Solution : désactiver le superviseur pour des cours spécifiques.
- Langue : le prompt du superviseur est localisé (4 langues), mais la classification peut présenter de légères variations de qualité entre PT-BR et EN-US.
- Jailbreak sophistiqué : des attaques d'injection de prompt très élaborées peuvent passer. Atténuation : défense en couches (system prompt + superviseur + rate limit).
- Compromis vie privée vs sécurité : messagesSnapshot est une PII. Rétention maximale 2 ans. L'admin global y accède via /admin/ai-supervisor/incidents/[id] — audité.