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>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 09:46:01 +02:00
1 parent b0c53e32af
commit f74f64ed90
9 files changed
+1153 -5

No files matched your search

+27 -1
View File
@@ -180,7 +180,9 @@ func msgTokens(m Message) int {
for _, tc := range m.ToolCalls {
n += (len(tc.Function.Name) + len(tc.Function.Arguments)) / 4
}
return n
// Image gardée (KEEP_TURN_IMAGES) : son coût mesuré par le moteur, que le
// seul marqueur textuel sous-estimerait de loin. 0 sans la clé.
return n + m.imgTokens
}
// estimateTokens estime la taille de l'historique en tokens, raisonnement
@@ -376,7 +378,25 @@ func compactMessages(ctx context.Context, msgs []Message, caps Caps) ([]Message,
// vue MODÈLE (msgs) dans tous les cas ; la vue d'envoi ne sert qu'à construire
// la requête de résumé. refused : un résumé a été obtenu, mais la réduction
// reste sous les 20 % exigés.
//
// Images gardées par KEEP_TURN_IMAGES (chat_keep_images.go) : retirées
// D'ABORD, dans tout l'historique — leur légende et imageLostMarker restent.
// Leur retrait suffit à changer l'historique, même si le résumé qui suit est
// refusé. Sans la clé, il n'y en a aucune : chemin d'avant.
func compactMessagesOpt(ctx context.Context, msgs []Message, caps Caps, opt compactOpts) (out []Message, changed, refused bool) {
stripped, freed := dropKeptImages(msgs)
if freed == 0 {
return compactMessagesCore(ctx, msgs, caps, opt)
}
logKeptImagesDropped("compaction", freed)
if out, changed, _ = compactMessagesCore(ctx, stripped, caps, opt); changed {
return out, true, false
}
return stripped, true, false
}
// compactMessagesCore : la compaction elle-même (voir compactMessagesOpt).
func compactMessagesCore(ctx context.Context, msgs []Message, caps Caps, opt compactOpts) (out []Message, changed, refused bool) {
// Budget de queue = fraction de la CONVERSATION (pas de la fenêtre). Le lier à
// la fenêtre était le bug : une conversation de 25k tokens dans une fenêtre de
// 64k gardait 16k (0.25×64k) en queue → torse minuscule → réduction < 20% →
@@ -1027,6 +1047,12 @@ func shrinkToFit(msgs []Message, actual int) ([]Message, bool) {
}
target := int(float64(ctxWindow()) * 0.6 / ratio)
out := append([]Message(nil), msgs...)
// Avant tout : les images gardées par KEEP_TURN_IMAGES, retirées en bloc
// comme à la compaction (sans la clé, aucune).
if s, freed := dropKeptImages(out); freed > 0 {
logKeptImagesDropped("réduction", freed)
out = s
}
for i := range out {
if out[i].ReasoningContent == "" && out[i].ReasoningModel == "" {
continue