Commit Graph
64 Commits
Author SHA1 Message Date
Claude 393004cb06 Arrêt du moteur : attendre tout l'arbre, pas seulement le chef
En conteneur, userSvcStop n'attendait que le processus `loki serve`. Avec
Strata, le moteur qui tient la VRAM (cache d'experts, ~9 Go) est un autre
processus qui finit de s'arrêter après le serveur Python : la relance
suivante démarrait llama-server sur une carte encore pleine (« cudaMalloc
failed: out of memory » à l'encodeur de vision) et Strata mesurait la RAM
disponible avec l'ancien moteur encore chargé (mode mmap choisi à tort).

stopProcTree relève l'arbre (session, groupe et descendants) avant tout
signal, SIGTERM puis SIGKILL aux survivants, et ne revient qu'une fois
tout parti.

Diagnostic : une pile d'appels citant load_model ne passe plus pour une
nouvelle tentative (la cause mémoire était perdue), et le chat donne
l'échec de chargement au lieu de « il démarre, réessaie ».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wfx8uW1un1WwWJ6xzAbkV
2026-10-08 07:18:53 +00:00
MichaelandClaude Opus 5.5 5016396998 Repris d'AJEAN : recherche d'images, aperçu du navigateur, vignettes, liens d'images
- web_images (amont v0.17.7) : recherche d'images DuckDuckGo, pendant de
  web_search ; les images du web s'affichent dans le fil avec la loupe, sans
  Referer, et disparaissent si le site les refuse. Schéma minimal pour tenir
  le budget du préambule.
- Aperçu en direct du navigateur piloté (cu_autoshot.go) : une capture après
  chaque action browser_* qui change la page, gardée en RAM, jamais envoyée au
  modèle, retirée du journal en fin de tour ; une carte dans le fil, en fondu.
- Vignettes calculées par le serveur (?thumb=, amont v0.17.9) : ~560 px au
  lieu de l'original (7 Ko au lieu de 550 Ko sur une capture), chargées deux
  par deux avec un nouvel essai ; la loupe ouvre l'original.
- Un lien vers une image ([graphique](graph.png)) l'affiche au lieu d'un
  bouton de téléchargement.
- Chrome du computer use tué avec Loki sous Linux (Pdeathsig), même en cas
  d'arrêt brutal : plus de navigateur orphelin dans le conteneur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:30:05 +02:00
MichaelandClaude Opus 5.5 4b659ac186 Raisonnement : NUDGE_IN_TOOL décide sur le verdict de la forme de la requête
La sonde de gabarit gardait le verdict de la DERNIÈRE forme sondée (outils,
chat_template_kwargs, reasoning_effort), quelle qu'elle soit. NUDGE_IN_TOOL
pouvait donc glisser le rappel de budget dans un résultat d'outil sur la foi
d'un « préfixe instable » conclu pour une autre forme — un autre jeu
d'outils, la réflexion coupée, un autre niveau — dont le rendu n'a rien à
voir.

- Les verdicts sont aussi rangés par forme (empreinte tplShape, au plus 16,
  la plus ancienne cède) ; tplCapsFor rend celui de la forme demandée.
- NUDGE_IN_TOOL calcule la forme de la requête courante (mêmes outils,
  kwargs et niveau que ceux envoyés) et ne place le rappel dans le résultat
  que si CETTE forme est « instable » pour ce modèle ; forme jamais sondée ou
  inconnue : message à part, comme sans la clé. Une relance outils coupés ou
  tool_choice « none » (forme que la sonde ne voit jamais) se replie aussi.
- L'affichage (/api/perf/summary) et REASONING_ECHO gardent le dernier
  verdict, comme avant. Sans la clé, rien ne change.
- Tests : verdict par forme dans la sonde, autre forme / autres kwargs /
  autre niveau refusés, bout à bout avec le verdict d'une autre forme
  (échouait sur l'ancien code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:50:15 +02:00
MichaelandClaude Opus 5.5 c888afb493 Contexte : avec PROJ_SNAPSHOT, la date et le dossier de travail passent dans le bloc figé
Le système portait deux valeurs qui bougent : la date du jour — à minuit,
toute discussion en cours recalculait son prompt entier — et le dossier de
travail, propre à chaque discussion, si bien que deux discussions ne
partageaient même pas leur système. Avec PROJ_SNAPSHOT, ces valeurs
rejoignent le bloc projet figé, en tête du premier message ; le système ne
garde que les consignes (« ton dossier de travail est dans ton contexte »).

- Bloc « Environment (from Loki) » : date (avec l'année, et l'année périmée
  que la consigne web nommait), dossier de la discussion, ou poste distant
  ciblé (nom, système, dossier, hors ligne).
- Contenu toujours vivant : relu à chaque tour ; un changement de jour part en
  une ligne de <context_update>, tout autre écart (poste hors ligne, autre
  cible) renvoie le message entier. L'avertissement « hors ligne » ne fige
  donc plus le système.
- Hôte, compte et dossier des scripts, les mêmes pour toutes les discussions,
  restent dans le système.
- Seul l'assemblage d'un tour de discussion avec la clé pose caps.envInCtx :
  tâches planifiées, sous-agents, vérification et terminal gardent leur
  système, date et dossier compris.
- Sans la clé : système identique à l'octet (gabarits relevés sur le code
  d'avant, test). Format d'instantané passé à 2 : les instantanés persistés
  sont repris neufs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:08:07 +02:00
MichaelandClaude Opus 5.5 5f00b6f041 Contexte : corrections de relecture du lot 2 — seul un bloc posé par Loki est retiré, jamais un texte tapé
Les blocs <context_update> de PROJ_SNAPSHOT étaient reconnus à leur seul
préfixe de texte, et le nettoyage tournait AUSSI sans la clé (requête,
historique persisté, titre, tâche du vérificateur, export, résumé). Un message
de l'utilisateur qui commençait par cette balise — un journal de requête collé
par qui travaille sur Loki — perdait son début en silence : le défaut n'était
plus identique à l'octet.

- Message.CtxUpd marque le message où Loki a posé un bloc (prependCtxUpdate),
  comme ImgRelay pour les relais d'image ; seuls les messages marqués sont
  nettoyés, et la marque tombe avec le bloc.
- La marque ne quitte jamais Loki : retirée à l'envoi avec celle des relais.
- Sans la clé, aucun message n'est marqué : rien n'est retouché.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:31:48 +02:00
MichaelandClaude Opus 5.5 39487f9449 Cache : corrections de relecture du lot 2 — KEEP_TURN_IMAGES retire les images avant tout résumé, partout
Le README promet que les images gardées partent d'abord et que le résumé
(avec perte) ne suit que s'il reste nécessaire. C'était vrai en début de tour
et entre deux étapes, pas ailleurs : la compaction de fin de tour, le filet
réactif (prompt refusé) et la fenêtre pleine en génération retiraient les
images ET résumaient d'un même geste, alors que le retrait seul suffisait.

- Fin de tour : même retrait que le début de tour, puis le seuil est relu.
- Prompt refusé pour débordement : images retirées, requête rejouée sans
  elles, avant la compaction de secours.
- finish=length : images retirées d'abord ; l'étape est rejouée sans résumé
  si le contexte libéré suffit.

Sans la clé, aucune image gardée : chemins d'avant à l'identique.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:30:14 +02:00
MichaelandClaude Opus 5.5 28ba44eb46 Slots : SLOT_PERSIST garde l'état de la discussion à la bascule de preset, en opt-in
Passer de A à B puis revenir à A relance llama-server deux fois, et la
conversation de A est recalculée en entier au retour. Avec SLOT_PERSIST=on
(off par défaut), Loki demande au moteur d'écrire l'état du slot 0 juste
avant la bascule, et le recharge au retour avant le premier message de la
discussion — seulement si tout concorde, sinon calcul normal, en silence.

llama.cpp ne vérifie à la relecture que types et tailles : rien ne dit quel
build ni quels réglages ont calculé ce cache. La clé de validité est donc
l'empreinte de tout ce qui touche au calcul, prise par « loki serve » au
lancement : ligne de commande complète (modèle, CTX, types KV, NGL, lots,
gabarit, SPEC, EXTRA_ARGS…), LLAMA_ARG_*/GGML_*/CUDA_*, taille et date du
modèle et de ses tranches, du projecteur, du brouillon, du binaire et de
ses bibliothèques. Pas de cas « mise à jour du moteur » : elle change la clé.

- --slot-save-path posé aussi pour SLOT_PERSIST accepté, sous les mêmes
  conditions de sécurité que l'isolation du lot 1 (boucle locale ou clé
  d'API) ; refusé avec le décodage spéculatif (le save ne garde pas le
  brouillon ; /slots speculative revérifié à chaque fois), des poids sur
  CPU, un --slot-save-path à la main ; la ligne reste alors celle d'avant
- sauvegarde depuis l'interface ou une tâche, avant l'arrêt : le slot doit
  porter un tour de la discussion (tampon d'engineMarkMain), ≥ 4096 jetons,
  ≤ 8 Gio estimés, double de place libre ; nom provisoire, renommé
  seulement si aucune requête n'est partie pendant l'écriture
- rechargement avant la première requête de la discussion (ou son
  préchauffage) : même clé de moteur, même discussion, slot 0 vierge (/slots
  sans id_task) ; une seule tentative, fichier retiré ; toute erreur =
  calcul normal
- deux fichiers au plus ; clé retirée = tout effacé au lancement suivant ;
  SLOT_PERSIST préservé à la bascule comme HOST (sinon B effaçait l'état de A)
- README : SIDE_SLOT, le cache KV plein ne déclenche jamais de compaction
- tests : plan et refus, ligne inchangée, empreinte (clé d'API exclue,
  fichiers et réglages inclus), rotation, séquence save puis restore sur un
  faux moteur, refus à la sauvegarde et au rechargement, autre clé, défaut
  sans aucun appel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:17:08 +02:00
MichaelandClaude Opus 5.5 7a57322a62 Slots : SIDE_SLOT ouvre un second slot pour les travaux annexes, en opt-in
En --parallel 1, une vérification, un sous-agent, une tâche ou un bench
prennent le slot de la discussion : son état part dans le cache RAM et en
revient, ou se recalcule s'il n'y tient plus. Avec SIDE_SLOT=on (off par
défaut), le moteur ouvre deux slots et la discussion garde le sien.

Dimensionnement choisi pour qu'un débordement concurrent soit impossible par
construction : cache KV NON unifié (--no-kv-unified) et -c 2×CTX, soit deux
flux séparés de CTX jetons. Sous cache unifié, les deux slots partagent le
pool et, plein, llama.cpp renvoie « Context size has been exceeded. » aux
deux — que Loki prenait pour un débordement de la conversation. Chaque slot
garde la fenêtre entière : ctxWindow et la compaction restent sur CTX, rien
n'est raccourci. --cache-idle-slots ne vide les slots au repos que sous cache
unifié : rien à régler.

- lancement : refus (un slot, ligne d'avant, note au journal) si modèle + deux
  états dépassent 90 % de la VRAM, VRAM ou GGUF inconnus, poids sur CPU,
  moteur sans --no-kv-unified, PARALLEL≠2, ou -c/-np/-kvu/--no-kv-unified/
  --kv-unified-per-slot/--cache-idle-slots dans EXTRA_ARGS (ou leurs
  LLAMA_ARG_*) ; note de VRAM en plus, avertissement si NGL imposé
- routage id_slot seulement si /props annonce 2 slots d'au moins CTX jetons :
  0 pour le tour, ses étapes, le préchauffage et le résumé en continuation ;
  1 pour vérification, sous-agents, tâches, bench, résumé sur transcription
- clients /v1 (proxy et relais) : corps réécrit en id_slot 1, quel qu'il soit
- cohérence lot 1 : l'effacement de slot s'abstient (2 slots) ; une requête
  du slot 1 n'avance pas le numéro d'engineSlotHolds, la continuation de
  compaction reste possible après une vérification
- « Context size has been exceeded. » sans nombre de jetons, deux slots en
  service : requête rejouée telle quelle, jamais compaction ni réduction
- préchauffage permis sur les deux slots de SIDE_SLOT, vers le slot 0
- éditeur de preset : VRAM du second slot ou raison du refus
- tests : ligne identique sans la clé, chaque conflit refuse sans changer la
  ligne, routage par nature, aucun id_slot sur un slot / preset externe /
  fenêtre partagée, rejeu sans compaction, proxy forcé, préchauffage

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:02:40 +02:00
MichaelandClaude Opus 5.5 f74f64ed90 Cache : KEEP_TURN_IMAGES garde les images d'outils et NUDGE_IN_TOOL range le rappel de budget, en opt-in
Une capture ou une image vue par see_image ne vivait que le temps du tour :
au tour suivant elle manquait à l'historique, le préfixe en cache divergeait
à elle et toute la boucle d'outils qui suivait était recalculée. Sur Qwen3.5,
le rappel de budget en message user faisait de même pour le tour en cours :
le nouveau message devient la « dernière question » et tout le tour est rendu
autrement. Deux clés, off par défaut et étiquetées zone grise ; sans elles,
requêtes identiques à l'octet près (testé).

- KEEP_TURN_IMAGES : image gardée par référence (chatimg, chiffrée si la
  mémoire l'est), relue à l'identique avant d'être gardée, vision active
  revérifiée à chaque tour ; preset externe seulement s'il déclare la vision
- coût mesuré par le moteur (écart de prompt_tokens moins le texte ajouté) ;
  image non mesurable = éphémère comme avant ; total sous 10 % de la fenêtre,
  la première qui dépasse et les suivantes du tour restent éphémères
- compaction, réduction forcée et début ou milieu de tour au seuil retirent
  ces images d'abord (légende et imageLostMarker gardés), le résumé ne suit
  que s'il reste nécessaire ; clé retirée ou vision perdue : retirées au tour
- relais marqué ImgRelay (persisté, retiré à l'envoi) : ni demande réinjectée
  ni preuve de demande servie, fil rouvert compris ; une image non gardée
  n'entre plus dans l'historique par une compaction en cours de tour
- discussion seulement (ni tâche, ni sous-agent, ni vérification) ; fichier
  d'image rafraîchi à chaque usage, l'élagage de 24 h ne le prend pas
- NUDGE_IN_TOOL : rappel de budget au bout du dernier résultat d'outil, même
  message persisté ; moteur local, sonde de gabarit « préfixe instable » pour
  ce modèle ; sinon, ou forme inattendue, message à part comme avant

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 09:46:01 +02:00
MichaelandClaude Opus 5.5 b0c53e32af Compaction : COMPACT_CONTINUATION, relecture — un refus mémorisé ne vaut que pour le même fil
Le refus mémorisé ne regardait que la taille du contexte. Il n'est évalué
qu'au-dessus du seuil : une discussion vidée, éditée ou régénérée qui
remontait dans la même plage de jetons voyait sa compaction sautée sur la foi
d'un refus qui concernait un autre fil.

- le refus garde l'empreinte de l'historique (celle de perfPrefix) ; seul un
  historique qui prolonge celui d'alors en profite, tout autre retente
- testé : historique modifié ou raccourci, refus noté par une vraie
  compaction, filet réactif jamais bloqué

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 09:25:35 +02:00
MichaelandClaude Opus 5.5 2097c9f915 Compaction : COMPACT_CONTINUATION résume dans le prolongement du prompt en cache, en opt-in
Le résumé d'une compaction part aujourd'hui dans une requête à part — prompt
du résumeur et transcription des anciens tours — que le moteur local calcule à
froid : des dizaines de secondes sur un 27B, des minutes sur un MoE, à chaque
compaction. Nouvelle clé COMPACT_CONTINUATION (off par défaut) : quand le slot
porte encore le prompt de la discussion, la requête de résumé est celle du tour
telle qu'elle est partie, suivie d'une seule demande de résumé ; le moteur ne
calcule qu'elle. Sans la clé, requêtes de compaction identiques à l'octet près
(prompt du résumeur comparé à une copie figée, testé).

- vue MODÈLE pour tout ce qui est rangé : bornes, archives recall, demande
  réinjectée, garantie de réduction et sortie ne changent pas ; la vue
  d'envoi (turnViewDry, wireMessages, buildChatPayload) ne sert qu'à la
  requête, rien d'injecté n'entre dans l'historique (testé)
- frontière recalée sur la vue envoyée (messages non système un pour un,
  vérifiés rôle par rôle), désignée par le nombre de messages gardés et le
  début du premier ; tout résumer avant, dater l'avancement à la frontière
- mêmes règles de résumé (+ mode Code), même budget, température 0.2 sans
  l'échantillonnage du preset, enable_thinking=false ajouté aux arguments du
  tour, reasoning_effort du tour, tool_choice « none », sans flux
- messages utilisateur de fin hors de la vue : pas en cache, et pas deux
  `user` d'affilée pour les gabarits stricts
- slot vérifié : tampon posé par une étape de tour acceptée (llm_slots.go,
  sous le verrou du compteur en vol), perdu dès qu'une autre requête part —
  vérification, sous-agent, tâche, préchauffage, résumé, bench, /v1 — ou que
  le modèle ou la fenêtre changent ; relu à l'envoi
- marge en jetons réels (dernier compte + non vu + demande + budget + 5 %)
- repli sur la transcription au moindre écart : refus du moteur, réseau,
  appel d'outil émis (tool_calls décodés) ou écrit en texte, raisonnement
  seul, résumé vide ; refus du gabarit ou appel d'outil = suspendue pour le
  modèle jusqu'au redémarrage, deux échecs de suite aussi
- avec la clé : pas de résumé quand même un vide ne réduirait pas de 20 %
  (borne exacte, avant tout archivage) ; après un refus faute de réduction,
  pas de nouvel essai avant +10 % de contexte, jamais à 90 % de la fenêtre,
  oublié quand le contexte baisse
- réactif, fenêtre pleine, bouton manuel, tâches, sous-agents, terminal,
  preset externe : chemin d'avant ; télémétrie kind=compact avec la discussion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 09:21:57 +02:00
MichaelandClaude Opus 5.5 630e84cc08 Cache : PREWARM prépare le prochain tour pendant que l'utilisateur lit, en opt-in
Une compaction, un dernier message rendu autrement au tour suivant ou une
tâche planifiée qui a pris le slot laissent au message suivant un recalcul
inévitable : des secondes sur un 27B, des dizaines sur un MoE aux experts en
RAM, avant le premier mot. Nouvelle clé PREWARM (off par défaut) : dès que le
moteur local est libre, Loki lui envoie la requête du prochain tour suivie
d'un message utilisateur « . », max_tokens 1, sans flux. Le moteur calcule le
préfixe et pose un point de reprise au début de ce message ; le vrai tour ne
calcule plus que le sien. Sans la clé, aucune requête de plus (testé).

- assemblage partagé, contenu vivant : turnViewDry (même code que turnView,
  sans rien toucher à la discussion, décision PROJ_SNAPSHOT via
  projSnapDecide), wireMessages, turnReasoning et buildChatPayload, extraits
  de runChat ; corps identique à l'octet près à celui d'avant (matrice
  d'options et de modes, testée contre une copie de l'ancien code)
- jamais devant un vrai travail : toute requête de Loki l'annule au départ
  (compteur de llm_slots.go, sous son verrou), sauf un tour de chat dont les
  messages sérialisés, outils et arguments du gabarit prolongent exactement
  le préfixe préchauffé
- moteur local, un seul slot (/props total_slots), pas pendant un tour, une
  tâche, un bench, un travail annexe ni une requête en vol ; un seul à la
  fois ; pas si le prochain tour compactera, ni à moins de 10 min de minuit
- requête brute : ni compaction, ni relance, ni effort appris, ni CtxUsed,
  ni stats ; erreurs lâchées (une ligne de journal par statut) ; rien persisté
- déclencheurs : fin de tour sans file d'attente, fin de tâche (projet forcé
  levé, slot effacé) ; full = aussi changement de discussion après 3 s
- capacités du dernier tour gardées en mémoire par discussion ; mode Code =
  rôle d'un message ordinaire, une demande de plan diverge et annule
- télémétrie kind=prewarm ; la vérification du cache RAM après une tâche se
  fait sur lui

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 09:01:57 +02:00
MichaelandClaude Opus 5.5 4932999d5c Contexte : PROJ_SNAPSHOT fige le bloc projet par discussion, ses changements arrivent en mise à jour, en opt-in
Le contexte du projet (description, index mémoire, trackers, AGENTS.md) part
en tête du premier message utilisateur, reconstruit à chaque tour : une page
créée ou une valeur de tracker notée faisait recalculer toute la conversation
derrière lui, 10 à 45k tokens. Nouvelle clé PROJ_SNAPSHOT (off par défaut) :
avec on, chaque discussion garde une copie datée du bloc, renvoyée à l'octet
près, et les changements arrivent en tête du message suivant dans un bloc
<context_update from="loki">, rangé dans l'historique. Sans la clé, la requête
est identique à l'octet près (testé contre l'ancien assemblage).

- en-têtes figés « as of <date> » pour l'index mémoire et les trackers, sans
  « answer straight from this » ; ligne fixe du système, seulement avec la clé
- mises à jour par type : +/~/- par page (clé « ](fichier) »), une ligne
  complète par tracker (clé = slug), texte COMPLET pour la description,
  AGENTS.md, ou un index dont la prose a changé — jamais de diff de prose
- état annoncé structuré et persisté (ProjSnap, omitempty), instantané pris
  sur marqueur explicite : la première page d'un projet vide arrive en mise
  à jour, le premier message ne bouge pas
- rafraîchi (blocs retirés de tout l'historique) quand le prompt change de
  toute façon : compaction début/fin/manuelle, compaction ou réduction en
  cours de tour (bloc vivant aussitôt, rangé comme instantané : pas de
  recalcul de plus), système ou outils modifiés, redémarrage de Loki, réglage
  moteur ou modèle, projet, nom, mode mémoire, mode Code, dépôt, textes des
  en-têtes ; et au-delà d'un seuil de mises à jour accumulées
- mise à jour posée sous c.mu avec l'epoch, après la compaction de début de
  tour ; texte ou multimodal ; retirée du titre, de l'export JSON, de l'entrée
  du résumeur et de la tâche du vérificateur
- loadFrom, Reset et le chargement remettent l'instantané à zéro ; clé retirée
  = nettoyage ; agent off = rien du projet ; tâches et presets externes
  inchangés ; un sous-agent ne voit pas le bloc du tour
- ordre des trackers déjà déterministe (lot 1, test existant)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:44:46 +02:00
MichaelandClaude Opus 5.5 7c9aa04fd9 Raisonnement : REASONING_ECHO renvoie au moteur local la réflexion du modèle, en opt-in
Les gabarits Qwen3.5/3.6 rendent <think>…</think> pour chaque message
assistant après la dernière question : c'est leur format entraîné. Loki ne
gardait que le texte et les appels, donc à chaque étape d'une boucle d'outils
le modèle relisait ses étapes précédentes avec des blocs vides, et le moteur
recalculait le dernier message. Nouvelle clé REASONING_ECHO (off par défaut) :
avec on, le raisonnement séparé par le serveur est gardé avec chaque message
et renvoyé au même modèle. Sans la clé, rien n'est capturé ni envoyé : la
requête est identique à l'octet près (testé).

- Message.ReasoningContent (reasoning_content) + ReasoningModel, persistés ;
  seulement depuis reasoning_content du flux, jamais depuis le découpage
  « </think> » fait chez nous (rendu en double)
- un seul point de sortie (echoMessages) : étiquette toujours retirée,
  raisonnement retiré pour une API externe, un autre modèle, un message sans
  texte ni appel, ou si la sonde dit que le gabarit ne le rend jamais
- comptage : estimateTokens/compactBounds comptent ce qui part et que le
  gabarit rend (passé seulement si la sonde dit qu'il le garde, ou l'ignore) ;
  ctxAfter ne retire plus un raisonnement renvoyé
- débordement : relance d'abord sans raisonnement, avant toute compaction ;
  shrinkToFit retire le raisonnement le plus ancien avant tout le reste ;
  le torse compacté le perd comme le résumé
- erreur de gabarit (raise_exception, thinking, Failed to parse messages) :
  relance une fois sans raisonnement avant tool_choice none / outils coupés ;
  retenu pour le modèle si la relance passe
- runBuilderTurn applique enfin NewHistory comme generate (compaction perdue
  pendant une passe de correction) ; forwardStream affiche la bannière
- sonde de gabarit : nouveau verdict renders_reasoning
- REASONING_PRESERVE (lot 1) inchangée, son interaction documentée

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:26:04 +02:00
MichaelandClaude Opus 5.5 3af481ff05 Télémétrie : une sonde du gabarit de chat, diagnostique et sans risque pour le cache
Les pistes de réutilisation du cache dépendent de deux faits que rien ne
mesurait : le gabarit du modèle rend-il encore le raisonnement d'un tour
passé, et le rendu d'un tour d'outil reste-t-il un préfixe exact quand un
message utilisateur s'y ajoute ? La sonde pose ces deux questions au moteur
local par POST /apply-template — un rendu sans état, qui ne prend ni slot ni
place dans la file — et n'en tire que des verdicts oui/non/inconnu.

Elle ne touche à aucune construction de prompt : Loki ne renvoie toujours
aucun reasoning_content au modèle. Toute évolution qui voudrait s'en servir
passera sa propre revue, et lira « unknown » comme « ne rien changer ».

- moteur local seulement ; preset externe : aucune requête, rien d'affiché
- jamais de complétion de repli : avec un slot, elle évincerait le cache
- en tâche de fond après une complétion complète du fil principal, /health
  à 200, au plus une vérification par minute ; authHeader ; 3 s par requête
- mêmes outils, chat_template_kwargs et reasoning_effort que la complétion
- add_generation_prompt=false demandé au moteur ; un moteur qui l'ignore
  donne « inconnu » plutôt qu'un faux « instable »
- tour d'outil bien formé (identifiant de 9 alphanumériques, résultat lié)
- 404, 401, exception du gabarit, délai : inconnu ; 503 relancé
- cache par build_info + modèle + empreinte du gabarit + forme de requête
- verdict dans /api/perf/summary (champ template) et une ligne [tplprobe]
  au journal par verdict nouveau

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:10:42 +02:00
MichaelandClaude Opus 5.5 3dff7e0e04 Flux : corrections de relecture — le « +N » d'une écriture ne recompte plus tout le corps
Chaque événement de frappe (un par ligne) appelait bodyLineCount sur le corps
ENTIER pour remplir body_lines : un reste de coût quadratique, léger mais
inutile, puisque argPreview tient déjà le compte des sauts de ligne.

- argPreview.lineCount : même règle que bodyLineCount (saut de ligne final
  sans ligne de plus), en temps constant.
- Le test de décodage par morceaux vérifie l'égalité avec bodyLineCount à
  chaque préfixe (fuzz compris).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:54:19 +02:00
MichaelandClaude Opus 5.5 f1f6cbe4ea Flux : l'écriture d'un gros fichier ne coûte plus un temps quadratique côté Go
Pendant qu'un modèle tapait un write de 120 Ko, chaque morceau faisait relire
et redécoder tout le JSON des arguments, puis republiait le corps ENTIER à
chaque ligne : 250 Mo de flux SSE, autant de tas vivant dans le journal, et
près de 8 s de CPU volées au décodage quand des experts tournent sur le
processeur. Même travers pour la détection des appels d'outils écrits en
texte, qui repassait toutes les regex sur toute la réponse à chaque jeton.
Rien de ce que voit le modèle ne change : les arguments exécutés restent
entiers, la passe complète de fin de tour aussi.

- argPreview : lecture incrémentale de l'argument affiché et du corps, même
  règle que previewArgDone (barre oblique coupée gardée en attente) ;
  arguments accumulés dans un strings.Builder, cur.Function.Arguments recalé
  sur lui à chaque morceau.
- Événement de frappe : seules les 40 dernières lignes (4 Kio au plus) du
  corps, avec body_lines (vrai « +N ») et body_tail ; l'UI affiche « … » et
  garde le repli sur le décompte local.
- textualToolCallFrom : ne relit que la fin, depuis un vrai début de ligne
  avant la fin du balayage précédent, en reculant sur les suites que les
  motifs embarqués peuvent traverser. Surcharge retry_patterns : balayage
  complet, comme avant.
- Direct SSE : les événements trouvés à un réveil partent en une écriture
  (lots de 128 Kio / 256 événements au plus), un sceau par événement en E2E.
- Tests : fuzz d'argPreview contre previewArgDone, équivalence fenêtre /
  balayage complet au morceau près, corps borné de bout en bout, ordre des
  lots ; bancs d'essai.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:48:49 +02:00
MichaelandClaude Opus 5.5 7dd319a058 Cache : la ligne MCP du préambule ne manque plus au premier tour après un démarrage
Le préambule système était bâti (InjectSkills → baseSystemPrompt) AVANT que
runChat n'appelle EnabledTools, donc avant que mcpTools n'ouvre les connexions.
mcpPromptLine lisait le pool encore vide : le premier tour après un démarrage
partait sans la ligne MCP, le second avec — tout le prompt à recalculer pour
une ligne. Elle comptait en plus les outils masqués par l'utilisateur, et
s'affichait pour le planner, qui ne reçoit aucun outil MCP.

- prepareTurn calcule les outils UNE fois par tour, puis le préambule à partir
  d'eux ; runChatTools les reprend tels quels (pas de second passage par
  mcpEnsureAll, qui retentait deux fois un serveur en panne).
- mcpPromptLine se déduit de cette tranche : seuls les outils réellement
  envoyés sont comptés, et rien n'est annoncé sans outil MCP.
- trackerList départage les égalités d'horodatage par nom puis slug : la liste
  part dans le contexte, un ordre tiré au sort changeait le prompt.
- Le commentaire de tasks_run.go ne prétend plus que le préfixe d'une tâche
  égale celui du chat (dossier de travail et taskCaps diffèrent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:31:00 +02:00
MichaelandClaude Opus 5.5 9263df404d Cache : corrections de relecture — un rappel de loki ne laisse plus deux messages user d'affilée
Persister les rappels ouvrait trois façons de coller deux `user` dans
l'historique, celles-là même qu'appendNudge voulait éviter : sur un gabarit à
alternance stricte, chaque tour suivant aurait échoué.

- Fin de tour : un rappel resté sans réponse (stop, erreur) ou suivi d'un
  ajout en cours de réponse est retiré avant persistance (dropStrayNudges),
  comme avant sa persistance.
- Compaction : la queue ne commence plus sur un rappel ; la vraie demande
  réinjectée devant elle lui était collée. Elle recule d'un groupe d'outils.
- isLokiInjected ne se contente plus du préfixe « [system] » : un message de
  l'utilisateur qui commence ainsi n'est plus pris pour un rappel (sa demande
  était sautée par la compaction, le vérificateur et le titre).
- Relance tool_choice « none » : pas de repli après un stop, qui partait sur
  un contexte annulé et affichait une erreur.
- Tests : appel par le protocole et refus 4xx sous « none » (et rien
  d'exécuté), frontière de compaction, nettoyage de fin de tour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:25:39 +02:00
MichaelandClaude Opus 5.5 38b488515a Cache : les rappels de loki et la relance après un 500 ne recalculent plus la conversation
Le rappel de budget d'outils et la relance « pensé sans agir » partaient au
moteur sans entrer dans l'historique : au tour suivant, le fil divergeait
juste avant eux et toute la boucle d'outils qui suivait était recalculée.
Le modèle relisait en prime une histoire qu'il n'avait jamais vue. La
relance après un 500 (appel d'outil mal parsé) retirait les outils et
modifiait le message système : tout le prompt repartait de zéro, deux fois.

- Rappels persistés tels qu'envoyés (appendNudge), sauf derrière un autre
  message user : éphémères là, pour ne pas casser à chaque tour les gabarits
  à alternance stricte.
- Ils restent reconnaissables (isLokiInjected) : la compaction ne les
  réinjecte pas comme demande en cours et ne les prend pas pour la preuve
  qu'une demande a été servie ; la réduction forcée coupe d'abord aux vraies
  demandes ; le vérificateur, l'export, le titre et le résumeur les sautent
  ou les présentent comme note du système.
- Relance après un 500 sur le llama-server local : mêmes outils, même
  message 0, tool_choice « none » et la consigne au bout du dernier message,
  jamais persistée. Nouveau refus, appel tenté malgré tout ou réponse vide :
  repli une fois sur le chemin historique. Preset externe inchangé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:16:48 +02:00
MichaelandClaude Opus 5.5 26fc348506 Contexte : corrections de relecture — la garde de marge ne compacte plus en boucle, et le preset externe compte comme avant
La garde de marge de génération réclamait 1,2 × la plus longue génération
récente, sans plafond. Une étape de 40 k sur une fenêtre de 65 k (ou de 20 k
sur 32 k, sans budget de raisonnement) demandait plus de place que
l'historique fraîchement compacté n'en laisse : la garde repartait à chaque
étape, et chaque fois résumait le résumé précédent.

- Garde : jamais avant la moitié de la fenêtre. Au-delà, le rejeu sur
  « length » reste le filet.
- Preset externe : le complément « messages arrivés depuis la mesure » ne
  s'applique plus. Son CtxUsed garde le raisonnement, qui couvrait déjà ce
  complément ; l'ajouter le faisait compacter plus tôt qu'avant.
- Journal [ctx] : l'estimation d'avant compaction (début de tour, en tour)
  n'est plus comparée au compte d'une requête compactée, ce qui donnait de
  faux écarts.
- Tests : génération énorme juste après compaction, seuil de 50 % pour toutes
  les fenêtres, comptage externe contre local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:53:38 +02:00
MichaelandClaude Opus 5.5 d6023ef8a0 Contexte : le raisonnement jamais renvoyé ne compte plus, et la compaction ne part plus trop tôt
CtxUsed valait usage.prompt_tokens + généré, raisonnement compris. Or Loki
ne renvoie jamais ce raisonnement au modèle (Message n'a pas de champ pour
lui) : la requête suivante pesait 1 à 8 k jetons de moins que le compte, et
la compaction, qui perd de l'information, partait vers 44-47 k au lieu de
49 k sur une fenêtre de 65 k. Rien ne change dans les requêtes : seul le
compte qui décide de compacter.

- Comptage : seulement les morceaux reasoning_content séparés par le
  llama-server local, remis à zéro à chaque tentative. Le <think> découpé
  chez nous reste dans le message renvoyé pendant le tour, il n'est pas
  retiré ; l'API externe garde l'ancien calcul. Un morceau vaut au plus un
  jeton : on ne peut que sous-estimer, le sens sans danger.
- Début de tour : le message utilisateur (et la file) arrivé depuis la
  dernière mesure s'ajoute en estimation, comme en cours de tour.
- Vérificateur : sa trace isolée écrasait CtxUsed avec sa petite taille, et
  la fin de tour décidait sur ce chiffre-là. Plus maintenant ; la jauge de
  l'UI ne bouge pas non plus et suit le même calcul que le serveur.
- Marge de génération : le compte gonflé offrait une marge par accident.
  Elle est remplacée par une vraie garde — compacter si 1,2 × la plus
  longue génération récente ne tient plus dans la fenêtre. Plancher et
  marge seuls ne devancent jamais le seuil de 75 %.
- Fenêtre pleine en plein raisonnement (finish « length » au-delà du
  seuil) : compaction puis étape rejouée, au lieu du nudge « arrête de
  raisonner ».
- Journal [ctx] : estimation contre compte réel au-delà de 2 % d'écart,
  chaque « length » et chaque nudge, pour vérifier qu'ils n'augmentent pas.
- Le seuil reste à 75 % : la réserve fixe proposée (toolResultMax/3 + 6 k)
  laissait trop peu de place sans budget de raisonnement ni max_tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:45:55 +02:00
MichaelandClaude Opus 5.5 18a723a671 Télémétrie : corrections de relecture — compaction aussi tolérante qu'avant, pas d'alerte de cache sur une API externe, seuil qui suit EXTRA_ARGS
La relecture du relevé par complétion a trouvé quatre écarts, tous
d'affichage ou de robustesse ; rien ne touche aux requêtes ni au contexte.

- Compaction : la réponse lue d'un bloc passait de Decoder à Unmarshal,
  plus strict (un octet parasite après le JSON faisait échouer une
  compaction qui passait). Retour au Decoder, et perfWire fait de même.
- API externe : son cache suit ses propres règles, sans slot ni points de
  reprise à surveiller ici. Plus de ligne ambre « recalculés » pour elle ;
  le relevé reste dans l'anneau.
- Seuil d'alerte : même priorité qu'au lancement — -cms d'EXTRA_ARGS, puis
  LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT, puis CKPT_MIN_STEP ; -ub d'EXTRA_ARGS
  avant UBATCH.
- UI : « recalculés après sous-agent, client /v1 » au lieu des identifiants
  internes (subagent, foreign).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:34:57 +02:00
MichaelandClaude Opus 5.5 80b7793773 Télémétrie : chaque complétion dit enfin ce qu'elle a repris du cache, recalculé et accepté du brouillon
Jusqu'ici, impossible de savoir si une ligne volatile s'était glissée dans
le prompt, si une mise à jour du moteur avait cassé les points de reprise
ou si l'acceptation du MTP s'était effondrée : le chunk final de llama.cpp
porte ces chiffres (cache_n, draft_n, cached_tokens), on les jetait.

Lecture seule : aucun champ ajouté aux requêtes, et le comptage du
contexte (usage.prompt_tokens → CtxUsed → compaction) ne change pas.

- perf_log.go : décodage à part et tolérant (perfWire) — un compteur mal
  typé ne jette plus le chunk final ni ne fait échouer une compaction ;
  cached_tokens, strictement entier dans streamChunk, y déménage aussi.
- Inconnu n'est pas 0 : pointeurs, et l'UI garde « prefill N tok » quand
  le moteur ne dit rien de son cache.
- Nature (main, subagent, verify, task, compact, bench, foreign) portée
  par le contexte, pas par Caps ; TTFT au premier delta de tout type.
- Perte de cache = total précédent − cache_n, seulement quand les messages
  prolongent strictement ceux de la complétion précédente de la même
  discussion et de la même nature (empreintes cumulées) ; jamais sur un
  flux coupé. Ce qui s'est intercalé (sous-agent, client /v1, bench) est
  nommé.
- Anneau de 5000 entrées en mémoire, sans texte ; GET /api/perf/summary
  (derrière la clé) : taux de cache, recalcul par tour et par nature,
  médianes pp/tg par profondeur, acceptation du brouillon.
- Ligne [perf] sur stderr seulement avec LOKI_PERF_LOG.
- UI : « prefill X nouveaux / Y en cache », ambre au-delà d'un seuil
  relevé sur un modèle hybride (espacement des points de reprise).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:29:17 +02:00
MichaelandClaude Opus 5.5 c72467ce4c Moteur : corrections de relecture de l'isolation des travaux annexes — un slot que le travail n'a jamais pris n'est plus effacé
Un sous-agent, une vérification, une tâche ou un bench qui échoue avant que le
moteur ne le serve (erreur avant l'envoi, refus 4xx, arrêt immédiat) laissait
le slot 0 tel quel : il portait encore la conversation, que l'effacement jetait
alors sans qu'elle soit dans le cache RAM — recalcul complet garanti, soit
exactement ce que l'isolation devait éviter.

- engineServed() : runChat (réponse 200 du moteur local) et le bench le
  notent ; un travail annexe n'efface que si le moteur a servi au moins une
  requête de Loki depuis son début (finishSideJob, testable sans moteur).
- Les prompts d'un travail annexe ne sont plus retenus comme « la conversation »
  par noteEnginePrompt : deux vérifications de suite comparaient sinon la
  conversation au prompt du vérificateur, et le conseil pouvait se tromper.
- CACHE_RAM et CACHE_ISOLATE figurent dans le modèle de configuration
  commenté (loki config).
- Éditeur : une valeur fixée par EXTRA_ARGS ou LLAMA_ARG_CACHE_RAM (-1, 0)
  ne s'affiche plus comme « défaut du moteur ».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:44:33 +02:00
MichaelandClaude Opus 5.5 3565b594ea Moteur : cache de prompts dimensionné et travaux annexes isolés — la conversation n'est plus recalculée après un vérificateur ou un sous-agent
llama-server garde en RAM hôte une copie exacte des états de slot qu'il quitte
(KV, état récurrent, points de reprise, brouillon MTP) et la recharge octet pour
octet. Mais son défaut de 8 Gio ne tient pas une conversation de 30 à 65 k
jetons à côté de l'état d'un vérificateur, d'un sous-agent, d'une tâche ou d'un
bench : à la requête suivante il évince la conversation pour sauver l'annexe, et
tout est recalculé (30 à 80 s sur un 27B). Rien de ce que voit le modèle ne
change ici : seul le bruit de découpage des lots, comme le cache_prompt actuel.

- CACHE_RAM (Mio ; vide/auto, nombre passé tel quel, -1, 0) → --cache-ram,
  seulement si l'aide du moteur le connaît. Loki se tait devant -cram
  d'EXTRA_ARGS et LLAMA_ARG_CACHE_RAM. L'auto ne descend JAMAIS sous le défaut :
  il agrandit seulement un modèle tout-GPU (VRAM NVIDIA connue, aucun poids ni
  KV sur CPU, NGL complet), d'après l'état mesuré dans le GGUF (2,5 × KV à CTX
  + état récurrent et points de reprise des hybrides), plafonné à 30 % de la RAM
  (limite cgroup comprise) et à la moitié de la RAM libre.
- --slot-save-path LOKI_HOME/slots (0700, purgé au lancement) uniquement si le
  moteur est en boucle locale ou protégé par clé, et si le dossier existe. Les
  proxys /v1 et du relais refusent désormais toute action /slots (405).
- engineSideJob efface le slot 0 APRÈS le sous-agent, la passe de vérification,
  la tâche planifiée et le bench (pas après la compaction : rien à y gagner).
  Synchrone, borné à 2 s, et seulement si : moteur local, build ≥ 8660 lu dans
  /props, total_slots == 1, slot 0 au repos, aucune requête de Loki en vol
  (compteur tenu par le chat, les résumés, le bench et les proxys).
  CACHE_ISOLATE=off le coupe.
- usage.prompt_tokens_details.cached_tokens : si la conversation revient avec
  moins de la moitié en cache, conseil (une fois) de relever CACHE_RAM.
- Éditeur de preset : champ « Cache de prompts » à côté d'UBATCH, avec la
  valeur auto calculée par le serveur (/api/preset/cacheram).
- Lecteur GGUF : embedding_length, head_count et dimensions ssm.*.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:37:55 +02:00
MichaelandClaude Opus 5.5 b955181bb9 Mode Code : une écriture interdite est refusée dès son chemin, pas après le fichier
Repris du pré-vol d'OpenFox (tool-preflight, 2.0.154).

Un write sur un fichier existant jamais lu était refusé par le tracker…
une fois tout le fichier généré. builder.md demande des fichiers écrits en
entier : un refus pouvait coûter des minutes de décodage pour rien.

- Les schémas write, edit, mem_add et mem_edit annoncent le chemin AVANT
  le contenu. Une map Go sort ses clés par ordre alphabétique : le modèle,
  qui suit l'ordre du schéma, déroulait tout le contenu avant le chemin.
  L'interface nomme aussi le fichier dès le début de la frappe.
- Dès que le chemin d'un write/edit est complet dans le flux, les gardes du
  mode Code (fichier lu, chemin permis) sont vérifiées. Refus : la
  génération est coupée, l'appel réduit à son chemin entre dans
  l'historique avec le refus pour résultat, et le modèle repart (lire le
  fichier d'abord). Rien n'est écrit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:40:38 +02:00
MichaelandClaude Opus 5.5 d5632e3595 Mode Code : les consignes du dépôt (AGENTS.md, CLAUDE.md) font partie du contexte
Repris de context/instructions.ts d'OpenFox.

Un dépôt qui documente ses commandes de build et de test, ses conventions
et ses pièges le fait dans AGENTS.md ou CLAUDE.md. Le modèle les
redécouvrait à coups de read et de bash — quand il les redécouvrait.

En mode Code, le premier de ces fichiers trouvé (dépôt cloné détecté, puis
racine de la discussion) part avec le contexte du projet, tronqué à 4 000
caractères. Comme le reste du contexte projet, il est déplacé dans le
premier message utilisateur : le préfixe système, et le cache de prompt,
restent intacts.

Les tours de correction et de relance du builder reçoivent désormais le
même contexte projet que le tour de build : sans lui, le début du premier
message utilisateur changeait et tout le prompt était recalculé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:36:51 +02:00
MichaelandClaude Opus 5.5 dfafca6fb2 Mode Code : git sur le dépôt cloné, budget de build, sous-agents moins rigides
Trois trous relevés en comparant avec OpenFox.

- git_status / git_diff tournaient à la racine de la discussion, qui
  n'est souvent pas un dépôt : git_clone range le projet dans un
  sous-dossier, et le vérificateur — dont la consigne commence par
  git_diff — tombait sur « not a git repository ». Ils visent maintenant
  le sous-dossier demandé (dir), la racine si c'est un dépôt, sinon
  l'unique dépôt présent ; plusieurs : on demande de choisir.
- Budget d'outils triplé en mode Code : lire, éditer, compiler, relancer
  les tests dépasse vite 24 appels sans tourner en rond, et le troisième
  rappel (« n'appelle plus d'outil ») coupait le build au milieu.
- Sous-agents : température 0.6 au lieu de 0 (en glouton, Qwen avec
  réflexion boucle vite ; le TEMP du preset l'emporte toujours), et seul
  le texte écrit après le dernier outil revient au builder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:36:01 +02:00
MichaelandClaude Opus 5.5 2e0356cd6c Terminal : le vrai code de sortie, la sortie gardée au délai, sans ANSI
Repris de shell.ts / shell-tail.ts et diagnostics.ts d'OpenFox (2.0.160).

- « cmd | tail -N » : le code de sortie devenait celui de tail (0) et un
  build en échec passait pour réussi. Le tail est retiré de la commande et
  Loki garde lui-même les N dernières lignes : même sortie, vrai code.
- Délai dépassé : la sortie déjà produite est rendue (où en était le build,
  quel test bloquait) au lieu d'un « [timeout] » sec, avec le conseil de
  passer par bash_bg pour un serveur.
- Couleurs et séquences ANSI retirées : du bruit en tokens.
- La durée suit le code de sortie (« exit: 0 · 1.2s »).
- Mode Code : « cmd & » refusé, avec renvoi vers bash_bg — le process
  orphelin n'avait ni sortie lisible ni moyen d'être arrêté.
- Diagnostics LSP : erreurs d'abord, avec le compte total ; au-delà de la
  limite, des indices de style passaient devant l'erreur de compilation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:34:42 +02:00
MichaelandClaude Opus 5.5 a745d2399e Agent : read_file, str_replace, old_string… traduits vers les vrais outils
Généralisation du transformSubAgentAliases d'OpenFox. Les petits modèles
ont appris d'autres agents : read_file, str_replace, run_command, ou path /
old_string au lieu de file / old. L'appel tombait sur « outil inconnu »
ou sur un argument manquant, et le tour se perdait en allers-retours.

Le nom et les arguments sont traduits vers l'outil réel quand la cible est
disponible dans ce tour, avant que l'appel soit rangé dans l'historique
(le modèle y relit l'appel tel qu'exécuté). Un sous-agent appelé comme un
outil (« explorer », « code_reviewer ») devient subagent{role, task}. Un
appel déjà correct, ou dont la cible n'est pas offerte, reste intact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:32:29 +02:00
MichaelandClaude Opus 5.5 ad87f2fc9f Agent : un appel d'outil écrit en texte coupe le flux et la relance le cite
Repris d'OpenFox (stream-pure, agent-loop, 2.0.15x).

- Repéré PENDANT le flux (hors réflexion en ligne) : la génération est
  coupée tout de suite au lieu de laisser le modèle dérouler un faux appel
  — parfois un fichier entier — qui ne serait jamais exécuté.
- La consigne corrective cite l'extrait fautif : le modèle voit ce qu'il a
  mal écrit.
- Jusqu'à 3 relances consécutives au lieu d'une par tour ; le compteur
  repart à zéro après chaque appel émis par le protocole. Un long tour
  d'agent pouvait rater la syntaxe deux fois et finir sur un pseudo-appel.
- Nouveaux motifs : le format XML de Qwen3-Coder (<function=…>,
  <parameter=…>) quand le gabarit du moteur ne l'a pas converti.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:31:33 +02:00
MichaelandClaude Opus 5.5 8171899ac1 Contexte : fini le 400 « dépasse la fenêtre » qui bloquait la discussion
Repris d'AJEAN 0.17.5. Sur une fenêtre de 65k, un tour qui lit plusieurs
gros fichiers d'un coup passait de 60 % à plus de 130 % sans jamais
compacter, et l'erreur remontait telle quelle.

- Le test de compactage en cours de tour compte aussi les résultats
  d'outils de l'étape, que le moteur n'a pas encore vus. Sans usage côté
  serveur, l'estimation porte toujours sur tout l'historique.
- Tout résultat d'outil est borné à 30 000 caractères dans la vue du
  modèle, avec une mention qui l'invite à cibler. L'interface garde le
  résultat complet (« voir plus »).
- Dernier recours quand le moteur refuse encore le prompt et que le
  compactage n'a rien pu faire : shrinkToFit tronque les gros résultats
  d'outils puis retire les plus vieux échanges, vers 60 % de la fenêtre
  corrigés par la taille réelle lue dans l'erreur. Deux essais au plus.
- Tâches planifiées : la note de tâche passe en tête du message utilisateur
  au lieu du système. Le préfixe reste celui du chat et le cache de prompt
  de llama-server sert aux deux (l'amont mesurait 12 s de recalcul pour un
  « salut » après une tâche).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:23:21 +02:00
MichaelandClaude Opus 5.5 7f8c5d360f Agent : reprise après coupure, appels parallèles séparés, outils gardés hors 500
Repris d'AJEAN 0.16.4 et 0.17.4.

- Flux coupé en cours de réponse vers une API externe (Wi-Fi, VPN, proxy
  qui décroche) : le tour reprend au lieu d'être abandonné, jusqu'à 5 fois
  avec attente croissante. Rien d'affiché : la requête est rejouée (le
  raisonnement montré est retiré). Du texte affiché : il est rendu au
  modèle avec « continue là où tu t'es arrêté », et la suite s'ajoute à
  l'écran. Un outil à moitié écrit est clos puis réémis. Jamais pour le
  llama-server local : coupé, il a planté. Une coupure après le dernier
  chunk (finish_reason reçu) garde la réponse telle quelle.
- Appels d'outils parallèles : chaque morceau est rangé selon son champ
  index, plus selon sa position dans le chunk. Un serveur qui envoie un
  appel par chunk les fusionnait (noms écrasés, JSON « {…}{…} »).
- Outils coupés et « réponds maintenant » seulement sur un 500 (appel mal
  formé). Un 401, 429 ou 502 d'une API externe coupait les outils et
  rejouait le tour, masquant la vraie cause.
- Débit de décodage mesuré côté Loki quand le serveur n'envoie pas de
  timings ; le débit de lecture, inconnu, n'est plus affiché à 0.
- Windows : la connexion refusée (WSA 10061) est reconnue comme telle par
  la reprise réseau (TestConnexionRefuseeEstRejouee échouait sous Windows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:18:35 +02:00
MichaelandClaude Opus 5.5 2db0ecd431 Presets externes : vision, compactage, bench et badge pour une API distante
La reprise faite en parallèle sur l'autre branche (b667fd4, jamais poussée)
couvrait des trous de celle-ci. Ses ajouts, posés sur la version en place :

- chat_template_kwargs, propre à llama.cpp, ne part plus vers une API
  distante — ni au chat ni au résumé de compactage. Une API stricte
  (OpenAI) répond 400 à un argument inconnu : toute compaction échouait.
- Vision : case « le modèle accepte les images » (EXTERNAL_VISION) dans la
  fenêtre API externe. Elle remplace, en externe, MMPROJ et la sonde
  /props : un modèle distant multimodal ne pouvait jamais recevoir d'image.
- Le benchmark refuse de tourner sur un preset externe : healthCheck le dit
  prêt sans moteur, et la mesure tapait un port arrêté ou un moteur resté
  en vie, attribuée à tort à ce preset.
- Le badge de modèle des réponses porte le nom du modèle distant.
- Éditer le preset en service l'applique aussitôt (SavePresetApplying).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:13:56 +02:00
MichaelandClaude Opus 5.5 48b8193dca Chat : écrire pendant que l'IA répond, et deux fils ne fusionnent plus
Repris d'AJEAN 0.14.0, adapté.

AJOUT EN COURS DE RÉPONSE
Il fallait arrêter la génération pour glisser une précision (le serveur
répondait 409). Un message envoyé pendant une réponse part désormais EN
FILE : runChat l'injecte à la prochaine frontière d'étape (après un appel
d'outil) — le modèle en tient compte dans la SUITE de sa réponse — ou, si
le tour se termine avant, il devient le tour suivant, dans l'ordre. Le
bouton envoyer réapparaît à côté de stop dès qu'il y a du texte ; le
message s'affiche « en attente » au-dessus de la carte jusqu'à ce que le
flux le confirme. Stop abandonne la file (queue_dropped, le client retire
ses pastilles).

Écarts avec l'amont :
- Les messages en attente sont posés AU-DESSUS de la carte, pas dans le
  fil qui s'écrit encore (ils s'y seraient intercalés entre deux bulles).
- Dédoublonnage par identifiant d'envoi (cid) : l'UI réessaie un envoi dont
  la réponse s'est perdue sur le tunnel. Le 409 « déjà en cours » faisait
  office de garde ; sans lui, le réessai mettait le message deux fois en
  file.
- Une tâche planifiée qui occupe le modèle garde le refus 409 (sa fin ne
  dépile rien).
- Pas d'injection après un stop : la boucle repassait en tête une fois
  l'outil interrompu et journalisait le message en file (vu en test).
- runChat prend l'injecteur en paramètre variadique : les appels hors chat
  (tâches, vérification du mode Code, tests) ne changent pas.

DEUX FILS FUSIONNÉS
Un appareil déconnecté pendant qu'un autre changeait de discussion se
réabonnait avec un `from` hérité de l'ancienne : les Seq n'étant pas
globaux, la fin de la nouvelle discussion se greffait sur le début de
l'ancienne restée à l'écran. caught_up et reset portent maintenant l'id de
la discussion ; le client le renvoie (conv_id) et, s'il ne correspond plus,
le serveur ordonne un reset avant de rejouer. La garde « from au-delà du
dernier Seq » émet elle aussi ce reset (elle rejouait par-dessus l'écran
sans le vider).

Vérifié dans le navigateur avec un faux moteur : précision injectée après
l'outil dans le même tour, message en file devenu tour suivant, stop qui
abandonne la file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:11:40 +02:00
MichaelandClaude Opus 5.5 ae0e181711 Outils : aperçu dans le flux, « voir plus » à la demande, coupes annoncées
Repris d'AJEAN 0.15.1 → 0.15.7 (tool_results.go adapté : pas de
chiffrement chez nous).

VOIR PLUS
Chaque résultat d'outil partait en entier dans le flux (jusqu'à 12 000
caractères) et dans le journal rejoué à chaque ouverture : un long fil
d'agent en transportait des centaines. Le flux ne porte plus qu'un aperçu
de 1 600 caractères, la taille réelle (le « ~N tok » de la bulle reste
juste) et un id. Le résultat complet est rangé dans le bucket `toolres`,
sous « <discussion>.<aléa> » — un id propre, pas le tool_call_id que
certains parseurs recyclent. « voir plus » le charge au clic via
/api/chat/tool-result et lève le plafond de hauteur du bloc ; il se range
avec « copier » dans une barre au coin du résultat. Les résultats partent
avec leur discussion (convDelete) ; les orphelins sont nettoyés toutes les
200 écritures.

COUPES ANNONCÉES
- Terminal : une sortie de plus de 8 000 caractères était coupée en
  silence ; le modèle croyait tout voir. Elle commence désormais par
  « [sortie tronquée : N caractères au total, seuls les 8000 derniers sont
  gardés] ».
- MCP : l'amont transmet désormais la réponse en entier. On s'en approche
  sans renoncer au garde-fou (65k de contexte) : le plafond suit la
  fenêtre (CTX caractères, ≈ un quart de la fenêtre, jamais sous les
  12 000 d'avant), et la coupe dit la taille réelle.

Vérifié dans le navigateur avec un faux moteur qui appelle bash : aperçu
de 1 600 caractères, « voir plus » charge les 8 099, mention de coupe en
tête.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:08:46 +02:00
Michael 6aafeeea98 Revert "Résultats d'outils : aperçu + « voir plus », vrai compteur, MCP complet"
This reverts commit 91d1796615.
2026-10-02 23:08:31 +02:00
MichaelandClaude Opus 5.5 f7a4ee71e7 Chat : les images ne sont plus réécrites en base64 à chaque tour
Repris d'AJEAN 0.15.7 (chat_images.go et son test), adapté.

Une image jointe (ou une capture de web_screenshot) vivait en base64 dans
les messages de la conversation, et persist() réécrit la conversation
ENTIÈRE à chaque fin de tour : une photo de téléphone, c'étaient des Mo
remis sur disque à chaque réponse. Elle est désormais rangée une fois sous
$LOKI_HOME/chatimg/<empreinte>.<ext> (hors du dossier de travail, que l'IA
peut vider) ; l'historique n'en garde que « loki-img:<nom> ». Juste avant
l'envoi, expandImageRefs remet les octets exacts (cache mémoire borné à
64 Mo) : pour le modèle, rien ne change.

DEUX ÉCARTS AVEC L'AMONT
- Copie à l'écriture : l'amont remplace l'URL en place. Chez nous, persist
  peut tourner pendant qu'un runChat sérialise ces mêmes maps (compactage
  en vol, bascule de projet) — « concurrent map read and map write », fatal.
  Les parties modifiées sont donc recopiées.
- Le rechargement reste éphémère : stripImageParts retire toujours les
  images d'une conversation rouverte (garde-fou contre un modèle sans vision
  qui lirait le base64 comme du texte). L'amont, lui, les garde ; on
  pourra le suivre plus tard si l'on veut.

Pas de migration des archives : une conversation rouverte est allégée par
stripImageParts, comme avant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:05:34 +02:00
MichaelandClaude Opus 5.5 4a1054c1b7 Mémoire : après un compactage, le modèle sait quelles pages il avait lues
Repris d'AJEAN 0.14.0 (chat_mem_pinned.go et ses tests, tels quels).

Le compactage résume le torse, résultats de mem_read compris. Une page qui
portait TOUTES les règles d'une tâche se retrouvait réduite à trois mots
dans le résumé, et le modèle, après compactage, les oubliait. Le rappel ne
recopie rien : il LISTE les pages lues (bornées aux 24 plus récentes) et
invite à les relire si elles concernent la tâche. Il s'accumule d'un
compactage à l'autre — l'ancien rappel est relu comme source, puisque le
mem_read d'origine a disparu.

Posé seulement en mode agent (sans lui, pas de mem_read pour y donner
suite). Comme le contexte projet, le rappel est sorti du bloc système
commun vers le premier message utilisateur (isProjectSystem) : il est
propre à la conversation et casserait sinon le cache du système partagé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:04:51 +02:00
MichaelandClaude Opus 5.5 626227f2f2 Projets : changer de projet ne recalcule plus tout le prompt
Repris d'AJEAN 0.15.8, adapté : chez nous le contexte projet n'est jamais
persisté, il est injecté à chaque tour sous forme de messages système
préfixés — ce qui rend le déplacement trivial et sans migration.

LE PROBLÈME
Sur un modèle hybride (Qwen3.5 et suivants, couches récurrentes), llama.cpp
ne peut reprendre un prompt déjà calculé qu'à un point de sauvegarde, et il
n'en pose qu'au DÉBUT d'un message utilisateur. La description du projet,
l'index mémoire et l'index des trackers étaient fusionnés dans le bloc
système : deux projets divergeaient AVANT le premier point de reprise, et le
premier message après un changement de projet recalculait tout (l'amont
mesure ~8 s pour 5 000 tokens sur un 27B).

LE CORRECTIF
normalizeSystemMessages sort ces messages (isProjectSystem, par leurs
préfixes) du bloc système et les place, dans un bloc <project_context>, en
tête du PREMIER message utilisateur de la séquence envoyée. Le système
commun reste identique d'un projet à l'autre, donc en cache. L'historique
persisté et l'affichage ne changent pas (copie, entrée non mutée — testé).

InjectSkills ne fusionne plus son préambule DANS un message projet placé en
tête (cas d'un preset sans prompt système) : il y aurait perdu son préfixe
et serait resté dans le bloc commun.

Le gain ne vaut qu'entre projets de même mode mémoire : la consigne mémoire
fait partie du système commun.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:04:51 +02:00
MichaelandClaude Opus 5.5 4602614be0 Chat : mode agent coupé = modèle brut, vraiment
Repris d'AJEAN 0.13.12 / 0.13.13. Couper le mode agent retirait bien les
outils, la mémoire et le préambule Loki, mais laissait passer deux choses :

- Le prompt système du PRESET et le contexte du projet (description, index
  mémoire, trackers). Ils décrivent des outils et une mémoire que ce mode
  n'a pas : le modèle, persuadé de pouvoir agir, écrivait des <tool_call>
  en clair dans sa réponse. Agent off, generate n'injecte plus rien — le
  modèle reçoit les messages nus, comme un llama-server direct.
- Un tool_call que le moteur parvient à parser alors qu'aucun outil n'a été
  annoncé (agent off, ou relance sans outils après un 500). Il était exécuté,
  répondait « outil inconnu », et la boucle repartait. Il est désormais
  ignoré : le modèle répond en texte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:04:51 +02:00
MichaelandClaude Opus 5.5 97378da7af Agent : trois filets de secours qui faisaient plus de mal que de bien
Repris d'AJEAN 0.15.5.

COMPACTAGE DE SECOURS
Sur n'importe quel refus du moteur — appel d'outil mal formé, modèle en
cours de chargement, erreur de template — runChat résumait ~75 % de la
conversation avant de rejouer, même quand elle tenait en trois messages.
contextOverflow ne laisse passer que les vrais débordements : libellés de
llama.cpp (« exceeds the available context size ») et des API
OpenAI-compatibles (context_length_exceeded), ou, à défaut de libellé, une
conversation déjà à 90 % de la fenêtre.

ARGUMENTS D'OUTIL ILLISIBLES
Un JSON tronqué était neutralisé en {} (indispensable : le template les
re-parse à chaque requête) PUIS exécuté tel quel, d'où des erreurs
trompeuses (« fichier manquant », « commande vide ») qui envoyaient le
modèle sur une fausse piste. L'appel n'est plus exécuté ; le modèle reçoit
une erreur qui dit exactement quoi renvoyer. Des arguments VIDES restent
valides (outil sans paramètre).

RELANCE SANS OUTILS
La consigne imposait le français. Elle demande désormais la langue de
l'utilisateur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:04:35 +02:00
MichaelandClaude Opus 5.5 e12f07426f Diff : le compteur de lignes dit enfin la vérité
Repris d'AJEAN 0.16.0 (chat_diff.go et son test, repris tels quels : le
fichier n'avait pas bougé chez nous depuis la 0.13.5).

- Un fichier de 500 lignes écrit par l'agent affichait « +500 » pendant la
  frappe puis retombait à « +120 » : le diff envoyé à l'UI est plafonné à 120
  lignes, et l'UI recomptait sur la version tronquée. lineDiff et addedDiff
  rendent désormais les VRAIS totaux, calculés avant la coupe, et ils
  voyagent dans le journal (added/removed) — donc survivent au refresh.
  Repli sur l'ancien décompte pour les conversations d'avant.
- Une retouche d'une ligne dans un bloc de plus de 400 lignes s'affichait en
  remplacement complet (le LCS n'y était pas tenté). Les lignes communes en
  tête et en queue sont écartées d'abord : on voit le vrai changement, avec
  trois lignes de contexte, et il n'est plus repoussé hors de la fenêtre.
- Un contenu terminé par un saut de ligne ne compte plus une ligne de trop,
  côté serveur comme dans la bulle en cours de frappe (bodyLineCount).

Le vérificateur du mode Code (code_verify.go) journalise les mêmes champs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:04:30 +02:00
Claude c4e399e101 Mode code : sous-agents (explorer, code-reviewer, planner)
Reprise de l'idée des sous-agents d'OpenFox, sur la mécanique déjà en place
pour la passe de vérification : un runChat isolé, non persisté.

L'outil `subagent` délègue une question bornée à un rôle qui travaille dans
SON propre contexte et ne rend que sa réponse. Sur un modèle local, c'est la
fenêtre de contexte qu'on sauve : « trouve où est géré le cache » coûte dix
lectures de fichiers qui restaient ensuite dans l'historique jusqu'à la
compaction, alors que seule la réponse comptait.

Les rôles explorer et code-reviewer, jusqu'ici définis mais jamais appelés,
deviennent utilisables. Tous les rôles délégués sont en LECTURE SEULE : pas
de write/edit (ce qui modifie le dépôt reste dans le fil principal, sous les
yeux de l'utilisateur), pas de subagent (aucune récursion), pas de mémoire ni
de web. Seul le planner pose des critères — son prompt le lui demande — et
marquer un critère « passé » reste le privilège de la passe de vérification.

Le rôle verifier n'est PAS délégable : le builder se décernerait son propre
satisfecit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
2026-09-22 14:08:01 +00:00
Claude 74fb1fc781 Tâches : scripts planifiés sans modèle, et l'IA planifie la sienne
Deux reprises d'AJEAN autour des tâches planifiées.

Dossier de scripts (/data/scripts) : un dossier durable À CÔTÉ de memory,
hors du workspace. Le workspace est jetable — supprimer une discussion
emporte ses fichiers — donc un script qu'on veut garder n'y avait pas sa
place. Le briefing machine l'annonce à l'IA, qui y écrit et y lance ses
scripts normalement.

Tâche « script seul » (Task.Kind/Script) : le planificateur lance le script
sans charger le modèle ni consommer un token, sa sortie devient le
compte-rendu, et l'UI l'affiche comme n'importe quelle tâche. Elle tourne
hors du verrou de génération — d'où un registre à part pour l'afficher « en
cours » et l'arrêter (/api/tasks/stop), et un « tester » qui n'attend ni le
verrou ni le moteur. Sélecteur Consigne IA / Script seul dans la modale.

Outils task_list/create/update/delete : l'IA se donne elle-même rappels et
veilles récurrentes, cloisonnés par projet (dans un projet, elle ne voit et
ne pilote que ses tâches). Le budget du préambule passe de 8600 à 11000
caractères, avec le palier documenté dans le test.

Dossier mémoire réservé à ses outils : bash, write et edit ne le touchent
plus (guardToolOnly*). Un `cat memory/…` contournait l'index MEMORY.md, et
c'est la condition d'un chiffrement de la mémoire à venir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
2026-09-22 13:33:39 +00:00
Claude 91d1796615 Résultats d'outils : aperçu + « voir plus », vrai compteur, MCP complet
Le flux n'envoie plus tout le résultat d'un outil : un aperçu de 1600
caractères, sa taille RÉELLE et l'id de l'appel. Le bouton « voir plus »
charge le reste à la demande (/api/chat/tool-result), et déplie vraiment le
bloc (plafond de 280 px levé) au lieu de le laisser scroller. « voir plus »
et « copier » vivent dans la même barre, ancrée en bas à droite d'un
bloc-parent non scrollant : elle reste au coin quand on scrolle le résultat.

Le compteur « ~N tok » de la bulle disait la taille de ce que l'UI avait
reçu, donc toujours le plafond sur un long résultat. Il lit maintenant
result_chars, la taille réelle.

MCP : plus de troncature à 12000 caractères avant le modèle (même règle que
la lecture mémoire). Un serveur MCP est configuré exprès pour ce qu'il rend ;
le couper au milieu rendait la réponse inutilisable. L'UI, elle, n'en affiche
que l'aperçu.

Reprises d'AJEAN 0.15.1, 0.15.2 et 0.15.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
2026-09-22 13:24:53 +00:00
Claude 00b2350fb9 Navigateur piloté, images redressées : reprises d'AJEAN
L'amont (AJEAN 0.15) donne à l'IA le contrôle d'un navigateur ; Loki
embarquait déjà un Chromium pour `web_screenshot` sans jamais le piloter.

Contrôle du navigateur (computer_use.go, computer_cdp.go, browser_grid.go,
portés d'AJEAN) : l'IA ouvre une page en CDP, en reçoit les éléments
interactifs NUMÉROTÉS et agit par numéro (browser_open/snapshot/find/click/
type/key/scroll). Aucune vision requise — un petit modèle texte s'en sort.
Avec un projecteur chargé s'ajoutent browser_screenshot (image quadrillée)
et browser_click_xy. Interrupteur dédié (Réglages, `loki computer`,
/api/computer), sous le mode agent : cliquer et taper dans une page sont des
actions réelles, même niveau de confiance que bash.

Adaptation au conteneur : chromePath cherche D'ABORD le Chromium de
Playwright (PLAYWRIGHT_BROWSERS_PATH, /opt/pw-browsers), le seul navigateur
de l'image — sinon la fonctionnalité se déclarait absente là où le
navigateur est présent. LOKI_CHROME force un chemin, LOKI_CU_HEADFUL ouvre
une vraie fenêtre.

Préparation des images (web_upload_orient.go, porté d'AJEAN) : l'orientation
EXIF est cuite dans les pixels — le projecteur l'ignore et voyait les photos
de téléphone couchées — et le grand côté ramené sous 1568 px. Appliquée aux
pièces jointes, à see_image et aux captures.

Dédup des appels d'outils : bash, bash_bg, bash_tail, see_image et les
browser_* ne sont plus court-circuités sur un appel identique. Relancer la
même commande après avoir modifié un fichier est légitime, et un outil qui
porte une image dans un message à part renvoyait « [déjà fait] » SANS
l'image — le modèle tournait en boucle.

Mode code : le builder ne publie plus de lui-même (pas de commit/push/reset/
rebase ni de redémarrage de service sans demande explicite), reprise de la
leçon d'AJEAN 0.15.4 ; l'inspection en lecture seule reste encouragée.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
2026-09-22 13:19:37 +00:00
Claude b5904907e8 Reprise réseau, presets externes, see_image
Trois apports repérés chez AJEAN (v0.13.8) et OpenFox (2.0.118), portés et
adaptés à Loki.

## Reprise réseau du tour (llm_retry_net.go)

La requête de complétion partait une fois : un Do() qui échoue ou un statut
d'erreur tuait le tour. Les messages d'erreur le disaient eux-mêmes —
« réessaie dans quelques secondes » — autrement dit on demandait à
l'utilisateur de refaire à la main ce que le code pouvait faire seul. Une
tâche planifiée tombée pendant un redémarrage du moteur échouait pour de
bon, sans personne pour recliquer.

Trois reprises consécutives, 0,8 → 1,6 → 3,2 s, plafonnées, interruptibles
par un /stop. La règle de sûreté ne souffre pas d'exception : on ne rejoue
que TANT QU'AUCUN OCTET N'A ÉTÉ DIFFUSÉ, sinon la moitié de la réponse
déjà chez l'utilisateur serait dupliquée. Le compteur repart à zéro dès
qu'une réponse arrive.

500 n'est pas un statut de reprise : c'est ce que llama.cpp rend pour un
appel d'outil malformé ou un prompt trop long, deux échecs déterministes
que les filets sémantiques traitent déjà. Restent les codes qui disent
« pas maintenant » : 429, 502, 503, 504.

## Presets externes (backend_external.go, web_external.go)

Un preset avec EXTERNAL=1 route le chat vers une API OpenAI-compatible
distante (OpenAI, Groq, OpenRouter, un vLLM sur une autre machine) au lieu
du llama-server local. C'est un preset COMME UN AUTRE : même liste, même
bascule, même prompt système par preset. La différence ne vit qu'à deux
endroits — l'inférence (resolveChatEndpoint) et la bascule, qui arrête le
moteur local au lieu de le redémarrer.

La clé du serveur local ne part jamais chez un tiers : chaque endpoint
porte la sienne. La clé du preset n'est jamais renvoyée en clair à
l'interface, et un champ vide ne l'efface pas — il faut y avoir touché.
Une fenêtre dédiée plutôt que l'éditeur habituel : un modèle distant n'a
ni quantification, ni couches GPU, ni moteur. Un bouton teste la connexion
avant d'enregistrer, et rend le message de l'API plutôt que le JSON brut.

Le résumé de compactage part au même endroit que le chat : le laisser
taper le moteur local aurait cassé toute compaction sur un preset externe.

## see_image (chat_vision_tool.go)

Loki savait voir une pièce jointe et une capture qu'il venait de prendre,
mais pas un fichier qui dort sur le disque : « regarde ~/photos/bug.png »
n'avait aucune réponse, `read` rendant des octets binaires. L'outil charge
l'image et la réinjecte dans un message utilisateur multimodal — même
chemin que les pièces jointes.

Même règle ÉPHÉMÈRE que les captures (et non celle de l'amont, qui persiste
l'image) : l'image va dans le tour en cours, pas dans l'historique. Un
base64 persisté repartirait à chaque tour et finirait par dépasser la
fenêtre pour de bon.

Le marqueur de perte à la compaction existait déjà mais n'offrait qu'un
recours, « reprends la capture » — ce qui enverrait photographier une page
web alors que l'image perdue est un PNG du disque. Formulation généralisée.

## Au passage

toolCallLabel est extrait de runChat. Cette table nom d'outil → argument a
une double fonction — libellé affiché ET argument principal — donc un outil
absent s'exécute sur une chaîne vide : see_image répondait « chemin de
fichier manquant » quoi qu'on lui passe, sans que rien d'autre ne bronche.
Une table pareille se teste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129sffVC43rezAXUMQuzUog
2026-09-12 21:05:19 +00:00
MichaelandClaude Opus 5 17f8fa8f34 Mémoire longue (recall), projets et trackers
Trois apports repris d'AJEAN 0.12.9 → 0.13.5, adaptés au fork.

MÉMOIRE LONGUE DE LA CONVERSATION (chat_recall.go)
Le compactage résumait, donc perdait. Chaque gros bloc du torse est désormais
ARCHIVÉ verbatim sous un identifiant court (r7…) dans bbolt AVANT d'être
résumé ; le résumé cite l'id, et le modèle le rappelle avec recall(id) ou le
retrouve par mots-clés avec recall_search. Le contexte reste plat, l'archive
grossit sur disque. En mode code, un fichier lu ou un diff produit tôt dans la
session n'est plus perdu au compactage suivant.
Au passage : garde-fou anti-résumé-dégénéré, budget de résumé indexé sur la
fenêtre, queue ramenée à 20 % (compacter plus large ne coûte plus de perte), et
fin de l'épinglage du 1er message user — le modèle répondait à l'ancienne
demande au lieu de continuer la tâche en cours.

PROJETS (projects.go)
Un projet cloisonne une mémoire, ses discussions et ses trackers. La couture
est memoryDir(), qui pointe sur le projet actif : tout le code mémoire en
hérite sans le savoir. Migration automatique au premier démarrage (memory/*.md
→ memory/generale/, discussions et tâches orphelines rattachées). Une tâche
planifiée vise un projet et l'exécution le force, pour qu'une veille n'écrive
pas dans la mémoire du chantier affiché à l'écran.

TRACKERS (tracker.go)
3e type de mémoire : les données datées qui s'accumulent. On ne les lit jamais
en entier — consultation par niveaux (vue d'ensemble → année → mois →
événements), et la dernière valeur de chaque tracker est donnée d'emblée au
modèle, qui répond sans appeler l'outil.

Aussi : index MEMORY.md tenu par le CODE et injecté en tête de conversation
avec la description du projet (le modèle ne peut plus le désynchroniser) ;
prompt système rattaché au PRESET et non plus global — stocké en base, pas
dans le .env, qui ne saurait pas porter un texte multiligne.

Deux correctifs du lot précédent voyagent ici, faute de pouvoir séparer les
fichiers : msgText signale la présence d'une image au compactage, et le
garde-fou « pensé sans agir » passe à deux relances (nudgeCount/maxNudges).

Le paquet UI est regénéré dans le commit suivant, qui touche les mêmes sources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 22:34:33 +02:00