Skip to main content
Glama

AWHM Lite

CI

Memoria externa a largo plazo para agentes LLM. Sin nube, sin claves de API, se ejecuta completamente en local.

AWHM Lite ofrece a cualquier LLM memoria persistente a lo largo de las conversaciones mediante registros de solo adición, emparejamiento de patrones basado en regex, un grafo de memoria consciente de contradicciones, consolidación simbólica (cero llamadas a LLM) y recuperación mediante fusión de características léxicas y semánticas.

Estado: prototipo de investigación. Construido en febrero de 2026, publicado en agosto de 2026; v0.2.0 lo reforzó, v0.3.0 añadió hooks, la Etapa 2, resolución de entidades, viaje en el tiempo, almacenamiento SQLite y evaluación con corpus reales. 145 pruebas, CI en Python 3.11 a 3.13.

Documentación del proyecto

  • docs/awhm-whitepaper.md: el artículo completo de la arquitectura AWHM del que este proyecto es un subconjunto

  • docs/awhm-whitepaper-vs-lite.md: lo que Lite conserva y lo que omite

  • docs/Future Plans.md: el siguiente paso planificado (middleware silencioso por turno)

Related MCP server: claude-memory-mcp

Cómo se construyó

La arquitectura y las ideas que hay detrás son mías. El código fue escrito íntegramente por agentes de programación de IA (principalmente Claude Code) bajo mi dirección: yo definí el diseño, organicé las tareas, revisé el resultado y orienté el trabajo. El whitepaper se produjo de la misma manera.

INTERACTION TIME                        OFFLINE (SESSION END)
────────────────────                    ─────────────────────
┌──────────────────┐   real-time log    ┌──────────────────────┐
│  PRIMARY AGENT   │──────────────────► │  STAGE 1 CONSOLIDATION│
│  (user-facing)   │   (middleware,     │  (symbolic only,      │
└──────┬───────────┘    no LLM)         │   zero LLM calls)     │
       │                                └──────────┬───────────┘
       │ queries                                   │ writes
       ▼                                           ▼
┌──────────────┐    ┌──────────┐    ┌──────────────────────┐
│  RETRIEVAL   │◄───│ SESSION  │    │    FLAT MEMORY GRAPH  │
│  ENGINE      │    │ BUFFER   │    │                      │
│              │◄───┤(checked  │    │  nodes: episodic,    │
│ BM25 +       │    │ first)   │    │  semantic, procedural│
│ embedding    │    └──────────┘    │                      │
│ similarity   │◄───────────────────│  edges: typed        │
│              │                    │  strength: rec + freq│
└──────────────┘                    └──────────────────────┘
       ▲
       │ fallback (first ~10 sessions)
┌──────┴───────┐
│   RAW LOGS   │
│ (append-only)│
└──────────────┘

Instalación

# Core (numpy, spaCy, dateparser) plus the sentence-transformers embedding model
pip install -e ".[embeddings]"

# spaCy NER model (used in consolidation; without it, entity extraction is skipped)
python -m spacy download en_core_web_sm

# Claude Code MCP integration
pip install -e ".[mcp]"

# Optional: Anthropic SDK client for Stage 2 (the default Stage 2 client is
# the Claude Code CLI and needs nothing extra)
pip install -e ".[anthropic]"

sentence-transformers es opcional porque incorpora PyTorch. Sin él, inicia las sesiones con use_mock_embeddings=True (vectores deterministas basados en hash, suficientes para las pruebas y para probar el CLI). El modelo real (all-MiniLM-L6-v2, 22 MB) se descarga en el primer uso.

Inicio rápido

API de Python

from awhm import AWHMSession
from awhm.types import Role

# Start a session (also usable as a context manager: `with AWHMSession.start_session() as session:`)
session = AWHMSession.start_session()

# Log messages
session.log_message(Role.USER, "My name is Alice")
session.log_message(Role.ASSISTANT, "Hello Alice!")
session.log_message(Role.USER, "I prefer Python over JavaScript")
session.log_message(Role.USER, "The API endpoint is https://api.example.com/v2")

