Inicio / Artículos

Cuatro tipos de memoria que necesita tu agente de programación (y solo uno está implementado).

Los grafos de código le dan estructura a los agentes, pero los ingenieros también necesitan razones, historial y procedimientos. Un análisis de cuatro tipos de memoria, niveles de confianza y cómo combinarlos.

Editor: REACTAPP.TOP11 min de lectura2627 palabras

Fuentes: Your Coding Agent Is Always the New Guy on Monday.

Últimamente ha aparecido una familia de herramientas cuyo objetivo es impedir que los agentes de programación basados en IA examinen los repositorios. Elas analizan el código con tree-sitter, almacenan símbolos y conexiones en SQLite y las ofrecen a través de MCP, o bien ejecutan servidores de lenguaje que brindan al agente una semántica similar a la de un IDE real. Las mejoras reportadas son significativas y la idea es sólida.

Todas ellas responden al mismo tipo de preguntas: ¿quién llama a esto?, ¿qué tipo es ese?, ¿qué se rompería con un cambio?

Ahora observe cómo un ingeniero experimentado aborda un cambio en un área desconocida. Encontrar las funciones que lo invocan le toma unos treinta segundos. Luego sigue una hora de preguntas que no abordan estas herramientas. ¿Por qué se construyó de esta manera? ¿Qué sucedió la última vez que alguien intentó esto? ¿Cuál es la secuencia habitual aquí? ¿Debo regenerar el código antes de ejecutar las pruebas?

Cuatro preguntas, cuatro tipos diferentes de memoria. Casi todas las configuraciones de agentes actuales implementan una de ellas y la denominan contexto.

La prueba que las separa

Antes de la taxonomía existe la prueba que la genera. Para cualquier relación que se quiera que conozca el agente, hay que preguntarse si ya existe un artefacto formal que la registre y que pueda ser leído por una herramienta determinista.

Si es así, analícelo. El artefacto podría ser un compilador, un verificador de tipos, un lector de bytecode o un seguimiento distribuido. Utilizar un modelo de lenguaje para extraer relaciones que ya conoce un analizador es más lento, más costoso y menos preciso, además de descartar la información fiable que ya se posee.

Si no es así, la relación solo existe en texto escrito y en el juicio humano, y la extracción mediante modelos es la única forma de obtenerla.

Esto no se trata de una separación entre código y documentos. Se trata de si existe o no una gramática. La separación es lo suficientemente clara como para que los diferentes tipos de conocimiento requieran un método de extracción distinto, una frecuencia de actualización diferente y una semántica de confianza distinta. Al procesarlos todos a través de un único pipeline, al menos uno se manejará de manera incorrecta.

1. Memoria estructural: impacto de un cambio

