April 22, 2026 • Versión: v0.12.0

[Guía de mejora de Memory v2: recorrido asociativo, ponderación de importancia y olvido basado en acceso] - Memory v2 Enhancement Guide: Associative Traversal, Salience Weighting, and Access-Based Forgetting

Guía arquitectónica para extender Memory v2 de OpenClaw con recorrido de co-ocurrencia de entidades, retención ponderada por importancia y decaimiento basado en acceso para mejorar la precisión de recuperación en despliegues de agentes de larga duración.

🔍 Síntomas

Limitaciones Actuales de Recuperación en Memory v2

Los agentes que se ejecutan durante períodos prolongados (días a semanas) muestran una coherencia contextual degradada al utilizar los mecanismos de recuperación existentes. Los siguientes síntomas se manifiestan en despliegues en producción:

Síntoma 1: Recuperación Léxica Superficial

Al consultar información conceptualmente relacionada a lo largo del tiempo, el agente recupera solo coincidencias a nivel superficial:

$ openclaw memory recall "app performance improvements"
---
RETRIEVED FACTS (3):
- W(s=0.3) @config: Updated heartbeat interval from 5m to 30m.
- W(s=0.3) @config: Increased worker pool size to 4.
- W(s=0.3) @api: Added rate limiting middleware.

EXPECTED: Connection to Week 2 debugging session about slow database queries
ACTUAL: Generic config changes only

El agente no puede atravesar la cadena implícita: “performance” → “slow endpoint” → “database query” → “Sarah’s expertise.”

Síntoma 2: Ponderación Igualitaria de Recuerdos Dispares

Todos los hechos almacenados compiten igualmente por el presupuesto de contexto independientemente de su importancia:

$ openclaw memory recall "any recent updates"
---
RETRIEVED (k=10, context budget: 4KB):

1. W(s=0.3) @config: Updated heartbeat interval from 5m to 30m.
2. W(s=0.3) @config: Increased worker pool size to 4.
3. W(s=0.3) @config: Set log level to INFO.
4. W(s=0.3) @config: Disabled telemetry opt-in.
5. B(s=0.3) @Sarah @project: Sarah announced she's leaving next month.
6. B(s=0.3) @user @identity: User prefers morning standups.
...

CRITICAL GAP: No salience differentiation. Sarah's departure competes equally with log level changes.

Síntoma 3: Crecimiento Ilimitado del Índice Sin Decaimiento

Después de 30+ días de operación continua:

$ sqlite3 ~/.openclaw/memory.db "SELECT COUNT(*) FROM facts;"
487

$ sqlite3 ~/.openclaw/memory.db "SELECT COUNT(*) FROM facts WHERE last_accessed > datetime('now', '-7 days');"
12

Solo el 2.5% de los hechos fueron accedidos en la última semana, sin embargo, los 487 compiten en la puntuación de recuperación. El trabajo de reflexión debe procesar un conjunto cada vez mayor sin señal de priorización.

Síntoma 4: Contaminación por Nodos Hub (Referencia del Benchmark CLS-M)

Las entidades que aparecen en muchos hechos absorben la activación de recuperación:

$ sqlite3 ~/.openclaw/memory.db "SELECT entity, COUNT(*) as cnt FROM fact_entities GROUP BY entity ORDER BY cnt DESC LIMIT 5;"
entity|cnt
@Peter|203
@heartbeat|57
@api|89
@config|112
@system|78

La traversía directa de entidades a través de @Peter (203 hechos) diluye la señal para conexiones específicas y relevantes.

🧠 Causa raíz

Brechas Arquitectónicas en el Diseño Actual de Memory v2

El sistema de recuperación actual carece de tres mecanismos críticos que son esenciales para mantener la precisión en despliegues de larga duración:

Brecha 1: Recuperación de Entidades de Un Solo Salto

El modelo de recuperación consciente de entidades existente devuelve hechos directamente etiquetados con la entidad de consulta pero no atraviesa recursivamente entidades co-ocurrentes:

-- Current query (single-hop)
SELECT f.content, f.salience 
FROM facts f
JOIN fact_entities fe ON f.id = fe.fact_id
JOIN entities e ON fe.entity_id = e.id
WHERE e.name = 'performance';

