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>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 08:44:46 +02:00
1 parent 7c9aa04fd9
commit 4932999d5c
13 files changed
+1444 -49

No files matched your search

+36 -11
View File
@@ -498,23 +498,48 @@ const trackerIndexPrefix = "Trackers (tracker tool)"
// la dernière valeur — SANS appeler l'outil : elle ne l'appelle que pour fouiller
// l'historique ou pour modifier. Quelques lignes, jamais le contenu.
func trackerIndexMessage() (Message, bool) {
if memMode() != MemAlways {
list, ok := trackerIndexList()
if !ok {
return Message{}, false
}
return renderTrackerIndex(list, ""), true
}
// trackerIndexList : les trackers à annoncer, ok=false hors mode mémoire
// proactif ou s'il n'y en a aucun. Séparé du rendu pour le bloc figé
// (chat_projsnap.go), qui en tire aussi son état ligne à ligne.
func trackerIndexList() ([]trackerMeta, bool) {
if memMode() != MemAlways {
return nil, false
}
list := trackerList()
if len(list) == 0 {
return Message{}, false
return list, len(list) > 0
}
// trackerIndexLine : la ligne d'un tracker, « nom — dernière valeur (date) ».
func trackerIndexLine(m trackerMeta) string {
last := ""
if m.last != nil {
last = " — " + m.last.Text + " (" + fmtEvent(*m.last) + ")"
}
return m.Name + last
}
// renderTrackerIndex : le message d'index des trackers. asOf vide = la forme
// vivante (texte d'avant PROJ_SNAPSHOT, à l'octet près). Sinon la copie figée :
// datée, et sans « réponds directement d'ici », puisque des <context_update>
// plus récents peuvent en remplacer des lignes.
func renderTrackerIndex(list []trackerMeta, asOf string) Message {
var b strings.Builder
b.WriteString(trackerIndexPrefix + " — this is the COMPLETE list of your trackers, each with its LATEST point. To say which trackers exist or give a latest value, answer straight from this — do NOT call tracker with no arguments to list them, it would only repeat this. Call tracker(name[, when]) only to look further back in history, or with action \"add\"/\"edit\"/\"delete\" to change data.\n")
for _, m := range list {
last := ""
if m.last != nil {
last = " — " + m.last.Text + " (" + fmtEvent(*m.last) + ")"
}
fmt.Fprintf(&b, "- %s%s\n", m.Name, last)
if asOf == "" {
b.WriteString(trackerIndexPrefix + " — this is the COMPLETE list of your trackers, each with its LATEST point. To say which trackers exist or give a latest value, answer straight from this — do NOT call tracker with no arguments to list them, it would only repeat this. Call tracker(name[, when]) only to look further back in history, or with action \"add\"/\"edit\"/\"delete\" to change data.\n")
} else {
b.WriteString(trackerIndexPrefix + " as of " + asOf + trackerFrozenHead)
}
return Message{Role: "system", Content: strings.TrimRight(b.String(), "\n")}, true
for _, m := range list {
fmt.Fprintf(&b, "- %s\n", trackerIndexLine(m))
}
return Message{Role: "system", Content: strings.TrimRight(b.String(), "\n")}
}
// --- outil modèle -------------------------------------------------------------