# Query memory (works immediately via session buffer)
results = session.query("What language does the user prefer?")
for r in results:
    print(f"[{r.source}] {r.content}")

# Consolidate into long-term memory graph
session.consolidate_current()

# End session (flushes WAL, saves graph)
session.end_session()

Integración con un LLM

AWHM actúa como middleware. No llama a ningún LLM: lo conecta al sistema que uses:

from awhm import AWHMSession
from awhm.types import Role

session = AWHMSession.start_session()

def handle_message(user_text):
    session.log_message(Role.USER, user_text)

    # Retrieve relevant memories
    memories = session.query(user_text, k=5)
    memory_context = "\n".join(f"- {m.content}" for m in memories)

    # Inject into system prompt
    system = f"Memories from past conversations:\n{memory_context}"
    response = your_llm_call(system_prompt=system, user_message=user_text)

    session.log_message(Role.ASSISTANT, response)
    return response

# At end of conversation:
session.consolidate_current()
session.end_session()

CLI

awhm status                        # Show system stats
awhm query "Python preferences"    # Search memory
awhm query "API endpoint" --include-history --trace
awhm consolidate                   # Run Stage 1 on pending sessions
awhm snapshot create               # Backup current graph
awhm snapshot list                 # List snapshots
awhm snapshot restore --path FILE  # Restore from snapshot
awhm delete NODE_ID                # Hard-delete a node (privacy)
awhm eval --json                   # Run built-in benchmark report

Integración con Claude Code (hooks, recomendada)

Con los hooks, la memoria funciona en cada turno sin que el modelo tenga que llamar a una herramienta. Cada hook es un proceso separado y de corta duración; el búfer de sesión se reanuda de su registro write-ahead entre ellos.

Evento

Comando

Qué hace

UserPromptSubmit

awhm hook prompt

Registra el prompt, recupera las memorias más relevantes (BM25 + búfer; añade --semantic para usar también los embeddings) y las devuelve como contexto oculto

Stop

awhm hook stop

Registra la respuesta del asistente

SessionEnd

awhm hook session-end

Consolida la sesión en el grafo (añade --stage2, o define AWHM_STAGE2=1, para ejecutar también la Etapa 2 mediante claude -p)

awhm hook settings          # prints the block to merge into ~/.claude/settings.json

Los hooks nunca bloquean una sesión: cualquier fallo se escribe en stderr y el proceso sale con 0. Configura AWHM_DATA_DIR para cambiar dónde vive la memoria.

Integración con Claude Code (MCP)

Configuración

# Install with MCP support
cd awhm-lite
pip install -e ".[mcp]"

# Register with Claude Code
claude mcp add --transport stdio awhm-lite -- awhm-mcp

O añádelo manualmente a Nuevo .claude/settings.json:

{
  "mcpServers": {
    "awhm-lite": {
      "type": "stdio",
      "command": "awhm-mcp",
      "env": {
        "AWHM_DATA_DIR": "~/.awhm"
      }
    }
  }
}

Herramientas MCP disponibles

Herramienta

Descripción

memory_query

Busca en la memoria una consulta en lenguaje natural (include_history, with_trace opcionales)

memory_log

Registra un mensaje en el registro de conversación en bruto

memory_consolidate

Extrae onton de las sesiones pendientes al grafo

memory_status

Muestra el número de nodos, aristas y sesiones

memory_snapshot_create

Crea una instantánea de copia de seguridad

memory_delete_node

Borra definitivamente un nodo y limpia los datos de instantánea coincidentes

Una vez conectado, Claude Code tendrá acceso automático a estas herramientas y podrá consultar y guardar memorias entre sesiones.

Cómo funciona

Registros en bruto

Cada mensaje se añade a un archivo JSONL (uno por sesión). Solo consulta, nunca se modifica salvo para borrados físicos por privacidad. Este es el registro de verdad.

