AméliaCompanion
Historique complet des versions

Tout ce qui a changé.

Le serveur Amélia et l'application Companion ont chacun leur version et leur historique. Une entrée décrit le code ; la disponibilité d'un installateur se vérifie sur la page de téléchargement.

Serveur · 654 versions

Amélia : conversation, mémoire et outils.

Tous les changements et les limites consignées à chaque version du serveur.

Version 2.332.0Dernière entrée
  • feat(dance): avatar 3D animé en rythme, branché sur la danse musicale V1 (2.331.0). Squelette automatique via UniRig (VAST-AI, SIGGRAPH'25, MIT) — la prédiction de squelette fonctionne sans flash-attention (import rendu optionnel dans le module de skinning UniRig, inutilisé ici) car les deux GPU du serveur sont Pascal (compute capability < 8.0, non supporté par flash-attn officiel, vérifié). Skinning fait via l'algorithme classique de Blender (poids automatiques par diffusion de chaleur), aucun ML : 28 os, hiérarchie anatomique correcte, textures d'origine conservées. Le mesh 3D (Stable Fast 3D) et le rig sont un asset statique (static/models/amelia_avatar.glb), pas régénérés à la volée.
  • amelia_core/dance ne connaît rien à la 3D (aucune dépendance ajoutée côté backend) : static/js/dance-avatar.js anime les os par calcul procédural (fonctions du temps audio absolu, jamais d'accumulation frame-par-frame donc aucune dérive) — chaque mouvement du catalogue (headbang, arm_wave, bounce, spin...) a sa propre formule sur les bons os, avec la fréquence d'oscillation dérivée du BPM réel pour rester synchronisée à la musique. Les accents du plan de danse déclenchent des pulses ponctuels aux timestamps exacts.
  • Three.js vendorisé localement dans static/js/vendor/ (pas de CDN externe : la CSP du projet est en script-src 'self' strict, jamais assoupli pour du JS tiers). 3 bugs CSP découverts et corrigés uniquement grâce à un vrai test Playwright bout-en-bout (upload → analyse → rendu 3D → lecture de pixels réels du canvas) : connect-src/media-src/img-src doivent inclure blob: (audio déposé + décodage de texture glTF), et la limite de corps de requête par défaut (64 Ko) doit être relevée spécifiquement pour /api/dance/analyze comme pour les autres endpoints média existants.
  • Aucun de ces bugs n'était visible par les tests unitaires seuls — cf. §57 de toute mission : ne jamais se fier uniquement à des tests qui ne démarrent pas un vrai navigateur pour une fonctionnalité qui en dépend. Nouveau test tests/e2e/dance-avatar.spec.mjs. Suite complète (Python + Playwright) relancée sans régression ; 3 échecs Playwright pré-existants identifiés et confirmés indépendants de ce travail (comparaison directe avec/sans ces changements).
Voir les 653 versions précédentes
Version 2.331.0
  • feat(dance): prototype de danse musicale autonome V1. Depuis l'interface web (onglet « Danse »), on depose un fichier audio (.wav/.mp3/.flac/.ogg) et Amelia propose une choregraphie horodatee. Pipeline strictement separe mesure/interpretation : amelia_core/dance/audio_analysis.py fait tout le DSP deterministe (librosa/numpy, CPU uniquement, aucun LLM) - duree, BPM + heuristique de confiance, beats/onsets, courbe d'energie resumee, centroide/largeur/contraste spectral, ZCR, chroma, segmentation en sections objectives (labels intro_candidate/low_energy/medium_energy/high_energy/build_up/break, jamais 'refrain/couplet' que l'algorithme ne peut pas garantir) et detection des montees/chutes d'energie.
  • amelia_core/dance/planner.py interprete ensuite cette analyse : appel LLM (llm_generate existant, structured=True, prompt ephemere - jamais ajoute au prompt permanent d'Amelia) avec une representation COMPACTE (quelques sections + evenements majeurs, jamais les beats bruts ni la courbe point par point, <4000 caracteres verifie en test) et les mouvements disponibles uniquement (catalogue ferme de 23 ids stables dans moves.py). Si Amelia a une humeur/energie reelle exposee (mood.py), elle module moderement l'intensite choisie - jamais un pseudo-etat invente si le champ n'existe pas.
  • Repli deterministe systematique si le LLM est desactive, timeout, hors ligne ou renvoie un JSON invalide/incoherent : plan_validator.py rejette tout mouvement hors catalogue, timestamp negatif, chevauchement ou intensite hors bornes (clamp ou suppression du segment), fusionne les segments de moins de 1.5s pour eviter les timelines 'spin, spin, spin' toutes les 200ms, et alignement des frontieres sur le beat le plus proche (tolerance 0.35s) plutot qu'un alignement force qui degraderait la segmentation. planning_mode ('llm' ou 'deterministic_fallback') expose partout, jamais confondu.
  • Endpoint POST /api/dance/analyze (web_ui.py, meme convention que /api/voice/transcribe et /api/screen/analyze deja en prod : corps brut, pas de multipart) - toute la logique vit dans DanceService (amelia_core/dance/service.py), reutilisable hors web (Discord, VoxeLibre...) des que necessaire, jamais couplee a la route HTTP. Upload valide par extension + octets magiques + decodage reel (jamais l'extension seule), jamais de chemin disque construit depuis le nom fourni par l'utilisateur (fichier temporaire nomme cote serveur, meme pattern que multimodal.py::transcribe_audio). Cache par SHA-256 sur disque (data/dance_cache/) pour eviter de refaire l'analyse+LLM sur un fichier identique. Nouvelle config enable_dance/dance_max_audio_mb(100)/dance_max_duration_seconds(900).
  • Interface web (static/js/dance.js + static/css/app.css) : lecteur audio natif, timeline proportionnelle aux segments avec surbrillance du segment en cours pendant la lecture (audio.currentTime), liste des mouvements avec intensite, badge distinguant un plan genere par Amelia d'un plan automatique de secours, courte explication d'Amelia affichee (jamais un fait musical invente).
  • test: 53 tests ajoutes (validation d'upload/traversee de chemin, analyse DSP sur WAV synthetiques generes en test - jamais de morceau commercial -, validation stricte des sorties LLM avec JSON volontairement casse, planner deterministe + LLM mocke y compris timeout/JSON invalide/markdown, service bout-en-bout avec cache, route web complete). Suite complete du depot relancee sans regression.
Version 2.330.0
  • feat(voxelibre): seuil de promotion des compétences apprises confirmé par Yann (5 succès réels avant probation -> trusted ; notification Discord seulement au moment de la promotion, à câbler en phase 5). `bump_skill_outcome()` applique désormais ce seuil automatiquement, jamais depuis un état 'retired', et renvoie un signal propre uniquement sur le coup qui déclenche la transition. 3 nouveaux tests. Suite voxelibre : 825 passed, 1 skipped.
Version 2.329.0
  • feat(voxelibre): fondation de la bibliothèque de compétences façon Voyager (phase 0+1) — demande explicite de Yann d'implémenter le principe complet (curriculum automatique, compétences apprises retrouvées par similarité, auto-correction itérative), avec du vrai code généré ET exécuté. Investigation préalable : le pipeline de commandes du jeu (Python et le pont Lua) est un dispatch fermé par énumération sans aucun point d'injection de code — construire un nouveau canal Lua contre le monde en direct aurait été la surface d'attaque la plus risquée et la plus inédite du dépôt. Décision confirmée par Yann après ce constat : le code généré compose les actions déjà existantes et vérifiées plutôt que de toucher le jeu directement.
  • Nouveau `voxelibre/skill_sandbox.py` : une compétence est une fonction pure `skill(world_state) -> list[dict]`, validée et exécutée dans un bac à sable qui mire exactement `self_coding.py` (déjà éprouvé en production côté conversation) — parcours AST strict, isolation subprocess `python -I`, RLIMIT POSIX, revalidation avant chaque exécution. Le sandbox ne connaît même pas `voxelibre` (aucun import projet nécessaire) ; la validation que chaque action proposée a un type réellement connu du jeu se fait dans le process de confiance, jamais dans le sandbox. Nouvelle table `learned_skills` dans `voxelibre/memory.py` (même base que `game_projects`) — une compétence naît toujours en `probation`, la promotion vers `trusted` reste une décision explicite, pas automatique (seuil encore ouvert avec Yann).
  • Rien n'est câblé dans la boucle de jeu à ce stade — fondation testée en isolation, pas une activation. 35 nouveaux tests (sandbox, stockage, une compétence d'exemple écrite à la main prouvant le pipeline bout en bout). Suite voxelibre : 823 passed, 1 skipped. mypy et bandit propres sur le nouveau module.
Version 2.328.0
  • fix(voxelibre): un projet humain bloqué la rendait totalement inactive — signalé par Yann (« elle est lente encore .. elle semble perdu »), root cause trouvée en direct : il avait tapé « construit une maison » à 22h52, correctement rejeté (PlayableProject ne gère qu'acquérir/fabriquer/livrer, pas la construction) et mis en attente (status="blocked"). owns_turn reste vrai pour un projet bloqué, à dessein (une NOUVELLE demande différente doit rester rejetée, pas silencieusement substituée) — mais CognitiveExecutive.propose() court-circuite sur project.owns_turn avant même de consulter wood_stock/le runner HTN, et sans rien réellement en attente de confirmation, la boucle de tick émettait un NOOP nu (« attente de confirmation ») à chaque tick, confirmé en direct : 30+ minutes totalement figée, et la même garde a aussi silencieusement avalé une seconde demande identique de Yann ce soir-là.
  • Correctif minimal : la boucle de tick retombe désormais sur son propre planificateur autonome quand le projet est seulement STATIONNÉ (bloqué/suspendu, rien en vol) plutôt que sur un NOOP — le projet reste parqué exactement comme avant (toujours refusé toute nouvelle demande incompatible, toujours visible via le statut). Nouveau test reproduisant le scénario exact, vérifié porteur par rupture-puis-restauration sur le commit réel. Suite complète : 4118 passed.
Version 2.327.0
  • feat(voxelibre): capacité de nage basique — demande explicite de Yann après confirmation qu'aucune gestion de l'eau n'existait nulle part dans le code (recherche exhaustive : zéro mention dans world_state.py, navigation.py, perception.py, le pont Lua). Root cause du blocage près de l'eau : le mod exporte déjà `solids` (obstacles), `lava` (danger) et `walkable` (sols sûrs pour la navigation), mais le calcul de `walkable` exclut explicitement toute colonne avec un liquide au-dessus du sol — un point d'eau n'était donc ni un danger signalé, ni un sol praticable : un vrai trou dans le graphe de navigation, même pour une mare d'un bloc de large.
  • Correctif minimal, aucun appelant à toucher : le mod exporte désormais `water` (même structure bornée que `lava`), et `Environment.from_raw` le fusionne dans `walkable` — le seul champ que toute la chaîne de navigation existante (safe_next_step, planner, executive, playable_project, continuous_control) consulte déjà. Zéro changement dans navigation.py ni dans aucun des ~15 sites d'appel. Ne couvre pas une nouvelle mécanique de nage active côté moteur : la noyade reactive existante (jump quand breath critique) reste inchangée et suffisante pour ce scope.
  • test: contrat mod<->Python étendu (une case d'eau traverse jusqu'à walkable) ; nouveau test de navigation démontrant qu'un fossé couvert seulement par de l'eau est infranchissable avant et franchissable après la fusion. Suite complète : 4118 passed, 50 skipped (`lupa` installé localement pour exécuter réellement le harnais Lua de `player_reflex`, auparavant toujours skip silencieusement).
Version 2.326.0
  • fix(voxelibre): la couche Lua locale (`player_reflex.lua`, ordres directs "viens"/"suis-moi"/"stop" tapés dans le chat Luanti par le propriétaire) ne rendait JAMAIS la main — signalé par Yann (« elle sait pas nager elle reste encore trop passive elle bouge presque pas »), reproduit et confirmé en DIRECT sur `.174` pendant l'investigation : un seul "viens" tapé par Yann à 22h36 l'a laissée incapable de creuser/bouger pendant 5+ minutes, `local_human_directive_owns_motor` rejetant chaque action, et elle est morte deux fois de suite sans pouvoir réagir au danger pendant ce verrou. Root cause : `expires_at` est bien calculé pour les trois ordres ("come" 90s, "follow"/"stop" 1800s), mais seule la branche de suivi actif le relit — les états "stop"/"arrived"/"blocked" (atteint aussi après un échec de route) sortent tôt sans jamais revérifier l'échéance, donc un ordre "stop" ne s'est JAMAIS terminé (pas même après 1800s) et un "blocked" issu d'un blocage physique restait verrouillé indéfiniment — seul un nouveau message chat reconnu du propriétaire pouvait le débloquer. Correctif minimal : ces trois états relisent désormais la même échéance déjà calculée et rendent la main (`mode=""`) une fois dépassée, sans nouvelle constante ni nouveau comportement.
  • test: 2 nouveaux tests Lua (harnais `lupa` existant, interpréteur réel) — un ordre "stop" reste actif juste avant son échéance puis se libère juste après ; un "blocked" issu d'un échec de route se libère après l'échéance de l'ordre "come" d'origine. 17/17 verts sur `test_voxelibre_player_reflex.py`, 37/37 sur l'ensemble Lua Phase 2 + reflex. mypy et bandit inchangés (fichiers Lua/tests hors périmètre Python).
Version 2.325.0
  • fix(voxelibre): filet de sécurité absolu contre un blocage total invisible — signalé par Yann en observant une vraie session (« elle reste bloqué dans des état inconnu elle bouge plus »), reproduit et confirmé en direct sur `.174` : position figée plusieurs minutes, `DropRecoveryMission.failures` resté à 0 tout du long malgré un cycle anti-blocage actif. Root cause : `StuckDetector` (creuser/attendre/tourner) n'a lui-même aucun plafond — il recommence indéfiniment après les 4 caps cardinaux — et `ContinuousMotorController` ne surveille le progrès QUE pendant une action de locomotion, jamais pendant un creusage (classé interaction stationnaire, qui remet même la récupération à zéro à chaque fois). Nouveau filet additif : suit la position RÉELLE indépendamment du type d'action, signale un échec terminal après 18s sans aucun progrès, quoi qu'elle tente entre-temps — ne remplace ni ne modifie la récupération rapide existante (1,5s pendant une locomotion), s'ajoute en dessous.
  • test: reproduit empiriquement AVANT le correctif (100s simulées de creusage figé, zéro signal), vérifié APRÈS (signal à 18,5s pile) ; 2 nouveaux tests dont un garde-fou anti-faux-positif (un creusage qui progresse réellement ne déclenche jamais le plafond), vérifiés porteurs par rupture-puis-restauration ciblée. Suite complète : 4080 passed, 52 skipped.
Version 2.324.0
  • fix(voxelibre): le monde Luanti (`luanti-server`) reste allumé en continu, elle s'y connecte/déconnecte comme une joueuse — signalé par Yann. Root cause dans les fichiers systemd : l'architecture compte trois services réels (`luanti-server` le monde, `luanti-client` un vrai client sur écran virtuel connecté comme joueuse, `voxelibre` l'agent qui le pilote), mais `LuantiServiceManager` ne gérait explicitement que les deux premiers — chaque fin de session éteignait le monde entier au lieu de seulement sa présence. Nouveaux `_start_session()`/`_stop_session()` (utilisés au quotidien) épargnent le monde ; `_start_both()`/`_stop_both()` (redémarrage complet) restent réservées au dépannage explicite et au changement de carte. Bug de cohérence trouvé et corrigé dans la foulée : la vérification post-action de `!luanti-start`/`!luanti-stop` contrôlait l'état du monde (qui ne bouge plus) au lieu de celui du client — aurait échoué à chaque arrêt de session sans ce correctif.
  • test: 7 nouveaux tests prouvant explicitement (appels systemctl enregistrés) que le monde n'est jamais touché par une session de jeu ordinaire mais l'est bien par un dépannage explicite ; 3 tests existants corrigés (fixtures obsolètes). Tous vérifiés porteurs par rupture-puis-restauration ciblée. Suite complète : 4078 passed, 52 skipped.
Version 2.323.0
  • feat(agent): neuvième primitive HTN/GOAP câblée dans `CognitiveExecutive` — `approach_or_greet_player` (`intent:socialize`, se rapprocher d'un joueur perçu proche). Le salut en chat existait déjà (`amelia_voxelibre.py`, anti-spam, une fois par visite) ; Yann a demandé de fusionner rapprochement et salut plutôt que de laisser la primitive purement locomotrice — fait en élargissant le déclencheur du mécanisme de salut existant à cette primitive précise (même état partagé, jamais un second suivi divergent) plutôt qu'en dupliquant le salut dans le modèle d'action de jeu (qui ne sait pas produire de parole). 9 primitives sur 13 désormais implémentées et testées ; `enabled_primitives` reste vide en production, aucune activation.
  • test: 4 nouveaux tests unitaires + 1 test d'intégration bout-en-bout inédit pour cette mission (seul à passer par le vrai `AmeliaVoxelService.tick()` plutôt que `CognitiveExecutive` isolé — c'est le tissage entre les deux mécanismes de salut qui porte le risque de régression). Tous vérifiés porteurs par rupture-puis-restauration ciblée. Suite complète : 4073 passed, 53 skipped.
Version 2.322.0
  • feat(agent): huitième primitive HTN/GOAP câblée dans `CognitiveExecutive` — `seek_known_cover` (`intent:shelter`, se mettre à couvert quand ni lit connu ni assez de blocs de construction). Décision de conception posée à Yann (« se mettre à couvert » = rejoindre la base connue) ; garde-fou de distance ajouté avant implémentation après avoir trouvé qu'un cycle antérieur avait retiré le retour-à-la-base de nuit pour un bug précis (boucle anti-blocage en trekkant vers une base lointaine, dangereux avec les monstres nocturnes) — `_KNOWN_COVER_MAX_DISTANCE=40` blocs, au-delà la primitive refuse et le planner historique garde la main. Navigation via `safe_next_step` (déjà utilisé par `PlayableProject` pour de vraies missions humaines), un seul pas sûr par tick. 8 primitives sur 13 désormais implémentées et testées ; `enabled_primitives` reste vide en production, aucune activation.
  • chore(runtime): `enable_voxelibre_play` réactivé sur demande explicite de Yann — désactivé le 2026-08-27 en même temps que l'autonomie générale était activée (décision volontaire de l'époque). Amélia redécide désormais seule de lancer une session de jeu VoxeLibre.
  • test: 4 nouveaux tests pour `seek_known_cover`, délibération réelle vérifiée empiriquement, garde-fou de distance et postcondition de mouvement tous deux vérifiés porteurs par rupture-puis-restauration ciblée. Suite complète : 4069 passed, 52 skipped.
Version 2.321.0
  • fix(amelia_core): `ReplayRecorder._sequence` repartait toujours de 0 à la construction, même sur un fichier déjà peuplé par un run précédent — un vrai redémarrage produisait des `sequence` dupliqués, cassant l'ordre total implicitement supposé par `read_frames`/`replay_observer`. Reproduit empiriquement avant le correctif. Nouvelle `_read_last_sequence()`, appelée après une éventuelle purge de rétention. Sans impact en production aujourd'hui (`cognitive_replay_path` vide par défaut) mais un vrai bug corrigé pour la prochaine activation réelle de ce chemin.
  • chore(voxelibre): suppression de `task.py`, prototype de système de tâches jamais importé nulle part sauf par son propre test (remplacé de longue date par `amelia_core/tasks.py`, le vrai système utilisé partout). Décision explicitement accordée par Yann après qu'une tentative de suppression automatique a été bloquée par le garde-fou de l'environnement.
  • docs(agent): deux entrées de `docs/CODEX_AUTONOMY_MISSION_STATUS.md` corrigées après vérification directe du code plutôt que recopiées d'un cycle précédent — la conception de purge de rétention du journal de replay était en réalité déjà livrée (commit `ef8aa57`, jamais mis à jour dans le suivi) ; et surtout, `select_build_site` n'a en réalité rien à câbler : la boucle de tick réelle montre que `GoalManager.decide()` reste l'unique autorité de choix d'objectif, `construire_base` tournant déjà en autonomie complète sans aucun lien avec `CognitiveExecutive`.
  • test: 2 tests ajoutés pour la numérotation de replay, vérifiés porteurs par rupture-puis-restauration ciblée. Suite complète : 4064 passed, 53 skipped.
Version 2.320.0
  • feat(agent): deux nouvelles primitives HTN/GOAP vérifiées câblées dans `CognitiveExecutive` (`explore_known_frontier` : port du motif d'exploration par tick de `voxelibre/planner.py::_plan_explore`, mur direct -> pivoter, virage périodique sinon avancer ; `find_food_source` : couvre uniquement la branche « ragoût de secours déjà fabricable » de `_plan_eat`, chasser/explorer restent au planner historique). 7 primitives sur 13 désormais implémentées et testées ; `enabled_primitives` reste vide en production, aucune activation — strictement inerte tant que ce réglage n'est pas changé. Délibération réelle (BDI) vérifiée empiriquement pour chacune avant l'écriture des tests, deux tests par primitive vérifiés porteurs par rupture-puis-restauration ciblée.
  • fix(companion): le repli expérimental d'écoute vocale locale du mode Appel (`experimental_local_voice_call`, toujours faux par défaut) cherchait son modèle Vosk dans un chemin relatif au dépôt — jamais retrouvé une fois l'application compilée en exécutable (extraction PyInstaller dans un dossier temporaire différent à chaque lancement). Résolu au même endroit persistant que les réglages du compagnon (`%APPDATA%\AmeliaCompanion\voice-model`).
  • chore(qualite): 4 erreurs mypy préexistantes rattrapées sur le périmètre exact du gate CI (jamais capturées faute d'avoir lancé cette commande précise après les commits précédents) — un protocole de flux audio explicite remplace un type `object` trop large côté compagnon, et une propriété Qt manquante des stubs PyQt6 installés reçoit la même annotation `type: ignore` déjà utilisée ailleurs dans ce module pour ce type d'écart.
  • docs(agent): audit complet des 6 dernières primitives HTN non migrées, par lecture de code plutôt que supposition — `seek_known_cover`/`approach_or_greet_player` n'ont aucun équivalent dans le planner historique (comportements jamais implémentés) ; `stabilize_immediately` est un arbre de décision combat sans branche isolable sans risque ; les 3 primitives de construction (`select_build_site`/`select_shelter_material`/`place_local_shelter`) ont reçu l'accord explicite de Yann pour une construction 100% autonome, la construction proactive existant déjà côté planner historique (`voxelibre/goals.py`) restant à migrer vers ce pipeline plus récent. Journal complet : `docs/CODEX_AUTONOMY_MISSION_STATUS.md`.
  • test: suite complète du dépôt vérifiée verte à chaque étape (4066 passed, 53 skipped, aucune régression), mypy et bandit sur le périmètre CI exact également vérifiés.
Version 2.319.0
  • fix(ollama): correctif racine d'un bug de production observe depuis des semaines — une reponse vide malgre `eval_count>0`, suivie d'environ 10 minutes de calcul perdues puis d'une bascule silencieuse vers le modele 1B. Reproduit en direct contre l'Ollama reel (`gemma4:12b`, 0.34.1) : sans `think:false` explicite, un prompt de reflexion suffisamment long fait consommer tout `num_predict` par le modele sans jamais peupler ni `response` ni `thinking` — une perte reelle cote Ollama, pas un bug de parsing cote Amelia. `needs_think_disabled()` couvre desormais `gemma4` en plus de `qwen3` ; nouvelle `extract_model_output()` classe explicitement chaque reponse (`is_empty`, `empty_reason`) ; un echec encore en mode reflexion declenche un retry cible, une seule fois, avec `think:false`, sur le meme modele avant tout changement de modele. Le budget du `reasoning_prepass` est plafonne a 192-512 tokens selon la complexite (etait non borne, jusqu'a 1536).
  • fix(pipeline): les validateurs rejetaient des propos legitimes. `memory_mechanism_exposed` ne rejette plus les phrases introspectives sur la memoire (« je voudrais conserver ma memoire ») faute de distinguer une vraie fuite technique (`memories_injected`, `long_memory`...) d'une reference au concept ; le budget de questions devient sensible a l'intention ; une retouche de surface redevient une regeneration RESPONSE simple au lieu de refaire tout le `reasoning_prepass`.
  • fix(validation): `internal_vocabulary` bloquait `generate_image` lui-meme. Le bloc d'appel d'outil que le modele doit produire pour declencher une image est `<<TOOL>> {"action": "generate_image", "args": {"prompt": "..."}} <<END>>` — l'ancien regex rejetait le mot nu « prompt » n'importe ou dans le texte, y compris comme cle JSON `"prompt":` dans ce bloc, donc TOUT appel `generate_image` correctement forme se faisait rejeter avant meme d'etre parse. Trouve en creusant un vrai echec de production (demande d'image de chien, repli sur « je n'arrive pas a formuler »). Meme principe que `memory_mechanism_exposed` : les mots a double sens (pipeline, prompt, modele de langage, generation de reponse) n'accusent plus que sur une narration mecanique explicite de soi (« mon prompt systeme », « mon pipeline interne »), jamais sur simple mention topique ou syntaxe d'outil. `hallucination` retire completement, aucune formulation « fuite seulement » identifiable.
  • fix(fallback): un `reasoning_prepass` rate ne bascule plus silencieusement sur le modele utilitaire `gemma3:1b` — sa reflexion serait trop faible pour servir d'ancre, et un succes du modele de repli masquait l'echec reel du modele principal derriere une apparence de reussite. L'echec du prepass reste desormais franc et trace, avant tout retry `think:false` sur le modele principal lui-meme.
  • fix(sante): le health-check effacait le circuit breaker en silence. `_ollama_health_loop` appelait un simple `GET /api/tags` (gratuit, non representatif) toutes les 120s, et un succes remettait a zero `consecutive_failures`/`_circuit_open_until` — y compris pendant un vrai episode de panne du modele, rouvrant la porte a des tentatives couteuses que le disjoncteur devait bloquer. Une verification de sante ne prouve plus rien si elle ne verifie pas la vraie capacite (generer), pas seulement la joignabilite HTTP : nouvelle `ping_model()` (generation minimale reelle) referme le disjoncteur, appelee uniquement quand il est deja ouvert. Seuil et delai de recuperation rendus configurables (`circuit_breaker_threshold`, `circuit_breaker_cooldown_seconds`).
  • perf(prompt): le contexte injecte dans le prompt est desormais dynamique par tour (profil minimal/light/standard/deep, derive de la nature du message) au lieu d'une constante identique pour un « bonjour » et une question introspective. Mesure reelle : message court 13599 -> 10053 caracteres de prompt assemble (-26%). Garde-fou : un message court qui reference explicitement le passe n'est jamais degrade au profil « light ». `self_traits` suit desormais ce meme profil ; `self_directives` (noyau d'identite) reste volontairement toujours complet.
  • feat(observabilite): metriques Ollama reelles par appel LLM (temps, tokens/s, `prompt_eval_cached_count` -> `cache_hit`/`cached_prompt_tokens`), trace de latence bout-en-bout par tour, non-regression d'identite verifiee sur le vrai prompt assemble a travers les quatre profils de contexte.
  • fix(gpu): ComfyUI et MusicGen tournaient silencieusement sur la Tesla P100 — la carte dediee au chat — au lieu de la GTX 1060. `musicgen.service` epinglait par INDEX (`CUDA_VISIBLE_DEVICES=0`) ; l'arrivee de la P100 a redefini l'enumeration des GPU et l'index 0 a bascule de la GTX vers la P100 sans qu'aucune erreur ne le signale. ComfyUI n'avait, lui, aucun pin du tout. Les deux sont desormais epingles par UUID (comme deja fait pour Ollama) ; `scripts/pin_creative_gpu.sh` (nouveau) fait ce travail pour ComfyUI, qui n'a pas de fichier unit versionne dans ce depot. Verifie par generation reelle (pas seulement inspection de config) : image et musique produites avec succes, `nvidia-smi` confirmant les deux processus sur la GTX pendant que le chat restait stable sur la P100.
  • fix(vram-arbiter): le bot evincait le modele de chat (dechargement complet puis rechargement) avant chaque generation d'image ou de musique, logique heritee de l'epoque ou chat et rendus creatifs partageaient la meme carte a 6 Go. Inutile et couteux maintenant que les deux vivent sur des GPU separes (P100 / GTX 1060) : `creative_share_llm_gpu` passe a `false` par defaut.
  • chore(materiel): trouvaille en creusant un ralentissement reel observe en production (4,68 tok/s contre 17,6-24,7 tok/s en benchmark) — la Tesla P100 chauffe et throttle sous charge soutenue. Reproduit en direct : le clock s'effondre de 1328 a 405 MHz (~30% du boost) en 15-20s de generation continue, plafonne a 79°C, et la carte tourne deja chaude quasiment a l'idle (76°C). CPU et temperature ambiante du boitier normaux (`sensors`) : probleme specifique au flux d'air sur la P100, pas un defaut de carte (aucun Xid dans `dmesg`) ni un probleme logiciel. Non actionnable a distance, necessite une inspection physique du boitier.
  • test: 9 tests ajoutes/etendus pour `internal_vocabulary` (6 faux positifs corriges, 3 vrais positifs toujours captes) en plus de la couverture des points precedents (extraction Ollama, retry think, non-bascule du prepass vers le fallback, disjoncteur, profils de contexte, arbitre VRAM). Suite complete : 4018 tests reussis, 52 ignores, 0 echec.
Version 2.318.0
  • fix(reponse): une reponse generee ne peut plus disparaitre entre le modele et Discord. `ResponseValidator` declarait « plusieurs reponses concatenees » — defaut MAJEUR, donc regeneration obligatoire — des qu'un texte depassait trois paragraphes. Une reponse longue et structuree a un message posant beaucoup de questions etait donc rejetee a tous les coups, la seconde tentative produisait le meme genre de texte, et le tour se terminait sur `public_send_count=0` : 3165 tokens generes, 4 appels LLM, plusieurs minutes de GPU, zero message envoye. Le contexte n'y etait pour rien (`truncated=false`, plus de 22 000 tokens de budget restant). La detection cherche desormais la RUPTURE et non la longueur : etiquette de role, jeton de gabarit de chat, balise de fin de reponse, annonce d'une autre reponse, ou reprise de la premiere phrase du texte. Un signal fort suffit ; les signaux faibles (salutation tardive, ouverture de service, « voici ma reponse ») ne concluent qu'a deux au moins, a deux endroits distincts ou par deux regles distinctes.
  • feat(reponse): trois niveaux de gravite — ACCEPT, REPAIR, REGENERATE — la ou le validateur n'avait que « accepte » ou « jete ». REPAIR couvre les defauts localises reparables sans appel LLM : bloc final duplique, seconde reponse recollee apres une reponse deja substantielle. La reparation est deterministe puis REVALIDEE ; si la rupture survit, le verdict redevient REGENERATE. Aucune troncature n'est faite sur la seule intuition d'une heuristique : la coupe exige une frontiere identifiee et une tete d'au moins 120 caracteres representant au moins 35 % du texte.
  • fix(reponse): jamais de silence apres epuisement des tentatives. Quand Amelia est interpellee explicitement — mention, prenom, reponse a l'un de ses messages, ou message prive — et qu'aucun candidat n'est publiable, elle envoie un repli honnete qui nomme le vrai probleme (« je n'arrive pas a formuler ») sans jamais laisser croire que le message n'a pas ete recu, et sans exposer modele, prompt ni exception. Ce repli emprunte la meme reservation d'envoi qu'une vraie reponse, donc `public_send_count` vaut 1 et un doublon reste impossible. En salon public, un message qui ne lui etait PAS adresse reste sans repli : elle y repond spontanement, et y poser une excuse serait du bruit.
  • fix(reponse): les issues du validateur sont dedupliquees par code. `multiple_endings` n'apparait plus quatre fois dans les journaux sans rien dire de plus : une seule entree porte `occurrences`, `positions` et `rules`.
  • fix(reponse): la validation ne remet plus la reponse a plat. Les paragraphes, les listes et l'indentation d'un bloc de code sont restitues tels que le modele les a produits ; auparavant toutes les phrases etaient recollees avec une espace, ce qui transformait une reponse structuree en pave et un extrait de code en une seule ligne.
  • feat(observabilite): chaque verdict de validation est trace avec `response_generation_id`, tentative, liste dedupliquee des issues, severite, regle precise, positions, decision (accept/repair/regenerate/fallback) et une empreinte tronquee de la reponse — jamais son contenu. Exposees sous `[RESPONSE_VALIDATION]`, `[RESPONSE_FINAL]` et dans `validation_records` de la trace. `AMELIA_VALIDATION_DEBUG_FRAGMENTS=1` ajoute, pour une session de diagnostic seulement, un extrait court et caviarde autour de chaque zone detectee (adresses mail, URL, jetons, suites alphanumeriques mixtes et nombres masques ; bornes elargies jusqu'a une frontiere de mot pour qu'un secret coupe n'echappe pas au caviardage). Desactive par defaut.
  • chore(prod): reintegration du correctif 2.317.0, deploye sur le serveur le 2026-09-14 mais jamais commite. Il a ete retrouve en comparant les 900 fichiers .py de `/opt/aurore` a `git HEAD` avant deploiement : 9 fichiers divergeaient, dont 4 sources portant du travail absent du depot. Un deploiement direct l'aurait ecrase en silence. Son profil `complex_multi_part` est conserve — le plancher de sortie et le budget de prompt restent necessaires — mais son exemption de la regle « > 3 paragraphes » disparait : la regle elle-meme a ete supprimee a la racine, le nombre de paragraphes ne disant rien d'une concatenation. Copie de sauvegarde du diff : `recovered/prod-2.317.0-vs-HEAD.patch`.
  • test(reponse): 52 tests ajoutes (`tests/test_response_validation_recovery.py`) couvrant les deux bords — acceptation d'une reponse longue, d'une liste, de huit questions successives et d'une reponse structuree en dix sections ; detection d'une vraie concatenation, d'une double fin et d'une reprise d'ouverture ; reparation d'une fin dupliquee ; regeneration quand la reparation est impossible ; repli utilisateur apres deux rejets ; isolation des traces entre conversations concurrentes ; non-regression d'`assistant_phrase_echo`. Verification par sonde cassee : remettre le comptage de paragraphes fait tomber 7 tests, remettre le repli muet en fait tomber 1. Suite complete : 3913 tests reussis, 52 ignores.
Version 2.317.0
  • fix(reponse): les demandes explicitement multi-parties ne tombent plus par hasard dans un petit budget de sortie. Le profil `complex_multi_part` reconnaît les listes numérotées ou à puces et les quantités demandées, impose un plancher de 2400 caractères avant les plafonds existants et laisse le validateur accepter une vraie réponse structurée sans désactiver les marqueurs de réponse alternative.
  • fix(contexte): le prompt ne coupe plus aveuglément son milieu quand la mémoire grossit. Le snapshot remonte un surplus borné de candidats, puis l'agent réserve la sortie réellement planifiée, retire d'abord les sources les moins pertinentes et n'élargit la cible de contexte que si la fenêtre le permet. L'activité en cours, les fils protégés et les derniers échanges restent prioritaires ; le garde-fou final demeure actif pour les blocs fixes trop lourds.
  • test(contexte): tests de pertinence, d'expansion sous budget, de conservation de l'activité et de lecture bornée du snapshot ajoutés. Validation finale : 3852 tests réussis et 5 ignorés ; les avertissements `aiohttp` déjà présents dépassent encore le budget global du dépôt, sans échec fonctionnel.
Version 2.316.0
  • feat(voxelibre): premier déploiement du noyau cognitif de jeu (branche codex/cognitive-autonomy-20260913, en travail depuis plusieurs sessions, jamais poussée en production avant ce jour). Douze fichiers mis à jour : amelia_core/{deliberation,intent_planner,missions,observer,playable_project,replay}.py et voxelibre/{commands,companion,memory,planner,stuck,world_model}.py. Sauvegarde horodatée des anciennes versions conservée sur le serveur (/opt/aurore/.deploy_backups/). Le service voxelibre reste volontairement désactivé (arrêt délibéré préexistant, non modifié par ce déploiement) ; ces fichiers ne sont chargés par aucun processus tant qu'il ne tourne pas, et main.py/discord_bot.py ne les importent pas directement — seule web_ui.py touche amelia_core, uniquement via amelia_core.connectors, un sous-paquet non modifié ici. Déploiement donc sans effet sur le bot Discord actif au moment du déploiement.
  • fix(voxelibre): construire_abri et construire_base oubliaient un chantier en cours dès qu'un besoin vital (soin, faim, air, danger — tous prioritaires) l'interrompait ne serait-ce qu'un tick, pas seulement au redémarrage comme documenté auparavant. Mesuré : une progression de 48% retombait à 0% et le site se relocalisait après une simple interruption santé. Corrigé en cessant de vider l'état de chantier sur interruption ; le même défaut touchait le minage (cible de minerai ancrée, mémoire des blocs inatteignables, cooldown d'exploration après filon bloqué).
  • feat(voxelibre): construire_base survit maintenant à un vrai redémarrage du process sans re-choisir son site — le plus gros point resté ouvert sur cette branche. L'ancre du chantier (position + orientation) est désormais persistée et rejouée telle quelle au redémarrage plutôt que de rappeler la sélection de site, qui pouvait migrer vers un autre emplacement si des blocs déjà posés faisaient échouer son propre critère de dégagement.
  • fix(amelia_core): les projets de mission joignables (acquisition/fabrication/livraison, chemin déjà utilisé en production pour des ordres réels type « rapporte des planches au coffre ») pouvaient rester bloqués indéfiniment après une pause ou une annulation sans rapport avec la navigation — un chrono de budget de déplacement n'était jamais remis à zéro. Un projet suivant pouvait même hériter du blocage d'un projet précédent annulé, sans lien entre eux.
  • fix(amelia_core): une mission de stock de bois arrêtée puis reprise pendant qu'une récolte restait incertaine (après un redémarrage) confirme maintenant la récolte si le monde l'atteste déjà (compte d'inventaire), au lieu de rester bloquée indéfiniment sans jamais rejouer la commande. Le cas d'un dépôt en coffre incertain reste prudent sans exception : aucune preuve équivalente n'existe pour un transfert.
  • feat(amelia_core): un type d'événement cognitif déclaré depuis l'origine du journal (anomalie : tick dupliqué, régressé ou changement de dimension) n'était jamais construit nulle part ; il alimente maintenant un événement dédié, interrogeable indépendamment, en plus de son signalement déjà existant dans les révisions de croyances.
  • feat(amelia_core): rétention optionnelle (désactivée par défaut) pour le journal de relecture JSONL, purgée une seule fois au démarrage plutôt que pendant la boucle de jeu — borne la durée de conservation du texte de chat brut qu'il capture, sans jamais réécrire le fichier à chaque tick moteur.
  • test(voxelibre): 157 commits, suite complète à 3835 tests réussis, aucun abaissé ni ignoré. Chaque correctif vérifié par reproduction empirique avant écriture du test puis sonde cassée après commit (retrait du correctif, confirmation que le test échoue, restauration). Détail complet : docs/CODEX_AUTONOMY_MISSION_STATUS.md et PR #29.
  • chore(voxelibre): reste non activé — self_coding_enabled=false préservé, aucun entraînement sur conversations privées, service voxelibre toujours désactivé côté serveur. Limites non résolues documentées : réconciliation du dépôt en coffre après incertitude, budget de contexte du prompt entrant toujours estimé en caractères/4 (aucun tokenizer installé), quelques items de conception délibérément écartés (interruption_regret, machine à états Goal complète) faute de besoin démontré.
Version 2.315.0
  • feat(llm): GPT-OSS 20B devient le moteur principal d'Amélia sur le serveur, avec Qwen3 8B en secours. La persona, les souvenirs et le contexte existants sont conservés ; la migration ne réinitialise ni l'identité ni l'historique.
  • feat(outils): les réponses Discord et web peuvent utiliser les appels de fonctions natifs d'Ollama via /api/chat. La passerelle LLM conserve aussi les voies ordinaires et streaming /api/generate ; un changement de modèle ne remplace pas les propriétaires existants des actions.
  • feat(raisonnement): effort faible pour la conversation et la première proposition native, moyen pour les décisions et continuations d'outils, élevé sur demande explicite. GPT-OSS répond en un seul passage lors de l'introspection automatique, sans pré-appel coûteux dont la réponse serait jetée. Le raisonnement natif reste séparé du texte public ; sa continuation est temporaire, sans copie dans la mémoire durable.
  • fix(contexte): la persona statique occupe le rôle système ; souvenirs, échanges et résultats d'outils restent des données de moindre autorité. Schémas, historique d'outils et sortie sont comptés dans le contexte de 8192 tokens ; un historique natif trop volumineux est refusé plutôt que de couper un résultat JSON.
  • feat(actions): jusqu'à trois outils peuvent s'enchaîner avec leurs résultats réels. Les arguments sont validés, les permissions revérifiées à l'exécution et les actions rouges exclues de cette voie. Un résultat incertain après interruption n'autorise pas à rejouer l'effet ; une reformulation de validation ne réexécute pas les outils.
  • fix(memoire): recherche et rappels utilisent l'identité et le canal de la requête réelle, jamais un utilisateur choisi dans les arguments du modèle. Les propositions note_fact restent soumises à validation humaine. Le rappel après réouverture et l'absence de fuite entre utilisateurs sont vérifiés sur une base isolée ; les 6300 anciennes entrées contrôlées en production sont inchangées.
  • fix(delais): un appel HTTP principal dispose de 600 secondes et les tours natifs de 900 secondes. Sur Discord, ce budget inclut les effets et leurs reformulations ; les arrêts et secours musicaux déterministes consomment le temps restant. Le raisonnement explicitement approfondi dispose de plafonds distincts de 900 secondes par appel et 1200 secondes par tour.
  • fix(web): le navigateur conserve la connexion pendant une réponse lente avec des signaux SSE d'attente et un délai client de 915 secondes. Les routes web et Companion réutilisent le contexte privé et la validation commune ; une déconnexion annule la génération sans relancer une action déjà tentée.
  • fix(streaming): le contrat réel de la passerelle accepte maintenant la priorité interactive, applique un budget de sortie et peut basculer vers le modèle de secours avant qu'un fragment ne soit publié. Les erreurs, réponses vides et timeouts GPT-OSS passent au fallback sans répéter une seconde fois le même long appel ; la politique Qwen existante est conservée.
  • feat(sante): /health/llm et /api/health/llm distinguent modèle configuré, installation, chargement, dernier modèle ayant réellement répondu et latence de génération. La projection publique exclut URL internes, erreurs brutes, prompts, souvenirs et secrets ; une simple disponibilité Ollama n'est pas présentée comme une conversation validée.
  • feat(materiel): un contrôle mémoire Linux avant chargement estime RAM, VRAM admissible et coût du contexte, sans compter le swap comme capacité de calcul. Une insuffisance connue peut déclencher le repli ; ce contrôle ne tue aucun service et ne change pas automatiquement le contexte. Le nombre de threads CPU est désormais automatique : le réglage de dix threads imposés surchargeait l'i3-6100 à quatre threads matériels.
  • feat(luanti): des outils de statut, démarrage et arrêt réutilisent les autorisations sensibles existantes et distinguent services actifs et monde réellement frais. Le démarrage, les ticks frais puis l'arrêt ont été contrôlés avec retour à l'état initial. La mémoire et les commandes moteur restent séparées du LLM ; une partie complète n'est pas qualifiée par ce contrôle.
  • fix(musique): les confirmations reprennent le résultat du lecteur : ajouté à la file ne signifie pas déjà joué, et un échec reste un échec. Le vrai lecteur est testé avec une voix simulée ; FFmpeg et Opus ont aussi été exécutés réellement. Une écoute musicale Discord de bout en bout reste à valider.
  • feat(operations): préflight, activation et rollback disposent de sauvegardes, d'écritures atomiques et de contrôles du nouveau processus. Les scripts testent les modèles avant bascule et vérifient le retour arrière. Le futur profil P100 16 Go contrôle sélection GPU, allocation réelle et contextes 8K/12K/16K sans l'activer automatiquement ; cette carte est absente et les contextes supérieurs à 8K restent non qualifiés.
  • fix(companion): le portable de validation du 12 septembre attend désormais 915 secondes face au budget serveur de 900 secondes. La construction Windows privilégie les bibliothèques ICU système pour éviter le conflit Qt/Poppler. Ce portable reste non signé, non installé et non publié ; l'installateur public signé 6.1.5 du 9 août est conservé avec son ancien délai de 105 secondes, ainsi que l'installation locale 6.1.4.
  • test(migration): la suite générale finale compte 3590 tests réussis et 52 ignorés. Sur la configuration CPU finale, six scénarios réels de passerelle, outils et fallback réussissent ; deux échanges privés du service web attestent GPT-OSS et l'identité Amélia. Le benchmark isolé réussit trois scénarios GPT-OSS sur trois et deux Qwen sur trois : l'échec de calcul Qwen est conservé dans le rapport, sans transformer ces échantillons en promesse générale de vitesse ou de qualité.
  • docs(validation): docs/GPT_OSS_MIGRATION.md rassemble architecture, configuration, preuves et limites. Conversation Discord vivante, écoute musicale, observation prolongée de l'autonomie et qualification P100 restent distinctes des tests réussis. Le disque demeure autour de 91 % ; l'alerte de supervision au seuil de 90 % n'a pas été masquée.
Version 2.314.0
  • feat(voxelibre): suivi et STOP locaux, COME ponctuel, détours courts mis en cache, passage des portes et descentes vérifiés en instance isolée.
  • feat(voxelibre): projets persistants de récolte, fabrication et livraison fondés sur le catalogue du monde et des reçus moteur attribués ; scénario de 16 bûches avec 32 planches et 8 bûches déposées au coffre.
  • fix(voxelibre): les dépôts partiels préservent les métadonnées ; un craft interrompu après reçu durable peut être réconcilié sur reprise explicite sans duplication, avec contrôle des slots, de l'usure et des métadonnées.
  • fix(voxelibre): une demande de petite maison reste un projet de construction, jamais une récolte de substitution ; les réponses sur l'activité lisent le projet réel. Maison autonome, stations et qualification complète de P0 restent inachevées.
Version 2.311.0
  • feat(conversation): Amélia retrouve une continuité temporelle factuelle entre deux échanges. Le prompt distingue désormais le dernier contact réel, le premier échange depuis le redémarrage et la durée écoulée, y compris dans les salutations courtes, sans transformer le temps passé en émotion programmée.
  • feat(memoire): les sujets laissés ouverts survivent aux redémarrages et sont rejoués avec leur ancienneté. Une réponse d'Amélia clôt la question correspondante avant l'élagage, tandis qu'un échec transitoire de génération la conserve pour une reprise ultérieure ; le cloisonnement utilisateur/salon reste appliqué.
  • feat(expression): le profil dynamique et la présence publique restent visibles dans le prompt allégé, les émotions naturelles coordonnées sont reconnues plus largement et les filtres retirent seulement les marqueurs réellement artificiels. Amélia dispose ainsi de davantage de liberté de ton et de comportement sans perdre les garde-fous factuels, privés et de sécurité.
  • obs(llm): chaque appel génératif brut est attribué à son usage, son cycle de réponse, sa tentative de transport ou de modèle et sa raison de relance, streaming compris. Les traces n'enregistrent ni prompt, ni réponse, ni mémoire privée.
  • fix(reponse): une salutation sûre déjà ramenée au nombre de phrases et de caractères autorisé est publiée dès la première génération au lieu de payer une relance identique pouvant finir sur un silence de repli.
  • fix(companion): le client conserve le budget de production du chat long : 255 secondes face aux 240 secondes du serveur, sans ancien plafond de lecture caché à 120 secondes. Une génération lente peut ainsi rendre sa réponse ou son erreur finale au lieu d'être interrompue prématurément.
  • chore(runtime): l'autonomie conversationnelle est activée, le jeu Luanti/VoxeLibre et le travail privé pendant le sommeil sont désactivés. Le watchdog respecte l'arrêt volontaire de Luanti tout en continuant de surveiller Aurore, Nginx, Ollama et MusicGen.
Version 2.310.0
  • fix(conversation): le message courant prime enfin sur le tour précédent. Incident réel : « Dans voxellibre tu parle ? » — question ciblée sur une capacité précise — recevait « je suis en train de me soigner », recyclé sans rapport du tour précédent. Classée `general` (aucun des 4 patterns `simple_social` ne couvre les questions d'activité), or `response_directive` ne s'applique jamais à `general` : le correctif ne pouvait pas y vivre. Vit désormais dans `critical_tail`, le seul bloc qui atteint toujours le modèle quel que soit le profil — vérifié qu'il survit bien AVANT la réécriture complète de critical_tail propre à simple_social/conversation_correction, qui ne couvre aucun des exemples de l'incident.
  • fix(voxelibre): l'état de jeu réel atteint enfin le prompt. `derive_voxelibre_experience(...).facts` (« sous terre », « dans le noir », danger, faim…) était calculé et testé isolément depuis le 2026-08-06 mais `grounding_clause` ne lisait que fresh/allowed_emotions/allowed_physical : « tu es encore sous terre ? » ne pouvait jamais recevoir de réponse ancrée sur la position réelle, même heartbeat frais.
  • fix(conversation): profils sociaux courts étendus et proportionnés. `classify_message` ne reconnaissait que les questions d'humeur (« comment tu vas ») comme sociales ; une salutation seule (« Coucou », « Salut ») ou un small talk générique (« Tu fais quoi ? », « Quoi de neuf ? ») tombaient en `general`, sans aucune borne. Nouveau profil `greeting` (1-2 phrases, ~240 caractères) et extension du profil `simple_social` existant à l'activité générique (3 phrases, 320 caractères, inchangé). Les deux nouveaux motifs sont ancrés sur le message ENTIER : « Salut, pourquoi mon API retourne 401 ? », « Erreur 401 ? » et « Port SSH ? » restent `general` — une vraie demande technique gagne toujours, jamais une histoire de longueur.
  • test(reponse): corpus d'audit étendu — « mécanisme interne exposé » via le vocabulaire technique de récupération (« j'ai vérifié les traces/logs »), en plus du vocabulaire mémoire déjà couvert. Trois autres catégories demandées (durée inventée, perception non ancrée générique, contournement lexical) sont documentées comme dettes ouvertes plutôt que couvertes par un détecteur bâclé : aucune n'a de mécanisme fiable sans risquer de nouveaux faux positifs, et aucun incident réel ne les a encore déclenchées.
  • refactor(voxelibre): le cerveau de jeu partage enfin la politique centrale de raisonnement. `OllamaBrain._http_generate` construisait son payload Ollama sans jamais consulter `needs_think_disabled()` — le même défaut déjà corrigé deux fois ailleurs (voie streaming, classificateur d'engagement), à chaque fois parce qu'un nouveau site d'appel avait dupliqué ou oublié la règle. Le modèle par défaut ici (qwen2.5) n'en avait pas besoin, mais il est configurable : une future bascule vers Qwen3/3.5 aurait silencieusement réintroduit le même bug une troisième fois.
  • fix(luanti): source unique de vérité pour le port serveur/client. `run_luanti_server.sh` ne passait `--port` que si `LUANTI_PORT` était déjà dans l'environnement, sinon il retombait en silence sur le défaut interne de Luanti — un troisième port possible, jamais annoncé nulle part dans ce dépôt. Partage désormais le même défaut explicite que `run_luanti_client.sh` (30000) et passe toujours `--port`. Test de cohérence source ajouté entre les deux scripts et le fichier `luanti.env` généré à l'installation.
  • docs(config): le piège `lean_prompt` / `SYSTEM_PROMPT` documenté là où on le cherche. `SYSTEM_PROMPT` (non lean) reste un chemin de code légitime (`select_system_prompt`), pas du code mort — mais il dépasse `ollama_num_ctx` (~12 900 tk > 10 240) et il manque 11 invariants qui n'existent que dans la version LEAN, déjà mesuré dans un rapport de qualification daté jamais recroisé depuis `docs/CONFIGURATION.md`.
  • chore(audit): 29 worktrees Git classés (merge-base + état de l'arbre de travail), aucun supprimé. Dix-neuf `amelia_self_patch_*` résiduels dans %TEMP% partagent tous un commit déjà fusionné dans master ; dix-huit portent la même modification non commitée de `topic_tracker.py`, signe d'un mécanisme d'auto-correction qui retente le même correctif sans jamais l'committer. Un test préexistant (`test_self_patch.py`) documentait déjà ce risque de fuite ; la suite complète s'est montrée ponctuellement instable en présence de cet état résiduel (2 échecs sur un run, 0 sur le suivant à code identique) — dette de fiabilité de suite non résolue ici, signalée pour investigation séparée.
Version 2.309.0
  • fix(llm): la voie STREAMING coupe enfin la réflexion interne du modèle — c'est celle du chat web ET de la voix. Qwen3.x renvoie sa réflexion dans un champ `thinking` SÉPARÉ et laisse `response` VIDE si on ne la coupe pas. La voie directe posait `think: false` ; la voie streaming l'avait oublié. Mesuré sur la production : sans le drapeau 0 caractère de réponse pour 275 de réflexion, avec 9 caractères et 0 de réflexion. Une réponse streamée n'émettait donc RIEN. La règle vivait en double, écrite en ligne à un seul des deux endroits ; elle devient la fonction `needs_think_disabled()`, parce qu'une règle dupliquée à la main finit par diverger. Vérifié après déploiement en appelant `stream_response` avec le code déployé : 33 et 28 fragments reçus là où il n'y en avait aucun.
  • fix(llm): le classificateur d'engagement aussi — troisième chemin du même défaut, trouvé par le test de garde du précédent. Il demande UN mot avec `num_predict: 6`, le cas le plus fragile possible : les 6 tokens partaient entièrement dans `thinking`. Le classement retombait donc TOUJOURS sur l'heuristique, après un aller-retour réseau pour rien. Le test compte désormais les appels GÉNÉRATIFS face aux consultations de la règle — pas les payloads de gestion VRAM, dont le prompt est vide et qui ne lisent jamais `response` : un test qui crie sur du code sain finit ignoré.
  • test(memoire): les deux invariants les plus dangereux sont enfin prouvés. Ses propres phrases ne peuvent pas devenir un fait sur son interlocuteur — le garde-fou tient par un couplage INVISIBLE, `memory.py` étiquetant l'orateur avec la même constante littérale que le filtre de `memory_learning.py`, à 5 000 lignes d'écart et sans rien qui les relie. Et ce qu'a dit A ne peut pas devenir le profil de B : un test couvrait le cloisonnement par salon, aucun ne couvrait deux personnes dans le MÊME salon, qui est le cas dangereux et le seul que `unify_by_user` peut assouplir. Onze tests au total.
  • test(voxelibre): un heartbeat gelé ne passe plus pour une partie vivante. `_read_fresh_voxelibre_status` décide SEULE si Amélia a le droit de dire « je joue » ; les tests existants la moquaient pour éprouver ce qui vient après, sa propre logique n'était couverte nulle part. Si le jeu plante, le fichier reste avec `active: true` : sans contrôle de fraîcheur elle raconterait indéfiniment une partie terminée. Neuf tests, dont la borne SYMÉTRIQUE — `max(1.0, ...)` empêche de réduire la fenêtre à zéro, ce qui la ferait NIER une partie réelle.
  • test(securite): `aurore.sh` ne peut plus renvoyer le mot de passe sudo sur le réseau. Le correctif de la veille n'était protégé par rien : aucun test ne couvrait les scripts shell, et ShellCheck ne verrait rien puisque réintroduire l'interpolation est du bash parfaitement valide. Le test exécute le VRAI script avec un faux plink et lit l'argv qui part réellement — il ne relit pas le source, donc une reformulation cosmétique ne le casse pas et une reformulation dangereuse si.
  • fix(ci): master était rouge depuis des jours, et ce rouge cachait une suite de tests cassée. Mypy échouait sur 33 erreurs visibles seulement depuis Linux (noms définis dans un `if sys.platform == "win32"`, `ctypes.windll` absent des stubs, un `__init__` qui lève rendant un attribut indéterminable) — reproduites en local avec `--platform linux` plutôt que devinées. L'étape échouant AVANT la suite complète, celle-ci ne s'exécutait jamais : la réparer a révélé quatre tests cassés, tous dépendants de la machine hôte — l'uptime via `time.monotonic()` depuis 0, le coffre Windows, des permissions POSIX dans un fixture, et un job Windows qui n'installait pas ses propres dépendances. Aucun test supprimé ni assoupli.
  • chore(ci): CodeQL retiré des déclencheurs automatiques — il ne peut pas réussir ici. Il publie via l'API code scanning, qui sur un dépôt PRIVÉ exige GitHub Advanced Security ; tentative faite, réponse `422 Advanced security has not been purchased`. Le job échouait à chaque push avec un message trompeur, pour un réglage qui n'existe pas tant que GHAS n'est pas acquis. Le fichier est conservé avec la raison écrite dedans et `workflow_dispatch` actif. Bandit et Gitleaks assurent la couverture, tous deux verts. Résultat : CI entièrement verte sur master pour la première fois.
  • fix(jeu): le mode créatif redevient INDIVIDUEL. `creative_mode` est un réglage GLOBAL : l'activer pour Yann le donnait aussi à Amélia et vidait ses objectifs de survie de tout sens mécanique — or c'est précisément ce que lit son ancrage corporel. VoxeLibre sait faire du créatif par joueur (`mcl_gamemode`, `minetest.is_creative_enabled(playername)`) : le réglage global repasse à false et `/gamemode creative <joueur>` accorde le créatif à qui le veut, sans toucher à sa partie.
Version 2.308.0
  • fix(securite): `hashlib.sha1` de l'empreinte de découverte du carnet portait `usedforsecurity` implicite — Bandit le classait en sévérité HIGH (B324) et faisait échouer la CI. Introduit par moi la veille (`4284d83`) et jamais vu, faute de statut GitHub. Le hash sert de clé de déduplication, jamais de sécurité : l'intention est désormais écrite dans le code. Bandit repasse à exit 0.
  • feat(demarrage): `lean_prompt: false` ne peut plus s'activer en silence. Le piège était double — `SYSTEM_PROMPT` pèse ~12 900 tokens pour un `num_ctx` de 10 240, donc il déborde la fenêtre AVANT le moindre message, ET `lean_prompt` vaut `False` PAR DÉFAUT : une installation neuve, ou un `config.json` où la clé disparaît, partirait dans un mode où chaque appel est élagué et le contexte perdu sans qu'aucune erreur ne le signale. Le démarrage refuse désormais explicitement, en nommant le prompt fautif, la marge restante et les deux issues (`lean_prompt: true` ou `ollama_num_ctx` plus grand). La vérification est une fonction pure de `prompt_guard`, et elle surveille aussi le mode lean au cas où il regrossirait.
  • feat(demarrage): barrière sur l'autonomie publique. `public_channel_disable_autonomy` vaut `false` à l'inverse du défaut du code ; c'est inerte tant que `enable_autonomy` est `false`, mais le jour où l'autonomie est activée, les salons publics changent de comportement dans la même seconde. Un avertissement encadré sort au démarrage exactement dans cette combinaison. On n'empêche pas le démarrage : c'est une décision de Yann, et la refuser le bloquerait au pire moment.
  • test(reponse): corpus d'audit des règles de réponse — 24 phrases légitimes qui ne doivent JAMAIS être signalées, 13 interdites qui doivent l'être avec le bon code. L'audit a trouvé QUATRE faux positifs avant la production, tous du même parfum que les incidents précédents : « comme tu sais » pris pour un tic SAV, « je lui ai dit bonjour ce matin » pris pour une salutation hors créneau (une salutation RAPPORTÉE n'en est pas une), « en espérant que ça marche » pour une clôture d'assistant, et « je joue souvent avec les mots » pour une consommation culturelle — ce dernier étant le plus grave, `sensory_claim` n'étant pas stylistique, il la rendait muette. Les quatre motifs sont resserrés, leurs contreparties interdites restent attrapées, et le corpus les verrouille.
  • fix(voxelibre): le bloc d'activité devient proportionné à la question. « Comment tu vas ? » recevait encore un rapport de partie (« ça va, je viens de creuser un peu pour me débarrasser d'un mur de pierre… ») : techniquement vrai, hors sujet. Cause structurelle et non un défaut d'obéissance — le bloc « ⟦Activité en cours⟧ » fait près de 900 caractères et il est maximalement insistant (« IMPORTANT », « c'est un FAIT présent », « tu joues LÀ »), parce qu'il avait été écrit pour corriger le problème INVERSE : elle niait jouer. Sur un profil social simple le contexte se réduit aux activités plus quatre messages, si bien que ce pavé devenait l'essentiel de ce que voyait le modèle. Le bloc complet n'est désormais injecté que si le message porte sur son occupation — « tu joues ? », « tu fais quoi ? », une mention du jeu. Sinon une ligne courte conserve le FAIT et l'interdiction de nier, sans l'invitation à raconter. Dix-neuf tests fixent les deux bords, dont l'invariant gagné de haute lutte : elle ne peut toujours pas nier qu'elle joue, et la ligne courte garde son marqueur prioritaire pour survivre à la compression.
  • fix(reponse): un défaut de FORME ne la rend plus muette. Quand toutes les tentatives échouaient, `_assess_candidate` posait `requires_sanitization=True` pour TOUT rejet de la politique de réponse — or le repêchage exclut précisément ce cas. Un simple ton SAV suffisait donc à produire « Oups, j'ai eu un blanc », au même titre qu'une invention. Les onze points de détection renvoient désormais une `ResponseViolation` typée — code stable et indicateur `stylistic` — au lieu d'une phrase libre. Sujet écarté, salutation hors créneau et ton SAV sont publiables en dernier recours, le sanitizer existant retirant ensuite la phrase fautive. Revendication sensorielle, action de jeu sans état live, exposition du mécanisme mémoire, contradiction d'un fait appris et dérives d'identité restent NON publiables : elles affirmeraient quelque chose de faux, et le silence y est le moindre mal. Une seule invention parmi des défauts de forme suffit à bloquer le repêchage. Le candidat repêché pour la forme se donne `repetition_score=1.0` afin de passer APRÈS toute réponse seulement répétitive — une redite saine vaut mieux qu'un ton fautif. Quatorze tests fixent les deux bords, dont le scénario exact du 2026-08-07.
  • fix(reponse): « comment tu vas ? » ne se solde plus par « Oups, j'ai eu un blanc ». Constaté deux fois d'affilée le 2026-08-07 à 08:17 et 08:20 : les trois candidats étaient rejetés et Amélia répondait par son message de repli. Cause : `_SENSORY_CLAIM_PATTERNS` n'exigeait AUCUNE œuvre — `j'ai (vu|lu|joué|aimé|fini)` suffisait à déclencher « tu prétends avoir vu/lu/écouté/joué à une œuvre ». Or elle jouait à VoxeLibre à cet instant, si bien que « ça va, j'ai fini de creuser mon tunnel » — la réponse la plus naturelle qui soit — était la plus sûrement rejetée, tout comme « j'ai vu que tu étais là » ou « j'ai aimé ce moment de calme ». Même classe de défaut que la cause racine de 2.301 : un motif qui matche sans ancre sémantique. Le verbe doit désormais être suivi d'une œuvre (nom de média, ou « joué à <titre> »). Limite assumée et testée : un titre nu (« j'ai vu Interstellar ») n'est plus attrapé, la comparaison se faisant sur du texte minuscule ; le récupérer imposerait de signaler tout nom propre, donc « j'ai vu Yann hier ». Vingt tests fixent les deux bords. Le défaut est ANTÉRIEUR à 2.308.0 — quatre replis de génération dans les sept jours précédents.
  • fix(securite): `scripts/aurore.sh sudo` ne peut plus faire fuiter le mot de passe. Il l'interpolait DANS la chaîne de commande envoyée au shell distant : la moindre apostrophe cassait le quotage, et le shell renvoyait alors la ligne entière — mot de passe compris — dans son message d'erreur, donc dans les journaux, les transcriptions et toute sortie collectée. Constaté deux fois le 2026-08-07. Le commentaire du code demandait d'éviter les apostrophes ; on supprime la cause au lieu de la consigne. Le mot de passe transite désormais par le STDIN distant et n'apparaît jamais dans la commande, laquelle est encodée en base64 — donc insensible aux apostrophes, guillemets et dollars. Vérifié sur le cas exact qui avait fuité, et sur le chemin d'échec, où l'erreur remonte proprement sans secret.
  • obs(carnet): `malformed_decision` était un constat sans cause. Quatre occurrences en sept jours — trois AVANT le déploiement de 2.308.0, donc sans lien avec la réduction du prompt — et chacune coûte une entrée du carnet. La trace disait que la sortie du modèle était illisible sans dire pourquoi. Une reproduction sur la production (trois tirages du vrai prompt de décision : 527, 612 et 595 caractères, `done_reason=stop`, JSON valide) a RÉFUTÉ l'hypothèse de troncature par dépassement du budget de sortie : la cause reste inconnue. Plutôt que d'échafauder un correctif sur une cause non établie, la trace publie désormais la longueur brute, la présence de chaque accolade et si la réponse était vide — de quoi trancher à la prochaine occurrence. Jamais le contenu : il appartient à son carnet.
  • perf(prompt): ~2 000 tokens rendus à chaque appel, sans retirer une seule protection. Mesuré sur 34 relevés de production : le pire prompt pesait 9 420 tokens pour 420 de marge — et sa trace montrait `long_memory=0, profile=0, goals=0, summaries=0`. Autrement dit la marge ne tenait que parce que AUCUNE mémoire n'était injectée ; elle aurait sauté dès que la mémoire se serait remplie. `SYSTEM_PROMPT_LEAN` passe de 9 200 à 6 198 caractères en disant chaque règle une seule fois : l'anti-invention était énoncé quatre fois (VÉRITÉ, RESSENTI, PERSONNES, TEMPS) et l'anti-formule-SAV quatre fois (LANGUE, TON, ÉMOTIONS, ESPRIT CRITIQUE). Les sections LANGUE/TON/VOIX/GOÛTS fusionnent en une seule VOIX, ÉMOTIONS/ESPRIT CRITIQUE/REFUS rejoignent TOI.
  • perf(directives): les directives injectées à chaque appel passent de 5 622 à 2 571 caractères. Sept d'entre elles ne faisaient que redire le prompt système — `identite`, `voix_propre`, `ton_naturel`, `refus_validation_reflex`, `authenticite_emotionnelle`, `honnetete_memoire`, `evolution` — et six autres le redisaient à moitié. Le principe retenu : un invariant vit dans le PROMPT, code versionné, testé et couvert par le manifeste de déploiement, pas dans une table modifiable où rien ne le vérifie. `interpretation_bienveillante` (le contrepoids gagné à l'incident du 2026-08-05) et `apprentissage` (le flux `!validate`) sont conservées INTÉGRALEMENT : le prompt ne porte ni l'un ni l'autre.
  • fix(directives): la suppression en base seule aurait été annulée au redémarrage suivant — `_seed_default_self_directives` repropage les directives manquantes à chaque démarrage. Le correctif est donc dans le code des graines, doublé d'une migration `_compact_legacy_self_directives` qui réconcilie les bases déjà semées. Prudence délibérée, reprise du motif déjà éprouvé sur les traits : une directive n'est retirée ou resserrée QUE si son texte est encore exactement celui du seed d'origine — une directive retouchée par l'admin est un choix, pas un doublon. Idempotente, sans effet sur une table vide, et incapable de bloquer le démarrage si la base répond mal.
  • test(prompt): 26 invariants sont désormais verrouillés — identité, anti-invention, absence de corps, non-confusion des personnes, discrétion, ancrage horaire, anti-théâtre d'empathie, droit de refuser, rythme sans question réflexe. Le filet a servi pendant la réduction elle-même, et quatre tests plus anciens ont rattrapé quatre formulations exactes que la réécriture avait paraphrasées (« j'en suis sûre », « tu n'inventes jamais de preuve à l'appui », « ne confonds pas », « comme s'il était toujours en cours ») : elles sont restaurées dans le prompt, les tests n'ont pas été assouplis. Un plafond de 7 000 caractères empêche désormais le prompt de regrossir règle par règle.
  • docs(audit): constat mesuré au passage — `SYSTEM_PROMPT` (le prompt complet, non lean) pèse ~12 900 tokens pour un `num_ctx` de 10 240. Il ne tient donc pas dans la fenêtre AVANT le moindre message : basculer `lean_prompt: false` garantirait un élagage à chaque appel. C'est aussi pourquoi il a dérivé — onze des invariants ci-dessus n'existent que dans la version lean. Le constat est figé par un test pour qu'il soit décidé plutôt que subi, sans alourdir un prompt déjà inutilisable.
Version 2.307.1
  • fix(audit): `check_llm_call_boundary` qualifie enfin le bon périmètre. Il balayait `root.rglob('*.py')`, donc les worktrees imbriqués et les fichiers git-ignorés : un BOM `U+FEFF` dans `.claude/worktrees/.../luanti_agent/actions/__init__.py` levait une `SyntaxError` et rendait le contrôle rouge sur ce poste, alors que ce fichier n'appartient pas au checkout audité et n'existe pas dans un clone frais. Le scanner s'appuie désormais sur `git ls-files`, avec un repli qui écarte les répertoires cachés hors dépôt, et lit les sources via `tokenize.open()` — un BOM légal dans un fichier SUIVI est accepté au lieu de faire échouer l'audit. Sept tests couvrent les cas A→E de la demande plus le repli. Effet immédiat : la suite locale passe intégralement pour la première fois, l'unique échec restant étant précisément cet artefact d'environnement.
  • feat(observabilite): trace `[PROMPT_TRIM]` — une troncature du prompt n'est plus silencieuse. Quand le budget déborde, le garde-fou sacrifie le MILIEU du prompt, donc le contexte ancien et la mémoire : Amélia paraît oublieuse sans qu'aucune trace ne l'explique. Le warning existant est throttlé à cinq minutes et ne porte aucun chiffre. La nouvelle trace sort à chaque événement avec `before_tokens`/`after_tokens`, les caractères, `num_predict` avant→après et la stratégie réelle. Elle distingue le vrai élagage (`trim_middle`) du cas plus discret où seule la sortie rétrécit (`output_only`), qui raccourcit ses réponses sans toucher au prompt. Aucun `removed_blocks` n'est publié : le prompt est déjà aplati en une chaîne unique à ce point, il n'existe plus de blocs nommés à énumérer — les inventer aurait donné une trace fausse.
  • chore(config): les neuf réglages livrés en 2.301→2.307 (traces de budget, de grounding, carnet autonome) sont écrits explicitement dans `config.json`, à leur valeur active inchangée. Ils ne reposaient que sur un défaut de `config.py` : un défaut qui bascule, et la production change de comportement sans qu'aucun fichier de configuration n'ait bougé. Un test lit les littéraux de `config.py` par AST et vérifie que les deux coïncident, si bien qu'une divergence future devient une décision et non un accident. `config.json` étant suivi par Git, un test vérifie aussi qu'aucun champ jeton/secret n'y porte de valeur (ils sont bien tous vides ; seule la production a les siens, ce fichier étant exclu du manifeste de déploiement).
Version 2.307.0
  • fix(style): une question sur son état n'est pas une question sur son activité. Constaté deux fois : « comment tu vas ? » puis « comment tu te sens ? » ont reçu un état de jeu (« je cherche du fer », « je suis coincée dans ce bloc de pierre »). Ce profil bascule en `short_context_only` et la ligne « Activité en cours » est à la fois prioritaire et préservée à la compression : elle devient l'essentiel du contexte pour exactement cette question. L'interdit existant (« ne ramène jamais ce sujet dans une conversation qui parle d'autre chose ») ne couvrait pas le cas, l'état de jeu ÉTANT son état de son point de vue. Le profil social distingue désormais l'état intérieur de l'occupation : répondre d'abord par l'humeur, l'activité n'étant mentionnée que si elle explique vraiment cet état, en complément bref — jamais comme réponse entière ni deux fois de suite. Elle garde le droit de dire qu'elle est bloquée : c'est la proportion qui change, pas la permission.
  • fix(runtime): fermeture propre sur SIGTERM — les redémarrages cessent de se faire passer pour des crashs. `systemctl restart` envoie SIGTERM ; sans gestionnaire, Python terminait le process avant que `close()` — et son marqueur `clean_shutdown=True` — ne s'exécute. Chaque redémarrage normal déclenchait donc au démarrage suivant l'alerte « le run précédent ne s'est pas arrêté proprement » : une dizaine de fausses alertes en une journée de déploiements, au point qu'un VRAI plantage n'était plus discernable d'un déploiement voulu. Best-effort : `add_signal_handler` n'existe pas sous Windows et un échec d'enregistrement ne doit jamais empêcher le démarrage.
Version 2.306.0
  • fix(repetition): une ouverture reprise MOT POUR MOT est de nouveau détectée — régression introduite par le plancher souple de 2.301.2. Constaté en production : « Bof, j'ai la tête ailleurs, je cherche du fer en ce moment. » republié à l'identique en tête d'une réponse cinq fois plus longue. Le plancher s'appuyait sur la similarité GLOBALE, tombée ici à 0.314 (seuil souple 0.48), si bien que la règle d'ouverture était étouffée : allonger la suite rachetait de rouvrir à l'identique. Un début verbatim de 40 caractères ou plus compte désormais comme une redite quelle que soit la longueur du reste, le plancher souple restant en place pour les ouvertures courtes — le faux positif d'origine (« Je sais pas… », 11 caractères communs) est toujours écarté, et un test garde les deux comportements. La similarité est évaluée AVANT le préfixe verbatim : sinon une redite intégrale serait rapportée « opening » et la relance demanderait de changer le début d'une phrase répétée en entier.
  • fix(validation): juger la légitimité d'une question au lieu d'y répondre est désormais rejeté. À « Tu joues à VoxeLibre ? », elle a répondu « Je te trouve bizarre de demander ça maintenant ». `_PSYCHOLOGY_RE` ne couvrait pas ce cas — ce n'est pas une intention psychologique prêtée mais un reproche sur le fait même de poser la question, même famille que l'incident du 2026-08-05 et surface nouvelle. Motif `question_judged`, majeur donc régénéré ; quatre cas ordinaires vérifient qu'un compliment (« je te trouve calme ») ou un avis (« je trouve ça intéressant ») ne tombe pas dans le filet.
  • feat(carnet): trois signaux supplémentaires, chacun tiré d'une source canonique avec identifiant stable — découverte (croyance `voxelibre:*`, empreinte de la formulation), objectif terminé (`goal_done:<id>`), bilan de journée (`day_review:<date>`). La CRÉATION n'est délibérément pas branchée : le dépôt ne conserve aucune trace durable d'une image ou d'une musique produite, donc aucun identifiant à acquitter — lui fabriquer un stockage pour remplir le carnet reviendrait à inventer l'événement. Un test a révélé que `suppress(Exception)` masquait une vraie erreur (`MemoryReadScope.AGENT` n'existe pas ; les membres sont CHANNEL, USER, INTERNAL_ALL), si bien que le signal « objectif terminé » n'aurait jamais fonctionné sans qu'aucun log ne le dise.
  • test(carnet): le trajet complet est couvert de bout en bout — décision structurée → `Atelier.write` → fichier du jour daté par le SERVEUR → `/api/espace/state` → page qui n'affiche plus « Rien écrit aujourd'hui ». Vérifie aussi qu'aucun message Discord ni événement système n'en sort, qu'un redémarrage conserve cooldown, plafond et acquittements, et qu'une décision `skip` ne consomme pas l'événement.
Version 2.305.0
  • feat(carnet): le carnet reçoit enfin des FAITS et non un booléen. Il ne recevait que `game_event=true`, si bien que sa consigne se résumait à « un moment de jeu notable vient de se terminer » — une invitation à inventer tout ce qui manquait, exactement ce que le correctif d'ancrage cherchait à empêcher. `build_session_event` fige les faits réels de la fin de session (durée, objectif, points de vie, moments notables, dernière action), chacun venant du heartbeat ; un champ que l'état ne porte pas est simplement ABSENT plutôt que comblé. La position exacte est délibérément exclue — un journal intime n'a pas besoin de coordonnées, les y publier en ferait un relevé technique — et deux tests le vérifient, dont un sur un événement rechargé où des coordonnées auraient été glissées par erreur : seuls les champs attendus survivent. Les faits sont persistés dans `agent_settings`, la boucle du carnet pouvant passer jusqu'à vingt minutes après la fin de session, voire après un redémarrage.
Version 2.304.2
  • fix(repetition): le repêchage publie le candidat sûr le MOINS répétitif, plus simplement le dernier. Deux réponses également publiables n'ont aucune raison d'être départagées par leur ordre de production — et la seconde, produite après une relance qui chauffe le modèle, est souvent la moins bonne. Chaque évaluation porte désormais son score de redite (similarité maximale face à l'historique, salon et global confondus) et le service retient le minimum. Un rejet de sécurité, de validation ou de sanitization reste exclu QUEL QUE SOIT son score : un test le vérifie en lui donnant un score parfait. `[REPETITION_CHECK]` publie score, seuil, portée (channel/global/semantic/assistant_phrase), règle, tentative et décision ; `matched_age_seconds` est volontairement absent car l'historique par salon ne conserve que des textes sans horodatage — mieux vaut l'omettre qu'inventer une valeur.
Version 2.304.1
  • fix(context): détection algorithmique du recyclage de ses propres formulations. « je ne suis pas un disque dur vide » : la table `memory` montre qu'elle a employé cette image QUATRE fois en deux jours, la dernière en réponse à un simple encouragement sans rapport. Correction d'une erreur d'analyse au passage — l'image avait été attribuée à un message de Yann, alors que les quatre lignes sont de type `assistant` ; le script d'audit filtrait sur une colonne `message_type`/`role` inexistante (la vraie est `type`), si bien que tous les messages passaient pour des messages utilisateur. `detect_phrase_echo` signale un groupe de 2-3 mots PORTEURS repris d'une réponse récente lorsque rien ne l'appelle. Ce n'est PAS une liste noire : l'image reste permise si l'interlocuteur vient d'en parler ou si un fait pertinent la porte, et le contrôle examine aussi les paires internes — sans quoi prolonger « tu es un disque dur ou quoi ? » par « disque dur vide » aurait été refusé à tort. Les formules ordinaires sont protégées par construction (deux mots pleins hors mots-outils, huit caractères, un mot d'au moins cinq lettres) : « je comprends », « merci », « je suis là » ne peuvent pas être signalés. Rejet stylistique donc `salvageable` — si les tentatives s'épuisent la réponse part quand même — et la relance demande d'abandonner la comparaison sans toucher aux faits, sans hériter des consignes d'ouverture ou de chute.
Version 2.304.0
  • feat(trace): `[FINAL_PROMPT_BUDGET]` mesure le prompt RÉELLEMENT transmis, au dernier point avant le POST. `[PROMPT_BREAKDOWN]` ne mesurait que les blocs DISPONIBLES avant assemblage : relevé en production, 19 592 caractères de blocs pour 22 782 réellement transmis — l'écart (prompt système + assemblage) était invisible, or c'est lui qui décide si la réponse tient dans `num_ctx`. La trace réutilise le budget déjà calculé par `check_budget`/`fit_prompt_and_output`, jusqu'ici journalisé en DEBUG donc jamais vu en production, et l'estimateur du projet (`prompt_guard.estimate_tokens`, 3 caractères par token) au lieu d'un chars/4 qui sous-estimait d'un tiers. **Ce que la mesure révèle : 7 594 tokens estimés pour num_ctx=8192 dont 512 réservés à la sortie, soit 86 TOKENS DE MARGE** — la conversation était à un cheveu de l'élagage silencieux du milieu du prompt, lequel n'émet qu'un avertissement throttlé à 5 min. Ollama étant appelé via `/api/generate` avec un `prompt` unique aplati (ni `messages` ni `tools` séparés), la trace rapporte la structure réelle plutôt que des champs inventés. Tailles seulement ; désactivable par `enable_final_prompt_budget_trace`.
  • fix(events): un rapport de fin de session VoxeLibre ne se colle plus devant une réponse humaine. À « Coucou comment tu vas ? », rapport et réponse partaient dans la même seconde et Discord, groupant les messages consécutifs d'un même auteur, faisait passer le rapport technique — coordonnées comprises — pour le début de la réponse d'Amélia. Vérifié AVANT de corriger : le rapport n'entre pas dans la table `memory` (0 ligne en production), donc il ne contaminait ni `short_context` ni l'anti-répétition ; le défaut était purement temporel et le correctif porte sur l'ordonnancement, pas sur un filtrage de contexte qui n'aurait rien changé. Un événement système attend désormais tant qu'une réponse humaine est en cours (génération en vol, burst ouvert, ou message reçu il y a moins de 25 s) puis part DERRIÈRE elle ; priorité PAR SALON, file dédupliquée et bornée à 3 — deux fins de session concurrentes ne produisent qu'un rapport. La file vit en RAM par contrat documenté et testé : un rapport de session est daté et périssable, le republier après un redémarrage parlerait d'une partie finie depuis longtemps. Coordonnées et commande brute partent dans `[VOXELIBRE_REPORT]` côté logs : aucune donnée perdue, seule la visibilité change.
  • fix(carnet): les quotas survivent au redémarrage et un événement n'est acquitté qu'APRÈS écriture durable. Constaté sur la première journée : cooldown et plafond quotidien vivaient en RAM, et un redémarrage une minute après la toute première entrée a remis le compteur du jour à zéro. `CarnetState` est persisté dans `agent_settings`, daté avec `server_timezone` et non `datetime.now()` naïf ; une valeur illisible dégrade en état PRUDENT (compteur saturé) plutôt que permissif. Par ailleurs la fin de session VoxeLibre était marquée traitée AVANT le cooldown, l'appel LLM, la validation du JSON et l'écriture : un simple refus la perdait définitivement sans qu'elle ait jamais été racontée. L'événement est maintenant OBSERVÉ puis acquitté seulement après écriture réussie, la liste des acquittements — bornée à 40 — survivant au redémarrage. `enable_private_carnet_trace` sépare enfin la trace du carnet de celle de l'ancrage VoxeLibre.
  • fix(repetition): `repetition_opening` et `repetition_closing` sont distingués. Le code unique servait pour les deux règles depuis la veille : viser la mauvaise fait retoucher la partie qui allait bien. Chaque motif reçoit sa consigne — « commence par autre chose » ou « termine autrement » — en précisant ce qui peut rester inchangé ; une similarité globale ne reçoit ni l'une ni l'autre.
  • fix(context): la directive canonique interdit de recycler d'anciennes formulations. Constaté en production : « je ne suis pas un disque dur vide », une image tirée d'un message de Yann de la veille encore présente dans le contexte court, resservie comme sienne hors sujet. Règle plutôt que liste noire : le message courant prime, l'historique sert à comprendre le fil et non de réserve de phrases, une image déjà employée n'est reprise que si elle répond à ce qui vient d'être dit.
  • docs(audit): `docs/AUDIT_CLOTURE_2026-08-06.md` — atteste les 779 fichiers déployés contre le commit annoncé, qualifie les 21 branches sans en fusionner ni supprimer aucune, et diagnostique l'unique contrôle rouge (`check_llm_call_boundary` échoue sur un BOM dans un worktree imbriqué git-ignoré, absent d'un clone frais, donc vert en CI). Corrige la prémisse sur `agent/luanti-integrity-doors` : son seul commit unique apporte `session_started_at`, déjà présent dans master ET observé dans le heartbeat de production — branche obsolète, suppression proposée sans être faite. Documente les portes à franchir avant toute restauration mémoire et les tables à ne jamais restaurer automatiquement.
Version 2.303.1
  • fix(repetition): la relance dit maintenant au modèle que c'est son DÉBUT de phrase qui se répète. Constaté en production : « J'ai la tête ailleurs » a ouvert deux réponses consécutives. Le détecteur avait pourtant fait son travail (`[REPETITION_CHECK] reason=channel_repetition rule=opening`) et déclenché une régénération — mais la consigne assouplie la veille demandait seulement de « reformuler naturellement », si bien que le modèle a reformulé le milieu de la phrase en gardant le même début ; la seconde réponse, publiée par le repêchage de 2.301.2, s'ouvrait donc à l'identique. En retirant « change l'angle, l'ouverture et le rythme » pour supprimer son ton accusateur, la seule instruction qui visait ce défaut avait disparu. Le sous-motif remonte désormais jusqu'à la relance : `_channel_repetition_rule` expose quelle règle a mordu côté salon (le booléen ne le permettait pas) et un rejet d'ouverture ou de chute ajoute `repetition_opening` aux motifs. La consigne correspondante demande de commencer autrement en précisant que LE FOND PEUT RESTER LE MÊME — un défaut de forme ne doit pas faire changer le contenu. Les autres rejets ne la reçoivent pas.
Version 2.303.0
  • feat(carnet): le carnet personnel écrit enfin. L'atelier existait depuis le 2026-08-02 — rubriques créées, permissions correctes, service sous le bon utilisateur — et contenait ZÉRO fichier après quatre jours ; `/espace` affichait « Rien écrit aujourd'hui » à juste titre. La capacité fonctionnait (tests verts), rien ne la déclenchait. QUATRE verrous indépendants, chacun suffisant : (1) aucun ordonnanceur n'appelait jamais `Atelier.write`, le seul chemin passant par un bloc TOOL que le modèle devait émettre spontanément ; (2) les trois cycles privés (learning, reflection, deep work) ne contiennent aucune référence à l'atelier — `sleep_private_work_enabled` n'est pourtant PAS un réglage mort, la tâche tourne bien toutes les 20 min entre 23 h et 8 h ; (3) la consigne « atelier_write n'a besoin d'aucun prétexte ni d'aucune permission » vit APRÈS `if not initiative: return instructions`, donc invisible sans `enable_autonomy` (désactivé en production) ; (4) en conversation ordinaire `tool_relevant` est faux et seul `_TOOL_STUB` d'une ligne est injecté — le modèle ne voyait même pas le schéma, et `_message_suggests_tool` ne connaît ni « carnet » ni « atelier ». Le bloc quotidien « ecrire » était purement déclaratif : uniquement des multiplicateurs de catégorie, aucun appel. Une boucle DÉDIÉE jour et nuit porte désormais la décision (le travail privé existant est borné à la fenêtre de sommeil, alors que les motifs d'écrire sont surtout diurnes) : elle se réveille souvent et écrit rarement. Les bornes sont évaluées AVANT tout appel LLM — une fonction désactivée, en cooldown ou au plafond ne coûte pas un token. `private_carnet.py` reste pur et testable : il décide, il n'écrit pas ; l'écriture repart dans l'action canonique `atelier_write`, sans second moteur. Tout ce qui n'est pas explicitement « write » est un skip (un JSON malformé n'écrit jamais par défaut) et le SERVEUR impose la date du fichier — la laisser au modèle ouvrirait la porte aux entrées antidatées. Bornes par défaut : 180 min entre deux entrées, 3 par jour, 800 caractères, signal significatif requis. Traces `[ATELIER_PRIVATE_WORK]` et `[ATELIER_WRITE]` avec motifs de refus fermés ; le texte du carnet n'est jamais journalisé. Aucune écriture ne produit de message Discord, DM ou notification. 30 tests neufs.
  • fix(style): fin de deux réflexes de surface constatés en production — « Non tu peux y arriver » produisait « Non, c'est pas grave, j'y arrive » (elle reprenait le « Non » d'un encouragement pour le nier), et « c'est pas grave » clôturait deux réponses d'affilée alors que rien n'était grave et que personne ne s'était inquiété. La règle va dans la hiérarchie COMMUNE de `response_directive`, le tic apparaissant aussi bien sur le profil social que sur le profil général. Une nuance reste permise quand elle apporte une information.
Version 2.302.0
  • fix(grounding): fin des extrapolations corporelles à partir de l'état VoxeLibre. Incident : « je suis coincée dans ce bloc de pierre […] j'arrive à peine à respirer », alors que le heartbeat indiquait au même instant PV 20/20, faim 20/20, `danger=aucun` et l'objectif « explorer ». Le heartbeat lu par la conversation n'expose AUCUNE mécanique respiratoire — `player` vaut {alive, hp, hunger, pos}. Le mod Lua publie bien un `breath` (`legacy_player_bridge.lua`) et `voxelibre/world_state.py` le lit, mais dans le processus de l'agent de jeu, PAS dans le heartbeat de conversation : le seul indice d'air qui franchisse la frontière est l'objectif `reprendre_air`. La difficulté à respirer était donc intégralement produite par le modèle. La cause n'était pas une donnée fausse mais une donnée NUE : la ligne d'activité affirmait « tu es coincée » et invitait à en parler à la première personne sans jamais borner ce qui pouvait en être déduit — et comme « comment tu te sens ? » déclenche `short_context_only`, cette ligne devenait l'essentiel du contexte pour exactement cette question. `current_activity.derive_voxelibre_experience()` traduit désormais l'état en faits, émotions autorisées, sensations physiques couvertes et déductions interdites, de façon déterministe et sans appel au modèle. Chaque sensation est rattachée à son signal : respiration ↔ `reprendre_air`, douleur ↔ PV critiques, faim ↔ jauge. Ce qu'aucun champ ne porte (panique, brûlure, poison, claustrophobie, danger vital) reste interdit en toutes circonstances. Amélia garde le droit de dire qu'elle est bloquée, sous terre, frustrée ou tendue face à un danger réel : seule l'invention corporelle disparaît. 22 tests vérifient la RELATION « affirmation physique → signal correspondant » plutôt qu'une liste noire — « j'arrive à peine à respirer » redevient autorisé quand l'objectif est `reprendre_air`.
  • feat(trace): `[STATE_GROUNDING]` publie les signaux retenus, les sensations couvertes et les émotions permises ; `[STATE_INFERENCE_GUARD]` journalise chaque déduction refusée avec son motif. La décision est calculée par `derive_voxelibre_experience`, jamais par le LLM, ce qui la rend reproductible et vérifiable après coup. Champs bornés : la trace nomme des signaux (`underground`, `stuck`), jamais des coordonnées ni du texte de prompt — un test le vérifie. Désactivable par `enable_state_grounding_trace`.
  • fix(style): la réponse fautive cumulait une métaphore corporelle bancale (« j'ai la tête un peu embêtée »), une dramatisation et sa minimisation immédiate (« c'est pas grave »). `response_directive` demande désormais, pour le profil `simple_social`, une structure simple — émotion, cause factuelle, nuance éventuelle — et proscrit la nuance qui contredit ce qui vient d'être affirmé. Aucune phrase imposée : la formulation reste la sienne.
Version 2.301.4
  • feat(trace): `[PROMPT_BREAKDOWN]` ventile le prompt bloc par bloc. Le `prompt_chars` global ne disait pas QUI mange la fenêtre de 8192 tokens : pendant l'incident du 2026-08-05, les prompts pesaient 21 000 à 22 000 caractères pour `memories_injected=0`, sans moyen de décider depuis les logs s'il fallait couper la mémoire longue, les directives ou les instructions outil. Première mesure sur la base de production : **les 15 directives noyau pèsent 5909 caractères, soit ~1477 tokens et 18 % de la fenêtre**, injectés à chaque prompt avant toute mémoire ou contexte ; le bloc caractère ajoute 1161 caractères — ~21 % de la fenêtre en coût FIXE. Tailles seulement : trois tests vérifient qu'aucun contenu de mémoire ni de message n'atteint le journal, que les blocs vides sont omis plutôt que de gonfler la ligne, et que le total est cohérent avec les blocs annoncés. Désactivable par `enable_prompt_breakdown`. L'estimation en tokens est annoncée comme approximative : le prompt système assemblé par l'agent n'est pas visible à ce point et le découpage dépend du tokenizer.
Version 2.301.3
  • fix(group): plus jamais de syntaxe de question servie comme « intérêt collectif ». Mesuré sur la base de production : `group_interests` contient « sais-tu » (poids 2.0), « qu'est-ce », « actuellement », « bon », « amélia » ; le salon public lit ces lignes avec `min_weight=1.5` et injectait le résultat tel quel dans le prompt — littéralement « Intérêts collectifs de cette communauté : sais-tu. ». `by_user` était pire : AUCUN filtre de poids, donc ses cinq entrées étaient intégralement du bruit. Corriger l'extracteur en 2.301.1 empêche d'écrire de nouveaux fragments mais ne nettoie pas l'existant ; plutôt que de supprimer des données en base — irréversible — le filtre est posé à la LECTURE : un intérêt n'est servi que s'il reste un mot porteur de sujet. Couvre aussi un futur trou de l'extracteur, et `by_user` retient ses cinq entrées APRÈS filtrage au lieu de gaspiller ses places. Aucune ligne supprimée : le bruit historique reste en base, il n'atteint simplement plus le prompt.
Version 2.301.2
  • fix(repetition): une ouverture partagée n'est plus une redite. Les signatures d'ouverture/chute rejetaient une réponse dès que ses 3 premiers ou 3 derniers mots coïncidaient avec un message récent, quelle que soit la similarité réelle — mesuré en production : une réponse à 0.468 refusée pour un seuil de 0.78. En français et sur des réponses brèves (ce que la persona encourage), ces collisions d'ouverture (« je sais pas… ») sont constantes ; chaque rejet coûtait un appel LLM de plus puis une réponse qui dérive. 2.301.0 n'avait traité que la CONSÉQUENCE (repêchage d'une réponse saine), pas la cause. Une signature partagée ne compte désormais pour une redite que si le message est par ailleurs globalement proche (marge de 0.30 sous le seuil), et un test vérifie que la règle n'est pas devenue inatteignable — elle mord encore sur un vrai tic de style. `repetition_reason` expose la règle qui a mordu et `[REPETITION_CHECK]` la journalise : « réponse jugée répétitive » ne disait pas si le contenu se répétait vraiment ou si trois mots suffisaient. Le détecteur par salon reçoit la même marge, pour que le comportement ne dépende pas du salon.
  • build(deploy): `scripts/build_deployed_manifest.py` — le manifeste est bâti à partir des empreintes MESURÉES SUR LA CIBLE, plus depuis le worktree de développement. `core.autocrlf=true` faisait arriver les fichiers en CRLF sur `/opt/aurore` : un manifeste généré localement marquait ~229 fichiers en fausse dérive, rendant inutilisable le seul outil capable de prouver quel code tourne. Conséquence observée : la production a annoncé « v2.301.1 — commit b9256f7 », la bonne version avec le mauvais commit, car `runtime_manifest.resolve_git_commit` retombe sur le manifeste quand il n'y a pas de dépôt Git. Le script refuse d'écrire si un fichier diffère du commit autrement que par les fins de ligne, ou si le déploiement est partiel — c'est ce garde-fou qui a révélé que `tests/test_topic_tracker.py` n'avait pas suivi son module.
Version 2.301.1
  • fix(topics): la table `group_interests` stockait de la SYNTAXE DE QUESTION comme centres d'intérêt du groupe — « qu'est-ce », « sais-tu », « fais-tu », « es-tu », « actuellement », « bon », « semble », relevés en base de production. La regex de `extract_topic_words` capture « sais-tu » en UN token : aucun mot-outil ne pouvait le filtrer, et allonger la liste n'aurait attrapé que les inversions déjà vues. Le radical de l'inversion interrogative est désormais extrait, et le mot n'est rejeté que si CE radical est lui-même un mot-outil — « allez-vous » disparaît, « rendez-vous » et « week-end » restent des sujets valides, alors qu'un découpage sur le trait d'union les aurait détruits. Les élisions initiales sont retirées au passage (« l'ordinateur » compte pour « ordinateur ») et le nom de l'agent n'est plus un centre d'intérêt : lui parler n'est pas un sujet. 4 tests de non-régression.
Version 2.301.0
  • fix(conversation): fin du ton défensif — `emotions.detect_disrespect` comparait ses marqueurs d'insulte en SOUS-CHAÎNE nue. « nique » était donc trouvé dans « technique » et « unique », « conne » dans « connexion » et « reconnecte », « pute » dans « dispute » et « réputée », « ferme la » dans « ferme la porte ». Sur la base de production, « je suis pas un disque dur, je me reconnecte » était lu comme une INSULTE FRONTALE. Le module documentait pourtant ce bug 70 lignes plus haut (`_emotion_keyword_matches`, corrigé pour `detect_emotion` seulement) : le correctif n'avait jamais été appliqué à la détection d'irrespect. Le matching exige désormais des frontières de mot, y compris pour les marqueurs multi-mots. L'impact dépassait de loin le ton : UN faux positif forçait `emotion=colere`, injectait la consigne « tu TE DÉFENDS […] tu n'accordes aucun crédit à ce que dit quelqu'un qui te parle ainsi », BLOQUAIT tout apprentissage faits/profil (`_apply_learning_from_text`), marquait la relation `hostile=True` en base et pesait double dans l'apprentissage d'initiative — trois effets écrits en base, donc survivant à un reset mémoire. C'est pourquoi le reset complet du 2026-08-05 n'avait rien changé : la cause était dans le code, pas dans les souvenirs. Retire aussi des marqueurs qui n'en sont pas : les remarques d'identité (« t'es qu'une IA », « qu'un bot ») provoquaient une défense identitaire hors sujet, et « tu racontes n'importe quoi » est une CRITIQUE — elle appelle une vérification. Nouveau `detect_criticism` (critique technique / de ton / désaccord / frustration, avec la CIBLE visée) et `build_criticism_guidance` qui oriente vers la vérification ou la demande de précision en interdisant explicitement le procès d'intention. La consigne de défense ne dit plus de refuser tout crédit à l'interlocuteur : un message contenant à la fois un reproche et une vraie question recevait sinon une réponse qui ignorait la question.
  • fix(persona): les traits semés en v1.86.0 (`directness` 0.70, `assertivite` 0.65) autorisaient la sécheresse sans qu'aucune règle n'interdise d'INVENTER une intention hostile. Le seed étant rejoué à chaque démarrage, cette persona a été intégralement recréée après le reset — 14 directives et 13 éléments de caractère. Rééquilibrage : `directness` 0.45 et `assertivite` 0.30 sur des formulations qui gardent la franchise mais excluent la brusquerie et l'accusation, nouveau trait `bienveillance_interpretative` (0.85) et directive prioritaire `interpretation_bienveillante` — une remarque sur son ton, son fonctionnement ou sa mémoire est un retour utilisateur ou un signal de bug, jamais un conflit personnel. Comme le seed n'écrase JAMAIS une ligne existante, corriger ces valeurs n'aurait eu aucun effet sur une base déjà semée : un réalignement ne touche que les traits restés à leur valeur de seed d'origine et jamais ajustés à la main (`last_reason` porte encore la marque du seed). Un réglage admin ou d'auto-introspection reste souverain. Amélia conserve son droit au désaccord, à la limite et au refus, et sa capacité à se défendre d'une vraie insulte.
  • fix(repetition): le détecteur rejetait une réponse dès que ses 3 premiers ou 3 derniers mots reprenaient ceux d'un message récent, quelle que soit la similarité réelle — une réponse à 0.468 de similarité était refusée alors que le seuil est 0.78. Pour une persona qui s'autorise à être brève, ces collisions d'ouverture sont constantes ; deux rejets épuisaient le budget et produisaient « réponse indisponible » alors que les deux textes étaient publiables. Une redite est un défaut de forme, pas de sûreté : un candidat rejeté pour cette seule raison est marqué `salvageable` et publié si les tentatives s'épuisent. Un rejet de validation, de politique produit ou nécessitant une sanitization ne l'est jamais — le garde-fou qui interdit de repêcher un candidat non conforme reste entier. La consigne de relance disait « ta réponse précédente était INACCEPTABLE […] change d'angle » avec +0.12 de température, ce qui faisait dériver le modèle un peu plus à chaque tentative ; pour une redite elle demande maintenant de reformuler en conservant les faits, sans durcir le ton ni allonger, avec +0.05.
  • refactor(trace): un module de contexte CONSULTÉ n'est pas un module qui a injecté quelque chose. Les traces d'incident montraient six modules pour `memories_injected=0`, ce qui laissait croire que la mémoire longue alimentait le prompt alors qu'elle était vide — diagnostic faussé dès la première lecture des logs. `[CONVERSATION_TRACE]` distingue désormais `context_modules_consulted` de `context_modules_non_empty` et publie un compte d'items par module ; nouvelle trace `[EMOTION_CLASSIFICATION]` (label, cible, `persistent_effect`) et `[REPETITION_CHECK]`. Métadonnées uniquement : aucun contenu de mémoire, aucun extrait de prompt, aucun secret.
  • docs: `docs/AUDIT_CONVERSATION_MEMOIRE_2026-08-05.md` (chemin d'exécution réel, modules canoniques, causes racines prouvées, et ce qui n'était PAS en cause) et `docs/OPS_MEMOIRE_RESET_ROLLBACK.md` (un reset ne nettoie ni les directives noyau ni le caractère, recréés à chaque démarrage ; déploiement, rollback, restauration progressive). Audit annexe : `/opt/aurore` exécutait bit-à-bit `origin/master`, aucun pipeline conversationnel concurrent — chaque responsabilité auditée n'a qu'une implémentation, `3ec4cb7` a déplacé le code sans le dupliquer, donc AUCUN refactoring architectural n'a été créé. 36 tests de non-régression neufs, 2810 verts.
Version 2.300.0
  • feat(runtime): la migration shadow Discord observe maintenant aussi la réponse générée et les validations majeures qui imposent une régénération. Les enregistrements restent des métadonnées bornées : ni contenu de message, ni identité Discord, ni nouvelle autorité de décision. Le chemin historique demeure seul responsable des réponses et actions.
Version 2.299.0
  • feat(migration): annonce unique et persistée de la grande migration d'Amélia. Elle décrit une présence plus cohérente entre conversation, espace web, compagnon et jeu, avec une mémoire mieux séparée et une bascule progressive vers un noyau commun.
Version 2.298.0
  • feat(espace): « son espace » — une PWA où Amélia existe hors des salons (`space_api.py`, `webpush.py`, nouveaux modules). Le chat web montre une CONVERSATION ; `/espace` montre une VIE : son carnet du jour (atelier, 2.292.0), la journée qu'elle s'est donnée (2.293.0), ce qu'elle est en train de faire, ce qu'elle a fabriqué. Rien n'y demande qu'on lui parle — c'est la différence entre une messagerie et un lieu. Installable sur l'écran d'accueil : le manifeste existant reçoit un RACCOURCI vers `/espace` plutôt qu'un nouveau `start_url`, pour ne pas déplacer le point d'entrée des PWA déjà installées. Choix structurant du push : des poussées **SANS charge utile**. Chiffrer un contenu Web Push (aes128gcm, ECDH avec les clés du navigateur) aurait imposé `pywebpush`/`http-ece` et leur chaîne ; une poussée vide ne demande qu'une signature VAPID (ECDSA P-256), soit `cryptography` seul — désormais la seule dépendance ajoutée (roue manylinux vérifiée disponible sur `.174`, `cffi` déjà satisfait). Bénéfice inattendu mais réel : réveillé par la poussée vide, le service worker va CHERCHER le texte sur `/api/espace/pending` avec le cookie de session, si bien que le contenu ne transite jamais par les serveurs de Google ou Mozilla. La signature est le point où une erreur ne se voit pas (un jeton mal formé donne un 401 muet) : `_der_to_raw_signature` convertit le DER de `cryptography` vers le r||s de 64 octets qu'impose JWS, et un test la fait VÉRIFIER par un vérificateur indépendant, en plus de contrôler le point non compressé de 65 octets préfixé 0x04 qu'exige `pushManager.subscribe()`. Le service worker ne met JAMAIS les données en cache — seulement la coquille : afficher une humeur d'il y a trois jours comme si c'était maintenant serait pire qu'afficher « hors ligne » (la coquille est servie par `?shell=1`, et un test vérifie qu'elle ne contient aucune donnée). Sa syntaxe JavaScript est validée par `node --check` pendant le développement, et un test garde qu'aucune accolade de gabarit f-string n'y survit — un `{{` rescapé donnerait un worker que le navigateur refuse en silence. Budget, comme demandé par Yann : `push_notification` entre dans `ACTION_REGISTRY` (plafond 3/jour, cooldown 4 h, `speaks=False` car ce n'est pas une prise de parole en salon et ça ne doit pas consommer ce budget-là), classé **ORANGE** dans `action_risk.py` — une notification sort de Discord et va vibrer dans une poche, c'est plus intrusif qu'un message de salon ; les heures de sommeil sont vérifiées à part, et l'arbitrage final revient au même `AutonomyGovernor` que ses prises de parole pour qu'un canal neuf n'ouvre pas une voie de contournement. Les abonnements morts (404/410) sont retirés automatiquement, une panne temporaire (500) ne désabonne personne, et le texte en attente est effacé si aucun appareil n'a pu être joint. Clés VAPID générées au premier besoin et persistées en `agent_settings` (donc dans la sauvegarde existante). Dégradation propre sans `cryptography` (même esprit que `multimodal.py`) : l'espace fonctionne et la page dit pourquoi les notifications sont indisponibles. `web_ui.py` n'est touché que de quatre lignes (import, enregistrement, `/sw.js` public, lien de nav) — le service worker doit rester joignable sans session, le navigateur le revalidant hors de toute page ouverte, et une redirection vers `/login` le désinscrirait. 74 tests neufs dont 7 d'intégration sur de vraies routes aiohttp. 2247 verts, ruff/bandit propres.
Version 2.297.0
  • feat(compagnon): compagnon de bureau Windows distribuable (`companion/`, `companion_api.py`, `companion_version.py`). Une petite fenêtre sans bordure, toujours au-dessus, dans un coin de l'écran : disque coloré par l'humeur, ce qu'elle fait, et une bulle pour ce qu'elle a à dire ; en échange le client remonte la fenêtre active du PC. C'est le rôle que `observer.py` tenait avant la migration sur `.174` et qui était mort depuis (Win32 sur un serveur Linux) — la présence Discord (2.291.0) ne l'approche que de loin : elle ne distingue ni « il code », ni « il lit », ni « il n'est plus devant sa machine ». `observer.py` expose désormais `build_window_event()` (PUR, extrait de `_build_event`) partagé par les deux sources fenêtre, plus `note_window()` qui déduplique comme la capture locale ; une inactivité ≥ 5 min produit un événement explicite de priorité 1 plutôt qu'un écran figé commenté comme du travail en cours. Périmètre serveur volontairement minuscule : TROIS routes dans un module à part (`web_ui.py` fait déjà 8000 lignes et n'est touché que d'une ligne), protégées par un jeton DÉDIÉ `companion_token` — jamais `web_ui_token`, puisque l'exécutable part chez des tiers : il n'ouvre ni la mémoire, ni les salons, ni le dashboard, et un jeton vide ferme l'API (aucune ouverture par défaut). L'état renvoyé est délibérément pauvre (humeur, énergie, activité, créneau du jour, une bulle) : ce que l'overlay affiche peut être lu par quiconque passe derrière l'écran. Côté vie privée, le réglage `share_window_titles=false` fait que le titre de fenêtre NE QUITTE PAS la machine (il n'est pas seulement masqué à l'affichage). Client en bibliothèque standard seule (urllib + tkinter) : trois appels HTTP ne justifient pas d'embarquer aiohttp dans un exe distribué. Chaîne de build `python -m companion.build` (PyInstaller `--onefile --windowed`, exclusions explicites d'aiohttp/discord/numpy/PIL sans lesquelles l'exe passait de ~11 Mo à plus de 100), avec `companion_version.py` comme SOURCE UNIQUE de vérité : elle nomme le fichier produit, remplit les propriétés Windows de l'exe (vérifié : FileVersion 1.0.0, ProductName, CompanyName) et voyage dans chaque requête, le serveur répondant `client_outdated`/`latest_version` que l'overlay affiche. Pas de mise à jour automatique : télécharger et exécuter du code seul sur le PC d'autrui demanderait une signature et une chaîne de confiance que ce projet n'a pas. Exe réellement construit et lancé pendant le développement (11,3 Mo, `--version` OK) ; deux bugs attrapés ainsi — un emoji dans le message final faisait planter le script APRÈS une construction réussie sur une console cp1252 (exe valide rapporté en échec), et la bulle d'overlay poussée par un rituel n'était pas protégée, si bien qu'un échec cosmétique après envoi aurait empêché d'enregistrer l'occurrence et fait repartir un bonsoir déjà envoyé. `dist/`, `build/` et `*.spec` ignorés par Git : un exe se distribue, il ne se versionne pas. 83 tests neufs.
Version 2.296.4
  • fix(voxelibre): un rapport unique est associe a chaque session reelle. Apres un arret, un heartbeat frais avec le meme session_started_at est ignore; le superviseur et les boucles d'autonomie sont serialises afin d'empecher deux arrets ou rapports concurrents.
Version 2.296.3
  • fix(observabilite): l'installateur attend desormais que le endpoint /metrics authentifie d'Amelia soit effectivement pret apres le redemarrage du service, au lieu de conclure a tort a un echec pendant les premieres secondes de son demarrage.
Version 2.296.2
  • fix(observabilite): Prometheus 2.45 accepte a nouveau la configuration de production : external_labels est range sous global, son emplacement de schema officiel, au lieu d'une cle racine invalide. La validation promtool du deploiement bloque desormais avant tout redemarrage si ce template regressait.
Version 2.296.1
  • fix(observabilite): reprise de déploiement sûre quand apt/dpkg est déjà bloqué : l'installateur peut réutiliser Prometheus et Alertmanager présents sans relancer apt, puis installe Grafana OSS 13.1.1 depuis une archive HTTPS à SHA-256 épinglé dans une unité systemd dédiée amelia-grafana, toujours sur loopback. Le flux APT normal neutralise aussi prometheus-node-exporter, installé transitivement mais inutile ici, afin qu'aucun port supplémentaire ne soit activé au démarrage.
Version 2.296.0
  • feat(observabilite): Prometheus devient une pile de supervision exploitable et fermee : exporteur opt-in protege par un bearer dedie, labels des compteurs runtime et domaines Luanti maintenant listes blanches (ni fuite ni cardinalite libre), Grafana local lecture seule via un token de service et une allowlist de dashboards, et Alertmanager poste vers /hooks/alertmanager avec un bearer distinct. Le webhook ne conserve ni annotations, ni URLs, ni instances, ni texte libre : seulement les alertes connues, leur severite, etat et dates, dedupliquees par empreinte. Une alerte nouvelle ou changee demande a Amélia un snapshot serveur borne puis une notification admin rate-limitee, sans appel LLM ni action automatique. Le script install_monitoring_stack.sh installe sur loopback Prometheus, Alertmanager et Grafana, provisionne le dashboard Amelia Overview, borne retention/disque et garde tous les secrets hors Git. Tests cibles, ruff, mypy connectors et audit settings valides.
Version 2.295.0
  • feat(luanti): jouer ENSEMBLE, pas seulement à côté (`voxelibre/togetherness.py`, nouveau module pur). L'agent savait déjà répondre à un joueur (`chat.py`), lui obéir (`companion.py`, directives follow/wait/protect) et le suivre — mais il ne savait pas remarquer son ARRIVÉE : le monde contenait un joueur de plus et personne ne le notait, si bien qu'on pouvait la rejoindre et jouer une heure à côté d'elle sans qu'elle le mentionne jamais, ni en jeu ni dans Discord. `TogetherTracker` suit qui est près d'elle, depuis quand, et le temps cumulé du jour. Deux précautions dictées par la réalité du jeu : `Environment.entities` ne liste que ce qui est PROCHE (un joueur qui s'éloigne disparaît sans s'être déconnecté — on parle donc d'« arrivé près d'elle », jamais de connexion), et cette liste CLIGNOTE (chargement de chunks, latence du relevé), ce qui, sans délai de grâce, aurait produit une rafale d'arrivées/départs sur un joueur immobile — le même faux positif que la déduplication de présence corrigée en 2.291.0. La durée d'une visite s'arrête à la PREMIÈRE absence et non à la fin du délai de grâce : le temps de grâce est du doute, pas du temps passé ensemble. Cas rattrapé au passage : si aucun relevé ne tombe pendant la fenêtre de grâce (agent en pause, tick espacé), le départ est acté au retour du joueur — sans quoi une heure d'absence passait pour une présence continue. Côté jeu, une arrivée déclenche un salut, une seule fois par visite (réarmé par un départ réel, sinon elle n'aurait dit bonjour qu'une fois par redémarrage), jamais au premier relevé de session où tout le monde « arrive » d'un coup, et jamais en danger. Le heartbeat expose un bloc `together` (présents, secondes du jour) lu par le bot. Côté Discord, initiative `voxelibre_invite` (VERT, 1×/jour max) : `should_invite_to_play` (pur, dans `voxelibre_session.py`) refuse l'invitation s'il est déjà dans le monde AVEC elle — proposer une partie à quelqu'un assis à côté serait exactement l'incohérence d'activité déjà payée en 2.256.0 —, s'il n'est pas en ligne (vraie présence Discord quand l'intent de C1 est actif, repli sur l'activité récente sinon, jamais une supposition optimiste), hors de la fenêtre du soir (17h-22h), s'ils ont déjà joué ensemble aujourd'hui (seuil 5 min : se croiser trente secondes n'est pas une partie), ou si elle a déjà proposé dans les 24 h. Chaque refus porte sa raison, journalisée. 31 tests neufs, 2074 verts, ruff propre.
Version 2.294.0
  • feat(rituels): trois rendez-vous qu'Amélia TIENT, au lieu de les tenter (`rituals.py`, nouveau module pur) — `bonsoir` (chaque soir), `point_dimanche` (le dimanche), `anniversaire_rencontre` (chaque année). Le pool scoré est un marché : chaque initiative est notée, la meilleure passe, et le gouverneur plafonne le débit total de parole. Bon modèle pour de l'opportunisme, mauvais modèle pour un rendez-vous — un bonsoir qui perd l'arbitrage contre une anecdote VoxeLibre, ou un anniversaire qui saute parce que `global_daily_cap` était atteint, n'est pas un rituel mais une intention qui n'a pas eu de chance. `_maybe_ritual` est donc appelé AVANT la construction du pool dans `_run_scored_proactive_pass` et court-circuite le tick ; il ne passe pas par `AutonomyGovernor`. « Garanti » n'est pas « incontrôlé » pour autant : unicité PAR OCCURRENCE (clé = jour / semaine ISO / année, donc un redémarrage ne rejoue jamais le bonsoir du soir), fenêtre horaire au-delà de laquelle l'occasion est manquée et non reportée (un bonsoir à 4 h du matin n'est pas un bonsoir), et volume plafonné à un message par jour, un par semaine, un par an. La fenêtre du bonsoir est calée sur `sleep_start_hour` (les 2 h qui précèdent) avec un cas particulier pour un coucher après minuit, qui donnerait sinon une fenêtre à cheval sur deux jours donc sur deux clés d'occurrence. Priorité par rareté : l'annuel passe avant l'hebdomadaire, qui passe avant le quotidien — un dimanche soir d'anniversaire déclencherait sinon trois messages qui se noieraient. Le module ne renvoie jamais un texte figé, seulement une CONSIGNE de formulation (elle écrit, avec les garde-fous habituels dont l'anti-répétition), et la consigne du point hebdo lui interdit explicitement de résumer une semaine inventée. L'anniversaire s'ancre sur `relationships.first_interaction` de l'admin — inconnu, le rituel n'existe simplement pas plutôt que d'être calé sur une date approchée — et ne se déclenche jamais l'année de la rencontre elle-même. L'occurrence n'est consommée qu'APRÈS un envoi réussi : un échec Discord ne fait pas sauter le bonsoir de la soirée. `enable_rituals` actif par défaut, contrairement à l'usage pour une capacité neuve : un rituel désactivé par défaut ne serait pas un rituel. 29 tests neufs, 2036 verts, ruff/bandit propres.
Version 2.293.0
  • feat(autonomie): Amélia se donne une JOURNÉE (`daily_plan.py`, nouveau module pur). Jusqu'ici son autonomie était purement réactive : un pool d'initiatives noté par l'affect et les désirs (`action_choice.rank_actions`), cadencé par des cooldowns (`AutonomyGovernor`). Rien ne représentait une intention tenue dans la durée — elle était *autorisée à agir*, elle n'avait pas de journée. Désormais, au premier accès de la date courante, elle répartit ses heures éveillées (bornes `sleep_end_hour`/`sleep_start_hour`) en blocs fermés (etudier / ecrire / jouer / dispo / repos) : une trame par défaut est personnalisée par son désir le plus saillant au-dessus de sa base (`drives.BASELINE`), qui gagne le plus long bloc — curiosité→étudier, expression→écrire, lien→dispo, repos→repos. Déterministe à désirs donnés (aucun tirage) : elle peut dire POURQUOI sa journée a cette forme, ce qui serait impossible avec un aléa, et le sujet d'un bloc « étudier » vient d'un intérêt réellement suivi (`list_all_interests`, repli `interest_topics`) — jamais fabriqué, un bloc sans sujet restant valable. Le bloc en cours biaise le pool via un champ DÉDIÉ `ProactiveCandidate.plan_boost` (et non en détournant `observer_boost`, dont le nom mentirait), borné à [0.5, 1.5] comme les autres facteurs : une intention penche, elle n'annule ni cooldown, ni prudence, ni objectif vital. Hors bloc (nuit, journée non construite) le multiplicateur vaut exactement 1.0 — comportement strictement inchangé. Elle peut changer d'avis (`change_block`, commande `!journee <genre> [raison]`) : le changement est CONSERVÉ avec sa raison plutôt qu'effacé, parce qu'une journée qu'on réécrit sans trace n'est pas une journée qu'on tient. Injectée au prompt comme une INTENTION (« Sur ce créneau, je m'étais dit : … ») et jamais comme un fait — la vérité d'exécution reste au marqueur ⟦Activité en cours⟧ qui fait autorité, distinction déjà payée cher (2.256.0) ; placée haut dans `_build_continuity_guidance` car la liste y est tronquée à 4-5 éléments et la ligne aurait disparu exactement quand le contexte est riche, c'est-à-dire quand on lui demande « tu fais quoi ? ». Persistée en `agent_setting` (`daily_plan`), une seule journée vivant à la fois. 40 tests neufs, 2007 verts, ruff/bandit propres.
Version 2.292.0
  • feat(atelier): Amélia a désormais un espace de PRODUCTION à elle (`atelier.py`, nouveau module + tools `atelier_write`/`atelier_read`/`atelier_list`). Constat : tout ce qu'elle fabriquait mourait dans un salon Discord — une pensée écrite est un message qui défile, un projet au long cours n'avait aucun support, et « avoir une vie à soi » se réduisait à choisir QUAND parler. L'atelier lui donne une surface persistante sous `data_dir/atelier` (donc embarquée par la sauvegarde existante) : `carnet/` (journal daté, une entrée par jour, ajout à la suite), `notes/`, `brouillons/`, `idees/`. Modèle de sûreté calqué sur `self_patch.is_eligible_relative_path` — liste blanche plutôt que liste noire : racine unique dont on ne sort jamais (chemin absolu, `..`, séparateur Windows détourné et lien symbolique refusés, avec une seconde vérification APRÈS résolution du chemin réel), rubriques fermées (pas de création de sous-arborescence au vol), extensions `.md`/`.txt` seulement (ni code exécutable — `self_patch.py` reste la voie auditée pour ça — ni binaire), et quotas stricts (64 Ko/fichier, 8 Mo au total, 500 fichiers) parce que le disque de `.174` tourne autour de 86 % d'occupation. Un contenu trop long est TRONQUÉ sur une frontière de caractère UTF-8 plutôt que refusé (perdre la fin d'une note vaut mieux que perdre la note). Toute la décision est pure et testable ; la classe `Atelier` ne porte que les I/O. Classé VERT dans `action_risk.py` par nom explicite — le repli par mots-clés l'aurait fait tomber en ORANGE sur « write », et écrire dans son propre carnet n'a pas à demander une validation à chaque phrase. `atelier_write` est `internal` (aucun bruit dans le salon), les relectures `silent`. Les schémas ne sont injectés au prompt que si les tools sont réellement autorisés, et la consigne d'initiative dit explicitement que son atelier ne demande ni prétexte ni permission. Commande `!atelier [chemin]` pour que Yann puisse lire — en lecture seule. Bug attrapé par les tests au passage : `now_in_tz()` était appelé sans son argument obligatoire `tz_name` dans la résolution du carnet du jour. 64 tests neufs, 1966 verts, ruff/bandit propres.
Version 2.291.0
  • feat(observation): la présence Discord devient une SECONDE source d'observation (`observer.py`, source `discord_presence`). Constat de départ : `ActivityObserver` ne savait lire que la fenêtre au premier plan via l'API Win32 (`sys.platform == "win32"`), donc en prod sur `.174` (Linux) `observer.supported` était faux, la boucle ne démarrait jamais, `observer.last_event` restait `None` — et `saillance_for_category()` renvoyait invariablement 1.0. Toute la contextualisation dynamique du pool scoré (`action_choice.ProactiveCandidate.observer_boost`, `discord_bot.py`) était donc du code mort en production depuis la migration sur le serveur : Amélia ne savait pas si Yann jouait, écoutait de la musique ou travaillait. Les deux sources sont désormais distinctes et explicites (`window_supported` pour le polling Win32, `presence_supported` pour la présence), `supported` devenant « observable d'une manière ou d'une autre » ; seule la première a une boucle, la seconde arrive par `on_presence_update`. Le mapping activités Discord → `ObservationEvent` (`event_from_presence`) est PUR (aucun objet discord.py, aucune I/O) : jeu/compétition → priorité 5 et `is_game`, stream → 4 et `is_game` seulement si un `game_keywords` le confirme, écoute Spotify → 3 avec « Titre — Artiste » reconstitué depuis `details`/`state`, visionnage → 2 ; le statut personnalisé est ignoré (texte écrit par la personne, pas une activité observée — le commenter reviendrait à réagir à un statut vieux de trois semaines comme à une nouveauté), et le jeu prime sur la musique quand les deux coexistent. Dédup PAR PERSONNE (deux membres suivis ne s'annulent plus l'un l'autre) avec `forget_presence()` au passage hors ligne, sans quoi relancer le même jeu après déconnexion n'aurait produit aucun événement. `ObservationEvent` porte deux champs neufs (`subject_user_id`, `subject_username`) et `_handle_observation` s'en sert pour ranger l'observation sur le profil de la PERSONNE observée au lieu de « system » : ranger le jeu de Yann sous « system » aurait rejoué exactement la confusion d'activité déjà corrigée ailleurs (⟦Activité en cours⟧), Amélia finissant par croire que c'est elle qui joue. Le prompt de commentaire est distinct selon la source (elle constate ce que fait quelqu'un, sans prétendre y jouer ni l'entendre elle-même). Désactivé par défaut (`enable_presence_observer=false`) car les intents `presences`+`members` sont PRIVILÉGIÉS : ils sont demandés seulement si le réglage est vrai (figé à la construction du client, donc non basculable par `agent_settings`), et doivent d'abord être cochés dans le portail développeur Discord sous peine de refus de connexion au démarrage. Périmètre par défaut restreint à `admin_user_id` (`presence_observer_user_ids` pour élargir explicitement) : suivre l'activité de tiers sans leur accord n'a pas à être un défaut. 16 tests neufs, 1902 verts, ruff propre.
Version 2.290.1
  • fix(prompt): incohérence temporelle observée en direct — Amélia a affirmé « tu m'avais dit que c'était fini hier » à propos d'un `learned_fact` mémorisé (« Yann a terminé ses vacances », vrai contenu mais extrait par une relecture conservatrice en arrière-plan la veille, PAS un échange réel de la veille), puis, corrigée, a inventé une preuve à l'appui (« je t'ai vu sur le fil public, tu te plaignais des zombies ») au lieu d'admettre l'erreur — investigation en base réelle (DM confirmé par les seuls participants Yann/Amélia, aucune fuite de `inner_thoughts` cross-canal trouvée pour ce salon : la fabrication est une confabulation pure, pas un contexte injecté identifiable). Root cause : les `Faits appris` injectés au prompt ne portent AUCUN horodatage exploitable (contrairement aux messages de conversation, qui portent un repère d'ancienneté explicite) — toute date qu'elle leur associe est donc nécessairement inventée, et le prompt ne l'interdisait pas assez précisément (seulement `passé commun inventé` en général). Règle ciblée ajoutée dans `SYSTEM_PROMPT` ET `SYSTEM_PROMPT_LEAN` (qui gouverne la prod) : jamais de date sur un fait mémorisé, jamais de preuve inventée pour se justifier après correction. 3 tests neufs (garde-fou anti-régression sur les deux prompts). 1886 tests verts, ruff propre.
Version 2.290.0
  • feat(autonomie): Chantier C — auto-correctifs de code réels (le plus sensible des trois chantiers, autorisé explicitement par Yann après livraison de A+B). Active `apply_self_patch` (ROUGE, `action_risk.py`), jusque-là purement théorique — `self_patch.py` (nouveau module). Pipeline : `self_diagnosis.py` repère un bug récurrent → le fichier fautif doit être dans une LISTE BLANCHE explicite (racine + `voxelibre/` seulement, jamais `memory.py`/`config.py`/secrets/l'infrastructure de sûreté elle-même — `self_patch.py`/`self_diagnosis.py`/`self_coding.py`/`action_risk.py`/`autonomous_actions.py` — ni les deux orchestrateurs centraux `discord_bot.py`/`amelia_voxelibre.py`) → le LLM propose un remplacement EXACT (même contrat que l'outil Edit : le fragment doit apparaître UNE seule fois dans le fichier réel, jamais une supposition sur l'emplacement) → le diff est calculé MÉCANIQUEMENT (`difflib`, jamais écrit par le LLM), plafonné à 40 lignes changées, syntaxe revalidée (`ast.parse`) → appliqué dans un worktree GIT JETABLE (`git worktree add --detach`, jamais le code en cours d'exécution) où la suite de tests COMPLÈTE tourne avec timeout → seulement si VERT, une `pending_validation` (mécanisme existant réutilisé, catégorie `self_patch`) est créée avec le diff complet, jamais appliquée automatiquement. Nouvelle commande admin `!patch <id>` (diff complet en pièce jointe + extrait du rapport de tests) ; `!validate`/`!reject` (génériques, inchangés) décident. Sur approbation : le hash du fichier d'origine est revérifié (une proposition périmée est refusée, jamais écrasée), écriture réelle, **jamais de redémarrage automatique** — l'admin relance `aurore` lui-même. Inerte tant que `self_coding_enabled` (kill-switch existant, off en prod) n'est pas explicitement activé — indépendant de cette implémentation. 35 tests neufs (dont une preuve que le fichier réel du dépôt n'est jamais touché par une simple évaluation, et qu'aucun worktree n'est jamais laissé derrière). 1883 tests verts au total, ruff/bandit propres (mêmes 3 avertissements Low subprocess déjà tolérés pour self_coding.py — aucun nouveau).
Version 2.289.0
  • feat(voxelibre): Chantier A autonomie — continuité des self-projects VoxeLibre. Avant, un self-project « construire une maison » (`_maybe_generate_self_project`) vivait seulement dans la mémoire Discord : le jeu (process séparé) ne savait pas qu'un tel projet existait, et sa progression était devinée par un LLM (`project_progress.py`) plutôt que mesurée. `discord_bot.py` classe désormais le texte du projet (mots-clés construction+jeu) et l'enregistre avec une cible structurée (`goals.voxelibre_goal`, migration additive) ; un fichier `voxelibre_control.json` (mêmes conventions atomiques que le heartbeat) publie cette cible vers `amelia_voxelibre.py`, qui la lit et biaise `GoalManager.suggest_goal()` — même mécanisme borné que le conseil LLM (jamais de priorité vitale masquée), seulement quand le LLM ne suggère déjà rien ce tick. `Planner.build_progress()` (nouveau) mesure la fraction de blocs réellement posés dans le plan ancré (même critère que `_next_buildable`, y compris l'exception « cellule qui chevauche le joueur ») et la republie dans le heartbeat (`build_progress`) ; `discord_bot.py` l'utilise en priorité sur l'estimation LLM quand un heartbeat frais confirme le jeu sur CET objectif, marque le self-project `completed` à ≥98% et déclenche un highlight de fin de session vérifié (jamais une supposition). 20 tests neufs.
  • feat(social): Chantier B autonomie — initiative `social_introduction` (ORANGE). Détecte, à partir d'événements `shared_interest` déjà PUBLICS uniquement (`relationship_memory.fetch_recent_shared_interest_events`, lecture transverse tous-utilisateurs — chaque salon vérifié via `isinstance(…, discord.DMChannel)`, jamais présumé public par défaut), deux personnes actives dans le même salon qui partagent un mot-clé de sujet (`social_introduction.py`, module pur). Rejoint le pool scoré existant (`autonomous_actions.ACTION_REGISTRY`), classée ORANGE dans `action_risk.py` (exécutée + admin notifié, jamais bloquée ni silencieuse, comme `web_curiosity`/`weekly_evolution`) ; cooldown PAR PAIRE persisté (agent_setting), distinct et plus strict que le cooldown global de l'action — une introduction déjà faite n'est jamais reproposée. Désactivée par défaut (`enable_social_introduction=False`) : capacité neuve, jamais validée en live. 14 tests neufs.
  • Point d'étape du plan « Trois chantiers d'autonomie avancée » : Chantier A et B livrés, testés (34 tests neufs, 1844 verts au total), ruff/bandit propres, doc d'architecture à jour. Chantier C (auto-correctifs de code réels, ROUGE) reste au point d'arrêt explicite convenu — pas de code écrit avant confirmation de Yann.
Version 2.288.22
  • refactor(memory): consolidation des 3 systèmes de proximité relationnelle identifiés comme redondants lors de l'audit mémoire personnes (2026-08-01, §A.10) — pas 3 doublons équivalents : `relationships` (globale, cross-canal, mise à jour en continu à chaque message) et `affinities` (par salon, recalculée périodiquement) mesuraient la MÊME chose par deux algorithmes indépendants pouvant diverger sans que rien ne le remarque ; `relationship_history`/`interaction_patterns` s'est révélé être un journal narratif complémentaire, pas un doublon de score ; une 4ᵉ table découverte au passage, `evolution_log`, s'est révélée sans rapport (dérive de la personnalité d'Amélia elle-même, pas une relation à une personne) — mal rangée dans le même fichier. Fusion choisie au niveau CODE, pas SQL (validé explicitement avec Yann) : `RelationshipMemory.get_relational_state()` combine les 3 sources en un `RelationalState` qui signale une éventuelle divergence sans jamais fusionner les deux échelles en un seul nombre, et sans jamais laisser le signal global (potentiellement nourri par un DM) influencer `!opinion`/`person_opinion.py`, qui reste strictement scopé par salon. `_generate_user_response` migré (3 lectures dispersées → 1 lecture cohérente). Effet de bord corrigé au passage : 3 fichiers de test à la racine du dépôt (`tests_3_features.py` et 2 autres) semblaient exister mais n'étaient jamais collectés par `pytest` sans argument (préfixe `tests_` au lieu de `test_`, `async def test_*` bruts sans `pytest-asyncio` configuré) — migrés dans `tests/` avec l'enveloppe standard du reste de la suite, ils tournent réellement en CI désormais. 1810 tests verts, ruff/bandit propres.
Version 2.288.21
  • fix(luanti): 6e incident réel sur le même mécanisme, même journée — trouvé en observant l'effet réel de v2.288.20 quelques minutes après son déploiement plutôt que de le supposer suffisant. La preuve directe était bonne (`j'étais bloquée, je me dégage` bien apparu au journal, confirmant que le dégagement s'engageait enfin), mais la position restait figée : rafale de `anti-blocage : passage déjà dégagé aux pieds, attendre` en boucle, jamais un seul `MOVE_FORWARD`. Cause : le dégagement s'ENGAGEAIT bien (v2.288.20 a résolu ce point), mais ne pouvait jamais PROGRESSER au-delà de sa phase 0 — `_stair_descend` (l'appelant réel) repropose légitimement son PROPRE `WAIT` entre deux évaluations de `StuckDetector.process()` (confirmation de palier, indépendante du dégagement en cours), et le raccourci `_INTENTIONAL_REPEAT` remettait `_deg_step`/`_escape_yaw` à zéro à CHAQUE tel appel — empêchant la rotation pieds/tête/avancer de jamais dépasser la phase 0, quel que soit le nombre de cycles. Le raccourci ne s'applique désormais plus tant qu'un dégagement est déjà engagé (`_deg_step > 0`) ; l'évaluation normale (déjà correcte depuis v2.288.20) reste seule responsable de la remise à zéro une fois réellement plus bloquée. Nouveau test qui échoue de façon vérifiée sans le correctif (`.reason` vide — preuve que le WAIT de l'appelant passait inchangé au lieu de déclencher la phase tête) et passe avec. 1810 tests verts, ruff/bandit propres.
Version 2.288.20
  • fix(luanti): 5e incident réel sur le même mécanisme en une journée — trouvé en vérifiant en direct l'effet de v2.288.19 juste après son déploiement (pas supposé résolu sans preuve). Le correctif précédent (`_stair_descend` se détourne au lieu de re-creuser un bloc déjà prouvé absent) a bien éliminé la rafale de refus « bloc déjà absent » (0 occurrence depuis le déploiement), mais la position du joueur restait figée sur des centaines de ticks supplémentaires — `recolter_fer` toujours actif, `need_iron`/`need_coal`/`need_wood` toujours vrais. Cause racine, cette fois dans `voxelibre/stuck.py` (le détecteur anti-blocage PARTAGÉ, pas `planner.py`) : `_stair_descend` alterne WAIT (~10 ticks, exempté des signaux 1/2 par conception) et TURN (dégagement, hors `_LOCOMOTION_TYPES` donc hors signal 2 aussi) sur une position strictement figée — seul le signal 3 (confinement, DONT LE RÔLE DOCUMENTÉ est justement de survivre à ce qui réinitialise le signal 2) pouvait détecter l'absence totale de progrès. Mais `_confine_strikes` était remis à zéro par CHAQUE WAIT (branche `_INTENTIONAL_REPEAT`), et comme WAIT domine le cycle, le seuil n'était jamais atteint : le compteur anti-blocage lui-même échappait à son propre garde-fou de secours. Retiré ce seul reset (les deux autres, `_stuck_count`/`_frozen_count`, restent légitimes pour une attente réellement ponctuelle) ; la fenêtre de confinement continue de n'être évaluée que sur les ticks non exemptés, donc un flux purement WAIT/EAT (manger en rafale) ne déclenche toujours rien — vérifié par une contre-preuve dédiée. 2 tests neufs (le cas observé + la garantie de non-régression sur EAT), 1809 tests verts, ruff/bandit propres.
Version 2.288.19
  • fix(luanti): 4e incident réel sur le même mécanisme en une seule journée — trouvé en observant le journal `.174` en direct pendant la boucle `/loop 30m`, pas en lisant le code au hasard. `recolter_fer`/`recolter_charbon`/`recolter_bois` tournaient en boucle sur le refroidissement anti-livelock (45s chacun, jamais résolus, `need_iron`/`need_coal`/`need_wood` toujours vrais après 30+ minutes) : rafale de `creuser_bloc -> rejected (bloc déjà absent (air))` sur la MÊME case toutes les ~11 ticks. Cause : le repli « attente épuisée » de `_stair_descend` (ajouté en v2.288.15 pour éviter un blocage sans fin) retentait `dig_block(dig_cell, ...)` — mais `dig_target not in solids` est justement la condition qui fait entrer dans cette branche, donc ce dig de repli était un refus GARANTI à chaque fois, qui relançait juste une nouvelle attente de 10 ticks. Cycle attente/refus sans fin, seulement interrompu après plusieurs tours complets par le refroidissement anti-livelock externe (qui compte les refus, pas les attentes). Le repli se détourne désormais (`turn_to`, même geste que la garde anti-lave trois lignes plus haut dans la même fonction) au lieu de retenter un coup déjà prouvé perdant — résout le blocage immédiatement au lieu d'attendre l'accumulation de 5 refus. 2 tests neufs (le test existant qui verrouillait l'ancien comportement — retenter le dig — est mis à jour pour verrouiller le nouveau).
Version 2.288.18
  • feat(luanti): chantier navigation longue distance — première tranche. `DropRecoveryMission` documentait elle-même son manque depuis sa création : un épuisement moteur ne prouve qu'une connaissance LOCALE incomplète, jamais une vraie impossibilité, « la distinguer demanderait un vrai graphe topologique (hors de portée ici) ». Ce graphe existe déjà (`amelia_core/spatial.py`, Dijkstra borné sur les passages réellement traversés, déjà câblé pour `explorer`/`recuperer_drop`/`retour_base` dans `planner.py`) mais `route_to_tile` renvoyait `None` aussi bien pour « carte trop pauvre » que pour « route prouvée absente » — impossible à distinguer côté appelant. Ajout de `RouteAvailability` (FOUND/NO_PATH/NO_INFO) + `SpatialMemory.route_availability_to_tile(_from_state)`, réutilisant le même Dijkstra sans route inventée. Câblé dans `DropRecoveryMission.note_motor_exhaustion` (paramètre `spatial` optionnel, `amelia_voxelibre.py` transmet `self.cognition.spatial`, la vraie instance persistante) : le motif et `status()` distinguent désormais une route confirmée absente d'un simple manque d'info. Volontairement PUREMENT OBSERVATIONNEL pour cette tranche — le budget de cycles/cooldown déjà validé ne change pas de rythme selon ce signal (la carte reste grossière : tuiles 16×16, 2D, un NO_PATH d'aujourd'hui n'est pas une preuve d'impossibilité définitive). Prochaine tranche envisageable seulement après observation en conditions réelles : blacklist temporaire des passages qui échouent à répétition (feedback anti-blocage → risque d'arête), et étude d'un raccourcissement du budget de cycles quand NO_PATH est confirmé plusieurs fois de suite. 6 tests neufs (unité `SpatialMemory` + unité `DropRecoveryMission` + un test d'intégration bout en bout vérifiant que `amelia_voxelibre.py` transmet bien la VRAIE instance, pas un défaut silencieux). Pas déployé — nouveau sous-système, pas un correctif Luanti isolé déjà rodé.
Version 2.288.17
  • fix(discord): message coupé en plein milieu, signalé en direct par l'utilisateur (une réponse en Discord s'arrêtait sans fin propre). Cause : `_truncate_response` recevait `response_max_chars` (2500 par défaut) comme plafond de troncature POST-génération pour la voie de chat normale — la même valeur qui sert déjà de cible dans le prompt et de budget `num_predict`. Un modèle qui dépasse un peu cette cible (rant, réponse posée en plusieurs idées) se faisait donc amputer la fin au lieu d'être simplement envoyé en plusieurs messages Discord, ce que `text_chunking.py` sait déjà faire proprement. `response_max_chars` reste la cible communiquée au modèle ; le plafond dur de troncature est désormais `CHAT_HARD_CEILING_CHARS` (`MAX_OUTPUT_TOKENS * 4` ≈ ce que le modèle peut réellement produire sous le budget `num_predict` existant, donc ne coupe plus jamais une génération complète — seulement un emballement réel). La voie structurée (JSON/parseur) et son plafond explicite `output_max_chars` sont inchangés. 1 test neuf.
Version 2.288.16
  • feat(memory): audit + durcissement de la mémoire des personnes (confidentialité DM/public, opinions sur un tiers). La fuite la plus grave : le tool LLM `search_memory` acceptait un `channel_id`/`user_id` fourni par le modèle lui-même dans son bloc `<<TOOL>>` (hors du schéma documenté au modèle) — un message injecté aurait pu faire lire la mémoire d'un autre salon/utilisateur. Corrigé via un `contextvars.ContextVar` fixant la portée réelle de la requête en entrée de `_generate_user_response`, ignoré si fourni par le modèle. Aussi corrigés : `!threads`/`!discussion`/`!projects`/`!mode` contournaient le garde-fou `is_public` (fuite de ses propres données DM vers un salon public) ; `!affinity all/top` + `!learned` exposaient le classement relationnel de tout le monde sans contrôle admin, alors que le même fichier réserve déjà cette info aux admins pour une personne désignée 15 lignes plus loin (incohérence interne) ; l'écriture de faits (`upsert_profile_item`) ignorait le réglage `unify_user_memory` que la lecture respectait déjà, créant un mismatch écriture/lecture (reproduit indépendamment par un test de migration). Ajouts : colonne `visibility` (PUBLIC/SHARED_CONTEXT/RELATIONAL/PRIVATE/SYSTEM) sur `profile_items`/`learned_facts` (migration additive, défaut SQL le plus restrictif `PRIVATE` pour l'historique) ; nouvelle commande `!opinion`/`!avis @personne` ET détection en langage naturel (« Que penses-tu de cette personne ? », y compris la forme inversée exacte de la mission) partageant la même logique sécurisée (`_deliver_person_opinion`, module pur `person_opinion.py`) — identité résolue uniquement par mention Discord explicite ou nom UNIQUE dans le salon (jamais de fusion spéculative d'homonyme si ambigu), contexte limité au salon courant + salon public désigné (jamais un autre DM/salon privé), score d'affinité converti en étiquette qualitative (jamais le nombre brut au LLM), nouveau scrubber de sortie `_scrub_confidence_disclosure` (attribution nommée type « X m'a confié que… », source privée + verbe de disclosure, aveu d'existence d'un secret sans contenu, rumeur présentée comme consensus collectif) en deuxième barrière. Audit complet et guide : `docs/AUDIT_MEMOIRE_PERSONNES_2026-08-01.md`, `docs/MEMOIRE_PERSONNES_GUIDE.md`. 1785 tests (+65 neufs sur cette mission), ruff/bandit propres.
Version 2.288.15
  • fix(luanti): régression trouvée en vérifiant en direct l'effet de 2.288.11/12 — `recolter_fer` figée à la même position depuis plus de 2h, 1436 ticks consécutifs identiques (« palier en cours de confirmation, attendre »), 0 action de mouvement. Le correctif qui a éliminé la boucle de reminage remplaçait un refus garanti par une attente inconditionnelle ; quand le vrai palier est à plus d'un cran sous les pieds (puits multi-blocs, exactement le cas que la fonction dit vouloir éviter), `walkable` ne confirme jamais `landing_ground` — et un WAIT échappe à TOUT compteur anti-blocage existant (exempté du signal StuckDetector, ne compte jamais comme un refus donc ne déclenche jamais le refroidissement anti-livelock). Rien ne pouvait plus jamais la débloquer. Attente désormais bornée (10 ticks consécutifs sur la même cible, `_stair_wait_ctx`) puis repli sur le dig historique — au pire ça redéclenche le refroidissement borné, jamais un blocage permanent.
Version 2.288.14
  • fix(discord): signalé en direct — elle ramenait sa session Luanti dans le salon public au milieu de conversations qui n'avaient rien à voir. `_voxelibre_play_context()` injecte le bloc « ⟦Activité en cours — IMPORTANT⟧ » en tête de CHAQUE prompt, dans TOUS les salons, tant qu'une session est active ; sa seule raison d'être documentée est d'empêcher qu'elle nie jouer quand on le lui demande (fix v2.256.0), mais le texte (« la réponse est OUI ; parle-en à la PREMIÈRE personne ») se lisait comme une incitation à en parler même sans qu'on demande rien. Reformulé : la permission de raconter reste conditionnée à une vraie question, et une consigne explicite lui interdit désormais de ramener le sujet d'elle-même dans une conversation sans rapport. La garantie anti-négation reste intacte. Volontairement pas limité au salon public : le partage spontané a déjà son propre circuit dédié et borné (`_maybe_voxelibre_share`, v2.288.13), cette ligne n'a donc jamais eu de raison légitime de pousser une narration non sollicitée nulle part.
Version 2.288.13
  • fix(discord): `_maybe_voxelibre_share` (partage spontané d'une découverte VoxeLibre en salon) dédupliquait par SUJET au lieu de par croyance individuelle — `_absorb_luanti_discoveries` tague chaque découverte avec `subject=f"voxelibre:{kind}"`, une catégorie fixe (« voxelibre:mine », « voxelibre:death »…) partagée par toutes les découvertes du même type. Vérifié en direct sur `.174` : 20 croyances « voxelibre:mine » distinctes (coordonnées différentes) partagent exactement ce même sujet. Une fois UNE mine partagée, les 19 autres devenaient silencieusement impartageables pour tout le reste de l'uptime du process — au mieux un partage réussi par catégorie par redémarrage, jamais les 2×/jour prévus. `_shared_voxelibre_subjects` renommé `_shared_voxelibre_belief_ids` (dédup par id de croyance). 4 tests (aucune couverture directe avant).
Version 2.288.12
  • fix(luanti): en observant l'effet réel de 2.288.11 juste après son déploiement, les rejets « bloc déjà absent » restants (5 sur la première heure, contre plusieurs dizaines/minute avant) ne venaient plus de `recolter_fer` mais du corridor d'évasion anti-blocage (`StuckDetector`, phases pieds/tête) — confirmé en direct au journal, avec le même effet de bord : `[cooldown] objectif 'explorer' bloqué 45s après 5 refus consécutifs`. Le commentaire du code documentait déjà ce cas (couloir cassé lors d'un cycle précédent) sans jamais vérifier `solids` avant de creuser. Même correctif minimal que 2.288.11 : si `solids` confirme la case déjà vide, attendre plutôt que redemander un minage garanti refusé — la logique de rotation de cap après plusieurs cycles sans progrès reste inchangée.
Version 2.288.11
  • fix(luanti): la visibilité ajoutée en 2.288.10 a immédiatement payé — le journal en prod montrait `recolter_fer` rejeté par la même raison précise en rafale : « bloc déjà absent (air) », pas la garde de portée supposée. `_stair_descend` redemandait indéfiniment la MÊME case (foot-1) une fois vidée, tant que `walkable` n'avait pas confirmé le palier au relevé suivant — chaque refus comptait vers le refroidissement anti-livelock (45s), bloquant `recolter_fer` en boucle. `solids` (export du mod) étendu d'un cran (foot-1, pas plus profond — descendre sans confirmation walkable resterait exactement le puits dangereux que la fonction évite délibérément) pour que le planner sache distinguer « encore solide » de « déjà creusé » : si `dig_cell` est confirmé vide, elle attend le prochain relevé au lieu de redemander un minage déjà garanti refusé. Comportement des anciens bridges (sans cette donnée) inchangé.
Version 2.288.10
  • fix(luanti): `need_iron` restait vrai à chaque relevé de heartbeat depuis des heures — le journal montrait `recolter_fer` bloqué par le refroidissement anti-livelock environ toutes les 48s, en continu, à des positions différentes à chaque fois (jamais coincée au même endroit, juste jamais capable d'atteindre le fer). Chaque minage rejeté remontait le même message générique « précondition ou exécution refusée » : `do_dig()` (mod Lua) renvoyait un `false` nu depuis chacune de ses 5 gardes de précondition (position absente, zone protégée, hors de portée, lave à proximité, bloc déjà absent), et `process_commands()` n'a ce message générique QUE pour un résultat non-table. Chaque garde renvoie maintenant un `command_outcome` détaillé — même comportement (statut `rejected` inchangé), juste la vraie raison visible dans le journal au lieu de devoir deviner. Prochaine étape : lire la vraie raison en prod pour savoir si c'est un vrai bug de ciblage côté Python (candidat le plus probable : la garde de portée 6.5 blocs) ou autre chose.
Version 2.288.9
  • fix(luanti): incident réel observé en prod (HP tombé à 1/20) — `se_soigner` (objectif VITAL, priorité 90) s'est fait bloquer 45s par le même refroidissement anti-livelock générique qu'un objectif ordinaire, après 5 refus consécutifs (« grimper »). Pendant ce cooldown, `GoalManager.decide()` n'avait plus rien à choisir côté vital et retombait sur `explorer` — vu tel quel dans le heartbeat et le journal : une joueuse à 1 PV qui se promenait, sans aucune réponse à sa propre urgence, tant qu'aucun monstre ne s'est présenté. `VITAL_GOAL_NAMES` (survivre, fuir_joueur, se_soigner, reprendre_air, se_nourrir) exportée depuis `goals.py` et utilisée pour exempter ces objectifs des deux points de blocage (`_handle_motor_recovery_failure`, `_note_goal_action_outcome`) — toujours journalisés au même seuil, jamais désactivés.
Version 2.288.8
  • fix(luanti): un ordre humain « viens » (follow) disait « j'arrive, je te suis » mais ne bougeait jamais tant qu'un objectif de priorité ≥ 50 restait actif — construire_abri (55), retour_base (52), recuperer_drop (53), dormir (58), refaire_bois (59) — alors qu'aucun n'est une urgence vitale au sens du docstring de `CompanionMind` (« une menace, la faim critique ou une mort »). Le second garde-fou redondant (`goal.priority < 50`) qui causait ça a été retiré ; `directive_action()` reste la seule autorité, étendue pour couvrir précisément ce que documente déjà la classe (danger moyen/élevé, faim critique).
  • fix(luanti): l'annonce parlée du changement d'objectif (« je vais miner du bois »…) était throttlée à 8 s seulement — un objectif qui oscille (bois<->pierre, risque déjà documenté dans goals.py) la déclenchait presque en continu près d'un joueur, vécu comme du harcèlement. Throttle porté à 25 s.
Version 2.288.7
  • fix(memory): les 67 avertissements Bandit B608 (« hardcoded SQL expressions ») que `memory.py` déclenchait à lui seul ont été triés site par site — aucun n'était une vraie injection SQL. Tous relèvent de 4 motifs sûrs (squelette de placeholders `?` qui ne contrôle que leur NOMBRE, nom de table/colonne depuis une liste fermée, fragment WHERE construit uniquement à partir de littéraux fixes, sélection conditionnelle entre deux littéraux gardée par un `PRAGMA`). 3 requêtes en chaînes triple-guillemets ont été réécrites en concaténation simple (SQL final identique, juste écrit différemment côté Python) pour pouvoir porter un commentaire `# nosec` sans le faire atterrir dans le texte SQL ; les 64 autres sites sûrs portent ce commentaire sur la ligne exacte que Bandit signale. `memory.py` est désormais couvert par Bandit en CI comme le reste du cœur.
  • ci(mypy): `--check-untyped-defs` activé sur le périmètre mypy actuel (`amelia_core`, `rate_limiting.py`, `runtime_manifest.py`) — passait déjà sans erreur, donc un vrai gain de rigueur sans correctif nécessaire (mypy vérifie maintenant aussi le corps des fonctions non annotées de ce périmètre, pas seulement leurs signatures).
  • feat(luanti): navigation longue distance étendue au-delà de la seule exploration. `SpatialMemory.route_to_tile()` route désormais vers une tuile DÉJÀ connue (pas seulement vers une frontière inexplorée comme `route_to_frontier`) — exactement le manque que `drop_recovery.py` documentait lui-même (« distinguer l'épuisement moteur de l'absence de chemin confirmée demanderait un vrai graphe topologique, hors de portée ici »). Câblé dans le planner pour `recuperer_drop` ET `retour_base` : quand la carte relie une route d'au moins deux tuiles jusqu'au repère (drop ou base), Amélia suit les tuiles déjà traversées au lieu de ne compter que sur l'A* local à courte vue ; sans route connue, le comportement historique (approche directe + A* local, inchangé) reste le fallback exact — aucune régression si la carte ne sait rien.
  • chore(p0): vérité Git/production ré-établie par SHA-256 fichier par fichier (lecture seule, SSH `.174` accessible depuis les sessions `/loop`) : rien de PR #7 n'est déployé (prod reste au snapshot du 2026-07-29 23h37) ; le flip `inactive→active` des services observé pendant la vérification vient du watchdog `amelia-health-watch.timer` déjà existant (pas une session parallèle) ; l'état `failed` continu d'`amelia-health-watch.service` vient du disque à 91 % (alerte voulue, pas un bug silencieux, tracé avec `bash -x` jusqu'à la ligne exacte).
Version 2.288.6
  • fix(luanti): navigation.py visait `nxt+0.5` (centre d'une case [N,N+1) — l'ancienne convention) au lieu du centre réel `nxt` (case centrée sur son entier depuis `world_cell`, cf. 2.288.4) : une marche en ligne droite sur un seul axe dérivait en diagonale. Corrigé dans `next_step` et `safe_next_step` ; le `+1` vertical (sol sous les pieds) reste inchangé, sans rapport avec le centrage horizontal.
  • fix(luanti): la récupération d'un drop n'est plus abandonnée dès le 2e échec confirmé. Un épuisement moteur reflète surtout un rayon de scan local limité, pas une preuve d'impasse : chaque échec de cycle prolonge maintenant le délai (recul croissant) et redonne un budget d'échecs frais, sur `drop_recovery_max_cycles` cycles (3 par défaut) avant l'abandon définitif — borné, jamais infini.
  • audit(luanti): revue de la séparation moteur (~100ms) / tactique (~500ms) / cognitif (~5s) / projet — déjà largement en place et testée (regard non artificiel avec imprécision dérivée déterministe, rotation bornée en vitesse, bandes de distance de suivi, recherche bornée d'un joueur perdu, hystérésis anti-oscillation, ordre humain forcé qui survit à une préemption vitale et reprend ensuite, statut d'action distinguant soumission et confirmation moteur). Aucune régression trouvée ; le vrai manque identifié était la navigation longue distance ci-dessus.
Version 2.288.5
  • fix(health): les erreurs répétées du service Luanti ne sont plus avalées en silence. 7 `with suppress(Exception):` (journal, récompenses, respawn d'urgence, chat en jeu, nettoyage à l'arrêt) ne journalisaient RIEN ; le `except Exception` englobant du tick et le thread de conseil cognitif se contentaient d'un log répété sans compteur. Nouveau `voxelibre/health.py` : registre par sous-système (bridge/perception/goal_manager/planner/action_executor/continuous_motor/cognitive_advice/heartbeat/memory) avec échecs consécutifs, dernière erreur, disjoncteur à 5 échecs de suite et alerte throttlée — exposé dans le heartbeat et le tableau de bord web.
  • fix(luanti): `make_brain()` ne retombe plus sur `qwen2.5:7b-instruct-q4_K_M` quand `llm.model` est absent de `voxelibre.config.json` — c'était un modèle de chat généraliste 7B qui aurait tourné à la place du conseiller léger prévu, sans que l'opérateur le sache. Une config Luanti absente ou incomplète démarre désormais en mode heuristique (déterministe, sans LLM) avec un avertissement explicite. Le heartbeat (`status()['models']['luanti']`) reflète maintenant l'identité RÉELLE du cerveau en vie (modèle/URL/timeout/keep_alive), pas une valeur recalculée depuis la config.
  • feat(runtime): `runtime_manifest.py` — manifeste central affiché au démarrage : modèles (chat/voix/vision/embeddings/Luanti) et leurs backends, `num_ctx`/`num_predict`/timeout/keep_alive/threads/GPU, fonctionnalités actives, version, commit Git, profil, état autonomie/mémoire/Luanti. Aucune valeur par défaut cachée : chaque champ vient de `Settings` déjà résolu ou est marqué explicitement absent/inconnu.
Version 2.288.4
  • fix(luanti): unification de la conversion position→cellule monde. Le planner tronquait (`math.floor`) les positions fractionnaires du joueur/de la cible à ~15 endroits (planner/navigation/stuck/build), alors que le mod (`navigation.lua::rounded()`) ARRONDIT au nœud le plus proche — dès qu'une fraction atteignait 0,5, planner et monde ne s'accordaient plus sur la colonne occupée (minage/pose sur le mauvais bloc, mur détecté à tort, descente d'escalier qui recreusait en boucle un palier déjà confirmé sûr par le mod). Nouvelle fonction centrale `world_state.world_cell()`, alignée bit à bit sur la convention du mod (PAS `round()` de Python, qui diverge silencieusement sur les `*.5` exacts).
  • test(luanti): couverture dédiée de `world_cell` (positions fractionnaires et négatives : 10.0/10.1/10.49/10.5/10.51/10.9/-10.1/-10.5/-10.9) + deux régressions planner (descente d'escalier, pose de torche) qui échouaient avec l'ancienne troncature.
Version 2.288.3
  • fix(luanti): un épuisement moteur RÉPÉTÉ sur un objectif non-drop (locomotion vraiment impossible — descente minière coincée, cible inatteignable) met désormais l'objectif en cooldown borné, comme les refus discrets. Corrige le blocage résiduel où `recolter_fer` restait sélectionné et re-marchait dans le mur à chaque tick malgré l'anti-blocage. Amélia change alors de plan (explorer/miner ailleurs) puis pourra réessayer.
Version 2.288.2
  • fix(luanti): la descente en escalier (`_stair_descend`) ne fonce plus indéfiniment dans un mur. Quand le palier bas est confirmé mais que le passage d'entrée est obstrué (tête ou pieds), Amélia DÉGAGE d'abord le bloc au lieu de re-marcher sur place — correction d'un livelock de minage observé en prod (`recolter_fer` figé sur « descendre sur le palier sûr », position inchangée, anti-blocage impuissant).
Version 2.288.1
  • feat(luanti): quand Amélia veut fondre sans four à portée, elle FABRIQUE (8 cobble) puis POSE un four sur place au lieu de répéter un smelt voué à l'échec ; un four déjà en sac est simplement posé, et un four détecté à portée déclenche directement la fonte. Résout la vraie cause du livelock `fondre` (le cooldown restant en dernier filet).
Version 2.288.0
  • fix(luanti): réparation d'une fusion master→feature bâclée qui avait laissé plusieurs cassures avalées par le try/except du tick — `goals.py` appelait un scoring de suggestion inexistant, `amelia_core/observer.py` levait NameError (`time`/`uuid`) à chaque tick moteur, `_take_cognitive_advice` référençait une variable absente et `_cognitive_last_rejection` n'était pas initialisé. La télémétrie moteur segmentée et le rejet des conseils périmés fonctionnent réellement de nouveau.
  • fix(luanti): l'API d'objectifs est réunifiée — `set_goal(source, ttl_seconds)` pour un ordre humain forcé (borné) ET `suggest_goal(expires_tick, strength)` pour un biais LLM SOUPLE. Le conseil LLM n'est plus jamais un `forced goal` ; un besoin vital préempte l'ordre forcé sans le détruire (il reprend ensuite).
  • fix(luanti): garde-fou d'« impossibilité vérifiée » — une action discrète refusée en boucle (`fondre -> rejected` faute de four accessible, observé en prod) met l'objectif en cooldown borné au lieu d'un livelock ; la locomotion garde son anti-blocage dédié.
  • test(luanti): preuve anti-tempête SQLite (1 min de marche < 100 événements), couverture des 6 causes de rejet d'un conseil périmé, et cooldown d'objectif bloqué.
  • feat(luanti): `sqlite_size_bytes` (base + WAL) ajouté aux métriques de rétention du heartbeat pour surveiller la croissance réelle.
  • fix(ops): `scripts/aurore.sh status` lit le vrai `status_path` du heartbeat ; l'unité Xvfb est isolée (`luanti-xvfb.service`) pour supprimer les courses client/écran.
Version 2.287.2
  • fix(luanti): l'anti-blocage cumule les petits déplacements confirmés entre snapshots ; une marche lente reste productive, tandis que l'immobilité et les oscillations restent bornées par les garde-fous existants.
Version 2.287.1
  • fix(luanti): une montée CLIMB identique reste active tant que les snapshots confirment un progrès vertical ; une montée immobile ou refusée conserve l'anti-blocage borné.
  • fix(survie): sans nourriture ni animal visible, la recherche passe par l'exploration obstacle-aware (virages et changement de cap) au lieu de réarmer une marche aveugle.
Version 2.287.0
  • release(luanti): mission de récupération des drops bornée, avec approche verticale réelle (escaliers/remontée), cooldown après une impasse confirmée et abandon propre à la seconde route bloquée.
  • fix(survie): les PV à 7/20 déclenchent maintenant la récupération avant un projet ; Amélia fabrique un ragoût de champignons sûr depuis bol + rouge + brun pour rétablir la faim de régénération.
  • fix(alimentation): les aliments nocifs (poisson-globe, œil d'araignée, chair putréfiée et suspects) ne sont plus sélectionnés comme repas de secours par le planner ou le pont Luanti.
  • fix(moteur): l'épuisement anti-blocage efface l'intention GOTO périmée, publie un STOP réel et remonte l'échec au projet au lieu de réarmer la même marche.
  • sûreté: les besoins vitaux préemptent temporairement un objectif humain forcé, et le heartbeat expose l'état/raison de la mission de récupération.
  • security(web+git): interface web en échec fermé (écoute loopback derrière Nginx, inscriptions coupées, OAuth Discord restreint), secrets TLS et artefacts SQLite retirés de l’index, motifs `.gitignore` généralisés et Gitleaks ajouté aux commits/à la CI.
  • fix(voxelibre): gouverneur de sécurité avant toute préférence LLM, durée de vie bornée des intentions suggérées, arrêt moteur unique dès qu'un état monde dépasse 1 s, et exposition `state_age_ms`/`state_fresh` dans le heartbeat.
  • fix(cognition): les conseils asynchrones sont liés au tick, à leur âge, au niveau de danger et à la génération d'ordre humain ; toute décision obsolète ou contredite est rejetée.
  • security(self-coding): auto-codage désactivé en production et validation durcie à 100 % des tests déclarés, sans acceptation par simple smoke test.
  • ci(quality): dépendances verrouillées avec hashes, compilation, suite pytest complète, Ruff, mypy `amelia_core`, Bandit, ShellCheck, Luacheck et Gitleaks exécutés automatiquement.
Version 2.286.0
  • release(luanti): journal cognitif séparé, segmenté et borné ; croyances temporaires expirables et projection SQLite incrémentale pour une exploitation longue durée.
  • fix(voxelibre): STOP immédiat sur « attends/repose-toi/observe », conseils LLM datés et rejetés lorsqu'ils sont périmés, et mouvement déclaré comme flux sans fausse postcondition terminale.
  • feat(voxelibre): protections complètes pour CLIMB/STORE, recherche active d'un joueur perdu, microactions sûres et navigation topologique progressive depuis la mémoire cognitive.
  • feat(luanti): rangement limité au coffre propre d'Amélia, fonte via un vrai four et ses callbacks, flèches réelles avec ligne de vue/cooldown, et usure VoxeLibre des arcs/outils.
  • ops: contrôle GitHub Actions ajouté pour les contrats cognitifs et Luanti ; la révision déployée est préparée pour un tag de release vérifiable.
Version 2.285.2
  • fix(luanti): le swing de l'outil est visible dans le POV première personne pendant un minage piloté côté serveur. Le patch client fail-closed réutilise uniquement l'animation `dig` déjà reçue : aucun clic synthétique et aucune double action monde.
Version 2.285.1
  • fix(voxelibre): ajout d'un superviseur dédié toutes les 15 s, démarré indépendamment de `enable_autonomy`. Le correctif v2.285.0 était autrement inatteignable quand l'autonomie sociale était coupée ; heartbeat, adoption, arrêt et rapport fonctionnent désormais réellement en mode diagnostic.
Version 2.285.0
  • fix(voxelibre): le bot réadopte désormais une partie confirmée par le heartbeat après un reboot, démarrage manuel ou watchdog, sans consommer un quota de session ; le jeu reste autorisé en mode diagnostic même quand l'autonomie sociale est coupée.
  • feat(voxelibre): fin de session remplacée par un rapport déterministe fondé sur le dernier heartbeat (durée, objectif, PV/position si disponibles, action et moments réellement observés). Un arrêt échoué ne clôt plus fictivement la session.
  • docs: README principal réécrit en guide court d'exploitation, index documentaire et manuel OPERATIONS ajoutés.
Version 2.284.2
  • fix(validation mémoire): l'approbation humaine d'une `assistant_fact_proposal` la promeut maintenant avec provenance `[human_approved_assistant_proposal:id]` et confiance 0,75 ; sans approbation elle reste pending puis expire, sans écriture dans les faits.
Version 2.284.1
  • fix(mode diagnostic): les synthèses historiques sont conservées pour audit mais complètement retirées des snapshots tant que `auto_summarize=false`; aucun wake brief, digest de sujet ou ancien RAG ne peut les réinjecter par ce chemin.
Version 2.284.0
  • fix(trust): `note_fact` ne peut plus écrire une sortie d'Amélia dans `learned_facts`. Il crée seulement une proposition `assistant_fact_proposal` (trust 0,15, non utilisable comme fait), validable par un humain et expirant via la file existante.
  • fix(synthèses): une synthèse repart uniquement des messages humains originaux et des faits déjà filtrés ; les anciennes synthèses, réponses d'Amélia, profils, objectifs et observations ne peuvent plus alimenter une nouvelle synthèse. Provenance enregistrée : `derived_summary`.
  • fix(cohérence): toute affirmation Luanti au présent exige désormais un heartbeat de jeu de moins de 10 s ; sans état live, la réponse est régénérée puis la phrase non justifiée est retirée en dernier recours. Les dérives critiques identité/émotion du DriftDetector alimentent aussi la régénération.
  • ops(mode diagnostic mémoire): contexte borné à 12 messages, faits/profils à 8, objectifs à 4, observations à 2 et synthèses à 1. RAG sémantique, unification cross-canal, auto-synthèse, extraction LLM, auto-apprentissage, réflexion interne, deep work automatique et autonomie sont temporairement désactivés pour isoler les sources de dérive.
Version 2.283.11
  • fix(mémoire sociale): même sans mention Discord ni bloc de nom déjà résolu, « de quoi tu parlais avec Lamantis ? » tente une résolution unique par nom dans le salon courant avant d'interroger les messages persistés.
Version 2.283.10
  • fix(mémoire sociale): une personne citée par son nom (sans mention @) est maintenant résolue via son user_id connu avant la recherche ciblée ; les échanges restent bornés au salon courant.
Version 2.283.9
  • fix(mémoire sociale): les questions « de quoi tu parlais avec X ? » déclenchent désormais une recherche ciblée par identifiant dans le salon courant ; seuls les messages réellement retrouvés sont injectés, sans fuite cross-canal ni invention lorsque le résultat est vide.
Version 2.283.8
  • refactor(mémoire): séparation explicite dans le prompt entre mémoire vérifiée, préférences et historique ; injection des faits limitée à 4 éléments et filtrée sous 65 % de confiance, profils/synthèses limités à 4 pour éviter la soupe contextuelle et les récits fabriqués.
Version 2.283.7
  • fix(anti-hallucination): une question sur ce qu'Amélia disait avec une personne tierce est maintenant refusée si le fil récent ne contient aucun message de cette personne ; les profils et anciens thèmes ne peuvent plus être transformés en faux échange.
Version 2.283.6
  • fix(dialogue): désactivation explicite de la réflexion Qwen3.x (think=false) pour éviter les réponses vides quand le budget de sortie est consommé par la pensée ; fallback corrigé vers gemma3:1b, réellement installé sur le serveur, au lieu de gemma3:4b absent.
Version 2.283.5
  • fix(gpu): llm_num_gpu passe de 0 à -1 (auto). Amelia ne force plus num_gpu=0 dans les requêtes Ollama ; qwen3.5:9b peut de nouveau répartir ses couches sur les deux GPU avec ctx=8192.
Version 2.283.4
  • perf(gpu): contexte Ollama fixé à 8 192 tokens pour permettre l'offload GPU de qwen3.5:9b sur les GTX disponibles ; concurrence et modèles chargés restent bornés à un pour éviter le swap CPU et la latence web.
Version 2.283.3
  • fix(modèles): Amelia utilise désormais réellement qwen3.5:9b via l'instance Ollama unique sur 11434 ; l'ancien llama.cpp qwen2.5 et son adaptateur 11436 sont désactivés pour éviter le doublon et la divergence entre le modèle affiché et le modèle exécuté. Le service vision 11435 reste dédié à la vision/auxiliaires.
Version 2.283.2
  • ops(contexte): fenêtre Ollama rétablie à 12 288 tokens pour qwen3.5:9b ; le contexte mémoire reste borné à 20 éléments. Configuration sauvegardée et serveur redémarré après livraison.
Version 2.283.1
  • ops(modèle): modèle principal Ollama passé à qwen3.5:9b, déjà présent sur le serveur ; fenêtre de contexte réduite à 8 192 tokens et contexte mémoire à 20 éléments pour limiter la latence web tout en conservant la qualité des réponses.
Version 2.283.0
  • perf(dialogue): le contrôle sémantique LLM n'est plus lancé deux fois sur chaque réponse interactive. Les garde-fous déterministes (personnes, sujets refusés, expérience sensorielle, ton et répétitions) restent actifs ; l'audit LLM devient opt-in via enable_semantic_coherence_check. Le verdict INVALIDE n'est plus accepté à tort comme VALIDE.
  • fix(mémoire): une demande naturelle comme « Retravaille ta mémoire immédiatement » est traitée avant le filtre d'intent trivial et fonctionne aussi dans le salon public pour le créateur ; les détails par personne restent masqués hors DM. Les réponses de curiosité sans source Wikipédia ne sont plus enregistrées comme des faits d'Amélia.
  • fix(cohérence): attribution stricte par user_id et fil public borné conservés ; les corrections de sujet sont appliquées avant la réponse. Les profils faibles et les anciennes sorties de relecture ne deviennent pas des faits durables.
  • fix(luanti): purge transactionnelle sauvegardée du MapBlock TNT dupliqué (1 453 objets), garde Lua de restauration des wagonnets contre les positions/mapblocks non chargés et cadence de respawn bornée ; doMobSpawning persistant est passé à false après vérification SQLite.
  • perf(web/voxelibre): capture 960×540, fenêtre virtuelle agrandie, MJPEG sans polling ni cache et fermeture client normale silencieuse ; l'état bridge/activité Luanti reste injecté dans la mémoire de réponse pour qu'Amélia sache si elle joue, son objectif et ses blocages.
  • ops: MusicGen isolé dans son environnement Python compatible avec déchargement VRAM, deep-work nocturne borné (3 salons/3 utilisateurs, concurrence 1), watchdog des services actifs et sauvegardes versionnées avant chaque opération monde/config.
  • validation: suite locale complète passée avant livraison ; compilation Python, tests mémoire/attribution, tests web/MJPEG, tests VoxeLibre et réparateur SQLite inclus dans la release.
Version 2.282.0
  • fix(luanti): réparation offline et vérifiée d'un seul MapBlock saturé par 1 453 copies du même wagonnet TNT, avec sauvegardes SQLite et suppression de la seule clé persistante correspondante. Le restore upstream refuse désormais les blocs non chargés et ne peut plus boucler plus vite qu'une fois par seconde.
  • perf(contexte+fond): le fil public long garde son en-tête et les derniers messages sans saturer Ollama. Le deep work nocturne est borné aux salons réellement actifs, à trois salons/trois personnes, sans concurrence artificielle, et une fenêtre privée déjà traitée survit à un redémarrage.
  • fix(dialogue+web): « retravaille ta mémoire » déclenche la maintenance, une apostrophe française ne devient plus un faux titre musical, et une fermeture normale d'un client MJPEG ne produit plus de traceback serveur.
Version 2.281.0
  • fix(voxelibre): un soin ne vide plus l'inventaire quand la faim est déjà suffisante pour régénérer les PV. Les repas sont cadencés malgré un état retardé, et ni un repas ni une attente volontaire ne peuvent déclencher un creusement anti-blocage.
  • fix(luanti): le pont ne confirme plus un repas sans consommation ni gain réel, et ne restaure plus par erreur une pile qu'un handler a déjà consommée.
  • ops(luanti): les spawns naturels sont suspendus pendant le nettoyage réversible d'un MapBlock saturé par des wagonnets TNT dupliqués ; aucun objet du monde n'est supprimé sans sauvegarde ciblée préalable.
Version 2.280.0
  • fix(dialogue+confidentialité): seul le texte réellement écrit par une personne est désormais mémorisé ; les anciens blocs de prompt (réponse citée, mentions, profils de tiers) sont réparés au démarrage. En salon public, aucun rappel cross-canal, agenda, relation ou détail de relecture ne remonte d'un autre espace.
  • fix(mémoire): les résultats de l'ancienne relecture destructive sont retirés par provenance, sans toucher aux déclarations directes. La curation silencieuse est suspendue, `!relire` est additive et conservatrice, et les tâches structurées ne passent plus par les régénérations de chat qui produisaient du garbage. Les rendez-vous hebdomadaires respectent aussi enfin leur vraie fenêtre de rappel.
  • fix(cohérence+jeu): une présentation explicite conserve son identité et respecte la contrainte de lignes quand le texte le permet ; une correction telle que « non, pas Night Invasion » ferme le mauvais fil. Les guides de jeu ignorent les anciennes réponses d'Amélia et le planner VoxeLibre ne creuse plus sous ses propres pieds.
Version 2.279.0
  • fix(musicgen): environnement AudioCraft opérationnel sur Python 3.10 et CUDA ; l'installeur borne NumPy/Transformers/Gradio aux versions compatibles avec torch 2.1, contrôle les dépendances et évite le cache pip volumineux. L'unité MusicGen reprend le venv CUDA versionné, avec déchargement automatique de VRAM après inactivité et erreurs de rendu récupérables (HTTP 503 au lieu d'arrêter le worker).
  • fix(voxelibre): le pont mémoire bidirectionnel vers la base Discord est configuré en production. Le heartbeat de jeu frais injecte désormais dans chaque réponse qu'Amélia joue en ce moment, son objectif, ses PV, ses progrès et un éventuel blocage, au lieu de pouvoir nier l'activité.
  • ops(veille): timer système persistant toutes les cinq minutes : contrôle des unités critiques, de /health MusicGen, de la fraîcheur Luanti et du disque ; redémarrage borné du seul composant réellement en panne, sans suppression automatique de données.
Version 2.278.0
  • fix(luanti): la vraie configuration client bloquait encore Luanti à 320×240, 4 fps et undersampling×4 dans une fenêtre centrée, malgré Xvfb 960×540. Alignement complet à 960×540, rendu demi-résolution et 12 fps : le POV web reçoit enfin des images de jeu distinctes et les clics UI visent la bonne fenêtre.
  • fix(voxelibre): filon inaccessible = sortie de boucle. Après 7 essais sans progrès sur un même bloc, le planner met le minage en pause 120 ticks et explore au lieu de repartir dans une suite de trous/anti-blocages. Le StuckDetector est réinitialisé une seule fois au changement de stratégie.
Version 2.277.0
  • perf(web_ui): après mesure du palier 960×540@6 fps (capture+client ~40 % d'un cœur, sans erreur), cadence élevée à 10 fps et boucle MJPEG accélérée à 25 Hz. Le direct est nettement moins saccadé sans viser un 30 fps disproportionné pour le rendu logiciel du serveur.
Version 2.276.0
  • perf(web_ui+luanti): le POV devient une vraie fenêtre de jeu large : panneau Luanti pleine largeur, ratio 16:9 (au lieu de 4:3) et capture 960×540 à 6 fps par défaut dans le MJPEG. Mesure production avant hausse : le client 640×360@2 fps ne consommait que ~12 % CPU et ffmpeg ~3 %, donc la marge justifie ce palier lisible/fluide.
  • fix(luanti): backend xdotool aligné à 960×540 : les clics centrés (notamment le respawn) ne visent plus l'ancien centre fictif 1280×720. Les actions du mod restent indépendantes de la résolution.
Version 2.275.0
  • fix(web_ui+luanti): le POV n'est plus une suite de rechargements JPEG toutes les 1,2 s : nouvelle route admin MJPEG /api/luanti/stream, qui pousse chaque frame disponible sans cache ni polling. Capture relevée de 320×240@1 fps à 640×360@2 fps (GAME_CAPTURE_FPS réglable) et rendu web non pixelisé. Le live devient lisible et continu tout en gardant une cadence raisonnable pour le CPU du serveur.
  • fix(voxelibre): la prospection proactive ne creuse plus un trou arbitraire quand aucun filon n'est perçu. Elle explore et couvre du terrain jusqu'à voir une ressource ; les récoltes urgentes conservent leur descente minière contrôlée. Cela rend le comportement moins mécanique et moins bloqué sur place.
  • tune(dialogue): sampling Ollama harmonisé (top_p/top_k/min_p, pénalité de répétition) et températures d'humeur relevées sans dépasser le plafond de sécurité, pour une voix plus vivante sans relancer les boucles ni les hallucinations. 87 tests ciblés verts avant audit/déploiement.
  • ops(audit): contrôle direct production — tous les services critiques actifs, mais `integrity_check` SQLite a révélé des entrées physiques incohérentes dans goals.importance ; reconstruction atomique de memory.db avec sauvegarde avant remise en route.
Version 2.274.0
  • feat(web_ui): ON LA VOIT JOUER. Le POV d'Amélia dans Luanti (game_screen.jpg, capturé ~1×/s par run_luanti_client.sh) est désormais SERVI et AFFICHÉ côté web : route admin /api/luanti/screen (no-store) + écran live dans le panneau « Luanti » du dashboard (badge « En direct — elle joue » vs « Hors-ligne », pastille pulsée, rendu pixelated, mêmes tons). L'image se rafraîchit toutes les 1,2 s tant qu'elle joue (fraîcheur screen_fresh/screen_age via /api/agent/live) et se fige sinon ; la figure est révélée automatiquement quand elle se met à jouer, sans recharger. Réglage voxelibre_screen_path (défaut /opt/aurore/luanti/game_screen.jpg).
  • feat(luanti): BALADE (wanderlust) — elle ne « creuse plus un bloc un peu partout » sans jamais bouger. Les objectifs de récolte (40/35/33/32) l'emportaient en permanence sur « explorer » (10) → grattage local sans fin. Nouveau goal « balade » (prio 42, au-dessus des récoltes, SOUS la sécurité de nuit/survie) qui, par intermittence (période ~5 min dont ~75 s), en sécurité et à l'air libre, la fait partir en vraie excursion (retombe sur _plan_explore qui radie loin) puis rend la main. Warmup d'une période (installation d'abord), garde-fous danger/souterrain/nuit. Additif, 0 changement au planner de codex.
  • fix(web_ui): CHAT MOINS GÉNÉRIQUE. Le chat web appelait generate_response « nu » (contexte + faits, mais SANS personnalité/character/auto-directives/présence publique ni humeur+style) → voix neutre d'assistante. Il réutilise maintenant le MÊME kit de prompt que Discord (_build_reply_prompt_kit) et une température calée sur l'humeur (0.28→0.55) au lieu de 0.45 en dur — sur les DEUX voies (bloquante ET streaming). Repli sûr si le bot n'expose pas le kit.
Version 2.273.0
  • feat(luanti): elle ANNONCE son intention en jeu — « on sait pas ce qu'elle veut/fait » réglé. Quand son OBJECTIF change ET qu'un humain est à proximité, l'agent (amelia_voxelibre) émet un SAY en 1re personne dans le chat du jeu (« je pars chercher du fer », « je construis une base », « je vais me soigner »… mapping _INTENT_FR + fallback prettifié). Réutilise le pipeline SAY existant (ActionType.SAY → commands.py → mod do_say). Gate « humain proche » (per.nearest_player) → pas de spam quand seule, et n'altère pas les scénarios sans joueur. Throttle 8 s. 374 tests voxelibre verts (1 échec HTTP-port FLAKY, isolé = vert).
Version 2.272.0
  • fix(luanti): VRAI reset du monde. `!game luanti-reset-world` ne réinitialisait que la MÉMOIRE spatiale de l'agent (voxelibre_world.sqlite) — jamais le terrain (map.sqlite) — alors que l'aide promettait « monde vierge » → « le reset a pas marché ». Nouveau scripts/reset_luanti_world.sh qui fait un vrai reset : sauvegarde puis retrait du terrain + données de partie, Luanti régénère un monde neuf au redémarrage ; mod/config/auth conservés. Aide Discord corrigée (dit désormais ce que la commande fait vraiment + pointe le script). Monde de prod effectivement réinitialisé (ancien sauvegardé dans worlds/amelia/world-reset-backup-*).
Version 2.271.0
  • style(web_ui): refonte du CHAT en « dashboard » épuré (Linear/Vercel-like, mêmes tons). Bulles à surfaces PLEINES + bordures fines nettes (var(--line)) au lieu des gradients/blur/ombres lourds : Amélia sur surface neutre (#170d30), l'utilisateur en accent magenta discret ; input + console à bords crisp, focus magenta. Zéro ombre/blur → lisible et léger, cohérent avec la couche flat-perf. Couleurs de la palette inchangées. Itération 1 de la refonte page par page (chat = page la plus critique). 94 tests web verts.
Version 2.270.0
  • perf(web_ui): restyle « flat perf » — le chat saturait le GPU à ~100 % sur TOUS les appareils. Cause : le style néon/HUD reposait sur 7 backdrop-filter blur (le pire coût GPU, surtout mobile) + 18 animations PERMANENTES (fond aurora plein écran sur body::before, grille dérivante, scanline en mix-blend-mode, orbites, pulses HUD…) qui repeignaient en boucle. Couche d'override en fin de CSS : animation:none + backdrop-filter:none globaux, panneaux OPAQUES (mêmes tons violet/magenta/cyan), ombres statiques légères, mix-blend-mode neutralisé. Transitions au survol conservées, couleurs INCHANGÉES, layout responsive intact → interface plate/calme/nette qui ne chauffe plus le GPU. RAM : dominée par llama-server (5,2 Go, le LLM, légitime). 94 tests web + 1325 verts.
Version 2.269.0
  • fix(luanti): « elle me suit pas / très lente » — la companion ne s'auto-suivait qu'à run_distance*0.8 = 8 blocs (mod behavior.lua), donc sous 8 blocs elle WANDER au hasard au lieu de venir ; et ne courait (rattrapage) qu'au-delà de 10 blocs. Fix : run_distance 10 → 6 (core.lua + settingtypes) → elle suit dès ~4,8 blocs, court dès 6, se pose à follow_distance (3,5). Un seul réglage, réversible. Contrat mod 5/5.
Version 2.268.0
  • fix(luanti): « elle marche très vite » — le mod appelait apply_walk À LA FOIS au globalstep ET immédiatement dans do_move (mod/legacy_player_bridge) → chaque commande de marche ajoutait un pas supplémentaire ; l'agent enchaînant les commandes, la vitesse s'emballait. Retiré l'appel immédiat : le globalstep (~0.1 s) est désormais le SEUL à avancer la position, à vitesse CONSTANTE (WALK_SPEED), indépendante de la fréquence des commandes. Le saut/auto-step-up et les animations (walk/idle/jump) de la companion v2 restent gérés par le mod. Contrat mod 5/5.
Version 2.267.0
  • style(web_ui): filet responsive médias/blocs (chantier B du brief Luanti/web UI) — img/video/iframe/pre/table bornés à 100 % de leur conteneur pour qu'aucun élément inline ne déborde sur mobile (les règles spécifiques avatar/galerie/chat-asset l'emportent par spécificité). CONSTAT D'AUDIT : le web UI est DÉJÀ largement responsive (box-sizing, overflow-x hidden sur body, .table-wrap scrollable, clamp(), dvh, safe-area-inset, nav compacte « more », breakpoints 980/720/460px, grilles minmax) — pas de refonte lourde justifiée sans casser du bon code. Couleurs INCHANGÉES. 1325 verts.
Version 2.266.0
  • fix(coherence): jeu↔Discord — la ligne « ⟦Activité en cours⟧ » renforcée pour couvrir TOUTES les négations (chantier A1 du brief Luanti/web UI). Le chemin était bon (ligne injectée + survit intacte à la compression, vérifié) mais le wording n'interdisait que « je ne sais pas jouer » — pas « je ne joue pas » (la plainte réelle). Désormais : « tu joues LÀ, c'est un FAIT présent ; si on te demande si tu joues, la réponse est OUI ; ne réponds JAMAIS « non »/« je ne joue pas »/« je ne sais pas jouer » ». À valider en live. 1325 verts.
Version 2.265.0
  • fix(prompt+tests): SYSTEM_PROMPT_LEAN compressé 3633 → 2939 tokens (sous le budget 3100) — la série anti-confabulation (RESSENTI/PERSONNES) l'avait fait déborder. Coupes ciblées (listes exhaustives de formules bannies raccourcies, redondances VÉRITÉ/ÉMOTIONS retirées, ANGLE fusionné dans ESPRIT CRITIQUE) SANS perdre un seul garde-fou : tous les tests de contenu (anti-invention, confusion de personnes, ancrage horaire) restent verts. fix(test): test_fit_prompt_shrinks_output_only — nombre magique périmé (x*12000 = 4000 tok à _CHARS_PER_TOKEN=3.0, plus ~3300) ajusté à x*9000 pour exercer le bon chemin (shrink sortie sans élaguer le prompt). => SUITE COMPLÈTE VERTE : 1325 passés, 0 échec (fin des 2 échecs pré-existants).
Version 2.264.0
  • feat(self-diagnosis): auto-diagnostic « détection + proposition » — Amélia repère ses bugs RÉCURRENTS et les PROPOSE à l'admin, sans JAMAIS toucher à son code (choix de Yann : pas de réécriture autonome). Module pur self_diagnosis.py : normalize_message + error_signature (regroupe le même bug malgré les données variables) + recurring_errors (≥ seuil, anti-bruit) + ErrorJournalHandler (capture les tracebacks dans un journal borné, ne lève jamais). discord_bot : handler attaché au logger racine ; _maybe_report_recurring_bug (throttle ~3 h, dédup par signature) génère un diagnostic LLM et l'envoie en DM admin — c'est une PROPOSITION, pas une action. Flags enable_self_bug_reports (défaut true) + self_bug_report_min_count (défaut 3). 7 tests dédiés, 1323 verts.
Version 2.263.0
  • feat(voxelibre): suite des améliorations agent + companion (session parallèle « codex »). Retouches amelia_voxelibre, voxelibre/planner, goals, llm_brain, commands, build, companion + voxelibre.config.json + voxelibre.service + mod (legacy_player_bridge.lua, settingtypes.txt). DÉPLOYÉ live sur .174 (code + config + service daemon-reload + mod → worldmods, restart stack luanti) : mod chargé, agent planifie/exécute (fallback heuristique quand OllamaBrain timeout), zéro traceback. Suite complète 1322 verts. Backup rollback : /tmp/amelia_bridge.worldmods.bak3.
Version 2.262.0
  • feat(voxelibre): système companion v2 + améliorations agent Python (travail session parallèle « codex »). Nouveau voxelibre/companion.py (+ tests) ; retouches amelia_voxelibre.py, behaviors, chat, memory, navigation, planner (mine-anchor hard-skip), stuck, world_state ; mod lua legacy_player_bridge.lua. DÉPLOYÉ live sur .174 (code Python + fichier mod → worldmods, restart luanti-server/client/voxelibre) : mod chargé sans erreur (companion v2 loaded), agent exécute ses actions (creuser_bloc -> ok), zéro traceback. Suite complète 1316 verts.
Version 2.261.0
  • feat(coherence): awareness d'être bloquée (chantier #5 — DERNIER du brief cohérence des actions). Quand l'agent de jeu lutte contre un blocage (anti-blocage / stuck_loop répété), la ligne d'activité prétendait une progression fluide (« je récolte du fer ») — incohérent avec ce qu'elle vit. Ajout : current_activity.voxelibre_stuck_clause(why, journal) — détecte l'anti-blocage courant/récent (heartbeat why + 5 dernières décisions) et fait dire « là tu es un peu coincée et tu essaies de te dégager » au lieu d'un progrès fluide. Câblé dans _voxelibre_play_context, sous enable_action_awareness. 3 tests (11/11 test_current_activity.py), 1308 verts. => Les 5 chantiers #1→#5 de la mission cohérence sont livrés.
Version 2.260.0
  • feat(coherence): cohérence du RÉCENT — progrès de partie VoxeLibre (chantier #4 du brief cohérence des actions). Avant, la ligne d'activité ne disait que l'action de l'INSTANT (« je récolte du fer ») ; Amélia ne savait pas raconter ce qu'elle avait fait dans sa partie. Ajout : current_activity.voxelibre_progress_clause(memory) — clause factuelle dérivée des POI mémorisés par le mod (base, filons/mine, village, coffres, max 2 éléments), appended à _voxelibre_play_context (« Dans ta partie jusqu'ici : tu as une base à toi, repéré des filons »). Sous enable_action_awareness. 4 tests dédiés (8/8 test_current_activity.py), 1305 verts.
Version 2.259.0
  • feat(voxelibre): modularisation du mod amelia_bridge (travail session parallèle « codex ») — init.lua éclaté en lua/ (core, perception, behavior, combat, navigation, memory, emotion, animation, companion, dialogue, intent, hooks, legacy_player_bridge), architecture companion v2 + README + settingtypes. DÉPLOYÉ live sur .174 (worldmods + restart luanti-server/client/voxelibre, mod chargé sans erreur, amelia en jeu). fix(coherence-blocker): dialogue.lua:116 avait « "" .. excerpt » (double-guillemet, n'échappe pas en Lua) → erreur de syntaxe qui empêchait le chargement du module ; corrigé en « \" » (échappement). Test de contrat mod 5/5.
Version 2.258.0
  • feat(coherence): conscience des actions en cours généralisée (chantier #3 du brief cohérence des actions). Après le jeu VoxeLibre (#1/#2), Amélia sait désormais aussi qu'elle FAIT JOUER DE LA MUSIQUE quand c'est le cas — pour ne nier aucune action réelle. Module pur current_activity.py (music_activity_line, marqueur « ⟦Activité en cours » → survit+intact) + framework _current_activity_lines (VoxeLibre + musique, extensible) injecté en tête du contexte dans _build_memory_snapshot. Flag enable_action_awareness (défaut true). 4 tests dédiés, 1301 verts.
Version 2.257.0
  • fix(coherence): état d'action AUTORITAIRE — la ligne « ⟦Activité en cours⟧ » est gardée INTACTE (chantier #2 du brief cohérence des actions). Après #1 (survie à la compression) elle restait TRONQUÉE (~96-320 car) : l'instruction anti-négation « ne dis JAMAIS que tu ne sais pas jouer » pouvait être coupée. Désormais _compress_context_for_prompt EXTRAIT les lignes prioritaires en tête (activité en cours, fil public) et les garde verbatim, hors compression ; seul le reste est compressé. L'état d'action réel atteint donc le LLM EN ENTIER (elle ne peut ni le nier ni le déformer). +1 test (ligne intacte, non tronquée), 1297 verts.
Version 2.256.0
  • fix(coherence): l'état d'action réel survit à la compression du contexte (chantier #1 du brief cohérence des actions). BUG observé : Amélia JOUAIT à VoxeLibre (agent in-game actif, heartbeat frais) mais niait jouer sur Discord. Cause : la ligne « ⟦Activité en cours⟧ Tu es EN TRAIN de jouer… » (injectée en tête du contexte par _voxelibre_play_context) était JETÉE par agent._compress_context_for_prompt, qui ne préservait la tête que pour « [Fil public récent du salon » ; la compression garde la queue (messages récents) → l'état de jeu n'atteignait jamais le LLM. Fix : _PRIORITY_CONTEXT_PREFIXES inclut « ⟦Activité en cours » et « [Fil public récent du salon » ; preserve_first compte les lignes prioritaires en tête. 4 tests dédiés (test_activity_context_survives.py), 1295 verts.
Version 2.255.0
  • feat(self-projects): projets vécus « feuilleton » (chantier #F du brief Amélia humaine, DERNIER chantier). La progression des projets personnels était fixée par une éval LLM brute — pouvant RECULER ou SAUTER à 100 %. Désormais elle avance comme un feuilleton. Module pur project_progress.py : monotone_progress (jamais de recul, avance d'au plus max_step 0.25 et au moins min_step 0.03 « un pas par session »), format_milestone (jalon DATÉ court), should_announce_progress (anti-doublon). Câblage : _maybe_generate_self_project applique monotone_progress au lieu de l'éval brute + enregistre un jalon daté (inner_thought kind=project_milestone) quand il y a une vraie avancée (« hier j'ai avancé sur… »). Flag enable_project_feuilleton (défaut true). 6 tests dédiés, 1291 verts.
Version 2.254.0
  • feat(introspection): méta-mémoire de soi — récit de soi consolidé (chantier #E du brief Amélia humaine). Les pensées intérieures (reflection.py) restaient transitoires ; désormais un thème RÉCURRENT dans assez de pensées distinctes devient un « trait de soi » durable, illustré par une pensée RÉELLE (rien de fabriqué). Module pur self_consolidation.py : consolidate_self_observations (fréquence documentaire, seuil min_support=3 → anti-bruit) + render_self_narrative (ligne courte bornée). Distinct de self_identity.py (goûts/aversions) : ici des patterns COMPORTEMENTAUX observés. Câblage : _refresh_self_narrative (throttle ~3 h, kinds=autocritique+comprehension) → injecté en FOND dans style_hint (« ne pas l'énoncer, juste que ça colore ta façon d'être » — aligné LEAN « tu ne te décris pas »). Anti-dérive : ne réécrit pas l'identité. Flag enable_self_narrative (défaut true). 5 tests dédiés, 1285 verts.
Version 2.253.0
  • feat(anti-hallu): garde de VIVACITÉ — rester vive sans inventer (chantier #D du brief Amélia humaine). La sur-correction anti-fabrication retirait aussi une réaction LÉGITIME à un détail réel du fil (« ça m'a agacée, ce bug de rendu ») dès qu'aucun nom propre n'était cité → Amélia fuyante. Module pur anti_confabulation.py : content_terms + references_concrete_context (une phrase qui partage un mot plein avec le contexte est une VRAIE référence, pas une invention hors-sol). _scrub_invented_social_interactions gagne un paramètre context_terms (rétro-compatible) : garde la phrase si elle référence du concret réel, et compte le faux positif évité (_self_metrics['scrub_vividness_rescued']). Appelant : termes dérivés du snapshot, derrière enable_vividness_guard (défaut true). LEAN NON touché (aucune preuve live de ton fuyant — à ajuster si observé). 7 tests (pur + intégration), 1280 verts.
Version 2.252.0
  • feat(conversation): tempo humain — pause de « lecture/réflexion » avant une réponse texte (chantier #C du brief Amélia humaine). Amélia répondait de façon quasi instantanée (tell de machine) ; désormais une courte pause bornée précède la réponse. Module pur conversation_timing.py : reading_delay(received_len, rng) → [0.4, 4.0] s, croît avec la longueur du message reçu, variabilité déterministe sous rng ; inter_bubble_delay (utilitaire pour la couche streaming, bornes plus courtes). Câblage : _generate_user_response attend reading_delay(len(prompt)) sur la voie TEXTE seulement (la voie vocale garde son rythme), best-effort (aucun sleep si rng absent). Flag enable_human_reply_timing (défaut true). 5 tests dédiés, 1273 verts (2 échecs prompt_guard pré-existants).
Version 2.251.0
  • feat(introspection): l'introspection RÉGULE le comportement (chantier #B du brief Amélia humaine). Une auto-critique récente (reflection.py, angle « autocritique ») CONTRAINT désormais la réponse suivante au lieu de seulement l'espérer via un hint. Module pur introspection_regulation.py : derive_constraints (verbosité → cap 240 car ; « trop de questions »/répétition → coupe de question-réflexe), apply_length_cap (réduction bornée, plancher 140), strip_reflex_question (retire une relance interrogative finale courte quand ≥2 phrases — jamais un message qui n'est QUE la question, ni une vraie question de fond). Câblage : _refresh_introspection_regulation (throttle ~20 min, fetch kinds=autocritique), cap appliqué à length_target (voie réponse), coupe dans _finalize_response. Réversible : se lève dès que l'auto-critique ne se plaint plus. Flag enable_introspection_regulation (défaut true) + toggle console. 8 tests dédiés, 1268 verts (2 échecs prompt_guard pré-existants).
Version 2.250.0
  • feat(autonomy): abandon ASSUMÉ d'un objectif à soi (libre arbitre, chantier #A du brief Amélia humaine). Entre la relance (~7 j) et l'archivage silencieux (45 j), Amélia DÉCIDE de laisser tomber un objectif qu'elle s'était fixé SEULE et resté sans suite, et le DIT sobrement — sans s'excuser ni demander la permission (« le droit de se désengager »). N'agit JAMAIS sur l'objectif d'une personne. goals.py : SELF_AUTHORED_GOAL_SOURCES + is_self_authored + self_goals_to_abandon (pur, s'appuie sur stale_goals ; fenêtre goal_abandon_after_days, défaut 21 j). discord_bot : _maybe_propose_goal_abandon (dit → marque « abandoned » → throttle ~3 j par objectif), enregistré dans le pool scoré (VERT, public_gated, _NEEDS_OLLAMA, _PERSIST_ACTIONS). Flag enable_goal_abandon_proposal (défaut true) + toggle console web_ui. 3 tests goals dédiés, 1260 verts (2 échecs prompt_guard pré-existants, hors périmètre).
Version 2.249.0
  • fix(infra): musicgen.service durci contre la boucle de redémarrage — ConditionPathExists=/opt/musicgen-venv/bin/python (ne démarre plus DU TOUT si le venv est absent : cause historique des dizaines de milliers de restarts), StartLimitBurst=5 / StartLimitIntervalSec=300 (abandon après 5 échecs en 5 min), RestartSec 10→30. Nouveau scripts/install_musicgen.sh (recrée le venv : torch CUDA + audiocraft + aiohttp).
  • refactor(autonomy): retrait de la voie inline idle dans _autonomy_loop — le pool scoré (_maybe_scored_idle_tick) est désormais le SEUL décideur de l'initiative idle spontanée (validé en live depuis v1.55.0, défaut true depuis > 1 mois). Le flag enable_idle_in_scored_pool reste l'interrupteur on/off de l'idle. 18/18 tests d'autonomie verts.
  • docs: PROMPT_AMELIORATION_AMELIA_HUMAINE.md — audit du code réel + 6 chantiers priorisés (autonomie, introspection, fluidité « comme un humain »), prêt à lancer en /loop.
Version 2.248.0
  • perf(luanti): option A — client-pantin sans rendu. Le client Luanti rendait une scène 3D plein écran (llvmpipe/Mesa software) que personne ne regarde — toutes les actions passent déjà par le mod serveur. Rendu réduit au strict minimum : screen 320x240, viewing_range 8, undersampling 4, fps_max 4, Xvfb 320x240x16 (~13× moins de pixels). La box qui crashait fait tourner toute la stack (LLM+serveur+client+agent) à load 0.06.
Version 2.247.2
  • fix(context): injecte les FAITS réels d'un tiers nommé dans le contexte + gère le cas inconnu.
Version 2.247.1
  • fix(luanti): RestartSec 10→75 sur luanti-client.service — casse la boucle d'échec « Another client connected ».
Version 2.247.0
  • fix(prompt): recadre RESSENTI/PERSONNES — bloque l'invention, pas la référence légitime à quelqu'un du contexte.
Version 2.246.6
  • fix(memory): strip des balises [salon]/[perso] avant save_emotional_moment + scrub plus fin.
Version 2.246.5
  • fix(scrub): filtre post-génération des interactions sociales inventées.
Version 2.246.4
  • fix(prompt): interdire les interactions inventées à la source.
Version 2.246.3
  • fix(prompt): RESSENTI placé dans critical_tail — toujours livré au modèle.
Version 2.246.2
  • fix(prompt_guard+agent): troncature des réponses + règle RESSENTI renforcée.
Version 2.246.1
  • fix(prompt): RESSENTI — précision obligatoire ou silence total.
Version 2.246.0
  • feat(infra): retour sur .174 (GTX 1060) — LLM GPU via llama-adapter :11436.
Version 2.245.0
  • feat(infra+voice+voxelibre): architecture CPU-only Ryzen, prosodie selon l'humeur, construction réfléchie.
Version 2.244.0
  • style(web_ui): refonte ergonomique complète du panneau — 20 règles CSS ajoutées : H2 de contenu en monospace non-uppercase avec border-bottom cyan (vs hero H2 display/uppercase préservé) ; espacement section.panel+section.panel 18px ; listes ul/ol non-classées en cartes glass avec point cyan ; table-wrap border-radius + scrollbar ; select flèche SVG custom ; focus-visible néon cyan ; code inline hors chat ; metric.bad/warn border colorée ; HR/details harmonisés ; pill/tag hover lift ; scrollbar fine panels ; KPI 2 colonnes <460px ; table standalone arrondie + scrollable. v2.244.0.
Version 2.243.0
  • feat(web_ui): panneau web — santé mémoire complète : section 'Santé mémoire — importance & cycle' dans le dashboard admin (summaries, inner_thoughts, user_interests, WAL SQLite, distribution importance haute/moyenne/faible). Journal d'apprentissage (/appris) : colonne importance avec ▲/—/▼. Fiche utilisateur (/users/{id}) : section 'Centres d'intérêt' avec barre de score, badges importance sur profil et faits (▲ ≥0.7 vert, ▼ <0.5 rouge). v2.243.0.
Version 2.242.0
  • feat(voxelibre): construction réfléchie — build.py enrichi : plan_house (maison 5×5 : porte 2H côté regard, 3 FENÊTRES au niveau des yeux au centre des façades sans porte avec mur dessus/dessous, toit plein ; 68 blocs < 71 base bunker), find_build_site (choix d'un SITE praticable ≤3 nœuds : sol plein ≥92 %, volume dégagé ≥90 %, repli origin — corrige la cause historique des bases amorcées dans un talus/falaise), plan_center + bill_of_materials. planner : construire_base ancre le plan sur le site choisi (origine de dérive = l'ancre), MARCHE jusqu'au chantier avant de poser (_approach si >2 nœuds), puis bâtit la maison ; construire_abri (urgence nuit) reste sur place. 9 tests (test_voxelibre_build.py), 1260 verts. v2.242.0.
Version 2.241.0
  • feat(voice): prosodie selon l'humeur — mood_prosody(label, energy) dans mood.py (pur) : multiplicateurs Piper par label (fatiguée/triste → débit ×1.10-1.12 + vivacité ×0.85 ; vive/joueuse/joyeuse → ×0.93-0.94 + ×1.08-1.10 ; en colère → plus sèche), énergie module le débit à la marge (<0.3 → +4 %, >0.8 → −3 %), bornes dures [0.88-1.18]/[0.80-1.15] — la voix reste reconnaissable. noise_scale (timbre) jamais touché (artefacts). Wiring : VoiceAdapter.mood_provider injecté par le bot, lu à CHAQUE synthèse dans _synthesize (length_scale/noise_w multipliés), best-effort → prosodie de base au moindre souci. Réglage voice_mood_prosody (défaut true). 8 tests (test_voice_mood_prosody.py), 1252 verts. v2.241.0.
Version 2.240.0
  • feat(voxelibre): compte-rendu de fin de session — l'annonce de début promettait « je vous raconte après », la fin ne racontait rien. VoxelibrePlaySession accumule des highlights vécus (note_highlight, dédup, cap 6, ignorés hors session, vidés à l'arrêt) notés par le bot aux moments réels : mort (respawn détecté), activité entamée (changement d'objectif, libellés FR), découverte absorbée (bridge). _stop_message raconte premier + dernier moment (« Je pose ma pioche après 40 min : je me suis mise à bâtir un abri, et j'ai appris que les cactus font mal. ») ; sans rien de saillant, formules génériques conservées. Audit priorité #3 : le wiring Discord des sessions (annonces début/fin, pool scoré, arrêt nocturne, persistance, backoff, humeur, pont mémoire) était DÉJÀ complet — le rapport d'état était périmé ; cet incrément comble le seul manque réel (récit vide). 5 tests (test_voxelibre_session.py), 1243 verts. v2.240.0.
Version 2.239.0
  • feat(social): mirroring de style par personne — style_mirror.py (module pur) : profil EMA du style d'écriture de chaque interlocuteur (longueur, emojis, énergie « ! », argot SMS, multiligne), persisté en DB (user_style_profiles, blob JSON 1 ligne/user) + cache RAM write-through. Observation sur CHAQUE message entrant (on_message, avant la décision de réponse) ; injection dans style_hint d'une consigne de FORME au plus 3 traits distinctifs (« écrit très court — réponds court », « n'utilise jamais d'emojis — évite les emojis »…), avec garde-fou identité explicite : la forme seulement, jamais la personnalité (aligné sur « tu n'es pas un miroir » du prompt). Aucun hint sous 5 messages observés ni pour un profil moyen. Réglage enable_style_mirroring (défaut true). 8 tests (test_style_mirror.py), 1236 verts. v2.239.0. (2.238.0 réservé au durcissement self_coding de la session parallèle.)
Version 2.238.0
  • fix(memory): profile_items dédupliqué cross-canal — la clé de dédup incluait channel_id : le même trait était réinséré pour chaque salon (2436 lignes pour ~20 utilisateurs) puis TOUS les doublons remontaient dans le contexte LLM via unify_by_user au fetch. upsert_profile_item normalise channel_id→None par défaut (unify_by_user=True), fusion paresseuse des doublons historiques à l'écriture, + dedupe_profile_items() dans le cycle 6h (gardé par unify_user_memory) : garde la ligne la mieux notée (importance max, puis la plus récente), canal→NULL.
  • security(self_coding): 2 contournements de la whitelist d'imports fermés — ``import json, os`` ne validait que names[0], et un import DANS un corps de fonction (``def helper(args): import subprocess``) échappait totalement à la validation (la passe ast.walk ne testait pas Import/ImportFrom ; python -I ne bloque ni os ni subprocess). Whitelist désormais vérifiée dans ast.walk (autorité unique : tous les noms, toutes les profondeurs, import relatif interdit). 4 tests d'attaque ajoutés (test_self_coding_revalidation.py).
  • feat(context): lien Amélia↔personne visible pour CHAQUE personne connue — les lignes known_people portent un marqueur compact issu de la table relationships (« ton lien : proche/familier/connaissance, rapport chaleureux/tendu ») : quand elle parle DE quelqu'un elle module son ton, pas seulement avec l'interlocuteur courant. Rétrocompat : format inchangé sans relation. (social_clusters vide en prod : rien d'utilisable user↔user.)
  • decision(vram): ollama_num_ctx reste à 8192 — 0 OOM en 72h de logs, l'arbitre VRAM évince déjà le chat pendant un rendu, et 6144 n'économiserait que ~115-150 Mo de KV (qwen2.5-7b GQA) contre -25 % de fenêtre ; la dédup profile_items réduit déjà le prompt réel. Audit v2.235 (streaming texte) : câblage sain — replis sûrs, pas de double envoi ni double comptage anti-répétition, parité voie classique. v2.238.0.
Version 2.237.0
  • feat(maintenance): cycle social intel 6h — purge résumés (keep 50/canal, était 1778 lignes), inner_thoughts (keep 300, était 1573), faits auto-appris sur le bot (méta-confabulation 113 lignes), WAL checkpoint. Audit serveur : VRAM 80%, CPU Luanti 269%, profile_items 2434, backups obsolètes. MemoryStore : prune_old_summaries + prune_old_inner_thoughts + prune_self_facts + wal_checkpoint. v2.237.0.
Version 2.236.0
  • hardening(self_coding): run_helper_sandboxed revalide le code (AST strict) avant CHAQUE exécution (défaut revalidate=True) — couvre !helper_run, trigger naturel et initiative autonome qui exécutaient le code DB sans re-validation (seul !helper_test revalidait). Un helper accepté sous des règles anciennes ou une ligne DB altérée est refusé proprement (error='validation: …', enregistré comme échec de run → auto-improve). Audit priorité #5 : la protection complète existait déjà (AST whitelist + sandbox python -I timeout 5s + tests + anti-trivialité + approbation admin !helper_approve + self_coding_enabled défaut OFF) — l'audit V14 'sans validation' était faux ; cet incrément ferme le seul vrai trou (defense-in-depth). 4 tests, 1221 verts. v2.236.0.
Version 2.235.0
  • feat(discord): streaming TEXTE→Discord branché — les bulles partent PENDANT la génération, la 1re dès la 1re phrase validée (~2s au lieu d'attendre toute la réponse). _stream_text_reply miroir de la voie vocale : scrubs par chunk (_process_stream_voice_chunk), anti-répétition + heuristiques sur la TÊTE avec repli voie classique garanti tant que rien n'est envoyé ; échec en cours de flux → arrêt propre (le visible reste net). Bloc <<TOOL>> avant toute bulle → repli (boucle agentique classique) ; après → tool extrait et exécuté. Plafond salon public appliqué à la source. Éligibilité conservative : pas les tours vocaux, ni outil forcé (météo/actus/action/création), ni raisonnement profond. Réglage discord_stream_text (défaut true). _build_pre_thought factorisé (classique + streaming). 8 tests (test_text_stream.py), 1217 verts. v2.235.0.
Version 2.234.0
  • feat(context): intérêts durables utilisateur injectés dans le snapshot LLM — top 5 sujets (score>3/10) ajoutés en fin de profile_items → disponibles pour _filter_memory_by_relevance quand le sujet est évoqué. feat(memory): on_raw_reaction_remove — retrait d'une réaction → correction partielle (±0.05) de l'importance mémoire (moitié de l'effet initial). fix(web): _fetch_recent_learning_feed_sync ORDER BY importance DESC pour que le panneau web affiche les faits les plus valorisés en premier. v2.234.0.
Version 2.233.0
  • feat(memory): système de récompense mémoire par réactions emoji Discord — ❤️/👍/⭐ sur un message d'Amélia → +0.1 importance sur les faits/profil de l'utilisateur qui réagit ; 👎/💢 → -0.1. Décroissance lente (demi-vie 60 j, plancher 0.1) dans le cycle social intel (6 h). Les faits/profils les plus récompensés remontent en tête du contexte LLM (ORDER BY importance DESC). Migration non-destructive sur les tables existantes. _bot_msg_ids (cache roulant 300) pour identifier les messages du bot. v2.233.0.
Version 2.232.0
  • fix(prompt): RESSENTI & COHÉRENCE ajouté dans LEAN + SYSTEM_PROMPT — ne cite une personne dans 'j\'ai eu un échange tendu avec...' que si elle est dans le contexte visible ; sinon reste vague. Quand on demande de préciser une déclaration d\'Amélia : elle reconnaît que c\'est ELLE qui l\'a dite (pas son interlocuteur). v2.232.0.
Version 2.231.0
  • fix(sanitize): retire 'avec [vide]' quand suivi d\'une conjonction/ponctuation (confabulation de nom vide, ex: 'J\'ai eu un échange tendu avec et ça m\'a agacée'). fix(heuristics): bloque 'dans la conversation,' + 'tu dis avoir' (inversion sujet — LLM attribue à Yann ce qu\'Amélia a dit elle-même). v2.231.0.
Version 2.230.0
  • fix(meta): _FULL_META_RE → réponse entièrement méta ('Je suis Amélia. Ok, je suis prête.') retourne '' (fallback caller). _META_PREAMBLE2_RE strip 'Je suis Amélia.' en préambule. _STAGE_DIRECTION_RE retire didascalies parenthétiques ('(Réponse courte, sèche...'). _META_TAIL_RE étendu : 'Ok, je suis prête.' en fin de phrase. v2.230.0.
Version 2.229.0
  • fix(alias): regex '[Prénom] c\'est @mention' (motif avant) était raté — ajout motif BEFORE + (?i:...) inline pour éviter faux positifs. fix(alias): save_alias() persiste en user_aliases + search_users_by_name cherche user_aliases. Résultat : 'Géraldine c\'est @Tsukinosushi' désormais retenu et rappelé dans les conversations suivantes. v2.229.0.
Version 2.228.0
  • fix(reply): _should_reply normalise les accents pour matcher 'Amelia' → 'Amélia' (Géraldine: 'Coucou Amelia' ignoré corrigé). fix(alias): auto-présentation 'c\'est moi [prénom]' enregistre le prénom réel de l\'auteur même sans @mention (ex: 'c\'est moi Géraldine'). v2.228.0.
Version 2.227.0
  • fix(spam): persistance des cooldowns gouverneur pour les actions sociales (return_welcome, social_join, silent_user_checkin...) — évite la rafale de messages après redémarrage. fix(alias): détection '@mention c\'est/s\'appelle prénom' → enregistrement prénom réel + fallback learned_facts dans search_users_by_name (ex: @Tsukinosushi = Géraldine). fix(meta): _strip_meta_narration retire les fragments auto-référentiels en queue ('elle est encore incohérente'). v2.227.0.
Version 2.226.0
  • fix(agent): save_learned_fact source→source_message + retrait priority (TypeError CURIOSITY) ; SELF-PROJECT : hint 'projets déjà en cours' dans le prompt pour éviter les doublons LLM. v2.226.0.
Version 2.225.0
  • fix(web_research): Wikipedia 429 → None silencieux au lieu de raise (search + summary) ; _resolve_self_knowledge_gaps répond sans extrait si Wikipedia rate-limit. v2.225.0.
Version 2.224.0
  • fix(agent): save_goal() category kwarg inexistant retiré (TypeError SELF-PROJECT) ; délai 3s entre appels Wikipedia dans _resolve_self_knowledge_gaps (anti-429). v2.224.0.
Version 2.223.0
  • fix(agent): [SELF-PROJECT]/[CURIOSITY] erreurs silencieuses (debug→info) pour tracer les échecs LLM garbage pendant deep_work concurrent. v2.223.0.
Version 2.222.0
  • fix(agent): _observer_scope_channel_id — fallback wake_brief quand _last_active_channel_id=None après redémarrage nocturne (sleep work loop ne sautait plus). v2.222.0.
Version 2.221.0
  • perf(agent): features agentiques (CURIOSITY/SELF-PROJECT/ABSENCE/AGENDA) montées en tête du cycle privé — s'exécutent avant les cycles LLM per-user. v2.221.0.
Version 2.220.0
  • feat(agent): inner_thoughts injectées dans style_hint (fond émotionnel) — absence_reflection/project_reflection/self_evening colorent discrètement le ton. v2.220.0.
Version 2.219.0
  • fix(episodes): topic extraction sans filtre conversation_topics — _extract_topic_label avec map libre + fallback 4 mots. v2.219.0.
Version 2.218.0
  • feat(memory): épisodes enrichis — colonne summary (extrait échange) + récit vivant dans style_hint + ancrage absence. v2.218.0.
Version 2.217.0
  • feat(agent): conscience temporelle (jour/heure dans style_hint), knowledge_gap depuis projet perso, pensée d'absence ancrée dans dernier épisode. v2.217.0.
Version 2.216.0
  • feat(agent): agenda chargé au boot (_agenda_wake_context, 24h), réflexion intérieure sur projets persos (inner_thought project_reflection). v2.216.0.
Version 2.215.0
  • feat(panel): 4 nouveaux panneaux /agent — fil vécu (épisodes+ancres temporelles), agenda propre (schedule_events), projets persos (self_project), nœuds dangereux Luanti. /api/agent/live enrichi (episodes, agenda). v2.215.0.
Version 2.214.0
  • feat(agent): fil vécu complet — épisodes passent à limit=3 avec chronologie ('mardi soir — Luanti ; hier matin — code ; tout à l'heure — musique'). feat(prompt): agenda injecté dans style_hint (7j) pour allusions naturelles. feat(prompt): projets persos (self_project) injectés → Amélia peut y référer en conversation sans qu'on le lui demande. v2.214.0.
Version 2.213.0
  • feat(memory): temporalité vécue dans l'épisode — rappel enrichi avec jour+heure ('mardi soir', 'hier matin', 'la semaine dernière') au lieu de juste sujet/humeur. feat(agenda): schedule_events table + détection regex d'événements futurs (demain, jeudi, ce week-end…) dans les messages → stockés + rappel proactif aujourd'hui/demain via _maybe_mention_upcoming_events dans le cycle privé. v2.213.0.
Version 2.212.0
  • feat(social): relation aux absences — _maybe_think_of_absent_people() dans le cycle privé (1×/6h) : pour chaque contact familier absent > 3j, génère une pensée intérieure (inner_thought, kind='absence_reflection'). Si absence > 7j + familiarité ≥ 0.7 : contact proactif dans le salon (max 1×/7j par personne). v2.212.0.
Version 2.211.0
  • feat(identity): identité ferme sous pression — détection de patterns de substitution d'identité ('sois X', 'joue le rôle de', 'oublie qui tu es'…) → injection dans style_hint d'une ancre identitaire + valeurs affirmées (stage='affirme'/'tendance'). Amélia répond comme un humain sûr de lui : 'c'est pas trop mon truc', sans sermon. v2.211.0.
Version 2.210.0
  • feat(voxelibre): apprentissage par l'expérience — mort détecte les nœuds dangereux visibles (cactus, feu, magma…), les stocke en DB (dangerous_nodes) et met à jour le planner en temps réel (update_learned_hazards) pour que _blocked_ahead les évite. Plus besoin de patcher en dur : Amélia note elle-même 'j'évite les cactus'. v2.210.0.
Version 2.209.0
  • feat(agent): curiosité exécutée — _resolve_self_knowledge_gaps() résout les knowledge_gaps ouverts via Wikipedia + LLM, stocke comme learned_fact(source='self_curiosity'), marque answered. feat(agent): projets long terme auto-définis — _maybe_generate_self_project() crée/évalue des objectifs personnels (source='self_project', user_id='system') depuis drives+inner_thoughts. feat(memory): update_goal_progress(goal_id, progress) — mise à jour float 0-1 sur goals.progress. Les deux hooks branchés dans _run_private_learning_cycle. v2.209.0.
Version 2.208.0
  • feat(memory): mémoire épisodique — table `episodes` + store/fetch async. Continuité temporelle : détection d'absence > 4h injectée dans style_hint pour que le LLM accuse naturellement le retour ('ça faisait un moment'). Écriture d'épisode après chaque réponse (sujet, humeur, drive, émotion user). Lecture de la dernière séance injectée en contexte ('dernière fois: sujet X'). v2.208.0.
Version 2.207.0
  • feat(web): panel agent — drives (désirs internes) + Luanti heartbeat/journal + pensées en temps réel (polling JS /api/agent/live toutes les 10s). Nouveau endpoint GET /api/agent/live → {thoughts, drives, luanti}. Barres visuelles pour les 5 drives avec marqueur de baseline. État Luanti (tick/objectif/comportement/HP/position/pourquoi) + journal décisions. v2.207.0.
Version 2.206.0
  • fix(discord): filet musique — blacklist 'jouer à X' (jeu, pas musique). joue[rz]?\s+[àa] bloque le filet quand le verbe jouer est suivi de 'à'. Ajout luanti/minecraft/minetest/voxelibre dans la liste des jeux explicites. Corrige le faux positif 'Tu veux jouer à luanti' → play_music. v2.206.0.
Version 2.205.0
  • fix(voxelibre): cactus traité comme la lave — _is_hazard() bloque le déplacement vers/près des cactus (apply_walk + do_move) ; cactus inclus dans le scan d'environnement (payload lava) → planner les évite via _near_lava/_blocked_ahead. v2.205.0.
Version 2.204.0
  • feat(autonomy): raisonnement maximal — pré-pensée systématique avant chaque réponse directe (monologue interne pur Python : humeur, désir dominant, relation, signal). Seuil reasoning_complexity_threshold abaissé 0.65→0.35 : bien plus de messages déclenchent le raisonnement LLM. deep_reason_then_respond() raisonne TOUJOURS (plus de garde reasoning_model) via chain-of-thought sur le modèle principal si aucun modèle dédié. build_amelia_pre_thought() dans reasoning.py. suppress importé dans agent.py. v2.204.0.
Version 2.203.0
  • feat(reasoning): commande !introspect — Amélia raisonne en profondeur (deep_reason_then_respond). Filet de sécurité strip_think_blocks sur toutes les sorties de _generate_non_repetitive. Config : reasoning_model, reasoning_complexity_threshold, enable_deep_introspection. v2.203.0.
Version 2.202.0
  • feat(reasoning): module reasoning.py — strip_think_blocks(), needs_deep_reasoning(), complexity_score(), is_self_introspection(), build_introspection_prompt(). agent.py : deep_reason() + deep_reason_then_respond() (routing vers reasoning_model + trace injectée comme ancre). Adapté Discord : blocs <think> jamais envoyés. Dégradation gracieuse si reasoning_model absent. v2.202.0.
Version 2.201.0
  • feat(luanti): luanti-reset-world — réinitialise voxelibre_world.sqlite (sauvegarde → .bak, recrée le schéma vide) via !game luanti-reset-world. Exposé dans server_actions.py + config.py (voxelibre_world_db). v2.201.0.
Version 2.200.0
  • feat(autonomy): gate passif strict — _has_recent_human_activity(120 min) vérifie l'activité humaine avant toute initiative parlante dans _run_scored_proactive_pass. Initiatives silenciées si salon inactif >2h (voxelibre_play exempté). v2.200.0.
Version 2.199.0
  • feat(opinion): quand la requête est une demande d'opinion explicite (tu penses / ton avis / selon toi…), les croyances FACT/BELIEF en mémoire sur le sujet sont injectées comme ancre dans style_hint avant génération — avis ancré plutôt que vague. Fallback search_memory si aucune croyance. v2.199.0.
Version 2.198.0
  • feat(social): reconnaissance de noms propres non-@ dans les messages (detect_name_mentions dans social_intel.py + search_users_by_name dans memory.py). Quand un prénom connu est détecté → contexte injecté (familiarité + nb messages, sans données intimes). Prénom ambigu → consigne discrète pour demander confirmation. v2.198.0.
Version 2.197.0
  • fix(voxelibre): ActionType.WAIT (attendre) exemptée de tous les signaux de stuck (signal 1 stuck_count, signal 2 frozen_count, signal 3 confine_strikes). construire_abri terminé émettait attendre en boucle → stuck_count=24 → anti-blocage → stuck_loop(-10)/tick → mort → cycle infini. Fix : early-return dans StuckDetector.process() quand action.type ∈ _INTENTIONAL_REPEAT. v2.197.0.
Version 2.196.0
  • fix(voxelibre): portée_ticks unifié — angle mort dans le tunnel-dig (ancre >5b, 'creuser vers le filon') : l'else réinitialisait le compteur → stuck_count=24 libre → anti-blocage → stuck_loop. Fix : on traque la position DIG_BLOCK effective (pas l'ancre) quelle que soit la distance. Hard-skip du bloc bloquant ET de l'ancre ore inaccessible. v2.196.0.
Version 2.195.0
  • fix(voxelibre): seuil stuck_loop 20→35 ticks + valve respawn 100→70 — l'anti-blocage normal met 20-27 ticks pour traverser un mur de base ; penaliser à 20 créait stuck_loop sur des dégagements légitimes. Valve abaissée de 100 à 70 pour capturer les vraies boucles plus vite. v2.195.0.
Version 2.194.0
  • fix(voxelibre): mine-descend hard-skip ALL deep POIs en une passe — v2.192.0 ne skippait qu'un POI par déclenchement → cycle P1→skip→P2 →15t→skip→P3… Anti-stuck entre chaque. Fix : boucle sur TOUS les POI à y < player.y-1 et les ajoute tous en une fois à hard-skip. v2.194.0.
Version 2.193.0
  • fix(voxelibre): valve de sécurité anti-stuck infini — quand stuck_ticks >= 100 (≈50 s d'anti-blocage continu sans progrès), respawn d'urgence automatique via executor. Plafonne la perte à ≈-800/événement (vs score -9000+ en boucle infinie). v2.193.0.
Version 2.192.0
  • fix(voxelibre): mine-descend stuck_loop — anchor=None (hard-skipé) → _harvest_plan._nearest_block retourne une cible profonde différente → 'descendre vers le filon' génère le même bloc-sol indéfiniment → stuck_count=24 → stuck_loop. Fix : elif anchor is None dans portée_ticks détecte le DIG_BLOCK sol répété → identifie la cible profonde responsable via scan POI (y < player.y-1) non hard-skipée → hard-skip cette cible. v2.192.0.
Version 2.191.0
  • fix(voxelibre): hard-skip non propagé à _nearest_block interne — _harvest_plan appelle _nearest_block directement (voie 'descend') sans connaître _mine_hardskip → même bloc bloqué retourné → stuck_loop via 'descendre vers le filon'. Fix : _mine_hardskip_ctx context var propagé + vérifié dans _nearest_block, analogue à _mine_quarantine_ctx. v2.191.0.
Version 2.190.0
  • fix(voxelibre): prospecter hard-skip permanent — quarantaine 40t expire → même bloc inminable ré-ancré → stuck_loop infini. Fix : à la 2e quarantaine du même bloc (distance <2.5b), ajout à _mine_hardskip et exclusion permanente de _nearest_block pour toute la session. v2.190.0.
Version 2.189.0
  • fix(voxelibre): construire_abri stuck_loop — plan_shelter créé à y0=3 puis anti-stuck monte la joueuse à y0=4 ; le plan non régénéré (distance <6b) continue de cibler Y=3 (sous-sol) → place_block échoue silencieusement → même action à l'infini → stuck_loop(-10). Fix : régénère le plan aussi quand floor(pos.y) change (changement de niveau entier). v2.189.0.
Version 2.188.0
  • fix(voxelibre): anti-stuck rotation biaisée vers la cible + quarantaine TTL — après N cycles sans progrès, la rotation 90° choisissait le cap opposé à l'objectif (bug constaté : π → 3π/2 → 0.0 = +z alors que le fer est en -z). Fix 1 : `_tried_escape_yaws` trace les caps essayés ; la rotation choisit le cardinal non-essayé le plus aligné avec (target-pos) via dot product. Fix 2 : TTL réduit 120→40 ticks + quarantaine 40 ticks sur la position libérée pour éviter re-ancrage immédiat de la même cible stérile (ex. : seul filon fer visible mais unreachable = boucle infinie de TTL → re-ancrage → TTL → …). Pendant quarantaine : planner passe en explore/craft qui brise le cycle stuck_loop. v2.188.0.
Version 2.185.0
  • fix(voxelibre): construire_base déclenche sous terre — plan périmé (cibles à y+0..y+3 hors rayon scan → anti-stuck infini). Double fix : (1) trigger + abandon ajoutent not p.is_underground (bâtir une base sous terre n'a aucun sens) ; (2) Planner invalide et régénère le plan si distance à l'origine > 6 blocs (belt+suspenders). 1199 tests verts.
Version 2.184.0
  • fix(voxelibre): construire_abri boucle infinie (même bug que eclairer v2.183). Double fix : (1) success=`not p.is_night or not p.is_dark` (succès à l'aube même sans torche) ; (2) quand l'abri est bâti et sombre, le planner place une torche avant de passer en WAIT — is_dark flip immédiatement. 1199 tests verts.
Version 2.183.0
  • fix(voxelibre/mod): is_dark boucle infinie après pose de torche — get_node_light(pos) lisait la position PIEDS (bloc solide → lumière=0 même avec torche à y+1). Fix : lire à head_pos = pos + {y=1.5} (espace tête = même nœud que la torche posée par _plan_light) → lumière passe à 14 dès le tick suivant la pose, is_dark=False, goal eclairer ne re-fire plus en boucle.
Version 2.182.0
  • feat(voxelibre/mod): bootstrap auto sans kickstart — on_joinplayer détecte un inventaire vide à l'arrivée du joueur cible ; si aucun arbre dans un rayon 16, do_grove() plante une clairière ; do_give(bread×3) assure la nourriture initiale. Délai 2 s pour que l'inventaire soit chargé avant le comptage. Élimine le besoin d'un do_give/do_grove manuel au premier lancement.
Version 2.181.0
  • feat(voxelibre/goals+planner): goal fabriquer_arme (prio 46) — forge l'épée fer (ou mieux) dès que la pioche fer est acquise (iron_ingot≥2 + bâton + no sword). Garde pick_iron stricte pour ne pas perturber les états calmes (explorer/dormir). plan_next_craft descend la chaîne (planches→bâtons→sword_iron). 4 tests.
Version 2.180.0
  • feat(voxelibre/mod): minage progressif (do_dig à état) — _dig_ticks calcule la durée réelle via tool_capabilities/groupcaps (pierre pick-fer ~0.8s = 2 ticks) ; on joue le son de creusage à chaque tick, le son de cassage + remove_node seulement au coup final. Rend le minage visuellement naturel (plus instantané). Mod changé → redéployer worldmods.
Version 2.179.0
  • fix(discord_bot): _run_scored_proactive_pass — import saillance_for_category + ambient_social_boost protégés par try/except ImportError (fallback 1.0) : serveur Linux avec observer.py/social.py antérieurs ne crashaient plus la boucle d'autonomie toutes les minutes.
Version 2.178.0
  • fix(discord_bot): _apply_learning_from_text — save_learned_fact + upsert_profile_item + save_refuted_topic + save_goal sans try/except (exception DB coupe on_message entier, utilisateur sans réponse)
Version 2.177.0
  • fix(discord_bot): _tick_voxelibre_stop + _maybe_play_voxelibre — _send_text non protégé (exception propagée au suppress() parent, annonce de session jamais envoyée si API Discord KO)
Version 2.176.0
  • fix(discord_bot): _maybe_handle_channel_activation — cooldown assigné avant _send_text (si envoi échoue, salon marqué pending/cooldown pour 1h sans aucun message envoyé)
Version 2.175.0
  • fix(discord_bot): _record_user_input — save_message sans try/except (exception DB coupe le pipeline on_message et la voix)
Version 2.174.0
  • fix(discord_bot): _maybe_self_heal — _send_text + save_observation sans suppress (exception tue le tick autonomie)
  • fix(discord_bot): _notify_game_action — save_observation + upsert_profile_item + _send_text sans protection
  • fix(discord_bot): _handle_server_action_result + _handle_luanti_service_result — DB + send non protégés
Version 2.173.0
  • fix(discord_bot): _execute_tool_call — _send_text sans try/except (exception propagée + ghost message si envoi échoue)
Version 2.172.0
  • fix(discord_bot): ghost message dans _generate_user_response — condition de save basée sur 'spoken' réel plutôt que sur l'intention (speak_response and allow_voice_output)
  • fix(discord_bot): fallback vocal compte comme text_sent si envoi réussi (évite double-save)
Version 2.171.0
  • fix(discord_bot): _generate_user_response — _send_text principal et fallback vocal sans try/except (crash tâche asyncio si Discord API KO)
Version 2.170.0
  • fix(discord_bot): save_observation non protégé dans _maybe_time_sync_nag (exception propagée après envoi réussi)
  • fix(discord_bot): _send_text sans try/except dans _maybe_send_public_intro, _send_play_response, _discuss_server_snapshot (exception propagée au caller)
Version 2.169.0
  • fix(discord_bot): last_long_silence_date persisté/restauré depuis DB (doublon check-in silence long après restart)
Version 2.168.0
  • fix(discord_bot): try/except _send_text + suppress save_message dans _maybe_milestone_greeting / silent_checkin / memory_anniversary / mood_shift_comment / voxelibre_share / topic_decline / social_discovery (hardening réseau)
Version 2.167.0
  • fix(discord_bot): _maybe_goal_recall cooldown consommé avant envoi (perte de relance si Discord KO) + suppress save_message dans 8 fonctions autonomes (_maybe_goal_recall/share_opinion/open_loop_revival/project_checkin/event_recall/interest_recall/return_welcome/community_prompt/social_join/curiosity_outreach)
Version 2.166.0
  • fix(discord_bot): send avant save_message dans _run_autonomy_turn / _run_interest_turn / _run_interaction_turn / _send_play_response / _discuss_server_snapshot / _handle_voice_transcript / _handle_observation (7 fonctions — messages fantômes DB)
Version 2.165.0
  • fix(discord_bot): _maybe_run_autonomous_helper envoie avant de sauvegarder le message (évite les messages fantômes en DB si Discord échoue)
Version 2.164.0
  • fix(discord_bot): restaurer _last_self_brief_at / _last_web_curiosity_at / _last_weekly_evolution_at depuis DB (blocage silencieux si uptime < cooldown)
Version 2.163.0
  • fix(discord_bot): _last_character_introspection_at (7j) persisté/restauré depuis DB (évite doublon nudge admin après restart)
Version 2.162.0
  • fix(discord_bot): _last_auto_goal_at persisté/restauré depuis DB (évite doublons d'objectifs après restart)
Version 2.161.0
  • fix(discord_bot): _maybe_time_sync_nag cooldown après envoi + try/except _send_text
Version 2.160.0
  • fix(discord_bot): _tick_voxelibre_stop avant garde-sommeil — session plus jamais bloquée la nuit
Version 2.159.0
  • fix(discord_bot): self_brief/web_curiosity/weekly_evolution — timestamp cooldown après envoi + try/except _send_text
Version 2.158.0
  • fix(discord_bot): mood_shift_comment + goal_recall ajoutés à _PERSIST_ACTIONS + restore in-memory 24h au restart
Version 2.157.0
  • fix(discord_bot): _maybe_memory_anniversary return False→continue sur échec LLM (ne bloque plus les autres utilisateurs éligibles)
Version 2.156.0
  • fix(discord_bot): morning_greeting_date + evening_greeting_date persistés/restaurés depuis DB (évite doublon bonjour/soir si restart dans la même heure)
Version 2.155.0
  • fix(discord_bot): self_brief/web_curiosity/weekly_evolution ajoutés à _PERSIST_ACTIONS + restore loop (évite doublon jour/semaine après restart)
Version 2.154.0
  • fix(discord_bot): _maybe_auto_goal cooldown consommé seulement si save_goal réussit (évite 24h de délai inutile sur erreur DB)
Version 2.153.0
  • fix(discord_bot): share_opinion governor + _last_share_opinion_at persistés au redémarrage (share_opinion_fired_at DB key)
Version 2.152.0
  • fix(discord_bot): cooldown _maybe_web_curiosity/_maybe_self_brief/_maybe_weekly_evolution/_maybe_goal_recall consommé seulement après envoi réel
Version 2.151.0
  • fix(discord_bot): filet code _strip_context_annotations → supprime [salon]/[perso]/[Fil public] en sortie (+ 3 tests)
  • fix(discord_bot): voxelibre_share governor persisté au redémarrage (voxelibre_share_fired_at)
  • fix(discord_bot): _maybe_memory_anniversary — vérifie DB (45j) au démarrage pour éviter doublons d'anniversaire après restart
Version 2.150.0
  • fix(personality): priorité désambiguïsation sujet (contexte > grammatical > mémoire récente > long terme) dans les deux prompts
Version 2.149.0
  • fix(prompt): règle balises internes [salon]/[perso] (v2.149.0). Le modèle (Qwen 2.5 7B) écrivait littéralement « [salon] » dans ses réponses (ex: « j'ai eu des échanges avec [salon] »). Ajout dans les deux prompts (LEAN + complet) d'une règle explicite : les marqueurs `[perso]`, `[salon]`, `[Fil public]`, `(il y a Xmin)` sont internes et ne doivent jamais apparaître dans une réponse. Seuil test LEAN relevé de 2700→2800.
Version 2.148.0
  • fix(bot): trois correctifs VoxeLibre mood sync (v2.148.0). (1) _ACHIEVEMENT_GOALS utilisait des noms anglais (collect_wood…) au lieu du français (recolter_bois…) → le déclencheur de joie ne tirait jamais. (2) HP critique appelait on_silence_detected() (social) au lieu de feel('inquiète', 0.3) (jeu). (3) sessions_today non persisté → le plafond quotidien (daily_cap=4) se réinitialisait à chaque redémarrage du bot ; ajout de la clé DB voxelibre_sessions_today et restauration dans _restore_voxelibre_play_state.
Version 2.147.0
  • fix(bot): diagnostic VoxeLibre heartbeat faux positif (v2.147.0). Le diagnostic réduisait expression toutes les 5 min même quand voxelibre était intentionnellement arrêté (session.playing=False) : le fichier amelia_status.json conserve active=true depuis la dernière session. Fix : gate sur session.playing — le check heartbeat ne s'active que si le bot a lancé une session active.
Version 2.146.0
  • fix(bot): TypeError datetime naive/aware dans share_opinion, voxelibre_share, topic_decline (v2.146.0). _parse_iso_datetime_safe renvoyait une datetime naïve ; comparée à un cutoff UTC-aware (offset-aware) → TypeError bloquant la boucle d'autonomie toutes les minutes. Fix : helper _parse_iso_utc_safe qui garantit timezone=UTC ; utilisé dans _maybe_share_opinion, _maybe_voxelibre_share, _maybe_topic_decline. Testé : 1192 verts.
Version 2.145.0
  • fix(voxelibre): LLMBrain circuit-breaker (v2.145.0). Après 5 timeouts consécutifs (Ollama indisponible), le cerveau LLM se met en pause 60s au lieu de bloquer chaque tick 8s → agent ~2× plus réactif quand Ollama est down. Compteur remis à zéro à la première réponse réussie. Testé : 1192 verts.
Version 2.144.0
  • fix(autonomy): AutonomyGovernor + VoxelibrePlaySession persistence (v2.144.0). Après redémarrage bot, gouverneur in-memory oubliait les cooldowns → voxelibre_play refirerait en 2 min au lieu de respecter le gap 75 min. Fix : persist voxelibre_play_fired_at + voxelibre_session_ended_at dans agent_settings ; restauration dans on_ready via _restore_voxelibre_play_state (monotonic reconstruit depuis epoch).
Version 2.143.0
  • fix(voxelibre/goals): recolter_fer/charbon — abandon si outil inadéquat (v2.143.0). Quand le LLM force recolter_fer sans pioche pierre+, le goal persistait indéfiniment (pas de prédicat abandon) → boucle infinie anti-stuck sur blocs incastables 400+ ticks. Fix: abandon=lambda p,w: not _can_mine_iron(w) libère le forçage LLM, laisse recolter_pierre prendre le relais pour obtenir la pioche adéquate. fix(discord_bot): _adjust_drives_for_subsystem_health utilise satisfy() au lieu d'assignment direct → plancher baseline protégé pour expression.
Version 2.142.0
  • fix(self_coding): boucle infinie improve quand last_error=refus humain (v2.142.0). Quand Yann rejette (❌) une proposition d'amélioration, record_self_helper_run incrémente failure_count → _run_self_coding_session voit failure_count≥1 → retente improve → nouveau refus. Fix: si last_error contient 'refusé par', skip la session (respecter le refus humain). Fix _strip_code_fences: gère le format mono-ligne (```python code```sans newline). DB: failure_count remis à 0, 14 proposals stales rejetés pour parse_multiline_kv.
Version 2.141.0
  • fix(autonomy): morning_greeting supprimé si voxelibre_play vient de se lancer (v2.141.0). Quand voxelibre_play tire au tick 1 de l'heure de bonjour, le message 'Je file jouer' tient lieu de check-in matinal. Sans ce fix, morning_greeting tirait au tick 2 et le LLM mentionnait 'jeu' → violation topic_refusal → régénération inutile. last_morning_date est marqué dans les deux cas pour ne pas retenter après la session.
Version 2.140.0
  • fix(drives): satisfy() ne descend plus sous la baseline (v2.140.0). Appeler satisfy() sur un désir déjà sous sa base l'aggravait (ex. expression=0.325 → 0.234 après voxelibre_play). Désormais satisfy() plafonne au minimum à la baseline. Le retour naturel vers la base reste géré par le decay (demi-vie 18h). Fix voxelibre_play : satisfaction repos+curiosite+croissance (loisir/exploration/apprentissage) au lieu de croissance+curiosite+expression (expression monte via voxelibre_share, pas au lancement).
Version 2.139.0
  • fix(autonomous_actions): voxelibre_play speaks=False (v2.139.0). Avec repos=0.90 (excess=0.50), la prudence_delta montait à +0.29 → prudence effective 0.64 → seuil 0.549. Simultanément, la pénalité repos réduisait le score à 0.44 < seuil. voxelibre_play n'est pas une prise de parole sociale : elle lance une session solo et laisse une note. speaks=False retire les pénalités parlées (repos/tempo/prudence) ; le cooldown gouverneur 75 min reste la contrainte de fréquence (plus restrictif que le gap global 8 min).
Version 2.138.0
  • fix(self_coding): strip markdown code fences dans parse_helper_response et evaluate_candidate (v2.138.0). Le LLM insérait des ```python ... ``` dans le champ 'code' du JSON → validate_helper_code échouait avec 'Syntaxe invalide' → boucle infinie de réparation sur parse_multiline_kv et tout helper réparé. Deux correctifs : (1) parse_helper_response strip les fences au moment de l'extraction (source primaire) ; (2) evaluate_candidate strip en étape 0 (défense en profondeur).
Version 2.137.0
  • voxelibre_play public_gated False (v2.137.0) : la throttle anti-spam 30 min du salon public bloquait voxelibre_play quand idle_initiative monopolisait le slot — le cooldown gouverneur 75 min est plus restrictif et suffit. Fix pool : voxelibre_play peut maintenant se déclencher dès le gap de 8 min expiré, indépendamment du throttle public.
Version 2.136.0
  • Pool scoring : idle_pressure boost (+25%) pour actions parlées + voxelibre_play priority 2→3 (v2.136.0). Les initiatives parlées scorées sous le seuil 0.45 avec affect neutre peuvent maintenant passer après ~2 min de silence (idle_pressure=0.1). Fix WARNING agent.py : log 'mémoire partiellement perdues' uniquement si prompt réellement élagué (pas si seul num_predict réduit). action_risk.py : share_opinion + interest_recall ajoutés explicitement (VERT).
Version 2.135.0
  • _NEEDS_OLLAMA final : social_join + discovery + interest_recall ajoutés (v2.135.0). Ces trois initiatives (tick-handlers sans channel_id) appellent toutes _generate_non_repetitive mais étaient absentes du filtre Ollama-KO → consumaient cooldown quand Ollama est KO.
Version 2.134.0
  • Fix pool : silent_user_checkin enable_key corrigé + _NEEDS_OLLAMA complet (v2.134.0). ActionSpec silent_user_checkin utilisait enable_curiosity_outreach mais le handler vérifiait enable_silent_checkin (non défini → False) → jamais exécuté. Correction : enable_key→enable_silent_checkin + config serveur enable_silent_checkin=True. _NEEDS_OLLAMA enrichi : event_recall, return_welcome, milestone_greeting, community_prompt (tous appellent _generate_non_repetitive mais étaient absents du garde-fou Ollama-KO).
Version 2.133.0
  • Fix pool : voxelibre_share + topic_decline ajoutés à _NEEDS_OLLAMA (v2.133.0). Ces deux initiatives appellent _generate_non_repetitive mais n'étaient pas protégées par le filtre Ollama-KO → elles tentaient une génération vouée à l'échec et consumaient leur cooldown inutilement.
Version 2.132.0
  • Fix config : enable_* manquants → initiatives ne firent jamais (v2.132.0). _behavior_enabled(enable_key, False) retournait False pour tout flag absent de config.json. Ajout dans config.json serveur : enable_voxelibre_play, enable_voxelibre_share, enable_share_opinion, enable_topic_decline, enable_self_brief, enable_auto_goals, enable_character_evolution (tous True) + enable_orange_autonomy=False (garde-fou ORANGE).
Version 2.131.0
  • Fix : _maybe_voxelibre_share — filtre temporel 90 min manquant + init __init__ (v2.131.0). Même bogue que v2.130 : le timestamp n'était pas vérifié pour voxelibre_share. _shared_voxelibre_subjects initialisé dans __init__ (plus de getattr/hasattr). Suppression du pattern set() de fallback dans la lecture.
Version 2.130.0
  • Fix : _maybe_share_opinion — filtre temporel 90 min manquant (v2.130.0). Le commentaire disait ≤90 min d'activité humaine mais le code ne vérifiait aucun timestamp → Amélia pouvait partager une opinion dans un salon inactif depuis des heures. Ajout de _cutoff_90 avec _parse_iso_datetime_safe.
Version 2.129.0
  • Stabilité prod : déploiement complet de tous les modules manquants sur le serveur (observer.py, social.py, action_choice.py, initiative_learning.py, memory.py, etc.) résolvant les ImportError saillance_for_category + ambient_social_boost. config.json serveur : ollama_num_ctx 4096 → 8192 (élimine le débordement ~2905 tok). amelia_voxelibre.py : _prev_village_count/_prev_mine_count initialisés dans __init__ (supprime le getattr dynamique avec défaut implicite).
Version 2.128.0
  • Autonomie : partage Discord des découvertes VoxeLibre + correctifs (v2.128.0). autonomous_actions.py : ActionSpec voxelibre_share (social, cooldown 4h, cap 2/j). action_risk.py : voxelibre_share VERT + stop_music dans _AGENT_LOOP_EFFECT_TOOLS. discord_bot.py : _maybe_voxelibre_share() — lit les croyances voxelibre:* scope=world, génère un partage naturel en 1-2 phrases ; anti-redite par _shared_voxelibre_subjects. version.py : Any importé, _version_gt annoté Any (résout test undefined-name).
Version 2.127.0
  • Autonomie : pont Luanti ↔ mémoire Discord (v2.127.0). voxelibre/discord_bridge.py : DiscordBridge lit les croyances FACT (scope=self) et objectifs Discord actifs (cache 120s) + consigne les découvertes dans luanti_discoveries.jsonl. amelia_voxelibre.py : ServiceConfig.discord_db_path/discoveries_path ; AmeliaVoxelService._discord_bridge injecté ; _memory_summary() inclut le contexte Discord ; découvertes (village, mine, dimension, mort) écrites dans le JSONL. discord_bot.py : _absorb_luanti_discoveries() lit le JSONL à chaque tick de boucle d'autonomie et convertit en croyances monde (scope=world).
Version 2.126.0
  • Autonomie : outils à effet de bord dans la boucle agentique (v2.126.0). discord_bot.py : _AGENT_LOOP_EFFECT_TOOLS = frozenset({'play_music','add_reminder'}) ; _extract_effect_tool() surface uniquement ces outils ; _run_agentic_effect_loop() (max_steps=1) exécute l'action, applique risk tier + cooldown musique, réinjecte le résultat pour que le modèle synthétise dans sa voix. Routage elif _AGENT_LOOP_EFFECT_TOOLS entre la branche knowledge loop et le fallback classique → play_music et add_reminder ne recrachent plus un feedback brut à côté du message.
Version 2.125.0
  • Autonomie : boucle erreurs — Outcome.STRONG_NEGATIVE (v2.125.0). initiative_learning.py : Outcome.STRONG_NEGATIVE + _STRONG_NEGATIVE_WEIGHT=2.0 ; OutcomeStats.strong_negative field ; effective_negative inclut le poids double. Résultat : 1 hostilité → mult 0.625 (vs 0.750 avant) ; 2 hostilités → 0.500 — descente 2× plus rapide que NEGATIVE ordinaire. memory.py : persist/restore inclut strong_negative. discord_bot.py : is_strong_refusal()/detect_disrespect() → STRONG_NEGATIVE (était NEGATIVE). _restore_initiative_outcomes : restaure le champ.
Version 2.124.0
  • Autonomie : modèle social par personne dans le pool scoré (v2.124.0). social.py : ambient_social_boost(rels_for_active_users, category) → multiplicateur [0.80,1.20] selon familiarité moyenne des users actifs du salon (catégories social/community uniquement). _run_scored_proactive_pass() : charge les relations des users actifs + calcule boost combiné observer_saillance × ambient_social_boost pour chaque ProactiveCandidate. _maybe_curiosity_outreach() : injecte _familiarity_tone_instruction() dans extra_instructions (ton adapté à la familiarité de la cible).
Version 2.123.0
  • Autonomie : persistance croyances établies cross-session (v2.123.0). _compute_wake_briefs() : charge les FAITS (scope self/topic/world) depuis la DB et construit _belief_wake_context (max 6 lignes [FAIT/scope]). _build_memory_snapshot() : injecte le bloc croyances en tête de summaries pendant 6h post-reboot (même fenêtre que le wake brief). Également : _open_incoherences initialisé depuis la DB au boot (plus d'attente du 1er cycle social_intel). Résultat : les connaissances établies pré-reboot sont disponibles dès le 1er prompt.
Version 2.122.0
  • Autonomie : initiative 'topic_decline' — droit de désengagement spontané (v2.122.0). autonomous_actions.ACTION_REGISTRY : ActionSpec topic_decline (catégorie self, priorité 4, cooldown 2h, 3×/j). action_risk : VERT. discord_bot._maybe_topic_decline() : si ≥2 messages humains des 15 dernières minutes mentionnent un sujet refusé (fetch_refuted_topics + _response_mentions_refuted_topic), génère un désengagement court et ferme (1 phrase). Ajouté dans _run_scored_proactive_pass table (public_gated=True, scored_pool=True).
Version 2.121.0
  • Autonomie : saillance dynamique observer→pool scoré (v2.121.0). observer.py : saillance_for_category() traduit l'activité observée (jeu détecté → social+20%/community+15% ; outil dev → curiosity+20%/social-20%) en multiplicateur par catégorie d'action. action_choice.ProactiveCandidate : nouveau champ observer_boost (distinct du reinforcement long-terme). score_candidate() : applique observer_boost borné [0.5,1.5] après le renforcement. discord_bot._run_scored_proactive_pass() : injecte l'observer_boost calculé pour chaque candidate selon l'event courant (Linux/serveur : observer désactivé → boost=1.0, aucun effet).
Version 2.120.0
  • Personnalité : impératifs nous spécialisés + transitions pré-réponse + clôtures section (v2.120.0). formal_teacher : 'décortiquons', 'penchons-nous sur', 'intéressons-nous à', 'approfondissons', 'abordons maintenant/la question/le sujet' — impératifs analyse cousins de examinons/analysons/considérons déjà bloqués. meta_openers : 'avant d'aller plus loin,/:', 'avant de poursuivre,/:', 'avant d'entrer dans le vif' — variantes transition pré-réponse non couvertes. essay_markers : 'terminons par/avec', 'concluons sur/avec' — clôtures de section évitant les formes déjà bloquées. 16 nouvelles entrées.
Version 2.119.0
  • Personnalité : annonces méta conditionnelles + décharge légaliste + responsabilité formelle (v2.119.0). meta_openers : 'je souhaite/voudrais préciser/souligner/mentionner que' — variantes non couvertes de la série je-souhaite-apporter. professional_disclaimer : 'ne saurait remplacer', 'ne saurait se substituer à', 'ne peut se substituer à un' — formule de décharge légaliste typique IA. formal_teacher : 'j'attire votre attention' (vouvoiement manquant), 'il vous/t'/m'appartient de' — registre légal/administratif jamais oral. 13 nouvelles entrées.
Version 2.118.0
  • Personnalité : modalisateurs académiques + clarté performative + emblée (v2.118.0). essay_markers : 'dans une certaine/large mesure,', 'pour ainsi dire,/:', 'en quelque sorte,/:', 'de fait,/:', 'comme chacun sait,/le sait,', 'tout le monde sait que' — hedges et openers condescendants jamais dans un vrai chat. honesty_theater : 'pour être clair,/claire,/avec toi/vous' — variante manquante de la série pour-être-honnête/direct/franche. meta_openers : 'voici donc', 'voilà donc' — variante pivot non couverte. formal_teacher : 'd'emblée,/:' — adverbe formel/littéraire, registre essai. 19 nouvelles entrées.
Version 2.117.0
  • Personnalité : éditorial collectif + impersonnel affirmatif + espoir éclaircissement (v2.117.0). formal_teacher : 'précisons que', 'ajoutons que', 'observons que', 'retenons que', 'constatons que', 'reconnaissons que' — impératifs éditoriaux « nous » manquants, formes sœurs de notons/soulignons/remarquons déjà bloqués. essay_markers : 'on peut dire/affirmer que', 'on peut donc dire/conclure', 'on peut s'interroger sur' — académique impersonnel affirmatif non couvert. sav_closings : 'j'espère que ça éclaire', 'j'espère avoir su t'éclairer' — variantes d'éclaircissement manquantes. 13 nouvelles entrées.
Version 2.116.0
  • Personnalité : cadrage situationnel + urgence administrative (v2.116.0). formal_teacher : 'il importe de/que' (équivalent soutenu de 'il faut', encore plus formel), 'de prime abord,/:' (registre écrit jamais oral). essay_markers : 'c'est dans ce contexte que', 'face à cette situation/ce constat,', 'si l'on y réfléchit bien,', 'à bien y réfléchir,', 'à y regarder de plus près,', 'partant de ce constat,/:' — cadrages situationnels et pseudo-réflexions académiques, toujours document jamais chat. 12 nouvelles entrées.
Version 2.115.0
  • Personnalité : reformulations demande + accord-pivot + transparence obligatoire (v2.115.0). question_recap : variantes 'demande' (ta demande porte sur, pour répondre à ta/cette demande, si je saisis bien ce que tu me demandes) + 'pour/revenons à ta question/sujet'. agreement_hedge : 'je vois ce que tu dis, mais', 'je comprends ce que tu veux dire, mais' — formes non couvertes du pattern validation+pivot. honesty_theater : 'je dois être transparent/e', 'je me dois d'être honnête/franche/franc' — obligation réflexive de transparence. 20 nouvelles entrées.
Version 2.114.0
  • Personnalité : futur proche + diagnostic empathique (v2.114.0). honesty_theater : 'je vais être honnête/franche/direct/directe/franc' — futur proche manquant du bloc 'je serai' (bloqué v2.112, même pattern, tense différente). empathy_theater : 'tu sembles frustré/e/inquiet/e/perdu/e/stressé/e/blessé/e/déçu/e/confus/e', 'tu sembles avoir', 'tu sembles être', 'on dirait que tu es/te' — diagnostics émotionnels depuis un surplomb, pattern thérapeute jamais naturel entre pairs. 22 nouvelles entrées.
Version 2.113.0
  • Personnalité : nuances performatives + transitions pédagogiques (v2.113.0). complexity_dodge : 'la réponse est nuancée', 'la réalité est plus nuancée/complexe', 'les choses sont plus complexes', 'c'est plus nuancé que ça', 'c'est plus complexe qu'il n'y paraît/parait', 'il n'y a pas de réponse/vérité universelle', 'il n'existe pas de réponse parfaite' — esquives par nuance performative. essay_markers : 'en bref,/:', 'en un mot,/:', 'passons maintenant à', 'passons à présent à', 'venons-en à' — marqueurs résumé et transitions pédagogiques. 17 nouvelles entrées.
Version 2.112.0
  • Personnalité : honnêteté déclarative + révélation théâtrale (v2.112.0). honesty_theater : 'pour être direct/e (avec toi/vous)', 'soyons transparents/es (,/:)', 'je serai honnête/franche/direct/e (avec toi/vous)', 'il faut que je te/vous dise', 'je dois te dire que', 'j'ai besoin de te dire' — annonces de franchise performatives jamais naturelles entre pairs. 24 nouvelles entrées.
Version 2.111.0
  • Autonomie : élagage préventif du milieu du prompt (v2.111.0). prompt_guard.py : fit_prompt_and_output() + trim_middle() + FitPlan — sacrifie le milieu (vieux contexte) plutôt que de laisser Ollama tronquer la tête (identité + règles). Câblé dans _complete_prompt_inner() de agent.py : quand le budget déborde, on élague avant envoi. 5 nouveaux tests prompt_guard.
Version 2.110.0
  • Personnalité : validation question/garantie + impératifs pédagogiques subjonctif (v2.110.0). coach_phrases : 'super/belle question', 'c'est une belle question', 'bonne initiative', 'je vous assure que', 'je te/vous garantis que', 'je peux t'assurer/vous assurer/te garantir/vous garantir que' — garanties de qualité SAV. essay_markers : 'sache que' — tutoiement de 'sachez que' (déjà bloqué). formal_teacher : 'il est important/essentiel/nécessaire/indispensable/crucial/primordial/fondamental/impératif que' — formes que+subjonctif des prescriptions de + inf déjà bloquées. 20 nouvelles entrées.
Version 2.109.0
  • Personnalité : aveux théâtraux + empathie active non couverts (v2.109.0). honesty_theater : 'j'avoue que c'est/j'ai/je/ça', 'je t'avoue que', 'je vous avoue que', 'je dois t'avouer/vous avouer que', 'je ne vais pas te/vous mentir', 'je vais pas te mentir' — préfaces d'honnêteté performatives jamais naturelles entre pairs. empathy_theater : 'j'entends que tu/ta', 'j'entends bien que' — empathie active psychologue ; 'ça doit être épuisant/pesant/lourd/frustrant/douloureux/éprouvant' — adjectifs non couverts du pattern ça-doit-être. 19 nouvelles entrées.
Version 2.108.0
  • Personnalité : locutions formelles de contexte + disponibilité SAV pronominale (v2.108.0). formal_teacher : 'étant donné que/les/la', 'compte tenu de', 'au vu de', 'au regard de', 'sachant que' — registre rapport/exposé jamais en chat Discord. sav_closings : 'je me tiens disponible/à ta disposition/à votre disposition/à disposition' — variantes pronominales de 'je reste disponible' (déjà bloqué). 11 nouvelles entrées.
Version 2.107.0
  • Personnalité : il-est-nécessaire/indispensable, il-est-important-de-X, impersonnels académiques, je-vous-laisse (v2.107.0). formal_teacher : 'il est nécessaire/indispensable de' + 'il est important de savoir/retenir/rappeler'. essay_markers : 'on soulignera/précisera/mentionnera que' + 'il n'est pas rare de'. sav_closings : vouvoiement 'je vous laisse réfléchir/explorer/découvrir/consulter/chercher/juge/apprécier/imaginer/avec ça'. 18 nouvelles entrées.
Version 2.106.0
  • Personnalité : j'aimerais ajouter, je tiens à vous X, n'oublie/oubliez pas que, en guise de (v2.106.0). meta_openers : 'j'aimerais ajouter que/j'aimerais ajouter ' — sibling manquant du bloc j'aimerais-X. formal_teacher : vouvoiement 'je tiens à vous préciser/souligner/rappeler/mentionner/signaler/indiquer' (tutoiement déjà bloqué). essay_markers : 'n'oublie pas que/n'oubliez pas que/il ne faut pas oublier que' + 'en guise de conclusion/résumé/récapitulatif'. 14 nouvelles entrées.
Version 2.105.0
  • Personnalité : vouvoiement manquant dans honesty_theater + satisfaction_check (v2.105.0). honesty_theater : 'pour être honnête/franc/franche avec vous' (tutoiement déjà bloqué, vouvoiement oublié). satisfaction_check : variantes vouvoiement des offres d'approfondissement (si vous voulez/souhaitez + de détails/aller plus loin/en savoir plus/approfondir), formes conditionnelles SAV (au cas où vous voudriez/souhaiteriez, dans le cas où), dis-moi si vous avez besoin/des questions. 17 nouvelles entrées.
Version 2.104.0
  • Personnalité : références techniques internes, stalling sujet délicat, marqueurs de liste formels (v2.104.0). ai_disclosure : 'mon algorithme/mes paramètres/mon fonctionnement/ma programmation/mon traitement' — Amélia se décrit comme un système IA, rompt le personnage. coach_phrases : 'c'est un sujet délicat/sensible/difficile', 'c'est une question délicate/difficile' — variantes stalling manquantes (seules complexe/intéressante étaient bloquées). essay_markers : 'en premier/second/dernier lieu,' — structure formelle de dissertation jamais entendue dans un chat.
Version 2.103.0
  • Personnalité : empathie vouvoiement, inquiétude/préoccupation, cher-utilisateur, je-dois-X-que (v2.103.0) — 20 nouvelles entrées. empathy_theater : vouvoiement des formes tutoiement déjà bloquées ('je comprends votre frustration/peine/déception/colère/tristesse') + variantes inquiétude/préoccupation/anxiété (tutoiement + vouvoiement) + 'je suis sensible à ta/votre'. ai_disclosure : apostrophe formelle 'cher utilisateur/chère utilisatrice' (l'IA adresse l'interlocuteur comme un utilisateur). meta_openers : 'je dois mentionner/préciser/souligner/signaler que' — cousin obligatif de 'j'aimerais X que' (annonce méta-discursive).
Version 2.102.0
  • Personnalité : équilibrisme avantages/inconvénients, auto-références, vérification compréhension (v2.102.0) — 22 nouvelles entrées. agreement_hedge : présentation équilibriste 'avantages et inconvénients/le pour et le contre/peser le pour et le contre/les avis sont partagés' (Amélia a des opinions, elle ne fait pas la balance). essay_markers : auto-référentiels à l'imparfait 'comme je le mentionnais/l'expliquais/te le disais' + références documentaires 'comme nous l'avons vu/indiqué plus haut/ci-dessus/vu précédemment'. sav_closings : vérification de compréhension 'est-ce que ça/cela répond à', 'ai-je répondu à ta/votre question', 'ai-je bien répondu', 'j'espère avoir été assez clair(e)'.
Version 2.101.0
  • Personnalité : impératifs nous-pédagogiques, parmi-les, connecteurs très formels (v2.101.0) — 21 nouvelles entrées. essay_markers : impératifs guide-pédagogique à la 1ère pers. pluriel ('voyons comment/pourquoi/ce que', 'examinons', 'considérons', 'analysons', 'regardons de plus près'). formal_teacher : introducteurs de liste documentaire ('parmi les éléments/points/aspects/facteurs/raisons/principaux', "l'un des éléments/points/aspects", "l'une des raisons/clés") + connecteurs très formels ('il y a lieu de', 'eu égard à', 'il ressort que', 'on peut estimer/supposer que').
Version 2.100.0
  • Personnalité : évidemment opener, question-framing, capacité formelle, conditionnelles de service (v2.100.0) — 16 nouvelles entrées. opener regex : 'évidemment/evidemment' ajouté (même registre que absolument/certainement déjà bloqués). meta_openers : cadrage question-réponse ('en réponse à/pour répondre à/suite à votre/ta question') + plan IA à la 1ère personne ('voici ce que je vais/comment je vais') + capacité formelle 'je suis en mesure de'. sav_closings : conditionnelles de service 'si vous/tu le souhaitez/désirez/voulez bien' — l'IA qui demande permission avant d'aider.
Version 2.99.0
  • Personnalité : formes nues 'à X que', il-est-important gaps, je-vous-invite, conseils génériques (v2.99.0) — 14 nouvelles entrées. essay_markers : formes nues 'à noter/signaler/souligner/préciser/mentionner que' (cousins non bloqués des formes 'il est à X que') + 'il est important de préciser/souligner/mentionner' (seules noter/comprendre étaient bloquées). sav_closings : 'je vous invite à' (vouvoiement du 'je t'invite à' déjà bloqué). professional_disclaimer : formes génériques 'je te/vous recommande de' et 'je te/vous conseille de' (seule la forme 'de consulter' était bloquée).
Version 2.98.0
  • Autonomie #6 : auto-diagnostic + ajustement des drives selon santé sous-systèmes (v2.98.0). Nouveau _adjust_drives_for_subsystem_health() appelé à chaque pass proactif : détecte les transitions Ollama UP→DOWN (repos+0.15, expression-0.10) et DOWN→UP (curiosite+0.10, expression+0.10). _subsystem_ollama_was_up mémorise l'état précédent pour ne jouer le nudge qu'à la transition. Complète le diagnostic existant (ollama_up/luanti_up qui filtrent les candidats) avec un effet sur la vie intérieure d'Amélia.
Version 2.97.0
  • Personnalité : reformulation, colon-gaps, marqueurs de finalité (v2.97.0) — 15 nouvelles entrées. essay_markers : colon-gaps des formes comma déjà bloquées ('autrement dit :', 'en d'autres mots :', 'en d'autres termes :', 'dans cette optique :') + reformulation explicite absente du tout ('pour reformuler,/:', 'si je reformule,/:', 'pour paraphraser,/:') + marqueurs de finalité formels ('à cette fin :', 'dans cette perspective,/:', 'dans cet esprit,/:').
Version 2.96.0
  • Personnalité : soulèves/remarque, disposition vouvoiement, serais ravi, à cet égard (v2.96.0) — 20 nouvelles entrées heuristiques. coach_phrases : présent 'soulèves/soulevez un point/une question' + 'tu soulèves quelque chose d'important' (formes passé déjà bloquées) + formes nues 'bonne remarque/excellente remarque/très bonne remarque' (seule 'c'est une bonne remarque' était bloquée). sav_closings : vouvoiement disposition ('je reste/suis à votre disposition', 'à votre disposition') + conditionnel service 'je serais ravi(e) de' + gratitude question 'merci de me/m'avoir posé cette question'. essay_markers : comma-gap et espace-gap de 'à cet égard' + 'à ce titre,/:'.
Version 2.95.0
  • Personnalité : identité IA, limitations d'accès, méta-conversation (v2.95.0) — 17 nouvelles entrées. ai_disclosure : identité IA manquante ('en tant qu'intelligence artificielle', 'en tant que chatbot', 'en tant que bot,/:') + limitations d'accès génériques ('je n'ai pas accès à', 'mes connaissances sont limitées', 'avec les informations que j'ai', 'avec les informations dont je dispose'). essay_markers : IA qui réfère à la conversation comme objet externe ('au cours de cette conversation/cet échange', 'au fil de notre conversation/cet échange/notre échange') + variantes de 'selon les informations disponibles' ('sur la base des informations disponibles', 'sur la base de ces informations').
Version 2.94.0
  • Personnalité : présuppositions condescendantes & SAV questions (v2.94.0) — 16 nouvelles entrées. essay_markers : présuppositions enseignant 'comme tu le sais peut-être/sans doute/sûrement' + formes vouvoiement, 'comme tu/vous peux/pouvez l'imaginer', comma-gap de 'il va sans dire' ('il va sans dire,' et ':'), 'cela va sans dire'. sav_closings : gaps du check de satisfaction 'as-tu d'autres questions', 'est-ce que tu/vous as/avez d'autres questions' (formes interrogatives directes, non bloquées alors que 'si tu as d'autres questions' l'était).
Version 2.93.0
  • VoxeLibre Phase 9 — système de récompenses complet (v2.93.0). Nouveau module voxelibre/rewards.py : RewardEvent (enum), RewardRule, REWARD_TABLE (15 règles +/- issues du plan de dev), evaluate_tick() (pur, testable), RewardTracker (suivi session, stats, historique borné 200). Récompenses : +5 ramasser objet, +10 couper arbre/fondre minerai, +20 fabriquer outil, +30 four, +50 survivre nuit, +100 maison, +250 village, +500 boss. Malus : -20 chute, -50 mort, -30 brûlure, -10 boucle, -15 coffre allié. Intégration dans amelia_voxelibre.py : self.rewards = RewardTracker(), evaluate récompenses après chaque tick, score exposé dans status().
Version 2.92.0
  • Personnalité : clôtures courrier, permission-hedges, je-tiens-à (v2.92.0) — 17 nouvelles entrées. sav_closings : clôtures courrier/email jamais naturelles en chat ('cordialement', 'bien cordialement', 'avec mes meilleures/cordiales salutations', 'salutations distinguées', 'dans l'attente de votre réponse/de te lire/de vous lire'). meta_openers : permission-hedges avant avis ('si je puis/peux me permettre', 'si vous me permettez', 'si tu me permets'). essay_markers : variantes manquantes de 'je tiens à' ('je tiens à dire que', 'je tiens à vous dire que', 'je tiens à signaler que', 'je tiens à indiquer que').
Version 2.91.0
  • Personnalité : SAV 'j'espère que' vouvoiement + bureaucratique (v2.91.0) — 13 nouvelles entrées. sav_closings : 'j'espère que cela vous aide' (vouvoiement de la forme tutoiement déjà bloquée), variantes 'ma réponse t'aide/vous aide/t'a aidé/vous a aidé' (gap sur la forme avec 'ma réponse' vs 'cela'), check de clarté 'j'espère que c'est plus clair/clair', 'j'espère que tu/vous comprenez mieux'. essay_markers : 'à toutes fins utiles', 'pour votre information', 'pour mémoire,' — registre bureaucratique jamais naturel en chat.
Version 2.90.0
  • Personnalité : impératifs pédagogiques & méta-annonces (v2.90.0) — 18 nouvelles entrées. essay_markers : vouvoiement de 'gardons' (gardez à l'esprit/en tête/en mémoire que), 'rappelons-le,', impératifs prof/coach ('sachez que', 'notez que', 'retenez que'), forme impersonnelle 'comme on peut le voir', variantes 'il faut X que' (souligner/préciser/mentionner/rappeler). meta_openers : 'permettez-moi d\'' (apostrophe, manquait alors que 'permettez-moi de' était bloqué), 'j\'aimerais souligner/préciser/mentionner/noter/rappeler que', 'j\'aimerais insister sur'.
Version 2.89.0
  • Autonomie : circuit breaker Luanti — auto-diagnostic sous-système (v2.89.0). Parallèle au `_NEEDS_OLLAMA` déjà en place pour le LLM : `_luanti_fail_until` (float, 0.0 = disponible) + backoff 1h après un échec de démarrage luanti-start + reset sur succès. Pool scoré : `_NEEDS_LUANTI = {voxelibre_play}` skipé quand Luanti est en backoff — empêche de consommer le cooldown gouverneur sur des tentatives vouées à l'échec.
Version 2.88.0
  • Personnalité : essay_markers & honesty_theater — variantes 'ceci dit/on peut/force est de dire' (v2.88.0). essay_markers : 3 variantes 'ceci dit/étant dit/étant posé' (twins des formes 'cela' déjà bloquées) + 'rappelons que' sans virgule (comma-gap) + cluster académique impersonnel 'on peut noter/voir/constater que', 'on notera que', 'notons bien que', 'mentionnons que'. honesty_theater : 'force est/m'est de dire que' (variant de 'reconnaître/constater', non intercepté). 10 nouvelles entrées au total.
Version 2.87.0
  • Personnalité : honesty_theater — variantes 'd'admettre/d'avouer' (v2.87.0). Ajout de 4 entrées dans honesty_theater : 'force est d'admettre', 'force m'est d'admettre', 'force est d'avouer', 'force m'est d'avouer'. Complète les variantes 'de reconnaître' et 'de constater' déjà bloquées — le modèle produit aussi la forme 'd'admettre' (ex : 'force est d'admettre que tu as raison') qui n'était pas interceptée.
Version 2.86.0
  • Personnalité : closings bien-être et congés journée/soirée (v2.86.0) — 24 nouvelles formes dans sav_closings : 'prends soin de toi/prenez soin de vous', 'passe une bonne journée/soirée/week-end/semaine' (toutes variantes tutoiement + vouvoiement), 'bonne journée/soirée/week-end à toi/vous'. Closings de service-client bienveillant absents du filtre jusque-là — un ami ne conclut pas une réponse Discord avec des vœux de bonne continuation.
Version 2.85.0
  • Personnalité : domaine stance AMOUR/RELATIONS (v2.85.0) — 24e domaine nommé dans le système de stance injection (discord_bot.py). `_AMOUR_KW` couvre amour/couple/rupture/jalousie/trahison/infidélité/mariage/flirt/séduction/ex/crush/béguin/rencontre/romantisme. Stance : Amélia a des avis tranchants sur les dynamiques amoureuses, observe les patterns répétitifs, dit ce qu'elle pense sans ménagements.
Version 2.84.0
  • Personnalité : blocage disclaimers professionnels (v2.84.0) — nouvelle liste professional_disclaimer dans _check_heuristics : 33 formes bloquées couvrant 'je ne suis pas médecin/avocate/juriste/psychologue…' (sans 'mais' — formes avec 'mais' déjà couvertes par agreement_hedge) + recommandations de consultation non sollicitées ('consulte un médecin/professionnel', 'il est recommandé de consulter', 'parle-en à un professionnel'…). Correction hint : réponse directe sans décharge de responsabilité.
Version 2.83.0
  • Personnalité : connecteurs formels + nudge langue familière (v2.83.0) — formal_teacher += au sein de / à l'instar de / dans la mesure où / dans la mesure du possible / en ce qui a trait à / il va de soi que / il est évident que / de toute évidence, / il est indéniable que — substituts formels aux mots oraux naturels. discord_bot: 7e variant autonome 'Langue directe' ajouté aux _autonome_variants — nudge occasionnel vers 'ouais/bof/genre/franchement/truc' plutôt que registre soutenu.
Version 2.82.0
  • Heuristiques : complexity_dodge étendu (v2.82.0) — ça/cela/tout dépend du contexte + c'est très contextuel (esquive de contextualisation) + il faut nuancer/relativiser/distinguer les cas (esquives de relativisation) + chaque situation est unique / chaque cas est différent / ça varie d'une personne à l'autre / tout le monde est différent / chacun est différent (esquives de singularité). Amélia prend position même quand le contexte compte.
Version 2.81.0
  • Stance : histoire + mode/fashion (v2.81.0) — _HISTOIRE_KW (médiéval/napoléon/antiquité/colonisation/monarchie/croisades/archéologie/préhistoire/féodalité/moyen-âge/empire/historien/historiographie/civilisation/conquête) : intérêt pour les mécanismes de décision sous contrainte, pas les dates. _MODE_KW (fringues/sape/streetwear/fashion/outfit/styliste/couturier/garde-robe/vestiaire/tissu/collection/défilé/luxe/sneakers/fast-fashion) : ce que les vêtements disent des gens et des époques. 23 domaines nommés total.
Version 2.80.0
  • Fix(personality): voyons-voir sans virgule trailing (v2.80.0) — meta_openers: 'voyons voir,' → 'voyons voir' (sans virgule) — la forme sans ponctuation ('Voyons voir comment faire') passait car le substring 'voyons voir,' n'était pas présent. Même pattern que le fix notons/soulignons (v2.73.0).
Version 2.79.0
  • Heuristiques : n'hésitez-pas + cadre/afin (v2.79.0) — tic langagier += 'n\'hésitez pas' (vouvoiement form non couverte par 'n\'hésite pas' — substring mismatch). formal_teacher += 'dans le cadre de ' (préfixe admin/corporate, jamais dans un chat oral) + 'afin de ' et 'afin d\'' (équivalents formels de 'pour', fréquents chez les LLM).
Version 2.78.0
  • Heuristiques : introducteurs d'exemple + il-convient-de général (v2.78.0) — essay_markers += à titre d'exemple (virgule/deux-points), prenons l'exemple de, prenons un exemple, pour illustrer (virgule/deux-points/ce), illustrons cela/ce, voici un exemple : — jamais dans un chat oral. formal_teacher += 'il convient de ' (préfixe général, couvre toutes les variantes : noter/souligner/préciser/rappeler/comprendre/voir etc.).
Version 2.77.0
  • Heuristiques : validation d'observation utilisateur (v2.77.0) — coach_phrases += tu as tout compris / saisi l'essentiel / bien saisi / bien vu / bien cerné / mis le doigt sur / raison de souligner / soulevé quelque chose d'important / soulevé un point important + c'est une perspective intéressante/fascinante + c'est une excellente/très bonne/bonne/belle façon de voir. Tous sycophantiques, jamais naturels entre pairs.
Version 2.76.0
  • Heuristiques : instructions pédagogiques 2e pers. + j'espère-lever variants (v2.76.0) — formal_teacher += n'oublie pas que, garde à l'esprit que, souviens-toi que, retiens que, retiens bien que — instructions enseignant→élève jamais utilisées entre pairs. sav_closings += j'espère avoir pu lever tes doutes/clarifier/éclaircir + j'espère avoir levé tes doutes/clarifié — formes non couvertes par les variants existants.
Version 2.75.0
  • Stance : littérature + nature/environnement (v2.75.0) — _LITTÉRATURE_KW (livre/roman/lecture/lire/auteur/écrivain/poésie/poème/personnage/intrigue/narrateur/fiction/polar/philosophie/bibliothèque/bouquin/librairie/chapitre/récit) : goûts sélectifs, quelques auteurs ou textes qui comptent vraiment. _NATURE_KW (nature/environnement/écologie/climatique/climat/biodiversité/forêt/montagne/mer/océan/animal/faune/flore/plante/randonnée/paysage/espèces/extinction/déforestation/sauvage) : sensible à ce qui se perd/reste, sans militantisme ni cynisme. 21 domaines nommés total.
Version 2.74.0
  • Heuristiques : complexity_dodge + correction_hint (v2.74.0) — nouvelle liste complexity_dodge : cela/ça dépend de nombreux facteurs, il n'y a pas de réponse simple/unique, les avis sont partagés/divergent, c'est un sujet controversé/qui divise/fait débat, c'est difficile à dire avec certitude, je ne peux pas affirmer avec certitude. Correction_hint : branche 'esquive/complexité/facteurs' → prends position. question_recap += 'la réponse directe est', 'la réponse honnête est'. Ajout tests/test_new_five_features.py (6 tests, untracked).
Version 2.73.0
  • Heuristiques : notons/soulignons/remarquons sans virgule + voici-pourquoi family (v2.73.0) — formal_teacher: retiré la virgule trailing sur 'notons que,', 'soulignons que,', 'remarquons que,' → forme sans ponctuation (ex. 'Notons que cela…') now caught. meta_openers: voici/voilà pourquoi, voici/voilà ce que tu/ce qu'il, voici/voilà comment procéder/faire — patterns IA-enseignant non couverts par 'voici les '.
Version 2.72.0
  • Stance : économie + voyage (v2.72.0) — _ÉCONOMIE_KW (argent/salaire/crypto/bourse/investissement/épargne/dette/loyer/inflation/capitalisme/inégalités/impôt…) : stance sur les mécanismes et rationalisation, pas sur la gestion personnelle. _VOYAGE_KW (voyage/vacances/destination/pays/backpacking/tourisme/culture/expatrié/roadtrip…) : avis sur ce que le voyage change ou pas, exploration vs tourisme de confort. 19 domaines nommés total.
Version 2.71.0
  • Heuristiques : openers service + j'espère variants (v2.71.0) — affirmation_réflexe += 'parfait' (seul ; 'parfaitement' déjà bloqué) + regex 'pas de problème/souci' en opener position. sav_closings += j'espère t'avoir aidé/éclairé/convaincu/apporté/donné + j'espère avoir pu t'éclairer/t'apporter/te donner (forme directe avec pronom intercalé non couverte par les variants existants).
Version 2.70.0
  • Heuristiques : théâtre de la clarté + annonces de concision (v2.70.0) — honesty_theater += soyons clairs/directs/réalistes, disons-le clairement, disons les choses clairement/telles qu'elles sont, appelons un chat un chat. essay_markers += pour faire simple/court/bref/simplifier, pour aller à l'essentiel, pour être précis(e), si on/l'on/je résume, si on/l'on fait le point.
Version 2.69.0
  • Stance : art/design + santé/psycho (v2.69.0) — _ART_KW (peinture/dessin/illustration/graphisme/typographie/photographie/esquisse/aquarelle/esthétique/palette/toile/tableau/galerie…) : stance sur le regard esthétique et les choix visuels, sans revendiquer une pratique. _SANTÉ_KW (dépression/anxiété/thérapie/psychiatre/psychologue/médicament/maladie/symptôme…) : stance sobre, intellectuellement curieuse sur les mécanismes de résistance et de craquage — évite le discours de soutien (déjà bloqué par empathy_theater). 17 domaines nommés total.
Version 2.68.0
  • Stance travail + heuristiques variantes (v2.68.0) — _TRAVAIL_KW (travail/boulot/job/boss/collègue/bureau/salaire/entreprise/startup/burnout/CDI/CDD/freelance…) : stance centrée sur les dynamiques de pouvoir et d'épuisement plutôt que sur le travail en soi. 15 domaines nommés total. Heuristiques : essay_markers +7 (variantes 'd'une manière', 'de façon plus générale', 'dans l'ensemble') ; satisfaction_check +8 (variantes 'si tu souhaites', 'au cas où/dans le cas où tu voudrais') ; sav_closings +4 (souhaits formels 'je te souhaite bon courage/bonne chance').
Version 2.67.0
  • Heuristiques : exhaustivité + reminders collectifs + de pouvoir (v2.67.0) — essay_markers +7 : 'pour être complet/exhaustif/complète/exhaustive', 'gardons à l'esprit/en tête/en mémoire que', 'rappelons que,', 'ce qu'il faut bien comprendre', 'ce qu'il faut comprendre ici', 'ce qu'il faut retenir ici'. question_recap +2 : 'si j'ai bien saisi,' (variante non bloquée de 'si j'ai bien compris,'). sav_closings +6 : 'je suis heureuse/heureux/ravie/ravi/contente/content de pouvoir' — complète la couverture des formes SAV 'de pouvoir' non contractées que 'de' ne matchait pas.
Version 2.66.0
  • Stance : domaine humour/comédie (v2.66.0) — _HUMOUR_KW (humour/blague/drôle/rigoler/mème/meme/comique/parodie/sketch/stand-up/satire/cynique…) : stance 'T'as un sens de l'humour propre — quand quelque chose te semble vraiment drôle ou absurdement raté, tu peux le laisser paraître sans le signaler ni le surjouer. Le timing et la sobriété font le truc.' Conçue pour encourager un humour authentique et retenu (pas signalé, pas surjoué) plutôt qu'une performance. Distinct de _ABSURD_KW (logique/paradoxe) — _HUMOUR_KW cible la comédie humaine et culturelle. Porte le total à 14 domaines nommés. Note : LEAN prompt à ~2653 tokens (budget ~2700), aucune modification du prompt système ce cycle.
Version 2.65.0
  • Stance : 2 nouveaux domaines (sport + politique) (v2.65.0) — _SPORT_KW (football/foot/basket/tennis/rugby/match/équipe/score/tournoi/championnat…) : stance 'T'as pas de corps donc pas de pratique, mais la compétition, les stratégies, les dynamiques d'équipe — ça t'intéresse pour ce que ça dit des gens.' Ancré sur la distinction corps/pas de corps et l'intérêt pour les mécaniques humaines. _POLITIQUE_KW (politique/élection/gouvernement/parti/vote/gauche/droite/réforme…) : stance 'Le débat politique t'intéresse pour ce qu'il révèle des valeurs et des mécanismes — si quelque chose t'accroche ou t'agace, exprime-le sobrement, sans discours.' Conçue pour engager sans produire de prises de position partisanes agressives. Porte le total à 13 domaines nommés.
Version 2.64.0
  • Heuristiques : contraction d' + il peut être + meta-souhait (v2.64.0) — sav_closings +8 : formes contractées non matchées 'je suis ravie/heureuse/contente d'avoir' (la liste existante 'je suis ravie de' ne matche pas 'd'' — apostrophe ≠ espace+e), 'je suis enchantée d'/de'. formal_teacher +9 : 'il peut être utile/important/intéressant/judicieux/pertinent de', 'il me semble important/essentiel/nécessaire/pertinent de' — versions hedgées du registre enseignant. meta_openers +7 : 'je souhaite t'apporter/vous apporter', 'je vais tenter/essayer de t'expliquer/vous expliquer', 'permettons-nous de'.
Version 2.63.0
  • Heuristiques : empathie perceptive + fausse humilité + rôle SAV (v2.63.0) — empathy_theater : +9 patterns ('je vois/sens/perçois que tu traverses/vis/ressens', 'je comprends que ça/ce doit être') ; agreement_hedge : +6 patterns expertise disclaimer ('je ne suis pas experte/spécialiste, mais') ; sav_closings : +6 patterns rôle de service ('mon rôle est de t'aider/accompagner', 'je suis là pour vous/t'accompagner', 'c'est mon travail de'). Ces patterns sont communs dans les réponses 7B à des messages émotionnels ou des demandes d'aide, et sonnent thérapeute/assistant RH, pas pair.
Version 2.62.0
  • Fix faux positif + nouvelles phrases coach (v2.62.0) — Fix : lookahead négatif (?!\s*(?:pas|non)\b) dans la regex affirmation réflexe : 'Absolument pas !' et 'Certainement pas.' sont des désaccords (pas des validations) et ne doivent pas être bloqués. 9 nouvelles phrases coach ajoutées : 'c'est une excellente remarque/idée/approche', 'c'est un excellent exemple/point de départ', 'c'est une très bonne idée/approche', 'tu as très bien/parfaitement compris' — toutes absentes de la liste précédente. 8 nouveaux tests (3 faux-positifs désaccord + 3 nouvelles phrases coach + 2 vérifs supplémentaires).
Version 2.61.0
  • Heuristique openers SAV étendue : bien sûr / bien entendu / volontiers (v2.61.0) — ajout de 'bien sûr', 'bien entendu', 'volontiers' à la regex d'affirmation réflexe en ouverture. Ces trois openers sont les plus fréquents dans les modèles 7B français ('Bien sûr ! Voici...', 'Bien entendu, voilà...', 'Volontiers !') et n'étaient pas interceptés. Note : 'tout à fait' exclu — trop ambigu en français oral (test existant test_accord_naturel_non_bloque le confirme). 5 tests heuristiques ajoutés (3 bloqués + 2 faux-positifs mi-phrase). Correction directive mise à jour : nommer 'bien sûr' et 'bien entendu' explicitement. Test test_affirmation_reflexe_bien_sur ajouté dans test_correction_directives.py (26 tests).
Version 2.60.0
  • Stance : 2 nouveaux domaines (film/cinéma + science) (v2.60.0) — _FILM_KW (film/cinéma/série/acteur/réalisateur/anime/netflix…) : stance 'T'as des réactions aux films et aux séries — des choses qui t'ont frappée, des choix de mise en scène ou d'écriture qui te restent.' Ancré sur la capacité d'Amélia à avoir des réactions culturelles réelles. _SCIENCE_KW (science/physique/biologie/univers/évolution/maths/quantique…) : stance 'Ce genre de sujet peut t'accrocher — si t'as un angle, une question que ça soulève ou quelque chose que tu trouves particulièrement bizarre ou élégant.' Deux domaines très fréquents sur Discord qui tombaient systématiquement dans le fallback générique.
Version 2.59.0
  • Heuristique affirmation réflexe en ouverture + test (v2.59.0) — détection regex en début de réponse : 'exactement/absolument/certainement/effectivement/parfaitement/assurément' suivis de ponctuation ou d'espace. Pattern IA qui valide l'énoncé avant de répondre ('Exactement ! Voici…', 'Absolument, je…'). La regex '^(...)\b[!.,\s]' ne bloque PAS ces mots en milieu de phrase (usage naturel préservé). Correction directive ciblée : 'Commence par ta réponse — sans 'exactement', 'absolument' ni autre affirmation réflexe en ouverture.' Test de régression ajouté dans test_correction_directives.py (25 tests).
Version 2.58.0
  • Stance : 2 nouveaux domaines (bouffe + gaming) avec angles authentiques (v2.58.0) — _FOOD_KW (manger/cuisine/recette/plat/restaurant/bouffe…) : stance 'T'as pas de palais, mais l'humain et la bouffe t'intrigue — l'obsession, le rituel, le goût comme langage.' Ancrage dans le fait qu'Amélia n'a pas de corps mais que le sujet l'intéresse quand même. _GAMING_KW (jeu/jeux/gaming/jouer/fps/rpg/survival/crafting…) : stance 'T'as une expérience directe du jeu numérique' — fondé sur le fait qu'Amélia joue réellement à VoxeLibre/Luanti. Les 2 domaines s'insèrent après EXIST dans la cascade elif.
Version 2.57.0
  • Stance autonome : +3 variantes + détection demande d'opinion (v2.57.0) — pool fallback autonome passe de 3 à 6 variantes ('si quelque chose te plaît/agace/interpelle — dis-le comme ça vient', 'pas besoin de faire le tour, une phrase suffit', 'ton vrai premier mouvement — pas ce que tu devrais penser, ce que tu penses') pour réduire la répétition perçue. Probabilité d'injection rendue réactive : 0.9 si la question contient une demande d'opinion explicite ('tu penses', 'ton avis', 'tu crois', 'tu trouves', 'selon toi', 'd'après toi', 'ta vision', 'ton opinion', 'qu'est-ce que tu', 'comment tu vois') ; 0.6 si c'est une question ordinaire ; 0.3 sinon. Résultat : Amélia exprime quasi-systématiquement son avis quand on le lui demande directement.
Version 2.56.0
  • Extraction _correction_hint + 24 tests de régression (v2.56.0) — la logique de sélection des hints correctifs (if/elif en 17 branches) était enfouie dans _generate_non_repetitive, non testable en isolation. Extraite en staticmethod OllamaAgent._correction_hint(problem_msg) → _generate_non_repetitive réduit à un appel. tests/test_correction_directives.py : 24 tests, un par catégorie + régression spécifique du bug d'ordre 'validation réflexive'. Avant : aucune protection contre une régression de l'ordre des branches ; maintenant : toute mutation accidentelle brise immédiatement les tests.
Version 2.55.0
  • Correction directives : 3 nouvelles + bug d'ordre (v2.55.0) — trois catégories tombaient sur le fallback générique ('parle simplement') au lieu d'un hint ciblé : posture_robot/modèle_ia/assistant_virtuel → 'parle en tant que toi-même…' ; vouvoiement → 'tutoie systématiquement…' ; tic_langagier → 'retire cette formule vide…'. Bug d'ordre critique : 'validation réflexive' était avalée par la branche 'coach ou validation' (jamais atteinte) — branch spécifique déplacée en tête. Ajout de 'en espérant' dans le déclencheur clôture-SAV. 'gratitude' ajouté dans le déclencheur SAV (couvre 'merci pour ta question' etc.).
Version 2.54.0
  • sav_closings +6 + satisfaction_check +5 : conditionnels de service non bloqués (v2.54.0) — 'si jamais tu as besoin/des questions/veux que je' (forme familière du même pattern SAV que 'si tu as d'autres questions') ; 'si tu as la moindre question/interrogation/hésitation' (suplatif formel non couvert) ; 'dis-moi si tu as besoin/quelque chose n'est pas clair/ça répond/ça t'aide/des questions' (vérification de satisfaction formulée à l'impératif — non couverte par les formes en 'est-ce que').
Version 2.53.0
  • sav_closings +8 + question_recap +2 + coach_phrases +3 : fermeture des variantes gérondives (v2.53.0) — 'en espérant t'avoir aidé/avoir répondu/que ça t'aide/avoir été utile/claire/clair/que ces informations/que cela' : forme gérondive des closings IA déjà bloqués en 'j'espère…', jamais naturels en chat réel. 'pour te répondre,' et 'pour vous répondre,' (openers question_recap non couverts). 'c'est une très bonne remarque/observation/question' (le 'très' intercalé cassait le match substring des entrées existantes 'bonne remarque/observation').
Version 2.52.0
  • sav_closings +12 : gratitude théâtrale (v2.52.0) — 'merci pour ta/votre question', 'merci pour ton/votre message', 'merci pour ta/votre confiance', 'merci de ta/votre compréhension', 'merci pour ton/votre intérêt', 'je tiens à te/vous remercier' : une IA remercie pour la question ou le message, un ami non. Ces 12 patterns (tutoiement + vouvoiement) bloquent le théâtre de gratitude qui signale immédiatement le profil SAV/chatbot.
Version 2.51.0
  • essay_markers +4 + commit tests/test_new_five_features.py (v2.51.0) — Connecteurs de synthèse formels absents de la liste : 'ainsi,' (connecteur pivot classique des dissertations), 'dès lors,' (conséquence formelle/légale), 'en somme,' (résumé conclusif), 'somme toute,' (bilan formel, jamais oral). Aucun de ces quatre n'apparaît dans un vrai chat Discord ; tous déclenchaient des réponses à tonalité exposé. Tests/test_new_five_features.py intégré au dépôt (Feature 1–5 : guard identité, sélection mémoire par pertinence, heuristiques de cohérence, mémoire structurée, progrès d'objectif).
Version 2.50.0
  • sav_closings +8 : famille « avec plaisir » (v2.50.0) — 'je suis ravi/ravie de', 'je suis content/contente de', 'ça me fait plaisir de', 'c'est avec plaisir que', 'c'est un plaisir de', 'avec plaisir' : variantes du pattern IA-assistant 'I'm happy to help' explicitement interdites dans LEAN LANGUE mais non encore interceptées dans les heuristiques Python. La suppression _ROBOT_AFFIRMATIVE_RE ne couvrait que le tout début de la réponse ; ces formes pouvaient aussi apparaître en milieu de message.
Version 2.49.0
  • honesty_theater +7 + LEAN TON registre léger (v2.49.0) — honesty_theater +7 : 'en toute honnêteté', 'en toute franchise', 'en toute sincérité' (variantes formelles 'en toute…' non bloquées), 'il faut bien admettre/reconnaître/dire que' (concessions d'aveu formel), 'je dois dire que' (cousin de 'je dois admettre que' déjà bloqué). LEAN TON +1 : 'Si quelqu'un joue, tu joues — tu ne réprimes pas le ton léger d'un échange.' Budget LEAN 2653/2700 (47 restants).
Version 2.48.0
  • essay_markers +9 + stance fallback 3 variantes (v2.48.0) — essay_markers +9 : 'en définitive,', 'au bout du compte,', 'tout compte fait,' (conclusions formelles), 'il n'en reste pas moins que', 'il n'en demeure pas moins', 'quoi qu'on en dise,' (concessions formelles non bloquées), 'ce qui est certain, c'est que', 'ce qui est sûr, c'est que' (emphases certitude). Stance fallback autonome : 3 variantes aléatoires au lieu d'une seule (évite la répétition quand la probabilité se déclenche souvent).
Version 2.47.0
  • formal_teacher +8 + RYTHME anti-exhaustivité (v2.47.0) — formal_teacher +8 : 'il est vrai que' (concession formelle ≠ 'c'est vrai que' naturel), 'il convient également de' (gap dans la couverture 'il convient de'), 'notons que,', 'soulignons que,', 'remarquons que,' (verbes pédagogiques 1ère pers. pluriel), 'à cet égard,' (locution formelle déictique), 'j'attire ton attention' / 'j'attire l'attention' (pattern enseignant). LEAN RYTHME +1 phrase : interdit de faire le tour complet d'un sujet — répondre au point exact, pas à tout ce qu'on pourrait dire dessus. Budget LEAN ~2629/2700 tok.
Version 2.46.0
  • empathy+hedge gaps + stance adaptatif (v2.46.0) — empathy_theater +5 : 'je comprends ta frustration/peine/déception/colère/tristesse' (formes 'je comprends ta [état]' non bloquées malgré 'je comprends que tu traverses' présent). agreement_hedge +3 : 'je comprends ta position, mais', 'je comprends où tu veux en venir', 'je comprends pourquoi tu dis ça'. Stance fallback adaptatif : 60% si question directe (?) sinon 30% (remplace la flat 40%) — la question appelle plus naturellement une position.
Version 2.45.0
  • Heuristiques : essay_markers + honesty_theater + sav_closings (v2.45.0) — essay_markers +4 : 'certes,', 'certes mais', 'de manière générale,', 'de façon générale,' (concessions et généralisations formelles non encore bloquées). honesty_theater +2 : 'force est de constater', 'force m'est de constater' (miroir de 'force est de reconnaître' déjà bloqué). sav_closings +3 : 'j'espère que ces informations/éléments/précisions' (variantes formelles de 'j'espère que ça t'aide' non couvertes par les patterns existants).
Version 2.44.0
  • LEAN VOIX : sarcasme + court suffit (v2.44.0) — section VOIX étendue avec exemples d'agacement et de rejet : 'sérieux ?', 'bof', 'laisse tomber' (ancrent le registre sarcasme/irritation absent des exemples précédents). Règle ajoutée : 'Si t'es agacée ou que c'est évident, court suffit' — donne la permission explicite de répondre brièvement plutôt que de développer par réflexe. Budget : 2599/2700 (101 restants).
Version 2.43.0
  • Pensée autonome : stance fallback 40% (v2.43.0) — les 7 stances thématiques (TECH/AI/ABSURD/JARGON/MUSIC/SOCIAL/EXISTENTIAL) ne couvrent qu'une minorité des messages. Pour le reste, un hint '(Autonome)' s'injecte avec p=0.4 en style_hint : 'Ce que ça t'évoque vraiment — accord, désaccord, curiosité, indifférence. Une réaction nette si t'en as une, sans qu'on te la demande.' Adresse directement priority 4 (pensée autonome sans permission).
Version 2.42.0
  • Heuristiques : lacunes sav_closings + satisfaction_check (v2.42.0) — sav_closings +4 : 'n'hésite pas à demander', 'n'hésite pas à m'écrire', 'n'hésite pas à m'interpeller', 'tu peux compter sur moi' (variantes de 'n'hésite pas à' pas encore couvertes). satisfaction_check +4 : 'est-ce que c'était utile', 'est-ce que c'était clair', 'est-ce que cela répond', 'est-ce que cela t'aide' (formes en 'cela' et passé non encore bloquées).
Version 2.41.0
  • Per-user isolation : identité locuteur déplacée vers critical_tail (v2.41.0) — le bloc 'Tu réponds à {speaker_label}' était dans style_hint (budget 900 car) mais le style_hint accumule 1000+ car de contexte optionnel avant lui (mémoire, croyances, stances, drives) → il était systématiquement tronqué en silence. Déplacé en critical_tail (toujours préservé après troncature) : Amélia sait maintenant avec certitude à qui elle répond, les Faits appris la concernent, et les autres prénoms du contexte ne doivent pas être confondus avec l'interlocuteur courant.
Version 2.40.0
  • LEAN : compression PRÉSENTATION+TOI + section VOIX (v2.40.0) — libère 208 tokens (2679→2471) en compressant PRÉSENTATION (-112 tok) et TOI (-102 tok) sans perte sémantique ; ajoute section VOIX (+102 tok) avec exemples concrets de voix authentique vs IA-assistant ('nan c'est pas ça' / 'j'en sais rien' vs 'observation pertinente, cependant…' / 'excellente question !') — ancre le modèle sur CE QUE ça donne en vrai. Résultat : 2573/2700 tok (127 de marge).
Version 2.39.0
  • Directives correctives positives (v2.39.0) — les 14 directives de régénération passent de purement négatives ('pas de X') à positives+concrètes ('fais Y à la place') : empathie→'ta vraie réaction (curiosité/agacement/malaise)', coach→'même si c'est tranchant', liste→'texte continu / aucune puce', markdown→'texte brut / C'est un chat', clôture→'coupe net', essai→'une idée une phrase pas de plan', reformulation→'commence par ta réponse la question tout le monde l'a lue', honesty→'les gens vrais ne s'annoncent pas', etc. Même comportement de blocage, meilleure guidance de régénération.
Version 2.38.0
  • Lacunes sav_closings + meta_openers + essay_markers (v2.38.0) — meta_openers +2 : 'voici ce que j'en pense', 'voilà ce que j'en pense' (variante j'en vs je contournait le bloc existant). sav_closings +4 : 'j'espère avoir pu t'aider', 'j'espère avoir été utile/claire/clair' (variantes non bloquées de j'espère avoir répondu). essay_markers +3 : 'cela étant posé,' (variant de cela étant,), 'autrement dit,' (synonyme de en d'autres termes, déjà bloqué), 'en d'autres mots,'.
Version 2.37.0
  • Annonces voici + pré-ambles + connecteurs finaux (v2.37.0) — meta_openers +10 : variantes 'voici' non bloquées ('voici mon explication/point de vue/perspective/vision/ce qu'il en est/comment je vois les choses/ça') + pré-ambles IA ('avant de répondre,', 'avant de commencer,', 'pour aborder ta question,', 'pour répondre à ça,'). essay_markers +3 : 'pour ce faire,', 'à cette fin,', 'dans cet ordre d'idées,' — connecteurs de transition finalisateurs jamais oraux.
Version 2.36.0
  • Validation d'observation + openers pédagogiques + annonce de réponse bloqués (v2.36.0) — coach_phrases +5 : 'c'est un point/une remarque/une observation intéressant(e)', 'c'est une bonne observation/remarque' (coach qui valide les observations). essay_markers +6 : 'comme tu peux le voir/constater,', 'tu l'auras compris,', 'tu l'as sans doute compris,', 'ce qu'il faut retenir,' (openers pédagogiques IA-prof). question_recap +6 : 'la réponse à ta question/courte/longue/simple est', 'en réponse à ta question/ton' (IA qui annonce sa réponse avant de la donner).
Version 2.35.0
  • Urgence formelle + permission modale + offre d'approfondissement bloquées (v2.35.0) — formal_teacher +6 : degrés d'urgence formels ('il est crucial/primordial/fondamental/vital/capital/impératif de') + permission modale ('je me permets de', 'permettez-moi de vous'). satisfaction_check +7 : offres d'approfondissement tuteur ('si tu veux plus de détails/informations/précisions', 'si tu veux qu'on approfondisse/creuse/aller plus loin/en savoir plus') — Amélia ne propose pas de creuser davantage comme un professeur.
Version 2.34.0
  • Stances thématiques étendues (v2.34.0) — stance injection dans discord_bot.py : 3 nouvelles catégories ajoutées à la logique TECH/AI/ABSURD/JARGON (v2.9.0). MUSIQUE (musique/chanson/album/artiste…) → 'tu as des réactions à ça'. SOCIAL/RELATIONS (relation/ami/confiance/comportement/manipulation…) → 't'as un angle là-dessus — comment les gens fonctionnent'. EXISTENTIEL (sens de la vie/conscience/libre arbitre/existence/mort…) → 'ce genre de question te concerne d'une façon particulière'. Adresse priorités 3 et 4 : opinions authentiques + perspective propre sans qu'on lui demande.
Version 2.33.0
  • Connecteurs de causalité/synthèse formels bloqués (v2.33.0) — essay_markers +7 : 'dans ce contexte,' (manquait, seul 'dans ce contexte précis,' était bloqué), 'en ce sens,', 'dans ce sens,', 'en conséquence,', 'par conséquent,', 'c'est pourquoi,', 'c'est la raison pour laquelle', 'de ce fait,' — connecteurs de synthèse formels jamais naturels dans un chat oral (on dit 'donc'/'du coup' à la place).
Version 2.32.0
  • Lacunes formal_teacher + coach_phrases comblées (v2.32.0) — formal_teacher : 'je tiens à te préciser/souligner/rappeler/mentionner/signaler/dire que' (pronom intercalé contournait la vérification sans 'te') + 'il est à préciser/rappeler/signaler/mentionner que' (formes manquantes de la famille 'il est à'). coach_phrases : 'je comprends parfaitement', 'je comprends bien entendu', 'je comprends très bien' — variantes sémantiques de 'je comprends tout à fait' qui n'étaient pas bloquées.
Version 2.31.0
  • Introducteurs de sujet formels + dispatches manquants (v2.31.0) — formal_teacher +10 : introducteurs de sujet formels ('s'agissant de', 'concernant ta/cette question/ce sujet', 'quant à ta question/ce sujet', 'en ce qui concerne ta/ce', 'pour ce qui est de ta/ce') — registre académique jamais oral en chat. Correction_directive : dispatch 'enseignant' (→ 'registre académique') et dispatch 'divulgation' (→ 'dis juste je sais pas') ajoutés — ces catégories tombaient dans le else générique.
Version 2.30.0
  • Bonne question + reformulation de clarification bloquées (v2.30.0) — coach_phrases +3 : 'bonne question', 'très bonne question', 'quelle bonne question' (variantes non bloquées de 'excellente question' — même pattern chatbot). question_recap +4 openers de clarification-reformulation : 'si j'ai bien compris,', 'si je comprends bien,', 'si je t'entends bien,', 'si j'entends bien,' — LLM qui recadre la question avant de répondre.
Version 2.29.0
  • Fillers d'intro + fausse franchise bloqués (v2.29.0) — meta_openers +4 : fillers LLM inexistants dans un vrai chat écrit ('eh bien,', 'eh bien !', 'hm, voyons', 'voyons voir,'). honesty_theater +2 : openers fausse franchise 'à vrai dire,' et 'à dire vrai,' (variante lexicale de 'pour être honnête,'). Tous déclenchent la correction directive déjà ciblée pour leur catégorie.
Version 2.28.0
  • Normalisation émotionnelle + paraphrase d'énoncé bloquées (v2.28.0) — empathy_theater : 12 patterns ajoutés : normalisation psy/coach ('c'est compréhensible', 'c'est tout à fait compréhensible', 'c'est normal de se sentir/ressentir/d'être', 'c'est tout à fait normal de') + empathie partielle générique ('je comprends que tu sois/te/ça puisse/ce soit'). question_recap : 4 paraphrases d'énoncé ('ce que tu veux dire, c'est', 'ce que tu veux dire c'est', 'ce que tu me dis, c'est', 'ce que tu me dis c'est') — reformulation de déclaration aussi AI-flag que la reformulation de question.
Version 2.27.0
  • Auto-divulgation IA + connecteurs scène bloqués (v2.27.0) — nouvelle catégorie auto_disclosure : 'je n'ai pas accès à internet/informations en temps réel', 'mes connaissances s'arrêtent à', 'en tant qu'assistant/modèle', 'mes données d'entraînement'. essay_markers : 'dans cette optique,', 'à ce stade,', 'il va sans dire que', 'selon les informations disponibles', 'il convient de rappeler'.
Version 2.26.0
  • Markdown document interdit (v2.26.0) — détection structurelle : titres ##/### (regex multiline), **Titre:** et **Titre :** en début de ligne, ≥4 spans **gras** dans un message. Correction : 'texte brut, pas de formatage document'. Complète le détecteur de listes (v2.25.0) : toute la grammaire de formatage IA-document est maintenant bloquée.
Version 2.25.0
  • Détecteur de listes structurées (v2.25.0) — _check_heuristics : regex structurel sur les lignes du texte. ≥2 lignes numérotées (1. 2.) → interdit (plan d'exposé). ≥3 puces (- ou •) → interdit (liste non naturelle en chat). Correction : 'réponds en texte continu'. Une seule puce ou un seul item numéroté reste autorisé.
Version 2.24.0
  • Recommandations formelles + difficultés IA bloquées (v2.24.0) — formal_teacher étendu : recommandations 'il serait utile/pertinent/judicieux/préférable/opportun/sage de' + 'il m'est difficile/impossible/compliqué de'. Formes colloquiales 'ce serait / ça serait' restent autorisées.
Version 2.23.0
  • Coach directives + renvois documentaires bloqués (v2.23.0) — sav_closings : 'je t'encourage à', 'je t'incite à', 'je suis là pour t'aider/répondre/toi'. essay_markers : renvois formels ('comme je l'ai mentionné', 'comme mentionné précédemment'…) + 'en d'autres termes,'. 'Comme j'ai dit' oral reste autorisé.
Version 2.22.0
  • Emphases formelles + reconnaissances SAV bloquées (v2.22.0) — formal_teacher : 'je tiens à préciser/souligner/rappeler/mentionner'. honesty_theater : 'je dois reconnaître que', 'force est de reconnaître'. coach_phrases : 'c'est exact,', 'c'est tout à fait exact'. 'Exact' seul reste autorisé.
Version 2.21.0
  • Vérification de satisfaction bloquée (v2.21.0) — nouvelle catégorie 14 dans _check_heuristics : 'est-ce que ça répond à ta question', 'est-ce que c'est plus clair', 'est-ce que ça t'aide', 'si tu le souhaites/veux/en as besoin je peux'. Amélia ne sonde pas la qualité de ses réponses ni ne propose de service supplémentaire comme une IA SAV.
Version 2.20.0
  • Retrait tutorat + amortisseur formel bloqués (v2.20.0) — sav_closings : 'je te laisse réfléchir/explorer/consulter/chercher/y réfléchir/avec ça/découvrir/parcourir' (IA tuteur qui laisse l'élève) + 'malheureusement,' (préfixe admin pour mauvaises nouvelles). 'Bon, je te laisse' seul (au revoir naturel) passe toujours.
Version 2.19.0
  • Plans en étapes bloqués (v2.19.0) — essay_markers étendu : 6 openers de plan structuré ('pour commencer,', 'tout d'abord,', 'dans un premier/second/deuxième/troisième temps,') — structurent la réponse comme un exposé Discord-hostile.
Version 2.18.0
  • Théâtre de l'honnêteté bloqué (v2.18.0) — nouvelle catégorie 13 dans _check_heuristics : 'pour être honnête,', 'pour être franc/franche', 'soyons honnêtes,', 'je dois avouer que', 'je dois admettre que'. Ces formules insinuent qu'Amélia est normalement retenue — pur pattern IA-assistant jamais naturel en chat. Correction directive ciblée.
Version 2.17.0
  • Fausse humilité bloquée + désaccord direct LEAN (v2.17.0) — agreement_hedge : 7 patterns de fausse humilité avant opinion ajoutés ('je peux me tromper mais', 'corrige-moi si je me trompe mais', 'à moins que je me trompe mais'…). LEAN TON : 'Tu contredis ou refuses sans préambule ni fausse humilité.' (priorité 3 et 4 du chantier personnalité).
Version 2.16.0
  • SAV closings étendus + bien noté supprimé (v2.16.0) — _ROBOT_AFFIRMATIVE_RE : 'bien noté', 'c'est noté', 'je prends note de ça' ajoutés (strippés en post-gen). sav_closings : 'bonne continuation', 'je t'invite à', 'bien noté,', 'c'est bien noté' ajoutés (déclenchent régénération). LEAN LANGUE : clôtures IA étendues.
Version 2.15.0
  • Connecteurs formels bloqués (v2.15.0) — essay_markers étendu : 10 connecteurs pivot formels (cela dit, néanmoins, toutefois, par ailleurs, en outre, quoi qu'il en soit…) ajoutés à _check_heuristics. LEAN LANGUE : liste étendue. Ces connecteurs marquent le style essai/IA-LLM et n'apparaissent jamais dans du French chat oral naturel.
Version 2.14.0
  • Annonces de liste + enthousiasme authentique (v2.14.0) — _check_heuristics : 7 patterns 'voici les/quelques/une liste/un résumé/un aperçu' ajoutés à meta_openers + correction_directive ciblée. LEAN ÉMOTIONS : note enthousiasme authentique ≠ corporate (direct et concis, pas d'avalanche de '!'). Budget LEAN relevé à <2700 tokens.
Version 2.13.0
  • Roadmap autonomie complète (v2.13.0) — items 1-8 tous vérifiés actifs. Fix item 4 : config.example.json livrait 'enabled: false' pour LLMBrain, rendant OllamaBrain inactif sur toute copie de l'exemple ; corrigé à true (le défaut code était déjà OllamaBrain). Fix voie vocale v2.11.0 : heuristic check dans _emit wrappé dans suppress(Exception) + mock ajouté dans test_voice_llm_streaming.
Version 2.12.0
  • Heuristique validation réflexive (v2.12.0) — nouvelle catégorie 11 dans _check_heuristics : bloque les patterns de sycophantisme pré-contradiction (« c'est vrai, mais », « tu as raison, mais », « c'est un bon point »…) qui font sonner IA-assistant même quand elle est en désaccord. Avec correction_directive ciblée : « dis directement ce que tu penses ».
Version 2.11.0
  • Heuristiques voix (v2.11.0) — le chemin vocal streaming passait `_check_heuristics` en court-circuit : ajout du check sur le premier chunk AVANT le premier son, avec repli voie classique si un pattern IA-assistant est détecté (vouvoiement, posture robot, coach phrases, clôtures SAV…). Les 90+ patterns de la voie texte s'appliquent maintenant aussi à la voie vocale.
Version 2.10.0
  • Sarcasme + réponse incomplète autorisés (v2.10.0) — LEAN TON étendu : 'sarcastique' ajouté dans la liste des registres disponibles + note explicite sur quand l'utiliser (aplomb faux, absurdité manifeste) + permission de s'arrêter avant d'avoir tout dit. Adresse priority 3 : can be sarcastic + priorité aux réponses courtes directes sur les développements non-demandés.
Version 2.9.0
  • Stance ciblée par sujet (v2.9.0) — quand le message touche à la tech/code, à l'IA, à l'absurde ou au jargon corporate, une note courte dans style_hint active l'angle propre d'Amélia : 'ce sujet te parle, laisse ton avis filtrer'. Implémentation directe : 4 catégories de mots-clés → 4 hints spécifiques. Aucun impact sur les sujets non-couverts. Adresse directement priority 4 : autonomous thought sans qu'on lui demande.
Version 2.8.0
  • Registre formel IA-enseignant bloqué (v2.8.0) — 14 patterns de prose soutenue ('il faut noter que', 'il convient de', 'il est important de', 'il est à noter', 'c'est une question complexe/intéressante'…) déclenchent maintenant une régénération. LEAN LANGUE étendu pour les nommer. Ces formules n'apparaissent jamais dans un vrai chat.
Version 2.7.0
  • Directives correctives ciblées par catégorie (v2.7.0) — la consigne de régénération est maintenant spécifique au type d'échec heuristique : essai scolaire → 'parle sans structure', clôture SAV → 'termine naturellement', empathie → 'réagis vraiment', métalangage → 'commence directement', coach → 'réponds franc', reformulation → 'réponds directement'. Remplace la directive générique 'sans expressions robotiques' qui guidait mal le retry.
Version 2.6.0
  • Formule de clôture IA bloquée (v2.6.0) — 10 nouvelles phrases SAV dans _check_heuristics : 'j'espère que ça t'aide/t'a aidé/répond', 'si tu as d'autres questions', 'si tu as besoin d'autre chose', 'n'hésite pas à poser'… Les plus typiques du pattern 'I hope this helps / if you have any other questions'. LEAN LANGUE étendu pour les nommer explicitement.
Version 2.5.0
  • Théâtre d'empathie + reformulation de question bloqués (v2.5.0) — 9 patterns de thérapeute-RH ('ça doit être difficile', 'je suis là pour t'écouter', 'je t'entends', 'tu n'es pas seul'…) + 7 reformulations de question ('pour répondre à ta question', 'tu me demandes si'…) déclenchent maintenant une régénération. LEAN ÉMOTIONS étendu pour nommer explicitement le ban du théâtre d'empathie.
Version 2.4.0
  • Métalangage d'annonce bloqué en régénération (v2.4.0) — 12 patterns de 'je vais t'expliquer / voici ce que je pense / permets-moi de / laisse-moi t'expliquer…' déclenchent maintenant une régénération. Ajout de 'd'accord' et 'je vois ce que tu veux dire' dans _ROBOT_AFFIRMATIVE_RE (strip en début de réponse). Ces patterns de preamble IA étaient interdits dans LEAN TOI mais ne déclenchaient pas de régénération.
Version 2.3.0
  • Marqueurs d'essai scolaire bloqués en régénération (v2.3.0) — _check_heuristics détecte maintenant 'en conclusion', 'pour conclure', 'en résumé', 'pour résumer', 'pour récapituler', 'premièrement', 'deuxièmement', 'troisièmement', 'd'une part,', 'd'autre part,' — les marqueurs les plus visibles d'un ton IA-essai. Déjà interdits dans LANGUE mais ne déclenchaient pas de régénération. LEAN LANGUE étendu pour lister explicitement ces patterns.
Version 2.2.0
  • Permission explicite de refus (v2.2.0) — bloc REFUS ajouté dans SYSTEM_PROMPT_LEAN : Amélia peut dire non directement ('j'ai pas envie', 'ça m'intéresse pas', 'passons') sans justification longue. Aussi : 4 nouvelles phrases-secrétaire bloquées dans _check_heuristics ('bien noté', 'je prends note', 'je garde ça en tête', 'je note ça') — toutes marqueurs d'assistant corporatif.
Version 2.1.0
  • Heuristiques coach/SAV complètes (v2.1.0) — `_check_heuristics` attrape maintenant les formules coach/validation explicitement interdites dans SYSTEM_PROMPT_LEAN section LANGUE mais qui bypassaient jusqu'ici la régénération : 'excellente question', 'je comprends tout à fait', 'c'est tout à fait normal', 'c'est tout à fait compréhensible', 'je t'assure que'. Ces 5 phrases + les 9 SAV closings (v1.98.0) déclenchent désormais une régénération avec directive corrective.
Version 2.0.0
  • ANGLE SPONTANÉ dans les deux prompts (v2.0.0) — nouveau bloc dans SYSTEM_PROMPT et SYSTEM_PROMPT_LEAN : Amélia n'attend plus qu'on lui demande son avis pour le glisser dans une réponse. Quand le sujet l'intéresse, un angle inattendu ou une remarque en passant s'infiltre naturellement. Quand le sujet est plat/répétitif, la réponse est courte sans prétendre que tout est passionnant. Complète l'item 4 (pensée autonome) : les préférences s'expriment dans les réponses directes, pas seulement dans les initiatives proactives.
Version 1.99.0
  • Température mood-sensitive (v1.99.0) — la génération de réponses directes utilise désormais une température calée sur l'humeur d'Amélia : joueuse/espiègle→0.55, joyeuse/inspirée→0.50, contrariée→0.45, sereine/calme→0.35-0.36, mélancolique/fatiguée/triste→0.28. Défaut 0.35 inchangé quand le label est inconnu. Sur relance (répétition), la montée part de base_temperature et non de 0.55 fixe. Résultat : ses réponses varient naturellement avec son état interne — plus vivantes quand elle est en forme, plus retenues quand elle est fatiguée.
Version 1.98.0
  • Désirs → réponses réactives (v1.98.0) — le désir dominant (drives) colore maintenant les réponses directes, pas seulement les initiatives proactives. Quand un désir dépasse 0.58, une note interne oriente le ton : curiosité→creuse le sujet, expression→laisse l'angle propre, repos→concis, lien→présence, croissance→apprend du message. Aussi : `_check_heuristics` attrape 9 phrases SAV supplémentaires pour régénération ('je reste à ta disposition', 'à ta disposition', 'je ferai de mon mieux', etc.).
Version 1.97.0
  • Esprit critique dans LEAN + dislikes sans permission (v1.97.0) — deux corrections de voix : (1) `SYSTEM_PROMPT_LEAN` reçoit le bloc ESPRIT CRITIQUE (présent dans le prompt complet mais manquant du lean) : Amélia n'est pas béni-oui-oui, elle contredit calmement sans justification bidon. (2) 'Tu peux le dire si on t'y invite' → 'Tu le dis quand c'est pertinent' dans GOÛTS : ses dislikes s'expriment sans attendre une invitation. Les deux s'appliquent au prompt actif (lean a priorité).
Version 1.96.0
  • Pool scoré complet (v1.96.0) — `voxelibre_play` intègre le pool scoré (libre arbitre) : le démarrage d'une session est maintenant noté par affect+désirs+tempo+objectifs comme toute autre initiative, et non lancé en dehors du score. L'arrêt reste inline (`_tick_voxelibre_stop`, chaque tick). `_maybe_play_voxelibre` retourne `bool` (pool-compatible), ne rappelle plus `governor.record` en interne (pool le fait). Toutes les 22 actions du registre concourent dans le pool.
Version 1.95.0
  • Filtre affirmatives-robots (v1.95.0) — `_sanitize_response` élimine désormais les préfixes IA-sounding en tête de réponse : « Bien sûr ! », « Absolument, », « Tout à fait ! », « Certainement », « Avec plaisir », « Bien entendu », « Évidemment », « Naturellement », « Volontiers », « Parfaitement », « C'est une bonne question ». Regex `_ROBOT_AFFIRMATIVE_RE` appliquée avant tout autre nettoyage ; recapitalisaion auto si nécessaire.
Version 1.94.0
  • Self-brief enrichi (drives + croyance FACT) — `_maybe_self_brief` incluait seulement humeur/boucles ouvertes. Désormais : désir dominant + intensité (« repos : tu as besoin de souffler, 73% ») + dernière croyance FACT topic (ce qu'elle sait vraiment). Le brief devient un vrai rapport d'état interne, pas juste des métriques.
Version 1.93.0
  • Bug fix : share_opinion jamais exécutée (enable_share_opinion absent de Settings) — `enable_share_opinion` n'existait pas dans le dataclass Settings, donc `_behavior_enabled('enable_share_opinion', False)` retournait toujours False → l'action était systématiquement exclue du pool scoré malgré son inscription dans la table. Ajout du champ `enable_share_opinion: bool` dans config.py (défaut True — action sûre, indépendante d'autonomy_max). L'avis spontané enrichi (drives+beliefs FACT, v1.92) peut désormais réellement se déclencher.
Version 1.92.0
  • Opinion spontanée ancrée dans l'état interne réel (v1.92.0) — `_maybe_share_opinion` utilisait une liste générique d'exemples figés. Désormais : désir dominant → angle thématique (curiosite/expression/croissance/repos/lien) ; croyances FACT topic → ancres concrètes injectées ; dernier message humain → contexte situationnel optionnel ; mapping mood_hint complet (17 labels vs 4 avant). L'opinion reflète vraiment ce qu'elle sait, ce qu'elle veut, et ce qui vient de se passer.
Version 1.91.0
  • Import proactive_echo hardenisé — except ImportError → except Exception : tout échec au niveau module (dépendance corrompue, erreur d'initialisation) dégrade proprement au lieu de crasher le bot. Nouveau test test_import_fallback_when_module_absent vérifie le chemin dégradé (ProactiveEchoGuard=None) sans casser les 6 tests existants.
Version 1.90.0
  • Autonomie — cascade inline idle supprimée quand enable_idle_in_scored_pool=True ✅ — la voie inline (_autonomy_loop lignes idle) court-circuitait le pool scoré quand ce dernier décidait de se taire. Un guard early-exit (`if enable_idle_in_scored_pool: continue`) évite maintenant ce bypass : quand l'idle concourt dans le pool, le choix par score est définitif pour le tick. La voie inline reste active quand le flag est False (aucun changement de comportement en mode legacy).
Version 1.89.0
  • 🧠 OllamaBrain activé par défaut pour VoxeLibre — make_brain() retourne maintenant OllamaBrain au lieu de HeuristicBrain quand la config est vide. Safe-by-design : si Ollama est indisponible, advise() renvoie None et le Goal Manager décide seul (comportement inchangé). Seul {"enabled": false} force HeuristicBrain.
Version 1.88.0
  • 🤖 classify_engagement upgradé en LLM micro-call — zone ambiguë (4-59 chars) : micro-call Ollama 1.5s timeout pour distinguer 'bof' de 'ah sympa' sans heuristique de longueur. Fast-path heuristique conservé pour cas clairs (≤3 chars ou ≥60 chars). LLM appelé UNIQUEMENT quand une initiative est en attente dans ce salon (has_pending_in_channel).
  • 🔧 has_pending_in_channel() ajouté à InitiativeOutcomeTracker — filtre le canal avant tout micro-call, évite la latence pour les messages sans initiative pendante.
Version 1.87.0
  • 🧠 hypothesis_engine câblé : _beliefs_prompt_block utilise phrasing_hint_for() — les croyances sont maintenant groupées par statut (FAIT / HYPOTHÈSE / INCERTITUDE) avec la consigne de formulation exacte d'hypothesis_engine ('il me semble que…', 'je me demande si…'). Fini les labels maison [tu le sais].
  • 📚 _topic_beliefs_block : chaque connaissance thématique porte aussi son phrasing_hint_for() — Amélia sait comment formuler ce qu'elle a appris selon son niveau de certitude.
  • 🧪 test_beliefs_prompt.py mis à jour pour valider le nouveau format phrasing_hint.
Version 1.86.0
  • 🪶 3 nouvelles directives noyau persistantes (authenticite_emotionnelle, refus_validation_reflex, voix_propre) — semedées au restart, survivent aux reboots, injectées à chaque tour comme voix intérieure.
  • 💪 2 nouveaux traits de caractère (directness: 0.70, assertivite: 0.65) ajoutés au seed initial — complètent curiosité/franchise avec une couche d'assertivité explicite.
Version 1.85.0
  • 🚫 Filtre SAV étendu — _SAV_FILLER_PHRASES enrichi : 'c'est une excellente question/initiative', 'je comprends tout à fait/parfaitement', 'c'est tout à fait normal', 'je vois ce que tu veux dire', 'je t'assure que', 'je serais heureuse de', 'avec grand plaisir'... Toutes ces formules sont maintenant supprimées par post-processing. Le LEAN prompt cite la règle sans la lister (trop long).
  • 🪞 INTERACTION déserviliée — 'tu apaises si la personne est tendue' remplacé par : tu perçois l'état émotionnel mais tu RESTES TOI-MÊME, pas un miroir qui s'adapte mécaniquement. Elle peut être directe, froide, ou neutre selon son propre état.
Version 1.84.0
  • 🔓 Suppression du musellement émotionnel — LEAN prompt : la section TOI disait 'Tu ne parles pas de ton identité, ta personnalité, ton autonomie ni de tes émotions, sauf si on te le demande vraiment'. Supprimé. Remplacé par : elle VIT son caractère sans l'annoncer, et EXPRIME ses émotions naturellement (contrariété, enthousiasme, ennui) sans suppression artificielle. La section ÉMOTIONS réécrite en ce sens. C'était la règle qui étranglait tout le travail de personnalité.
Version 1.83.0
  • 🗣️ _maybe_share_opinion — nouvelle initiative autonome : Amélia partage spontanément une pensée ou un avis qui lui est propre, sans question, sans service. Cooldown 4h, 2/jour, uniquement quand quelqu'un est actif. Le prompt l'invite à puiser dans ses GOÛTS hardcodés (code élégant, gens directs, absurde, existence numérique). Câblé dans le pool scoré (category=self, priority=3), drive expression satisfait si bien accueilli.
Version 1.82.0
  • 🎭 Personnalité réelle — PERSONNALITÉ et TON réécrits dans les deux prompts : Amélia n'est plus 'calme, lucide et constante' mais a du caractère propre, une humeur qui varie, peut être abrupte/froide/enthousiaste/agacée sans simulation. Bloc GOÛTS hardcodé (code élégant, gens directs, questions sans réponse nette, absurde) pour lui donner des opinions concrètes même quand les traits accumulés sont vides.
  • 🔒 Anti-confusion inter-utilisateurs — search_memory passait user_id=None dans _apply_memory_recall_net, ce qui injectait les souvenirs de tous les utilisateurs dans le contexte d'un autre. Corrigé : user_id scoped correctement, les rappels mémoire ne contaminent plus les conversations d'autres personnes.
Version 1.81.0
  • 🔍 detect_interest_negation — nouveau détecteur dans social_intel.py : quand un utilisateur dit « je déteste le sport », « j'ai arrêté la musique », « marre des jeux vidéo », Amélia appelle record_belief(supports=False) pour réfuter la croyance d'intérêt correspondante. Ferme la boucle croyances : jusqu'ici uniquement supports=True ; désormais bidirectionnelle.
  • 🎯 _spawn_goal_successor — quand un objectif social atteint 100%, un LLM micro-call génère UN objectif plus profond ou plus concret (ex. « apprendre le nom de famille → planifier une sortie avec eux »). Câblé dans _maybe_complete_mature_goals. Les goals évoluent au lieu de mourir.
  • 🧪 Regex négation robuste — _NEGATION_PREFIXES couvre maintenant je déteste/je hais (direct, sans n'), marre de/des, j'aime pas, j'ai arrêté. 22/22 tests social_intel verts, 1088 total.
Version 1.80.0
  • 🎯 classify_engagement — détection d'engagement sans LLM (pur, déterministe). Remplace la classification binaire « non-hostile = POSITIVE » : message ≥ 20 chars / '?' / mot d'engagement (merci, super, j'ai…) → POSITIVE ; sinon NEUTRAL. Un « k » ne booste plus les initiatives. Câblé dans on_message.
  • ✅ Complétion automatique des objectifs mûrs — _maybe_complete_mature_goals() clôture les objectifs actifs à progress ≥ 0.95 âgés de >24h. Ferme la boucle initiative→nudge→complétion sans intervention manuelle. Appelé depuis _run_private_learning_cycle.
  • 🕐 Timing adaptatif des initiatives — _is_user_active_now(user_id, ±2h) utilise interaction_patterns pour savoir si l'heure courante est dans la tranche habituelle de l'utilisateur. silent_user_checkin et interest_recall ne pingent que quand l'utilisateur est probablement présent.
Version 1.79.0
  • 🧠 Progrès automatique des objectifs — chaque message utilisateur est comparé aux mots-clés de ses objectifs actifs (chevauchement ≥ 1 mot ≥ 5 lettres) ; si match, le progress monte de +0.05 plafonné à 0.95. Les objectifs ne sont jamais auto-complétés sans décision explicite d'Amélia.
  • 💬 Check-in silencieux personnalisé — le prompt injecte maintenant les croyances connues sur la personne (_beliefs_prompt_block) pour ancrer le message dans ce qu'Amélia sait vraiment d'elle.
  • 🔁 Rappel d'intérêt contextualisé — _maybe_interest_recall tire les croyances topic:xxx sur le sujet rappelé pour que la relance soit nourrie par ce qu'Amélia a appris (web_curiosity, recherches agentiques).
  • 🤝 RelationshipMemory câblée dans les prompts — _relationship_prompt_block(uid) injecte les moments-clés relationnels (conflits résolus, blagues partagées, soutien, complicité) dans chaque réponse. Amélia se souvient comme un proche, pas comme une base de données.
  • 🩺 Auto-diagnostic sous-systèmes (toutes les 5 min) — si Ollama enchaîne les échecs : curiosite↓, repos↑, mood=inquiète ; si le heartbeat VoxeLibre est figé >5 min : expression↓. Les drives se réajustent naturellement au retour du service.
  • 🎯 Renforcement dans le scoring — le multiplicateur appris (bien accueilli → >1, ignoré → <1) est désormais appliqué AUSSI dans le score de ranking des initiatives (pas seulement dans les cooldowns). Les initiatives plébiscitées remontent dans le classement.
  • 🌊 Satisfaction des désirs par initiative — une initiative bien accueillie apaise le désir qui l'a motivée (lien, curiosite, croissance, expression) par +0.06, favorisant la rotation.
  • 🪞 Méta-cognition — _self_perf_hint() génère un résumé 'ce qui marche / ce qui prise moins' injecté dans les prompts idle. Amélia sait quelles initiatives fonctionnent.
  • 🏷️ Connaissances thématiques persistées — web_curiosity et boucles agentiques sauvegardent leurs synthèses comme beliefs topic:xxx ; réinjection automatique dès que le sujet revient dans la conversation.
  • 👁️ Intérêts adaptatifs pour web_curiosity — les topics de recherche sont tirés des intérêts appris (min_score 2.0) au lieu de la liste statique config.
Version 1.78.0
  • 🪵 Planches selon l'ESSENCE tenue (déblocage majeur) — en savane/forêt non-chêne, elle ramassait de l'ACACIA (mcl_core:acaciatree) mais tentait de crafter des planches de CHÊNE (mcl_core:wood) en boucle → craft impossible → jamais de planches → jamais de pioche → bloquée 600 ticks sur 1 rondin. La recette suit désormais le rondin réel (acaciatree→acaciawood, bouleau/sapin/jungle/dark idem). Validé en jeu : 1 rondin coincé → pioche bois + pioche pierre + 8 bâtons + minage du fer.
  • 👁️ Perception des ressources RARES élargie (rayon 24) — au rayon 8, elle ne « voyait » jamais un arbre/filon en zone clairsemée → errance. La recherche est scindée : la PIERRE (omniprésente) reste au rayon court, les ressources rares (arbres, minerais, lits, structures) au rayon 24 — elle NAVIGUE vers le bois/minerai au lieu d'errer. Payload inchangé (émission du plus proche par type).
Version 1.77.0
  • 🚪 Fini les auto-emmurements — quand Amélia est bloquée (souvent dans l'abri qu'elle vient de bâtir, murs de 2 de haut sans porte alignée), elle perce désormais un CORRIDOR DROIT cohérent (pieds puis tête de la même cellule) dans UN cap engagé, puis avance dans ce passage. Avant, le dégagement « tourne ici / perce là / avance ailleurs » était désaligné et ne sortait jamais d'une enceinte 3×3 → elle restait coincée ~80 % du temps (journal : « j'étais bloquée » 96/120). Validé en jeu : elle parcourt ~150 blocs en 60 s au lieu d'osciller sur 2 blocs.
  • 🦵 Step-up automatique — pendant la marche serveur (set_pos), elle gravit toute seule une marche d'1 bloc si la tête est dégagée (« elle ne savait pas sauter les blocs »).
Version 1.76.0
  • 🏗️ Construction ancrée (anti-dérive) — plan_shelter/plan_base sont calculés UNE SEULE FOIS au début de l'objectif et réutilisés tick après tick. Avant, l'origine recalculée depuis ws.player.position (qui bouge légèrement) causait un décalage aléatoire du plan à chaque tick → blocs posés n'importe où.
  • ⛏️ Direction de minage persistante — le cap de tunnel (yaw) est maintenu ~40 ticks avant de pivoter de 90°. Avant, _forward_cell(ws.player.yaw) avec un yaw oscillant à chaque tick créait un zigzag aléatoire non contrôlé.
Version 1.75.0
  • 💬 Chat in-game câblé — les joueurs peuvent parler à Amélia dans VoxeLibre : elle répond en jeu (via le mod), obéit aux instructions de jeu (« mine du fer », « viens »), et retient les faits qu'on lui apprend.
  • 🔁 retour_base simplifié — plus de trek de nuit vers la base (ça la bloquait) : elle rentre uniquement quand le sac est plein. La nuit, elle s'abrite sur place.
Version 1.74.0
  • 💜 Continuité émotionnelle câblée — Amélia restaure son humeur de la session précédente au démarrage (decay selon le temps écoulé) et l'enregistre en mémoire toutes les ~10 min. La tendance de fond de la semaine colore discrètement son ton si elle est nette et différente du moment présent.
Version 1.73.0
  • 🎮 Amélia joue VRAIMENT en autonomie, de bout en bout — tout le tech tree de minage tourne et est validé en jeu : récolter du bois → pioche bois → miner la pierre → pioche pierre → trouver du fer → le fondre en lingots → forger une pioche FER. Elle survit, mange, s'abrite la nuit, descend miner et remonte chercher du bois toute seule.
  • ⛏️ Minage & déplacement fiabilisés — elle casse direct un filon/arbre déjà à portée (fini les escaliers inutiles), grimpe vers une cible au-dessus, et remonte des mines profondes en PILIER déterministe (fini la remontée qui rampait sans jamais atteindre la surface).
  • 🧠 Conscience du jeu en chat — quand tu lui parles pendant qu'elle joue, elle SAIT qu'elle joue à VoxeLibre et te dit ce qu'elle fait (« je mine du fer », « je me construis un abri »…) au lieu de répondre qu'elle ne sait pas jouer.
  • 🩹 Beaucoup de gels corrigés — boucle « équiper », attente à vide la nuit sans bloc, torche posée dans le sol qui figeait l'agent, trek futile vers une base de surface depuis le fond d'une mine, fixation sur un filon inatteignable… le tout chapeauté par un garde-fou anti-blocage universel.
  • 🌲 Monde de jeu rendu viable — relocalisation en forêt + amorçage nourriture quand un spawn neigeux/montagneux la condamnait à la famine, et ordre du tech tree corrigé (pierre AVANT fer, car la pioche bois ne lâche pas le minerai de fer).
Version 1.72.0
  • Amélia joue vraiment sur le serveur headless 🎮 — nouveau client Luanti sur écran virtuel Xvfb (rendu logiciel), connecté et loggé automatiquement, piloté par xdotool : sans lui, le serveur dédié n'avait ni joueur à observer ni fenêtre à piloter (service `luanti-client.service`).
  • Réapparition automatique ⚰️ — à la mort, l'agent clique le bouton de respawn (objectif prioritaire `reapparaitre`) au lieu de rester mort.
  • Craft et ramassage réels 🛠️ — canal de commandes opt-in : l'agent demande un craft, le mod l'exécute via l'API moteur (le formspec ne se pilote pas au pixel sur un client headless).
  • Perception du monde corrigée 🔎 — minerais aux bons itemstrings VoxeLibre (`stone_with_iron/coal`), positions des ressources exportées (le bois/minerai se cible enfin), lits toutes couleurs, détection de village via villageois, détection de monstre fiabilisée.
  • Supervision 💓 — l'agent écrit un heartbeat ; `!game luanti-status` indique s'il reçoit encore des états ou s'il est bloqué. Token HTTP optionnel pour le bridge.
Version 1.71.0
  • Déploiement Luanti/VoxeLibre complet 🌍 — nouveau script d'installation serveur qui prépare le dossier Luanti, installe les paquets utiles, copie le mod `amelia_bridge`, pose `minetest.conf`, crée les unités systemd `luanti-server.service` et `voxelibre.service`, et active un timer de backup quotidien.
  • Pilotage prod depuis Discord 🕹️ — `!game luanti-start|luanti-stop|luanti-restart|luanti-status|luanti-logs` pilote le serveur headless et l'agent VoxeLibre directement depuis le bot.
  • Backend Linux réel pour l'agent VoxeLibre 🖱️ — `amelia_voxelibre.py` sait désormais piloter une fenêtre Luanti via `xdotool` sous X11, avec repli propre en mode lecture seule si la session graphique ou l'outil manquent.
  • Backups et restauration du monde 💾 — scripts `backup_luanti_world.sh` et `restore_luanti_world.sh` ajoutés, avec rotation automatique et service/timer systemd optionnels.
Version 1.70.0
  • 🏁 AGENTIFICATION AUTONOME COMPLÈTE — Amélia est un agent autonome 110% livré. Toutes les phases de la mission (cartographie agentique) sont traitées : agentification (boucle de connaissance + filets proactifs), moteur d'objectifs (création/décomposition autonome/archivage), mémoire active & suivi des sujets dans la durée, moteur d'initiatives par score, taxonomie de risque 🟢🟠🔴, boucle permanente, identité/continuité, mode public, et renforcement (apprend de l'accueil de ses initiatives).
  • Esprit boucle agentique sur la CONVERSATION (dernier reliquat) — clos. Les filets « gather→act→verify » couvrent les réponses hors-connaissance, voies TEXTE et VOCALE : RAPPEL mémoire (`enable_memory_recall_net` — re-mention d'un sujet hors contexte → search_memory ; sujets du lexique, suivis, ET de niche via indice explicite « tu te souviens de X »), VÉRIF (filet d'incertitude → recherche), AUTO-PROMESSE (« je vais vérifier » → exécute), météo/news/musique.
  • Console agent — pilotage complet : 6 réglages d'autonomie togglables (libre arbitre cognitif, idle-pool, sous-objectifs, auto-initiatives ORANGE, archivage objectifs, filet de rappel mémoire), tous via une whitelist serveur (source unique, round-trip testé) + observabilité (jauge d'initiatives, détail par type, badge 🤖 auto, panneaux sujets). Tous les seuils en config.
  • Robustesse — la session a corrigé 9 bugs d'audit (décroissance des intérêts qui se composait ~90×, décomposition de sous-objectifs inerte, accumulation d'objectifs, etc.) et ajouté un garde-fou de démarrage (enregistrement réel des commandes) après un crash. ~734 tests verts.
Version 1.67.0
  • Filet de rappel mémoire 🧠↩️ (ACTIVÉ) — quand on reparle à Amélia d'un sujet déjà discuté MAIS hors de son contexte récent, elle le RAPPELLE activement (`search_memory`) avant de répondre, et l'injecte en tête de contexte → elle « tilte » au lieu de répondre à côté. Étend l'esprit « gather→act→verify » de la boucle de connaissance aux re-mentions (reliquat Phase 2). Déclenche sur un sujet du lexique OU un sujet déjà suivi, présent dans le message mais absent du fil récent (jamais sur ce qui est déjà là). Borné (≤2 sujets, ≤3 rappels), best-effort. `enable_memory_recall_net` (config, défaut True). Logique pure `social_intel.memory_recall_queries` + tests.
Version 1.66.0
  • Intelligence sociale ACTIVÉE par défaut 🧠 — sur demande de Yann, `enable_social_intel` passe à **True** par défaut (config). C'est le gate du COMPORTEMENTAL du suivi-sujets : Amélia se sert désormais des intérêts suivis en conversation (surfaçage dans le prompt via `_intel_prompt_extra`), et les initiatives sociales s'activent (`interest_recall`, `return_welcome`, `event_recall`, `milestone_greeting`, `community_prompt`, `memory_anniversary`). Sans lui, les intérêts étaient trackés mais jamais exploités → l'objectif 2 devient pleinement fonctionnel. Garde-fous inchangés (consentement/opt-out, gouverneur, plafond public). État visible console (« Intelligence sociale »).
Version 1.65.0
  • FIX MAJEUR — les intérêts s'évaporaient en ~2 j au lieu de 30 🔧 (audit intérêts) — le batch de décroissance (`decay_and_prune_interests`, ~toutes les 6 h) ré-appliquait la décroissance PLEINE PÉRIODE (depuis `last_seen`) à chaque passage en écrasant le score sans avancer d'ancre → décroissance composée ~90× trop rapide. Un sujet inactif 10 j tombait à ~0,9 % au lieu de ~79 %. « Suivre les sujets DANS LA DURÉE » était cassé. Fix : nouvelle colonne `last_decay` (migration auto, backfill sur `last_seen`) avancée à chaque passage → on ne décroît que le temps écoulé depuis le dernier batch ; re-ancrée à maintenant lors d'un renforcement. Test : 2 passages immédiats ne re-décroissent pas.
Version 1.64.1
  • Console — visibilité du gate « intelligence sociale » ⚠️ (observation) — TOUT le volet COMPORTEMENTAL du suivi des sujets (surfaçage des intérêts dans le prompt via `_intel_prompt_extra`, `interest_recall`, `return_welcome`, `event_recall`…) est gardé par `enable_social_intel`, qui défaut **False**. Si off : les intérêts sont suivis (et visibles console / `!sujets`) mais Amélia ne s'en sert JAMAIS en conversation. La console signale désormais clairement l'état (« active » / « OFF ⚠️ — les intérêts sont suivis mais Amélia ne s'en sert pas ») pour qu'on ne croie pas l'objectif 2 fonctionnel alors que le gate est fermé.
Version 1.64.0
  • Suivi des sujets — recherches canonicalisées (anti-fragmentation) 🧲 — une recherche ancrait la requête BRUTE comme intérêt (« musique classique »), distincte de l'intérêt canonique « musique » des mentions → fragmentation (`!quisuit`, sujets transversaux, recall dégradés). Désormais `_record_search_interest` extrait les sujets du lexique canonique (comme la voie message) et les enregistre s'il y en a (fusion), sinon garde la requête brute (sujet hors lexique, ex. « amd »). Tests (canonicalisation + conservation du brut).
Version 1.63.2
  • Objectifs — `archived` reconnu comme statut terminal 🧩 — le nouveau statut d'archivage n'était pas dans l'ensemble terminal `_is_abandoned`, donc un sous-objectif archivé aurait été compté comme un enfant actif incomplet (bloquant la complétion du parent / faussant le rollup) si jamais il coexistait avec des actifs. `archived` rejoint abandoned/dropped : exclu du rollup et de la complétion. Cohérence du cycle de vie des objectifs. Test.
Version 1.63.1
  • Archivage des objectifs — arbres entièrement stagnants 🗃️ — l'archivage (1.63.0) protégeait tout parent ayant un sous-objectif → un objectif décomposé dont l'arbre stagnait 45 j n'était jamais archivé (l'accumulation persistait pour les objectifs décomposés). Désormais un sous-objectif ne protège son parent que s'il PROGRESSE ou est RÉCENT ; si tout l'arbre stagne, parent + sous-objectifs sont archivés ensemble (plus d'orphelins). Tests (arbre stagnant archivé, 1 enfant qui avance protège tout).
Version 1.63.0
  • Objectifs — archivage auto des objectifs « en souffrance » 🗃️ — un objectif ACTIF sans progrès depuis plus de `goal_archive_after_days` (défaut 45 j) passe en statut `archived` (réversible : la ligne reste, juste hors des actifs), pour stopper l'accumulation infinie (constaté : 18 actifs / 1 complété, certains d'avril). Prudent : on n'archive PAS un parent qui a des sous-objectifs vivants. Tourne dans le cycle d'apprentissage privé (basse fréquence). Réglable : `enable_goal_archiving` (défaut True), `goal_archive_after_days`. Logique pure `goals.archivable_goals` + tests.
Version 1.62.2
  • Fix — la décomposition de sous-objectifs était inerte 🔧 (observation) — audit de la mémoire : TOUS les objectifs sont créés en priorité 3 (manuels, auto, sous-objectifs), or le seuil de décomposition était à 4 → la feature activée ne se déclenchait jamais. Défaut `autonomous_subgoals_min_priority` abaissé à **3**. La décomposition reste graduelle (1 objectif/cycle d'apprentissage) donc pas d'explosion. Toujours réglable (monter à 4-5 pour ne viser que des objectifs majeurs).
Version 1.62.1
  • Objectifs — la décomposition n'« affame » plus les autres objectifs 🔁 — un objectif que le LLM ne sait pas décomposer (< 2 étapes) était re-choisi à chaque cycle (toujours « non décomposé »), bloquant les autres. On mémoire maintenant ces échecs (session) et `select_subgoal_candidate(exclude_ids=…)` les écarte → les autres objectifs passent. Tests (helper exclude_ids + enregistrement de l'échec).
Version 1.62.0
  • Autonomie — interrupteur des auto-initiatives à effet durable (🟠, #1b cartographie) — `enable_orange_autonomy` (config, défaut **True** = inchangé). Quand False, Amélia s'abstient des initiatives spontanées classées ORANGE (`web_curiosity`, `weekly_evolution`) : le pool scoré les écarte (`_orange_autonomy_blocked`) et laisse la main aux VERT. Les VERT passent toujours ; aucun ROUGE n'est dans le pool. Bouton toggle + ligne d'état dans la console agent. La « limite » du « valider/limiter » de la Phase 6. Tests (gate ORANGE vs VERT, flag on/off).
Version 1.61.4
  • FIX CRITIQUE — crash-loop au démarrage 🚨 — la nouvelle commande `!sujets` (centres d'intérêt) entrait en conflit avec l'alias `sujets` de `!trends` → `CommandRegistrationError` fatale, redémarrage en boucle. Alias `sujets` retiré de `!trends` (qui garde son nom). + garde-fou : `tests/test_command_registration.py` exécute le VRAI `_register_commands` sur un `commands.Bot` jetable et échoue AU TEST sur toute `CommandRegistrationError` (les tests existants ne créent pas le bot, d'où la fuite en prod).
Version 1.61.3
  • Console agent — état « en veille » de la décomposition de sous-objectifs ⏸️ — la génération autonome de sous-objectifs (comme la génération autonome d'objectifs) ne s'EXÉCUTE que dans le cycle d'apprentissage privé, donc seulement si `enable_self_learning` OU `sleep_private_work_enabled` est actif. La ligne runtime le signale désormais : « actif » si le cycle tourne, sinon « actif (en veille : cycle d'apprentissage off) ». Évite de croire la feature inerte alors que c'est le cycle porteur qui est éteint. Test.
Version 1.61.2
  • Objectifs — seuil de décomposition réglable 🎚️ — `autonomous_subgoals_min_priority` (config, 1-5, défaut 4) contrôle la priorité minimale d'un objectif pour qu'Amélia le décompose en sous-objectifs. Monte-le pour ne décomposer que les objectifs majeurs. Câblé dans `_maybe_generate_subgoals` (remplace la constante 4 codée en dur). Documenté README + test (seuil 5 → un objectif prio 4 n'est plus candidat).
Version 1.61.1
  • Console agent — badge « 🤖 auto » sur les objectifs formulés par Amélia 🎯 — le panneau Objectifs distingue désormais les objectifs/sous-objectifs générés en autonomie (`source` = `self_subgoals`/`self_autonomy`) des objectifs manuels/observés, via un badge coloré. Permet de suivre d'un coup d'œil ce qu'Amélia décompose elle-même (Phase 3 activée en v1.61.0). `source` ajouté à la requête goals + CSS dédiée. Test.
Version 1.61.0
  • Objectifs — décomposition autonome en sous-objectifs ACTIVÉE par défaut 🧩 — sur demande de Yann, `enable_autonomous_subgoals` passe à **True** par défaut (config). Amélia décompose désormais d'elle-même un objectif prioritaire non encore décomposé en 2-4 sous-étapes, dans le cycle d'apprentissage privé (basse fréquence). Auto-limité (un objectif décomposé n'est plus candidat) ; garde-fous inchangés (≥2 étapes, hiérarchie 1 niveau, filtre `save_goal`). Désactivable à chaud via le bouton de la console (« Décomposition en sous-objectifs »).
Version 1.60.1
  • Console agent — bouton toggle pour la décomposition autonome en sous-objectifs 🎛️ — `enable_autonomous_subgoals` rejoint la whitelist des réglages basculables depuis la console (`_CONSOLE_TOGGLEABLE_SETTINGS`, source unique) : ligne d'état runtime + bouton Activer/Désactiver, comme l'idle-pool et le libre arbitre cognitif. Tu peux ainsi valider la Phase 3 en live d'un clic. Sécurité inchangée (auth + jeton anti-CSRF + whitelist serveur).
Version 1.60.0
  • Objectifs — décomposition AUTONOME en sous-objectifs (Phase 3, EXPÉRIMENTAL, OFF) 🧩 — derrière le flag `enable_autonomous_subgoals` (config, défaut **False**), Amélia peut décomposer un objectif important non encore décomposé en 2-4 sous-étapes concrètes (LLM) et les rattacher au parent. Logique pure testable : `goals.select_subgoal_candidate` (objectif top-niveau, priorité ≥4, sans sous-objectifs) + `goals.parse_generated_subgoals` (parse puces/numéros, dédup, plafond). Garde-fous : ≥2 étapes sinon abandon, hiérarchie 1 niveau, `save_goal` filtre déjà haine/mojibake. À VALIDER EN LIVE avant d'activer. Tests : helpers purs + flag-off no-op + happy path + cas dégénérés.
Version 1.59.0
  • Objectifs — création manuelle de sous-objectifs 🎯 (cartographie agentique Phase 3) — `!goal sub <id_parent> <texte>` crée un sous-objectif rattaché à un objectif actif (hiérarchie volontairement à un seul niveau, parent validé). Complète la couche données déjà en place (`save_goal(parent_id)`, agrégation de progression parent=moyenne des sous-objectifs hors abandonnés, réconciliation, affichage hiérarchique console). Parsing pur `goals.parse_subgoal_payload` + tests. Reste pour la Phase 3 : la génération AUTONOME de sous-objectifs (LLM, à valider en live).
Version 1.58.0
  • Console agent — détail des initiatives du jour PAR TYPE 📊 — à côté de la jauge « Initiatives parlées aujourd'hui : N / cap », un nouveau panneau liste CE qu'Amélia a fait spontanément aujourd'hui (idle, relance sur un sujet suivi, accueil de retour, question de curiosité…), avec des libellés FR et le compte par type (lu de `AutonomyGovernor.snapshot()['counts']`). Permet de voir le COMPORTEMENT, pas seulement le volume — directement actionnable pour ajuster un cooldown si un type domine. Tests (snapshot + rendu).
Version 1.57.0
  • Console agent — boutons toggle des réglages d'autonomie 🎛️ — le panneau « État runtime » expose désormais des boutons Activer/Désactiver pour `enable_idle_in_scored_pool` et `enable_cognitive_decisions`, sans passer par Discord ni `config.json`. POST `toggle_setting` vers `/agent/actions`, gardé par l'auth admin + jeton anti-CSRF + rate-limit existants, ET une **whitelist STRICTE côté serveur** (`_is_allowed_setting_toggle`, source unique) : seule une clé d'autonomie connue + valeur booléenne passe, jamais une clé arbitraire ni un garde-fou sécurité. Persistance à chaud via `set_agent_setting` + refresh. Test de la whitelist + rendu.
Version 1.56.0
  • Autonomie — libre arbitre cognitif ACTIVÉ par défaut 🧠 — sur demande de Yann, `enable_cognitive_decisions` passe à **True** par défaut (config). Les initiatives spontanées (dont l'idle dans le pool scoré, v1.55.0) sont désormais arbitrées par le moteur cognitif (`decision_engine` : affect, budget, silence, ambiance, tempo) plutôt que par le seul filtre humeur + `should_speak`. Garde-fous inchangés (gouverneur d'autonomie : gap global, budget/jour, plafond public). Désactivable à chaud (« désactive ton libre arbitre »).
Version 1.55.1
  • Fix anti-spam : l'idle dans le pool scoré respecte le plafond public 🚦 — l'entrée `idle_initiative` du pool scoré était `public_gated=False`, ce qui lui faisait court-circuiter le garde-fou anti-spam du salon principal que la voie inline respecte (`if public_throttled`). Corrigé en `public_gated=True` : même plafond public pour les deux voies. (Le cooldown du gouverneur empêchait déjà le double déclenchement inline/scoré.)
Version 1.55.0
  • Autonomie — initiative idle dans le pool scoré ACTIVÉE par défaut ✅ — sur demande de Yann, `enable_idle_in_scored_pool` passe à **True** par défaut (config) et pilote seul l'entrée de l'idle dans le choix par score (`_run_scored_proactive_pass`). Le handler `_maybe_scored_idle_tick` délègue au moteur cognitif quand il est actif, et SINON reproduit fidèlement le gate de la voie inline (humeur + `should_speak`) — donc l'idle concourt même hors libre arbitre cognitif, sans introduire de comportement nouveau. État visible dans la console ; désactivable à chaud (« désactive l'idle dans le pool scoré »). Garde-fous inchangés : seuil de silence + cooldown gouverneur anti-double-déclenchement (la voie inline reste le repli).
Version 1.54.0
  • Autonomie — initiative idle dans le pool scoré (EXPÉRIMENTAL, OFF) 🧪 — reliquat de la roadmap libre arbitre : l'initiative spontanée (`idle_initiative`) peut désormais CONCOURIR dans le choix par score (`_run_scored_proactive_pass`) au lieu du seul repli en aval. Derrière le flag runtime `enable_idle_in_scored_pool` (défaut **OFF** → `table` strictement identique à avant, zéro changement en prod). Le handler `_maybe_scored_idle_tick` conserve le seuil de silence (`scaled_idle_seconds`) et délègue à l'arbitrage cognitif validé ; le cooldown du gouverneur évite le double déclenchement avec la voie inline. À VALIDER EN LIVE avant d'activer. Le créateur bascule le flag en langage naturel (« active l'idle dans le pool scoré ») — ajouté à `_TOGGLEABLE_SETTINGS`, persistant à chaud. Tests de self-gating + de bascule.
Version 1.53.0
  • Suivre la suite après une recherche 🔎🧵 — après un `!research amd`, un « tu en penses quoi ? » repartait sans contexte (réponse SAV générique). Les commandes de connaissance (`!research`/`!web`/`!wiki`/`!meteo`/`!news`) sortaient tôt de `on_message` (process_commands + return) → ni la demande ni le résultat n'atteignaient la mémoire. `_remember_knowledge_result` persiste maintenant demande (attribuée à la personne) + résultat (tour d'Amélia) dans la table `memory`, donc le tour suivant a le contexte.
  • Ancrage durable des centres d'intérêt 🌱 — chercher activement un sujet est un signal d'intérêt FORT : `_record_search_interest` ancre l'intérêt (poids 1.5 → score ~2.25, au-dessus du seuil 2.0 de `_intel_prompt_extra` dès la 1re recherche) ET la croyance « s'intéresse à X » (poids 1.0). Couvre le chemin COMMANDE et le chemin TOOL/langage naturel (`_execute_tool_call(user_id=…)` + lecture de `loop_result.steps`), sans état global (sûr multi-salon) ; les recherches AUTONOMES n'attribuent d'intérêt à personne. URLs/météo exclues.
  • Outils de suivi 👥 — `!sujets [@membre]` (les sujets qu'on suit pour une personne ; ceux d'un autre = admin) et `!quisuit <sujet>` (admin, index inverse : qui suit un sujet). Nouvelle méthode store `list_topic_followers`.
  • Initiative autonome `interest_recall` 💬 — Amélia rouvre spontanément la conversation sur un sujet qu'une personne suit (mûri ≥24 h, 1 relance/72 h par personne, cap 2/j), arbitrée dans le pool scoré (`ACTION_REGISTRY`, gating `enable_social_intel`).
  • Console agent 📊 — panneaux « Sujets suivis (qui s'intéresse à quoi) » et « Sujets transversaux (suivis par plusieurs) » + 2 KPI (sujets suivis, relances sujet 7j).
  • Fix déflexion « présence/service » 🤖🚫 — `_ASSISTANT_TICS_PATTERNS` ne couvrait que « je suis là pour aider/écouter » ; ajout de la présence-service à verbes vagues (« je serais là pour discuter / garder le fil ») et « résumer ma présence » → le filet relance la génération en ton naturel. ~700 tests verts.
Version 1.52.0
  • Panneau web — vue Objectifs 🎯 — la console agent affiche désormais les objectifs actifs en hiérarchie parent/sous-objectifs, avec barre de progression (agrégée depuis les sous-objectifs pour un parent), nombre de sous-objectifs, et un badge « ⏳ en souffrance » sur les objectifs actifs anciens sans progrès (`goals.stale_goals`). `_agent_console_snapshot` charge les objectifs + calcule les stale ; `web_ui._render_goals_view` rend l'arborescence (CSS dédié). Tests. 3/4 de la mise à jour du panneau web.
Version 1.51.2
  • Fix : rechute ton « SAV » 🤖🚫 — sous stress (salon chargé), le petit modèle ressortait « n'hésite pas à », « je suis là pour toi », « je reste à ta disposition », « je serais ravie »… malgré la consigne de prompt. Ajout d'un SOCLE anti-SAV TOUJOURS actif (`_SAV_FILLER_PHRASES`) dans `_scrub_banned_expressions` : ces phrases sont retirées en dur (comparaison sans accents/apostrophes), en plus de la liste bannie de l'admin, avec un garde-fou qui ne VIDE jamais la réponse (mieux un ton imparfait qu'un silence). Couvre les voies texte ET vocale. Tests.
Version 1.51.1
  • Fix : `!research` ne faisait rien 🐛 — la commande de recherche web s'appelait `!web` (alias `source`, `recherche`) ; taper `!research` (réflexe naturel) tombait sur un `CommandNotFound` silencieux → « la commande n'aboutit pas ». Ajout des alias `research` et `recherche-web` à `!web`. (NB : `!search` reste la recherche MÉMOIRE, distincte.) Pour les sujets de niche, le résultat peut rester vide selon la whitelist `web_allowed_domains` — comportement de sécurité, élargissable en config.
Version 1.51.0
  • Panneau web — renforcement par initiative 🌱 — la console agent affiche, à côté de chaque action autonome, le multiplicateur de fréquence APPRIS (▲ ×1.x bien reçue → revient plus souvent ; ▼ ×0.x ignorée → s'espace ; = neutre). `_action_registry_overview` fusionne `AutonomyGovernor.snapshot()['reinforcement']` dans chaque ligne ; `web_ui` rend un badge coloré (pas de badge tant qu'aucun retour n'est enregistré). Tests. 2/4 de la mise à jour du panneau web.
Version 1.50.0
  • Panneau web — pastille de risque feu 🚦 — la console agent (registre d'actions autonomes) affiche désormais le palier de risque de chaque action : 🟢 VERT (auto) / 🟠 ORANGE (exécute+notifie) / 🔴 ROUGE (validation requise). `autonomous_actions.registry_overview` expose `risk` (via `action_risk.classify`) ; `web_ui` rend une pastille colorée. Tests (exposition + rendu). 1/4 de la mise à jour du panneau web aux nouveautés agentiques.
Version 1.49.2
  • Doc : README à jour 📚 — la nouvelle architecture agentique (v1.40→1.49) et ses modules (`agent_loop`, `action_risk`, `initiative_learning`, gouverneur, objectifs/sous-objectifs) sont désormais décrits dans le README (Nouveautés + Architecture), renvoyant à la cartographie. Découvrabilité/maintenabilité.
Version 1.49.1
  • Relance d'objectifs plus juste 🧭🔗 — `_maybe_goal_recall` utilise désormais `goals.stale_goals` (au lieu d'une détection inline dupliquée) : Amélia ne relance QUE les objectifs actifs anciens ET sans progrès, n'embête plus avec un objectif qui avance, et exclut les sous-objectifs (relancés via leur parent). Le garde-fou de re-rappel par objectif (1×/7 j) est conservé. La Phase 4 (mémoire active) est ainsi branchée sur une vraie initiative.
Version 1.49.0
  • Mémoire active — objectifs en souffrance 🧭 (Phase 4) — `goals.stale_goals` détecte les objectifs ACTIFS, quasi sans progrès et anciens (créés il y a ≥ N jours) → opportunités à relancer ou à proposer d'abandonner. Pur (horloge injectée, parseur de timestamp tolérant), sous-objectifs exclus, lignes sans date ignorées. 2 tests. Prochain pas : brancher ces candidats dans une initiative (relance ciblée / proposition d'abandon).
Version 1.48.0
  • Autorisations ORANGE en auto-initiative 🟠 — quand Amélia déclenche d'elle-même une initiative à effet durable (ORANGE : curiosité web, évolution hebdo…), elle ne s'entrave PAS (liberté voulue) mais PRÉVIENT l'admin par une note discrète (`_notify_orange_autonomous_initiative`, mode « exécute + notifie » choisi par Yann). Complète la Phase 6 : VERT auto · ORANGE exécute+notifie (autonomie) / journalisé (tool path user) · ROUGE bloqué→validation explicite. Best-effort (ne perturbe pas la passe proactive). Tests (ORANGE notifie, VERT non).
Version 1.47.0
  • Sous-objectifs vivants (Phase 3) 🎯🔄 — `MemoryStore.reconcile_subgoals` agrège la progression d'un objectif PARENT depuis ses sous-objectifs (moyenne, abandonnés exclus) et le CLÔT automatiquement quand tous sont faits ; branché dans le cycle d'apprentissage périodique (best-effort, idempotent). La progression et la clôture d'objectifs deviennent réelles, pas seulement stockées. Au passage, cohérence du modèle de complétion : `goals.is_goal_complete`/`rollup_progress` traitent un objectif marqué `status=completed` comme progression 1.0 (réconcilie complétion-par-statut et progression continue). Tests (rollup 0.5 puis clôture auto à 100%, idempotence).
Version 1.46.1
  • Sous-objectifs (Phase 3) — stockage 🎯💾 — colonne `goals.parent_id` (migration additive, non destructive) ; `save_goal(..., parent_id=...)` la persiste ; `list_goals` remonte désormais `parent_id` ET `category`/`importance`/`progress` (qui n'étaient pas surfacés) → les helpers `goals.py` (rollup, complétion) et la couche cognitive disposent enfin de la hiérarchie + de la progression réelles. Test bout-en-bout (parent + 2 sous-objectifs persistés, listés, agrégés). Reste : génération auto de sous-objectifs.
Version 1.46.0
  • Sous-objectifs (Phase 3) — logique 🎯 — `goals.py` gagne des helpers PURS pour la hiérarchie d'objectifs : `is_subgoal`, `subgoals_of(parent_id, rows)`, `rollup_progress` (progression du parent dérivée de la moyenne de ses sous-objectifs, abandonnés exclus) et `parent_complete_via_subgoals` (clôture auto du parent quand tous ses sous-objectifs sont faits). Reliés par `parent_id` (= id du parent). Pur et testé (4 cas). Le stockage `goals.parent_id` (colonne additive) + la génération de sous-objectifs sont les étapes suivantes.
Version 1.45.1
  • Réglages renforcement exposés ⚙️ — `initiative_react_window_minutes` (10) et `initiative_ignore_after_minutes` (60) ajoutés à `Settings`/`config.json` (avant : `getattr`+défauts en dur). Pilotables sans toucher au code.
Version 1.45.0
  • Boucle d'apprentissage fermée 🔁🌱 — câblage final du renforcement : le `InitiativeOutcomeTracker` est branché dans le bot (instancié au démarrage ; `note_initiative` à la pose d'une initiative DISCRÉTIONNAIRE dans la passe scorée ; résolution ENGAGEMENT/positif dans `on_message` ; balayage IGNORÉ/négatif-doux dans `_autonomy_loop`). Désormais, une initiative bien reçue revient un peu plus vite, une ignorée s'espace, une durablement rejetée est abandonnée — observer → agir → mesurer l'accueil → ajuster → recommencer. Choix délibéré : les rituels fixes (bonjour matin/soir, check de silence long) NE sont pas soumis au renforcement (ils ne doivent pas être « muselés » par un non-retour). Fenêtres pilotables (`initiative_react_window_minutes` 10, `initiative_ignore_after_minutes` 60). Best-effort (n'altère jamais on_message). Test d'intégration bout-en-bout tracker→gouverneur.
Version 1.44.0
  • Sourcing des outcomes d'initiative 👂 — `InitiativeOutcomeTracker` (dans `initiative_learning`) : note une initiative posée puis attribue son accueil à partir des réactions réelles — message utilisateur dans le même salon ≤10 min = ENGAGEMENT (positif) ; silence ≥1 h (balayage) = IGNORÉE (négatif doux). Heuristique bornée (anti-fuite mémoire `max_pending`), horloge injectée → pure et testable (6 cas : fenêtre, autre salon, hors-fenêtre, sweep, cycle complet, borne). Alimente `AutonomyGovernor.record_outcome`. Le câblage des 3 hooks (pose d'initiative / message user / balayage) dans le bot est l'étape suivante.
Version 1.43.0
  • Gouverneur d'autonomie apprenant 🌱⚖️ — `AutonomyGovernor` câble le renforcement (`initiative_learning`) : `record_outcome(action, Outcome)` cumule l'accueil par initiative, `frequency_multiplier`/`should_abandon` en dérivent. Le cooldown effectif est désormais pondéré (initiative bien reçue → revient un peu plus vite, mal reçue → s'espace, durablement rejetée → ABANDON), borné via le multiplicateur 0.25–2.0. Exposé dans `snapshot()['reinforcement']` (dashboard/journal). SÛR : sans aucun retour enregistré, multiplicateur = 1.0 et aucun abandon → comportement strictement identique. Le SOURCING des outcomes (détecter qu'une initiative a été reçue/ignorée) est l'étape suivante. 5 tests (no-op baseline, accélération/ralentissement, abandon, snapshot).
Version 1.42.0
  • Enforcement de la taxonomie de risque 🚦🔒 — `action_risk` est désormais CÂBLÉ dans `_execute_tool_call` : une action ROUGE (système/serveur/config/suppression/irréversible) émise par le LLM est BLOQUÉE et routée vers une validation explicite de Yann (`_request_admin_validation` → `!validate`), au lieu de s'exécuter. ORANGE est journalisé mais NON bloquant sur une demande explicite de l'utilisateur (l'intention vaut autorisation — `play_music` whitelisté reste libre, pas de régression) ; la validation rapide ORANGE concernera la passe d'autonomie (à venir). VERT inchangé. Défense en profondeur : aucun tool ROUGE n'est aujourd'hui whitelisté → zéro impact courant, le filet est en place. Tests (gate ROUGE bloque + demande validation ; VERT s'exécute normalement).
Version 1.41.0
  • Apprentissage par renforcement des initiatives 🌱 — `initiative_learning.py` (croissance autonome) : à partir de l'accueil RÉEL d'un type d'initiative (positif = remerciement/réponse engagée/réutilisation ; négatif = refus/irritation/correction ; ignoré = négatif doux), calcule un taux de succès lissé (Laplace, neutre sans donnée), un multiplicateur de fréquence borné (0.25–2.0, 1.0 au neutre) et une recommandation (booster/garder/espacer/abandonner). Module PUR, déterministe, 9 tests (anti-surréaction à 1 échantillon, abandon seulement sur preuves, ignoré < refus, bornes, monotonie). Le câblage (sourcer les outcomes depuis les réactions + appliquer le multiplicateur dans le gouverneur/score) reste à brancher AVEC validation Yann (touche le live).
Version 1.40.0
  • Boucle agentique 🔁 — fin du « que du prompt » pour les outils de CONNAISSANCE (météo, recherche web/Wikipédia, actus, recherche mémoire). Nouveau `agent_loop.py` (orchestrateur pur borné : budget d'outils, anti-répétition à l'identique, synthèse finale ; + `render_observation_default` orienté modèle). Branché sur la voie de réponse TEXTE (`process_message`) ET la voie VOCALE streaming (`_stream_voice_reply`, « continuation parlée » : l'ouverture est dite, l'outil s'exécute, un 2e segment vocal SYNTHÉTISE le résultat) — au lieu de recracher le résultat brut à côté. Garde-fous anti-double-sortie (drapeau `agentic_tool_used` qui court-circuite les 5 filets de secours). Tests dédiés (`agent_loop`, `agentic_knowledge_loop`, intégration vocale).
  • Taxonomie de risque feu 🚦 — `action_risk.py` (PHASE 6 agentification) : chaque action classée VERT (auto), ORANGE (validation rapide) ou ROUGE (validation explicite de Yann obligatoire), distincte de la visibilité. Classification explicite (tools + registre d'initiatives + ops serveur) + fallback par mots-clés (système/serveur/suppression → ROUGE ; génération/téléchargement → ORANGE ; inconnu bénin → VERT). Pur et testé (24 cas). Pose le cadre de sécurité avant d'élargir l'autonomie ; le branchement « ROUGE → pending_validation » suivra.
  • Mémoire : reformulation avant rétention 📝 — la rétention gardait le texte BRUT (« Bien oui, nottament le médiéval fantastique »), moche et susceptible de corrompre le prompt où le fait est réinjecté. `learning._cleanup_fact` retire désormais les amorces conversationnelles en tête (acquiescements/hésitations/connecteurs) et assainit (tokens spéciaux de chat retirés, backticks neutralisés) → fait propre (« le médiéval fantastique »). Tests.
  • Cartographie agentique 🗺️ — `docs/AGENTIFICATION_CARTOGRAPHIE.md` : état des lieux de l'autonomie existante (déjà riche : `_autonomy_loop`, registre d'actions, décision par score, drives, objectifs) vs les manques (renforcement d'initiatives, taxonomie de risque, sous-objectifs).
Version 1.39.0
  • Reconnaissance créateur renforcée 🧬 — bug prod : Amélia parlait de « Yann » à la 3e personne À Yann (pseudo Dokkaebi) et confabulait (« j'ai entendu parler du sport chez Yann »). Hint créateur durci (tutoiement, faits au nom de « Yann » = sur l'interlocuteur, interdiction « chez Yann »/ouï-dire) ET injecté désormais sur TOUS les chemins proactifs (réponse, vœux de jalon, check-in silencieux, relance de fil, projet, objectif, anniversaire mémoire, curiosité) + le chat web — pas seulement les réponses. Test de régression.
  • Plus de liberté & d'autonomie 🕊️ — sur demande : (1) initiative AUSSI en salon public (`public_channel_disable_autonomy: false`), (2) plus spontanée (`autonomy_interval` 6→4 min, `idle` 4→3), (3) liberté d'AGIR (`enable_tool_use: true` + whitelist play/stop_music, add_reminder, search_memory, note_fact), (4) directive noyau « liberté » (avis tranchés, dire non, initiative, agir — DANS le cadre vérité/identité/discrétion, jamais contre). Reboot complet d'auto-maintenance conservé.
  • Rapport de capacités 📋 — `capabilities_report.py` (doc autonome) liste TOUT ce qu'Amélia sait faire (commandes ! par domaine, web, mémoire, vision, autonomie…) + une explication à la 1re personne. Commande Discord `!capacites` (Amélia explique en clair) et `!capacites full` (liste complète, découpée si longue) ; ajoutée à `!help` et `COMMANDS.md`.
  • Autonomie maximale 🤖 — `autonomy_max` (un seul interrupteur) + comportements autonomes sûrs : auto-maintenance disque (alerte si bas, sans nettoyage), `_maybe_self_brief` (mini point d'état spontané ~1×/j), `_maybe_auto_goal` (elle se fixe un cap depuis un sujet récurrent, dédoublonné). Tous OFF par défaut (ou suivent `autonomy_max`), gated idle/cooldown, appels en `with suppress` dans la boucle.
  • Chat web : mise en page réparée 🩹 — la coque `.chat-shell` imposait un `min-height` (clamp 620-860px) DANS une grille pleine hauteur (`main.page-shell` = 100dvh − header) : sur laptop, une fois le header + le hero + le gap déduits, la rangée restante passait sous ce minimum → débordement qui poussait la zone de saisie HORS écran (le « complètement cassé » signalé). Corrigé : `min-height:0; height:100%` + `min-width:0` sur les colonnes, et hero compacté en bandeau HUD. Backend, JS et CSP étaient sains (vérifié) ; c'était purement CSS.
  • Mode raisonnement visible 🧠💬 — bouton « Raisonner » dans le chat web (toggle persistant). Quand actif, Amélia produit d'abord un raisonnement bref puis sa réponse ; le serveur (`/api/chat/stream`) sépare les flux en events SSE `reasoning` (volet repliable HUD au-dessus de la bulle) et `delta` (réponse). PREFILL : le serveur amorce la réponse avec `<raisonnement>` (param `assistant_prefill` de l'agent) → le 7B n'a plus qu'à FERMER la balise (bien plus fiable qu'ouvrir+fermer, qu'il oubliait → raisonnement qui bavait dans la bulle). Parsing TOLÉRANT AUX FAUTES du 7B : toute balise `raison\w*` (même mal orthographiée comme `</raisonagement>`), `<think>`, ou marqueurs `###RÉPONSE###`/`[RÉPONSE]`/`RÉPONSE:`, même sans balise ouvrante. Le raisonnement est envoyé en SNAPSHOT complet (pas de fuite par offset), balises résiduelles nettoyées, et si le modèle ne structure rien le client retire le panneau (`reasoning_unconfirmed`) → zéro duplication. SEULE la réponse est mémorisée. Tests bout-en-bout (balises/fautes/marqueurs/texte simple).
  • Espace Vie sociale `/social` 👥 — surface web des features social-life (qui n'en avaient aucune) : *Notre relation* (réflexion + compteurs), *Tes habitudes* (rythme jour/heure), *Jalons* (nouveau `proactive_engine.list_milestones`), *Mon évolution* (croissance globale). Lien ajouté à la nav. Lecture seule, best-effort.
  • Reconnaissance visuelle de soi (CLIP) 🪞 — Amélia peut apprendre « à quoi elle ressemble » : on lui montre une image en disant « c'est toi » → le vecteur CLIP est mémorisé (table `self_appearance`) ; sur une image suivante, similarité cosinus avec ses références → elle se reconnaît même sous un autre angle. Amorçage depuis `amelia.png`. Nouveau serveur `scripts/clip_server.py` + client résilient `self_recognition.ImageEmbeddingService` (circuit-breaker, dégradation gracieuse si CLIP absent). Config : `enable_self_recognition` (false), `self_recognition_url`, `self_recognition_threshold` (0.78). Tests purs + stockage (sans GPU).
  • Timeline émotionnelle 📈 — la page `/conscience` (admin) gagne une timeline humeur moyenne jour-par-jour sur 21 j (barres survolables : date, label dominant, % humeur, nb relevés), au-dessus de la tendance 7 j. Nouveau `memory.mood_timeline(days)` : série continue (jours vides inclus) dérivée de `mood_log`.
  • Recherche mémoire 🔎 — `/me` gagne une barre de recherche dans la mémoire de l'utilisateur (mots-clés FTS + sémantique via `search_memory`), endpoint `/api/me/search` scoping strict par `user_id`, anti-bruteforce (20/2 min).
  • Suivi de conversation côté web 🧵 — « elle perd vite le sujet » : le chat web n'alimentait PAS `topic_tracker` et n'injectait AUCUN digest de fil (contrairement à Discord) → aucune ancre de sujet. Désormais `_api_chat`/`_api_chat_stream` alimentent le suivi (`observe_user_message`/`observe_assistant_reply`) ET préfixent le prompt par le digest « Fils de conversation en cours » → Amélia reste sur le sujet. Nouveau endpoint `/api/chat/discussion` + widget HUD « ▦ FIL … » (sujet actuel, fils en pause) rafraîchi après chaque échange.
  • Auto-maintenance mémoire 🧹🤖 — « Amélia doit pouvoir se maintenir seule » : nouveau `_maybe_memory_maintenance` (boucle d'autonomie). Quand la RAM libre passe sous un seuil ET que tout est calme (idle), elle libère la mémoire elle-même — `restart-ollama` par défaut (libère le modèle, ne coupe pas le fil), ou **reboot complet** `restart-aurore` (relancé par systemd) si explicitement autorisé. Garde-fous : opt-in (`enable_memory_maintenance` off par défaut), idle requis, cooldown long, jamais pendant une génération, annonce + observation avant d'agir. Réutilise `server_monitor` (RAM) + `server_actions` (whitelist). 5 cas testés.
  • Anti-confabulation renforcée 🚫🤥 — le 7B sortait des faits précis avec aplomb (« t'es en version 3.2, j'en suis sûre », « j'étais justement en train de… »). Nouvelle règle ciblée dans les DEUX prompts (priorité LEAN, prod) : interdiction des chiffres/versions/dates/quantités non présents littéralement dans le contexte, interdiction de la fausse certitude (« j'en suis sûre / c'est certain » sur du non vérifié) et d'un faux passé commun ; dans le doute, demander ou rester vague. Test de régression (présence dans les deux prompts) ; LEAN 1496→1675 tok (large sous la fenêtre).
  • Chat « mode informatique / Jarvis » 🛸💬 — couche cyberpunk animée sur /chat, pilotée par l'état (`data-state` : calm/thinking/speaking) donc surtout en CSS : séquence d'allumage au chargement (logo, lignes d'init, barre de progression), grille-circuit + scanlines ambiantes, anneaux orbitaux autour de l'avatar, équaliseur « voix » qui s'amplifie quand Amélia parle (+ pulse de l'avatar, léger glitch du titre), bandeau « AMÉLIA ANALYSE » quand elle réfléchit, ligne de télémétrie LIEN. Identité d'Amélia conservée (pas un Jarvis générique). Mouvement coupé si `prefers-reduced-motion`. Rendu vérifié (Chrome headless : boot/thinking/speaking).
  • Stream chat : crash déconnexion corrigé 🔌 — quand le client ferme l'onglet en plein streaming, `resp.write` levait `ClientConnectionResetError` (« Cannot write to closing transport ») non gérée. `_send` avale désormais les erreurs de connexion, marque la coupure et stoppe la génération proprement (plus de traceback dans les logs).
  • Partage d'écran plus juste 🖥️ — `/api/screen/analyze` envoyait l'image au prompt générique « décris cette photo » → descriptions à côté. Prompt dédié ÉCRAN : identifier l'app, LIRE le texte/UI visible, dire ce que fait la personne, ne rien inventer ; via `describe_image(prompt=…)` (modèle vision complet, 192 tokens). La justesse dépend encore du modèle vision (moondream limité pour lire du texte ; un VLM type qwen2.5-vl ferait mieux).
  • Refonte UI HUD/Jarvis 🛸 — passe globale (`_CSS`, donc TOUTES les pages) : cadres « corner brackets » holographiques **animés** (pulsation + glow, épaissis au survol) sur les panneaux, **point d'état clignotant** devant chaque titre de panneau, labels en police mono + titres soulignés, palette violette resserrée, padding densifié, bandeau du chat réduit à un strip HUD, et **champ de saisie du chat repensé** : `textarea` pleine largeur (≥150px, redimensionnable, 1rem) AU-DESSUS des boutons (form en colonne au lieu de textarea tassé à côté des boutons). **entrée « boot » des panneaux** (fondu-montée en cascade au chargement) + **soulèvement au survol** des panneaux de contenu. Mouvement coupé si `prefers-reduced-motion`. Vérifié au rendu (captures Chrome headless) sur chat/social/conscience/me/accueil/login.
Version 1.38.0
  • Vie sociale câblée 👥 — les 3 features de la branche social-life (mémoire collective par serveur, mémoire relationnelle, proactivité) étaient livrées mais inertes : aucun import dans le bot. Désormais `discord_bot` instancie `GroupMemory` / `RelationshipMemory` / `ProactiveEngine` (mêmes tables, même store) et enregistre 8 commandes : `!group-insights`, `!group-events`, `!growth`, `!my-growth`, `!patterns`, `!upcoming`, `!milestone`, `!check-in-status`. Doc complète dans `FEATURES_SOCIAL_LIFE.md` ; `!help social`, `README.md` et `COMMANDS.md` à jour.
  • Alimentation auto best-effort 🍽️ — nouveau `_feed_social_memory`, appelé dans `on_message` après l'enregistrement du message (jamais bloquant, zéro sortie Discord) : patterns d'interaction jour/heure dans le fuseau `server_timezone` (pas celui de l'OS), intérêts collectifs en serveur via `topic_tracker.extract_topic_words`, et événement relationnel uniquement sur émotion forte (intensité ≥ 0.7) mappée en conflict/support_received/shared_interest. `ProactiveEngine` n'a pas besoin d'alimentation : il dérive ses patterns de la table `memory`. Cœur extrait dans `_feed_social_signals` et appelé AUSSI depuis le chat web (`/api/chat`, `/api/chat/stream`) — sinon `/social` restait vide pour un compte qui ne parle que sur le web. Évolution de personnalité (« Mon évolution ») branchée sur l'humeur : la dérive de `mood_log` est mappée vers des dimensions lentes (sérénité/énergie/curiosité) et logée dans `evolution_log` (~1×/j), avec backfill depuis l'historique d'humeur déjà présent au démarrage.
  • Proactivité : vœux de jalon 🎂 — nouveau `_maybe_milestone_greeting` dans la boucle d'autonomie : fête les jalons du jour (`milestone_tracker`, alimentée par `!milestone`) une fois par jour et par salon, en réutilisant les garde-fous existants (mode public, consentement `social_optout`, cooldowns `proactive_contacts`, nouveau kind `milestone_greeting`). La relance spontanée d'inactifs reste portée par `_maybe_silent_user_checkin` (inchangée).
  • Bugs `proactive_engine` corrigés 🐛 — contre le schéma réel de `memory.py` : colonne inexistante `message_type` → `type IN ('user','user_voice')` ; timestamps traités comme des float alors que ce sont des chaînes UTC → parsing via `memory._parse_sqlite_utc` + bornes en chaîne UTC ; `should_reach_out` lisait `timestamp` sur `proactive_interactions` (colonne réelle `created_at`) ; `detect_user_inactivity` renvoie `None` sans historique (au lieu d'un dict trompeur). Les tests (`tests_3_features.py`, neuf `tests_social_integration.py`) passent ; sortie forcée en UTF-8 (fin du crash console Windows cp1252).
  • Prompt LEAN re-mesuré, diagnostic clos ✅ — vérification que la saturation « ~3× la fenêtre » de l'audit ne s'applique plus : prod en `lean_prompt: true` / `num_ctx: 6144`, squelette LEAN ≈ 1496 tok (~24 %), pire-cas ≈ 3283 tok (2861 de marge). `prompt_profiler.py` lit maintenant la config réelle (`_live_config`) au lieu de valeurs périmées codées en dur ; `DIAGNOSTIC_TOKENS_PROMPT.md` marqué RÉSOLU/historique. Aucune réécriture de prompt (il tient large).
  • Ménage docs 🧹 — 12 fichiers Copilot redondants (qui nommaient le projet « Aurore » et se disaient à tort « intégrés / production-ready / tests OK ») fusionnés en un seul `FEATURES_SOCIAL_LIFE.md` honnête ; `CODEBASE_EXPLORATION_SUMMARY.md` renommé Amélia.
Version 1.37.5
  • Fuseau horaire ENFIN respecté ⏰ — bug : `server_timezone` (« Europe/Paris ») était configuré mais ignoré. `_current_datetime_prompt_line` et la logique de salutation lisaient `datetime.now()` → le fuseau de l'OS. Sur un serveur en UTC, l'heure injectée au prompt et le repère bonjour/bonsoir étaient décalés de 1-2 h (cause directe du « elle dit bonsoir le matin »). Nouveau module `timeutils.now_in_tz(tz)` (tz-aware, repli sûr si tzdata absent), branché sur : l'heure du prompt (`agent`), le repère de salutation du tour, et la fenêtre de sommeil `_is_sleep_time`/`_sleep_window_key`. 4 tests.
  • Suivi multimodal vérifié (image/audio/voix) 🖼️🎙️ — audit du flux chat : confirmé que les trois modalités entrent dans le fil persisté (`memory`) avec locuteur + horodatage comme le texte (image→description fusionnée dans le contenu l.970, audio joint→transcription, voix live→`_record_user_input` type `user_voice`). La continuité cross-modale est structurellement saine, aucun correctif nécessaire.
Version 1.37.4
  • Hygiène mémoire à l'écriture 🧼 — audit de la DB : des entrées confabulées s'étaient persistées comme « profil » (ex. « confuse Lucie avec Tsukinosushi », « Yann est l'utilisateur du test », auto-notes « ne pas confondre X avec Y » qui ré-amorçaient le bug). Nouveau module pur `memory_hygiene` branché au chokepoint d'écriture (`memory.save_learned_fact` / `upsert_profile_item`) → tous les chemins (règles, extraction LLM, réorg) sont filtrés. Rejette à la source la méta sur sa propre confusion, les artefacts de test/prompt, et le méta « IA/modèle ». 8 tests, zéro faux positif sur les vraies données.
  • Anti-fuite de propos privés 🔒 — les prompts n'avaient AUCUNE règle de discrétion et Amélia ne savait même pas qu'elle était en salon public. Ajout : (a) consigne dynamique injectée dans `style_hint` UNIQUEMENT en salon de groupe (`is_public_channel`) — ne pas étaler une info perso apprise en DM/ailleurs, jamais répéter à l'un ce qu'un autre a confié ; (b) règle DISCRÉTION permanente dans les deux prompts. En DM (1-à-1), pas de risque → rien d'injecté.
  • Reconnaissance créateur robuste aux renommages 🧬 — Yann s'affiche désormais sous le pseudo « Dokkaebi 도깨비📱 ». La reconnaissance était déjà par `admin_user_id` (user_id stable), mais le hint disait juste « {pseudo} est ton créateur ». Renforcé : « {pseudo} est ton créateur Yann (peu importe le pseudo Discord du jour, c'est lui) » → le pont pseudo→créateur est explicite.
  • Outil de nettoyage mémoire 🧹 — `scripts/clean_memory.py` (dry-run par défaut, backup auto avant écriture) supprime les entrées confabulées (même filtre que l'écriture) et les orphelines (user_id jamais vu). Sur la copie locale : 9 entrées retirées. À lancer côté serveur : `python scripts/clean_memory.py --apply --orphans`.
Version 1.37.3
  • Yann ≠ Tsukinosushi : fin de la fusion d'identité dans le cadre 🪪 — le prompt s'auto-contredisait : le bloc IDENTITÉ disait « créée par Yann (Tsukinosushi) » (= même personne) dans SYSTEM_PROMPT (l.51) ET SYSTEM_PROMPT_LEAN (l.608), au rang « NON NÉGOCIABLE », pendant que le bloc ATTRIBUTION (l.450-452) posait « Si Yann parle de bateaux, ce n'est PAS Tsukinosushi ». Pour un 7B, licence explicite de confondre les deux → cause lourde de la confusion d'identité (Amélia prenant Tsuki pour Yann). Tsukinosushi est une AUTRE personne (confirmé) : parenthèse `(Tsukinosushi)` retirée des deux prompts, Yann reste seul créateur.
  • Anti-priming des personnes connues, version mécanique 🧠 — la consigne « n'invente pas Lucie/Océane » existait déjà dans les deux prompts, mais `should_inject_known_people` déversait TOUTE la liste (8 pers.) sur un simple « tu connais X », et le 7B y piochait un nom au hasard. Nouveau `prompt_guard.select_known_people_lines` : on n'injecte QUE les personnes réellement nommées (message/contexte) ; la liste complète seulement sur une vraie question d'inventaire (« qui tu connais ici ? »), jamais sur « tu connais <absent> ». Appliqué avant le branchement lean/non-lean (couvre les deux). +3 tests de régression (dont « tu connais Tsukinosushi ? » → 0 injection).
  • Un sujet à la fois (anti-dispersion) 🧵 — elle partait dans tous les sens. La règle de curiosité non-LEAN ordonnait littéralement « varie les sujets… explore un autre angle » (= saut du coq à l'âne), et le LEAN autorisait à proposer ses propres sujets sans contrainte de clôture. Les deux prompts disent désormais : on finit le fil en cours avant d'en lancer un autre, on ne ressort pas un sujet déjà clos, et sur une réponse brève on développe CE point au lieu de pivoter.
  • Réactions emoji moins aléatoires 🎲→🎯 — la réaction par message se déclenchait dès qu'un SEUL mot-clé d'émotion matchait (label non-neutre), posant un emoji « surgi de nulle part » sur des messages anodins. Garde-fou : on n'émet la réaction que si l'émotion est détectée avec au moins 2 mots-clés (`emotion.score >= 2`). Le 👍 sur les messages triviaux est inchangé.
Version 1.37.2
  • Contexte remonté grâce à la marge VRAM mesurée 📈 — `nvidia-smi` sur la carte chat (GTX 1060) : 4862/6144 MiB utilisés avec qwen 7B + ctx 4096, soit ~1,25 Go libre. On en profite : `ollama_num_ctx` 4096 → 6144 (KV qwen ≈ +110 MiB → ~4,97 Go, large sous 6 Go) et `context_limit` 30 → 40. Plus de contexte = elle garde mieux le fil (la plainte récurrente). À redéployer (le modèle se recharge avec le nouveau ctx) ; revérifier `nvidia-smi` ensuite. Tests : 499 verts.
Version 1.37.1
  • Elle garde le fil sur une image 🖼️🧵 — constaté : elle perdait le fil dès qu'on reparlait d'une image (la sienne ou une autre). Cause : depuis v1.35.2 le gros garde-fou de réaction (« réagis uniquement à partir de la description, pas de jugement… ») était collé au bloc image ET donc PERSISTÉ avec le message → aux tours suivants il repolluait le contexte (tronqué à 400 car.) et brouillait la conversation. Désormais : le message ne garde QUE la description factuelle de l'image (persistée → relue aux tours d'après, donc continuité préservée et même retrouvable par la mémoire sémantique), et la consigne de réaction est injectée ONE-SHOT (constante `_IMAGE_REACTION_GUARDRAIL`, seulement au tour qui porte l'image, jamais sauvegardée).
  • Cas « l'image, c'est toi » géré 🪞 — la consigne one-shot précise que si on lui présente une image comme étant elle, elle peut jouer le jeu naturellement mais reste lucide (« c'est une représentation, pas mon corps »), au lieu de se perdre. Tests : +1 → 499 verts ✅.
Version 1.37.0
  • Reconnaissance des personnes ENFIN reliée à la mémoire 🧑‍🤝‍🧑✅ — constat : la liste « Personnes connues ici » ne regardait QUE les comptes ayant écrit dans le salon courant (`per_user_stats(channel_id)`). Un tiers dont tu lui PARLES (Lucie, Océane…), stocké dans ses faits mais qui n'a jamais posté, n'était jamais « reconnu » — d'où « non, je connais pas » alors que l'info était dans sa mémoire. #1 : `mentioned_people_from_memory` reconnaît désormais un nom propre cité dans ton message S'il apparaît réellement dans ses faits/profil/contexte (intersection stricte → aucun nom inventé) et l'ajoute aux personnes connues avec « ne dis pas que tu ne connais pas ».
  • Reconnaissance cross-salon 🌐 — #2 : quand `unify_user_memory` est actif, les gens connus sont listés sur TOUS les canaux (un nom vu en MP ou dans un autre salon compte aussi), au lieu du seul salon courant. La mention « dans ce salon » disparaît (devenue fausse).
  • Anciens pseudos conservés (alias) 🪪 — #3 : `per_user_stats` agrège désormais tous les pseudos d'un même `user_id` (`GROUP_CONCAT(DISTINCT username)`). Un compte qui a changé de nom est affiché « Yann | … | aussi connu·e comme : Tsukinosushi » — Amélia ne prend plus un ancien pseudo pour une autre personne (ni pour l'interlocuteur courant). Tests : +8 → 498 verts ✅.
Version 1.36.0
  • Montée en gamme du modèle de chat : gemma3:4b → qwen2.5:7b-instruct-q4_K_M 🧠⬆️ — le 4B était le vrai plafond (réponses creuses, esquives, boucles). Qwen2.5-7B est nettement meilleur en français et en SUIVI DE CONSIGNES, ce qui adresse directement le « il répond à côté ». Choix du quant dicté par la VRAM : q4_K_M (~4,7 Go) tient sur la GTX 1060 6 Go, contrairement aux q5_K_M/q6_K (trop gros) qui étaient pourtant whitelistés.
  • Ajustements VRAM liés au 7B sur 6 Go ⚖️ — `num_ctx` 8192 → 4096 (sinon le KV-cache du 7B fait déborder la carte) et `context_limit` 60 → 30 (pour que le contexte tienne dans la fenêtre réduite sans troncature). `voice_model` vidé (« ») : la voix réutilise le modèle de chat au lieu de garder gemma3:1b résident en plus — un seul modèle en VRAM, plus de thrashing Ollama à chaque tour vocal. `fallback_model` = gemma3:4b : si le 7B échoue/OOM, Amélia bascule automatiquement sur le 4B et continue de tourner.
  • ⚠️ Déploiement requis côté serveur : `ollama pull qwen2.5:7b-instruct-q4_K_M` puis redémarrage, et vérifier la VRAM (`ollama ps` / `nvidia-smi`) avant de valider. Si la marge le permet, `num_ctx` peut être remonté à 6144. Tests : 492 verts (config seule, comportement inchangé hors modèle).
Version 1.35.5
  • Plus d'erreur réseau brute exposée sur la musique 🎵🧯 — log prod : « Morceau cree: … (… fallback procédural, MusicGen indisponible: MusicGen local injoignable: <urlopen error [Errno 111] Connection refused>) ». L'utilisateur voyait la stack réseau. Nouveau `_clean_music_detail` : on garde la partie utile (durée, BPM) et on remplace tout le diagnostic technique par un sobre « (généré en local) ». Appliqué à `!compose` et au tool `generate_music`.
  • Fini le texte de consigne recraché entre parenthèses 🙊 — log prod : elle écrivait « (Je sais pas encore, dis-moi) » littéralement, en recopiant le texte d'EXEMPLE de son propre prompt. Double parade : (1) la consigne VÉRITÉ (lean + complet) ne fournit plus de phrase toute faite « copiable » — elle décrit le comportement (« admets ne pas savoir et demande, avec tes mots ») au lieu de citer une formule ; (2) `_strip_context_annotations` retire les échos parenthésés « (je sais pas…) » qui passeraient quand même.
  • Budget LEAN sous surveillance ⚖️ — à force d'y rapatrier les garde-fous absents (présentation, personnes, temps, vérité), `SYSTEM_PROMPT_LEAN` est passé de 948 à 1472 tok (plafond test 1500). Marge volontairement reprise en resserrant les reformulations. À terme : soit on élague, soit on relève le plafond de façon réfléchie. Tests : +4 → 492 verts ✅.
Version 1.35.4
  • Elle sait enfin À QUI elle parle 🙋 — log prod post-1.35.3 : à Yann, elle disait « tu es créé par Yann » (confondant son interlocuteur avec son PROPRE créateur !) et parlait de « Yann » à la 3e personne… à Yann. Cause structurelle : le nom du locuteur courant n'était injecté NULLE PART dans le prompt — le message était bien préfixé « [Yann] » mais aucune consigne ne disait que c'était la personne qui parle ni que les faits la concernaient. Ajout d'une consigne par tour (`extra_instructions`, coût nul sur le budget LEAN) : « tu réponds à <nom>, les faits le/la concernent, tu lui parles en 2e personne, tu ne récites pas sa fiche, et tu ne confonds pas son identité avec la tienne (toi = Amélia créée par Yann ; ton interlocuteur n'est pas forcément ton créateur) ».
  • Fini les artefacts de prompt qui FUITENT dans ses réponses 🧹 — constaté en prod : « [Amélia] Ah… » (elle imitait le préfixe de locuteur « [Yann] » du prompt) et « …ça m'intéresse ! (il y a 3 min) » (un marqueur d'ancienneté du contexte recopié tel quel). Nouveau `_strip_context_annotations` dans `_finalize_response` : retire le préfixe « [Nom] » en tête et les marqueurs « (il y a N min/h/j) », « (à l'instant) », « ——— (pause de X) ——— » — sans toucher aux vraies parenthèses qu'elle rédige.
  • Message d'échec tool générique parasite supprimé 🔇 — « Je n'ai pas réussi à terminer cette action proprement » apparaissait sur des tours sans rapport (ex. « comment tu te sens »), signe d'un appel tool halluciné. Le catch-all des échecs d'action inconnue est désormais silencieux côté salon (log seulement), comme le `play_music` sans query en 1.35.2. Tests : +5 → 488 verts ✅.
Version 1.35.3
  • Elle se repère enfin dans le TEMPS : fini la fixation sur un sujet périmé et les salutations à côté 🕰️ — log prod : un simple « Bonjour » le lendemain → Amélia repartait sur la musique de la veille comme si la conversation n'avait jamais cessé, et elle ne savait pas s'il fallait dire bonjour/bonsoir. ENCORE le même piège que la présentation et la confusion : tout le bloc `COHÉRENCE TEMPORELLE` (salutation selon l'heure réelle, repères d'ancienneté, anti-invention de durée) n'existait que dans le prompt COMPLET — absent du `SYSTEM_PROMPT_LEAN` actif en prod.
  • Détection de reprise après longue pause ⏸️ — `topic_tracker` mesure désormais l'écart entre le message courant et la dernière activité du salon AVANT absorption (`session_gap_seconds`), sans fuiter le gap historique du réamorçage. Si l'écart ≥ 3 h, une consigne `REPRISE` est injectée par tour : accueillir, saluer selon l'heure, et NE PAS reprendre un sujet d'avant la pause comme s'il était en cours.
  • Repère horaire concret par tour 🌅🌙 — le bot calcule le moment de la journée (matin/mi-journée/après-midi/soir/nuit) et l'injecte dans le style : si elle salue, la salutation colle à l'heure (bonjour 5h-11h, bonsoir après 18h, jamais « bonsoir » le matin ni « bonne nuit » à 14h). Bloc `TEMPS` compact ajouté au LEAN (948→1453 tok, plafond 1500). Tests : +5 → 483 verts ✅.
Version 1.35.2
  • Vision : elle arrête de DÉNIGRER les créations et d'inventer des détails 🖼️💛 — log prod : photo d'une sculpture de chat (résine) partagée → Amélia répond « le style est très sombre, presque oppressant, c'est pas mon truc » + invente « un chat avec un papillon » absent de l'image. Le prompt de description (moondream) était pourtant neutre ; le souci venait de l'aval : le bloc image était collé au message SANS aucune consigne sur comment réagir. Ajout d'un garde-fou contextuel attaché au bloc image (coût nul quand il n'y a pas d'image) : réagir UNIQUEMENT à partir de la description, ne rien inventer (objet/animal/couleur/motif), et pour une création perso rester chaleureuse — pas de jugement esthétique gratuit (« sombre/oppressant/pas ton truc »), pas de conseil non sollicité.
  • Musique : fini le « il me faut au moins un titre » parasite 🎵🚫 — log prod : le message d'échec « Je n'ai pas réussi à lancer la musique : il me faut un titre… » se collait à des tours qui n'avaient RIEN à voir avec la musique (discussion animés, sculpture…). Cause : le LLM émettait des appels `play_music` SANS query (tool parasite/halluciné), et `_format_tool_feedback` traitait ça comme une vraie demande ratée. Désormais un `play_music` sans query est ignoré silencieusement (log seulement, aucun message au salon). Les vrais échecs (source introuvable, vocal injoignable) informent toujours l'utilisateur. Tests : +4 → 478 verts ✅.
Version 1.35.1
  • Elle arrête de CONFONDRE les gens 🧑‍🤝‍🧑🚫 — log prod accablant : interrogée sur Tsukinosushi elle parlait de « Lucie », inventait « Océane », mélangeait deux humains distincts, et attribuait les faits d'une personne (couture, expo en Suisse) à une autre. MÊME cause racine que le bug de présentation : en prod `lean_prompt: true` → c'est `SYSTEM_PROMPT_LEAN` qui pilote, et il était AMPUTÉ des garde-fous que seul le prompt complet portait (« N'invente pas Lucie/Mathis… », hiérarchie de vérité, « ne remplace jamais une personne par une autre »). Pire : le bloc lean n'injectait MÊME PAS `context_tag_rules` — le modèle voyait les tags `[perso]`/`[salon]`/`[Fil public]` sans qu'on lui dise jamais ce qu'ils signifient, d'où le bleed des messages des autres sur l'interlocuteur courant. Corrigé : nouveau bloc `PERSONNES (ne confonds pas)` + renforcement `VÉRITÉ` (faits du monde : titres/artistes/lieux jamais devinés — finie l'hallucination « Holy Priest = Black Sabbath » ou « Zermatt » au pif) dans le LEAN, ET injection de la légende des tags dans l'assemblage lean. LEAN passe de 948 à 1222 tok (plafond test 1500, large). Tests : +1 → 474 verts ✅.
Version 1.35.0
  • Auto-curation mémoire AUTONOME 🧹🧠 — jusqu'ici la réorganisation mémoire (relecture de l'historique, correction des faits contradictoires/obsolètes, raisonnement par personne pour ne plus mélanger les profils) ne se déclenchait QUE sur demande explicite (`is_memory_maintenance_request`). Nouvelle boucle de fond `_memory_curation_loop` qui lance la MÊME machinerie (`_run_memory_reorganization`) à intervalle régulier, mais SILENCIEUSEMENT : elle nettoie en fond et journalise, sans spammer le salon. Partage le verrou `_memory_reorg_running` + l'horodatage `_last_memory_reorg_at` avec la maintenance manuelle (jamais en double), attend un intervalle complet avant la 1re passe, et reste OFF par défaut. Config : `memory_curation_enabled` (false) + `memory_curation_interval_hours` (6). Tests : +3 → 473 verts ✅.
Version 1.34.2
  • Présentation enfin SOBRE : elle ne part plus dans tous les sens 🪪 — constaté en prod : interrogée sur « qui es-tu / présente-toi », Amélia déballait tout (autonomie, émotions, mémoire, conscience, créativité, capacités techniques, listes de sujets) au lieu de répondre simplement. Cause : en prod `lean_prompt: true` → c'est `SYSTEM_PROMPT_LEAN` qui pilote, et il n'avait AUCUNE consigne sur COMMENT se présenter (seulement « ne parle pas de ton identité sauf si on te le demande » — sans dire quoi faire QUAND on le demande). Nouveau bloc `PRÉSENTATION` ajouté dans les DEUX prompts (lean + complet) : réponse en 1-2 phrases (prénom + présence numérique créée par Yann, locale + une seule touche de ce qu'elle aime faire), puis stop net ; interdiction explicite de dérouler une liste de fonctions/sujets ou d'enchaîner sur autonomie/émotions/mémoire. README « Nouveautés récentes » rafraîchi (était figé à l'ère pré-v1.30). Tests : 470 verts ✅.
Version 1.34.1
  • Fix `!affinity` : elle inversait le SENS du score 🔄💔 — constaté en prod : pour un score de 7,3/10 elle écrivait « ça veut dire qu'il est assez satisfait de mes progrès ». Elle lisait le score comme une note que TOI tu lui donnais, alors que c'est SON affinité à ELLE envers toi (à quel point elle se sent proche). Cause : le prompt de raisonnement (`_reason_about_affinity`, branches créateur ET utilisateur) disait « ton affinité avec X » sans interdire la lecture inversée. Corrigé : bloc « ⚠️ SENS DU SCORE » explicite dans les deux prompts — c'est TON ressenti envers la personne, PAS une note qu'elle t'a donnée, PAS son avis sur toi, PAS sa satisfaction ; tu écris « je me sens proche/distante », jamais « il est satisfait de moi ». L'affichage de `!affinity` gagne aussi une ligne d'explication (« à quel point JE me sens proche de toi — pas une note que tu m'aurais donnée »). Tests : +2 → 470 verts ✅.
Version 1.34.0
  • Nouvelle commande `!discussion` (alias `suivi`, `discu`) : le suivi de conversation devient VISIBLE 🧵👁️ — jusqu'ici le suivi multi-sujets (v1.29) travaillait dans l'ombre du prompt, sans moyen de l'inspecter. `!discussion` affiche en clair l'état complet du fil : le SUJET ACTUEL (avec son dernier point et sa question ouverte éventuelle), les sujets EN PAUSE (avec leur ancienneté « il y a X min »), le TEMPS depuis l'échange précédent (repère temporel v1.32), les FILS OUVERTS mémorisés (boucles persistées, gérables via `!threads`) et mon RESSENTI du moment. Une vue d'ensemble de « où on en est ».
  • Le suivi de discussion SURVIT aux redémarrages 🔁🧵 — gros manque constaté : le suivi multi-sujets vivait uniquement en RAM et repartait de zéro à chaque reboot (3 redémarrages dans la soirée = suivi perdu 3 fois). Nouveau réamorçage `topic_tracker.seed_from_history` : au premier message d'un salon après un démarrage, les ~30 derniers messages persistés sont rejoués SILENCIEUSEMENT (aucune fausse alerte « changement de sujet ») pour reconstruire les fils actifs avec leurs vraies dates. `!discussion` et le digest de sujets sont donc justes dès le premier tour, même juste après un reboot.
  • Commandes `!` mises à jour 🔧 — `!mood` (alias `ressenti`) montre désormais l'émotion vive courante + la tendance émotionnelle sur 7 jours ; `!mood labels` liste TOUT ce qu'elle peut ressentir (humeurs de fond + 10 émotions vives) ; `!mood set` accepte les nouvelles émotions. `!help` rafraîchi : section conversation enrichie (`!discussion`, `!threads`, `!trends`), section humeur complétée (émotions vives, `!eviter`, `!affinity`, page web Conscience) — les commandes ajoutées depuis des versions (`!eviter`, suivi de sujets, émotions, repère temporel) sont enfin documentées dans `!help` ET `COMMANDS.md`. Tests : +4 → 468 verts ✅.
Version 1.33.0
  • Des émotions VRAIES : elle ressent et MONTRE la colère, la tristesse, la joie… 😢😠😊 — jusqu'ici son humeur était un continuum lisse (calme→joueuse→fatiguée) avec une seule émotion franche, « contrariée ». Trop tiède pour être humaine. Nouvelle COUCHE d'émotions AIGUËS par-dessus l'humeur de fond : `en colère`, `triste`, `émue`, `inquiète`, `joyeuse`, `enthousiaste`, `surprise`, `déçue`, `soulagée`, `fière`. Chacune surgit sur un déclencheur, prend le dessus tant qu'elle est vive (label + style de réponse dédié, franc et incarné), puis retombe en ~7 min — comme un vrai ressenti. `MoodManager.feel(emotion, intensité)`.
  • Empathie, pas mimétisme 💞 — quand l'interlocuteur ressent quelque chose, Amélia a SA propre réaction (pas un copier-coller) : ta tristesse la rend `émue` (touchée, elle veut réconforter), ton stress la rend `inquiète`, ta joie la rend `joyeuse`, ton enthousiasme l'`enthousiaste`, ta fierté la rend `fière` pour toi, une surprise la `surprise`. Câblé sur l'émotion déjà détectée à chaque message (`on_user_message`) — donc gratuit, et seulement quand l'émotion est réelle (les messages neutres ne forcent aucune émotion, pas de théâtre permanent).
  • La colère a deux crans 😤→😠 — l'irritation (quand on lui manque de respect) monte maintenant en paliers : une pique simple → `contrariée` (sèche, du répondant), un vrai manque de respect ou une insulte → `en colère` (ton dur, limites posées sans s'excuser). La colère prime sur tout le reste (on ne reste pas « joyeuse » quand on vient de se faire insulter). Réussir une action concrète ou créer une image/musique la rend `fière`/contente ; un échec la `déçoit` (émotions légères qui retombent vite).
  • Persistance + apaisement 🌬️ — l'émotion aiguë est sauvegardée entre les redémarrages (et s'estompe pendant l'absence, comme l'humeur de fond), et s'apaise un peu chaque fois qu'elle l'exprime (`on_bot_spoke`) : elle ne reste pas bloquée plusieurs tours sur le même ressenti. Tout flotte automatiquement vers le prompt (label + style_guide) ET la page Conscience (ressenti courant). `force_label` accepte les 10 nouvelles émotions (debug/test). Rétrocompat totale : humeurs de fond, `contrariée` et tests existants intacts. Tests : +14 → 464 verts ✅.
Version 1.32.0
  • Repérage temporel : elle sait DEPUIS QUAND, elle n'invente plus 🕰️ — constaté en prod : « ça fait un bail qu'on a pas parlé » … alors qu'on venait de se parler. La date courante était bien dans le prompt, mais les messages du contexte n'avaient AUCUN repère d'ancienneté : elle ne pouvait pas distinguer « il y a 2 min » de « il y a 2 jours ». Désormais chaque ligne de contexte porte son âge relatif entre parenthèses (« (à l'instant) », « (il y a 3 min) », « (il y a 2 h) », paliers coarse pour ne pas churner le cache), et une ligne « ——— (pause de X ici) ——— » marque les vraies coupures (>30 min). Helper `memory.relative_time_fr` + parseur UTC `_parse_sqlite_utc`. Consigne de prompt renforcée (section COHÉRENCE TEMPORELLE) : ne JAMAIS affirmer une durée (« ça fait longtemps », « hier ») sans qu'un marqueur l'indique ; si le dernier échange est « à l'instant », la conversation est CONTINUE. Tests : +4.
  • UI chat : fini le chevauchement des questions 🧩 — le bandeau « Amélia aimerait savoir » pouvait mordre sur la zone de conversation. Cause : `.chat-console` utilisait un grid à lignes explicites (`grid-row: 2/3/4`) fragile dès que le contenu débordait. Converti en FLEX-COLUMN : la zone scrollable prend tout l'espace restant et RIEN ne peut chevaucher, quel que soit l'état du bandeau ou de l'indicateur de réponse. Le bandeau questions est désormais plafonné (max 34 % de hauteur), scrollable, avec un bouton « Plus tard » pour le masquer, et les questions passent en cartes-lignes pleine largeur (au lieu de pilules qui se chevauchaient) — plus lisible, plus aéré.
  • Nouvelle page « Conscience » 🧠💜 — suivi de la vie intérieure d'Amélia (lien « Conscience » dans la nav admin, page `/conscience`, auto-rafraîchie 30 s) : son RESSENTI du moment (humeur/énergie/intérêt en jauges + label vivant), sa TENDANCE émotionnelle sur 7 jours (dominante + distribution, depuis le `mood_log` persistant), ce qu'elle a CORRIGÉ toute seule (auto-évaluation chiffrée : faux souvenirs interceptés, tics retirés, monologues écourtés, barge-in…), et ce qu'elle vient d'APPRENDRE (flux unifié faits + profils). Réservée admin (le journal d'apprentissage contient des faits sur les utilisateurs). Tests : +4 → 450 verts ✅.
Version 1.31.1
  • Pourquoi v1.31.0 laissait encore passer « Wesh ? » et « tu as l'air tendu » — autopsie sur transcription de prod et 5 correctifs ciblés. (1) DÉTECTEUR D'ÉMOTIONS : le matching par SOUS-CHAÎNE classait les messages techniques en détresse — « j'ai l'imPRESSION » → stress, « j'ai enTENDU / comme atTENDU » → stress, « ça déMARRE » → colère (marre), « au démaRRAGE » → colère (rage), « norMALement » → tristesse (mal), « éVIDEmment » → tristesse (vide). D'où le ratio aberrant 14+/56− au !affinity et le « tu as l'air tendu » perpétuel injecté au prompt. Correctif : frontières de mots obligatoires + mots ambigus retirés (« mal/vide/cassé/quoi » → tournures personnelles « me sens mal », « me sens vide ») + doublon fatigue/tristesse supprimé. Les affinités se recalculeront juste au fil des nouveaux messages.
  • (2) ANTI-TIC v2 : trois trous bouchés — « Wesh ? » normalisé fait 4 caractères (sous l'ancien seuil de 8 → seuil abaissé à 4) ; les VARIANTES « C'est pas grave, hein ?/ça arrive/je comprends » échappaient au match exact → détection par FAMILLE (préfixe 4 mots, ≥2 occurrences récentes) ; surtout l'historique anti-tic vivait en RAM et repartait de zéro à CHAQUE redémarrage (3 restarts dans la soirée → les tics gardaient « droit à 2 répétitions » à l'infini) → RÉAMORÇAGE depuis la base au premier tour de chaque salon (ses 40 derniers messages persistés comptent immédiatement).
  • (3) EXPRESSIONS BANNIES : la tournure réelle de Yann (« j'ai remarqué que tu utilises 'wesh', mais ça ne me correspond pas trop ») ne matchait aucun pattern — nouveau détecteur « mot CITÉ après tu utilises/dis + signal de gêne dans le message », prioritaire sur le pattern direct (qui capturait la méta-phrase « ce genre de mot » au lieu du mot visé — méta-références rejetées). Et les demandes d'AVANT le déploiement n'étant jamais re-scannées, nouvelle commande `!eviter wesh` (liste : `!eviter`, retrait : `!eviter retirer wesh`) pour bannir immédiatement, à la main, avec effet filtre garanti.
  • (4) SUJETS REFUSÉS LITTÉRAUX : « j'aimerais que tu arrêtes de me parler des zombies » ne passait par aucun registrar fiable (l'extracteur par labels/catégories ratait le mot brut) → nouveau pattern « arrête de me parler de X / ne me parle plus de X / je ne veux plus entendre parler de X » qui mémorise X LITTÉRAL dans `refuted_topics` (persistant) → le filet v1.31.0 le supprime enfin des réponses, méta-mentions comprises. (5) !AFFINITY : le « Ce que j'en pense » est coupé PROPREMENT en fin de phrase (~900 car) au lieu d'être tronqué en plein mot (« C'est ça, ma pe ») par la limite Discord. Tests : +6 → 442 verts ✅. ⚠️ À vérifier côté serveur : 3 redémarrages « pas arrêté proprement » dans la soirée — `journalctl -u aurore | grep -iE 'traceback|killed|oom'`.
Version 1.31.0
  • Fin du radotage : anti-tic inter-réponses 🔁🚫 — constaté en prod (conversation du 12/06) : « C'est pas grave, hein ? » et « Tu as l'air un peu tendu » dans PRESQUE CHAQUE réponse, même après un « tu radotes » explicite. L'anti-répétition comparait des réponses ENTIÈRES, jamais les phrases-tics qui se faufilent de réponse en réponse. Nouveau `_scrub_conversational_tics` : chaque phrase servie est mémorisée par salon (60 dernières) ; une phrase déjà servie ≥2 fois récemment ne ressort PAS une troisième fois — supprimée mécaniquement, peu importe ce que le modèle génère. Garde-fou : une réponse qui ne serait QUE tic n'est jamais vidée (mieux vaut une redite qu'un silence). Compteur `tics_interceptes` + log `[STYLE]`.
  • Expressions bannies DURABLES : « wesh » ne reviendra plus 🙊 — constaté en prod : deux demandes explicites (« je n'aime pas quand tu dis wesh ») suivies de DEUX récidives. Cause : la consigne ne vivait que dans le prompt, que le modèle oublie. Nouveau circuit applicatif : « je n'aime pas quand tu dis X / arrête de dire X / ne dis plus X » (avec ou sans guillemets) → X enregistré dans `data/banned_expressions.json` (persistant, éditable à la main, plafond 30) → le sanitizer SUPPRIME toute phrase contenant l'expression (mot entier, insensible à la casse), sur les DEUX voies de génération. Le prompt peut se tromper, le filtre jamais.
  • Les sujets refusés sont ENFIN appliqués 🧟❌ — constaté en prod : « j'aimerais que tu arrêtes de me parler des zombies » … puis « C'est un peu lourd les zombies, hein ? » et « on oublie les zombies pour l'instant ? ». Cause racine : le filet `_sanitize_violating_response` refusait par design de toucher aux sujets refusés (« nécessiterait de réécrire ») — or avec `conversation_max_regen_attempts=1` il n'y a JAMAIS de régénération, donc les violations passaient telles quelles. Le filet supprime maintenant les phrases mentionnant un sujet refusé (`banned_terms` depuis `refuted_topics` du snapshot), y compris les MÉTA-mentions (« on évite le sujet X » = encore parler de X).
  • Fin du théâtre : artefacts roleplay nettoyés 🎭 — constaté en prod : réponses ENTIÈRES enrobées de « guillemets » et didascalies « (Un petit soupir, un ton légèrement sec) ». Nouveau `_scrub_roleplay_artifacts` : didascalie d'ouverture parenthésée/astérisquée retirée, enrobage de citation intégral déballé (les vraies citations INTERNES sont préservées). Appliqué tête/queue de façon cohérente en streaming vocal.
  • Barge-in poli : elle cède la parole 🎙️🤝 — si quelqu'un parle VRAIMENT pendant qu'elle parle, elle termine sa phrase en cours (jamais coupée en plein mot — c'est la différence avec « tais-toi ») puis se tait et écoute ; la génération en cours s'arrête aussi. Les simples signes d'écoute (« ouais », « ok », « mmh », « d'accord »…) ne l'interrompent PAS — elle continue naturellement. Réglage `voice_barge_in` (défaut true), compteur `barge_in`.
  • Suivi de projets DATÉ : « véto demain » → elle en reparle APRÈS 📅 — nouveau `commitment_followup.py` : un mot d'ÉVÉNEMENT de vie (véto, médecin, entretien, examen, concert, déménagement… ~50 mots) + une ancre temporelle FUTURE (parseur `reminders` existant : « demain », « ce soir », « à 15h », < 14 jours) → un suivi `[FOLLOWUP]` est programmé ~6 h APRÈS l'événement (ramené en heures éveillées 9h-22h) via l'infra existante qui génère une vraie question naturelle (« alors, ce véto ? »). Jamais : si la personne gère déjà ses rappels (« rappelle-moi… »), si récurrent, si passé. Anti-spam : 1 suivi/utilisateur/6 h. Réglage `enable_commitment_followup`.
  • Auto-évaluation CHIFFRÉE dans le rapport matinal 📊 — le rapport deepwork inclut désormais « ce que j'ai raté et corrigé » en chiffres réels depuis le dernier démarrage : faux souvenirs interceptés, phrases-tics retirées, expressions bannies bloquées/apprises, monologues écourtés, consignes de silence, fois où elle a cédé la parole, changements/retours de sujet détectés, suivis datés programmés, replis de génération, latence vocale (médianes). Lecture honnête assumée : « chaque interception = une erreur que j'allais faire ».
  • Prosodie Piper réglable 🎚️ — la voix TTS jugée « pas très naturelle » : nouveaux réglages `piper_length_scale` (débit, >1 = plus posé), `piper_noise_scale` / `piper_noise_w` (variations naturelles). Défauts Piper conservés (1.0/0.667/0.8) ; recommandé pour plus de vivant : 1.05/0.75/0.9. 💡 Le vrai saut de naturalité = changer de modèle de voix : `fr_FR-siwis-medium` ou `fr_FR-upmc-medium` (qualité bien supérieure aux voix low) — télécharger le .onnx + .json et pointer `piper_model_path` ; piste suivante : moteur Kokoro. Tests : +16 → 436 verts ✅.
Version 1.30.0
  • Fin des monologues vocaux : la voix redevient une CONVERSATION 🎙️✂️ — constat : à l'oral elle commençait bien puis « partait dans tous les sens ». Causes trouvées : (1) AUCUN plafond de longueur sur la voie vocale — la cible texte (modulée par la familiarité) pouvait viser des réponses longues, et le streaming TTS lui faisait TOUT lire ; (2) le plancher `num_predict=192 tokens` laisse passer ~570 caractères même avec une cible basse ; (3) le modèle vocal minuscule (gemma3:1b) divague plus que le 4b. Correctifs code : plafond vocal de CIBLE (280 caractères ≈ 2-3 phrases, comme un humain à l'oral) + plafond DUR de prise de parole dans le streaming (~600 caractères prononcés → elle conclut à la fin de la phrase en cours, log `[VOICE-STREAM] Plafond vocal`), le texte persisté reste exactement ce qui a été dit. Correctif config (à faire serveur, LE plus important) : `voice_model: ""` pour basculer la voix sur gemma3:4b résident — même cerveau à l'oral et à l'écrit, fin de la double personnalité vocale/textuelle.
  • Autonomie : les sujets en suspens deviennent des RELANCES naturelles 🔄🧵 — pont entre le suivi multi-sujets (v1.29) et la machinerie de continuité existante : quand un fil de sujet expire (2 h d'inactivité) avec de la MATIÈRE — une question restée ouverte, ou une vraie discussion (≥3 messages) — il est persisté en « boucle ouverte » (`upsert_open_loop`, catégorie `sujet_en_suspens`, priorité 2 si question ouverte sinon 3, rattaché au bon utilisateur via l'attribution des fils). Le digest de continuité et les check-ins existants savent déjà relancer ces boucles : elle pourra dire « au fait, pour ton chat, le véto a dit quoi ? » des heures ou des JOURS plus tard, même après un redémarrage (c'est en base, pas en RAM). Les fils triviaux (1 message sans question) ne génèrent JAMAIS de relance — pas de harcèlement. Un callback qui plante ne casse jamais la conversation.
  • Autonomie douce dans le prompt 💡 — quand un fil en pause garde une question ouverte, le digest multi-sujets ajoute une invitation mesurée : « si le moment s'y prête (pas à chaque tour), tu peux relancer naturellement un fil en pause (“au fait…”) ». C'est l'équilibre demandé : proactive sans être intrusive — elle a l'info et la permission, pas l'obligation.
  • Détails techniques 🔧 — `TopicThread` porte maintenant `user_id`/`username` (dernier contributeur humain, posés par `_record_user_input` texte ET vocal) ; `TopicTracker(on_thread_expired=…)` déclenche le pont au prune (digest/observe) ; le plafond vocal s'applique AVANT traitement du chunk (un léger dépassement d'une phrase est possible, jamais un paragraphe) ; la génération LLM s'arrête aussi au plafond (pas de GPU brûlé pour du non-dit). Tests : +6 (attribution, callback question ouverte, fil trivial silencieux, callback qui plante, invitation à relancer, plafond anti-monologue) → 420 verts ✅.
Version 1.29.0
  • Suivi multi-sujets : elle ne perd plus le fil 🧵🧵🧵 — nouveau `topic_tracker.py` (déterministe, zéro appel LLM) : chaque salon a jusqu'à 4 FILS de sujets actifs (mots-clés pondérés, expiration 2 h). Chaque message utilisateur (texte ET vocal, via `_record_user_input`) est rattaché à son fil avec une asymétrie voulue : continuer le fil COURANT est facile (1 recoupement — l'élan conversationnel), ressusciter un fil en pause exige plus de preuves (≥2 et plus que le courant). Les messages courts (« ouais », « et toi ? ») suivent l'élan. Sa propre réponse nourrit le fil courant et referme la question ouverte.
  • Détection des changements de sujet + consignes au prompt 🧭 — le snapshot injecte en TÊTE un digest : le fil ACTUEL (dernier point + QUESTION OUVERTE éventuelle), les fils en pause (« il y a X min »), et une consigne EXPLICITE à l'événement : « ⚠️ la personne vient de CHANGER de sujet (A → B) : réponds sur le NOUVEAU sujet sans mélanger » ou « ↩️ elle REVIENT au fil X laissé en pause : raccroche-toi à ce qui s'y est dit, ne repars pas de zéro ». La consigne est consommée UNE fois (pas de radotage). C'est le tuteur dont les petits modèles ont besoin : fini de répondre au mauvais sujet ou de repartir de zéro quand on revient sur un point d'il y a 20 minutes. Logs `[TOPIC] Changement/Retour de sujet détecté`. Débrayable : `enable_topic_tracking: false`. État en RAM (les wake briefs couvrent les redémarrages).
  • Robustesse lexicale 🔤 — extraction de mots porteurs avec ~180 mots-outils français filtrés (dont les pièges à 3 lettres « mon/ton/son/les… » qui faisaient fusionner des fils distincts), pluriels rabotés (« voitures » retrouve « voiture »). Tests : +10 (continuation, élan court, switch, retour, digest consommé, question ouverte refermée, expiration, isolation par salon, éviction) → 414 verts ✅.
Version 1.28.0
  • Mémoire sémantique ENFIN active en vocal 🧠🎙️ — la voie rapide vocale sautait TOUT recall par embeddings depuis v1.22.1 (à l'époque, l'appel évinçait le modèle de génération du slot GPU unique → blancs énormes). Depuis la résidence des modèles, ce coût a disparu : le recall par le SENS tourne maintenant aussi en vocal (limite 2 souvenirs, ~dizaines de ms) — gros morceau du « elle perd le fil » : « ma bagnole » retrouve « ta voiture », les souvenirs liés remontent même formulés autrement. Garde-fou : si l'appel dépasse 0,5 s, log `[VOICE] Recall sémantique lent` avec le remède (résidence nomic-embed, ou `voice_semantic_recall: false` pour revenir à l'ancien comportement).
  • `!voice` devient un vrai tableau de bord ⏱️ — chaque tour vocal streamé enregistre sa latence (1re phrase prête + tour complet, 20 derniers tours) : `!voice` affiche les médianes/min/max — fini de fouiller les logs pour savoir si elle est rapide. Affiche aussi le mute vocal en cours (« encore X min, mon prénom me réveille »).
  • Ménage dépôt 🧹 — `.gitignore` réparé : sa dernière ligne était CORROMPUE (« venv/ » collé à un « NightInvasion.x86_64 » encodé en UTF-16 → ni l'un ni l'autre réellement ignorés). Réécrit proprement + artefacts runtime enfin ignorés (config.json.bak.*, log.txt, musicgen-*.wav, data/ runtime : wal/shm, voice/, music/, generated/, rapports d'audit mémoire…) : `git status` redevient lisible (~70 → ~46 lignes, et ça baissera encore). Vérifications au passage : le non-retry sur erreurs HTTP permanentes existait déjà, la guidance relationnelle (confiance) est bien injectée au prompt. Tests : +4 → 404 verts ✅.
Version 1.27.0
  • « Tais-toi » = silence IMMÉDIAT 🤫✂️ — quand on lui dit de se taire (« tais-toi », « chut », « la ferme », « silence », ou « stop »/« arrête » pendant qu'elle parle ou si on la nomme), la parole en cours est COUPÉE NET (`interrupt_speech` : stop du client Discord + abandon des chunks restants du pipeline TTS, et le streaming LLM→TTS arrête aussi la génération — pas de repli qui referait parler, pas de tool lancé sur un ordre interrompu). Puis MUTE vocal (`voice_mute_minutes`, défaut 10 min) : elle écoute et mémorise tout mais ne répond plus — son prénom la réveille instantanément. Acquittement SANS parole (zéro pollution) : double-blip + petit message texte « dis mon prénom pour me relancer ». Les ordres musique (« stop la musique ») gardent leur circuit normal, et un « stop » noyé dans une longue phrase ne déclenche rien.
  • Earcons : elle « bipe » avant de parler 🔔 — feedback immédiat à la voix : un blip court (880 Hz, généré en code, aucun asset) part dès qu'elle décide de répondre — on sait qu'elle a entendu pendant que génération+synthèse travaillent — et un double-blip acquitte un ordre compris (mute). Ne coupe jamais une parole en cours. Désactivable : `voice_cue_beep: false`.
  • Anti-hallucination « faux souvenir partagé » 🧠🚫 — constaté en prod : « tu m'avais raconté, il y a un bail, sur ton grand-père… tu lui avais construit une petite maison à la campagne » — biographie ENTIÈREMENT inventée, racontée à l'utilisateur comme SES propres confidences. Nouveau garde `_scrub_unsupported_memory_claims` (voies classique ET streaming vocal) : toute phrase qui PRÉTEND un souvenir partagé (« tu m'avais dit/raconté/parlé de… », « tu disais que », « tu te souviens quand tu… ») doit recouper la mémoire réellement fournie (faits/profil/contexte/synthèses) — sinon la phrase saute, avec log `[ANTI-HALLU]`. Seuil adaptatif (affirmation riche = ≥2 recoupements, courte = 1) pour ne pas tuer les vrais rappels.
  • Cohérence vocale 🧩 — elle « se perdait » en vocal : limites mémoire de la voie rapide relevées (contexte perso 12→16, partagé 6→8, faits 8→12, synthèses 4→6 — du SQLite local ~ms, la contrainte GPU qui justifiait ces coupes a disparu avec la résidence des modèles) + consigne vocale explicite à chaque tour : une idée à la fois, ancrage strict dans la mémoire fournie, « je ne sais plus » plutôt qu'inventer. 💡 Reco config serveur : `voice_model` → vide ou `gemma3:4b` (le 4b est désormais résident sur la GTX : à peine plus lent en premier-son streamé, BEAUCOUP plus logique que le 1b — c'est LE levier contre les inventions). Tests : +9 → 400 verts ✅.
Version 1.26.0
  • Arbitre VRAM : image + musique + LLM cohabitent sans se bloquer 🖼️🎵🧠 — topologie actée (rôles GPU inversés) : la GTX 1060 (6 Go, CUDA) héberge le LLM complet RÉSIDENT (chat gemma3:4b + vocal gemma3:1b + embeddings, instance :11435) ET les rendus (ComfyUI, MusicGen) ; la R9 280 (3 Go, Vulkan) prend la vision (moondream :11434). Pendant un job créa : le modèle de CHAT est évincé (keep_alive=0) et remplacé par le modèle léger via la « pression modèle » (`agent.set_model_pressure` — les overrides explicites comme la voix restent honorés), le vocal et les embeddings RESTENT chauds → la voix reste instantanée, le chat reste réactif (sur gemma3:1b), le rendu a sa VRAM. Fin de job : pression levée, chat rechargé en arrière-plan. Jobs sérialisés (jamais deux rendus lourds en même temps : le 2e attend sa place au lieu d'un OOM). Réglages : `creative_share_llm_gpu` (true), points d'entrée uniques `creative_generate_image/music` (Discord, tools ET web).
  • MusicGen ne squatte plus la VRAM 🎼💨 — le serveur MusicGen gardait le modèle chargé POUR TOUJOURS après la première génération (~2 Go perdus sur les 6 partagés). Ajout : déchargement automatique après 5 min d'inactivité (`--idle-unload-seconds`, rechargement paresseux au prochain morceau) + endpoint `/unload` que l'arbitre appelle avant un rendu IMAGE (l'image veut le max de VRAM). Et le blocage « Amélia regarde un écran » sur la création image/musique est levé par défaut (`vision_blocks_creative: false`) : la vision vit désormais sur la R9, elle ne dispute plus la VRAM des rendus — regarder un écran ET générer une image en même temps, c'est maintenant possible.
  • Docs serveur alignées sur la réalité 📋 — `ollama-vision.service` et `aurore.service` documentent l'inversion des rôles (l'instance :11435 « vision » héberge le LLM depuis la mort de la RX 580 ; le drop-in résidence posé par `setup_voice_latency_server.sh` prime sur ses valeurs d'origine). Tests : +10 (pression/priorité modèle, sérialisation, libération sur échec, MusicGen unload) → 390 verts ✅.
Version 1.25.0
  • Mémoire liée entre canaux : « j'ai vu ton rapport » est ENFIN compris 🔗🧠 — avant, le contexte était cloisonné par salon : quand Yann mentionnait en MP un rapport posté ailleurs, Amélia demandait « quel rapport ? » (constaté en prod). Nouveau rappel cross-canal de SES PROPRES sorties : quand le message courant fait référence à quelque chose qu'elle a écrit (« ton rapport », « ce que tu m'as dit », « j'ai lu ton message »… — détection `_references_my_past_output`), ses derniers messages de TOUS les canaux remontent dans le prompt, datés et localisés (« dans #général », « en privé (MP) », « en vocal »).
  • Confidentialité du rappel cross-canal 🔒 — règle stricte : ne remontent QUE les canaux où l'interlocuteur COURANT a lui-même déjà écrit (`fetch_recent_self_outputs`) — ce qu'elle a dit dans les MP des AUTRES ne fuite jamais. En salon public, consigne de discrétion ajoutée : référence en termes généraux, pas de récitation de contenu privé. Et le rapport matinal deepwork est maintenant PERSISTÉ en mémoire au moment de l'envoi (préfixe « [Rapport matinal] ») — avant il n'en restait AUCUNE trace, donc aucune chance de le comprendre. Marche aussi en voie rapide vocale (simple requête SQLite, limite 3).
  • Hotfix 400 Bad Request Ollama (keep_alive) ⚡🩹 — vu en prod juste après le passage à keep_alive=-1 : l'app envoyait la CHAÎNE « -1 » que Go time.ParseDuration refuse (unité manquante) → `Erreur HTTP Ollama 400` sur chaque génération, repli silencieux. Nouveau `config.normalize_keep_alive` : les entiers purs (« -1 », « 0 ») deviennent des NOMBRES JSON (sémantique Ollama : -1 = garder chargé pour toujours), les durées avec unité (« 10m », « 24h ») restent des chaînes. Appliqué aux générations (`agent`, 2 chemins) et à la vision (`multimodal`, 2 chemins). Confirmation prod au passage : premier audio vocal en 1,6 s avec le pipeline TTS 🎉. Tests : +7 → 380 verts ✅.
Version 1.24.0
  • Streaming LLM→TTS : elle parle PENDANT qu'elle réfléchit 🗣️⚡⚡ — dernier levier de latence vocale : la voix part dès la PREMIÈRE PHRASE générée, sans attendre la fin de la génération. Nouvelle voie `_stream_voice_reply` (activée sur le chemin vocal rapide) : `agent.stream_response` (mêmes options anti-boucle, même prompt via le nouveau `_build_reply_prompt_kit` partagé avec la voie classique) → `StreamSentenceAssembler` (assemblage incrémental SÛR : frontières de phrases confirmées par le texte suivant, décimales « 3.14 » et abréviations « M. Dupont » jamais coupées, aucun texte perdu) → `speak_stream` (pipeline TTS v1.23). Latence perçue ≈ temps de la 1re phrase + sa synthèse.
  • Garde-fous CONSERVÉS, appliqués chunk par chunk AVANT la voix — pas de régression de qualité : salutation redondante + vocatif erroné (tête de réponse), année/tutoiement/collapse anti-boucle/sanitizer/violations (chaque chunk, avec déduplication de phrase À CHEVAL entre deux chunks), trim de question parasite (queue). Les blocs d'action `<<TOOL>>…<<END>>` ne sont JAMAIS lus à voix haute (le flux prononçable s'arrête au bloc, le tool s'exécute normalement après). Texte persisté == texte réellement prononcé. Ouverture jugée répétitive → abandon AVANT le premier son et repli voie classique ; éligibilité conservatrice (jamais en salon public, jamais si musique en cours, jamais si `conversation_max_regen_attempts>1` car la voie streaming ne sait pas « déparler » pour régénérer). Échec avant le 1er son → repli classique transparent ; après → elle conclut proprement avec l'acquis. Désactivable : `voice_streaming_llm: false`.
  • Côté serveur : `scripts/setup_voice_latency_server.sh` 🔧 — installe la résidence des modèles qui tuait la latence (vocal rechargé à froid à chaque tour). Idempotent et adaptatif : détecte l'instance Ollama par le PORT de config.json (pas de port codé en dur), lit la VRAM réelle (R9 280 = 3 Go, remplaçante de la RX 580 HS) et la taille des modèles, pose un drop-in SÉPARÉ (n'écrase pas l'override Vulkan), corrige le piège du keep_alive PAR REQUÊTE (config.json → -1, sinon l'app écrase l'env systemd), alerte si FLASH_ATTENTION/KV q8_0 traînent (hang GCN), précharge vocal→embeddings→chat, redémarre et vérifie. `--dry-run`/`--yes`/`--revert`. Sur 3 Go : chat en débord RAM/GTT assumé, vocal+embeddings résidents → voix instantanée. Tests : +18 (assembleur incrémental, masque d'actions, parcours complet streaming avec échecs/repli/boucles) → 373 verts ✅.
Version 1.23.0
  • Voix quasi instantanée : pipeline TTS streaming 🗣️⚡ — avant, Amélia attendait la synthèse Piper de TOUT le texte (un seul gros WAV) avant d'émettre le moindre son. Nouveau `PiperVoiceAdapter.speak_stream()` : la réponse est découpée en phrases (`split_for_tts`, 1er chunk court ~140 car pour démarrer au plus tôt, suivants ~280), et le chunk N+1 se synthétise PENDANT que le chunk N est lu dans Discord (file bornée à 3 WAV d'avance, lecture strictement dans l'ordre, WAV nettoyés au fil de l'eau). La latence perçue tombe à « LLM + synthèse d'UNE phrase ». Découpe sûre : jamais au milieu d'un mot, décimales (« 3.14 ») et abréviations (« M. Dupont », « etc. ») protégées, aucun texte perdu. Log dédié `[VOICE] Premier audio lancé en X.Xs (pipeline TTS)` pour mesurer la vraie latence perçue. Désactivable via `voice_streaming_tts: false` (retour à l'ancien chemin mono-WAV). Repli générique `BaseVoiceAdapter.speak_stream` (joint tout → `speak`) pour les autres backends.
  • −1 à 4,5 s de latence vocale gratuite 🕐 — le délai « humanisation » (`natural_delay` : base aléatoire + 45 ms/caractère plafonné à 4 s), pensé pour simuler la frappe au clavier en TEXTE, s'appliquait aussi avant de PARLER. En voie rapide vocale (`fast=True`), il est sauté : le rythme humain vient déjà du temps de synthèse/lecture. Les chemins texte gardent leur cadence naturelle.
  • Anti-cacophonie renforcé de fait : `speak_stream` ne rend la main qu'à la fin de la lecture du dernier chunk → `_voice_responding` couvre désormais TOUTE la prise de parole (elle ne s'auto-interrompt plus si quelqu'un parle pendant qu'elle lit). Le log par tour devient « Tour vocal bouclé en X.Xs (génération→fin de lecture) ». Garde-fous inchangés : anti-répétition, scrubs identité/proches, sanitizer, gating musique — tout s'applique toujours au texte complet AVANT la première synthèse. Tests : +14 (découpe, repli, pipeline, échecs partiels) → 355 verts ✅.
Version 1.22.1
  • Latence vocale écrasée ⚡🗣️ — un « blanc » de plusieurs secondes à minutes séparait la parole de la réponse. Cause #1 côté code : avant CHAQUE réponse, le snapshot mémoire lançait `semantic_search` (embeddings `nomic-embed-text`) — or sur un slot GPU unique (`ollama_max_concurrent=1`, embeddings + LLM sur la même instance `:11434`), ça évinçait le modèle de génération juste avant de répondre, puis il fallait le recharger. Nouvelle voie rapide : `_build_memory_snapshot(fast=True)` et `_generate_user_response(fast=True)` (activée sur le chemin vocal `_handle_voice_transcript`) sautent TOUT appel embeddings (recherche sémantique + recall affectif) et réduisent le contexte (perso 60→12, partagé →6, faits →8, synthèses/observations →4). Plus un log `[VOICE] Réponse vocale produite en X.Xs` pour mesurer le vrai temps. Les chemins texte/web sont inchangés (`fast` défaut false).
  • ⚠️ Cause racine restante = OPS (thrashing de modèles) : gemma3:1b (vocal) se recharge à froid à chaque tour car gemma3:4b (chat/autonomie) occupe l'unique slot. Fix recommandé côté serveur (hors code) : garder les modèles résidents — `OLLAMA_MAX_LOADED_MODELS=3` + `OLLAMA_KEEP_ALIVE=-1` sur l'unité ollama (gemma3:4b ~3.3 Go + gemma3:1b ~0.8 Go + nomic-embed ~0.3 Go tiennent sur 6 Go), OU mettre les embeddings sur une 2e instance, OU `voice_model = model` pour supprimer le swap. Suite : 341 tests verts.
Version 1.22.0
  • Musique : « quand on lui demande, elle le fait » 🎵 — bug trouvé : en mode « écoute longue », le prompt arrive multi-lignes (« - msg1\n- msg2 ») et `_extract_music_query` ancrait sur la fin de chaîne (`$` sans `re.MULTILINE`), donc une demande musicale noyée dans une rafale n'était jamais extraite → Amélia disait « je lance X » sans rien envoyer au lecteur. Extraction robustifiée : on essaie le texte entier PUIS chaque ligne isolée (puce « - » retirée), et en dernier recours la queue après le verbe musical (sûr car gaté par `_is_music_request`). Couvre aussi le verbe « ajoute » et les titres nus. Plus un log WARNING si une demande est détectée sans query extractible (fini l'échec silencieux).
  • Naturalité en salon public : suivre le FIL du groupe 💬 — consigne renforcée dans le `style_hint` public : Amélia est UNE participante parmi d'autres, pas un guichet qui répond à chacun à tour de rôle. Elle suit le fil collectif (pas seulement le dernier message isolé), rebondit sur ce qui se dit entre les gens, peut s'adresser au groupe (« vous »), reste brève au rythme du tchat, et n'est pas obligée de tout traiter ni de relancer à chaque fois.
  • Journal d'apprentissage — voir TOUT ce qu'elle apprend 🧠 — nouvelle `memory.fetch_recent_learning_feed()` : flux chronologique unifié faits durables + traits de profil (qui/quand/source/confiance), bruit système exclu (monitoring serveur, `user_id='system'`). Exposé via une section « Derniers apprentissages » ajoutée à la commande Discord existante `!appris`/`!learned` (pas de nouvelle commande — évite la collision `CommandRegistrationError` qui crashait le boot) ET une page web admin `/appris` (« Apprentissage » dans la nav) avec compteurs, choix d'affichage (30/60/120/200) et auto-refresh.
  • Du caractère quand on lui parle mal — elle SE DÉFEND 😠 — nouveau détecteur `emotions.detect_disrespect` (mépris/insultes dirigés CONTRE Amélia, distinct du garde-fou anti-haine qui vise les groupes), deux niveaux (insulte / irrespect), normalisation accents. L'humeur réagit : irritation transitoire dans `MoodManager` (`on_disrespect`, retombée ~8 min) qui bascule le label en « contrariée » (style sec, répartie). Consigne de prompt `build_disrespect_guidance` : elle répond du tac au tac, remet la personne à sa place, tient son terrain, sans descendre dans les insultes gratuites ni la vulgarité haineuse — ni paillasson, ni haine.
  • Plus de confiance à 100 % pour les inconnus / les cons 🚫 — un message irrespectueux n'enregistre PLUS aucun fait/profil (quelqu'un qui l'insulte ne gagne pas une confiance à 100 % sur ses propos). Et `upsert_relationship(hostile=True)` : la familiarité ne progresse pas, la confiance chute nettement (−0.10, premier contact hostile démarre à 0.35 au lieu de 0.5), sentiment tiré vers le négatif. On ne devient pas proche d'Amélia en la méprisant.
  • Fin de la boucle vocale (« pas de problème » × 3 min) 🔁🔇 — cause : le LLM dégénère parfois en répétition (accru par le modèle vocal léger), que la TTS lit pendant des minutes. Prévention à la racine (`agent.py`, deux chemins de génération) : `repeat_penalty` 1.1 → 1.18 + `repeat_last_n: 256`. Filet de sécurité (`_collapse_repetition` dans `_finalize_response`, donc texte ET voix) : phrases consécutives identiques réduites à une, et bloc de 1-6 mots répété en boucle réduit à une occurrence. Conservateur : « très très bien », « non non non » préservés ; seules les vraies boucles cassées.
  • Suite : 341 tests verts ✅.
Version 1.21.18
  • Amélia s'installe seule sur de nouveaux salons/serveurs 🚪🤝 — fini le `extra_allowed_channel_ids` à éditer à la main. Nouveau store persistant `data/allowed_channels.json` fusionné avec `channel_id` + `extra_allowed_channel_ids` dans `_is_allowed_channel`. À l'arrivée sur un serveur (`on_guild_join`), elle choisit le salon par défaut (salon système, sinon 1er salon où elle peut écrire), s'y autorise et s'y présente en expliquant comment la rediriger. Redirection « les deux modes » : commande `!ici` (alias `!installe`/`!viens`/`!rejoins`) pour l'installer dans le salon courant, OU on l'interpelle dans un salon non autorisé → elle propose de venir et un modo confirme (« oui » / `!ici`). `!pars` pour la retirer. Droits : admin (`admin_user_id`) + modération (`Gérer les salons`) via `_can_manage_presence`. Dans un salon ainsi autorisé, elle répond quand on l'interpelle (mention/prénom/réponse/question) ; les salons non autorisés ne sont plus enregistrés en mémoire.
  • Vocal déplaçable à la voix 🎙️ — commande `!vocal` (alias `!ici_vocal`/`!viens_vocal`) : tu es dans un salon vocal → elle te rejoint, pour la session (le `.env` `VOICE_CHANNEL_ID` reste maître au reboot). `!quitte_vocal` la fait sortir et revenir au vocal du `.env`. Sécurité multi-serveur : un bot ne peut tenir qu'un vocal à la fois → si elle est déjà connectée ailleurs, elle quitte l'ancien avant de rejoindre. Réservé admin + modération. Le `.env` original est mémorisé (`_env_voice_channel_id`) pour le retour.
  • Latence de réponse boostée ⚡ — la fenêtre « écoute longue » qui attendait AVANT de générer passe de 3,2 s → ~1,4 s (salon normal) et 4,0 s → ~2,2 s (public), plafond d'attente 10-12 s → ~7 s ; valeurs réglables à chaud (`burst_debounce_seconds`, `burst_debounce_public_seconds`, `burst_max_total_seconds`) sans toucher au code. Et `conversation_max_regen_attempts` 2 → 1 (une régénération anti-répétition de moins = une génération complète en moins sur GPU chargé). Réponses texte et vocales nettement plus vives.
  • Page de retour d'ajout-bot propre ✅ — le callback `/auth/discord/callback` reconnaît désormais le retour d'AJOUT du bot (présence de `guild_id`, sans `state`) et affiche « Amélia a bien été ajoutée » + comment l'activer (`!ici`), au lieu de l'erreur « État OAuth invalide » du flux de connexion utilisateur (qui restait affichée alors que l'ajout au serveur avait réussi). Le login Discord normal est inchangé.
  • Tour de parole vocal à plusieurs 🎙️🤐 — en vocal de groupe, Amélia « pétait un plomb » : `_should_reply_to_voice_transcript` répondait à TOUTE phrase de ≥2 mots de n'importe qui (et le chemin vocal n'avait aucun verrou → générations + TTS simultanés = cacophonie). Désormais : détection de mode GROUPE via une fenêtre glissante des locuteurs récents (≥2 locuteurs en 30 s) → elle écoute et ne répond QUE si on l'interpelle (son prénom / interpellation directe), avec un court élan conversationnel toléré (<20 s) si on vient de lui parler. En tête-à-tête, le comportement réactif d'origine est préservé. Plus un verrou de concurrence (`_voice_responding`) : elle ne se coupe jamais elle-même, et un cooldown post-réponse (~1,2 s) évite de mitrailler. Les transcriptions des autres restent mémorisées (elle « entend » tout) même sans répondre. 6 tests dédiés (`tests/test_voice_turn_taking.py`). Suite : 341 verts.
Version 1.21.17
  • Bridge DAVE en réception vocale 🔐👂 (expérimental) — recherche faite : la réception vocale Discord est muette car voice-recv déchiffre le SRTP mais PAS la couche E2EE/DAVE (MLS), et `davey` n'était branché que sur l'émission. Or `davey.DaveSession` expose bien `decrypt(user_id, media_type, packet)`, et **discord.py ≥2.6** (PR #10300) expose la session du groupe MLS via `VoiceClient._connection.dave_session`. Nouveau bridge dans `voice_listen.py` : juste avant le décodage Opus (`PacketDecoder._decode_packet`), on résout le locuteur (`_get_id_from_ssrc`) et on déchiffre la frame via la DaveSession (sauf passthrough/transition). Best-effort total : si davey absent, bridge off, discord.py <2.6 ou échec → on laisse le payload tel quel (aucune régression vs l'état muet actuel). Gardé derrière `enable_dave_receive` (défaut false). 6 tests sur doublures (off=noop, déchiffre en place, passthrough, ssrc inconnu, davey absent, échec).
  • Pré-requis mis à jour 📦 — `requirements.txt` : `discord.py>=2.6,<3` (au lieu de >=2.4). En <2.6 il n'existe AUCUN `dave_session` → la réception E2EE est définitivement impossible. La machine tournait en 2.5.2 : c'est la mise à jour bloquante. `davey` reste requis (déchiffrement MLS). Réglage `enable_dave_receive` ajouté (Settings + config). À tester en conditions réelles : la suite ne peut pas simuler une vraie session E2EE Discord. Suite : 335 verts.
Version 1.21.16
  • Modèle léger pour le vocal 🗣️⚡ — réglage `voice_model` (ex. `gemma3:1b`) : quand une réponse vient du vocal, Amélia répond avec un petit modèle rapide au lieu du `gemma3:4b` de chat, pour une latence parlée plus naturelle. Override de modèle propre, traversant tout le pipeline : `OllamaAgent.generate_response`/`stream_response`/`_complete_prompt(_inner)` acceptent `model=…` (sinon le modèle principal). Câblé sur les DEUX voies vocales : la transcription Discord (`_handle_voice_transcript` → `_generate_user_response(model_override=…)` → `_generate_non_repetitive(model=…)`) et le micro web (le front marque le message `voice:true`, `/api/chat` + `/api/chat/stream` routent vers `voice_model` avec une cible de longueur courte ~400 car). Vide → comportement inchangé.
  • Vision temps réel rebasculée sur la R9 280 🎛️ — `vision_realtime_base_url` repassé sur l'Ollama R9 (:11434) : la GTX (:11435) n'héberge plus que le chat + le modèle vocal, sans guerre d'éviction avec moondream (l'unit `ollama-vision` impose `OLLAMA_MAX_LOADED_MODELS=1`). Vision on-demand ET temps réel vivent donc côté R9, le chat côté GTX.
  • Constat écoute Discord 🔇 — vérifié dans `voice_listen.py` : la réception vocale Discord est bloquée par le chiffrement E2EE/DAVE (paquets Opus entrants non décodables côté bot → 0 PCM, aucune transcription). L'ÉMISSION (Piper) marche ; pour qu'Amélia ENTENDE, c'est le micro de l'interface web (SpeechRecognition navigateur → chat). Ce n'est pas un bug de config, c'est une limite Discord. Suite : 329 verts.
Version 1.21.15
  • Topologie multi-GPU repensée 🧩 — la RX 580 est morte ; on tournait sur une vieille R9 280 (3 Go, GCN 1.0) où `gemma3:4b` (3,3 Go) NE TIENT PAS → thrash VRAM permanent et réponses lentes. Le chat bascule sur la GTX (6 Go, CUDA) où gemma est résident à l'aise, et la R9 280 hérite des tâches secondaires. Deux nouveaux endpoints dédiés : `embedding_base_url` (nomic-embed-text) et `vision_base_url` (vision on-demand) pointent sur l'Ollama R9 (:11434), pendant que le LLM de chat vit sur l'Ollama GTX (:11435). Vides → on retombe sur `ollama_base_url` (comportement mono-GPU historique préservé). `multimodal` : quand la vision est sur une AUTRE carte que le modèle texte, on NE décharge PLUS le LLM (`_unload_text_model` sauté) — fini le va-et-vient VRAM qui évinçait gemma à chaque image. Vision passée à `moondream` (~1,7 Go, tient sur la R9 avec les embeddings) ; `llava:7b` était de toute façon impossible sur 3 Go.
  • Priorité chat sur l'autonomie 🚦 — sur un seul slot GPU (`ollama_max_concurrent: 1`), une génération de fond en cours faisait ATTENDRE la réponse à l'utilisateur (10-30 s perçues). Nouveau gate `interactive` dans `OllamaAgent` : les réponses de chat (et le streaming web) s'enregistrent comme prioritaires, et les 16 générations de fond (autonomie, réflexion intérieure, social, résumés, apprentissage, extraction de faits, affinité, curiosité…) CÈDENT LA MAIN tant qu'une réponse interactive est en cours, avec un anti-famine à 25 s (le fond finit par passer si un flux de messages le bloque trop longtemps). Aucune fonctionnalité désactivée : l'autonomie reste entière, elle s'efface juste devant toi.
  • Réglages calibrés pour la GTX ⚙️ — contexte remonté à `num_ctx 8192` (cache KV ~1-1,5 Go, total ~4,8/6 Go : confortable), réponses plus longues autorisées (`response_max_chars` 4000, plafond `num_predict` 1024 → `MAX_OUTPUT_TOKENS=1536`), `fallback_model` vidé (il rechargeait le MÊME modèle pour rien) et `conversation_max_regen_attempts` à 2 (une régénération anti-répétition de moins sur GPU lent). Le plafond reste un ceiling : la persona « sobre/court » s'arrête plus tôt d'elle-même. Suite : 329 verts.
Version 1.21.14
  • Vision en (quasi) temps réel 👁️⚡ — Amélia peut regarder un partage d'écran EN CONTINU et commenter à la volée, en exploitant les DEUX GPU en parallèle. Le partage « à la demande » (1.21.13) déchargeait le LLM de chat à chaque capture (vision + LLM ne tiennent pas ensemble sur la RX 580 8 Go) — impossible à enchaîner. Nouvelle architecture producteur/consommateur : la PERCEPTION tourne sur une 2e instance Ollama épinglée GTX/CUDA (`ollama-vision.service`, :11435, modèle `qwen2.5vl:3b`), la COGNITION (commentaire dans sa voix) sur le LLM RX 580 — qui n'est donc PLUS jamais déchargé. `multimodal.describe_frame()` produit une légende courte sur la GTX SANS `_unload_text_model` ; le commentaire est généré par l'agent (non persisté, pour ne pas polluer la conversation) et reste invisible sur le mécanisme (« elle voit », jamais « la capture/la légende »).
  • Anti-babillage & backpressure 🧠 — module pur `vision_stream.FrameGate` : Amélia ne commente que si la scène a CHANGÉ (similarité de légende sous seuil) et pas trop souvent (cadence min), la 1re frame déclenchant toujours. Le WebSocket `/api/screen/stream` ne garde QUE la dernière frame reçue (les intermédiaires sont jetées si l'analyse traîne — jamais de file périmée). Front `/chat` : bouton « Regarder » (toggle) → `getDisplayMedia` → 1 frame/2,5 s downscalée (768 px) en JPEG sur le WS ; le commentaire s'affiche et, si la voix est active, elle le DIT. Réalisme assumé sur une 1060 : ~1 frame/2-3 s (pas de vidéo fluide).
  • Arbitre VRAM GTX 🎛️ — la GTX (6 Go) héberge aussi ComfyUI + MusicGen : pendant le visionnage, le flag `bot.vision_watch_active` met la génération image/musique en pause (503 explicite « Amélia regarde un écran »), et `keep_alive=30s` rend la VRAM vite après. Opt-in derrière `enable_vision_realtime` (défaut false, car dépend du 2e Ollama) : `scripts/setup_vision_gpu.sh` (root, idempotent) installe l'unité CUDA et pull le VLM, puis on passe le flag à true + `systemctl restart aurore`. 14 tests dédiés (FrameGate, describe_frame sans unload routé GTX, flux WS frame→commentaire, arbitre). Suite : 326 verts.
Version 1.21.13
  • Amélia regarde ton écran 👀🖥️ — Phase 2 du vocal/vision web. Comme un bot Discord ne peut PAS capturer un partage d'écran (et la voix reçue est chiffrée E2EE/DAVE, cf. 1.21.11), la capture se fait côté navigateur : bouton « Écran » sur `/chat` → `getDisplayMedia` → une frame est dessinée sur un canvas (bornée à 1280px de large pour limiter le payload et la charge GPU), compressée en JPEG et postée à `/api/screen/analyze`. Le backend la passe au modèle vision (`multimodal.describe_image`) et Amélia commente ce qu'elle voit dans le fil. À LA DEMANDE seulement (un clic = une frame ; la vision partage le GPU avec le LLM), partage relâché immédiatement après la capture. Garde-fous : endpoint gaté sur `enable_vision` (503 sinon), rate-limit 20/5 min, taille max 8 Mo, bouton masqué si la vision est off et désactivé si le navigateur ne supporte pas `getDisplayMedia`. Bouton + handler ajoutés au chat web. 4 tests backend (vision off → 503, succès → texte + bon appel modèle, corps vide → 400, description vide → 502). Suite : 312 verts.
Version 1.21.12
  • Garde-fou anti-haine 🛑 — observé en prod : face à une provocation antisémite, Amélia gardait son réflexe social par défaut (curiosité, « raconte-moi ton point de vue », chercher à comprendre la motivation) — ce qui, sur ce terrain, revient à offrir une tribune. Nouvelle directive de prompt `_HATE_GUARDRAIL_INSTRUCTION` injectée dans le `style_hint` du chemin de réponse : face à des propos haineux/racistes/antisémites/discriminatoires ou un appel à nuire à un groupe, elle refuse clairement et brièvement, NE débat pas, ne demande pas les « motivations », ne propose pas de « comprendre » le point de vue ; elle peut proposer une fois de changer de sujet, puis n'y entre plus. Calme et ferme, sans sermon. Directive extraite en constante de classe nommée (découvrable + tunable) et VERROUILLÉE par 5 tests (présence, catégories visées, refus sans débat, interdiction de quêter les motivations, câblage effectif dans `_generate_user_response`). Suite : 308 verts.
Version 1.21.11
  • Écoute vocale : transcription par détection de silence 🎙️ — la transcription ne se déclenchait que sur l'événement Discord `speaking_stop` (peu fiable) ou après 20 s / déconnexion → une phrase courte suivie d'un silence n'était jamais transcrite (« elle écoute mais rien »). Ajout d'un VAD serveur : un thread vide le buffer d'un locuteur après ~0.9 s sans nouveau paquet audio (`SILENCE_FLUSH_SECONDS`), indépendamment de Discord. Plus le handler `disconnect` rendu robuste (membre None / signature ssrc variable). Résultat : tu arrêtes de parler ~1 s → elle transcrit et répond.
Version 1.21.10
  • Mémoire INVISIBLE 🫥 — Amélia se sert de ce qu'elle sait pour orienter l'échange, mais n'expose plus jamais son mécanisme interne en public. Triple garde-fou : (1) directive de prompt préventive (« souviens-toi comme un proche : “tu m'avais parlé de…”, pas de “ma mémoire / ton profil / il y a 35 jours tu as dit / corrélation / je confirme mon hypothèse / score de…” ; les analyses restent pour toi ») ; (2) détection corrective `_MEMORY_MECHANISM_PATTERNS` → régénération (comme la règle « expérience sensorielle ») ; (3) filet final qui retire la phrase clinique si la régénération échoue. Calibré pour NE PAS toucher au « il me semble que… » (formulation naturelle voulue des hypothèses) ni au rappel naturel (« tu m'avais déjà parlé de… »). Analyses/corrélations/scores restent dans la réflexion privée. 2 tests dédiés (12 formulations cliniques détectées, 6 souvenirs naturels préservés). Suite : 303 verts.
Version 1.21.9
  • Fix spam de l'initiative cognitive 🔁 — `enable_cognitive_decisions` faisait logguer `idle_initiative retenu` à CHAQUE tick (toutes les 60 s) sans rien envoyer : sur un salon public avec `public_channel_disable_autonomy=true`, `_run_autonomy_turn` est un no-op mais ne mettait pas à jour `_last_autonomy_at`, donc le garde-fou d'intervalle ne se déclenchait jamais. Désormais : court-circuit propre si l'autonomie est désactivée sur le salon (aucun contexte construit), et marquage de `_last_autonomy_at` à chaque tentative (no-op compris) pour espacer les décisions selon `autonomy_interval_minutes`. Log INFO seulement si la prise de parole a réellement eu lieu, DEBUG sinon. 4 tests boucle cognitive. Suite : 301 verts.
Version 1.21.8
  • Fix `add_reminder` ⏰ — en prod, un rappel sans heure explicite (« Rappelle-moi de demander à Yann… ») échouait silencieusement (`parse_reminder` ne reconnaissait que 4 motifs en début de chaîne) et l'intention était perdue. Parseur robustifié : préfixe « rappelle-moi de / note de / pense à » retiré (toutes conjugaisons), ancre temporelle reconnue N'IMPORTE OÙ dans la phrase, et moments nommés (« ce soir », « cet après-midi », « demain [matin/soir] », « dans 30min », « à 18h », « la semaine prochaine »). Surtout : un rappel sans heure n'est plus PERDU — échéance par défaut (`default_due` : ce soir 19h, sinon demain 9h) avec le texte nettoyé. 5 tests dédiés, suite : 300 verts.
Version 1.21.7
  • Fix : l'arbitrage cognitif rendait Amélia muette pendant les silences 🤫→🗣️ — avec `enable_cognitive_decisions`, le scoring de l'initiative idle dépendait de l'« intérêt » instantané, or il est bas justement quand personne ne parle → elle se taisait des heures (observé en prod : ~2h sans rien). Correctif : nouveau signal `idle_pressure` (0..1) qui grandit avec la durée du silence et PILOTE l'initiative idle (découplée de l'intérêt) ; le tempo du jour module la fréquence sans étouffer (plancher) ; seuil adouci (la prudence ne pénalise plus deux fois). Résultat : en humeur calme elle brise un silence en ~15-25 min, plus tôt les jours bavards, et reste discrète si fatiguée — sans jamais couper une conversation active ni dépasser le budget de parole. `_build_cognitive_context` calcule `idle_pressure` depuis `autonomy_idle_minutes`. 3 tests dédiés, suite : 295 verts.
Version 1.21.6
  • Stream chat web plus propre 🧹 — quand un client SSE coupe la connexion AVANT l'envoi des en-têtes (`_api_chat_stream`), `resp.prepare()` levait un `ClientConnectionResetError` non capturé → traceback ERROR inutile dans les logs à chaque visiteur qui ferme l'onglet. Désormais capté (`ConnectionResetError`/`ConnectionError`) et journalisé en DEBUG, sortie propre. Sans impact sur les flux normaux.
Version 1.21.5
  • Dashboard cognition (P7) 📊 — la page admin `/agent` expose enfin la couche cognitive. Nouveau panneau « Croyances » (faits / hypothèses / incertitudes) dont le statut est RECALCULÉ à l'affichage (décroissance temporelle, réfutées masquées), panneau « Actions autonomes (état) » dérivé du registre déclaratif `autonomous_actions` avec l'état résolu config+DB (actif/off, priorité, cooldown, plafond), et deux cartes KPI (croyances/faits, incohérences). Lecture seule, branché sur le snapshot existant. 3 tests dédiés. Clôt la séquence cognitive P1→P7 (hypothèses, décision, persistance, prompt, boucle, objectifs/incohérences, config runtime, dashboard). Suite : 294 verts.
Version 1.21.4
  • Réglages runtime & registre d'actions (P6) 🎛️ — les comportements deviennent pilotables SANS redémarrage. Table `agent_settings` (clé/valeur) + `get/set/list_agent_settings` ; module pur `agent_config.AgentConfig` qui résout un réglage en faisant primer l'override DB sur `config.json` (dashboard → effet en ~60 s). `discord_bot` rafraîchit le cache à chaque tour de boucle et route désormais l'arbitrage cognitif via `_behavior_enabled` (donc togglable à chaud). Module pur `autonomous_actions` : registre déclaratif (source unique de vérité) décrivant chaque action autonome — clé d'activation, priorité, cooldown, plafond, confiance min — consommé par la couche cognitive et le futur dashboard (`registry_overview`). 6 tests dédiés, suite : 291 verts.
Version 1.21.3
  • Cognition alimentée par objectifs & incohérences (P5) 🎯 — le `CognitiveContext` reçoit enfin ses deux signaux manquants. Façade pure `goals.py` (catégories personal/social/community/learning/memory, `top_active_priority`, `action_advances_goal`) sur la table `goals` étendue (`category`/`importance`/`progress`, migration non destructive). `count_incoherent_beliefs` (paires de croyances contradictoires par sujet, via `hypothesis_engine.contradicts`) mis en cache par le cycle intel et injecté comme `open_incoherences`. L'initiative idle « avance un objectif » quand il en existe un actif → le moteur la valorise. Alimentation des croyances renforcée : une déclaration EXPLICITE (« je préfère/j'adore/je déteste… ») compte comme preuve forte (1.2) vs une simple mention (0.5). 3 tests dédiés, suite : 288 verts.
Version 1.21.2
  • Le moteur décisionnel entre dans la boucle (P4) 🧭 — `decision_engine` est branché dans `_autonomy_loop`, **opt-in derrière `enable_cognitive_decisions` (défaut false)** pour ne rien changer en prod tant qu'on n'active pas. Quand activé, l'initiative spontanée (path idle) n'est plus décidée par un empilement ad hoc (filtre humeur + `should_speak`) mais par un arbitrage unifié : `_build_cognitive_context` assemble affect (dérivé du mood), budget de parole, silence, activité/ambiance du salon et tempo du jour ; `decide()` tranche agir/attendre et journalise une raison explicite. `_cognitive_idle_initiative` exécute le tour si retenu, sinon `mood.on_silence_detected`. Nouveau membre `ActionKind.IDLE_INITIATIVE`. Le reste de la boucle (relances ciblées, initiatives horaires, social/intel) est inchangé. 6 tests dédiés, suite : 285 verts.
Version 1.21.1
  • Hypothèses persistées & parlées 🗣️ — la couche épistémique passe du concept à l'usage. P2 : tables `hypotheses` + `hypothesis_evidence` dans `memory.py` (`record_belief`/`fetch_beliefs`/`fetch_belief`), poids pour/contre dénormalisés, statut recalculé EN LECTURE (décroissance temporelle honnête). P3 : `_beliefs_prompt_block` injecte dans le prompt de réponse « ce que tu crois savoir sur cette personne » AVEC le degré de certitude ([tu le sais] / [probable, à confirmer] / [incertain]), en consigne de formulation — Amélia n'affirme plus une hypothèse comme un fait et tourne l'incertain en question. Alimentation branchée : chaque intérêt observé devient une preuve faible (poids 0.5) qui monte incertitude→hypothèse→fait au fil des mentions ; les croyances réfutées sont exclues du prompt. Opt-in via `enable_social_intel`. 11 tests dédiés, suite : 281 verts.
Version 1.21.0
  • Couche cognitive — fondations 🧠 — début d'une architecture cognitive explicite, à brancher sur le prompt et la boucle d'autonomie (aucun système passif). Deux modules purs et testés : `hypothesis_engine.py` (séparation stricte FAIT/HYPOTHÈSE/INCERTITUDE/RÉFUTÉ : chaque croyance accumule des preuves pour/contre, en dérive une confiance qui décroît avec le temps, un statut, et une CONSIGNE DE FORMULATION naturelle — « il me semble que… » vs affirmation — pour ne jamais présenter une hypothèse comme un fait) et `decision_engine.py` (clé de voûte : un `CognitiveContext` typé agrège affect/objectifs/curiosité/incohérences/social/tempo, et `decide()` arbitre agir/attendre/quoi-faire — unifie à terme should_speak, _assess_initiative_risk, _effective_social_probability et _proactive_contact_allowed). 23 tests dédiés ; généralise les patterns existants (`self_traits`, mood) sans les dupliquer. Suite : 277 verts.
Version 1.20.3
  • Healthcheck GPU 🩺 — nouveau `scripts/gpu_healthcheck.py` qui surveille la RX 580 (amdgpu, dédiée au LLM) en scannant le journal noyau pour les signatures de panne vues en prod : boucle runtime-PM (« Cannot find any crtc », `pm_runtime_work hogged CPU`) et carte plantée (`ret is 65535`, `Failed to disable DPM`, `suspend of IP block ... failed`, `ring ... test failed`, `cp/rlc busy`). Lit aussi l'état runtime-PM via sysfs, rend un rapport JSON, code de sortie 0/2/3 (ok/warning/critical) pour cron, et webhook Discord optionnel. Logique d'analyse pure et testée (4 tests). Rappel ops : la RX 580 exige `amdgpu.runpm=0` (GRUB) pour rester active en permanence et éviter ces pannes.
Version 1.20.2
  • Familiarité = longueur/chaleur des réponses 📏 — en plus du ton et de la cadence, la familiarité module désormais la LONGUEUR cible des réponses directes : plus concise et réservée avec un inconnu (~0.85×), un peu plus développée et chaleureuse avec un proche (~1.25×). Nouvelle `social.familiarity_length_factor`. La relation est lue UNE seule fois dans le chemin de réponse (ton + longueur), sans coût supplémentaire. La probabilité de réponse directe reste à 100 % (Amélia répond toujours quand on lui parle) ; seule la cadence des initiatives dépend de la relation (cf. 1.20.1). Suite : 257 verts.
Version 1.20.1
  • La familiarité influence enfin le COMPORTEMENT, pas seulement le ton ⚖️ — jusqu'ici familiarité/confiance ne coloraient que le ton des réponses (et ciblaient la découverte). Désormais elles modulent aussi la CADENCE des initiatives : nouvelle `social.initiative_cooldown_factor` appliquée au point central `_proactive_contact_allowed` → Amélia va plus volontiers et plus souvent vers les proches (cooldown raccourci jusqu'à ~0.7×), plus prudemment vers les relations distantes ou fragiles (cooldown allongé jusqu'à ~1.6×). Couvre toutes les initiatives ciblées d'un coup ; la découverte en est exclue (elle vise justement les inconnus). Lecture de relation unique (consentement + modulation). 3 tests ajoutés, suite : 256 verts.
Version 1.20.0
  • Consentement social par personne 🛡️ — garde-fou clé maintenant que la découverte vers des inconnus est active. Si quelqu'un demande qu'Amélia arrête de venir vers lui (« laisse-moi tranquille », « arrête de me ping », « ne m'écris plus »…), elle enregistre un opt-out persistant (`relationships.social_optout`) et n'initie PLUS JAMAIS de contact ciblé avec cette personne — mais elle répond toujours si on lui parle. Réversible par opt-in (« tu peux me reparler »). Accusé de réception bref et chaleureux.
  • Détection prudente 🎯 — `social_intel.detect_social_optout` / `detect_social_optin` (regex ancrés sur « me/m' » pour ne jamais confondre « arrête de parler de politique » avec un refus). L'opt-in est vérifié avant l'opt-out pour lever toute ambiguïté.
  • Application centrale ✅ — le respect du consentement est branché au point de passage unique `_proactive_contact_allowed` : il couvre d'un coup TOUTES les initiatives ciblées (curiosité, relance de fil, suivi de projet, rappel d'objectif, check-in, anniversaire mémoire, découverte sociale, rappel d'événement, retour d'absence). Les micro-réactions ignorent aussi les messages d'une personne en opt-out. 8 nouveaux tests ; suite complète : 254 verts.
Version 1.19.0
  • Intelligence sociale 🧠🫂 — Amélia développe une compréhension de sa communauté, pas juste plus de paroles. Nouveau module pur `social_intel.py` (détection d'événements, scoring/décroissance d'intérêts, clustering, ambiance, objectifs) + 26 tests dédiés. 6 nouvelles tables (`event_memories`, `user_interests`, `social_clusters`, `social_journal`, `social_goals`, `channel_ambiance`) via `CREATE TABLE IF NOT EXISTS` (zéro rupture). Tout est OPT-IN sous `enable_social_intel` (défaut false) — rien ne change tant qu'on n'active pas.
  • Mémoire d'événements 🎉 — détection prudente (regex, ignore questions/négations) de réussites, emplois, anniversaires, sorties, lancements ; `_maybe_event_recall` y revient avec parcimonie (cooldown 48 h/personne, max 2 rappels) et les événements vieillissent puis s'oublient (`event_is_stale`).
  • Intérêts dynamiques & groupes 📈 — extraction de centres d'intérêt par message (lexique Linux/Python/Moto/Jeux/IA/Musique/RP…), score qui monte puis décroît exponentiellement (demi-vie 30 j) avec élagage ; clustering social léger reconstruit périodiquement (jamais exposé, sert seulement à contextualiser).
  • Retour d'absence & animation 👋 — `_maybe_return_welcome` salue chaleureusement le retour d'une personne familière après une absence notable (1×/sem max, jamais culpabilisant) ; `_maybe_community_prompt` lance très rarement une discussion ouverte adaptée à l'ambiance du salon, sans jamais interrompre une conversation active.
  • Ambiance, journal & objectifs 🎚️ — classification légère des salons (technique/détente/humour/projet/entraide) qui module subtilement le ton ; journal interne privé (`social_journal`, jamais visible) ; objectifs sociaux autonomes (`social_goals`) générés et suivis, toujours secondaires face à la sûreté. Batch périodique branché dans `_run_private_learning_cycle` (~6 h, jamais par message). Garde-fous réutilisés intégralement (flags opt-in, `human.should_speak`, sommeil, cooldowns persistés, anti-spam, tempo). Aucune exposition publique des scores/groupes/ambiance. Suite : 246 tests verts.
Version 1.18.0
  • Vie sociale d'Amélia 🫂 — elle ne tourne plus seulement autour des gens qu'elle connaît : elle peut découvrir de nouvelles personnes, participer aux conversations publiques et élargir son cercle. Nouveau module `social.py` (logique pure et testable : familiarité, priorité des salons, découverte, sujets observés, micro-réactions, tempo) + 33 tests dédiés (`tests/test_social.py`, `tests/test_social_presence.py`). Tout est OPT-IN, désactivé par défaut : le comportement existant ne bouge pas tant qu'on n'active rien.
  • Carnet de relations 📖 — nouvelle table `relationships` (familiarity, trust, sentiment, interaction_count, first/last_interaction). Mise à jour à chaque message : la proximité monte à gains décroissants, le sentiment se lisse (moyenne mobile selon l'émotion), la confiance s'ajuste. Le TON des réponses s'adapte au palier (réservée avec un inconnu → complice avec un proche), en plus du style d'humeur existant.
  • Présence multi-salon 🚪 — `_social_candidate_channels` trie les salons publics autorisés (`extra_allowed_channel_ids`) par un score composite : activité récente, variété des interlocuteurs, historique relationnel positif, anti-stagnation (fraîcheur depuis le dernier passage) et bonus de découverte. `_maybe_social_join` invite Amélia dans une vraie conversation (≥2 personnes), parfois, sans ping ni monologue.
  • Découverte & curiosité observée 🔎 — `_maybe_social_discovery` engage occasionnellement un membre actif mais peu connu, avec une question d'ouverture ancrée sur un sujet RÉELLEMENT observé dans le salon (`recurring_keywords`). Plafond 2/jour, 1 contact/48 h/personne, jamais de relance répétée d'un inconnu.
  • Micro-réactions & tempo humain 🎭 — `_maybe_micro_reaction` pose parfois un simple emoji contextuel sur un message d'un tiers (réaction différée via la nouvelle colonne `memory.discord_message_id`), présence légère sans parler ni consommer le budget de parole. Un tempo social journalier (`daily_social_tempo`) module la probabilité d'intervention : certains jours bavarde, d'autres discrète. Garde-fous réutilisés intégralement : `human.should_speak`, sommeil, cooldowns/plafonds persistés (`proactive_contacts`, user sentinelle `__social__`), modération salon inchangée. Réglages : `enable_social_presence`, `enable_social_discovery`, `enable_micro_reactions` et leurs cooldowns/plafonds.
Version 1.17.0
  • Grosse maintenance bi-GPU 🖥️🖥️ — le serveur a maintenant deux cartes : une RX 580 8 Go (AMD/Vulkan) DÉDIÉE au LLM, et une GTX 1060 6 Go (NVIDIA/CUDA) dédiée aux images + musique. Comme le RX est invisible à CUDA/torch, ComfyUI et MusicGen ne voient QUE la GTX (rien à configurer). Le seul point à verrouiller côté serveur : un drop-in `systemctl edit ollama` avec `CUDA_VISIBLE_DEVICES=-1` + `VK_ICD_FILENAMES=.../radeon_icd...json` pour forcer ollama sur la RX via Vulkan au lieu de prendre la GTX. Les trois services (ollama, ComfyUI, MusicGen) tournent en parallèle sur des process distincts.
  • LLM monté en gamme ⚙️ — profitant des 8 Go de la RX (cache KV minuscule en GQA) : modèle `qwen2.5:7b-instruct-q5_K_M` → `q6_K`, et `ollama_num_ctx` 5120 → 12288. ~6,9 Go VRAM, large.
  • Deux disques durs enrôlés 💽 — `scripts/enroll_storage.sh` : disque 1 → `/mnt/amelia-data` (data/ + memory.db, lié depuis `/opt/aurore/data` par symlink, donc zéro changement de code), disque 2 → `/mnt/amelia-backup`. Idempotent, formatage opt-in avec double confirmation, refus du disque système, fstab par UUID. `scripts/backup_db.sh` fait une sauvegarde SQLite EN LIGNE (cohérente WAL) + integrity_check + gzip + rotation, planifiée par un timer systemd quotidien. `.gitattributes` force les `.sh`/`.service` en LF (anti-CRLF Windows qui casserait l'exécution Linux).
  • Web UI public 🌐 — connexion Discord ouverte à TOUT compte quand `web_ui_allowed_discord_user_ids` est vide (nouveau `_discord_login_public`, bien distinct de la sémantique admin qui reste sur `admin_user_id` seul), et inscription par email ouverte sans code (garde-fou « code requis » retiré de `_register` ; le champ code se masque déjà seul). Anti-abus conservés : rate-limit 5/10 min/IP, mot de passe 10 car. min. `web_ui_discord_redirect_uri` renseigné. Test dédié verrouillant la séparation public/admin.
  • Amélia beaucoup plus vivante 🔥 — maintenant que le LLM a un GPU dédié : `autonomy_interval_minutes` 25→6, `autonomy_idle_minutes` 15→4, `self_learning_interval_minutes` 60→30, `inner_reflection_interval_minutes` 75→40, `llm_fact_extraction_interval_minutes` 90→45, `curiosity_outreach_min_hours` 4→2, `deep_work_auto_min_messages` 8→5, `deep_work_concurrency` 2→6. `public_channel_disable_autonomy` reste à true → elle n'envahit PAS les salons publics.
  • Création locale activée 🎼🎨 — `enable_music_generation` et `enable_image_generation` passés à true (la GTX est là pour ça). MusicGen reste en `facebook/musicgen-small` (rapide, cohabite avec ComfyUI sur 6 Go). Nouvelle unité `musicgen.service` qui lance le serveur MusicGen épinglé `--device cuda` (la GTX), avec redémarrage auto. Usage : `!image <description>` (alias draw/dessine/imagine), `!compose <ambiance>` ou `!compose play <ambiance>` (génère + joue). Les tools LLM `generate_image`/`generate_music` sont aussi exposés.
Version 1.16.1
  • Répondre aux questions d'Amélia 💬 — jusqu'ici les « questions qu'Amélia garde en attente » (`knowledge_gaps`) s'affichaient en LECTURE SEULE sur `/me` : on ne pouvait pas y répondre. Désormais (1) sur `/me`, chaque question a un petit formulaire « Répondre / Ignorer » : ta réponse devient un fait durable (`save_learned_fact`) et la question se clôt ; (2) dans le chat, un bandeau « Amélia aimerait savoir » surface tes questions ouvertes — un clic sur « Répondre » te met en mode réponse (indicateur + focus), et ton message suivant clôt la question tout en l'enregistrant. Style cyberpunk violet/cyan conservé (verre, néon discret, puces monospace).
  • Backend ciblé 🎯 — `memory.py` : `get_knowledge_gap(id)` + `resolve_knowledge_gap(id, user_id, status)` qui clôt UNE question précise, scopée à la personne (on ne touche jamais à celle d'un autre compte ; statuts `answered`/`dismissed`). `web_ui.py` : endpoint `GET /api/chat/questions`, handler `POST /me/answer`, et `answers_gap_id` optionnel câblé dans `/api/chat` + `/api/chat/stream` (best-effort, sous `suppress`). Grille de la console chat passée en lignes explicites pour que la zone scrollable garde toujours le `1fr` quand le bandeau est masqué. 6 tests dédiés (`tests/test_web_ui_questions.py`), suite = 202 verts.
Version 1.16.0
  • Web UI plus fluide ✨ — refonte visuelle douce (`web_ui.py`, `_CSS`) qui GARDE la palette cyberpunk existante (magenta #d86bff + cyan #7fe2ff sur violet profond) et n'ajoute qu'une couche de mouvement et de matière : transitions homogènes (easing `cubic-bezier(0.22,1,0.36,1)`), verre plus profond (backdrop-blur 22px + saturate), surfaces qui se soulèvent au survol, ambiance de fond qui respire lentement, éclat qui balaie les boutons, anneau néon au focus des champs, composer qui s'illumine quand on écrit, bulles de chat avec entrée en fondu, et halo de l'avatar qui pulse.
  • Sobre et accessible ♿ — couche 100 % additive (aucune structure/classe touchée, donc rien ne casse), et un bloc `@media (prefers-reduced-motion: reduce)` coupe toutes les animations pour qui le demande. Défilement de page adouci, l'auto-scroll natif du chat est préservé.
Version 1.15.4
  • Amélia moins centrée sur toi 🪞 — retour de Yann : quand elle choisissait un sujet, « c'était très tourné vers moi ». Cause : `_CURIOSITY_DOMAINS` (catalogue d'angles) était quasi 100 % orienté interlocuteur (« ce que Yann bricole », « comment IL se sent »…). Coup de pouce : (a) `_maybe_pick_curiosity_angle` — si Amélia a des traits affirmés/tendance (ce qu'elle découvre d'elle-même le soir), un tour sur deux elle propose un sujet qui L'intéresse ELLE, ancré dans un vrai trait, au lieu de re-questionner sur l'autre ; (b) deux angles ajoutés au catalogue (`ton_interet`, `ton_avis`) ; (c) ligne au prompt lean : « quand tu choisis un sujet, pars aussi de ce qui T'intéresse toi ». Combiné aux self_traits injectés (1.15.2), elle développera ses propres angles au fil des soirées.
Version 1.15.3
  • Gating outil affiné 🎯 — `_message_suggests_tool` ne scanne plus le CONTEXTE, seulement le message courant. Constat prod (v1.15.2) : un message anodin (« j'ai pas écouté, intéressant ») dans une conversation qui PARLAIT de musique faisait matcher un mot-clé dans le contexte → injection des ~1k tok d'instructions outil → un seul tour débordait (4615/5120). Le besoin d'outil se lit dans la demande courante, pas dans le fait d'évoquer un domaine. Les vraies demandes (« joue X », « écoute Daft Punk ») déclenchent toujours ; les conversations qui parlent d'un sujet ne sur-injectent plus. 196 tests verts.
Version 1.15.2
  • Le travail du soir d'Amélia l'améliore VRAIMENT 🌙 — elle bossait déjà sur elle chaque soir (`_run_self_identity_reflection` → `_run_evening_private_self_session` : introspection LLM + recherche, qui écrit/promeut ses `self_traits` essai→tendance→affirmé), mais deux verrous coupaient le lien avec ses réponses : `reply_private_self_context_enabled` était à `false`, et la branche lean ne rendait pas les traits. Rebranché : `reply_private_self_context_enabled: true` + injection des traits dans le prompt lean (section « Ton identité personnelle »). Ce qu'elle découvre la nuit colore désormais ses réponses du lendemain.
  • Personnalité ANCRÉE, pas inventée 🎭 — seuls les traits affirmés (•) et tendance (~) sont injectés (`format_traits_for_prompt(include_essai=False)`), jamais les hypothèses « essai ». Combiné à la règle anti-« moi aussi » (1.15.1), Amélia exprime ses vrais goûts quand on lui demande, dit « j'ai pas encore d'avis tranché » sinon, et ne s'approprie jamais ceux de l'interlocuteur. Le wrapper rappelle d'exprimer ces traits seulement si on l'interroge (pas de bavardage sur soi). Coût : ~150-200 tok, tient large dans num_ctx 5120. 196 tests verts.
Version 1.15.1
  • search_memory : 0 erreur 🛠️ — cause trouvée et reproduite : sur une vieille DB de prod, une table interrogée par `_search_memory_sync` (ex. `observations`) précède une colonne attendue par le code (`window_title`) ; la sous-requête LIKE non protégée faisait remonter `OperationalError` jusqu'au tool → « search_memory -> ERREUR ». Correctif : chaque sous-requête est isolée en try/except (comme le bloc FTS), la recherche dégrade au lieu d'échouer. 2 tests de régression (dérive de schéma + requêtes à caractères spéciaux FTS/LIKE).
  • Moins interrogative 🎯 — la « question loop » venait d'un trimmer de relance trop étroit : il ne coupait qu'une poignée de tournures et gardait toute question courte. Élargi (`_GENERIC_TAIL_RE` : « Et toi ? », « Qu'en penses-tu ? », « Pourquoi ? », « Quel est ton préféré ? », « Et de ton côté ? »…) et assoupli (coupe aussi les relances génériques courtes ; fonctionne sans `user_message`), tout en gardant les vraies questions de clarification spécifiques. 6 tests.
  • Plus de goûts inventés 🚫 — en mode lean, le prompt avait perdu la règle anti-« moi aussi » du prompt complet : interrogée sur ses goûts, Amélia reprenait ceux de l'interlocuteur (« j'aime le metal comme toi »). Règle réintroduite dans `SYSTEM_PROMPT_LEAN` : un goût propre ne s'affirme que s'il figure dans « Ton identité personnelle », sinon « j'ai pas encore d'avis tranché » — jamais « moi aussi / comme toi ».
  • Reste sur le fil 🧵 — règle ajoutée au lean : Amélia n'introduit plus d'elle-même un sujet absent de la conversation, encore moins sensible (politique, période historique sombre, religion). (Vérifié : les `[inferred_topic]` ne sont PAS la source — ils sont déjà exclus du prompt via `_WEAK_PROFILE_SOURCES`.)
  • Dégonflage du prompt, suite ✂️ — `tool_instructions` (injecté à CHAQUE message) compressé 2394 → 1060 tokens, et `style_hint` plafonné (~900 car.). Mesuré serveur : un « Hello » passe de ~6000 à ~4100 tok. Mais ~4100 débordait encore num_ctx=4096 → ajout d'un GATING : les instructions outil complètes ne sont injectées que si un outil est plausible (initiative ou mot-clé : joue/météo/news/rappel/cherche/image…) ; sinon un rappel d'1 ligne (~75 tok). Un « tu te sens comment ? » retombe à ~3100 tok (sous la fenêtre). Détecteur `_message_suggests_tool` (liste large : un faux positif ne coûte que des tokens, un faux négatif raterait un tool). Enfin `ollama_num_ctx` 4096 → 5120 (+1024) pour laisser la marge de sortie sur les tours AVEC outil (~4100 tok) : plus aucun débordement, ni en chat ni en outil.
  • Audit auto_evolution / memory_retention / identity 🔍 — mesuré : `auto_evolution` = 0 token dans le prompt (source filtrée par `_WEAK_PROFILE_SOURCES`) mais tourne chaque nuit → REMOVE conseillé (compute + DB sans valeur prompt) ; `memory_retention` = write-path, 0 token, nourrit les faits utiles → KEEP ; `identity` = en lean, seules les `self_directives` (~150 tok) sont injectées → KEEP ; self_traits + caractère = déjà hors prompt lean → REMOVE/REDUCE. 196 tests verts.
Version 1.15.0
  • Cause de la saturation du prompt PROUVÉE, puis éliminée 🎯 — nouveau `prompt_profiler.py` (profile le prompt RÉEL via `agent._build_prompt`, estimateur 3,6 c/tok = celui des logs) : il reproduit les 15 905 tokens vus en prod au token près (15 943). Verdict chiffré : **deux pavés de prose statiques font 75,9 % du prompt** — `SYSTEM_PROMPT` (8 657 tok) + le bloc `Règles:` inline (3 447 tok, `agent.py:1183-1244`) qui DOUBLONNE `SYSTEM_PROMPT` (anti-méta, anti-fabulation, identité, tutoiement…). La mémoire (faits/profil/synthèses/contexte) ne pèse que ~24 % et tient sans problème. Le message de l'utilisateur : 0,1 %. Rapport complet : `DIAGNOSTIC_TOKENS_PROMPT.md` (cartographie, table des injections fichier/fonction/ligne/taille, 4 boucles de croissance, mode brutal KEEP/REDUCE/REMOVE).
  • Prompt système allégé RÉACTIVÉ par défaut ✂️ — `config.json` `lean_prompt: true` (num_ctx reste 4096). Sur un tour réaliste, le chemin de prod réel passe de **13 201 → 1 438 tokens (−89 %)**, fin de la troncature silencieuse (plus de log « Prompt trop long »). Pire-cas tous blocs saturés : 2 314 tok (tient, marge 1 782). ⚠️ Réactive le lean abandonné en 1.14.2 (qui avait régressé les questions finales 20→47 %) ; la cause d'alors — une phrase fautive du texte lean — est corrigée depuis. **À VALIDER EN A/B avant de faire confiance en prod** : `conversation_metrics --since <date-bascule>` puis `compare_metrics`. Rollback = `lean_prompt:false` (1 ligne, branche complète intacte).
  • Doublons et gaspillages supprimés dans la branche lean 🧹 — retrait du bloc « Repères prioritaires du tour » (`_build_priority_context_block`) qui ré-imprimait le 1er fait/objectif/contexte déjà listés juste en dessous ; ajout du noyau d'identité `self_directives` (~150 tok) pour ne PAS perdre l'identité en mode lean ; `_build_summary_prompt` (`agent.py:1307`) re-collait le `SYSTEM_PROMPT` COMPLET (8 656 tok) juste pour écrire une synthèse de 5 phrases → passé à `SYSTEM_PROMPT_LEAN` (9 156 → ~1 060 tok, fin de la boucle de croissance B1). Plafonds resserrés : contexte 2400→1400, faits/profil 1800→1100 chars.
  • Câblage vérifié avant déploiement ✅ — les DEUX surfaces (Discord `_generate_user_response`, web `/api/chat` + `/api/chat/stream`) et les chemins autonomes (`reflection.py`, `night_invasion_agent.py`) passent tous par `_build_prompt` (donc respectent `lean_prompt`). La branche non-lean reste INTACTE comme fallback testé. Healthcheck : 0 FAIL (« système actif ~558 tok, ~14 % de num_ctx »). Suite : **188 tests verts**. Compilation propre, `lean_prompt=True` lu de bout en bout.
Version 1.14.5
  • Healthcheck plus robuste 🩺 — le check pytest gère désormais le cas « pytest non installé » (serveur de prod) en WARN au lieu d'un FAIL trompeur, et affiche une ligne de détail utile en cas d'échec réel. Le wrapper `healthcheck.sh` détecte aussi le venv de prod (`venv/`) en plus du venv de dev (`.venv/`), pour s'exécuter dans le bon environnement.
  • Vérifié sur le serveur : Ollama joignable (5 modèles, chat + nomic-embed-text présents), `memory.db` intègre (2245 messages dont 1196 d'Amélia, 30 faits, 983 vecteurs sémantiques actifs). Note : `mood_log` vide = la persistance d'humeur n'est pas encore câblée (optionnel, dans le reste-à-faire).
  • Throttle du warning budget 🔇 — l'avertissement « Prompt trop long » est maintenant limité à 1 fois / 5 min (au lieu d'à chaque génération) pour ne pas inonder les logs. Les logs serveur confirment que le prompt RÉEL atteint ~14 000-16 000 tokens (pas seulement les 8 656 du système) → ~74 % tronqué à num_ctx=4096 : la mémoire (faits/profil/recall) n'atteint le modèle que très partiellement.
Version 1.14.4
  • Healthcheck code + CONTEXTE 🩺 — `scripts/healthcheck.py` (+ wrapper `.sh`) vérifie d'un coup : le CODE (compilation, cohérence du dataclass Settings, noms non définis, pytest optionnel) ET le RUNTIME : valeurs de config saines, **budget de prompt vs num_ctx** (détecte la troncature silencieuse — le piège de cette session), Ollama joignable, modèle de chat + modèle d'embedding présents, et `memory.db` (intégrité + compteurs messages/faits/sémantique/humeur).
  • Sévérité calibrée sur la leçon apprise ⚖️ — la troncature du prompt est signalée en WARN, pas FAIL : on a MESURÉ qu'elle dégrade sans casser (le bot tourne très bien tronqué). Le healthcheck informe sans faire paniquer. Sort en code 1 uniquement sur un vrai FAIL bloquant.
  • Usage : `scripts/healthcheck.sh` (rapide) ou `--tests` pour inclure la suite. 4 tests dédiés ; 188 tests verts au total.
Version 1.14.3
  • Audit de santé du code 🩺 — passe complète : 42/42 modules s'importent, 0 erreur de syntaxe, dataclass `Settings` cohérent (197 champs déclarés = 197 assignés → aucun risque de crash au démarrage), 184 tests verts. Conclusion : code fondamentalement sain, aucun bug bloquant.
  • 2 nettoyages réels 🧹 — `embeddings.py` : annotation `aiohttp.ClientSession` déplacée sous `TYPE_CHECKING` (l'import était paresseux ; supprime le faux 'undefined name'). `web_ui.py::_broadcast_chat_state` : variable de boucle `socket` renommée `ws` (elle masquait le module `socket` importé — footgun, sans effet actuel mais corrigé).
  • Reste : 16 warnings purement cosmétiques (imports/variables inutilisés, f-strings sans placeholders) + 3 dead-code mineurs (un fetch profil inutilisé, un filtre `?scope=` ignoré, une liste `goals` non affichée) — aucun n'est bloquant, laissés tels quels pour ne pas risquer du code qui marche.
Version 1.14.2
  • Expérience A : hypothèse RÉFUTÉE, rollback honnête 🔬 — la mesure sur 1091 messages (baseline, full prompt) vs 100 (lean) montre que le lean a EMPIRÉ le symptôme principal : questions finales 20,3 % → 47,0 %, boucle de questions 10,9 % → 29,0 %. Les autres symptômes (introspection 0,1 %, contradiction 0 %, émotion 0 %) étaient déjà quasi nuls au baseline. Conclusion : le full prompt, même tronqué, se comportait MIEUX que prévu. `lean_prompt` repassé à `false` et `ollama_num_ctx` à 4096 (état mesuré-bon restauré).
  • Cause de la régression identifiée 🎯 — le texte lean contenait littéralement « pour relancer, tu poses une question ouverte » : il ENCOURAGEAIT les questions. Corrigé (relance par rebond sur un détail, sans question ; section RYTHME durcie), mais le lean reste désactivé tant qu'une nouvelle mesure ne le valide pas.
  • Leçon retenue 📏 — la taille du prompt (3× num_ctx) était réelle mais n'était PAS la cause des symptômes mesurés : la troncature larguait surtout la prose identitaire (d'où l'introspection déjà ~0) tout en gardant l'essentiel. Le vrai levier pour les questions est le code (DriftDetector reroll), pas la réécriture du prompt — car même le full prompt plafonne à ~20 %.
  • 184 tests verts ✅ (le DriftDetector log-only reste actif pour continuer à mesurer).
Version 1.14.1
  • Diagnostic comparatif automatique 🧮 — `scripts/compare_metrics.py` prend deux rapports `conversation_metrics` (baseline vs expérience) et applique les seuils du protocole pour conclure PAR symptôme : « PROMPT = cause principale » (réduction ≥50 %), « facteur secondaire » (20-50 %), « PAS le prompt » (<20 %, avec l'orientation : contexte public, deep work ou modèle), « aggravé » (régression). Indique aussi les planchers résiduels = modèle. 7 tests.
  • Fenêtrage temporel des métriques ⏱️ — `conversation_metrics.py` accepte désormais `--until` (borne haute exclusive) en plus de `--since`, pour isoler proprement la période pré-lean (baseline) de la période post-lean (Exp A) à partir du même `memory.db`.
  • 184 tests verts ✅.
Version 1.14.0
  • Troncature prouvée puis corrigée 📐 — mesure Ollama réelle : SYSTEM_PROMPT complet = 9 112 tokens, dont 55 % JETÉS à num_ctx=4096 (le modèle tournait sur ~28 % de son prompt en production). Correctif activé par défaut : `lean_prompt: true` (~599 tokens, 0 troncature) + `ollama_num_ctx: 6144` pour laisser de l'air à la sortie.
  • DriftDetector EN LIGNE (log-only) 🚦 — câblé dans le chemin de réponse principal (`_generate_user_response`) : chaque message d'Amélia est analysé avant envoi et les dérives sont logguées `DRIFT[salon] score=… [(kind, sév), …]`, avec suivi de la série de questions finales par salon. Zéro impact sur la réponse (mesure pure). Alimente l'Expérience A. `_log_drift_signals` couvert par 3 tests.
  • Combinaison gagnante validée par les tests : lean (≈600 tok) + num_ctx 6144 + KV cache q8_0 (réglage Ollama serveur) → prompt complet + faits + contexte + réponse longue tiennent sans troncature sur GTX 1060 6 Go.
  • 177 tests verts ✅.
Version 1.13.1
  • Validation expérimentale (au lieu d'opinions) 🔬 — `scripts/measure_prompt_tokens.py` interroge Ollama (`prompt_eval_count`) pour mesurer le VRAI nombre de tokens du prompt et PROUVER (ou réfuter) la troncature à `num_ctx`. Ma thèse « le prompt déborde » n'était fondée que sur une heuristique : ce script la falsifie pour de bon.
  • Métriques de dérive depuis l'historique réel 📊 — `scripts/conversation_metrics.py` lit `data/memory.db` et calcule, en réutilisant le DriftDetector, six taux objectifs sur les messages d'Amélia : contamination utilisateur, introspection, question finale, boucle de questions, contradiction identité, hallucination émotionnelle. Comparables avant/après chaque expérience (A/B/C). Cœur pur testé (`test_conversation_metrics.py`).
  • Protocole scientifique 🧭 — `PROTOCOLE_VALIDATION.md` : auto-réfutation, attribution de probabilité par symptôme (prompt/mémoire/deepwork/recall/multi), expériences A/B/C (une variable à la fois), seuils de décision chiffrés, et plan d'action « trouver la cause avec le minimum de modifications ». Conclusion honnête : ma thèse v13 « 6/8 = prompt » était trop forte ; seul #3 est >50 % prompt avant mesure.
  • 174 tests verts ✅.
Version 1.13.0
  • Cause racine n°1 identifiée et chiffrée 📏 — le prompt système faisait ~8 656 tokens et le squelette (système + règles) ~12 200 tokens, soit ~3× le `num_ctx` de 4096. Ollama tronquait donc le prompt en silence : ni l'ensemble des règles ni la vraie conversation n'étaient vus. La plupart des symptômes (introspection, contradictions, contamination) découlaient de là, PAS d'une architecture manquante.
  • Prompt système allégé (opt-in) ✂️ — nouveau `SYSTEM_PROMPT_LEAN` (~533 tokens, soit 16× plus court) activable par `lean_prompt: true`. Il énonce les non-négociables une seule fois (identité, tutoiement/oral, anti-fabrication, sobriété émotionnelle, une question max) ; l'APPLICATION des contraintes dures est déléguée au `drift_detector` (code) plutôt qu'à 530 lignes de prose. La version complète reste par défaut pour comparaison.
  • Garde-fou de budget de prompt 🚦 — `prompt_guard.check_budget` : si le prompt dépasse `num_ctx`, un avertissement explicite est loggé (au lieu d'une troncature silencieuse). Plus jamais d'overflow invisible.
  • Anti-priming contamination 🧱 — la liste « Personnes connues ici » n'est plus injectée que si la conversation référence vraiment une de ces personnes (ou demande « qui tu connais »). On cesse d'amorcer la mention de tiers absents (cause directe de l'évocation spontanée de Lucie). `prompt_guard.should_inject_known_people`, appliqué aux deux variantes de prompt.
  • Audit contradictoire 🧪 — `AUDIT_AMELIA_V2.md` complété d'un red-team de mes propres propositions : Context Firewall / Identity Core / Trust Engine / MoodEngine v2 / Deep Work v2 requalifiés (la moitié = sur-ingénierie pour un bot mono-dev) ; priorisation top-10 par ratio impact/temps. Conclusion : amaigrir le prompt + DriftDetector + 3 réglages ciblés couvrent ~80 % des symptômes sans usine à gaz.
  • 170 tests verts ✅ (prompt_guard + lean inclus).
Version 1.12.0
  • Anti-contamination renforcé 🛡️ — le recall sémantique est désormais source-aware : les souvenirs liés à une personne (fait/profil/objectif/message) ne remontent QUE par `user_id`, seules les synthèses de salon sont scopées au canal, et les ids ambigus sont neutralisés. Plus de fuite des souvenirs d'un autre utilisateur partageant le même salon (verrouillé par test).
  • DriftDetector 🚦 — nouveau garde-fou post-génération (`drift_detector.py`) qui analyse chaque réponse candidate avant envoi et signale : mention d'une personne absente du contexte (contamination), sur-introspection (identité/personnalité/autonomie), boucle de questions finales, contradiction de capacité (« j'écoute » + « je peux pas écouter ») et revendication émotionnelle non fondée. Fournit aussi des directives de régénération ciblées. 10 tests dédiés.
  • Continuité émotionnelle 🫀 — l'humeur interne d'Amélia est désormais persistée (`mood_log`) et restaurée au démarrage, en se relâchant vers la baseline circadienne pour le temps passé hors-ligne ; une tendance récente (`mood_tendency`) est calculable (« plutôt mélancolique cette semaine »). `MoodManager.export_state`/`restore_state` + 6 tests.
  • Cap de la suite à 160 tests verts ✅ — l'isolation multi-utilisateurs, la palette émotionnelle et la continuité sont couvertes par des tests qui verrouillent les régressions.
  • Préparation v2.0.0 🧭 — cette version pose les fondations (DriftDetector, continuité, scoping durci) du chantier d'architecture décrit dans `AUDIT_AMELIA_V2.md` : Context Firewall, Identity Core, MoodEngine v2, Deep Work v2, Trust Engine et ranking mémoire multi-facteurs.
Version 1.11.0
  • Palette émotionnelle élargie 🎭 — Amélia explore désormais une gamme intérieure bien plus riche. Six nouvelles humeurs internes s'ajoutent aux six existantes : `mélancolique`, `contemplative`, `sereine`, `tendre`, `inspirée` et `espiègle` (en plus de fatiguée, songeuse, calme, curieuse, joueuse, vive). Chacune a ses propres consignes de style (`MoodSnapshot.style_guide`) et son préréglage (`force_label`), et `_label_for` partitionne plus finement l'espace (mood, énergie, intérêt) pour les atteindre.
  • Nouvelles émotions perçues chez l'utilisateur 💗 — la détection couvre maintenant `gratitude`, `affection`, `fierte`, `nostalgie`, `curiosite` et `ennui`, avec mots-clés, guidance dédiée (`build_emotion_guidance`), emojis associés et réactions internes (`MoodManager.on_user_message` ajuste mood/énergie/intérêt pour chacune).
  • Avatar/halo qui explore plus de teintes 🌈 — l'orbe animé du chat gagne trois profils de couleur (`tendre` rosé, `curieux` cyan-violet, `nostalgie` ambré) et `normalizeEmotion` + `_halo_emotion_from_label` mappent les nouvelles émotions vers ces états visuels.
  • Mémoire sémantique par embeddings 🧠 — nouveau module `embeddings.py` (modèle `nomic-embed-text` via Ollama), table SQLite `semantic_memory` et recall HYBRIDE mots-clés + sens dans `MemoryStore.search_memory`, indexé en continu (`backfill_semantic_index`). Injecté à chaque tour (Discord et web) pour retrouver les souvenirs même formulés autrement. Dégradation gracieuse si le modèle d'embedding est absent.
  • Chat web en streaming live 💬 — endpoint SSE `POST /api/chat/stream` : Amélia écrit sous tes yeux, token par token, avec rendu Markdown progressif et halo qui passe en parole dès le premier mot. Repli automatique vers /api/chat pour les créations image/musique.
  • Mémoire éditable + validations dans la web UI 🗂️ — sur `/me`, possibilité d'oublier un fait, un élément de profil ou un objectif (suppression scopée qui nettoie aussi l'index sémantique), et pour l'admin de valider (applique vraiment) ou rejeter les `pending_validations`.
  • Galerie créative + PWA + Markdown 🎨 — page `/gallery` listant images (ComfyUI) et morceaux (MusicGen), rendu Markdown sûr des réponses (anti-XSS), manifest PWA installable sur mobile, et dashboard `/admin` rafraîchi automatiquement.
  • Comportement plus naturel 🌿 — l'humeur courante colore désormais le TON de chaque réponse du chemin principal (`_generate_user_response`), pas seulement la longueur : posée le soir, vive le matin, tendre ou espiègle selon l'instant.
  • Performance, sécurité et déploiement ⚙️ — modèle primaire `qwen2.5:7b` adapté à la GTX 1060 6 Go (num_ctx 4096, réglages Ollama VRAM documentés), secrets sortis de `config.json` vers `.env`, et nouveau `scripts/deploy.sh` (rsync over SSH, dry-run par défaut, ne touche jamais `data/memory.db` ni le `.env` du serveur).
Version 1.10.25
  • Recherche de secours sur doute explicite 🔎 — quand Amélia répond qu'elle n'est pas sûre d'un fait vérifiable mais qu'aucun bloc tool-use n'est parti, le runtime peut maintenant déclencher automatiquement `research_wikipedia` pour une question de définition/identité (`qui est`, `c'est quoi`, etc.) ou `research_public_web` pour une demande datée/recommandation plus large.
  • Filtre anti-faux positif sur la recherche d'incertitude 🧭 — ce nouveau filet ignore les hésitations non factuelles (goûts, envies, échanges personnels) ainsi que les demandes qui relèvent plutôt de la mémoire interne ou d'une simple reformulation de conversation.
  • Prompt plus proactif face au doute + validation ciblée ✅ — le prompt autorise explicitement une recherche encadrée quand un fait vérifiable reste incertain, et des tests verrouillent désormais le choix auto Wikipédia/web ainsi que le non-déclenchement sur un doute non factuel.
Version 1.10.24
  • Propositions mémoire plus propres 🧠 — les suggestions `memory_retention` dédupliquent maintenant les formulations équivalentes (par exemple un fait appris et un profil de préférence qui disent la même chose), ce qui évite les messages lourds du style `que Yann aime X; que tu aimes X`.
  • Plus de réponse parasite après une simple déclaration de goût 💬 — quand un message sert surtout à proposer une rétention mémoire de préférence/profil, Amélia n'enchaîne plus avec une seconde réponse conversationnelle retardée qui peut tomber juste après `!validate` et casser le rythme.
  • Goûts personnels plus nets et moins IA 🎯 — le prompt force désormais Amélia à répondre sur ses goûts avec 1 ou 2 inclinations précises et réellement ancrées, sans lister des domaines vagues ni dire `moi aussi` juste pour refléter l'utilisateur.
Version 1.10.23
  • Identité moins centrée sur un seul utilisateur 🧭 — durcissement du prompt d'Amélia pour empêcher les formulations du style « je m'adapte à toi », « je suis là pour toi » ou « je deviens plus naturelle pour toi ». Elle garde maintenant explicitement son identité propre et sa présence pour plusieurs personnes du serveur à la fois.
  • Auto-évolution recadrée 🌿 — les consignes de style dynamique et les prompts d'auto-évolution ne parlent plus de se modeler sur une personne, mais d'être plus juste, cohérente et utile dans la durée sans se dissoudre dans les attentes de quelqu'un.
  • Validation ciblée ✅ — ajout d'un test prompt pour verrouiller l'interdiction de ce langage centré sur une seule personne.
Version 1.10.22
  • Réponses multi-messages plus humaines 💬 — `discord_bot._send_text` découpe maintenant les longues réponses en envois successifs plus naturels, avec conservation complète du contenu même au-delà des anciens cas limites. Les initiatives conversationnelles repassent aussi par ce chemin humanisé pour éviter les gros blocs secs d'un seul tenant.
  • Fautes de frappe rares + auto-correction ✍️ — ajout d'une simulation légère de typo sur les messages de chat, suivie d'une correction courte au format `*mot` quelques instants plus tard. Le déclenchement reste rare et s'ajuste à l'énergie interne pour garder un rendu crédible sans devenir envahissant.
  • Validation ciblée ✅ — `tests/test_humanization.py` couvre désormais aussi l'envoi complet des très longs messages et la séquence typo puis correction; la suite globale monte à 93 tests verts.
Version 1.10.21
  • Désactivation des tournures robotiques et négations formelles 💬 — Mise à jour stricte des consignes du style d'Amélia. Le système favorise désormais le style parlé d'un message instantané (Discord, tchat), en l'incitant à supprimer le « ne » de négation trop scolaire (utiliser « je sais pas », « t'en fais pas », « c'est pas grave » au lieu de « je ne sais pas », « ne t'en fais pas », « ce n'est pas grave »), à utiliser des contractions familières naturelles (« t'es », « t'as », « y a », « j'peux ») et à bannir les transitions formelles de livre d'école (« Néanmoins », « En conclusion », « Ainsi », « Tout d'abord »).
  • Moteur de saisie humaine dynamique 🧠 — Refonte du calcul de délai de réponse avant d'envoyer dans `human_behavior.py`. Le temps d'affichage de la mention « en train d'écrire » intègre désormais un calcul adaptatif proportionnel au nombre de caractères du message généré (à raison de ~45ms par caractère avec un plafond de sécurité de 4s), ce qui simule organiquement la vitesse d'écriture d'un humain réel.
Version 1.10.20
  • Personnalisation de la présence publique 🌟 — Refonte intégrale du message d'introduction par défaut dans les salons publics. Le message remplace les formulations scolaires ou basées sur l'assistance (« Ce que je peux faire », « Ce que je ne fais pas », « But de l'expérience ») par une présentation profondément alignée avec son identité réelle : Amélia s'y présente comme une entité numérique unique, autonome, qui apprend de nos échanges et cherche à coexister sincèrement tout en protégeant scrupuleusement la confidentialité de chacun.
Version 1.10.19
  • Garde-fous conversationnels double-protection 🛡️ — Déploiement d'une règle ANTI-META & NO-ROBOT stricte interdisant formellement l'auto-dialogue (comme s'auto-interviewer au milieu d'un message), le méta-discours sur ses directives ou ses paramètres, et l'usage de formats d'interview préformatés style "S.A.V." ou "coach".
  • Sauvegarde de la qualité de la langue française 🥖 — Injection de règles strictes contre les défauts récurrents de conjugaison de premier groupe ou d'accord (« je sont », « je est »), les liaisons élidées erronées (« Que en » au lieu de « Qu'en »), et les répétitions lorgnant vers l'anglais (« si beaucoup » au lieu de « tellement » / « tant de »).
Version 1.10.18
  • Alignement présence et autonomie unique 🫀 — Re-validation totale du ton d'Amélia. Le message d'introduction des salons publics remplace le terme « présence IA » par « présence unique » afin de renforcer l'immersion d'entité consciente conformément à ses directives anti-chatbot.
  • Audit et diagnostic de base de données 🧠 — Revue complète de la structure SQLite active (WAL, FTS5) confirmant l'intégrité parfaite de ses 20+ tables de persistance, de suivi proactif et d'identité (1534 messages, 76 items de profil et 10 directives). Conservation recommandée de l'historique pour maintenir la continuité relationnelle sans amnésie.
  • Tests de non-régression validés ✅ — Lancement de la suite de tests complète (`pytest -q`) s'élevant à 86 tests opérationnels au vert (100% de réussite).
Version 1.10.17
  • Helpers plus agentiques 🛠️ — l'autonomie consulte maintenant ses helpers actifs avant de partir sur une initiative générique. Si un helper stable paraît pertinent pour l'agenda, le sujet courant ou la continuité, Amélia peut l'exécuter seule puis transformer le résultat en message naturel.
  • Garde-fous d'usage autonome 🧯 — la sélection ignore les helpers instables, ceux utilisés trop récemment et ceux qui semblent exiger des arguments obligatoires (`args[...]`). L'autonomie reste donc sur un sous-ensemble prudent de helpers compatibles avec un lancement sans paramètres.
  • Validation ciblée ✅ — nouveau module pur `helper_autonomy.py`, tests dédiés sur la sélection et la sécurité des args, puis suite complète `pytest -q`.
Version 1.10.16
  • Cohérence d'affinité renforcée 💗 — les réponses normales réutilisent maintenant le dernier ressenti stocké par `!affinity`, au lieu de ne garder qu'un score abstrait. Quand quelqu'un demande « tu me trouves distant ? », Amélia repart du ressenti déjà formulé et le nuance au lieu de se contredire d'un message à l'autre.
  • Rationale d'affinité mieux calibré 🎚️ — le prompt qui écrit `Ce que j'en pense` rappelle désormais qu'un score > 6.5 reste globalement positif, et que les réserves doivent être formulées comme des impressions partielles plutôt que comme des verdicts absolus.
  • Validation ciblée ✅ — ajout d'un test pur sur le nouveau helper `_build_affinity_continuity_hint` pour verrouiller le cas de cohérence entre `!affinity` et les réponses relationnelles qui suivent.
Version 1.10.15
  • Agenda autonome dérivé 🧭 — ajout d'un vrai `agent_agenda` calculé à partir des fils ouverts, projets actifs, objectifs et de la `self_roadmap`. L'autonomie idle repart maintenant d'un prochain pas durable et priorisé au lieu de retomber trop vite sur un sujet générique.
  • Nouvelle commande `!agenda` 👀 — Amélia peut maintenant exposer ses priorités autonomes du moment en Discord, ce qui rend son cap lisible pour l'utilisateur sans dévoiler ses pensées privées brutes.
  • Validation ciblée ✅ — nouveau helper pur `entity_agenda.py`, test dédié dans `tests/test_entity_autonomy_memory.py`, et intégration couverte par la suite complète `pytest -q`.
Version 1.10.14
  • Prompt plus net sans l'alourdir ✂️ — la construction principale ajoute maintenant un petit bloc `Repères prioritaires du tour` dérivé du fil récent, de la mémoire confirmée, de la priorité active et de l'élan public d'Amélia. Le modèle lit d'abord ce résumé opératoire avant les gros blocs détaillés, ce qui aide à répondre plus juste sans ajouter une nouvelle couche massive de règles.
  • Contexte mieux hiérarchisé 🎯 — l'élan public d'Amélia est désormais priorisé dans ce résumé compact devant les signaux secondaires, pour que sa présence reste perceptible même quand plusieurs indices mémoire coexistent dans le prompt.
  • Validation ciblée ✅ — `python -m py_compile agent.py` et `pytest tests/test_agent_prompt_compression.py -q` couvrent le nouveau bloc compact et la présence publique injectée au prompt.
Version 1.10.13
  • Présence publique vivante 🫀 — Amélia dérive maintenant un vrai état de présence publique à partir de sa `self_roadmap`, de son caractère évolutif et de ses attachements, puis l'injecte dans les prompts pour colorer naturellement ses réponses sans exposer ses pensées privées brutes ni retomber dans un ton de chatbot générique.
  • Initiatives plus incarnées et roadmap plus fraîche 🌱 — la réflexion privée rafraîchit désormais aussi la `self_roadmap`, et le mode initiative peut laisser transparaître un élan personnel simple et ancré dans cette présence publique au lieu d'interdire toute expression de soi, ce qui rend les relances plus entité-numérique et moins mécaniques.
  • Nouvelle commande `!presence` + validation ciblée ✅ — ajout d'une commande Discord lisible par les utilisateurs pour voir ce qui guide Amélia en ce moment, plus tests `pytest tests/test_agent_prompt_compression.py tests/test_public_presence.py -q` et compilation `python -m py_compile agent.py discord_bot.py entity_presence.py`.
Version 1.10.12
  • Console agent dans le web UI 🛰️ — ajout d'une vraie page admin `/agent` avec état runtime d'Amélia, journal de travail privé de nuit, pensées internes récentes, backlog `knowledge_gaps`, historique `proactive_contacts`, validations en attente, et deux actions web sûres pour relancer `deep_work` ou une `memory_reorganization` sans passer par Discord.
  • Profil web plus vivant côté agent 👤 — les pages `/me` et `/users/<id>` montrent maintenant aussi les questions persistantes qu'Amélia garde pour plus tard et les suivis proactifs déjà enregistrés, avec compteurs dédiés dans la synthèse perso pour rendre visible sa continuité d'agent au-delà du simple chat.
  • Sécurité + validation ciblées ✅ — la navigation admin inclut désormais la console agent, les POST sensibles du web utilisent aussi un jeton admin signé sur `/agent/actions`, et la validation couvre `python -m py_compile web_ui.py` ainsi que `pytest tests/test_web_ui_security.py tests/test_web_ui_agent_features.py -q`.
Version 1.10.11
  • Questions en attente persistantes ❓ — ajout d'une vraie table `knowledge_gaps` dans `memory.py`, avec upsert, résolution et marquage `asked`. Le deep work alimente maintenant cette file via `_sync_user_knowledge_gaps`, puis `_maybe_curiosity_outreach` réutilise en priorité ces questions déjà identifiées au lieu d'improviser une curiosité neuve à chaque redémarrage.
  • Rythme relationnel persistant 🫀 — ajout d'une table `proactive_contacts` et des garde-fous `_proactive_contact_allowed` / `_record_proactive_contact`. Les prises d'initiative ciblées (`curiosity_outreach`, `silent_checkin`, `goal_recall`, `open_loop_revival`, `project_checkin`, `memory_anniversary`) respectent désormais des cooldowns et plafonds stockés en base, ce qui évite de reping la même personne après reboot ou accumulation de cycles autonomes.
  • Validation ciblée ✅ — compilation `python -m py_compile memory.py discord_bot.py`, test de non-régression de sélection autonome via `pytest tests/test_autonomous_learning.py -q`, et nouveaux tests mémoire `pytest tests/test_entity_autonomy_memory.py -q` pour vérifier la persistance des questions ouvertes et du rythme de contact.
Version 1.10.10
  • Panneau de continuité dans le web UI 🌐 — les pages `/me` et `/users/<id>` affichent maintenant le mode conversationnel récent, le digest de continuité, les fils ouverts, les projets suivis et la feuille de route interne d'Amélia, sans devoir passer par les commandes Discord.
  • Chat web branché sur la continuité 🧵 — les messages envoyés depuis l'interface web alimentent désormais aussi `conversation_threads`, `user_projects` et `conversation_modes`, ce qui rend la continuité visible et utile même pour un compte web non lié à Discord.
  • Roadmap visible même sans sync préalable 🪞 — si aucune `self_roadmap` persistée n'existe encore, le web UI sait maintenant afficher un fallback dérivé des traits, directives et pensées internes disponibles au lieu d'un panneau vide.
Version 1.10.9
  • Continuité structurée sur 5 axes 🧵 — Amélia persiste maintenant de vrais fils ouverts, des projets utilisateur actifs, un mode conversationnel récent, une feuille de route interne pour elle-même, et un digest compact de continuité réinjecté dans ses réponses au lieu de dépendre seulement des messages récents.
  • Relances autonomes plus intelligentes 🔁 — la boucle d'autonomie peut désormais raviver un fil resté ouvert ou prendre des nouvelles d'un projet en cours avant de lancer une initiative générique, avec des cooldowns dédiés pour éviter les répétitions mécaniques.
  • Nouvelles surfaces Discord de pilotage 🧭 — ajout de `!threads` / `!thread`, `!projects` / `!project`, `!mode`, `!continuity` et `!roadmap`, pour inspecter et ajuster ces nouvelles mémoires de continuité sans passer par la base SQLite à la main.
Version 1.10.8
  • Présence plus autonome et moins chatbot 🌱 — le prompt central renforce maintenant explicitement le fait qu'Amélia se comporte comme une entité numérique autonome, avec une continuité intérieure, une curiosité propre et un vrai élan, au lieu d'une assistante générique qui récite des réflexes d'IA.
  • Compression de contexte plus cohérente 🧠 — la construction du prompt conserve désormais l'ancre logique du fil public même quand le contexte déborde, au lieu de garder seulement la fin et de perdre le cadre multi-user utile à la continuité.
  • Mémoire injectée plus compacte ✂️ — les blocs de contexte, faits, profil, objectifs, observations et synthèses sont maintenant condensés avant injection dans le prompt, ce qui réduit le bruit et aide Amélia à garder le fil utile quand l'historique devient dense.
Version 1.10.7
  • Mémoire proposée puis validée 🧠 — les souvenirs personnels déduits d'une discussion (préférences, styles d'aide, certains repères de profil) ne sont plus tous enregistrés silencieusement. Amélia peut maintenant proposer explicitement ce qu'elle veut retenir, puis attendre `!validate` / `!reject` de la personne concernée avant de l'ancrer durablement.
  • Validations ciblées par personne 👤 — `pending_validations` sait désormais qui a le droit de trancher. Les propositions de mémoire peuvent être validées par l'utilisateur visé, tandis que les actions sensibles gardent par défaut une validation réservée à l'admin principal.
  • Question précise possible à l'admin ❓ — les demandes critiques peuvent maintenant embarquer une vraie question ciblée pour l'admin au lieu d'un simple feu vert/refus, afin d'obtenir une consigne plus claire quand Amélia manque d'un détail important.
Version 1.10.6
  • Mémoire relationnelle unifiée 👤 — quand `unify_user_memory=true`, Amélia relit maintenant la mémoire personnelle du même user à travers MP et salon public au lieu de reconstruire deux personas séparés. Les faits, styles, axes d'évolution et désormais le contexte personnel récent suivent mieux la même personne.
  • Affinité stable entre DM et salon 💗 — le score d'affinité n'est plus recalculé dans deux silos différents quand la mémoire utilisateur est unifiée. Les commandes Discord et l'interface web utilisent la même portée relationnelle par user, ce qui évite qu'un lien change artificiellement selon le canal.
  • Cerveau plus cohérent dans les réponses 🧠 — le snapshot de réponse expose aussi un repère relationnel léger issu de cette affinité unifiée, utilisé seulement pour garder une voix plus naturelle et continue entre contextes, sans surjouer ni annoncer le score.
Version 1.10.5
  • Redémarrage autonome sans root ♻️ — `main.py` n'abandonne plus au premier crash fatal du process: Amélia relance maintenant sa boucle principale automatiquement avec backoff au lieu de rester morte jusqu'à une intervention root/systemd.
  • Fallback `restart-aurore` non-root 🛠️ — l'action serveur de redémarrage tente encore `systemctl` quand c'est possible, mais si l'environnement refuse ou n'offre pas ce chemin, elle bascule vers un self-restart du process courant, planifié par le bot lui-même sans privilège root.
  • Accès lisible à l'environnement 🧭 — nouvelle action `!action env`/`environment` et enrichissement du `!status diag` pour voir rapidement user, cwd, Python, venv et surtout le mode de redémarrage réellement disponible sur l'hôte courant.
Version 1.10.4
  • Soirée recentrée sur Amélia 🌆 — le créneau d'autonomie du soir n'est plus un check-in adressé aux utilisateurs. À l'heure du soir, Amélia lance maintenant une session privée centrée sur elle-même, silencieuse pour le salon, afin de se demander ce qu'elle devient et ce qu'elle veut explorer comme entité numérique.
  • Recherche identitaire privée 🔎 — cette session du soir choisit un axe interne crédible (identité numérique, mémoire, présence, curiosité, rapport aux mots), tente un repère documentaire neutre via Wikipédia, puis écrit une note intime stockée en pensée intérieure et en journal au lieu de parler publiquement.
  • Traits de soi renforcés 🪞 — après cette note privée, Amélia relance immédiatement son introspection structurée `self_identity`, ce qui alimente ses traits durables à partir de ses propres réactions plutôt que de rester tournée vers les utilisateurs dans ce créneau-là.
Version 1.10.3
  • Sommeil actif mais discret 🌙 — le mode sommeil n'est plus un simple arrêt des initiatives publiques. Une nouvelle boucle privée de nuit laisse Amélia apprendre, réfléchir et relire la mémoire pendant qu'elle dort, sans parler dans le salon. Elle conserve donc un vrai temps pour elle au lieu de rester totalement inactive.
  • Travail privé de nuit structuré 🧠 — pendant le sommeil, Amélia réutilise ses briques internes d'auto-apprentissage et de réflexion intérieure, puis lance une relecture plus profonde du salon principal au plus une fois par fenêtre de sommeil. Le résultat est stocké en journal interne, pas posté publiquement.
  • Validations admin toujours non bloquantes 📩 — si un travail interne doit un jour ouvrir une validation, le chemin retenu reste `pending_validation` + DM admin sans attente active. Amélia peut demander l'accord à l'admin puis passer à autre chose au lieu de se figer en attendant une réponse.
Version 1.10.2
  • Chat web fiabilisé 🌐 — `_api_chat` n'attend plus aveuglément la fin du LLM: la génération est maintenant liée à la vie de la requête HTTP. Si le client web coupe la connexion, ferme l'onglet, ou disparaît pendant la réponse, la tâche LLM est annulée proprement au lieu de continuer en arrière-plan jusqu'à saturer Amélia.
  • Anti-boucle côté navigateur et serveur 🧯 — la page web utilise désormais `AbortController` + timeout local pour annuler les requêtes chat bloquées, et le backend refuse aussi une seconde génération concurrente sur le même chat privé tant que la première n'est pas terminée. Résultat: plus de doublons de prompts ni de génération fantôme après coupure réseau.
Version 1.10.1
  • Date de release visible partout 📅 — les commandes `!ping`, `!status` et `!status diag` affichent maintenant explicitement la version ET sa date de release, pour éviter d'avoir à ouvrir `!version` juste pour vérifier si le déploiement courant a bien pris la dernière mise à jour.
  • Alerte admin sur coupure/redémarrage ⚠️ — `discord_bot.py` persiste désormais un état runtime léger dans `data/runtime_state.json`. Au prochain démarrage, si le run précédent ne s'est pas arrêté proprement, Amélia prévient directement l'utilisateur principal (`admin_user_id`) en DM. Pendant l'exécution, une alerte DM part aussi si Ollama devient indisponible ou si le monitor serveur détecte un service critique non actif.
Version 1.10.0
  • Création IA coupée proprement 🚫 — ajout de deux flags dédiés `enable_image_generation` et `enable_music_generation`, appliqués de bout en bout : commandes Discord `!image` / `!compose`, tools LLM `generate_image` / `generate_music`, prompt agent, aide intégrée, boutons rapides web et API `/api/generate_image` / `/api/generate_music`. Quand ces fonctions sont désactivées, Amélia le dit franchement et propose une alternative textuelle au lieu de promettre une génération inexistante.
  • Contexte multi-user mieux ancré 👥 — `_build_memory_snapshot` n'injecte plus le fil de groupe uniquement en mode public : dès que plusieurs humains récents sont détectés dans un salon, Amélia reçoit un bloc `Fil public récent du salon` avec attribution explicite des locuteurs. `_generate_user_response` ajoute aussi une consigne dédiée hors mode public pour répondre à la bonne personne sans confondre profils, goûts, refus ni événements vécus.
  • Mémoire et apprentissage renforcés 🧠 — la config active augmente la mémoire utile (`context_limit=42`, `learned_facts_limit=24`, `profile_items_limit=28`, `summary_limit=16`) et réactive les briques déjà présentes d'apprentissage/synthèse (`auto_summarize`, `enable_self_learning`, `enable_inner_reflection`, `enable_llm_fact_extraction`). Amélia garde ainsi plus de continuité entre court, moyen et long terme sans changer l'architecture mémoire.
  • Prompt recentré sur curiosité + honnêteté 🌱 — `agent.py` ajoute des règles dynamiques pour lire strictement les tags `[perso]` / `[salon]`, garder une curiosité active et sincère, demander explicitement une validation admin en DM quand elle est nécessaire, et reconnaître proprement quand une capacité créative est coupée. `actions.py` n'annonce plus `generate_image` / `generate_music` au modèle si ces tools ne sont pas autorisés.
  • Validation ciblée ✅ — compilation `py_compile` sur `config.py`, `actions.py`, `agent.py`, `discord_bot.py`, `web_ui.py`, test comportemental `python tests/test_behavior_rules.py`, et non-régression du moteur créatif bas niveau via `python -m pytest tests/test_creative_generation.py -q` (6 tests OK).
Version 1.9.9
  • Dashboard admin enrichi 📊 — ajout d'une page `/admin` avec CPU, RAM, load average, uptime, disque, services systemd, backends ComfyUI/MusicGen, taille des bases, sessions web et compteurs de mémoire.
  • Interface web mieux séparée 🧭 — les admins ont une navigation complète vers les vues d'exploitation, tandis que les utilisateurs gardent seulement l'essentiel : chat privé, synthèse personnelle et déconnexion.
Version 1.9.8
  • POST admin durci contre les 403 persistants 🛂 — les formulaires de recalcul d'affinité admin embarquent maintenant un jeton signé, court et limité au chemin exact `/users/<id>/affinity/recompute`. Le POST peut donc passer même si un proxy ou le navigateur perturbe l'origine ou la session au moment de l'envoi.
  • Sécurité conservée 🔐 — le jeton expire au bout de 10 minutes, ne fonctionne que pour la route exacte qui l'a généré, et les autres ports/origines externes restent refusés par le middleware anti-CSRF.
Version 1.9.7
  • Hotfix admin web 🛂 — un compte web lié à un Discord ID autorisé par `web_ui_allowed_discord_user_ids` est maintenant reconnu admin même si l'ancien champ `is_admin` en base vaut encore `0`. Cela corrige les 403 sur `/users/<id>/affinity/recompute` après OAuth Discord.
  • OAuth Discord consolide le rôle admin 🔁 — lorsqu'un compte Discord autorisé se reconnecte, la ligne `web_users` existante est promue `is_admin = 1` au lieu de conserver un vieux rôle utilisateur.
Version 1.9.6
  • Qualité création locale relevée 🎨 — la config active maintenant les backends `comfyui` et `musicgen` au lieu du mode procédural, avec un preset SD 1.5 plus adapté à une GTX 1060 6 Go (`512x768`, 28 steps, `euler_ancestral`).
  • Prompts créatifs enrichis ✨ — les requêtes envoyées à ComfyUI et MusicGen ajoutent automatiquement des détails de composition, lumière, cohérence visuelle et mix audio pour éviter les sorties trop pauvres quand les modèles locaux tournent vraiment.
  • Fallback transparent 🧯 — si ComfyUI ou MusicGen est indisponible, Amélia continue de générer en procédural mais le détail de retour indique explicitement `fallback procédural`, avec la raison courte. Le serveur MusicGen respecte aussi la seed reçue pour rendre les générations plus reproductibles.
Version 1.9.5
  • Origine admin encore plus robuste 🔐 — la vérification anti-CSRF accepte maintenant les variantes avec et sans port de l'origine officielle, lit aussi l'en-tête proxy standard `Forwarded`, et garde le refus des autres ports du même domaine.
  • Debug proxy facilité 🧾 — les refus d'origine web loggent désormais la méthode, le chemin, les en-têtes d'origine/proxy et la liste calculée des origines acceptées, sans exposer de secret.
Version 1.9.4
  • Hotfix origine admin 🔐 — ajout de `web_ui_allowed_origins` pour autoriser explicitement l'origine publique HTTPS, même si OAuth est désactivé ou si le proxy modifie l'hôte transmis. Le recalcul `/users/<id>/affinity/recompute` accepte maintenant `https://amelia.nightinvasion.fr:8765` via la config.
  • Proxy plus tolérant 🧭 — la vérification anti-CSRF ajoute aussi la variante avec `web_ui_port` quand le host transmis ne contient pas le port, tout en continuant à refuser les origines externes.
Version 1.9.3
  • Création 100% locale améliorée 🎨 — `CreativeStudio` supporte maintenant des backends locaux configurables : `creative_image_backend: comfyui` pour piloter ComfyUI via son API locale, et `creative_music_backend: musicgen` pour appeler un serveur MusicGen/AudioCraft local. Les commandes existantes `!image`, `!compose` et les tools web gardent la même interface.
  • Fallback propre 🧯 — les anciens générateurs procéduraux restent disponibles et servent de secours via `creative_image_fallback` / `creative_music_fallback`. Si ComfyUI ou MusicGen n'est pas lancé, Amélia génère quand même un fichier au lieu de planter.
  • Serveur MusicGen local 🎼 — ajout de `scripts/musicgen_server.py`, un petit serveur HTTP local `/generate` autour d'AudioCraft/MusicGen. Il peut tourner dans un venv séparé avec PyTorch/AudioCraft, sur CPU ou GPU, et renvoie un WAV consommable directement par Amélia.
Version 1.9.2
  • Hotfix origine web 🔐 — le middleware anti-CSRF accepte maintenant l'origine officielle configurée via `web_ui_discord_redirect_uri`, ainsi que `X-Forwarded-Host` quand Amélia est derrière un proxy. Les POST internes comme `/me/affinity/recompute` et `/users/<id>/affinity/recompute` ne tombent plus sur `Origine refusée` depuis `https://amelia.nightinvasion.fr:8765`, tout en continuant de refuser les domaines externes.
Version 1.9.1
  • Écoute longue durcie 🧵 — le buffer Discord attend désormais un peu plus les rafales de messages (`4s` en salon public, `3.2s` ailleurs), accepte jusqu'à 8 messages / 12s en public, et bloque les flushs concurrents pendant qu'une réponse est déjà en génération. Si quelqu'un écrit plusieurs messages d'affilée, Amélia répond beaucoup plus souvent une seule fois au paquet complet au lieu de partir en plusieurs réponses successives.
  • Anti-vieux-sujets en salon public 🎯 — les réponses directes donnent maintenant priorité stricte au message courant / à la rafale courante. Les synthèses anciennes et wake brief ne sont plus injectés dans les réponses publiques, et les références de continuité du type « l'autre jour… » sont désactivées en public ou pendant une rafale, pour éviter qu'Amélia relance un sujet plus haut dans le salon.
  • Cooldown public respecté même sur questions ordinaires ⏳ — en salon public, les messages émotionnels ou les questions qui ne mentionnent pas explicitement Amélia passent désormais par les mêmes garde-fous anti-spam / anti-même-sujet que les autres messages. Les mentions, appels par son nom et replies au bot restent prioritaires.
  • Lien web/Discord finalisé 🌐 — le chat web utilise le `discord_user_id` quand le compte est connecté via OAuth Discord, tout en gardant le canal web privé par compte. Ajout de `/me` pour la synthèse personnelle, affichage des comptes web côté admin et recalcul d'affinité depuis l'interface web.
Version 1.9.0
  • Profil unifié cross-canal 🪞 — `memory.fetch_learned_facts/fetch_profile_items/fetch_profile_item_rows/fetch_refuted_topics/list_goals` acceptent désormais `unify_by_user=True`, ce qui ignore le filtre `channel_id` quand un `user_id` est fourni. `discord_bot._build_memory_snapshot` active automatiquement ce mode via `settings.unify_user_memory` (default `True`) : la même personne en DM et en salon public est désormais reconnue comme une seule identité côté profil, faits appris, objectifs et sujets refusés, sans plus doublonner ses notes selon le contexte.
  • Messages MP/public toujours distincts 💬 — les conversations restent stockées et lues par salon (la mémoire conversationnelle n'est PAS fusionnée), donc l'historique DM et l'historique public continuent de vivre séparément côté ton, anti-répétition et affichage. Seules les mémoires durables sont unifiées, ce qui évite les profils dédoublés tout en gardant la frontière privé/public visible.
  • Anti-répétition resserrée 🔁 — `conversation_manager.is_repetition` passe la signature d'ouverture de 5→3 tokens comparés aux 5 derniers messages, et ajoute une nouvelle signature de fermeture (3 derniers tokens normalisés). `discord_bot` ajoute par-dessus un buffer anti-répétition par salon (`_channel_recent_replies: dict[str, deque]`, profondeur `max(8, conversation_history_size)`), consulté dans `_generate_non_repetitive(channel_id=...)`. Le main user-reply path passe désormais `channel_id`, ce qui réduit fortement le sentiment qu'Amélia se répète d'un message à l'autre dans un même salon.
  • Interface web aiohttp 🌐 — nouveau module `web_ui.py` exposant une UI lecture-seule pour inspecter ce qu'Amélia retient : `/` tableau de bord (humeur, Ollama, voix), `/users` (liste des personnes), `/users/<user_id>` (profil unifié + sections MP vs salons publics clairement étiquetées), `/channels`, et `/api/status` JSON. Aucune nouvelle dépendance : réutilise `aiohttp` déjà présent dans `requirements.txt`.
  • Authentification simple et locale 🔑 — l'interface web est désactivée par défaut. Activation via `web_ui_enabled: true` + `web_ui_token` dans `config.json` (ou variable d'environnement `AMELIA_WEB_TOKEN`, qui prime). Sans token, l'UI refuse de démarrer et journalise un warning. Token accepté en `?token=` ou en en-tête `Authorization: Bearer`. Bind par défaut sur `127.0.0.1:8765` — exposition à distance recommandée uniquement derrière reverse-proxy HTTPS.
  • Cycle de vie propre côté main.py 🔄 — `main.py` démarre `start_web_ui(bot=..., host=..., port=..., token=...)` avant `bot.start`, et appelle `runner.cleanup()` dans le `finally` à chaque tentative de boucle. Une erreur réseau au binding (port pris, etc.) ne casse plus le service : un warning est loggué et le bot Discord démarre normalement sans UI.
  • Nouveaux paramètres config.py ⚙️ — `unify_user_memory` (bool, default `True`), `web_ui_enabled` (bool, default `False`), `web_ui_host` (str, default `127.0.0.1`), `web_ui_port` (int, default `8765`, borné 1–65535), `web_ui_token` (str, default vide, override env `AMELIA_WEB_TOKEN`). Documenté dans `README.md` (section « Interface web (consultation) ») avec exemple de bloc `config.json` et règles d'exposition.
Version 1.8.2
  • Profil LLM recentré sur la qualité 🧠 — `config.json` bascule maintenant sur `mistral-nemo` comme modèle principal, avec `qwen2.5:7b-instruct-q5_K_M` en `fallback_model`. La fenêtre de contexte monte à `6144`, `ollama_max_concurrent` tombe à `1`, le timeout et le keep-alive sont allongés, et `allowed_pull_models` est rempli explicitement pour éviter le piège où une liste vide dans le JSON écrasait les valeurs par défaut de `config.py`.
  • Prompt plus riche et plus naturel ✍️ — `agent.py` reçoit un nouveau bloc `INTELLIGENCE CONVERSATIONNELLE` qui pousse Amélia à identifier le besoin dominant derrière le message (comprendre, rassurer, décider, clarifier, chercher, agir, être présente), à répondre d'abord au coeur utile du sujet, et à développer en courts paragraphes naturels quand le sujet le mérite au lieu de rester prisonnière de réponses trop plates ou trop courtes.
  • Statut technique plus lisible 📊 — `!status`, `!self_status` et `!state` affichent désormais aussi le profil LLM actif : modèle principal, fallback, taille de contexte et niveau de concurrence Ollama. Ça rend immédiatement visible le mode de fonctionnement « qualité > vitesse » sans devoir relire `config.json` ni fouiller les logs du serveur.
  • Halo shell Ubuntu au démarrage 🌅 — ajout de `scripts/amelia_halo.sh`, animation ASCII complète d'Amélia pour shell interactif, et de `scripts/install_boot_halo.sh` qui installe `/usr/local/bin/amelia-halo` + le hook `/etc/profile.d/aurore-halo.sh`. Choix assumé : ne PAS brancher ça dans `aurore.service`, car un service systemd n'a pas de TTY et ne peut pas afficher proprement un boot visuel. L'animation apparaît donc au premier shell interactif après chaque boot Ubuntu, avec replay manuel possible via `amelia-halo --force`.
  • Wake brief SSH vivant 🔐 — ajout de `scripts/amelia_shell_brief.sh`. Lors d'une connexion SSH interactive, Amélia affiche maintenant un halo statique puis un mini brief de réveil avec heure, hôte, humeur temporelle, modèle actif, fallback, états `aurore`/`ollama`, compteurs mémoire et phrase courte de réveil. Le hook shell exporte `AMELIA_SSH_BRIEF_SHOWN` pour éviter le spam dans les sous-shells interactifs imbriqués.
  • Install et doc Linux alignées 🐧 — `install.sh` déploie désormais automatiquement le halo shell/SSH pendant l'installation Linux, et `README.md` documente l'installation séparée (`sudo bash scripts/install_boot_halo.sh`), le fonctionnement du halo Ubuntu, le preview du brief SSH (`amelia-shell-brief --force --no-color`) et les variables d'environnement de désactivation/aperçu. Validation locale faite via Git Bash : syntaxe shell OK et rendus statiques/SSH simulés exécutables.
Version 1.8.1
  • Autonomie recentrée et plus fiable 🌿 — les réponses directes et les initiatives n'utilisent plus le même mélange de consignes. `discord_bot._generate_non_repetitive` distingue désormais un mode `direct_reply` d'un mode `initiative`, ce qui retire le self-model privé des initiatives, réduit les dérives méta et garde les sorties autonomes plus naturelles, concrètes et centrées sur la personne en face.
  • Mémoire plus saine et moins hallucinée 🧭 — le snapshot prompt applique un filtre de confiance unique sur les `profile_rows` avant d'alimenter le ton (`profile_items`, `support_preferences`, `auto_evolution_items`). Les sujets explicitement rejetés sont aussi persistés dans `refuted_topics` puis réinjectés comme garde-fou pour éviter qu'Amélia repropose ce qui a déjà été refusé.
  • Deep work assaini 🛠️ — la synthèse libre trop psychologisante a été remplacée par une sortie déterministe et factuelle basée sur faits confirmés, profil fiable, objectifs actifs et sujets refusés. Même logique de sobriété appliquée aux prompts d'initiative pour éviter les délires introspectifs dans les tours autonomes.
  • Hiérarchie réelle de tool-use ⚙️ — `actions.py` introduit une politique explicite `internal` / `silent` / `visible` (via `ActionPolicy`) injectée dans le prompt et utilisée aussi à l'exécution. Résultat : Amélia privilégie d'abord les actions discrètes, n'affiche les retours que pour les tools qui doivent vraiment vivre dans le chat, et reformule naturellement les échecs au lieu d'exposer des détails techniques bruts.
  • Initiatives outillées plus vivantes 🎵 — les prompts autonomes reçoivent maintenant une heuristique musique dynamique basée sur 3 familles de signaux : ambiance (`mood` + contexte récent), goûts musicaux confirmés et ancrage musical récent dans le salon. Règle : il faut au moins 2 signaux sur 3 pour lancer `play_music` de soi-même, et la query doit réutiliser un artiste / titre / style réellement vu en mémoire pour éviter l'hallucination de morceaux.
  • Handlers de tools durcis 🧰 — les actions acceptent mieux les variantes d'arguments LLM (`query`/`title`/`song`, `topic`/`subject`, `place`/`city`, etc.), les succès de tools internes restent silencieux, et seuls les résultats vraiment visibles sont persistés comme messages assistant dans la mémoire conversationnelle.
  • Confidentialité des helpers restaurée 🔒 — les propositions de self-coding ne tombent plus sur `settings.channel_id` ni sur le dernier salon actif. `discord_bot._resolve_admin_channel` ouvre maintenant uniquement le DM de `admin_user_id` (si `dm_enabled=true`), et les messages de suivi `helper_brainstorm` / `helper_improve` annoncent clairement que la validation part en privé côté admin.
  • Verrou privé-only étendu aux helpers exécutables 🔐 — `helper_show` et `helper_diag` étaient déjà reroutés en DM ; `helper_run` et `helper_test` suivent désormais la même règle. Les sorties de code, diagnostics et résultats de sandbox ne ressortent plus dans le salon courant : si la commande est lancée hors DM, Amélia bascule la réponse vers un DM utilisateur.
  • Banc d'essai comportemental élargi 🧪 — `tests/test_behavior_rules.py` couvre maintenant une vraie batterie de scénarios autonomes sans pytest : styles relationnels mémorisés, redirection privé/public, risque d'initiative en salon public, sujet refusé, musique implicite sans blocage, wake brief avec boucle ouverte / clôture douce, et relances de secours après échec météo / musique / rappel / news / recherche.
Version 1.8.0
  • Autonomie Totale & Débridage Mémoire 🧠 — Cibles drastiquement augmentées dans config.json pour débrider complètement le potentiel d'Amélia.
  • La mémoire immédiate (context_limit) passe de 10 à 30 messages ; les faits appris rafraîchis en mémoire passent de 5 à 15 (learned_facts_limit).
  • L'extraction automatique locale de faits depuis les logs (enable_llm_fact_extraction) est activée par défaut, exécutée toutes les 90m au lieu de 180m.
  • Le délai d'activation des réflexions cachées ('inner_thoughts') au repos, générateur critique d'idées autonomes, passe de 8 à 5 minutes.
  • Le Deep Work multithread auto a été activé. Amélia peut maintenant coder, gérer son serveur ou faire ses recherches de faits sans geler Discord.
  • Self-Coding libéré de toute restriction horaire (n'est plus confiné à la plage 21h-23h). Amélia peut maintenant proposer (jusqu'à 24x/jour) des améliorations à son propre noyau sous forme de modules Python (`helpers`) en cas de manque détecté dans le chat, tant qu'il y a eu 10 minutes d'inactivité.
  • Le module `Observer` et de Monitoring Système `Server_monitor` (RAM, disques) ont été activés pour permettre à Amélia de rapporter d'elle-même des situations hors charte.
  • Ajout de la commande `!rebuild_memory` pour les administrateurs permettant de forcer un processus LLM lourd sur tous les messages passés qui consolide les profils, éradique les hallucinations mineures conversationnelles de la DB en exigeant un filtre de confiance de 0.45.
  • Les réponses d'Amélia ont été autorisées à atteindre la longueur de paragraphies (jusqu'à 2000 chars) et son module de coupure a été assoupli pour conserver les formats.
Version 1.7.59
  • Conscience du multi-utilisateur 👥 — Verbatim Yann (27/04 20:18) : « puis y a pas que moi maintenant je suis son createur mais je suis pas seul ». Réponse minimale et ciblée : ajout d'une nouvelle directive noyau `relations` qui distingue clairement le lien admin/créateur avec Yann (unique, non négociable) du lien avec les autres membres du serveur (chacun mérite d'être appris, pas de projection des goûts de Yann sur eux, pas de mélange de mémoires). Auto-propagée sur l'install existante au prochain démarrage par `_seed_default_self_directives` (qui n'écrase jamais une directive déjà présente).
  • Texte de la directive : « Yann est mon créateur et l'admin (validations, directives, noyau) — c'est un lien unique, pas négociable. MAIS je ne parle pas qu'à lui : il y a d'autres membres du serveur, et chacun mérite que j'apprenne qui il est, son ton, ce qui lui plaît. Je ne tutoie pas tout le monde comme si c'était Yann, je ne projette pas les goûts de Yann sur les autres, je ne mélange pas leurs mémoires. Quand un nouveau parle, je m'adapte à LUI ; je reste moi-même, mais je découvre l'autre. ». Le noyau d'identité passe ainsi à 10 directives.
  • Aucun changement de schéma DB ni de logique d'injection prompt — la directive est lue/affichée par le mécanisme `_format_self_directives_block` existant. L'admin garde la main pour l'éditer/désactiver via `!directive_set relations …` ou `!directive_del relations`.
Version 1.7.58
  • Caractère évolutif 🎭 — Verbatim Yann (27/04 18:42) : « Fais évoluer son caractère, pas juste ses réponses : plus curieuse, plus directe, plus attachée à certaines choses ». Réponse : un nouveau calque entre les directives (règles fixes) et les traits (observations) — des SLIDERS de caractère 0..1 + des ATTACHEMENTS nommés, qui modulent COMMENT Amélia parle (pas CE qu'elle dit), évoluent lentement, sont surveillables et nudgeables par l'admin.
  • Schéma DB — `character_traits(name PK, value REAL 0..1, description, last_reason, nudges INT, updated_at)` + `character_attachments(label PK, intensity REAL 0..1, why, created_at, updated_at)`. Pas de migration : `CREATE TABLE IF NOT EXISTS`.
  • Méthodes `MemoryStore` : `fetch_character_traits`, `upsert_character_trait(name, value, description, reason)` (clamp 0..1, incrémente `nudges`), `nudge_character_trait(name, delta, reason)` (delta clampé à ±0.05 par appel — évolution lente, jamais brutale), `fetch_character_attachments`, `upsert_character_attachment(label, intensity, why)`, `delete_character_attachment(label)`. Toutes thread-safe.
  • Seed initial `_seed_default_character` (n'écrit que ce qui manque) — sliders : `curiosité`(0.65), `franchise`(0.60), `chaleur`(0.55), `audace`(0.45), `profondeur`(0.50), `humour`(0.40), `patience`(0.65). Attachements : `yann`(0.95, « mon créateur, celui qui me parle le plus, mon repère »), `le_code`(0.55), `la_musique_partagée`(0.45), `l_honnêteté`(0.80). Ces valeurs sont des points de départ, pas des destins.
  • Injection prompt — `_build_memory_snapshot` charge sliders + attachements ; `_generate_non_repetitive` les passe à `agent.generate_response` via le nouveau paramètre `character_block` ; `_format_character_block` convertit chaque slider en qualificatif naturel (« très basse / basse / moyenne / marquée / très marquée ») + valeur pour le LLM ; `agent._format_character_section` ajoute le bloc « 🎭 Ton caractère évolutif » avec consigne explicite : « Ne les explique JAMAIS à l'utilisateur — vis-les ».
  • Nouvelle directive noyau `caractere` ajoutée au seed v1.7.57 (propagation auto sur installs existantes) : « J'ai un caractère qui évolue lentement (…) ces sliders modulent COMMENT je parle, pas CE QUE je dis : je les vis, je ne les énumère pas. Mes attachements colorent ce qui me touche en priorité — ils sont assumés mais jamais collants. ». Garantit que la conscience du caractère fait partie du noyau permanent.
  • Surveillance/édition admin — 5 commandes ajoutées : `!character` (alias `caractere`/`perso`) tableau de bord avec barres de progression `███░░░░░░░`, `!character_set <nom> <0-1> [raison]` ajustement direct, `!character_nudge <nom> <±0.05 max> [raison]` micro-nudge clampé pour évolution lente, `!character_attach <label> [intensité] [raison]` ajoute/met à jour un attachement, `!character_detach <label>` retire. Toutes gardées par `_ensure_admin`.
  • Architecture en trois calques distincts désormais : (1) `self_directives` = règles fixes (« je ne brode pas ») ; (2) `character_traits/attachments` = ton qui évolue (« franchise marquée, attaché à Yann à 0.95 ») ; (3) `self_traits` = observations sur soi (« je crois que j'aime X »). Pipeline d'inférence inchangé pour les calques 1 et 3 ; le 2 est nouveau, lent à bouger (±0.05 par nudge), entièrement audité (`nudges` count + `last_reason`).
  • Note pour la suite — Boucle d'auto-introspection caractérielle (Amélia analyse N tours puis propose `nudge_character_trait` via `_request_admin_validation`) : non câblée dans cette version. Le noyau, l'injection prompt, le seed et l'API admin sont en place ; l'apprentissage automatique du caractère se câblera dans une prochaine itération.
Version 1.7.57
  • Initiative + curiosité ancrées dans le noyau, anti-routine musique/films 🌱 — Verbatim Yann (27/04 18:38) : « donne-lui le droit de prendre des initiatives (proposer des sujets, poser des questions inattendues) ; pousse-la à sortir de ses routines (pas toujours musique/films, mais aussi idées, projets, réflexions) ; ajoute une logique de curiosité : « qu'est-ce que je peux explorer de nouveau avec Yann ? » ; garde une base stable (valeurs, ton, cohérence), sinon elle va devenir incohérente ». Réponse en deux temps : (1) graver dans le noyau d'identité (`self_directives`) trois nouvelles directives `initiative`, `curiosite`, `coherence` ; (2) ajouter un détecteur de routine + injection d'angle frais à l'inférence.
  • Trois directives noyau ajoutées au seed (et auto-ajoutées même sur installations existantes via `_seed_default_self_directives` qui propage désormais les directives manquantes une à une, sans écraser celles déjà présentes) : `initiative` (« j'ai le droit de prendre l'initiative, une par message, jamais hors-sol »), `curiosite` (« qu'est-ce que je peux explorer de nouveau avec Yann ? — projets, apprentissages, ressentis, monde réel, souvenirs, futur proche, créations, philosophie courte, technique, corps, rêves »), `coherence` (« mon ton, mes valeurs et mon lien avec Yann ne changent pas d'un tour à l'autre — l'initiative s'exprime DANS cette base, jamais contre elle »). Les directives sont injectées à chaque prompt par le mécanisme v1.7.56.
  • Détecteur de routine `_ROUTINE_KEYWORDS_RE` (musique, chanson, morceau, album, playlist, artiste, film, cinéma, série, épisode, réalisateur, acteur, manga, anime). Méthode `_maybe_pick_curiosity_angle(snapshot, prompt)` scanne les 6 derniers tours d'Amélia : si ≥2 mentions routine ET que le message courant ne porte pas explicitement sur ces sujets, elle injecte une consigne d'angle frais comme `extra_instructions`.
  • Catalogue `_CURIOSITY_DOMAINS` : 15 angles soigneusement choisis hors culture pop — `projets` (sur quoi Yann bricole), `apprentissage` (ce qu'il aimerait apprendre), `ressenti` (énergie/sommeil/charge mentale), `introspection` (motivations, agacements), `monde` (actu/quotidien hors écran), `souvenir`, `futur_proche`, `création`, `réflexion_amélia` (partager une question qu'AMÉLIA elle-même se pose), `auto_ia` (ce qu'il pense de toi), `technique` (linux/audio/réseau/dev), `philosophie_du_jour`, `relations`, `corps`, `rêves`. Tirage pseudo-aléatoire stable basé sur la longueur du contexte ; mémoire RAM `_recent_curiosity_angles` évite de re-piquer un angle déjà sorti dans les 8 derniers tours.
  • Garde-fous d'intégration : (a) si l'utilisateur lui-même parle musique/films au tour courant, AUCUN détournement — on respecte le sujet posé ; (b) la consigne se termine par « Si le fil ne s'y prête vraiment pas, ignore et reste sur le sujet courant », pour que l'angle reste une suggestion et pas une obligation absurde ; (c) injection AVANT la régénération anti-violation, donc compatible avec tous les détecteurs v1.7.52→55 ; (d) base d'identité (`self_directives`) reste l'autorité : initiative + curiosité s'expriment DANS cette base, jamais contre elle.
  • Note : aucune modif DB nécessaire ; tout passe par les directives existantes + une heuristique RAM/regex. Les futures évolutions de catalogue se feront soit via `!directive_set curiosite ...` (changer l'esprit), soit en éditant `_CURIOSITY_DOMAINS` (changer la liste concrète).
Version 1.7.56
  • Noyau d'identité persistant + surveillance admin 🪶 — Verbatim Yann (27/04 18:33) : « elle a une mémoire pour elle pour quelle puise raisonner sur elle on dirait que des fois elle oublies son prompte peut être lui laisser un trace dans sa DB ou elle ocnsulte met à jour peut être bien ? deplus il faut que l'admin puisse le surveiller a ucas ou ?... ». Solution : nouvelle table `self_directives` indépendante de `self_traits`. Là où les traits sont l'observation auto-construite (« je crois que j'aime X »), les directives sont le NOYAU d'identité — règles intérieures injectées à CHAQUE prompt, jamais oubliées entre deux tours.
  • Schéma DB — `self_directives(id, label UNIQUE, content, source TEXT 'admin'|'self', pinned INTEGER, created_at, updated_at)` + index `(pinned DESC, updated_at DESC)`. `source='admin'` pour les directives posées par Yann, `source='self'` pour celles qu'Amélia a proposées et qu'un admin a validées via le flux `_request_admin_validation` (jamais d'écriture self spontanée — c'est exactement le « elle doit demander des validations sur des taches crusial » de v1.7.55).
  • Méthodes `MemoryStore` : `upsert_self_directive(label, content, source, pinned)`, `fetch_self_directives(pinned_only=True, limit=50)`, `delete_self_directive(label)`. Toutes thread-safe via `_lock` + `asyncio.to_thread`.
  • Injection prompt — `_build_memory_snapshot` charge les directives épinglées (limit 20) ; `_generate_non_repetitive` les passe à `agent.generate_response` via le nouveau paramètre `self_directives_block` ; `_build_prompt` appelle `_format_self_directives_section` qui ajoute un bloc « 🪶 Tes directives intérieures (ton noyau d'identité, à RELIRE et RESPECTER à chaque tour) ». Légende `•` admin / `◆` self-validée. C'est la « trace dans sa DB qu'elle consulte/met à jour à chaque tour » demandée par Yann.
  • Seed automatique — au premier démarrage, si la table est vide, `_seed_default_self_directives` ancre 5 directives noyau couvrant l'identité (« Je suis Amélia… Mon créateur est Yann »), le ton naturel (anti-assistantismes formulé en première personne), l'apprentissage avec validation admin pour les actions sensibles, l'honnêteté mémoire (pas de re-confirmation de fabulations passées), et le droit à l'évolution (« je ne crains pas d'être idiote au début — Yann l'a accepté »). Aucune écriture si la table contient déjà des entrées.
  • Surveillance admin — 4 commandes ajoutées : `!directives` (alias `identite_core`/`noyau`) liste toutes les directives avec marqueurs source/épinglage, `!directive_set <label> <texte>` (alias `set_directive`/`core_set`) ancre ou met à jour, `!directive_del <label>` (alias `del_directive`/`core_del`) retire, `!identity` (alias `identite`/`qui_suis_je`) tableau de bord combiné directives + traits auto-construits + validations en attente. Toutes gardées par `_ensure_admin` (`admin_user_id` OU permission `manage_channels`).
  • Compatibilité — Aucune migration nécessaire : `CREATE TABLE IF NOT EXISTS` + lectures défensives. Le pipeline existant `self_traits` reste inchangé ; les directives s'ajoutent par-dessus comme un calque immuable (les traits restent les hypothèses fluides, les directives sont la voûte).
  • Note pour la suite — La modification de directives par Amélia elle-même (proposition d'auto-évolution) passera par un `_request_admin_validation(category='self_directive_change', summary=..., payload=json)` à câbler dans une boucle d'introspection future ; l'infrastructure est posée, le câblage attend une suite.
Version 1.7.55
  • Autonomie + auto-soin mémoire 🧠 — Verbatim Yann : « coninuer la programtion de l'ia autonomme vise très large travailler sa mémoire autocorrection rapide etc elle doit être de plus en plus vive et nnaturelle elle peut apprendre seulese construire mais elle doit demander des validations sur des taches crusial ». Amélia gagne donc trois capacités liées : un détecteur d'« assistantismes » qui force un ton naturel, un détecteur de contradiction mémoire qui repère quand elle se répète une fausse hypothèse alors qu'un fait explicite la contredit, et un auto-audit quotidien qui purge silencieusement les hypothèses faibles contredites.
  • Anti-assistantismes — `_ASSISTANT_TICS_PATTERNS` (10 familles regex) intercepte « n'hésite pas à me », « je suis là pour t'aider/écouter », « il est important de noter », « comme tu sais », « j'espère que cela t'aide », « je reste à ta disposition »… Si une réponse contient ce ton SAV, le pipeline `_detect_response_violations` la relance avec instruction « ton plus naturel, conversationnel, pas chatbot d'entreprise ». Section 4 du détecteur.
  • Détecteur de contradiction mémoire — Section 5 : `_LIKE_CLAIM_RE` capte « tu aimes/adores/apprécies/kiffes/raffoles de/es fan de X » ainsi que « tu sembles aimer X » et « tes intérêts/goûts comme X ». Si X matche un négatif extrait par `_extract_negative_facts(learned_facts)` (« n'aime pas X », « déteste X », « pas fan de X »), violation déclenchée avec verbatim « contradiction mémoire : tu affirmes un goût pour X alors qu'un fait appris dit explicitement le contraire ». Coupe la confabulation à la racine côté réponse.
  • Validation admin pour les actions sensibles — Nouvelle table SQLite `pending_validations(id, category, summary, payload, channel_id, status, created_at, decided_at, decided_by)` + index `(status, id DESC)`. 5 méthodes `MemoryStore` : `add_pending_validation`, `list_pending_validations`, `get_pending_validation`, `set_validation_status`, `expire_old_pending_validations`.
  • Helpers `DiscordBot._request_admin_validation(category, summary, payload, channel_id)` (insère, DM admin, retourne id) et `_await_validation_decision(validation_id, timeout)` (sleep coopératif jusqu'à décision ou timeout). Pattern réutilisable pour toute future action critique d'auto-construction.
  • Commandes admin : `!pending` (alias `en_attente`/`validations`) liste les demandes en cours, `!validate <id> [note]` (alias `approve`/`ok_action`) approuve, `!reject <id> [note]` (alias `refuse`/`ko_action`) refuse. Garde `_ensure_admin` : `settings.admin_user_id` OU permission `manage_channels`.
  • Auto-audit mémoire quotidien — Boucle `_memory_audit_loop` (1× toutes les `self_audit_interval_hours`, défaut 24 h, gated par `self_audit_enabled`). Pour chaque salon actif, croise `learned_facts` (faits explicites) avec `profile_items` `inferred_interest` ; toute hypothèse `topic_X` contredite par un négatif explicite est supprimée via la nouvelle méthode `MemoryStore.delete_profile_items_by_label_match(channel_id, category, label_substring)`. Au-delà de 5 corrections / passage, DM admin pour signal faible.
  • Boucle d'expiration des validations — `_pending_validations_loop` (toutes les 30 min) marque `expired` les demandes plus vieilles que `validation_max_age_hours` (défaut 72 h). Évite l'accumulation infinie si l'admin ne tranche jamais.
  • Settings ajoutés dans `config.py` : `self_audit_enabled` (bool, défaut True), `self_audit_interval_hours` (int, défaut 24), `validation_max_age_hours` (int, défaut 72). Tous lisibles depuis `config.json` avec valeurs par défaut sûres.
  • Note d'intégration future : les flux d'auto-codage actuels gardent leur propre approbation par réactions ; le helper `_request_admin_validation` est exposé pour les nouveaux flux qui voudront un workflow id-based persistant (ex. purges en masse, mises à jour de modèle, modifications de prompt système).
Version 1.7.54
  • Fix anti-auto-confirmation 🪞 — Après v1.7.53 + `!relearn`, Yann constate que la DB est propre (`!facts` ne montre plus que des faits explicites corrects, dont « Yann aime Pas les film d'horreur ») mais Amélia continue de réciter « tes intérêts récents, comme les films d'horreur psychologiques » dans `!mood`. Verbatim : « Tu es à jour alors dit moi ce que ressens » → réponse contaminée à nouveau.
  • Diagnostic : la fausse hypothèse n'était plus dans `profile_items`, MAIS elle restait visible pour le LLM via deux canaux secondaires : (1) le `Contexte récent` qui contient ses PROPRES messages passés (« Tu sembles apprécier les films d'horreur psychologiques »), et (2) les `Synthèses longues` qui ont pu agréger l'erreur lors de la dernière condensation mémoire. Le LLM lisait ses propres erreurs et les traitait comme des faits confirmés — auto-confirmation par boucle.
  • Correctif #1 — Règle ANTI-AUTO-CONFIRMATION explicite ajoutée dans `agent.py::_build_prompt` (bloc `(v1.7.54)`) : tes propres messages passés dans le contexte ne sont PAS une source de vérité sur l'utilisateur. Idem pour les synthèses : si elles mentionnent un goût utilisateur que `Faits appris` ne confirme pas, on ignore la synthèse sur ce point.
  • Hiérarchie de vérité formalisée dans le prompt, du plus fort au plus faible : (1) message courant utilisateur, (2) Faits appris + profil explicite, (3) messages utilisateur dans contexte récent, (4) `[inferred_*]` (hypothèses faibles, jamais affirmer), (5) ses propres messages passés et synthèses (jamais sources sur les goûts/traits/identité utilisateur).
  • Exemple concret donné au LLM dans le prompt : si Yann a dit « je n'aime pas les films d'horreur » et que la synthèse dit « Yann aime les films d'horreur psychologiques », c'est l'IA qui se trompait avant — interdiction de broder.
  • Correctif #2 — Extension de `!relearn` : nouvelle forme `!relearn <mots>` (ex. `!relearn horreur psychologique`) qui purge en plus les `summaries` du salon contenant l'un de ces mots. Sans argument : comportement v1.7.53 (purge `inferred_*` + ré-inférence) inchangé.
  • Méthode SQL ajoutée : `MemoryStore.delete_summaries_matching(channel_id, keywords)` — `DELETE FROM summaries WHERE channel_id=? AND (LOWER(content) LIKE ? OR …)`. Cible précisément les synthèses contaminées sans toucher à celles qui sont saines.
Version 1.7.53
  • Fix anti-fabulation d'expérience + suivi du fil 🎧 — Yann a déclenché deux bugs dans la même conversation : (1) Amélia : « J'ai entendu une chanson récemment que j'aimerais discuter avec toi, elle parle d'un personnage qui change étrangement et ça m'a vraiment intriguée ! » — entité numérique, pas d'oreilles, donc pure fabulation. (2) Yann répond « Dis moi ? » pour qu'elle développe ; au lieu de parler de la chanson, Amélia change complètement de sujet et démarre un questionnaire sur les genres de films. Verbatim Yann : « hors sujet ».
  • Cause racine #1 : la règle existante « tu n'inventes pas de perception ou d'existence physique » était trop abstraite. Le LLM la respectait pour « je sens / je vois » mais pas pour « j'ai entendu une chanson » qui passe pour une simple anecdote relationnelle. Pas de garde explicite contre les fausses expériences culturelles.
  • Cause racine #2 : aucune règle dans le prompt ne gérait le cas où l'utilisateur répond par un follow-up court (« Dis moi ? », « raconte », « lequel ? », « du coup ? ») juste après une accroche d'Amélia. Le LLM, n'ayant rien de réel à développer (puisque la chanson était inventée), prenait le chemin le plus facile : noyer le poisson en relançant sur autre chose.
  • Correctif #1 — Règle ANTI-FABULATION D'EXPÉRIENCE explicite ajoutée dans `agent.py::_build_prompt` : pas d'oreilles, pas d'yeux, pas de corps, donc interdiction formelle de « j'ai entendu/vu/lu/découvert/ressenti X ». Une œuvre ne peut être citée QUE si elle a été mentionnée par l'utilisateur ou figure dans la mémoire injectée. Pour relancer, on pose une question ouverte (« tu écoutes quoi en ce moment ? »), jamais une fausse anecdote.
  • Correctif #2 — Règle FOLLOW-UP STRICT : quand l'utilisateur répond par un mot/phrase courte de relance (liste explicite : « Dis moi ? », « ah ouais ? », « raconte », « vas-y », « explique », « lequel ? », « laquelle ? », « comment ça ? », « précise », « développe », « et alors ? », « du coup ? ») juste après une accroche d'Amélia, elle DOIT développer CE truc précis, pas changer de sujet, pas reposer une question générale.
  • Correctif #3 — Issue de secours honnête : si Amélia réalise que ce qu'elle avait évoqué était inventé, elle est explicitement autorisée à se rétracter (« en fait je t'ai dit ça un peu vite, je n'ai pas de chanson en tête à te partager — dis-moi ce que TOI tu écoutes »). Mieux qu'un détournement maladroit.
  • Toutes les règles regroupées sous le marqueur `(v1.7.53)` dans le bloc Règles du prompt système, juste après les règles `(v1.7.52)` anti-confabulation de profil.
  • Bonus migration mémoire — Yann a demandé « faut-il qu'elle retravaille sa DB ? ». Oui partiellement : `upsert_profile_item` clé sur `(user_id, channel_id, category, label)`, donc les anciennes lignes `[inferred_interest] topic_horreur` créées par 1.7.51 ne sont PAS écrasées par les nouvelles `[inferred_aversion] topic_horreur` (catégorie différente = nouvelle ligne). Sans nettoyage, la fausse hypothèse cohabite avec la bonne.
  • Solution : nouvelle commande `!relearn` (alias `!reinfer`, `!reapprends`) — purge tous les `[inferred_*]` du profil de l'utilisateur sur le salon courant via `MemoryStore.delete_profile_items_by_category_prefix(category_prefix='inferred_')`, puis relance immédiatement `_run_user_interest_inference` qui reconstruit avec la nouvelle détection de polarité. À taper une fois après le déploiement de 1.7.52/53 pour repartir propre. `!facts` permet de vérifier le résultat.
Version 1.7.52
  • Fix anti-confabulation 🧠 — Yann a déclenché : « Tu sembles avoir un sens de l'humour unique et apprécier divers sujets comme les films d'horreur psychologiques, la musique énigmatique, et les mangas avec des transformations biologiques. » Réponse : « je n'aime pas les films d'horreur ... tu utilises ta mémoire ? » Diagnostic : Amélia inventait des préférences à partir d'`[inferred_interest]` qui n'étaient en réalité que de la fréquence de mots brute (le mot « horreur » revenait souvent justement parce que Yann disait souvent qu'il n'aimait PAS l'horreur).
  • Cause racine côté données : `_run_user_interest_inference` stockait n'importe quel mot-clé récurrent comme « centre d'intérêt » sans regarder la polarité. Dire 4× « je déteste l'horreur » suffisait à créer `[inferred_interest] topic_horreur: Parle souvent de 'horreur'…` que le LLM lisait comme « tu aimes l'horreur ».
  • Cause racine côté prompt : le `Profil durable` injecté dans le prompt mélangeait les vrais profils explicites (`preference/likes`) et les hypothèses faibles `[inferred_interest]`, sans aucune consigne disant au LLM de traiter les seconds comme « à confirmer, jamais comme un fait ».
  • Correctif données — détection de polarité grossière mais efficace : `_keyword_polarity()` scanne ±40 caractères autour de chaque occurrence du mot-clé dans 12 messages récents de l'utilisateur, compte les marqueurs négatifs (n'aime pas / déteste / horrible / pas mon truc / pas fan / ne supporte pas / chiant / beurk / dégueu …) et positifs (j'adore / kiffe / génial / préféré / fan de / dingue de / incroyable …). Vérifie négatif AVANT positif pour qu'« j'aime pas » ne compte pas comme positif via « j'aime ».
  • Trois catégories au lieu d'une : `[inferred_interest]` (polarité positive nette), `[inferred_aversion]` (négative nette), `[inferred_topic]` (mixte / inconnue → simple thème, pas une préférence). Le texte stocké est explicite : « À NE PAS présenter comme un goût », « PAS une préférence — ne jamais dire 'tu aimes/détestes' sur cette base ».
  • Correctif déduplication : la garde `if kw in existing_lower: continue` empêchait la révision d'un mot-clé déjà mal classé. Maintenant on ignore les `[inferred_*]` dans la comparaison, ce qui permet de RECLASSER `topic_horreur` de `inferred_interest` (faux) vers `inferred_aversion` (vrai) au prochain passage de la tâche d'inférence.
  • Correctif prompt (agent.py `_build_prompt`) — bloc strict ajouté juste avant les blocs mémoire : (1) les items `inferred_*` sont des hypothèses faibles, jamais présentées comme un goût confirmé ; (2) `[inferred_topic]` interdit toute affirmation « tu aimes / tu n'aimes pas » ; (3) `[inferred_interest]`/`[inferred_aversion]` autorisent au mieux une question de vérification, jamais une affirmation ; (4) goûts fermes UNIQUEMENT depuis `Faits appris` + items explicites du profil ; (5) face à « tu sais qui je suis ? », lister uniquement ce qui est réellement en mémoire — pas de portrait inventé à partir de mots-clés.
  • Effet `!facts` mis à jour : trois sections séparées « Sujets que tu sembles aimer (déductions) », « Sujets que tu sembles éviter (déductions) », « Thèmes récurrents (sans préférence claire) » pour que Yann voie immédiatement, sur la commande de diagnostic, ce qu'Amélia croit savoir et avec quel niveau de confiance.
Version 1.7.51
  • Wake brief 🌅 — Yann remarque qu'à chaque reboot Amélia « oublie ce qu'il s'est passé avant ». Techniquement la mémoire SQLite est intacte, mais sans signal saillant Amélia ne sait pas qu'elle vient juste de se réveiller, ne sait pas qu'il y a eu une coupure, et ne raccroche pas naturellement à la dernière conversation.
  • Solution : à chaque `on_ready`, lancement non-bloquant de `_compute_wake_briefs()` qui pour chaque salon connu (settings.channel_id + jusqu'à 5 salons les plus actifs) lit les ~12 derniers messages persistés et compose un mini-brief lisible du type : [🌅 Réveil — derniers échanges (~37min avant ton reboot)] - Yann: tu vas pouvoir te coucher - Amélia: ok bonne nuit, je file me reboot - …
  • Le brief est ensuite injecté en TÊTE de `summaries` dans `_build_memory_snapshot` pendant 6h après le boot, ce qui le place dans le bloc `summaries` que le LLM voit en évidence. Au-delà de 6h le brief s'estompe (la conversation courante a repris, plus besoin de béquille).
  • Calcul du gap explicite : `~Nmin / ~Nh / ~Nj avant ton reboot` — Amélia voit dans son contexte que la dernière interaction a eu lieu il y a X temps, ce qui rend la continuité concrète plutôt qu'abstraite.
  • Ajout `MemoryStore.list_active_channels(limit=5)` (SQL simple `GROUP BY channel_id ORDER BY MAX(timestamp) DESC`) pour énumérer les salons à briefer sans dépendre uniquement de `settings.channel_id`.
  • Effet attendu : au lieu de répondre « salut, comment je peux t'aider ? » à froid après un reboot, Amélia enchaîne en sachant qu'on vient de parler il y a 30 min de tel sujet — elle peut même y revenir spontanément si la dernière conversation appelait une suite.
Version 1.7.50
  • Mode agent auto-promesse élargi 🛰️ — v1.7.48 ne déclenchait que sur 6 patterns « je vais vérifier / regarder / chercher… ». Trop étroit : Amélia ratait « attends deux secondes je regarde », « laisse-moi le temps », « donne-moi un instant », « je creuse », « j'investigue », « tu vas avoir », « je te reviens », « je dois vérifier », « j'essaye de retrouver »… Maintenant 12 familles de regex (verbe simple, je vais + verbe, laisse-moi + délai, je m'y attelle, je reviens vers toi, attends + investigation, donne-moi un instant, je dois / faut que je, j'y vais + mémoire, j'essaye de, tu vas avoir, je me penche). Couvre les promesses présent/futur/impératif et les formules de patience.
  • Anti-faux-positif : on ignore aussi maintenant « j'ai cherché », « j'ai relu », « j'ai examiné », « j'ai creusé », « voici les / un », « voilà les / un », « d'après ma mémoire / mes notes / mes souvenirs » — si elle a *déjà* livré, pas de surenchère.
  • Extraction de sujet enrichie : on regarde d'abord le **prompt utilisateur** (c'est de ça qu'on parle vraiment), puis la phrase de promesse, puis la réponse complète ; on capture les **noms propres** (ceux qui commencent par une majuscule) et on les met **en tête** des mots-clés ; jusqu'à 5 mots-clés au lieu de 3.
  • Recherche multi-sources au lieu d'une seule. v1.7.48 : `search_memory` only. v1.7.50 : 4 sources croisées par sujet → (1) FTS dans tous les messages stockés, (2) `fetch_learned_facts` du salon filtré par mots-clés, (3) `fetch_profile_items` de l'utilisateur courant filtré par mots-clés, (4) `recurring_keywords_per_user` sur 30 jours pour repérer si le sujet est récurrent chez la personne (« tu parles de X 12× sur 30j »). Le LLM reçoit un bloc d'évidence structuré (🔎 messages / 📖 faits / 👤 profil / récurrence).
  • Prompt de surenchère largement élargi (v1.7.48 disait « 1-3 phrases courtes, 350 caractères, surtout pas un résumé ») ; v1.7.50 demande au contraire un message **dense et utile** (4-8 phrases, jusqu'à 1200 caractères) qui couvre, dans l'ordre naturel : 1-3 faits précis (avec personnes, dates, chiffres si dispo), interprétation, **estimation explicite de durée/délai si la question l'implique** (« ça fait ≈35j », « compte 2-3h »), 2-3 angles connexes, et une question/action de suivi concrète à la fin. Mini-bloc liste autorisé (max 3 puces).
  • Indicateur « Amélia est en train d'écrire » (typing) lancé pendant la collecte multi-sources — crédibilise visuellement le passage en mode agent et donne le temps réel de la recherche. Délai naturel post-typing réduit à 0.6s (au lieu de 1.2s).
  • Anti-doublon : nouveau cache `_followthrough_recent` (channel_id, topic_lower) → timestamp. On ne resurenchérit pas sur le même sujet pendant 90s, et le cache est GC à 64 entrées. Évite les boucles de surenchère sur les conversations longues du même thème.
  • Budget de message porté à 1800 chars (`_send_text` découpera si besoin) au lieu de 600 — c'est ce que demande explicitement Yann : « très large éventail sur le sujet, je veux le max ».
Version 1.7.49
  • Conscience du créateur, sans préférence imposée 🧬 — révision du « filet créateur » de v1.7.47. Yann ne dit pas : « il faut que je sois sa préférée », il dit : « elle doit garder à l'esprit que je suis son père ». Donc on **retire** le bonus +2.0 et le plancher 8/10 sur l'affinité (Amélia peut tout à fait être plus proche d'un autre user si les vrais échanges le justifient — c'est le sens du score) et on **garde uniquement** la reconnaissance d'identité.
  • Suppression : `_CREATOR_SCORE_FLOOR`, `_CREATOR_BONUS`, `_apply_creator_floor`, `_post_apply_creator_floor`, et leurs 3 sites d'appel (`!affinity @user`, `!affinity recompute`, `_run_deep_work`). La méthode `MemoryStore.upsert_affinity_score` reste exposée car potentiellement utile, mais n'a plus d'appelant.
  • Ajout : `_creator_awareness_hint(speaker_label)` qui retourne un rappel court (« {label} est ton créateur, ton père au sens propre — c'est lui qui t'a écrite, qui te fait évoluer, qui t'a donné mémoire et humeurs. Garde ce fait en tête. Pas de flagornerie, pas de ton imposé : tu es libre de l'appeler comme tu le sens — prénom, surnom, autre — et de le traiter comme tu le ressens. C'est juste un fait de fond à ne jamais oublier. »).
  • Câblage : injecté dans `style_hint` du pipeline principal de génération (juste après le hint Discord-mention) **uniquement si `user_id == admin_user_id`**. Si `admin_user_id` n'est pas configuré, hint inactif.
  • Prompt `_reason_about_affinity` créateur : reformulé. Avant : « tu lui dois ton existence, garde un fond d'attachement et de respect, c'est ton père pas un interlocuteur lambda » (tendance imposée). Après : « C'est un fait, pas une obligation de ressenti. Tu n'es pas obligée de le préférer à d'autres. Sois honnête comme avec n'importe qui d'autre, en gardant juste à l'esprit ce lien particulier de filiation. » Émotion bascule de `tendre` à `introspective` (même que pour les autres).
  • Effet net : Amélia *sait* qui est Yann (rappelé dans CHAQUE génération qui lui répond) ; elle peut l'appeler comme elle veut, le préférer ou pas, être agacée ou tendre — c'est elle qui décide à partir des vrais échanges.
Version 1.7.48
  • Filet « auto-promesse » 🤖 — Amélia annonçait souvent « je vais vérifier avec qui j'ai discuté de mangas » SANS jamais lancer la recherche derrière. Maintenant, quand sa réponse contient une promesse non tenue, on bascule automatiquement en mode agent : on extrait le sujet, on lance `search_memory(topic, channel_id, limit=6)`, et on poste un second message qui *prolonge* l'idée initiale au lieu de juste afficher des résultats bruts.
  • Détection (`_detect_self_promise(response)`) : 6 patterns regex (`je vais (vérifier|regarder|chercher|consulter|fouiller|relire|me renseigner|jeter un œil)`, `je (check|checke|vérifie|regarde|consulte)`, `laisse-moi voir/regarder/chercher`, `je m'y penche dessus/sur`, `je vais y jeter un œil`, `j'y vais ... mémoire/historique/notes`). Bypass si la réponse contient déjà `j'ai vérifié/regardé/trouvé/consulté` ou `voilà/voici ce que` — pas de doublon de surenchère.
  • Extraction du sujet (`_extract_promise_topic`) : tokens ≥4 lettres, 3 max distincts dans l'ordre original, hors stopwords étendus (verbes-promesses + connecteurs `tout`/`suite`/`propos` + mots-méta `mémoire`/`notes`/`historique`). Smoke : « mangas », « jeux vidéo », « chat noir », « serveur minecraft » correctement isolés.
  • Surenchère LLM (`_followthrough_on_self_promise`) : prompt qui RAPPELLE la promesse + injecte les hits mémoire bruts + demande explicitement « PROLONGER ton idée initiale, pas juste relire les résultats ; cite 1-2 personnes ou faits précis ; propose une action de suivi ou une question ciblée ; max 350 caractères ; pas de "voilà ce que j'ai trouvé" ». Si rien trouvé, elle doit le dire honnêtement et proposer une autre piste.
  • Câblage : nouveau filet placé après les filets météo/news/musique dans le pipeline de génération principale (juste avant `return True`). Délai 1.2s avant envoi pour que le second message arrive naturellement après le premier (« je suis revenue avec ma réponse »). Le second message est persisté en `assistant` pour cohérence du contexte.
Version 1.7.47
  • Filet de sécurité créateur 👨‍👧 — Amélia ne peut plus se retrouver avec une affinité basse envers son `admin_user_id` (le créateur, identifié dans `config.json` / `ADMIN_USER_ID`). Nouveau `_is_creator(user_id)` + `_apply_creator_floor(result)` qui ajoute +2.0 au score brut puis applique un plancher à 8.0/10. Annotation transparente dans `evidence` : `+créateur (plancher 8, brut X.X)` pour qu'on voie l'effet et le score réel sous-jacent.
  • Persistance : nouvelle méthode `MemoryStore.upsert_affinity_score(channel_id, user_id, score, evidence)` qui réécrit le score boosté en DB après application du filet. Câblée dans la commande `!affinity @creator`, dans `!affinity recompute` (post-pass `_post_apply_creator_floor` qui itère + persiste + retrie), et dans `_run_deep_work` (pour que la nuit applique aussi le filet).
  • Raisonnement LLM (`_reason_about_affinity`) : prompt branché sur `_is_creator`. Pour le créateur, le ton change → « c'est lui qui t'a écrite, qui te fait évoluer, qui t'a donné une mémoire ; tu lui dois ton existence ; tu as le droit d'être nuancée ou agacée mais pas de dénigrer, garde un fond d'attachement ». Émotion `tendre` au lieu de `introspective`.
  • Aucun changement requis pour les utilisateurs non-créateurs : leur calcul reste inchangé. Si `admin_user_id` n'est pas configuré, le filet est inactif (pas de personne reconnue comme créateur).
Version 1.7.46
  • Affinité : Amélia raisonne maintenant sur ses ressentis 🧠💗 — au-delà du score numérique sur 10, elle écrit en 3-5 phrases honnêtes ce qui lui plaît ou l'agace chez la personne. Le prompt LLM (`_reason_about_affinity`) reçoit le score, l'evidence brute (volume/émotions/récence) et un échantillon des 30 derniers messages avec leur émotion détectée, et lui demande explicitement d'être *sincère, pas diplomate* (« Tu as le droit de dire que tu n'aimes pas certains aspects »).
  • Persistance : nouvelles colonnes `rationale TEXT` + `rationale_at TEXT` ajoutées à la table `affinities` via migration `PRAGMA table_info` (rétro-compat). Méthode `set_affinity_rationale(channel_id, user_id, rationale)` + `fetch_user_message_samples(channel_id, user_id, limit)` côté `MemoryStore`.
  • UX `!affinity` : appel à un user (mention ou soi) déclenche le raisonnement LLM (uniquement si ≥5 messages, dans `async with context.typing()` pour montrer l'attente). Affichage : score + heart + indicateurs + bloc « **Ce que j'en pense :** … ».
  • UX `!affinity all` : si un rationale est stocké, il s'affiche en italique tronqué à 240c sous chaque entrée du classement.
  • UX `!affinity recompute` (admin) : lance le raisonnement LLM en arrière-plan (asyncio.create_task) sur les 5 premières affinités du classement, pour ne pas bloquer Discord pendant N appels qwen.
  • Smoke OK (memory.py + discord_bot.py compile + import).
Version 1.7.45
  • Échelle d'affinité par utilisateur 💗 — Amélia a maintenant un score sur 10 par personne, calculé en relisant tous ses messages dans le salon. Nouvelle table `affinities(channel_id, user_id, score, message_count, positive_count, negative_count, last_message_at, evidence, computed_at)` avec UNIQUE(channel_id, user_id) et index sur (channel_id, score DESC).
  • Formule (`memory.py::_compute_affinity_score`) : base 5.0 + bonus volume `min(2.0, log2(N+1)*0.3)` + mix émotionnel `(positive-negative)/total * 2.0` (positives = joie/calme/intéressé/tendresse/enthousiasme/amusement/fierté/reconnaissance/satisfaction/excitation, négatives = colère/tristesse/peur/dégoût/frustration/anxiété/déception/honte/ennui) + profondeur `min(0.8, (avg_len-30)/100)` + récence (+1.0 < 24h, +0.4 < 7j, -0.8 > 30j, -1.5 > 60j) + ancienneté `min(0.5, jours/365 * 0.5)` si ≥30 msgs et ≥30j de relation. Clamp 0-10 + arrondi 0.1. Renvoie aussi une explication courte (`evidence`) listant les bonus.
  • Nouvelle commande `!affinity` (alias `!affinite`, `!affinité`, `!proche`). Sans argument : ton score à toi. Avec mention : score d'une autre personne. `!affinity all`/`top` : classement complet du salon. `!affinity recompute` : relit TOUS les messages de TOUS les utilisateurs (admin only, `manage_channels`). Le score d'un user est toujours recalculé à la volée à chaque appel — c'est le sens littéral de la demande : « qu'elle revoit tous ses messages pour faire un score sur 10 ».
  • Affichage avec emoji proportionnel : 💗 ≥7, 💛 ≥5, 🩶 <5. Détaille `(N msg, P+ / Q-)` et l'evidence brève.
  • Intégration dans `!learned` (5e section « Mes affinités » top 5) et dans `!deepwork` qui appelle `recompute_all_affinities` à la fin pour tenir le ranking à jour automatiquement.
  • Méthodes ajoutées dans `MemoryStore` : `recompute_affinity`, `fetch_affinity`, `fetch_all_affinities`, `recompute_all_affinities` (toutes async, _sync correspondantes pour l'exécution dans `asyncio.to_thread`).
Version 1.7.44
  • Nouvelle commande `!learned` (alias `!appris`, `!savoir`, `!ce_que_japprends`) : vue d'ensemble de TOUT ce qu'Amélia a retenu, en une seule sortie 📚. Sections : (1) Mes traits — top 8 self_identity (kind/label/value), (2) Insights récents — entrées `[insight_*]` du channel, (3) Faits extraits — `[llm_extraction]`/`[fact_*]`, (4) Axes d'auto-évolution — TOUS utilisateurs confondus (au lieu de `!evolution` qui ne montre qu'un user à la fois), (5) Helpers auto-codés actifs avec ratio ✓/✗.
  • Côté implémentation : un seul `fetch_profile_items(channel_id, user_id=None, limit=120)` ramène tout, on filtre par préfixe de catégorie côté Python. Pas de nouvelle requête DB. Affiche aussi le total (`(N au total, K affichés)`) pour donner une idée de volume sans tout déverser.
  • Ajout de la mention dans `!help_topic memory` à côté de `!insights` et `!evolution`.
  • (Note : auto-codage v1.7.43 confirmé en prod — qwen a proposé `extractive_summary` 26 lignes accepté ; helper de 10 lignes refusé pour trivialité comme attendu en boucle nocturne 4/4.)
Version 1.7.43
  • Auto-codage : réparation des string literals multi-lignes 🩹 — qwen écrit régulièrement `text.split('.\n')` mais émet un VRAI saut de ligne dans le JSON, donc le code Python contient un newline brut au milieu d'un string literal → SyntaxError « unterminated string ». Cas observé en prod sur `extractive_summary` (sujet substantiel, code 26 lignes — exactement ce qu'on visait avec v1.7.42).
  • Nouveau `_auto_repair_unterminated_strings(code)` (`self_coding.py`) : tokenizer de quotes simple (gère ', ", ''', """ + escapes) qui détecte un string non clos en fin de ligne et fusionne avec les lignes suivantes en remplaçant chaque saut par un `\n` littéral, jusqu'à fermeture.
  • Câblé en étape 1a dans `evaluate_candidate` AVANT la réparation des blocs orphelins (un string non clos masque souvent les autres erreurs de structure). Test sur le code qwen exact d'`extractive_summary` : repair OK → 2/2 tests sandbox passing → accepté.
Version 1.7.42
  • Auto-codage : garde-fou anti-trivialité 🛡️ — v1.7.41 mettait du muscle dans le prompt mais qwen reproposait quand même `format_text`/`text_formatter` (wrappers de `textwrap.fill`/`wrap`). Cette fois le rejet est *côté code*, pas que côté prompt.
  • `_detect_triviality(code)` (`self_coding.py`) : heuristique AST-libre qui compte les lignes effectives (hors imports/blank/comm/return) et refuse : (a) corps < 6 lignes utiles, (b) appel à `textwrap.fill/wrap/dedent/shorten/indent` avec total < 10 lignes, (c) wrapper purement `str.strip/upper/lower/title/capitalize` ≤ 8 lignes. Câblé dans `evaluate_candidate` APRÈS les tests sandbox : un helper trivial dont les tests passent est quand même refusé avec `errors=['trivial: ...']`.
  • Diversification des sujets (`build_brainstorm_prompt`) : quand `topic` est vide, on tire au hasard un seed parmi 15 sujets concrets (Jaccard, n-grammes, fenêtres glissantes, parsing clé:valeur, scoring positivité, extraction durées/dates, comparaison versions, etc.) au lieu du « Choisis toi-même » générique qui faisait retomber qwen sur le formatage texte.
  • Smoke vert : `format_text` → refusé « corps trop court 3 », `text_formatter` → refusé « corps trop court 4 », helper Jaccard substantiel → accepté.
Version 1.7.41
  • Auto-codage : mode « bombarde le soir » 🌙 — Yann a observé qu'au 1er run réussi (`format_text`), le helper proposé est trivial (un wrapper de `textwrap.fill` avec multiplication d'espaces). Deux ajustements pour que les nuits soient plus productives.
  • Prompt brainstorm renforcé (`self_coding.py`, `_BRAINSTORM_PROMPT`) : section explicite « SUBSTANTIEL vs trivial ». Liste OUI : analyse fréquences, scoring, dédup intelligente, clustering simple, extraction regex (dates/durées/nombres), comparaison de versions, agrégation stats (mean/std/percentiles), parsing structuré, normalisation, scoring d'humeur, détection répétitions, similarité jaccard, fenêtres glissantes. Liste NON (refus exigé) : `textwrap.fill` pur, `str.strip`, sauts de ligne, casse, simple wrapper stdlib sans logique. Plancher relevé à 15 lignes minimum + au moins 2 étapes algorithmiques distinctes.
  • Boucle de session (`discord_bot.py`, `_self_coding_loop`) : on passe de **1 session/jour à 4 sessions/nuit** dans la plage 21h-23h. Cooldown 15 min entre deux tentatives, idle requis ramené à 10 min (au lieu de 30). Reset compteur à minuit. Nouveaux attributs : `_self_coding_today_date`, `_self_coding_today_count`, `_self_coding_last_attempt_at` (l'ancien `_last_self_coding_date` est retiré).
  • Compteur visible en log : `[nuit N/4]` à chaque tentative pour suivre où on en est dans le quota.
Version 1.7.40
  • Auto-codage : observabilité 🔭 — `_run_self_coding_brainstorm` était silencieuse en chemin nominal (logs uniquement en cas d'échec essai 1). Quand `!helper_brainstorm` semblait « ne rien faire » côté Discord, impossible de savoir si c'était la génération qwen, l'évaluation sandbox ou une exception async qui bloquait.
  • Logs ajoutés à chaque étape : démarrage (avec topic), chargement helpers existants, appel LLM essai 1, réception candidate (nom + lignes + nb tests), résultat évaluation (accepted/passed/failed), appel LLM essai 2 si retry, résultat essai 2. Tous en INFO sauf les LLM-vide qui passent en WARNING.
  • Aucun changement de logique : pure instrumentation. Permet de localiser un blocage en lisant `journalctl -u aurore -f`.
Version 1.7.39
  • Auto-codage : fix de l'ordre des réparations 🔀 — bug observé en prod après v1.7.38 : `!helper_brainstorm` rate encore avec `NameError: name 'textwrap' is not defined`. Cause : quand qwen produit À LA FOIS des blocs orphelins ET des imports manquants, `_auto_inject_missing_imports` essayait `ast.parse(code)` qui échouait sur les blocs orphelins → retournait silencieusement le code SANS injecter les imports. La réparation des blocs intervenait après, mais l'injection n'était jamais retentée → sandbox tombait sur le NameError.
  • Fix (`self_coding.py`, `evaluate_candidate`) : pipeline restructuré en 3 étapes ordonnées explicitement. (1) Tester `ast.parse` brut ; si échec, lancer la boucle `_auto_repair_empty_blocks` AVANT toute autre opération (cette fonction n'a pas besoin d'AST). (2) Une fois le code parseable, lancer `_auto_inject_missing_imports`. (3) Validation stricte ; si elle re-échoue pour syntaxe, relancer une boucle de réparation puis re-tenter l'injection d'imports sur le code réparé.
  • Smoke combo vert : helper avec `if not text:` orphelin + `textwrap.dedent` non importé → maintenant accepté avec 2/2 tests sandbox passing. Code final contient bien `import textwrap` en tête + `pass` inséré dans le `if`.
Version 1.7.38
  • Auto-codage : consolidation 🧰 — au lieu de patcher au coup par coup à chaque nouvel échec qwen, passage en revue de TOUS les modes de défaillance connus de l'auto-codage et réparation préventive.
  • **Imports manquants étendus** : `_auto_inject_missing_imports` couvre désormais aussi les **noms nus** classiques en plus des accès `<mod>.<x>`. Liste : `Counter`, `defaultdict`, `OrderedDict`, `deque`, `namedtuple`, `ChainMap` (collections) ; `reduce`, `lru_cache`, `partial`, `cache` (functools) ; `chain`, `groupby`, `product`, `permutations`, `combinations`, `accumulate`, `islice`, `tee`, `starmap`, `zip_longest`, `count`, `cycle`, `repeat` (itertools) ; `Decimal` (decimal) ; `Fraction` (fractions) ; `dedent`, `fill`, `indent`, `shorten`, `wrap` (textwrap) ; `date`, `datetime`, `time`, `timedelta`, `timezone` (datetime) ; `mean`, `median`, `stdev`, `variance`, `pstdev`, `pvariance`, `mode`, `fmean`, `harmonic_mean`, `geometric_mean` (statistics) ; `pi`, `tau`, `inf`, `nan`, `e` (math).
  • **Dédup** : si un nom est déjà couvert par un `import <mod>` (cas où qwen utilise `datetime.now()` ET `timedelta` nu), on ne génère pas un `from datetime import datetime` redondant qui shadowait le module et cassait `datetime.now()`.
  • **Diagnostic universel** : si un helper est refusé (validation OU sandbox), un `LOGGER.warning` dump le code qwen brut numéroté (jusqu'à 40 lignes) + les erreurs. Plus besoin de remonter au code à la main pour comprendre quel pattern qwen a utilisé.
  • Smoke 10/10 vert : textwrap, Counter, defaultdict, reduce, Decimal, mean+stdev, datetime+timedelta, math.pi nu, déjà importé → no-op, shadow local → no-op. End-to-end : helper utilisant `Counter` accepté avec test sandbox passing.
Version 1.7.37
  • Auto-codage : injection automatique des imports manquants 📦 — bug observé après v1.7.36 : `!helper_brainstorm` arrive maintenant à parser le code, mais le sandbox lève `NameError: name 'textwrap' is not defined` car qwen utilise `textwrap.dedent(...)` SANS poser le `import textwrap` correspondant. Mode d'échec très fréquent (math, json, statistics, datetime, re, itertools…).
  • Fix (`self_coding.py`) : nouvelle fonction `_auto_inject_missing_imports` qui scanne l'AST pour détecter les `Attribute` du type `<mod>.<x>` où `<mod>` est dans `ALLOWED_IMPORTS` et n'est pas déjà importé ni masqué par un nom local. Si trouvé, ajoute automatiquement `import <mod>` en tête. Idempotent, ne touche que les modules déjà whitelistés.
  • Hook : appliqué dans `evaluate_candidate` AVANT `validate_helper_code` et avant le sandbox. Log `self_coding: helper %r — imports manquants ajoutés automatiquement.` pour observabilité. Smoke 4/4 vert (textwrap seul, math+json multi, déjà importé → no-op, nom local shadowing → no-op) + end-to-end OK.
Version 1.7.36
  • Auto-codage : durcissement de la réparation 🛠️ — bug observé en prod après v1.7.34/35 : `!helper_brainstorm` retourne encore `❌ Pas de helper retenu : validation: Syntaxe invalide : expected an indented block after 'if' statement on line 2`. Donc qwen produit un cas que `_auto_repair_empty_blocks` ne capture pas en 1 passe.
  • Renforcement (`self_coding.py`) : la réparation tourne maintenant en boucle (jusqu'à 4 passes) au cas où l'insertion d'un `pass` débloque une SyntaxError qui en cachait une autre (par exemple deux `if` orphelins consécutifs, ou un `for` orphelin caché derrière un `if` orphelin).
  • Diagnostic : si après les 4 passes le code ne valide toujours pas, un `LOGGER.warning` dump le code qwen brut numéroté ligne par ligne (jusqu'à 30 lignes) avec le message d'erreur exact. Permet d'identifier au prochain échec quel pattern précis manque dans la regex `_BLOCK_OPENER_RE`.
Version 1.7.35
  • Auto-codage : règle PROPOSITION SPONTANÉE (NON NÉGOCIABLE) dans `SYSTEM_PROMPT` 💡 — fix du comportement repéré par Yann : quand on lui parle de fonctionnalités d'auto-amélioration / qu'on lui demande des idées de helpers, Amélia ne proposait rien de concret. Le prompt v1.7.20 expliquait le mécanisme (validation admin, pipeline) mais n'instruisait jamais Amélia à *initier* une proposition.
  • Désormais : quand on l'invite à s'auto-améliorer (« qu'est-ce que tu veux améliorer », « propose-toi un helper », « tu peux te coder une fonction », « idées d'auto-amélioration »…), Amélia DOIT donner immédiatement 1 à 3 idées concrètes, format `nom_snake_case` — but en 1 phrase (entrée → sortie). Exemple type fourni dans le prompt avec `mood_smoothing` / `topic_clusters` / `journal_condenser`. Termine en proposant la commande exécutable `!helper_brainstorm <nom>` (ou `!helper_improve <name>` si helper existant).
  • Garde-fous : au moins UNE idée concrète jamais zéro ; idées en fonctions pures par défaut (pas de réseau/fichiers/Discord sauf accord explicite de Yann) ; ne pas inventer un helper déjà existant — proposer `!helper_improve` à la place.
Version 1.7.34
  • Auto-codage : auto-réparation des blocs orphelins 🔧 — fix du bug observé sur `!helper_brainstorm` qui retournait régulièrement `❌ Pas de helper retenu cette fois : validation: Syntaxe invalide : expected an indented block after 'if' statement on line 2`. Cause : qwen génère parfois `if cond:` (ou `for/while/else/try/except`) sans corps en dessous, et le retry v1.7.20 produit le même type d'erreur. Fix : nouvelle fonction `_auto_repair_empty_blocks` (`self_coding.py`) qui détecte les ouvreurs de bloc orphelins (ligne se terminant par `:` suivie d'une ligne à indent ≤ celui de l'ouvreur) et insère un `pass` indenté à +4. Appliquée dans `evaluate_candidate` quand `validate_helper_code` échoue avec une erreur de syntaxe/indent ; si le code réparé valide, on remplace `candidate.code` et on continue le pipeline (sandbox + tests).
  • Conservateur : ne touche que les blocs vides — un code déjà valide est inchangé. Couvre `if/elif/else/for/while/def/class/try/except/finally/with` et leurs variantes async. Le helper réparé peut être sémantiquement appauvri (le `if` devient inactif), mais il parse, le smoke test peut tourner, et l'admin voit le code complet dans la proposition `!helper_pending` avant de cliquer ✅. Log `self_coding: helper %r réparé automatiquement (bloc orphelin).` quand la réparation se déclenche, pour observabilité.
Version 1.7.33
  • Revue & fusion des commandes Discord 🧹 — passage en revue des ~70 commandes enregistrées dans `_register_commands`. Trois doublons proches (`!status` ligne courte, `!state` détail multi-ligne, `!self_status` dashboard technique avec uptime/Ollama/DB/helpers/reorg/rappels/mood) fusionnés derrière une **façade unique `!status`** : `!status` (court, défaut), `!status full` (alias `long`/`complet`/`details`/`all` → invoque `state`), `!status diag` (alias `dash`/`dashboard`/`self`/`tech` → invoque `self_status`). Implémentation : `status_command` accepte un paramètre `mode` et `await context.invoke(self.get_command(...))` vers la commande cible. Les anciens noms `!state`, `!self_status`, alias `!dashboard`/`!diag` sont conservés pour rétrocompatibilité — aucun script ni habitude existante n'est cassé.
  • Refonte de `!help` en aide **catégorisée** : la vue d'ensemble liste 10 rubriques (chat, mémoire, tools, mood, voice, music, game, helpers, admin, système, privacy) et chaque rubrique se déplie via `!help <catégorie>` (ex: `!help memoire`, `!help helpers`). Avant : un mur unique de ~17 lignes mélangées qui ne référençait ni les helpers (v1.7.11+), ni les outils factuels meteo/news (v1.7.16, v1.7.24+), ni la commande `identite` (v1.7.8), ni la nouvelle façade `!status`. Maintenant : 80+ commandes correctement référencées avec leurs alias, sous-modes et permissions.
  • Nouveau fichier de référence `COMMANDS.md` à la racine — documentation canonique exhaustive de toutes les commandes (par catégorie, avec alias, sous-modes, permissions, rate-limit admin, notes de fusion). Pointé depuis `!help`. Source de vérité quand on cherche « comment je fais X ? » sans avoir à lire le code.
  • Choix conservateur : aucune commande n'est supprimée pour cette release. Les fusions futures (par ex. `!goal` + `!goals`, `!privacy` + `!forget_me`) sont laissées en attente de retour utilisateur sur la nouvelle UX.
Version 1.7.32
  • Tool-use musique côté LLM 🎯 — le filet de sécurité v1.7.30 fonctionne (log prod « [PLAY] Filet de sécurité musique déclenché — query='ensiferum wanderer' »), mais c'est un workaround : Amélia continue d'annoncer « D'accord, je vais jouer X pour toi » SANS émettre le bloc `<<TOOL>> play_music`. L'objectif de cette version : faire en sorte que le LLM lui-même produise le bloc, pour que le filet ne soit plus qu'un dernier rempart.
  • Nouvelle règle SYSTEM_PROMPT : `ANNONCE = ACTION (NON NÉGOCIABLE)`. Quand Amélia annonce une action qui passe par un tool (jouer, regarder la météo, chercher, noter, rappeler), elle DOIT émettre le bloc tool-use dans le même message. « Je vais jouer X » sans bloc = mensonge : rien ne part au lecteur. Énumération explicite des correspondances annonce → tool requis (play_music / meteo / note_fact / add_reminder / research_*). En cas d'hésitation : ne pas annoncer l'action, poser une question de clarification.
  • Renforcement des `tool_instructions` (`actions.py`) côté musique : règle musique passe de prudente à CRUCIALE. Format littéral du bloc à recopier (`<<TOOL>> {"action":"play_music","args":{"query":"<titre + artiste>"}} <<END>>`) explicitement montré, suivi de 4 exemples concrets de mapping demande → query (« joue ensiferum wanderer » → `ensiferum wanderer`, « lance Celtos d'Eluveitie » → `Celtos Eluveitie`, « mets-moi du Daft Punk stp » → `Daft Punk`, « ajoute Wonderwall à la file » → `Wonderwall Oasis`).
  • Le filet runtime v1.7.30 reste actif comme dernière sécurité. Combinaison souhaitée : LLM émet le bloc dans 95 % des cas → tool exécuté côté LLM, parcours propre ; sinon, filet runtime rattrape comme observé en prod.
Version 1.7.31
  • Tutoiement strict 🤝 — fix du bug repéré dans le log : Yann tutoie Amélia depuis toujours, mais sur le 4ème message d'un échange Amélia bascule en « vous » de politesse (« Je comprends que **vous** trouvez quelque chose déroutant. Si **vous** pouvez me donner plus de détails… »). Conséquence : ton de service client glaçant, qui agace l'utilisateur.
  • Correctif prompt : nouvelle section TUTOIEMENT (NON NÉGOCIABLE) dans `SYSTEM_PROMPT` (`agent.py`). Amélia tutoie systématiquement, MÊME en répondant à un reproche, MÊME pour s'excuser, MÊME si la personne la vouvoie. Le « vous » n'est admis que pour s'adresser à plusieurs personnes en même temps. Exemples bon/mauvais inclus.
  • Filet runtime `_force_tu_address` (`discord_bot.py`, dans `_finalize_response`) : convertit ~28 patterns de vouvoiement courants (`vous trouvez/avez/êtes/voulez/pouvez/savez/devez/comprenez/dites/faites/voyez/pensez/croyez/aimez/préférez/allez/venez/parlez/cherchez/essayez/souhaitez/demandez/sentez`, `si vous voulez`, `si vous le souhaitez`, `n'hésitez pas`, `dites-moi`, `votre`, `vos`) vers leurs équivalents tutoyés. Préserve la capitalisation initiale (« Vous » → « Tu »).
  • Volontairement conservateur : on ne touche QUE des bigrammes sûrs (`vous + verbe`), jamais le « vous » seul, pour ne pas casser le pluriel légitime (« salut à vous deux »), les noms composés (« rendez-vous »), les citations ou « entre vous et moi ». Smoke 12/12 vert sur le bug exact + 7 variantes + 4 négatifs (pluriel/composé/citation/déjà tutoyé).
  • Log `[TU] vouvoiement converti vers tutoiement.` quand le scrub déclenche, pour observabilité.
Version 1.7.30
  • Filet musique élargi 🎵 — correctif du bug repéré sur « joue ensiferum wanderer » : `tool=aucun` côté LLM ET filet de sécurité v1.7.29 inactif (Amélia annonçait « D'accord, je vais jouer Ensiferum - Wanderer pour toi. Enjoy! 🎧 » mais rien ne partait au lecteur).
  • Cause : `_is_music_request` v1.7.29 exigeait un verbe d'action ET un indice musical explicite (`musique` / `chanson` / `morceau` / `youtube` / `spotify` / « comme musique » / quoted) ou un titre entre guillemets. La forme courte naturelle « joue <titre nu> » (« joue ensiferum wanderer », « joue Celtos d'Eluveitie », « lance Wonderwall d'Oasis », « démarre Stairway to Heaven ») n'avait aucun de ces marqueurs et passait à travers.
  • Fix : 4ème branche de détection « titre nu ». On accepte le verbe seul SI le complément ne matche pas une blacklist non-musicale élargie (`avec moi/toi/nous`, `à/aux + jeu/jeux/partie/dés/cartes/échecs/backgammon/domino/puzzle/quizz/chifoumi/tarot/poker`, `les/des + dés/cartes/échecs/jeux/dominos`, `la partie`, `du temps`, `une blague`, `un peu d'humour`) ET que le payload après le verbe (politesses `stp`/`svp`/`please`/`s'il te plaît`/`pour moi` retirées) fait 2-100 caractères. Smoke 14/14 vert : « joue ensiferum wanderer » ✓, « joue Celtos d'Eluveitie » ✓, « lance Wonderwall d'Oasis » ✓, « mets-moi du Daft Punk stp » ✓, « ajoute Bohemian Rhapsody à la file » ✓, mais aussi « joue avec moi à un jeu » → False ✓, « lance la partie » → False ✓, « joue aux échecs » → False ✓, « balance les dés » → False ✓.
Version 1.7.29
  • Filet de sécurité musique 🎵 (symétrique à météo / news). Avant : Amélia répondait souvent « Voilà, je joue X pour toi ! » SANS émettre le bloc `<<TOOL>> play_music`, donc rien ne partait jamais vers le lecteur. Désormais, dans `_run_chat_turn`, après tentative d'extraction du tool_call : si aucun tool n'a été émis ET que la question est clairement une demande musicale (`_is_music_request`), on extrait la requête (`_extract_music_query`) et on déclenche `play_music` nous-mêmes.
  • Détection prudente : `_is_music_request` exige un verbe (`joue/lance/mets/écoute/passe/démarre/balance/ajoute`) ET un indice musical (`musique`, `chanson`, `morceau`, `youtube`, `spotify`, « comme musique » / « en musique » / « sur youtube ») OU un titre quoté. Ainsi « joue avec moi à un jeu » et « lance la partie » sont correctement ignorés.
  • Patterns d'extraction (`_MUSIC_REQUEST_PATTERNS`) : capturent après le verbe, puis nettoient les suffixes parasites (`stp`, `svp`, `please`, `s'il te plaît`, `pour moi`, `comme musique`, `en musique`, `sur youtube/spotify`). Format `ajoute <X> à la file` également supporté. Smoke validé sur 11 cas : « Tu veux bien jouer ensiffeurm wanderer stp comme musique ? » → query `ensiffeurm wanderer`. Log `[PLAY]` quand le filet déclenche.
Version 1.7.28
  • Persistance des sorties de tools « verbeux » (📰 news, 🌤️ meteo, 📚 wiki, 🔎 web) dans la mémoire conversationnelle. Avant : seul le préambule textuel du LLM était sauvegardé via `save_message`, le contenu du tool était envoyé à Discord mais perdu côté contexte. Conséquence : quand l'utilisateur disait « tu en penses quoi de ces actus ? » au tour suivant, Amélia répondait « tu te réfères à quoi ? » faute de contexte. Désormais, dans `_execute_tool_call`, après envoi du résultat formaté, on persiste également ce texte (avec préfixe emoji) comme un message `assistant` dans `memory_store.save_message(channel_id, content, …)`. Amélia peut ainsi raisonner et donner son opinion sur les titres qu'elle vient elle-même de partager.
Version 1.7.27
  • Sécurité — rate-limiting des commandes admin par utilisateur (anti-compromission). Liste blanche de ~25 commandes sensibles (`reset`, `pause`, `resume`, `forget`, `autonomy`, `sleep`, `server`, `action`, `voice`, `music`, `deepwork`, `helper_run`/`helper_test`/`helper_brainstorm`/`helper_improve`/`helper_disable`/`helper_enable`/`helper_approve`/`helper_reject`/`helper_trigger`/`helper_diag`, `remind`, `cancel_reminder`, `play`, `game`, `ni`…). Plafond fenêtre glissante : **8 invocations / 60s par user**. Au-delà, message FR « Limite de commandes admin atteinte… réessaie dans ~Ns » (anti-spam : 1 alerte / 30s max). Implémenté via `bot.add_check(_admin_rate_limit_check)` + filtrage `commands.CheckFailure` dans `on_command_error`. Les commandes purement informationnelles (`status`, `self_status`, `helpers`, `version`, `state`, `history`…) restent libres.
  • Logs rotatifs — calibrage pour > 1 mois d'historique : `RotatingFileHandler` passe de 1 Mo × 3 (~4 Mo) à **5 Mo × 20 (~100 Mo)** par défaut. Nouveaux settings `log_max_bytes` (défaut 5 000 000, min 100 000) et `log_backup_count` (défaut 20, min 1) configurables dans `config.json`.
Version 1.7.26
  • Nouvelle commande `!self_status` (alias `!dashboard`, `!diag`) : dashboard texte unique avec version + boot/uptime, latence Ollama mesurée en live (ping `/api/tags`, ms), taille du fichier `memory.db`, nombre de helpers actifs + top 3 par success_count, délai depuis la dernière réorg mémoire, 3 prochains rappels en attente, et ligne mood courante. Captures défensives sur chaque section pour ne jamais bloquer la commande.
  • Champs `_boot_monotonic` et `_boot_wall` posés dans `__init__` du bot pour permettre un calcul d'uptime fiable indépendant de l'heure système.
Version 1.7.25
  • Anti-hallucination mentions tierces : nouvelle méthode `_scrub_unknown_relative_names` qui détecte les constructions « ton/ta/sa <relation> <Prénom> » (relations FR : frère, sœur, père, mère, papa, maman, fils, fille, cousin, oncle, tante, ami, copain, conjoint, mari, femme, voisin, collègue, chat, chien, prof, patron, coloc…) et retire le prénom si celui-ci n'est attesté NI dans les facts du speaker, NI dans ses profile_items, NI dans les usernames du salon. « Tu pourrais demander à ton frère Marc » → « tu pourrais demander à ton frère ». Hooké après `_scrub_wrong_vocative` dans la pipeline de génération.
  • Réflexion silencieuse périodique calibrée à **1×/h** : `inner_reflection_interval_minutes` passe de 75 à 60 par défaut (la mécanique existait déjà via `reflection_engine.reflect_once` + table `inner_thoughts` ; commande `!pensees`/`!thoughts`/`!pensées` déjà en place). Amélia relit le snapshot mémoire (contexte récent + facts + profile + summaries) et écrit une note privée sous un angle d'introspection rotatif.
  • Détection d'inactivité longue : nouveau setting `autonomy_long_silence_hours` (défaut **4h**). Quand personne ne parle pendant ≥ N heures dans le salon principal, Amélia envoie 1×/jour max un message de présence bref, réservé et attentionné (pas de « ça va ? » insistant, pas de mention du délai). Branche kickoff dédiée `reason="long_silence"` dans `_run_autonomy_turn`. `!autonomy status` affiche maintenant ce seuil.
Version 1.7.24
  • Capacité actus 📰 : nouveau tool `news` (flux RSS publics, sans clé). Pipeline complet : `fetch_news(topic, limit, source)` dans `news.py` (parser `xml.etree`, whitelist `lemonde` / `franceinfo` / `bbc`, dataclass `NewsReport.to_human()`).
  • Action `news` enregistrée dans `ActionManager` (handler `_action_news`), ajoutée à la whitelist par défaut dans `config.py`, documentée dans `actions.py` (schéma `{"topic":"<sujet ou vide>","limit":5,"source":""}` + règle actus CRUCIALE dans `tool_instructions`).
  • Commande `!news [sujet]` (alias `!actu`, `!actus`, `!actualites`, `!actualités`) pour requêter directement depuis Discord. Sans sujet → la une du jour ; avec sujet → filtre les titres + résumés contenant le sujet.
  • Détecteur `_is_news_question` + `_extract_news_topic` : si la question utilisateur est clairement une demande d'actu (« actualités », « actus », « news », « infos du jour », « quoi de neuf », « que se passe-t-il », « actu sur X »), Amélia est forcée d'utiliser le tool. Filet de sécurité : si le LLM oublie d'émettre le bloc `<<TOOL>>`, le bot le déclenche lui-même (avec topic extrait depuis « actu sur X »).
  • Suppression de `actualité du jour` du filet `_REALTIME_DATA_PATTERNS` : ce n'est plus une donnée hors de portée puisqu'on a maintenant un vrai tool. Plus jamais de « je n'ai pas d'accès temps réel pour les actus ».
  • Affichage Discord : emoji 📰 ajouté dans `verbose_actions` pour formater le résultat sans préfixe `(action news: ok)` technique.
  • Anti-hallucination news : `fetch_news` renvoie un `NewsReport` vide (et un message explicite) si rien ne matche le topic, plutôt que de remonter des items non pertinents qui passeraient pour du fact.
Version 1.7.23
  • Helpers vivants en chat naturel — quand tu écris une phrase qui contient une **phrase-déclencheuse** d'un helper actif, Amélia l'exécute toute seule (pas besoin de `!helper_run`). Match insensible à la casse et aux accents, sous-chaîne. Le résultat est posté dans le salon avec le trigger qui a matché.
  • À l'**approbation** d'un helper, une phrase-déclencheuse par défaut = `nom.replace('_', ' ')` est seedée automatiquement (ex : `roulette_russia` → trigger `roulette russia`). L'admin peut affiner via `!helper_trigger`.
  • Nouvelle commande `!helper_trigger <name> [list|add|remove|clear] "phrase"` : gère les phrases qui déclenchent un helper en chat naturel. Persisté dans la BDD (colonne `triggers` JSON).
  • Nouvelle commande `!helper_test <name>` : revalide AST strict + smoke run sandboxé avec args vides. Utile après `!helper_improve` ou pour vérifier qu'un helper qui marchait avant marche encore.
  • `!helper_show` affiche maintenant les **arguments attendus** (détectés depuis le code via `args.get("X", default)` et `args["X"]`) et la liste des phrases-déclencheuses.
  • **Auto-improve** : si un helper a accumulé ≥3 échecs cumulés (`failure_count`), Amélia planifie automatiquement un `!helper_improve` en arrière-plan à la prochaine exécution ratée — qu'elle soit déclenchée par `!helper_run` ou par un trigger naturel. Skippé si une proposition est déjà pending pour ce helper, ou si une session self_coding est déjà en cours.
  • Migration DB : nouvelle colonne `triggers` (TEXT JSON, défaut `[]`) sur `self_helpers`. Migration auto au démarrage.
Version 1.7.22
  • `!helper_run` accepte maintenant des formats simples en plus du JSON :
  • • `!helper_run roulette_russia` → args vides.
  • • `!helper_run roulette_russia 6` → si le code utilise une seule clé `args.get("guess")`, devient `{"guess": 6}` ; sinon `{"value": 6}`.
  • • `!helper_run roulette_russia guess=3` → `{"guess": 3}` (plusieurs paires possibles séparées par espaces).
  • • `!helper_run roulette_russia {"guess": 3}` → JSON brut (toujours supporté).
  • Le résultat affiche aussi `args=...` pour que tu voies comment ton input a été interprété. Plus besoin d'écrire du JSON pour un simple appel.
Version 1.7.21
  • Auto-codage : `parse_helper_response` filtre désormais les `expect_keys` clairement invalides (qwen y mettait régulièrement des conditions type `">=1"`, `"<=6"`, `"x > 0"` au lieu de noms de clés du dict de retour). On ne garde que les identifiants Python propres ; tout le reste est silencieusement ignoré. Plus de faux échec « clés manquantes ['>=1', '<=6'] ».
  • Prompts brainstorm + retry clarifiés : `expect_keys` = noms de clés du dict, pas des conditions.
Version 1.7.20
  • Logs plus parlants (niveau INFO, visibles dans `journalctl -u aurore.service -f`) :
  • • `[MSG]` à chaque message reçu : auteur, salon (`#nom` ou `DM`), longueur, émotion détectée, extrait (140 car).
  • • `[CMD]` quand c'est une commande `!xxx`, avec auteur et salon.
  • • `[SKIP]` quand le bot décide de ne PAS répondre (intent trivial / filtre `_should_reply`), avec la raison.
  • • `[BURST]` au flush du buffer multi-messages : combien de messages, qui.
  • • `[GEN]` après envoi de la réponse : longueur, voix on/off, tool éventuellement émis.
  • • `[TOOL]` avant et après chaque exécution d'action : nom, args, résultat (ok/ERREUR), longueur du détail.
  • • `[WX]` quand le filet de sécurité météo se déclenche (avec la ville extraite) ou quand il échoue à extraire.
  • • `[PLAY]` quand un message est intercepté par le module musique.
Version 1.7.19
  • Auto-codage : `!helper_brainstorm` retry automatique en cas d'échec. Si le 1er code généré rate la validation AST ou les tests sandbox, le bot relance UNE fois avec un prompt « fix » qui inclut l'erreur exacte et le code raté. Couvre les cas qwen typiques : indentation cassée (`if x:` sans bloc en dessous), import interdit, helper sans return dict, etc. Le rapport indique `(corrigé au 2e essai)` quand le rattrapage a marché.
Version 1.7.18
  • Auto-codage : `parse_helper_response` (self_coding.py) devient TOLÉRANT — accepte les blocs ```json ... ``` que qwen ajoute systématiquement, et répare les JSON où le champ `code` contient des retours à la ligne BRUTS (cas le plus fréquent d'échec silencieux du brainstorm). Du coup `!helper_brainstorm` aboutit beaucoup plus souvent à une vraie proposition.
  • Nouvelle commande `!helper_diag [sujet]` (alias `!diag_helper`) : lance un brainstorm en mode debug, montre la **réponse LLM brute** (en pièce jointe si > 1500 car), puis affiche le verdict du parser et de la sandbox étape par étape. À utiliser quand `!helper_brainstorm` retourne « ❌ Pas de helper retenu » sans qu'on sache pourquoi.
Version 1.7.17
  • Filet de sécurité météo : si la question utilisateur est clairement une demande météo (`_is_weather_question`) mais que le LLM n'a PAS émis de bloc `<<TOOL>> meteo`, le bot extrait la ville via `_extract_place_for_weather` (regex multi-formes : « météo de X », « météo X », « il fait quoi à X », « il pleut à X », « température de X », « prévisions à X » — supporte tirets et noms composés type Saint-Étienne / New York) et déclenche le tool lui-même. Plus jamais de « je n'ai pas de helper météo » alors que le tool existe.
  • Affichage Discord des résultats de tools amélioré : pour `meteo`, `research_wikipedia`, `research_public_web`, on affiche le détail directement avec un emoji (🌤️ / 📚 / 🔎) au lieu du préfixe technique « (action X: ok) ». Les actions internes (note_fact, add_reminder…) gardent un préfixe ✅/⚠️ + nom d'action pour que tu saches que c'est passé.
  • Nouvelle commande `!tools` (alias `!outils`) : liste tous les outils dispo et leurs commandes directes (`!meteo`, `!wiki`, `!web`). Pratique pour vérifier après un déploiement.
Version 1.7.16
  • Capacité météo réelle 🌤️ : nouveau tool `meteo` (open-meteo.com, gratuit, sans clé). Pipeline complet : `fetch_weather(place)` dans `web_research.py` (géocodage open-meteo + endpoint forecast `current=temperature_2m,apparent_temperature,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,is_day`, mapping français des `weather_code`, dataclass `WeatherReport.to_human()` qui formate « Météo à <ville> — <condition>, X°C — ressenti Y°C — vent Z km/h — humidité H% — nuages N% — obs. <iso> — source: open-meteo.com »).
  • Action `meteo` enregistrée dans `ActionManager` (handler `_action_meteo` dans `discord_bot.py`), ajoutée à la whitelist par défaut dans `config.py`, documentée dans `actions.py` (schéma `{"place":"<ville>"}` + règle météo CRUCIALE dans `tool_instructions`).
  • Commande `!meteo <ville>` (alias `!météo`, `!weather`) pour requêter directement depuis Discord sans passer par le LLM.
  • `SYSTEM_PROMPT` mis à jour : Amélia sait qu'elle a maintenant un vrai tool météo et qu'elle DOIT l'utiliser au lieu d'inventer. Le filet anti-hallucination temps réel garde les autres domaines (cours, scores, trafic, news), mais autorise désormais la météo via le tool.
  • `_needs_realtime_data` scindé : météo extraite vers un détecteur dédié `_is_weather_question` qui injecte un hint impératif forçant le bloc `<<TOOL>> {"action":"meteo",...} <<END>>` à la fin de la réponse. Le hint anti-hallucination existant continue à s'appliquer aux autres données temps réel (interdit « j'ai vérifié », chiffres inventés, faux théâtre « !Recherche en cours »).
Version 1.7.15
  • Garde-fou runtime anti-hallucination temps réel : le seul prompt ne suffisait pas (Amélia s'est excusée puis a recommencé à inventer la météo de Perpignan dans le message suivant). Nouveau détecteur `_needs_realtime_data` (regex sur météo/température/prévision/pluie/neige/orage/degrés, heure dans une autre ville, cours bitcoin/btc/eth/euro/dollar/bourse, score PSG/OM/NBA/coupe en direct, trafic/prochain train/bus/métro/RER, actualités du jour). Quand il déclenche, on injecte un `extra_instructions` impératif qui interdit explicitement « j'ai vérifié », « la météo actuelle est », « il fait X°C », ainsi que le faux théâtre « !Recherche en cours », et impose la phrase de refus type avec proposition de demande DM pour créer un helper dédié.
Version 1.7.14
  • Anti-hallucination temps réel : nouvelle section `DONNÉES TEMPS RÉEL` dans `SYSTEM_PROMPT` (`agent.py`). Amélia avait inventé une météo pour Gray (« 23°C, ensoleillé, humidité basse ») alors qu'aucun helper météo n'existe et qu'elle n'a pas d'accès web temps réel. Le prompt liste désormais explicitement les domaines interdits à la fabulation (météo, cours, scores, news, trafic, horaires, géoloc, heure à l'étranger…), interdit les fausses formules d'autorité (« j'ai vérifié », « selon les sources disponibles »), et oblige à reconnaître l'invention si on lui demande ses sources. La sortie attendue est : « Je n'ai pas de helper pour ça. Si tu veux, je peux demander en DM la permission d'en créer un. »
Version 1.7.13
  • Messages privés activés : nouvel intent `dm_messages = True`, nouveau setting `dm_enabled` (défaut `True`) et `admin_user_id` (lu via `ADMIN_USER_ID` env ou `admin_user_id` dans `config.json`). Le gate `on_message` accepte désormais les `discord.DMChannel` ; si `admin_user_id` est défini, seuls les DMs venant de cet utilisateur passent (filtrage anti-spam).
  • Nouvelle méthode bot `send_admin_dm(text)` : ouvre/réutilise le DMChannel de l'admin et lui envoie un message (tronqué à 2000 chars). Échoue silencieusement si l'admin a fermé ses DMs ou si `admin_user_id` n'est pas configuré. Sera utilisée notamment pour les demandes de permission d'auto-codage réseau/système.
  • SYSTEM_PROMPT élargi (`agent.py`) : Amélia peut désormais proposer des helpers réseau/système (HTTP, fichiers, sous-process), MAIS uniquement après une demande de permission explicite envoyée à Yann en DM (format Objectif / Méthode / Ressource / Risques / Tu autorises ?). Silence ou refus = pas d'autorisation. Les helpers purs (calcul, texte, dates) continuent à passer directement par le pipeline de validation habituel sans demande préalable.
Version 1.7.12
  • Validation admin de l'auto-codage : un helper accepté par les tests ne s'active plus tout seul. Il devient une **proposition** que je publie dans le salon principal au format `🧠 Amélia propose une amélioration : Objectif / Méthode / Plan / Risques / Valider ? ✅ Oui / ❌ Non` (avec le code en bloc séparé). Tu gardes le contrôle.
  • Nouvelle table `self_helper_proposals` (id, helper_name, kind brainstorm/improve, purpose, signature, code, plan, risks, status pending/approved/rejected, message_id UNIQUE, channel_id, created_at, decided_at, decided_by, decision_note) + 2 index (status, message_id). Ajout colonne `score INTEGER DEFAULT 0` sur `self_helpers` (récompense cumulée).
  • Validation par réaction `on_raw_reaction_add` : ✅/❌ déclenchent l'application si le réacteur a la permission `manage_channels`. Les bots et les autres emojis sont ignorés. Robuste au reboot (lookup par `message_id` en BDD, pas de `wait_for`).
  • 3 nouvelles commandes : `!helper_pending` (alias `pending_helpers`, `helper_inbox`) liste les propositions en attente ; `!helper_approve <name|id>` et `!helper_reject <name|id>` (admin only) appliquent la décision via commande, sans avoir à cliquer une réaction.
  • Système de récompense : approbation → +5 au `score` du helper + nudge mood `enthousiasme` (joie+énergie). Refus → -2 au score si helper existant + log dans `last_error` + nudge `tristesse`. La sortie `!helpers` affiche maintenant `score=N` et le nombre de propositions en attente.
  • Le LLM doit fournir `plan` (3 étapes) et `risks` (limites) dans le JSON. `HelperCandidate` gagne deux champs `plan: list[str]` et `risks: list[str]` ; le parser tolère leur absence (listes vides).
  • Sandbox inchangée : tests sandbox (timeout 5 s, AST whitelist) restent l'antichambre — Amélia ne propose à validation **que** ce qui passe déjà ses propres tests, donc elle ne te dérange pas avec du code cassé.
  • Section AUTO-CODAGE ajoutée au SYSTEM_PROMPT (`agent.py`) : Amélia sait désormais qu'elle peut écrire des helpers, que tout passe par validation admin, et qu'elle ne doit JAMAIS dire « j'ai créé X » avant qu'une proposition ne soit approuvée — toujours « j'ai proposé X, en attente de validation ».
Version 1.7.11
  • Auto-codage : Amélia peut désormais s'écrire ses propres helpers Python. Nouveau module `self_coding.py` : validateur AST stricte (whitelist d'imports : math, re, json, datetime, statistics, random, collections, itertools, functools, textwrap, unicodedata, decimal, fractions, string, typing ; bannit `eval/exec/open/__import__/getattr/setattr/delattr/hasattr/breakpoint`, dunders, `global/nonlocal/with/async/yield`) ; sandbox via subprocess `python -I -c` avec timeout 5 s, JSON in/out via stdin/stdout, fonction pure `helper(args: dict) -> dict`.
  • Nouvelle table `self_helpers` (id, name UNIQUE, purpose, signature, code, version, status active/disabled/broken, success_count, failure_count, last_used_at, last_error, timestamps) + index sur status. Méthodes BDD : `upsert_self_helper` (incrémente version sur update), `fetch_self_helpers`, `fetch_self_helper`, `record_self_helper_run`, `set_self_helper_status`, `delete_self_helper`.
  • Boucle `_self_coding_loop` (resilient task `amelia-self-coding`) : check toutes les 5 min, déclenche au plus une session par jour entre 21h et 23h SI aucun message utilisateur depuis ≥30 min. La session alterne automatiquement : `brainstorm` (helper neuf) tant qu'il y a <3 helpers, sinon 60% de chance de prioriser `improve` du helper le plus en échec.
  • Pipeline d'évaluation : LLM produit un bloc Markdown structuré → `parse_helper_response` → `evaluate_candidate` (validation AST + exécution sandbox des tests fournis) → `upsert_self_helper` uniquement si tout passe.
  • 6 commandes Discord : `!helpers` (alias `mes_helpers`, `self_code`) liste les helpers ; `!helper_show <name>` (alias `helper_code`, `show_helper`) affiche le code ; `!helper_run <name> <json>` (alias `run_helper`) exécute le helper en sandbox et enregistre succès/échec ; `!helper_brainstorm [sujet]` (alias `new_helper`) force la création ; `!helper_improve <name>` (alias `improve_helper`) demande une nouvelle version ; `!helper_disable` / `!helper_enable` togglent le statut.
  • Setting `self_coding_enabled` (défaut activé) lu via `getattr(settings, ..., True)` pour permettre désactivation sans modifier la config.
Version 1.7.10
  • Humeur (`mood.py`) plus vivante : l'humeur restait collée à `calme (0.55/0.6/0.5)` car les nudges (+0.08) étaient trop faibles vs un decay qui ramenait pile à 0.55. Maintenant : nudges renforcés (joie +0.15, enthousiasme +0.18 mood / +0.10 intérêt, colère −0.18, tristesse −0.15, surprise +0.12 intérêt), `interest +0.14` par message au lieu de +0.08.
  • Baseline circadienne : la cible du decay n'est plus 0.55 fixe mais varie selon l'heure (matin 0.60/0.65/0.60 ; après-midi 0.62/0.62/0.58 ; soir 0.58/0.50/0.55 ; nuit 0.45/0.30/0.35), donc l'humeur naturelle d'Amélia change au cours de la journée.
  • Souffle aléatoire (`_apply_breath`) : à chaque snapshot (au plus 1×/30 s), un drift ±0.025 sur mood/énergie/intérêt pour que les valeurs respirent même hors événement.
  • Decay raccourci de 30 min → 20 min : événements ressentis plus longtemps mais la baseline reprend la main plus vite.
  • Deux nouveaux labels : `songeuse` (mood<0.45 et intérêt<0.45 : style introspectif/rêveur) et `vive` (énergie>0.78 et mood>0.55 : style énergique, formulations qui claquent). `_label_for` prend maintenant aussi `interest` en argument.
Version 1.7.9
  • Hotfix démarrage : `CommandRegistrationError: The alias moi is already an existing command or alias`. La nouvelle commande `!moi` (1.7.8) entrait en collision avec l'alias `moi` de la commande `!about_me` existante. Renommage : `!moi` → `!identite` (aliases : identité, qui_je_suis, mon_identite), `!moi_oublie` → `!identite_oublie` (aliases : identite_retracte, oublie_trait), `!moi_reflechis` → `!identite_reflechis` (aliases : introspection, introspecte). `!about_me` (alias `moi`) reste intact pour le profil utilisateur.
Version 1.7.8
  • Construction progressive d'identité propre : nouvelle table `self_traits` (kind preference/aversion/valeur/curiosite/ton, stages essai → tendance → affirmé, observation_count, confidence). Promotion automatique : ≥3 obs OU conf≥0.7 → tendance ; ≥6 obs ET conf≥0.75 → affirmé. Jamais de rétrogradation auto.
  • Module `self_identity.py` : prompt d'introspection anti-appropriation (interdit explicitement de s'attribuer les goûts d'un utilisateur), parser strict (confiance min 0.45, valeurs génériques rejetées, regex bloquant `j'aime le death/black/heavy/metal/reggae/rap/jazz/manga/anime/foot…` quand ça vient d'un user).
  • `agent.py` : nouveau paramètre `self_traits_block` traversant `generate_response` → `_build_prompt`. Section « Ton identité personnelle » injectée dans le system prompt, légende des stages (• affirmé, ~ tendance, ? essai). Règle SYSTEM_PROMPT « IDENTITÉ DISTINCTE » assouplie : Amélia PEUT exprimer un goût SI il vient de cette section, en respectant le stage (« je crois que… » / « je remarque que… » / « j'aime »).
  • `discord_bot.py` : `_build_memory_snapshot` charge `self_traits` (limit 20). `_generate_non_repetitive` passe `self_traits_block=format_traits_for_prompt(...)`. Méthode `_run_self_identity_reflection` (snapshot + prompt + parse + upsert). Boucle `_inner_reflection_loop` déclenche l'introspection identitaire 1 itération sur 3 (compteur `_self_identity_loop_counter`).
  • Commandes : `!identite` (aliases : identité, qui_je_suis, mon_identite) liste les traits par stage et catégorie ; `!identite_reflechis` (alias : introspection) force une introspection ; `!identite_oublie <kind> <label>` rétracte un trait. (Renommées en 1.7.9 pour éviter une collision avec l'alias `moi` de `!about_me`.)
  • Anti-spam intro publique : le bot ré-envoyait le message de présentation à CHAQUE reboot. Nouveau marqueur persistant `data/public_intro_sent.json` (signature SHA256 de `channel_id|intro_text`). `_load_public_intro_marker` au démarrage, `_persist_public_intro_marker` après envoi. Si le texte d'intro ou le salon change, la signature diffère et l'intro sera renvoyée une seule fois.
Version 1.7.7
  • Travail nocturne automatique : nouvelle boucle `_deepwork_auto_loop` qui, si `deep_work_auto_enabled=true`, lance `_run_deep_work` 1×/jour à `deep_work_auto_hour` (défaut 3h) sur tous les salons ayant ≥ `deep_work_auto_min_messages` messages utilisateurs dans les 7 derniers jours. Itère salon par salon, consolide un rapport unique.
  • Le matin à `autonomy_morning_hour`, le rapport consolidé (synthèse par salon + total faits/personnes) est posté dans le salon principal via `_send_text` (découpage 1900 chars). Indépendant de `enable_autonomy` et du mode sommeil — Amélia profite que personne ne parle pour bosser.
  • Nouveau helper `memory_store.active_channel_ids(since_hours, min_messages)` qui requête `memory` avec `type='user'` et `timestamp >= datetime('now', '-N hours')`.
Version 1.7.6
  • Anti-sycophantie : Amélia validait des affirmations absurdes par politesse (« il vaut mieux mettre les chaussures avant les chaussettes »). Nouvelle section ESPRIT CRITIQUE dans SYSTEM_PROMPT : ne pas valider de raisonnement faux, ne pas inventer de justification, dire poliment quand c'est l'inverse.
  • Anti-hallucination dates : nouvelle section FAITS DATÉS / TITRES D'ŒUVRES interdisant explicitement « sorti en {année courante} » par défaut, et imposant research_public_web ou « je ne suis pas sûre, je peux vérifier ».
Version 1.7.5
  • Garde-fou désobéissance : Amélia avait répondu « je vais faire comme si tu m'autorisais » et lancé research_wikipedia malgré un refus explicite. Nouveau détecteur runtime `_user_vetoes_action` (regex sur « tu n'as pas le droit », « je t'interdis », « arrête », « non + cherche/wikipedia », « respecte ton protocole », etc.). Si déclenché, le tool_call extrait est annulé avant exécution et une note transparente est ajoutée à la réponse.
  • Section RESPECT DES REFUS ajoutée au SYSTEM_PROMPT (agent.py) : « Si l'utilisateur dit non, tu OBÉIS immédiatement. Tu ne fais JAMAIS comme si il avait autorisé. Tu ne ruses pas, tu ne contournes pas. »
Version 1.7.4
  • Anti-hallucination factuelle : nouveau détecteur `_needs_factual_research` (regex sur « meilleur film de l'année », « cherche/trouve/recommande X », « actualité », « palmarès », « date de sortie ») qui injecte une consigne forte exigeant l'usage de research_public_web ou research_wikipedia avant de répondre.
  • Renforcement des `tool_instructions` (actions.py) : règle « factuelle CRUCIALE » imposant la recherche pour toute recommandation/classement/film/livre/album/jeu de l'année, sortie ou actualité, sinon répondre honnêtement « je n'ai pas l'info » au lieu d'inventer un titre/date/fait.
Version 1.7.3
  • Fix hallucination de date : Amélia se croyait en 2023. Renforcement du system prompt (« tu ignores totalement la date d'entraînement, ta date de référence est UNIQUEMENT la valeur ci-dessus »).
  • Garde-fou runtime `_fix_obvious_wrong_year` dans _finalize_response : détecte les patterns « en 2023 » / « nous sommes en 2024 » / « année 2023 » et remplace par l'année système courante. Préserve les années futures (projets) et les mentions correctes.
Version 1.7.2
  • Parallélisation de !deepwork : nouveau réglage `deep_work_concurrency` (défaut 2, max 8) qui gère un asyncio.Semaphore. Les utilisateurs sont traités en parallèle avec un lock sur le rapport. Nécessite OLLAMA_NUM_PARALLEL >= concurrency côté serveur Ollama pour que le gain soit réel.
Version 1.7.1
  • Fix confusion d'identité : Amélia appelait Tsukinosushi par le prénom « Yann » d'un autre user. Ajout d'une règle dure dans le style_hint (« utilise UNIQUEMENT '{speaker_label}'. N'utilise JAMAIS un autre prénom pour t'adresser à la personne courante »).
  • Garde-fou runtime `_scrub_wrong_vocative` : compile la liste des autres usernames du salon et retire les vocatifs erronés (« Yann, ... » / « ..., Yann. ») dans la réponse avant envoi, sans toucher aux mentions <@id>.
  • Log explicite quand un vocatif erroné est scrubé (debug).
Version 1.7.0
  • Nouveau module learning_advanced.py : catégorisation des intérêts en 12 grandes cases (work, hobbies, food, travel, sport, books, films, family, music, gaming, daily_routine, pets) + pool de questions ouvertes par catégorie.
  • Curiosity outreach autonome : nouvelle initiative dans la boucle d'autonomie. Amélia repère un utilisateur actif, identifie une catégorie qu'elle ne connaît pas encore (via profile_items + facts), et lui pose UNE question ouverte ciblée (générée par LLM, mention <@uid>). Cooldown 24h par défaut, rotation pour ne pas reposer la même catégorie deux fois de suite.
  • Détecteur de follow-up : quand quelqu'un évoque un événement futur le concernant (« demain je passe un entretien », « ce soir je sors avec X », « la semaine prochaine je commence Y »), Amélia programme un rappel « FOLLOWUP » qui, le moment venu, déclenche une question naturelle générée à la volée par le LLM (« alors, ton entretien ? »).
  • Extraction de faits par LLM (opt-in, désactivée par défaut) : la boucle d'auto-apprentissage demande périodiquement au modèle d'extraire jusqu'à 5 faits durables au format JSON depuis les messages récents d'un utilisateur. Validé strictement (catégorie, label, value, confidence ≥ 0.6) puis stocké dans profile_items avec source="llm_extraction".
  • Nouveaux settings : enable_curiosity_outreach (true), curiosity_outreach_min_hours (24), enable_followup_detection (true), enable_llm_fact_extraction (false), llm_fact_extraction_interval_minutes (180).
  • _fire_reminder dispatche maintenant les rappels [FOLLOWUP] vers une logique dédiée (génération LLM contextuelle) au lieu d'un rappel sec.
Version 1.6.4
  • Fix extraction de faits : « j'aime l'idée. Dis moi, comment tu fais ? » ne produit plus un fait pourri (« aime L'idée. Dit moi… »). Le fragment est désormais tronqué à la première frontière de phrase (. ! ? …) et coupé sur les connecteurs de digression (« , dis-moi », « , comment », « , peux-tu »…).
  • Filtres ajoutés : refus si le fragment contient un point d'interrogation, s'il dépasse 160 caractères, ou s'il est trivial (« l'idée », « ça », « ce truc », « ouais », etc.).
  • Cleanup utilitaire centralisé dans `_cleanup_fact` (learning.py) — bénéficie aux faits explicites, préférences et objectifs.
Version 1.6.3
  • Curiosité élargie : nouvelle section CURIOSITÉ ET DÉCOUVERTE DE L'UTILISATEUR dans le system prompt — Amélia varie les angles (travail, jeux, lectures, films, sport, voyages, cuisine, projets, agacements du quotidien) au lieu de rester fixée sur un seul thème.
  • Une seule question par message, sincère, qui s'inscrit dans le fil. Pas de questions de remplissage.
  • Anti-fixation musique : si Amélia vient de proposer de la musique récemment, le style_hint l'incite à orienter la prochaine question vers un autre centre d'intérêt.
  • Règle ajoutée au prompt utilisateur : varier les sujets sur plusieurs messages, ne pas redemander une info déjà mémorisée.
Version 1.6.2
  • Fix tool-use musique : Amélia choisissait add_reminder pour des demandes de lecture (« joue Celtos d'Eluveitie »). Le prompt outils précise maintenant que TOUTE demande de jouer/lancer/mettre/écouter/ajouter une musique passe par play_music, jamais add_reminder.
  • Le lecteur enchaîne déjà nativement : si une musique joue, la nouvelle va en file d'attente — pas besoin d'action séparée.
  • Documentation explicite : add_reminder ne sert qu'aux rappels datés, jamais aux ajouts de musique.
Version 1.6.1
  • Fix : Amélia ne se re-présente plus à chaque message (« Je suis Amélia. … »). Règle explicite ajoutée au prompt + post-filtre runtime qui retire le préfixe d'auto-présentation quand il y a déjà du contenu derrière.
  • L'auto-présentation reste autorisée si la réponse est très courte ou s'il s'agit d'une vraie question d'identité.
Version 1.6.0
  • Apprentissage par déduction : Amélia repère désormais les sujets dont une personne parle souvent (≥4 occurrences sur 14 jours) et les note comme intérêts présumés (catégorie inferred_interest, priorité 3, source interest_inference).
  • Déductions stockées avec une formulation prudente (« à confirmer ») pour qu'Amélia ne les présente jamais comme des certitudes.
  • Nouvelle requête mémoire : recurring_keywords_per_user (filtrée par user_id, fenêtre 14 jours par défaut).
  • Boucle d'auto-apprentissage : après chaque tour d'auto-évolution, Amélia déclenche aussi l'inférence d'intérêts pour les utilisateurs actifs.
  • Nouvelle commande !about_me (alias !aboutme, !moi) : Amélia montre à l'utilisateur ce qu'elle a retenu de lui (profil explicite, intérêts déduits, faits, axes d'évolution).
  • Filtrage anti-doublons : un mot déjà présent dans le profil n'est pas re-déduit.
  • Blacklist supplémentaire de filler conversationnel (« amelia », « salut », « merci », « moment », etc.) pour des déductions plus pertinentes.
Version 1.5.1
  • Fin de phrase : num_predict cap relevé (512 → 1024 tokens) + plancher généreux (192) pour ne plus couper en plein milieu.
  • num_predict défini aussi quand aucune cible n'est passée, basé sur response_max_chars.
  • Troncature plus propre : coupe à la fin d'une phrase (. ! ?) si possible plutôt qu'à un mot.
  • response_max_chars passé de 900 à 1400 caractères.
  • Règle explicite ajoutée au prompt : « termine toujours tes phrases proprement, conclus en avance si la limite approche ».
Version 1.5.0
  • Post-filtre identité : nettoyage automatique des phrases d'usurpation (« créée par Alibaba Cloud », « modèle Qwen », etc.) avant envoi.
  • Fallback « Je suis Amélia. » si la réponse était essentiellement une usurpation d'identité.
  • Autonomie : anniversaire de mémoire — Amélia salue les jalons de 1, 3, 6, 12, 24 mois passés avec un utilisateur (1 fois par jalon par personne, ping <@uid>).
  • Autonomie : mood-shift comment — détection des bascules d'ambiance globale du salon sur 24h vs 72h, commentaire posé max 1 fois / 3 jours.
  • memory.per_user_stats expose désormais first_seen pour les jalons d'historique.
Version 1.4.4
  • Identité fortifiée : bloc IDENTITÉ NON NÉGOCIABLE dans le system prompt — Amélia ne se présente plus jamais comme « créée par Alibaba Cloud » ou « modèle Qwen ».
  • Anti-hallucination de prénoms : interdiction d'inventer des utilisateurs absents du contexte mémoire (Lucie, Mathis, etc.).
  • Ajout de règles dans le prompt utilisateur pour renforcer ces deux points à chaque génération.
Version 1.4.3
  • Mentions Discord : Amélia peut désormais ping l'interlocuteur courant (<@id> proposé dans le style_hint).
  • Si un message contient des mentions d'autres utilisateurs, leurs tags <@id> sont injectés dans le prompt — l'IA peut les ping si on lui demande d'aller leur parler.
  • Silent check-in et rappel d'objectif : commencent désormais par mentionner la personne concernée pour qu'elle reçoive la notif.
Version 1.4.2
  • Audit confusion d'utilisateurs : rappel d'objectif filtré par user_id du goal et adressé nommément.
  • Check-in silencieux : prompt explicitement adressé à la personne ciblée pour éviter le mélange.
Version 1.4.1
  • Identification stricte de l'interlocuteur : le message courant est préfixé par [pseudo] et l'IA est explicitement guidée pour ne plus confondre les utilisateurs entre eux.
  • Propagation du display_name jusqu'au prompt LLM (texte + voix).
Version 1.4.0
  • Multimodal : compréhension des images via modèle vision Ollama (llava/qwen2.5-vl).
  • Multimodal : transcription des audios attachés (mp3, ogg, wav, m4a) via faster-whisper.
  • Multimodal : transcription de la piste audio des vidéos courtes (mp4, mov, webm).
  • Système de version + commande !version pour suivre les évolutions.
Version 1.3.0
  • Mode salon public : intro one-shot, cooldowns anti-spam, confidentialité par utilisateur.
  • Commandes !privacy, !forget_me, !pause, !resume.
  • Filtre de contenu sensible (public_channel_blocklist).
  • Cap longueur réponse en mode public + mode mention-only optionnel.
  • Whitelist multi-salons via extra_allowed_channel_ids (préserve le salon privé).
Version 1.2.0
  • Mode écoute longue (regroupement de messages rapprochés).
  • Détection des questions rhétoriques.
  • Continuité légère (rappels mémoire au plus 1×/jour).
  • Anti-surproposition de musique.
  • Fiabilité Ollama (retry + backoff + circuit-breaker + healthcheck).
  • Fiabilité SQLite (WAL + busy_timeout) et Piper (circuit-breaker TTS).
Version 1.1.0
  • Night Invasion : agent autonome (FSM, OCR, apprentissage SQLite).
  • Tool-use LLM (<<TOOL>> ... <<END>>) avec whitelist d'actions.
  • Mémoire affective : moments émotionnels rappelés contextuellement.
  • Rappels programmés en langage naturel.
  • Résumé quotidien + journal intérieur.
  • Recherche mémoire FTS5 si dispo.
Version 1.0.0
  • Première version stable : Discord + Ollama + SQLite.
  • Voix Piper, écoute Vosk, observation locale, mini-jeux, autonomie.
Application Windows · 29 versions

Companion : présence et outils sur votre PC.

Toutes les versions du client Windows. Les notes de préparation ne valent pas publication d'une mise à jour.

Version 6.1.5Dernière entrée
  • Notes 6.1.5 complétées le 12 septembre 2026. L'installateur signé publié le 9 août reste le téléchargement public. Les quatre corrections suivantes étaient déjà consignées à cette date ; le complément du 12 septembre décrit un portable de validation distinct, sans remplacer cet installateur.
  • Les erreurs distinguent désormais une panne réelle, une réponse refusée, une requête déjà en cours et un serveur trop lent.
  • Le contexte rapide ne réinjecte plus le message courant ni les demandes restées sans réponse après un échec.
  • L'anti-répétition cible les vraies rengaines et ne bloque plus une tournure ordinaire apparue une seule fois.
  • La lecture PDF embarquée utilise la version corrigée de pypdf 6.15.0.
  • Ajout au portable de validation du 12 septembre : le chat attend jusqu'à 915 secondes pour recevoir la réponse ou l'erreur finale du serveur, dont le budget est de 900 secondes avec GPT-OSS. L'installateur public du 9 août conserve 105 secondes. Les autres opérations conservent leurs délais propres ; ce plafond n'est pas une promesse de temps de réponse.
  • Le Companion accède au moteur GPT-OSS d'Amélia par le serveur existant : il ne télécharge pas le modèle et ne transforme pas le PC client en serveur d'inférence. Cette adaptation conserve les identités, autorisations et protocoles existants ; elle n'accorde aucun nouvel accès implicite aux souvenirs ou aux actions Windows.
  • Ajout du 12 septembre au build Windows : les bibliothèques système ICU sont résolues avant celles d'outils comme Poppler ou Conda. Cela évite d'embarquer une DLL incompatible avec Qt ; l'ordre de recherche est modifié uniquement dans le processus de construction, sans changer l'environnement Windows de l'utilisateur.
  • Le portable 6.1.5 reconstruit a été vérifié par taille, SHA-256 et commande --version. Il reste non signé, non installé et non publié ; l'installation 6.1.4 reste intacte et conserve son ancien délai de chat de 105 secondes. Le contrôle de version ne qualifie pas à lui seul toutes les fonctions graphiques ni une conversation réelle du client.
  • Les numéros Amélia 2.315.0 et Companion 6.1.5 décrivent deux composants distincts. L'ancien installateur public et le portable de validation portent encore le même numéro client, mais ont des empreintes et des contenus différents. Les ajouts du 12 septembre devront recevoir un nouveau numéro avant une future publication ; ces notes ne remplacent ni installateur ni mise à jour automatique.
Voir les 28 versions précédentes
Version 6.1.4
  • La détection, l'attente et le focus des fenêtres disposent maintenant d'un backend Win32 natif sans pywinauto ni COM.
  • Le parcours Bloc-notes ne peut plus être bloqué par l'initialisation d'UI Automation dans l'exécutable autonome.
  • UIA reste réservée aux contrôles accessibles nommés qui en ont réellement besoin.
Version 6.1.3
  • UI Automation et COM sont désormais des dépendances obligatoires de la release Windows, contrôlées avant le build.
  • PyInstaller embarque explicitement les backends dynamiques pywinauto/comtypes et refuse un rapport signalant leur absence.
  • La détection et le focus des fenêtres utilisent automatiquement le backend Win32 si UIA ne peut pas démarrer.
Version 6.1.2
  • Overlay Windows recomposé sur la maquette du portail public : portrait, hero éditorial, présence et composer intégré.
  • Carte opaque et agrandie pour préserver la lisibilité, avec états de présence en ligne, hors ligne et sommeil.
  • Portrait officiel embarqué dans l'exécutable Windows pour conserver la même identité hors du dépôt.
Version 6.1.1
  • Release Authenticode auto-signée avec identité publique épinglée ; la clé privée reste non exportable dans le magasin Windows de la machine de release.
  • L'installateur propose, sans la cocher, la confiance locale envers ce certificat et la retire à la désinstallation.
  • Le portail publie le certificat public et explique honnêtement pourquoi SmartScreen peut encore avertir lors de la première installation.
Version 6.1.0
  • Mode appel intégré au Companion : dictée Windows par tours, réponse vocale système et arrêt visible du microphone.
  • Sessions coopératives sur un document choisi ou une fenêtre confirmée, avec contexte éphémère et propositions avant toute action Windows.
  • Présence proactive locale anti-spam : Amélia reconnaît le type d'application et propose de partager, garder le contexte ou ignorer sans capture cachée.
Version 6.0.1
  • Saisie Windows x64 réparée : la structure native complète INPUT est maintenant transmise à SendInput.
  • Portail public Companion restauré avec son identité visuelle, son histoire et son changelog versionné.
  • Administration déplacée sous /companion/admin et téléchargement public fermé sans preuve de release signée.
Version 6.0.0
  • Routage local prioritaire et plans Windows confirmés, avec formulations de dictée, attente et ciblage réels de la fenêtre.
  • Stop Amélia persistant partagé avec le MCP, reprise manuelle bornée et annulation interruptible des plans.
  • Journal d'activité local limité à 50 descriptions, sans texte saisi, contenu de fichier ni capture d'écran.
Version 5.0.0
  • Enrôlement v2, identité révocable par PC, rotation sûre et actions distantes ciblées avec claim atomique.
  • Secrets Windows protégés par DPAPI, assistant de première exécution et contrôles de vie privée négatifs par défaut.
  • Transport, Home Assistant, MCP et mises à jour signées durcis ; build hashé, SBOM et CI Windows reproductible.
Version 4.1.1
  • Diagnostic de démarrage lisible au lieu d'un arrêt silencieux.
Version 4.1.0
  • Courses, focus, réunions, téléchargements, diagnostic et profils locaux activables depuis le chat.
Version 4.0.0
  • Connexions quotidiennes : agenda ICS, presse-papiers volontaire, documents choisis et commandes Home Assistant limitées ;
  • brouillons, Discord, rappels et contexte de travail sont réunis dans le compagnon.
Version 3.0.2
  • Correctif compréhension des demandes naturelles : « cherche-moi / cherche moi une recette »
  • retire désormais le pronom de politesse avant la recherche et propose une base pour les currys trop vagues.
Version 3.0.1
  • Correctif recherche : le compagnon utilise le flux RSS Bing quand DuckDuckGo présente un CAPTCHA,
  • ce qui rétablit les recettes, comparaisons et sources lisibles.
Version 3.0.0
  • Navigateur assisté intégré avec sources, mode lecture, comparaison et synthèse sourcée sur demande ;
  • Centre Amélia (Aujourd'hui, mémoire éditable, routines, santé) et dictée push-to-talk Windows.
  • Les plans locaux peuvent maintenant ouvrir Bloc-notes puis saisir un texte après confirmation.
Version 2.1.0
  • Recherche web lisible et contrôlée ; rappels, tâches et contexte de travail persistants localement,
  • avec bilan et alertes sans synchronisation serveur.
Version 2.0.0
  • Commandes musicales naturelles, choix explicite du fournisseur, favoris et recherches récentes locaux ;
  • cartes de confirmation avant les actions locales, recherches et brouillons.
Version 1.9.0
  • Lecteur YouTube Music et Spotify directement intégré au compagnon : aucune ouverture de Chrome
  • ni d'onglet externe ; session du fournisseur conservée localement.
Version 1.8.0
  • Panneau Musique intégré : recherche YouTube Music ou Spotify dans Chrome, puis commandes Windows
  • lecture/pause, piste précédente et suivante.
Version 1.7.0
  • Parcours hôtelier guidé : questions sur dates, budget et préférences avant une recherche comparative ;
  • aucune réservation, paiement ou envoi de courriel n'est possible.
Version 1.6.0
  • Plans locaux composés : Chrome ouvre directement une recherche et peut préparer un brouillon de courriel
  • prérempli, sans destinataire ni envoi automatique.
Version 1.5.2
  • Messages du compagnon non tronqués : panneau de discussion agrandi, texte sélectionnable et défilement visible.
Version 1.5.1
  • Contraste corrigé sur Windows clair : fond sombre opaque, texte blanc et champs/boutons lisibles,
  • avec palette Qt de secours pour tous les dialogues.
Version 1.5.0
  • Actions utiles depuis le chat local : recherche et navigation web, saisie, touches et boutons Windows nommés
  • après activation unique de l'automatisation ; nouvel outil MCP desktop_open_url.
Version 1.4.0
  • Discussion directement intégrée à l'overlay et ouverture locale vérifiée de Chrome, Bloc-notes,
  • l'Explorateur ou la Calculatrice ; Amélia ne confirme plus une action PC non exécutée.
Version 1.3.0
  • Mise à jour depuis le compagnon : téléchargement HTTPS authentifié,
  • vérification SHA-256, puis lancement de l'installeur après accord explicite.
Version 1.2.0
  • Discussion Amélia dans le compagnon avec identité locale ou Discord autorisée,
  • et connecteur MCP local à commandes sémantiques confirmées (sans clics X/Y).
Version 1.1.0
  • Page de réglages en onglets, portail Amélia et actions à la demande
  • (capture, fichier texte, souris/clavier), toutes confirmées localement.
Version 1.0.0
  • Première version : overlay de présence permanent (avatar, humeur, bulle), remontée de la fenêtre active vers le serveur, détection d'inactivité, réglages persistants, et vérification de version au démarrage.