- Inicio
- Documentation
- Sesiones
- Arquitectura del árbol de sesión (actual)
Arquitectura del árbol de sesión (actual)
Referencia: session.md
Este documento describe cómo funciona actualmente la navegación del árbol de sesión: modelo de árbol en memoria, reglas de movimiento de hojas, comportamiento de ramificación e integración de extensiones/eventos.
Qué es este subsistema
Sección titulada «Qué es este subsistema»La sesión se almacena como un registro de entradas de solo adición (append-only), pero el comportamiento en tiempo de ejecución está basado en un árbol:
- Cada entrada que no sea de encabezado tiene
idyparentId. - La posición activa es
leafIdenSessionManager. - Añadir una entrada siempre crea un hijo de la hoja actual.
- La ramificación no reescribe el historial; solo cambia a dónde apunta la hoja antes de la siguiente adición.
Archivos clave:
src/session/session-manager.ts— modelo de datos del árbol, recorrido, movimiento de hojas, extracción de ramas/sesionessrc/session/agent-session.ts— flujo de navegación/tree, resumen, emisión de hooks/eventossrc/modes/components/tree-selector.ts— comportamiento interactivo de la UI del árbol y filtradosrc/modes/controllers/selector-controller.ts— orquestación del selector para/treey/branchsrc/modes/controllers/input-controller.ts— enrutamiento de comandos (/tree,/branch, comportamiento de doble escape)src/session/messages.ts— conversión de entradasbranch_summary,compactionycustom_messageen mensajes de contexto para el LLM
Modelo de datos del árbol en SessionManager
Sección titulada «Modelo de datos del árbol en SessionManager»Índices en tiempo de ejecución:
#byId: Map<string, SessionEntry>— búsqueda rápida de cualquier entrada#leafId: string | null— posición actual en el árbol#labelsById: Map<string, string>— etiquetas resueltas por id de entrada objetivo
APIs del árbol:
getBranch(fromId?)recorre los enlaces de padre hasta la raíz y devuelve la ruta raíz→nodogetTree()devuelveSessionTreeNode[](entry,children,label)- los enlaces de padre se convierten en arrays de hijos
- las entradas con padres faltantes se tratan como raíces
- los hijos se ordenan de más antiguo a más reciente por marca de tiempo
getChildren(parentId)devuelve los hijos directosgetLabel(id)resuelve la etiqueta actual desdelabelsById
getTree() es una proyección en tiempo de ejecución; la persistencia sigue siendo entradas JSONL de solo adición.
Semántica de movimiento de hojas
Sección titulada «Semántica de movimiento de hojas»Existen tres primitivas de movimiento de hojas:
-
branch(entryId)- Valida que la entrada exista
- Establece
leafId = entryId - No se escribe ninguna entrada nueva
-
resetLeaf()- Establece
leafId = null - La siguiente adición crea una nueva entrada raíz (
parentId = null)
- Establece
-
branchWithSummary(branchFromId, summary, details?, fromExtension?)- Acepta
branchFromId: string | null - Establece
leafId = branchFromId - Añade una entrada
branch_summarycomo hija de esa hoja - Cuando
branchFromIdesnull,fromIdse persiste como"root"
- Acepta
Comportamiento de navegación /tree (mismo archivo de sesión)
Sección titulada «Comportamiento de navegación /tree (mismo archivo de sesión)»AgentSession.navigateTree() es navegación, no bifurcación de archivos.
Flujo:
- Validar el objetivo y calcular la ruta abandonada (
collectEntriesForBranchSummary) - Emitir
session_before_treeconTreePreparation - Opcionalmente resumir las entradas abandonadas (resumen proporcionado por hook o resumidor integrado)
- Calcular el nuevo objetivo de hoja:
- al seleccionar un mensaje de usuario: la hoja se mueve a su padre, y el texto del mensaje se devuelve para prellenar el editor
- al seleccionar un custom_message: misma regla que para mensajes de usuario (hoja = padre, el texto prellena el editor)
- al seleccionar cualquier otra entrada: hoja = id de la entrada seleccionada
- Aplicar el movimiento de hoja:
- con resumen:
branchWithSummary(newLeafId, ...) - sin resumen y
newLeafId === null:resetLeaf() - en otro caso:
branch(newLeafId)
- con resumen:
- Reconstruir el contexto del agente desde la nueva hoja y emitir
session_tree
Importante: las entradas de resumen se adjuntan en la nueva posición de navegación, no en la cola de la rama abandonada.
Comportamiento de /branch (nuevo archivo de sesión)
Sección titulada «Comportamiento de /branch (nuevo archivo de sesión)»/branch y /tree son intencionalmente diferentes:
/treenavega dentro del archivo de sesión actual./branchcrea un nuevo archivo de rama de sesión (o un reemplazo en memoria para el modo no persistente).
Flujo de /branch orientado al usuario (SelectorController.showUserMessageSelector → AgentSession.branch):
- El origen de la rama debe ser un mensaje de usuario.
- El texto del usuario seleccionado se extrae para prellenar el editor.
- Si el mensaje de usuario seleccionado es raíz (
parentId === null): iniciar una nueva sesión mediantenewSession({ parentSession: previousSessionFile }). - En otro caso:
createBranchedSession(selectedEntry.parentId)para bifurcar el historial hasta el límite del prompt seleccionado.
Especificaciones de SessionManager.createBranchedSession(leafId):
- Construye la ruta raíz→hoja mediante
getBranch(leafId); lanza error si no existe. - Excluye las entradas
labelexistentes de la ruta copiada. - Reconstruye entradas de etiqueta frescas desde
labelsByIdresueltas para las entradas que permanecen en la ruta. - Modo persistente: escribe un nuevo archivo JSONL y cambia el manager a él; devuelve la nueva ruta del archivo.
- Modo en memoria: reemplaza las entradas en memoria; devuelve
undefined.
Reconstrucción de contexto e integración de resúmenes/custom
Sección titulada «Reconstrucción de contexto e integración de resúmenes/custom»buildSessionContext() (en session-manager.ts) resuelve la ruta activa raíz→hoja y construye el estado de contexto efectivo del LLM:
- Rastrea el último estado de thinking/model/mode/ttsr en la ruta.
- Maneja la última compactación en la ruta:
- emite primero el resumen de compactación
- reproduce los mensajes conservados desde
firstKeptEntryIdhasta el punto de compactación - luego reproduce los mensajes posteriores a la compactación
- Incluye entradas
branch_summaryycustom_messagecomo objetosAgentMessage.
session/messages.ts luego mapea estos tipos de mensaje para la entrada del modelo:
branchSummaryycompactionSummaryse convierten en mensajes de contexto con plantilla y rol de usuariocustom/hookMessagese convierten en mensajes de contenido con rol de usuario
Así, el movimiento en el árbol cambia el contexto al cambiar la ruta activa de la hoja, no mutando entradas antiguas.
Etiquetas y comportamiento de la UI del árbol
Sección titulada «Etiquetas y comportamiento de la UI del árbol»Persistencia de etiquetas:
appendLabelChange(targetId, label?)escribe entradaslabelen la cadena de la hoja actual.labelsByIdse actualiza inmediatamente (establecer o eliminar).getTree()resuelve la etiqueta actual en cada nodo devuelto.
Comportamiento del selector de árbol (tree-selector.ts):
- Aplana el árbol para navegación, mantiene el resaltado de la ruta activa y prioriza mostrar primero la rama activa.
- Soporta modos de filtro:
default,no-tools,user-only,labeled-only,all. - Soporta búsqueda de texto libre sobre el contenido semántico renderizado.
Shift+Labre la edición de etiquetas en línea y escribe medianteappendLabelChange.
Enrutamiento de comandos:
/treesiempre abre el selector de árbol./branchabre el selector de mensajes de usuario a menos quedoubleEscapeAction=tree, en cuyo caso también utiliza la UX del selector de árbol.
Puntos de contacto de extensiones y hooks para operaciones del árbol
Sección titulada «Puntos de contacto de extensiones y hooks para operaciones del árbol»API de extensión en tiempo de comando (ExtensionCommandContext):
branch(entryId)— crear archivo de sesión ramificadanavigateTree(targetId, { summarize? })— moverse dentro del árbol/archivo actual
Eventos alrededor de la navegación del árbol:
session_before_tree- recibe
TreePreparation:targetIdoldLeafIdcommonAncestorIdentriesToSummarizeuserWantsSummary
- puede cancelar la navegación
- puede proporcionar un payload de resumen utilizado en lugar del resumidor integrado
- recibe
signalde aborto (ruta de cancelación por Escape)
- recibe
session_tree- emite
newLeafId,oldLeafId - incluye
summaryEntrycuando se creó un resumen fromExtensionindica el origen del resumen
- emite
Hooks de ciclo de vida adyacentes pero relacionados:
session_before_branch/session_branchpara el flujo de/branchsession_before_compact,session.compacting,session_compactpara entradas de compactación que posteriormente afectan la reconstrucción del contexto del árbol
Restricciones reales y condiciones de borde
Sección titulada «Restricciones reales y condiciones de borde»branch()no puede apuntar anull; useresetLeaf()para el estado raíz previo a la primera entrada.branchWithSummary()soporta objetivonully registrafromId: "root".- Seleccionar la hoja actual en el selector de árbol es una operación sin efecto (no-op).
- El resumen requiere un modelo activo; si está ausente, la navegación con resumen falla inmediatamente.
- Si el resumen se aborta, la navegación se cancela y la hoja permanece sin cambios.
- Las sesiones en memoria nunca devuelven una ruta de archivo de rama desde
createBranchedSession.
Compatibilidad heredada aún presente
Sección titulada «Compatibilidad heredada aún presente»Las migraciones de sesión siguen ejecutándose al cargar:
- v1→v2 añade
id/parentIdy convierte el ancla de índice de compactación a ancla de id - v2→v3 migra el rol heredado
hookMessageacustom
El comportamiento actual en tiempo de ejecución utiliza la semántica de árbol versión 3 después de la migración.