Búfer de sesión

Un emparejador de patrones basado en regex se ejecuta en cada mensaje del usuario en tiempo real y captura:

  • Correcciones: "en realidad, X es Y", "no, es X"

  • Preferencias: "prefiero X", "usa siempre X" y "nunca hagas X"

  • Gatos: "el endpoint es X", "mi nombre es X"

  • Outcomees**: "eso funcionó", "eso falló"

Captura alrededor del 60-70 % de las señales explícitas sin llamadas a LLM. El búfer se consulta primero durante la recuperación para lograr continuidad instantánea dentro de la sesión. Durante la recuperación predeterminada, las entradas del búfer que una declaración posterior sustituye (misma ranura o una corrección explícita unos mensajes después) se ocultan, de modo que las correcciones ganan. Se almacena en registros de escritura de sesión (intervalo de vaciado de 30 s, omitido cuando no hay cambios).

Centro de memoria

Un grafo dirigido plano con tres tipos de nodos (episódicos, semánticos, procedimentales) y dos tipos de aristas (temporal, abstracción, asociación).

Cada nodo lleva ahora metadatos de ciclo de vida de contradicción:

  • canonical_key (identidad de ranura, p. ej. fact:my preferred language)

  • status (active, superseded, anulado)

  • supersedes (identificadores de nodos más antiguos reemplazados por este nodo)

  • valid_from / valid_to

  • confidence

Se guarda como JSON y se carga en memoria.

Compatibilidad con versiones anteriores: los archivos de grafo más antiguos (sin campos de ciclo de vida) se migran automáticamente en memoria al controlar.

Puntuación de fuerza

Cada nodo tiene una puntuación de fuerza compuesta:

S(v) = 0.4 * recency + 0.6 * frequency

La recencia usa descenso de ley de potencias: s_rec = (1 + 0.1 * hous)^(-0.3) , aproximadamente 0.71 a las 24 h, 0.40 a los 7 días, 0.27 a los 30 días. La frecuencia es el número de accesos normalizado según el tiempo percentil 90.

Consolidación (Etapa 1)

Se ejecuta al final de la sesión, con cero llamadas a LLM:

  1. NER a través de spaCy: personas, organizaciones, lugares, productos. Las etiquetas numéricas y similares a tiempo (CARDINAL, MONEY, DATE, ...) se filtran; creaban nodos de ruido. Se configura mediante ner_labels.

  2. Análisis temporal con dateparser: resuelve "ayer" y "5 de marzo" en marcas de tiempo ISO

  3. Extracción basada en reglas: los mismos patrones regex que el búfer de sesión, sobre los nuevos mensajes

  4. Vinculación de entidades: relaciona entidades con nodos existentes (un producto de coseno, luego acuerdo de tipo de entidad y comprobación de similitud de cadenas)

  5. Desdesduplicación: las declaraciones idénicas dentro del lote se colapsan; los casi duplicados de nodos existentes (coseno > 0.92) refuerzan ese nodo en lugar de crear uno nuevo

  6. Commit: asigna claves canónicas, sustituye las contradicciones, añada nodos y bordes y actualiza las puntuaciones de fuerza

Contradicciones: claves canónicas

Una clave canónica nombra el slot que una declaración rellena. Dos memorias activas con la misma clave se contradicen, de modo que la más nueva sustituye a la más antigua deja status=superseded, valid_to definido y el enlace supersedes al nuevo nodo.

Declaración

Clave

"Mi lenguaje preferido es Python"

fact:my preferred language

"Vivo en Nueva York"

fact:i live in

"Prefiero el modo oscuro"

preference:dark

"Nunca use pestañas para sangrar"

policy:use:tabs

"Uso Python para scripts"

ninguna (aditiva)