-- Returns only: facts explicitly tagged @performance
-- Misses: facts about @database that co-occur with @performance across the corpus

Esto es arquitectónicamente correcto para búsqueda exacta de entidades (“dime sobre X”) pero insuficiente para consultas exploratorias donde el agente descubre conexiones implícitas.

Brecha 2: Ausencia de Seguimiento de Importancia en el Momento de Retención

La visión fundamental del bucle de control de Letta es que el agente que tiene la experiencia debe decidir qué retener. Sin embargo, sin un parámetro de importancia en las llamadas de retención, esta decisión es binaria (conservar/descartar) en lugar de gradual:

-- Current (binary)
openclaw memory retain "Sarah is leaving the company next month"

-- Missing salience metadata that would distinguish:
-- A config file tweak (s=0.2)
-- A critical team change (s=0.95)

Sin importancia, el trabajo de reflexión no puede distinguir la señal del ruido—debe usar como proxy la recurrencia o la frecuencia de acceso, que son proxies deficientes para la importancia real.

Brecha 3: Sin Mecanismo de Decaimiento Basado en Acceso

El diseño actual trata todos los hechos históricos como igualmente recuperables independientemente de los patrones de compromiso:

-- No temporal or access-based scoring
SELECT content FROM facts 
ORDER BY created_at DESC  -- Only recency, not relevance
LIMIT 10;

Esto crea tres problemas en cascada:

  1. Degradación de precisión: A medida que crece el índice, la proporción de hechos relevantes vs. irrelevantes disminuye
  2. Ineficiencia del trabajo de reflexión: El procesador de reflexión debe evaluar un corpus cada vez mayor sin priorización
  3. Amplificación del ruido de hubs: Entidades de alto grado (apareciendo en 100+ hechos) dominan la traversía sin decaimiento

Análisis de Causa Raíz del Prototipo CLS-M

El prototipo CLS-M (132 nodos, 802 aristas) validó estas brechas empíricamente:

  • El recall fue aceptable (65%) pero la precisión fue pobre (35%)—lo que significa que el 65% del contenido recuperado era ruido
  • Los nodos hub destruyeron la precisión: El nodo heartbeat tenía 57 aristas, absorbiendo activación que debería haber ido a nodos específicos
  • El decaimiento basado en tiempo falló: Un hecho de hace 3 meses que es accedido semanalmente debería permanecer prominente; la edad por sí sola no es una señal de relevancia

La solución no es construir un grafo de conocimiento separado sino extender el índice SQLite existente con:

  1. Seguimiento de co-ocurrencia de entidades mediante ponderación de frecuencia inversa de documentos (IDF)
  2. Importancia como parámetro de primera clase en operaciones de retención
  3. Decaimiento basado en acceso que se reinicia en la recuperación (no decaimiento puramente basado en edad)

🛠️ Solución paso a paso

Fase 1: Extensiones de Esquema para Índice SQLite

Agregar columnas de importancia y seguimiento de acceso al esquema existente:

-- Migration: add_salience_and_access_tracking.sql

-- 1. Add salience column (0.0 to 1.0, default 0.5)
ALTER TABLE facts ADD COLUMN salience REAL DEFAULT 0.5;

-- 2. Add access tracking columns
ALTER TABLE facts ADD COLUMN last_accessed_at DATETIME DEFAULT NULL;
ALTER TABLE facts ADD COLUMN access_count INTEGER DEFAULT 0;

-- 3. Create index for access-based queries
CREATE INDEX idx_facts_last_accessed ON facts(last_accessed_at);
CREATE INDEX idx_facts_salience ON facts(salience);

-- 4. Precompute entity frequencies for IDF weighting
CREATE TABLE entity_stats AS
SELECT 
    e.id,
    e.name,
    COUNT(fe.fact_id) as fact_count,
    1.0 / LOG(COUNT(fe.fact_id) + 1) as idf_weight
FROM entities e
LEFT JOIN fact_entities fe ON e.id = fe.entity_id
GROUP BY e.id;

CREATE INDEX idx_entity_stats_fact_count ON entity_stats(fact_count);

Fase 2: Tabla de Co-ocurrencia de Entidades