Se trata del conocimiento del agente sobre el código en sí. Es la memoria que todos construyen, y el error habitual es alimentarla únicamente con información de una sola fuente.

  • Tree-sitter ofrece amplitud: un inventario de nodos y segmentos, soporte para más de 40 lenguajes, tolerancia a errores y medición en milisegundos por archivo. No resuelve tipos. Puede indicar que la línea 3 contiene una llamada a algo llamado execute, pero no a qué execute se refiere. La coincidencia de nombres no constituye un análisis de nivel compilador, y esta limitación se hace evidente en códigos polimórficos, en capas o con inyección de dependencias.
  • LSP garantiza corrección a través de textDocument/definition, references y implementation, respaldado por un verdadero verificador de tipos. Inicia lentamente, requiere una compilación funcional y debe mantenerse fuera de la ruta de consultas, ya que un servidor de lenguaje sin carga previa arruina cualquier margen de latencia. Úselo como herramienta de enriquecimiento en segundo plano.
  • Código bytecode muestra el código que solo existe una vez que se ha ejecutado la compilación: consultas derivadas de Spring Data, proxies CGLIB, mapeadores MapStruct, cualquier cosa que haya escrito un procesador de anotaciones. El código fuente no muestra nada de esto. target/classes lo muestra todo.
  • Salida AOT o /actuator/beans muestra cómo se conecta la inyección de dependencias, es decir, qué clase concreta cumple qué interfaz bajo qué perfil. Leer el código fuente no te lo puede decir, porque la respuesta no está allí.
  • Rastreos distribuidos proporcionan información sobre las conexiones entre servicios y los límites asíncronos: desde el controlador hasta HTTP y luego a otro servicio, desde el productor hasta un tema y luego al consumidor. Cada servidor de lenguaje se detiene en los límites de los módulos, y los sistemas distribuidos consisten básicamente en eso. Si utilizas OpenTelemetry, ya estás recopilando un grafo de llamadas entre servicios y descartándolo posteriormente.
  • La decisión de diseño más importante es asignar un nivel a cada arista:

    • resolved: un verificador de tipos lo confirmó, por lo que es seguro actuar en base a ello.
    • observed: fue generado por una ejecución real, lo que constituye la verdad objetiva para los caminos analizados.
    • inferred: se identificó únicamente por el nombre. Probablemente correcto, pero sin verificar.
    • ambiguous: existen varios candidatos. Se devuelven todos y no se elige ninguno.
    • dynamic: se calcula en tiempo de ejecución y es indecidible, por lo que se registra como tal y no se descarta.

    Si se le da a un agente un grafo sin niveles, tratará cada arista como un hecho y luego realizará refactores basándose en conjeturas. Si se le indica que una arista es inferred, la verificará antes de actuar. Esa columna representa la diferencia entre un sistema que engaña de forma silenciosa y uno que maneja las situaciones de manera más adecuada.

    Una consecuencia que pocas implementaciones mencionan abiertamente: en una base de código en capas de Spring o Django, el análisis de impacto proporciona un límite inferior, nunca una solución completa. Decir “17 llamadas, 3 envíos de interfaces sin resolver, 1 método generado” es seguro. Decir solo “17 llamadas” es engañoso.

    La tasa de abandono ocurre cada hora y una reconstrucción tarda segundos.

    2. Memoria de conocimiento: las razones detrás del código

    Esta memoria alberga los razonamientos, y se encuentra en documentos que ningún analizador puede leer: registros de decisiones, análisis posteriores a incidentes, hilos de solicitudes de integración, tickets, RFCs, documentos de diseño y mensajes de commit con contenido real.

    Considere un conteo de intentos de reintentar de 3 en un archivo de configuración. Su explicación está distribuida en un ADR de 2023, un análisis posterior sobre instrucciones de liquidación duplicadas y un hilo de conversación en el que alguien describió el SLA de una contraparte. La relación entre ellos, es decir, una política que surge debido a un incidente y que restringe una integración, se menciona en tres documentos, cada uno redactado de manera diferente.

    GraphRAG es una buena herramienta para esto, y es útil saber qué hace. Hay seis pasos:

    1. Chunking divide los documentos en unidades de texto de manera determinista.
    2. Extraction es un trabajo del LLM: un modelo lee cada bloque y genera entidades y relaciones.
    3. Descripción y resumen también es un trabajo del LLM, que combina la misma entidad descrita de cincuenta maneras en una sola descripción.
    4. Clustering de Leiden no necesita un modelo. Divide el grafo en comunidades anidadas según la modularidad, agrupando aquellas cuyos bordes internos superan con creces a los que salen hacia el exterior.
    5. Informes de comunidades son resúmenes generados por modelos de lenguaje grande para cada comunidad, escritos de abajo hacia arriba, con los niveles superiores creados a partir de los informes inferiores.
    6. Embebido produce vectores que sirven como puntos de entrada para las consultas.

    Dos conceptos merecen una atención más detallada. Un arista en este contexto no es un puntero escrito; contiene una descripción en prosa proporcionada por el modelo, junto con un peso y referencias de origen, de modo que cada arista registra la interpretación del modelo de una oración. De manera similar, una comunidad es algo más que un subgrafo arbitrario, ya que un conjunto aleatorio de nodos puede no compartir ninguna arista en absoluto, dejando puntos aislados sin nada que resumir. Leiden selecciona los clústeres cuyas aristas inducidas son densas, lo cual permite generar un informe coherente.

    La ventaja es la búsqueda global. En lugar de obtener los fragmentos más cercanos a la consulta, el sistema plantea la pregunta a cada informe de la comunidad por separado y combina las respuestas parciales. Las propiedades de todo el corpus se vuelven recuperables: una pregunta sobre los modos de fallo recurrentes en el camino de resolución tiene una respuesta que no se encuentra en ningún informe póstumo en particular. El RAG vectorial ordinario no puede ofrecer eso, por más precisamente que se ajusten los fragmentos.

    El proceso de actualización se realiza mensualmente y las reconstrucciones son costosas: tres de los seis pasos implican llamadas a modelos LLM, por lo que el costo de indexación aumenta con el tamaño del corpus, la cantidad de pasadas de recopilación y el precio del modelo. Esto es aceptable ya que el corpus apenas cambia.

    3. Memoria episódica: qué sucedió en intentos anteriores

    Este registro guarda la historia propia del agente en este repositorio en particular. Tocar SettlementService la última vez rompió tres pruebas de asignación. Modificar esa interfaz sin regenerar los mapeadores provoca que la compilación falle. Las dos PR más recientes que omitieron la capa de validación fueron rechazadas.

    También cuenta con una propiedad que ninguno de los índices semánticos puede reclamar: su verdad fundamental es determinista. Un análisis posterior incluye una afirmación humana extraída por un modelo. “El agente modificó X y CI se puso en rojo” es un resultado evaluado, decidido por el sistema de compilación. Para las preguntas que aborda, eso convierte a la memoria episódica en la fuente más fiable en este entorno.

    Hay tres aspectos del diseño que son fáciles de error:

    • Omite GraphRAG aquí. Los episodios siguen una línea temporal y tienen resultados. Agruparlos por densidad de enlaces es la estructura incorrecta; lo que se necesita es un ponderamiento según la recencia, la frecuencia y el éxito o fracaso.
    • Se requiere la expulsión de datos. Los índices semánticos pueden prescindir de ella, pero este no puede. Una lección obtenida al refactorizar dos arquitecturas hace tiempo no solo está desactualizada, sino que también orienta al agente en la dirección equivocada.
    • Los errores se acumulan. Supongamos que el agente razonó incorrectamente y registró el resultado. Este se convierte en un hecho recordado y cada sesión posterior lo hereda. Mantén el resultado (el CI falló, la PR fue rechazada) con alta confianza, mientras que la conclusión del propio agente se mantenga con mucha menor confianza; luego deja que la conclusión pierda validez mientras el resultado permanece.

    La tasa de abandono se calcula por sesión.

    4. Memoria procedural: en realidad no es un índice

    La memoria procedural abarca la forma en que se realizan las tareas: la orden de compilación, la lista de verificación, el orden de migración, el estilo del proyecto. El primer impulso de las personas es hacer que sea buscable, pero ese impulso debe ser resistido.

    Cualquier elemento siempre relevante se ve perjudicado al depender de la búsqueda. Debería encontrarse en el prompt del sistema o en un AGENTS.md, cargándose en cada sesión sin necesidad de búsqueda. Colocarlo tras el sistema de búsqueda genera un problema, ya que el agente no sabe dónde buscarlo, y confiar en que el agente encuentre algo por sí solo no constituye un mecanismo efectivo.

    Cualquier elemento que solo sea importante en ciertas situaciones debería ser una herramienta con un desencadenante específico. Por ejemplo, ejecutar el conjunto de integraciones debería ser algo que el agente pueda activar directamente, y no un documento del que deba recordar buscarlo por sí mismo.

    El caso que merece atención es el flujo de trabajo basado en conocimientos. Supongamos que el agente descubre que en este repositorio los mapeadores deben ser regenerados antes de ejecutarse las pruebas, y que ese orden sigue funcionando. Esa es una procedura derivada de episodios. Guárdela como una regla candidata con un recuento de éxitos, promuévela a las instrucciones siempre cargadas una vez que se demuestre su eficacia, y conserva los episodios originales para poder auditar la promoción.

    Por lo tanto, la memoria procedural no es un cuarto índice. Es lo que queda después del tercero, extraído de la recuperación y registrado como instrucciones.

    La unión: un espacio de nombres de símbolos

    Los tres recuerdos recuperados convergen en la identidad de un símbolo, y nadie ha resuelto esta parte. Los nodos estructurales utilizan nombres completamente calificados. Las entradas episódicas apuntan al símbolo que el agente manejó realmente, por lo que se unen con precisión. Las entidades en el índice de conocimiento son cadenas que un modelo extrajo de la prosa, como “SettlementService”, “el servicio de liquidación”, “la ruta de conciliación de liquidaciones” o “la restricción SLA de la contraparte”, y mapearlas a símbolos reales es una tarea de coincidencia difusa.

    Suponer lo contrario es exactamente el fallo que existen los niveles para prevenir, por lo que la unión cuenta con su propia escala:

    • exact: el nombre aparece literalmente y apunta a un solo símbolo.
    • strong: una frase normalizada apunta claramente a una clase.
  • weak: un concepto que abarca varias clases cuyos límites son vagos. Se vuelve a presentar un conjunto de símbolos y el agente los verifica.
  • unresolved: una restricción real para la que no existe ningún símbolo correspondiente en ningún lugar. “El SLA de la contraparte” es una verdadera restricción arquitectónica que el código no representa.
  • En cuanto a ese último nivel, consérvelo, devuélvelo y no fuerce una coincidencia. Una restricción no resuelta pero legible es mejor que un vínculo con un símbolo incorrecto, y forzar coincidencias es la forma en que un agente modifica con confianza la clase equivocada.

    Lo que realmente pregunta el agente

    La superficie de consultas podría verse así:

    what_breaks(symbol)        → structural: resolved callers, tiered
    why_is_this(symbol)        → knowledge: decisions, incidents, constraints
    what_happened_before(sym)  → episodic: past attempts and their outcomes
    related_by_intent(concept) → resolve a phrase to a symbol set, tiered
    

    Luego surge una pregunta a la que ninguno de los tres puede responder por sí solo: “Estoy a punto de cambiar la política de intentos en el proceso de liquidación. ¿Qué se rompe, por qué son tres y alguien ha intentado esto antes?”

    Los mapas de conocimiento relacionan “política de intentos” con SettlementService.retry en el nivel strong y devuelven la ADR y el análisis posterior. Structural toma ese símbolo y muestra sus llamantes, incluyendo dos servicios visibles solo en los registros de trazado. Episodic indica que un cambio similar del último trimestre dañó las pruebas de asignación.

    Esa es una respuesta construida a partir de tres tipos de evidencia, cada una generada por el mecanismo capaz de producirla y cada una etiquetada con el grado de confianza que se le puede tener.

    Qué cuesta

    • La mayoría de las colecciones de documentos son insuficientes. Nueve ADRs y commits que indican “arreglar error” no ofrecen nada con lo que trabajar al índice de conocimiento. Examina qué tienes antes de comenzar a desarrollar.
    • La resolución de entidades nunca es perfecta. La escalera te ayuda a lidiar con ello, pero no lo soluciona. Probablemente el nivel weak será el más concurrido, así que planifica realizar verificaciones en lugar de confiar ciegamente.
    • Comienza con algo limitado. Empieza con tree-sitter más LSP. Agrega bytecode cuando el código generado comience a dar respuestas incorrectas, y rastreos cuando las consultas crucen diferentes servicios. Añade el índice de conocimiento solo cuando el corpus merezca ser indexado.
  • Nadie lo ha construido todo, lo cual puede ser una oportunidad o una advertencia. Las piezas correspondientes se encuentran en comunidades diferentes: las personas especializadas en inteligencia de código no leen los artículos sobre GraphRAG, quienes trabajan con GraphRAG no piensan en verificadores de tipo, y quienes se dedican a la arquitectura de memoria suelen trabajar en asistentes de chat en lugar de en bases de código.
  • Piense en la incorporación de un nuevo empleado

    Supongamos que un ingeniero senior se une el lunes y recibe acceso al repositorio, pero nada más. Puede leer cada línea, rastrear cada llamada y ejecutar las pruebas. Para el viernes sigue siendo más lento que un colega contratado un año antes, y no se debe a su capacidad.

    Ese colega de hace un año cuenta con tres cosas que nunca estuvieron dentro del repositorio, algo que el recién llegado no tiene.

    Una de las razones son las decisiones inusuales. Se intenta tres veces debido a un incidente en marzo, y existe una solución temporal para la conciliación porque la API de una contraparte funciona mal los viernes. Ningún comentario lo indica; está mencionado en un ADR, en un análisis posterior y en una conversación.

    Otra razón es la historia: alguien intentó simplificar esa interfaz en el segundo trimestre y las pruebas de asignación dejaron de funcionar durante una semana, por lo que la gente prefiere dejarla tal como está.

    La tercera razón es la forma local de trabajar: se generan nuevos mapeadores antes de ejecutar las pruebas. Las solicitudes de cambio que omiten la validación son rechazadas. Nadie consulta un documento al respecto; simplemente así es como trabaja el equipo.

    Un agente que solo cuenta con un grafo de código es aquel contratado el lunes: recuerda todo el repositorio, pero no entiende por qué las cosas son como son, no tiene memoria de los intentos anteriores ni conocimiento de las costumbres locales. Cada sesión marca el inicio de un nuevo lunes.

    Ese es el problema, y ninguna herramienta es la culpable. Un grafo de código responde bien a su propia pregunta, y las implementaciones actuales son sólidas. Lo que falta es que tres de las cuatro cosas de las que depende un ingeniero competente no están presentes en absoluto en el conjunto de herramientas, por lo que la cuarta se ve obligada a asumir todo el trabajo.

    La versión de una línea

    La versión breve: el compilador se encarga de la estructura, ya que esta es determinista. Un modelo se ocupa de la intención, ya que esta consiste en texto no estructurado. El sistema de compilación se basa en la experiencia. El procedimiento se extrae de la experiencia y se carga de antemano, sin ser recuperado posteriormente.

    Ninguna herramienta de esta categoría comete errores en su memoria. Cada una procesa una cosa y se detiene, y nada las conecta entre sí. Un grafo mejor no es la solución. La solución son cuatro memorias, cada una sincera respecto a su nivel de fiabilidad, mapeadas en un espacio de nombres compartido de símbolos.

    Los términos y etapas del pipeline de GraphRAG siguen la implementación de Microsoft Research. Los costos varían ampliamente según el tamaño del corpus, la configuración de recolección de datos y la elección del modelo, por lo que realice pruebas con sus propios datos antes de tomar una decisión.

    Una verificación de quince minutos

    Elija una interfaz en su propio código que cuente con cinco o más implementadores conocidos, como PaymentProcessor o NotificationChannel, uno de los cuales esté conectado mediante @Autowired y no construido directamente. Pregunte a su grafo de código quién lo llama y cuente los resultados.

    Las pruebas publicadas para esta categoría miden tokens, llamadas a herramientas y latencia. Hasta donde se sabe, ninguna mide si el grafo de llamadas devuelto es correcto. En un código con interfaces declaradas e implementadores conocidos, se puede realizar una verificación de cinco minutos comparándolo con los datos reales que ya posee.