Aplicamos reglas deliberadamente conservadoras porque no hay un LLM que juzgue intención:

  • Mismas llaves: siempre reemplaza (el slot se relanzó con un nuevo valor).

  • Familias de preferencias / políticas: una corrección explícita (en realidad, prefiero Rust) supersede la anterior declaración de la misma familia si llega dentro de correction_window_messages (default 3) de la misma sesión. Sin marcador de corrección, las preferencias son aditivas: "Prefiero pestañas" y "Prefiero modo oscuro" se quedan activas.

  • Familias de hechos: sólo una coincidencia exacta de clave duplica; que una corrección sobre el endpoint de la API no pueda cambiar tu nombre por error.

  • Cualquier cosa que no se reconoce no recibe clave y nunca substituye.

Entidades

Las entidades mencionadas se resuelven a un único nodo independientemente de cómo se escriban. Las formas de superficie se normalizan (mayúsculas/minúsculas, posesivos, sufijos corporativos, dominios: "Acme Holdings Ltd" y "acme.com" ambas pasan a "acme"), luego se comparan either por alias exacto, por contención de términos sin ambigüedad ( "Acme" within "Acme Holdings") y finalmente por similitud de embeddings con el mismo tipo de entidad. Cada mención reconocida se registra como alias en el nodo, y las afirmaciones reciben aristas de asociación a las entidades que mencionan, de modo que la recuperación puede pasar de "Acme" a todo lo que se sabe of it.

Etapa 2 (refinamiento opcional con LLM, sin clave de API)

La Etapa 1 tiene un techo duro: capta "prefiero Rust" y no capta "entonces decidamos por Rust". La Etapa 2 se ejecuta después de la Etapa 1, sin conexión, y pide a un LLM que proponga las memorias que las reglas no encuentran. El LLM sólo propone: el código valida cada propuesta (esquema, números de mensaje citados de existencia, confianza mínima), elimina lo que ya capturado y se registra con las mismas reglas de slot y supersession. La recuperación sigue sin llamadas a LLM.

El cliente por defecto envía las peticiones al CLI de Claude Code (claude -p con salida estructurada), por lo que usa el claves de sesión que ya tienes y no se almacena ninguna API key. Marca la llamada para que los hooks de memoria no se disparen.

awhm consolidate --stage2                    # Claude Code CLI, default model
awhm consolidate --stage2 --stage2-model sonnet
from awhm import AWHMSession, AWHMConfig

config = AWHMConfig(stage2_enabled=True, stage2_model="sonnet")
with AWHMSession.start_session(config) as session:   # builds ClaudeCodeClient
    ...
    session.consolidate_current()

Cualquier objeto con un método complete_json(system, user, schema) -> str sirve como cliente (llm_client=...). También se incluye un cliente del SDK de Anthropic para quienes prefieran la facturación por API (stage2_client="anthropic", extra [anthropic] ).

Recuperación

Cero llamadas a LLM, basado en

  1. Comprobación del búfer: busca en el buffer de sesión primero (infoutas instantáneas, siempre por encima de los resultados del grafo)

  2. Identificación de anclas: solapamiento de términos BM25 más similitud coseno de embeddings (unión). El índice BM25 posa in-proceso (frecuencia invertida estilo Lucene; corpus peque se pueden puntuar correctamente) y se guarda en caché hasta que cambian los nodos.

  3. Filtro histórico: por defecto, solo los nodos del grafo con status=active son elegibles

  4. Puntuación de características: semántica + léxica + fuerza + confianza, y menos una penalización por contradicción. La fuerza se recalcula solo para los candidatos que se ponen ordenando.

  5. Devuelve top-k (default 10)

  6. Expansión de vecinos de un paso: los vecinos a un salto de las anclas (entidades enlazadas, episodios secuenciales) se incorporan al conjunto con un peso de arista decaid, puntuados mediante la característica associación. Solo puede expandir si la propio anchor está activa.

  7. Arranque en frío: initials las primeras 10 sesiones también se ejecuta BM25 sobre los registros en bruto. Esos aciertos se escalan in [0, raw_log_score_scale], para no superar nunca a una coincidencia real del grafo.

Viaje en el tiempo

