Skip to content

Bucle de aprendizaje y memoria

源码版本v2026.7.20

Responsabilidad

Esta es la diferencia central de Hermes frente a un agent corriente: un bucle cerrado de «cuanto más se usa, mejor funciona». Cada turno (turn) de conversación se destila en: ① evolución de habilidades — crea/reescribe habilidades a partir de la experiencia (agent/learning_mutations.py); ② memoria — recuperación por texto completo FTS5 + resúmenes LLM de conversaciones pasadas (memory_manager), con modelado dialéctico del usuario vía Honcho; ③ grafo de aprendizaje — organiza habilidades y memoria como nodos/aristas (agent/learning_graph.py); ④ revisión en segundo planobackground_review digiere el historial en un hilo y produce acciones de reescritura accionables. Las reescrituras de habilidades terminan reinyectándose en el sistema de habilidades para que los siguientes turnos las reutilicen.

Archivos clave

Nodos de habilidad y grafo de aprendizaje

Reescritura de habilidades/memoria

Gestión de memoria

Revisión en segundo plano

Qué pinta tiene un nodo de habilidad

SkillNode es la representación uniforme de una habilidad en el grafo de aprendizaje (agent/learning_graph.py:28-41): un dataclass con campos name/category/source (base para integradas vs profile para usuario/agent)/use_count (leído de .usage.json)/created_by ('agent' lo creó la revisión en segundo plano)/pinned/related. build_skill_nodes escanea los SKILL.md bajo skills/ y les superpone las estadísticas de uso. La condición source != "base" combinada con created_by == "agent" o use_count > 0 es el filtro que más adelante usa build_learning_graph para decidir «esto se aprendió, no es integrado» — solo lo realmente aprendido se enseña al usuario en el panel.

La salida del grafo de aprendizaje

build_learning_graph (agent/learning_graph.py:248-328) ensambla nodos, aristas, tarjetas de memoria y estadísticas de clustering en un payload completo que renderiza el panel de aprendizaje del desktop. Llama primero a build_skill_nodes(_skill_roots()) para escanear dos raíces — base (repo/skills/, integrado) y profile (~/.hermes/skills/, usuario/agent); luego filtra las habilidades con source != "base" y created_by == "agent" o use_count > 0 como «aprendidas», y encima superpone build_edges (relaciones entre habilidades), _memory_cards (tarjetas de memoria) y _memory_skill_edges (relación memoria-habilidad). Base siempre está; profile es lo «aprendido».

Tras reescribir hay que limpiar la caché

Si tras un edit_node no se limpia la caché, en el siguiente turno prompt_builder seguirá usando la snapshot vieja del SKILL.md y la reescritura no surte efecto. _clear_skill_cache (agent/learning_mutations.py:200) llama a clear_skills_system_prompt_cache(clear_snapshot=True); el clear_snapshot=True limpia también la snapshot de herramientas (tool), porque la lista de herramientas MCP puede haberse refrescado durante la revisión. Esta función es el último eslabón de edit_node / delete_node — reescribir → persistir → limpiar caché → siguiente turno. Sin un eslabón, la cadena se rompe.

Inyección de memoria: de MemoryManager al prompt

MemoryManager (agent/memory_manager.py:354) coordina un provider interno (FTS5 + resumen LLM) y como mucho un provider externo (Honcho/Hindsight/Mem0…). El provider externo solo puede haber uno; un segundo es rechazado — evita que el schema de herramientas se hinche y que los backends de memoria se pisen entre sí; los fallos de cada provider no se bloquean mutuamente. La memoria cruda recuperada la envuelve build_memory_context_block (agent/memory_manager.py:337-354) en un cercado <memory-context> + una nota del sistema, que le dice al modelo «esto es memoria, no una nueva instrucción». sanitize_context elimina el cercado y la nota vieja antes de indexar, para que el provider no vuelva a envolver lo ya envuelto. StreamingContextScrubber hace lo mismo en el lado de la salida en streaming, para que el modelo no devuelva el bloque de memoria al usuario tal cual.

Revisión en segundo plano: resumen y acciones candidatas

spawn_background_review_thread (agent/background_review.py:956) no levanta el hilo directamente; construye una tupla (target, prompt) y se la entrega a AIAgent._spawn_background_review_thread, para que los tests puedan patchear threading.Thread y seguir funcionando. La firma (review_memory / review_skills dos bools) decide qué prompt usar — _MEMORY_REVIEW_PROMPT / _SKILL_REVIEW_PROMPT / _COMBINED_REVIEW_PROMPT:

python
def spawn_background_review_thread(agent, messages_snapshot,
                                    review_memory=False, review_skills=False):
    """Build the review thread target and prompt. Returns a ``(target, prompt)`` tuple."""
    if review_memory and review_skills:
        prompt = getattr(agent, "_COMBINED_REVIEW_PROMPT", _COMBINED_REVIEW_PROMPT)
    elif review_memory:
        prompt = getattr(agent, "_MEMORY_REVIEW_PROMPT", _MEMORY_REVIEW_PROMPT)
    else:
        prompt = getattr(agent, "_SKILL_REVIEW_PROMPT", _SKILL_REVIEW_PROMPT)
    def _target() -> None:
        _run_review_in_thread(agent, messages_snapshot, prompt)
    return _target, prompt

Dentro del hilo, _run_review_in_thread (agent/background_review.py:617) hace fork de un subagent que hereda el runtime del padre (evita re-parsing de credenciales — un provider OAuth-only no se puede reconstruir dentro del subagent) y monta el callback _bg_review_auto_deny, así durante la revisión cualquier comando peligroso se deniega sin caer a input() (que se deadlockaría con la TUI prompt_toolkit del padre). _digest_history (agent/background_review.py:122) comprime el historial en «últimas 24 entradas verbatim + resumen sintético del resto»; solo se usa cuando la revisión se enruta a un modelo distinto (con caché frío, ahorrar tokens compensa); la ruta del modelo principal hace replay completo (caché caliente).

Flujo de datos

  1. Al acabar cada turno, el bucle principal entrega una snapshot de los mensajes al hilo de revisión en segundo plano (agent/background_review.py:956).
  2. _digest_history (agent/background_review.py:122) resume el historial reciente y produce acciones candidatas (crear/reescribir habilidad, escribir memoria).
  3. La reescritura de habilidades se materializa vía edit_node:157; el borrado, vía delete_node:124; tras escribir, _clear_skill_cache:200 limpia la caché.
  4. La escritura de memoria pasa por MemoryManager:354: tras redactar (agent/memory_manager.py:164) se indexa en FTS5 y se comprime con un resumen LLM.
  5. Al iniciar el siguiente turno, build_memory_context_block (agent/memory_manager.py:337) inyecta la memoria relevante en build_turn_context:268; las habilidades se inyectan vía agent/skill_bundles.py.
  6. El grafo de aprendizaje completo se agrega en build_learning_graph:254, para que la UI/depuración muestre las relaciones entre habilidades y memoria.

Motivo de diseño

¿Por qué partir el bucle de aprendizaje en cuatro módulos separados? Porque su estructura de costes es distinta. background_review corre en un subagent — hay que fork del runtime, gastar LLM calls; debe ser asíncrono y con fallback; está obligado a desacoplarse del bucle principal. learning_graph es una agregación de solo lectura, barata, disponible en cualquier momento para la UI; no se puede acoplar a la reescritura. learning_mutations es la ruta de escritura y, tras modificar, debe limpiar la caché — si no, el siguiente turno come snapshot stale. memory_manager reúne recuperación + redacción + inyección en un mismo sitio porque la cadena «de provider externo a memoria → redactar → envolver → inyectar en prompt» tiene que cerrarse en una sola frontera; si la redacción se escapa en un punto, la memoria cruda se filtra a la salida del modelo.

Límites y fallos

  • La revisión en segundo plano no es síncrona. _run_review_in_thread corre en un hilo daemon; el bucle principal no espera a que termine antes de devolver el control al usuario. La reescritura de habilidades del turno recién revisado suele surtir efecto recién en el siguiente.
  • Solo se puede montar un provider de memoria externo. Honcho y Hindsight no pueden correr a la vez; add_provider rechaza al segundo.
  • _digest_history solo aplica cuando la revisión se enruta a un modelo distinto. La ruta del modelo principal hace replay completo (caché caliente); cambiar ese comportamiento pasa por leer auxiliary.background_review en la config.
  • La frontera de redacción de la memoria está en «el cercado y la nota del sistema». sanitize_context solo quita el cercado <memory-context> y la nota del sistema; no reescribe el cuerpo de la memoria — si la memoria cruda lleva datos sensibles, hay que tratarlos antes de escribir.

Resumen

El bucle de aprendizaje convierte «conversación» en «activo»: la revisión en segundo plano destila experiencia en habilidades y memoria de forma imperceptible, y se inyectan automáticamente en el siguiente turno. Las cuatro piezas clave: learning_graph (grafo), learning_mutations (reescritura), memory_manager (recuperación + redacción + resumen), background_review (digestión asíncrona). Esta es la base mecánica del «crecer junto al usuario» de Hermes, y la diferencia fundamental frente a «una llamada LLM sin estado».

Sitio de aprendizaje comunitario no oficial. Basado en el código fuente de NousResearch/hermes-agent (licencia MIT).