Construir matriz de co-ocurrencia desde el índice de hechos existente:

-- Migration: build_entity_cooccurrence.sql

-- 1. Create co-occurrence table
CREATE TABLE entity_cooccurrence (
    entity_id_1 INTEGER NOT NULL,
    entity_id_2 INTEGER NOT NULL,
    cooccur_count INTEGER DEFAULT 1,
    cooccur_weight REAL DEFAULT 0.0,
    PRIMARY KEY (entity_id_1, entity_id_2),
    FOREIGN KEY (entity_id_1) REFERENCES entities(id),
    FOREIGN KEY (entity_id_2) REFERENCES entities(id)
);

-- 2. Populate from existing fact_entities (facts with 2+ entities)
INSERT INTO entity_cooccurrence (entity_id_1, entity_id_2, cooccur_count)
SELECT 
    fe1.entity_id,
    fe2.entity_id,
    COUNT(DISTINCT fe1.fact_id)
FROM fact_entities fe1
JOIN fact_entities fe2 ON fe1.fact_id = fe2.fact_id
WHERE fe1.entity_id < fe2.entity_id  -- Avoid duplicates
GROUP BY fe1.entity_id, fe2.entity_id;

-- 3. Compute weighted co-occurrence using IDF
UPDATE entity_cooccurrence SET cooccur_weight = (
    SELECT 
        CAST(cooccur_count AS REAL) * 
        (SELECT idf_weight FROM entity_stats WHERE idf_weight = entity_id_1) *
        (SELECT idf_weight FROM entity_stats WHERE entity_stats.id = entity_id_2)
    WHERE entity_cooccurrence.entity_id_1 = entity_id_1 
    AND entity_cooccurrence.entity_id_2 = entity_id_2
);

-- 4. Create index for fast co-occurrence lookups
CREATE INDEX idx_cooccur_lookup ON entity_cooccurrence(entity_id_1, cooccur_weight DESC);

Fase 3: Actualizaciones de Comandos CLI

Extender el comando retain con parámetro de importancia:

# Before
openclaw memory retain "Sarah is leaving the company next month"

# After (with salience)
openclaw memory retain "Sarah is leaving the company next month" \
  --type B \
  --entity Sarah \
  --entity project \
  --salience 0.95

Extender el comando recall con filtro de importancia y traversía asociativa:

# Before
openclaw memory recall "performance improvements"

# After (with enhanced options)
openclaw memory recall "performance improvements" \
  --k 10 \
  --min-salience 0.3 \
  --associative-depth 2 \
  --activation-decay 0.5

Fase 4: Algoritmo de Traversía Asociativa

Implementar traversía con límite de profundidad y decaimiento de activación:

def associative_traverse(seed_entities: list[str], depth: int = 2, decay: float = 0.5) -> dict:
    """
    Traverse entity co-occurrence graph with depth limiting and activation decay.
    
    Returns:
        dict: {entity_name: accumulated_activation_score}
    """
    activation = {}
    visited = set()
    
    # Initialize seed entities with full activation
    for entity_name in seed_entities:
        activation[entity_name] = 1.0
        visited.add(entity_name)
    
    current_entities = seed_entities
    current_activation = 1.0
    
    for hop in range(depth):
        next_entities = []
        next_activation = current_activation * decay
        
        for entity_name in current_entities:
            # Query co-occurring entities with IDF weighting
            cooccurring = query("""
                SELECT e.name, c.cooccur_weight, es.idf_weight
                FROM entity_cooccurrence c
                JOIN entities e ON c.entity_id_2 = e.id
                JOIN entity_stats es ON e.id = es.id
                WHERE c.entity_id_1 = (
                    SELECT id FROM entities WHERE name = ?
                )
                AND e.name NOT IN ({}),
                ORDER BY c.cooccur_weight * es.idf_weight DESC
                LIMIT 10
            """, entity_name)
            
            for coentity_name, cooccur_weight, idf_weight in cooccurring:
                if coentity_name not in visited:
                    contribution = next_activation * cooccur_weight * idf_weight
                    activation[coentity

            

Evidencia y fuentes

Esta guía de solución de problemas fue sintetizada automáticamente por la tubería de inteligencia de FixClaw a partir de las discusiones de la comunidad.