Los datos se almacenan como ventana de validez. Fechas "desde"/"desde" establecen valid_from, "hasta" valid_to, y una supersedencia cierra el marco del hecho más antiguo. consulta(..., as_of="2026-03-01") permite responder con lo que era cierto en ese momento, incluidas memories sustituidas:

awhm query "API endpoint"                       # what is true now
awhm query "API endpoint" --as-of 2026-02-01    # what was true then

Defina include_history=True para superados/retirados (se superasimina). Defina with_trace=True para trace of encoding features per result.

Evaluación

El benchmark integrado es un smoke test synthetic (tres queries de correcion fuerte +- etc.) y una auditoria de borrados. Los valores reales proceden de reprosesar un corpus:

awhm eval                                            # built-in synthetic benchmark
awhm eval --corpus my_sessions.json                  # native format, see below
awhm eval --corpus longmemeval_s.json --longmemeval --limit 50

Ambos reportan Recall@k, nDCG@k, la tasa de error de contradicción, la latencia p50/p95 y el recall por categoría. El formato nativo del corpus es {"sessions": [{"id", "messages": [{"role", "content"}]}], "questions": [{"id", "question", "expected": [...], "forbidden": [...], "as_of", "category"}]}. Las instancias de LongMemEval se consolidan y se examinan de forma aislada, siguiendo el protocolo del benchmark. La comparación se hace por subcadena de la respuesta, un límite inferior deliberado: los aciertos reformulados no se cuentan.

Medido (solo Stage 1, división oracle, 500 preguntas): Recall@5 0.196, desde 0.40 en hechos de usuario de una sola sesión hasta 0.00 en preferencias, a 4 ms por consulta. Ese es el techo de las expresiones regulares hecho visible; Stage 2 existe para superarlo. Tabla completa, advertencias y reproducción en docs/benchmarks.md.

Configuración

Todos los parámetros son configurables mediante AWHMConfig:

Parámetro

Predeterminado

Descripción

alpha

0.3

Tasa de decaimiento (exponente de la ley de potencias)

beta

0.1

Constante de escala del decaimiento

w_rec

0.4

Peso de la recencia en la puntuación de fuerza

w_freq

0.6

Peso de la frecuencia en la puntuación de fuerza

retrieval_profile

"balanced"

Perfil de ponderación de recuperación

w_semantic

0.55

Peso de la similitud semántica

w_lexical

0.20

Peso léxico de BM25

w_strength

0.15

Peso de la fuerza del nodo

w_confidence

0.10

Peso de la confianza de consolidación

contradiction_penalty

0.35

Penalización por memorias no activas

include_history_by_default

False

Incluir memorias reemplazadas/retiradas por defecto

trace_retrieval

False

Emitir trazas de clasificación por defecto

k

10

Recuperación de top-k

entity_link_threshold

0.85

Umbral de coseno para el enlazado de entidades

dedup_threshold

0.92

Umbral de coseno para la deduplicación

bm25_anchor_ratio

0.5

Ancla léxica si la puntuación >= ratio x mejor puntuación BM25

embed_threshold

0.3

Similitud coseno mínima para el conjunto de anclas

raw_log_score_scale

0.5

Límite superior para las puntuaciones de acierto raw-log en arranque en frío

neighbor_expansion / neighbor_decay

True / 0.6

Incluir vecinos del grafo a un salto de las anclas, con este multiplicador de peso de arista

w_association

0.10

Peso de la evidencia de vecinos en la mezcla

storage_backend

"json"

"json" (un archivo) o "sqlite" (guardados incrementales)

stage2_enabled

False

Refinamiento offline con LLM después de Stage 1

stage2_client / stage2_model

"claude-code" / None

claude-code (CLI, sin clave) o anthropic; alias del modelo, None = predeterminado del cliente

stage2_max_messages / stage2_min_confidence

60 / 0.5

Mensajes por llamada LLM; las propuestas por debajo de esta confianza se descartan

correction_window_messages

3

