- Accueil
- Documentation
- Sessions
- Architecture de l'arbre de session (actuelle)
Architecture de l'arbre de session (actuelle)
Référence : session.md
Ce document décrit le fonctionnement actuel de la navigation dans l’arbre de session : modèle d’arbre en mémoire, règles de déplacement des feuilles, comportement de branchement et intégration des extensions/événements.
En quoi consiste ce sous-système
Section intitulée « En quoi consiste ce sous-système »La session est stockée sous forme de journal d’entrées en ajout seul, mais le comportement à l’exécution est basé sur un arbre :
- Chaque entrée non-en-tête possède un
idet unparentId. - La position active est
leafIddansSessionManager. - L’ajout d’une entrée crée toujours un enfant de la feuille courante.
- Le branchement ne réécrit pas l’historique ; il change uniquement l’endroit où la feuille pointe avant le prochain ajout.
Fichiers clés :
src/session/session-manager.ts— modèle de données de l’arbre, parcours, déplacement des feuilles, extraction de branche/sessionsrc/session/agent-session.ts— flux de navigation/tree, résumé, émission de hooks/événementssrc/modes/components/tree-selector.ts— comportement interactif de l’interface arborescente et filtragesrc/modes/controllers/selector-controller.ts— orchestration du sélecteur pour/treeet/branchsrc/modes/controllers/input-controller.ts— routage des commandes (/tree,/branch, comportement du double-échap)src/session/messages.ts— conversion des entréesbranch_summary,compactionetcustom_messageen messages de contexte LLM
Modèle de données de l’arbre dans SessionManager
Section intitulée « Modèle de données de l’arbre dans SessionManager »Index à l’exécution :
#byId: Map<string, SessionEntry>— recherche rapide pour toute entrée#leafId: string | null— position actuelle dans l’arbre#labelsById: Map<string, string>— étiquettes résolues par identifiant d’entrée cible
API de l’arbre :
getBranch(fromId?)remonte les liens parents jusqu’à la racine et renvoie le chemin racine→nœudgetTree()renvoieSessionTreeNode[](entry,children,label)- les liens parents deviennent des tableaux d’enfants
- les entrées dont le parent est manquant sont traitées comme des racines
- les enfants sont triés du plus ancien au plus récent par horodatage
getChildren(parentId)renvoie les enfants directsgetLabel(id)résout l’étiquette actuelle depuislabelsById
getTree() est une projection à l’exécution ; la persistance reste sous forme d’entrées JSONL en ajout seul.
Sémantique du déplacement des feuilles
Section intitulée « Sémantique du déplacement des feuilles »Il existe trois primitives de déplacement des feuilles :
-
branch(entryId)- Valide que l’entrée existe
- Définit
leafId = entryId - Aucune nouvelle entrée n’est écrite
-
resetLeaf()- Définit
leafId = null - Le prochain ajout crée une nouvelle entrée racine (
parentId = null)
- Définit
-
branchWithSummary(branchFromId, summary, details?, fromExtension?)- Accepte
branchFromId: string | null - Définit
leafId = branchFromId - Ajoute une entrée
branch_summarycomme enfant de cette feuille - Quand
branchFromIdestnull,fromIdest persisté comme"root"
- Accepte
Comportement de navigation /tree (même fichier de session)
Section intitulée « Comportement de navigation /tree (même fichier de session) »AgentSession.navigateTree() est de la navigation, pas un fork de fichier.
Flux :
- Valider la cible et calculer le chemin abandonné (
collectEntriesForBranchSummary) - Émettre
session_before_treeavecTreePreparation - Optionnellement résumer les entrées abandonnées (résumé fourni par un hook ou résumeur intégré)
- Calculer la nouvelle cible de feuille :
- sélection d’un message utilisateur : la feuille se déplace vers son parent, et le texte du message est renvoyé pour pré-remplir l’éditeur
- sélection d’un custom_message : même règle que pour un message utilisateur (feuille = parent, le texte pré-remplit l’éditeur)
- sélection de toute autre entrée : feuille = identifiant de l’entrée sélectionnée
- Appliquer le déplacement de feuille :
- avec résumé :
branchWithSummary(newLeafId, ...) - sans résumé et
newLeafId === null:resetLeaf() - sinon :
branch(newLeafId)
- avec résumé :
- Reconstruire le contexte de l’agent à partir de la nouvelle feuille et émettre
session_tree
Important : les entrées de résumé sont attachées à la nouvelle position de navigation, pas à la fin de la branche abandonnée.
Comportement de /branch (nouveau fichier de session)
Section intitulée « Comportement de /branch (nouveau fichier de session) »/branch et /tree sont intentionnellement différents :
/treenavigue à l’intérieur du fichier de session actuel./branchcrée un nouveau fichier de branche de session (ou un remplacement en mémoire pour le mode non-persistant).
Flux utilisateur de /branch (SelectorController.showUserMessageSelector → AgentSession.branch) :
- La source de branchement doit être un message utilisateur.
- Le texte utilisateur sélectionné est extrait pour pré-remplir l’éditeur.
- Si le message utilisateur sélectionné est racine (
parentId === null) : démarrer une nouvelle session vianewSession({ parentSession: previousSessionFile }). - Sinon :
createBranchedSession(selectedEntry.parentId)pour forker l’historique jusqu’à la limite du prompt sélectionné.
Spécificités de SessionManager.createBranchedSession(leafId) :
- Construit le chemin racine→feuille via
getBranch(leafId); lève une exception si absent. - Exclut les entrées
labelexistantes du chemin copié. - Reconstruit des entrées d’étiquettes fraîches à partir de
labelsByIdrésolu pour les entrées qui restent dans le chemin. - Mode persistant : écrit un nouveau fichier JSONL et bascule le gestionnaire dessus ; renvoie le nouveau chemin de fichier.
- Mode en mémoire : remplace les entrées en mémoire ; renvoie
undefined.
Reconstruction du contexte et intégration résumé/personnalisé
Section intitulée « Reconstruction du contexte et intégration résumé/personnalisé »buildSessionContext() (dans session-manager.ts) résout le chemin actif racine→feuille et construit l’état effectif du contexte LLM :
- Suit le dernier état thinking/model/mode/ttsr sur le chemin.
- Gère la dernière compaction sur le chemin :
- émet d’abord le résumé de compaction
- rejoue les messages conservés depuis
firstKeptEntryIdjusqu’au point de compaction - puis rejoue les messages post-compaction
- Inclut les entrées
branch_summaryetcustom_messageen tant qu’objetsAgentMessage.
session/messages.ts mappe ensuite ces types de messages pour l’entrée du modèle :
branchSummaryetcompactionSummarydeviennent des messages de contexte modélisés avec le rôle utilisateurcustom/hookMessagedeviennent des messages de contenu avec le rôle utilisateur
Ainsi, le déplacement dans l’arbre modifie le contexte en changeant le chemin de la feuille active, et non en mutant les anciennes entrées.
Étiquettes et comportement de l’interface arborescente
Section intitulée « Étiquettes et comportement de l’interface arborescente »Persistance des étiquettes :
appendLabelChange(targetId, label?)écrit des entréeslabelsur la chaîne de feuille courante.labelsByIdest mis à jour immédiatement (ajout ou suppression).getTree()résout l’étiquette actuelle sur chaque nœud retourné.
Comportement du sélecteur d’arbre (tree-selector.ts) :
- Aplatit l’arbre pour la navigation, conserve la mise en surbrillance du chemin actif et priorise l’affichage de la branche active en premier.
- Prend en charge les modes de filtrage :
default,no-tools,user-only,labeled-only,all. - Prend en charge la recherche en texte libre sur le contenu sémantique rendu.
Shift+Louvre l’édition d’étiquette en ligne et écrit viaappendLabelChange.
Routage des commandes :
/treeouvre toujours le sélecteur d’arbre./branchouvre le sélecteur de messages utilisateur sauf sidoubleEscapeAction=tree, auquel cas il utilise également l’interface du sélecteur d’arbre.
Points d’extension et hooks pour les opérations sur l’arbre
Section intitulée « Points d’extension et hooks pour les opérations sur l’arbre »API d’extension au moment de la commande (ExtensionCommandContext) :
branch(entryId)— créer un fichier de session branchénavigateTree(targetId, { summarize? })— se déplacer dans l’arbre/fichier courant
Événements autour de la navigation dans l’arbre :
session_before_tree- reçoit
TreePreparation:targetIdoldLeafIdcommonAncestorIdentriesToSummarizeuserWantsSummary
- peut annuler la navigation
- peut fournir un payload de résumé utilisé à la place du résumeur intégré
- reçoit un
signald’abandon (chemin d’annulation par Échap)
- reçoit
session_tree- émet
newLeafId,oldLeafId - inclut
summaryEntrylorsqu’un résumé a été créé fromExtensionindique l’origine du résumé
- émet
Hooks de cycle de vie adjacents mais liés :
session_before_branch/session_branchpour le flux/branchsession_before_compact,session.compacting,session_compactpour les entrées de compaction qui affectent ensuite la reconstruction du contexte de l’arbre
Contraintes réelles et conditions limites
Section intitulée « Contraintes réelles et conditions limites »branch()ne peut pas ciblernull; utilisezresetLeaf()pour l’état racine-avant-première-entrée.branchWithSummary()prend en charge la ciblenullet enregistrefromId: "root".- Sélectionner la feuille courante dans le sélecteur d’arbre est une opération sans effet.
- Le résumé nécessite un modèle actif ; en son absence, la navigation avec résumé échoue immédiatement.
- Si le résumé est abandonné, la navigation est annulée et la feuille reste inchangée.
- Les sessions en mémoire ne renvoient jamais de chemin de fichier de branche depuis
createBranchedSession.
Compatibilité héritée encore présente
Section intitulée « Compatibilité héritée encore présente »Les migrations de session s’exécutent toujours au chargement :
- v1→v2 ajoute
id/parentIdet convertit l’ancre d’index de compaction en ancre d’identifiant - v2→v3 migre le rôle hérité
hookMessageverscustom
Le comportement actuel à l’exécution utilise la sémantique d’arbre version 3 après migration.