Lo cerca que debe estar una corrección explícita para reemplazar una preferencia/política

ner_labels

PERSON, ORG, GPE,

Etiquetas de entidad de spaCy que se convierten en nodo

buffer_flush_interval

30s

Intervalo de persistencia del WAL

ann_index_type

"none"

Modo de índice ANN reservado

delete_snapshots_on_hard_delete

True

Limpiar la memoria de instantánea coincidente en una eliminación permanente

from awhm.config import AWHMConfig

config = AWHMConfig(
    data_dir="~/.my-project-memory",
    k=20,
    w_rec=0.5,
    w_freq=0.5,
)

Directorio de datos

~/.awhm/
├── logs/                          # Raw JSONL logs (one per session)
│   ├── {session_id}.jsonl
│   └── ...
├── graph/
│   ├── memory_graph.json          # The memory graph (storage_backend="json")
│   └── memory_graph.sqlite        # ... or one row per node (storage_backend="sqlite")
├── snapshots/
│   └── snapshot_{timestamp}.json  # Manual backups
├── wal/
│   └── {session_id}.wal           # Per-session write-ahead logs
└── meta/
    ├── consolidated_sessions.json # Tracks which sessions have been processed
    ├── deletion_tombstones.jsonl  # Deletion tombstones
    └── deletion_ledger.jsonl      # Deletion audit ledger

Pruebas

pip install -e ".[dev]"
pytest tests/ -v

Todas las pruebas usan MockEmbeddingService (determinista entre procesos, sin descarga de modelos). ruff check . ejecuta el linter; el CI ejecuta ambas en Python 3.11, 3.12 y 3.13.

Dependencias

Paquete

Tamaño

Propósito

numpy

~29 MB

Cálculo vectorial

spacy + en_core_web_sm

~35 MB

NER

dateparser

~2 MB

Análisis de fechas

sentence-transformers (opcional, [embeddings])

~3 MB (+PyTorch ~350 MB)

Modelo de embeddings

mcp (opcional, [mcp])

~1 MB

Integración con Claude Code

BM25 está implementado dentro del paquete (unos 60 líneas), por lo que no hay dependencia de clasificación.

El modelo de embeddings (all-MiniLM-L6-v2, 22 MB) se descarga en el primer uso en ~/.cache/huggingface.

Estructura del proyecto

src/awhm/
├── __init__.py            # AWHMSession facade (top-level API)
├── config.py              # All parameters + path helpers
├── types.py               # Enums: Role, NodeType, NodeStatus, EdgeType, BufferEntryType
├── mcp_server.py          # MCP server for Claude Code
├── hooks.py               # Claude Code hook commands (prompt / stop / session-end)
├── timeutil.py            # Timestamp parsing, validity windows
├── eval/                  # Built-in benchmark + real-corpus replay (LongMemEval loader)
├── raw_log/               # Append-only JSONL logging
├── session_buffer/        # Regex pattern matching + WAL
├── graph/                 # Memory graph, strength scoring, JSON/SQLite stores
├── consolidation/         # NER, temporal, extraction, entities, dedup, Stage 2, pipeline
├── retrieval/             # Embedding, BM25, ranking, retrieval engine
├── snapshots/             # Snapshot create/restore/list
├── deletion/              # Hard-delete cascade
└── cli/                   # argparse CLI
A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A persistent memory MCP server for Claude Code that enables long-term recall across sessions via hybrid search, code intelligence, and tools for reading/writing memory.
    23
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A MCP server that gives Claude Code and other AI assistants long-term memory by automatically extracting technical knowledge from conversations and retrieving relevant experiences in future sessions.
    14
    MIT

View all related MCP servers

Related MCP Connectors

  • Cloud-hosted MCP server for durable AI memory

  • Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer

  • One memory, every AI: Claude, ChatGPT, Perplexity, Gemini, Cursor, OpenClaw, Hermes, any MCP client.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/juderosendev/awhm-lite'

If you have feedback or need assistance with the MCP directory API, please join our Discord server