Memory System (Español)
Elección de un proveedor de embeddings (v3.8.16+)
Sección titulada «Elección de un proveedor de embeddings (v3.8.16+)»El motor de memoria de OmniRoute admite cuatro fuentes de embeddings (src/lib/memory/embedding/). Cada una presenta distintas ventajas y desventajas en cuanto a latencia, coste, calidad del modelo y complejidad de configuración.
Fuentes de embeddings
Sección titulada «Fuentes de embeddings»| Proveedor | Fuente | Latencia | Coste | Calidad | Configuración |
|---|---|---|---|---|---|
transformers |
Modelo ONNX local (Xenova/all-MiniLM-L6-v2) | ~50-150ms (CPU) | Gratis | Buena | Solo npm install |
static |
Vectores precalculados (en caché) | <1ms | Gratis | N/D (depende del acierto de caché) | Ninguna |
remote |
API de OpenAI / Cohere / Voyage | ~100-300ms | $0.02-0.10/1M tokens | Excelente | Clave de API |
auto |
Selecciona la mejor fuente disponible durante la ejecución | Igual que la fuente seleccionada | Gratis | Igual que la fuente seleccionada | Ninguna |
| (caché) | Capa LRU en memoria sobre cualquier fuente | <1ms (acierto), latencia completa (fallo) | Gratis | Igual que la subyacente | Siempre activa (no es una fuente seleccionable) |
Árbol de decisión
Sección titulada «Árbol de decisión» ¿Cuál es el contexto de su despliegue? │ ┌───────────┼───────────┬──────────────┐ │ │ │ │ DESARROLLO/ PRODUCCIÓN PRODUCCIÓN PERÍMETRO / PRUEBAS PEQUEÑA GRANDE SIN CONEXIÓN │ │ │ │ ▼ ▼ ▼ ▼ transformers transformers remote (Qdrant) transformers (gratis, sin API) (mejor calidad) (sin internet) │ │ │ │ └────────┬──┴───────────┴──────────────┘ │ ▼ AÑADA SIEMPRE la capa `cache` encima (LruCache envuelve cualquier proveedor)Configuración de la base de datos y la API
Sección titulada «Configuración de la base de datos y la API»Las opciones de embeddings de memoria se configuran mediante la API/interfaz de Configuración, no mediante variables de entorno. Las claves pertinentes de la base de datos de configuración en Configuración (normalizeMemorySettings en src/lib/memory/settings.ts) son:
memoryEmbeddingSource:"transformers"(local),"remote"(basada en API, p. ej., OpenAI),"static"(almacén externo) o"auto"memoryEmbeddingProviderModel: identificador del modelo para fuentes remotas/estáticas (p. ej.,"text-embedding-3-small")memoryTransformersEnabled:true|falsememoryStaticEnabled:true|falsememoryVectorStore:"sqlite-vec","qdrant"o"auto"
Modelo local (transformers)
Sección titulada «Modelo local (transformers)»Utiliza transformers.js internamente para ejecutar modelos locales:
# Variables de entorno leídas en el código (src/lib/memory/embedding/index.ts):MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # Repositorio del modelo en HFMEMORY_STATIC_MODEL=minishlab/potion-base-8M # Modelo estático Potion de HFMEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # Directorio de cachéCaché LRU de embeddings
Sección titulada «Caché LRU de embeddings»La caché está siempre activa de forma predeterminada y se configura mediante variables de entorno:
MEMORY_EMBEDDING_CACHE_MAX=1000 # Número máximo de elementos en cachéMEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL (5 min)Cifras de rendimiento
Sección titulada «Cifras de rendimiento»Benchmark en un servidor x86 típico de 4 núcleos (textos de ~100 tokens cada uno):
| Proveedor | p50 | p95 | p99 | Coste / 1 millón de embeddings |
|---|---|---|---|---|
transformers (CPU) |
80ms | 180ms | 350ms | Gratis |
remote (OpenAI) |
120ms | 220ms | 400ms | ~$0.02 (ada-002) / $0.13 (3-large) |
static (Qdrant) |
15ms | 30ms | 60ms | Depende del alojamiento de Qdrant |
cache (acierto) |
<1ms | <1ms | 2ms | Gratis |
Patrones de extracción de hechos (v3.8.16+)
Sección titulada «Patrones de extracción de hechos (v3.8.16+)»El módulo extraction.ts (src/lib/memory/extraction.ts) utiliza coincidencia de patrones mediante expresiones regulares para extraer hechos estructurados de los mensajes de las conversaciones. Comprender estos patrones le ayudará a ajustar la calidad de la extracción para su caso de uso.
Categorías de patrones predeterminadas
Sección titulada «Categorías de patrones predeterminadas»| Categoría | Patrón de ejemplo | Captura |
|---|---|---|
| PREFERENCE_PATTERNS | "Prefiero <X>", "Me gusta <X>", "Odio <X>" |
Preferencias del usuario |
| DECISION_PATTERNS | "Usaré <X>", "Decidí <X>", "Elegí <X>" |
Decisiones del usuario (episódicas) |
| PATTERN_PATTERNS | "Normalmente <X>", "Siempre <X>", "Nunca <X>" |
Patrones de comportamiento persistentes |
Patrones de ejemplo (simplificados)
Sección titulada «Patrones de ejemplo (simplificados)»// De src/lib/memory/extraction.tsconst PREFERENCE_PATTERNS = [ /\bI\s+(?:really\s+)?prefer\s+([^.,\n]+)/gi, /\bI\s+(?:really\s+)?like\s+([^.,\n]+)/gi, /\bI\s+(?:hate|dislike|avoid)\s+([^.,\n]+)/gi,];const DECISION_PATTERNS = [ /\bI'?(?:ll|will)\s+use\s+([^.,\n]+)/gi, /\bI\s+(?:have\s+)?decided\s+(?:to\s+)?([^.,\n]+)/gi,];const PATTERN_PATTERNS = [/\bI\s+usually\s+([^.,\n]+)/gi, /\bI\s+always\s+([^.,\n]+)/gi];Qué se extrae
Sección titulada «Qué se extrae»Cuando un usuario dice:
“Prefiero TypeScript. Usaré Postgres para este proyecto. Siempre hago commit antes de hacer push. No me gusta Python.” La extracción produce 4 recuerdos:
Clave Categoría Tipo Contenido preference:typescriptpreference factual “TypeScript” decision:postgres_for_this_projectdecision episodic “Postgres para este proyecto” pattern:commit_before_pushingpattern factual “hacer commit antes de hacer push” preference:pythonpreference factual “Python”
Límites de extracción
Sección titulada «Límites de extracción»Para evitar una extracción descontrolada, se aplican los siguientes límites:
| Longitud mínima del contenido | 3 caracteres | | Longitud máxima del contenido | 500 caracteres |
Cuándo desactivar la extracción
Sección titulada «Cuándo desactivar la extracción»La extracción se ejecuta automáticamente siempre que la memoria esté activada; no existe un interruptor independiente exclusivo para la extracción. Para desactivarla, desactive la memoria por completo (enabled: false mediante PUT /api/settings/memory). Considere hacerlo cuando:
- Tenga un gran volumen de mensajes y el coste de extracción no sea trivial
- Sus conversaciones sean principalmente transitorias (chat, depuración) y no tengan valor a largo plazo
- Ya esté capturando el contexto mediante plugins personalizados
Ajuste de RRF híbrido (v3.8.16+)
Sección titulada «Ajuste de RRF híbrido (v3.8.16+)»El algoritmo Reciprocal Rank Fusion (RRF) combina resultados de FTS5 (palabras clave) y vectoriales (semánticos). El parámetro k controla cuánto peso se asigna a los resultados con una posición inferior.
La fórmula
Sección titulada «La fórmula»Para cada recuerdo candidato, la puntuación RRF es:
RRF(d) = Σ 1 / (k + rank_i(d))Donde:
kes la constante (60 de forma predeterminada)rank_i(d)es la posición del documentoden el i-ésimo sistema de recuperación (FTS, vectorial)- La suma se realiza sobre todos los sistemas de recuperación
Cómo afecta k a los resultados
Sección titulada «Cómo afecta k a los resultados»Valor de k |
Efecto | Ideal para |
|---|---|---|
k=0 |
Fusión pura de posiciones (sin suavizado) | Referencia teórica |
k=10-30 |
Da mucho peso a los primeros resultados; las posiciones bajas apenas contribuyen | Cuando los 3 primeros resultados suelen ser correctos |
k=60 (predeterminado) |
Equilibrado: los 10 primeros resultados contribuyen de forma significativa | Recuperación de propósito general |
k=100+ |
Más uniforme: incluso los resultados con posiciones bajas pueden dominar si aparecen en varios sistemas | Cuando la exhaustividad > precisión es fundamental |
Ajuste de k en la práctica
Sección titulada «Ajuste de k en la práctica»# Valor predeterminadoMEMORY_RRF_K=60
# Precisión agresiva (memoria pequeña, pocos documentos)MEMORY_RRF_K=20
# Máxima exhaustividad (memoria grande, consultas variadas)MEMORY_RRF_K=120Ejemplo con k=20:
- Posición FTS 1 → contribución
1/21 = 0.048 - Posición FTS 10 → contribución
1/30 = 0.033 - Posición vectorial 1 → contribución
0.048 - Máximo combinado:
0.096
Ejemplo con k=60:
- Posición FTS 1 → contribución
1/61 = 0.016 - Posición FTS 10 → contribución
1/70 = 0.014 - Posición vectorial 1 → contribución
0.016 - Máximo combinado:
0.033
Con un valor de k más alto, la diferencia relativa entre la primera y la décima posición es menor, por lo que el algoritmo depende más del consenso entre los sistemas de recuperación que de la confianza en la primera posición.
Cuándo cambiar k
Sección titulada «Cuándo cambiar k»| Síntoma | Pruebe |
|---|---|
| El primer resultado siempre gana, pero es incorrecto | Un valor de k más bajo (p. ej., 20): la confianza en la primera posición importa más |
| La respuesta correcta está entre las 5 primeras, pero no es la primera | Un valor de k más alto (p. ej., 100): una puntuación más uniforme recompensa el consenso |
| La exhaustividad es alta, pero la precisión es baja | Un valor de k más bajo: haga más nítida la clasificación |
| La exhaustividad es baja (faltan documentos relevantes) | Un valor de k más alto: dé una oportunidad a los documentos con posiciones inferiores |
Ponderación de RRF
Sección titulada «Ponderación de RRF»La fusión recíproca de posiciones utiliza pesos iguales para la posición vectorial semántica y la posición de búsqueda de texto completo:
RRF(d) = 1/(k + rank_vector) + 1/(k + rank_fts)No existen variables de entorno para ajustar los pesos individuales (MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT no existen).
Estrategia de resumen (v3.8.16+)
Sección titulada «Estrategia de resumen (v3.8.16+)»El módulo summarization.ts (src/lib/memory/summarization.ts) comprime los recuerdos más antiguos para mantener pequeño el conjunto activo y, al mismo tiempo, preservar la capacidad de recuperación.
Cuándo se activa el resumen
Sección titulada «Cuándo se activa el resumen»| Activador | Umbral (predeterminado) |
|---|---|
| Activación manual mediante API | no aplica |
Qué se resume
Sección titulada «Qué se resume»Desde summarization.ts se exportan dos puntos de entrada:
summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000)— condensa los recuerdos de una sesión en un único texto de resumen limitado por un presupuesto de tokens.summarizeMemoriesOlderThan(apiKeyId, days, dryRun)— la compactación basada en la antigüedad utilizada por la API: selecciona todos los recuerdos anteriores adays, crea a partir de ellos un único recuerdo de resumen condensado y (cuandodryRunesfalse) elimina los originales. PasadryRun: truepara previsualizar el conjunto candidato y el total de tokens sin modificar nada.
No hay ninguna pasada de agrupación por etiqueta/clave ni puntuación por recuerdo de “principal frente a resumible”: la selección se basa exclusivamente en el límite de antigüedad, y el texto del resumen es una línea condensada con el tipo como prefijo para cada candidato.
Activación del resumen
Sección titulada «Activación del resumen»El resumen es manual / opcional: la configuración autoSummarize es false de
forma predeterminada, por lo que nada se compacta automáticamente. Actívalo mediante la API:
curl -X POST http://localhost:20128/api/memory/summarize \ -H "Authorization: Bearer $OMNIROUTE_KEY"Para mantenerlo desactivado, simplemente conserva autoSummarize en su valor predeterminado (false).
Consejos para mejorar la calidad del resumen
Sección titulada «Consejos para mejorar la calidad del resumen»- Previsualiza primero con
dryRun:summarizeMemoriesOlderThan(..., true)devuelve la lista de candidatos y el recuento total de tokens para que puedas confirmar qué se combinaría antes de eliminar los originales. - Ejecuta el resumen durante las horas de poco tráfico si tienes un corpus de recuerdos grande; la llamada al LLM es la parte lenta.
# Estilo cron: resumir diariamente a las 3 a. m.0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \ -H "Authorization: Bearer $OMNIROUTE_KEY"Patrón de proveedor MemoryBackend
Sección titulada «Patrón de proveedor MemoryBackend»Fuente de referencia:
src/lib/memory/backend.ts,src/lib/memory/genericBackend.ts,src/lib/memory/manager.tsPruebas:src/lib/memory/__tests__/generic-backend.test.ts
El patrón de proveedor MemoryBackend introduce una capa de abstracción de backend conectable sobre el motor de memoria existente. En lugar de estar vinculado a una única implementación de almacenamiento, el sistema de memoria ahora admite varios backends (SQLite, Obsidian, Notion y backends HTTP personalizados) con enrutamiento configurable de backend principal y alternativas.
Arquitectura
Sección titulada «Arquitectura»┌──────────────────────────────────────────────────────────┐│ Rutas de la API ││ (src/app/api/memory/route.ts) │└──────────────────────┬───────────────────────────────────┘ │┌──────────────────────▼───────────────────────────────────┐│ MemoryManager ││ Orquestador singleton (manager.ts) ││ ││ Principal ──► Backend A (p. ej., SQLite) ││ Alternativa ─► Backend B (p. ej., Obsidian) ││ Backend C (p. ej., Notion mediante ││ GenericBackend) │└──────────────────────┬───────────────────────────────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼┌────────────┐ ┌────────────┐ ┌──────────────────┐│ Backend de │ │ Backend de │ │ Backend ││ SQLite │ │ Obsidian │ │ GenericMemory ││ │ │ │ │ (HTTP) │└────────────┘ └────────────┘ └──────────────────┘Interfaz principal (backend.ts)
Sección titulada «Interfaz principal (backend.ts)»Cada backend debe implementar la interfaz MemoryBackend:
interface MemoryBackend { readonly id: string; readonly displayName: string;
// CRUD create(input: CreateMemoryInput): Promise<Memory>; get(id: string): Promise<Memory | null>; update(id: string, updates: Partial<...>): Promise<boolean>; delete(id: string): Promise<boolean>; list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;
// Búsqueda search(config: SearchConfig): Promise<Memory[]>;
// Estado health(): Promise<HealthCheckResult>;
// Ciclo de vida (opcional) initialize?(): Promise<void>; shutdown?(): Promise<void>;}MemoryManager (manager.ts)
Sección titulada «MemoryManager (manager.ts)»Orquestador singleton que:
- Registra backends mediante
register(backend)— se llama durante el arranque desdeindex.ts - Configura el backend principal y las alternativas mediante
configure(primary, fallbacks) - Enruta las operaciones CRUD y las búsquedas al backend principal, con una cadena de alternativas en caso de fallo
- Comprueba el estado de todos los backends periódicamente
Comportamiento de las alternativas:
| Operación | Principal | Alternativas |
|---|---|---|
create |
✅ Solo el principal | ❌ |
get |
✅ Probar primero el principal | ✅ Alternativa si devuelve null |
update |
✅ Solo el principal | ✅ Sincronización sin esperar respuesta |
delete |
✅ Solo el principal | ✅ Sincronización sin esperar respuesta |
list |
✅ Solo el principal | ❌ |
search |
✅ Primero el principal | ✅ Alternativa en caso de error |
GenericMemoryBackend (genericBackend.ts)
Sección titulada «GenericMemoryBackend (genericBackend.ts)»Un conector HTTP genérico que adapta cualquier API REST a un MemoryBackend. Resulta útil para:
- Notion — conexión mediante la API de Notion
- Obsidian — conexión mediante la API REST local de Obsidian
- Backends personalizados — cualquier servicio que exponga una API RESTful de memoria
Configuración:
interface GenericBackendConfig { baseUrl: string; // URL base de la API del backend apiKey?: string; // Token Bearer para autenticación headers?: Record<string, string>; // Encabezados HTTP personalizados timeout?: number; // Tiempo de espera de la solicitud (predeterminado: 30000ms) backendType?: string; // Para registros
// Sustituciones de endpoints (los valores predeterminados usan convenciones REST) endpoints?: { search?: string; // predeterminado: "/memories/search" create?: string; // predeterminado: "/memories" list?: string; // predeterminado: "/memories" get?: string; // predeterminado: "/memories/{id}" update?: string; // predeterminado: "/memories/{id}" delete?: string; // predeterminado: "/memories/{id}" health?: string; // predeterminado: "/health" };
// Asignaciones de nombres de parámetros de consulta queryParams?: { query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options? };
// Asignaciones de nombres de parámetros de ruta pathParams?: { id?/memoryId? };}Los backends conocidos están preconfigurados en KNOWN_BACKENDS:
createKnownBackend("obsidian"); // → GenericMemoryBackend dirigido a localhost:27123createKnownBackend("notion"); // → GenericMemoryBackend dirigido a api.notion.com/v1Backends integrados
Sección titulada «Backends integrados»SQLiteBackend (sqliteBackend.ts)
Sección titulada «SQLiteBackend (sqliteBackend.ts)»El backend principal predeterminado. Encapsula el almacén de memoria existente basado en SQLite mediante src/lib/memory/store.ts. Se registra automáticamente durante el arranque.
import { sqliteBackend } from "./sqliteBackend";memoryManager.register(sqliteBackend);ObsidianBackend (obsidianBackend.ts)
Sección titulada «ObsidianBackend (obsidianBackend.ts)»Encapsula la integración existente con Obsidian (src/lib/memory/obsidianBackend.ts). Se conecta a un repositorio de Obsidian mediante la API REST local de Obsidian.
Configuración
Sección titulada «Configuración»La configuración de los backends de memoria se almacena en la tabla de configuración de la aplicación y se administra mediante src/lib/memory/settings.ts:
| Configuración | Clave de entorno/configuración | Valor predeterminado | Descripción |
|---|---|---|---|
| Backend principal | memoryPrimaryBackend |
"sqlite" |
ID del backend principal |
| Backends de respaldo | memoryFallbackBackends |
[] |
IDs ordenados de los backends de respaldo |
| Configuraciones de backend | memoryBackendConfigs |
{} |
Sustituciones de configuración por backend |
La configuración se normaliza mediante normalizeMemorySettings() y se almacena en caché en getMemorySettings().
Flujo de inicialización
Sección titulada «Flujo de inicialización»Arranque de la aplicación → importaciones de index.ts (efecto secundario): registran SQLiteBackend → initMemoryBackends() llamado desde el ciclo de vida de la aplicación: 1. Cargar la configuración (getMemorySettings) 2. Configurar el backend principal y los de respaldo 3. Inicializar todos los backends (comprobación de estado) 4. Listo para recibir solicitudesAñadir un nuevo backend
Sección titulada «Añadir un nuevo backend»- Implementa la interfaz
MemoryBackendensrc/lib/memory/<name>Backend.ts - Exporta desde
src/lib/memory/index.ts - Registra con
memoryManager.register(yourBackend)durante el arranque - Configura mediante la configuración: establece
memoryPrimaryBackendcon el ID de tu backend - Prueba tomando como referencia
src/lib/memory/__tests__/generic-backend.test.ts
Ejemplo: backend Brain
Sección titulada «Ejemplo: backend Brain»import { createGenericMemoryBackend } from "./genericBackend";
const brainBackend = createGenericMemoryBackend("brain", "BK-Brain", { baseUrl: process.env.BRAIN_API_URL || "http://localhost:9099", apiKey: process.env.BRAIN_API_KEY, endpoints: { search: "/api/memory/search", create: "/api/memory", health: "/api/health", },});
memoryManager.register(brainBackend);Verificación
Sección titulada «Verificación»Pruebas unitarias
Sección titulada «Pruebas unitarias»npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verboseResultado esperado: 35 pruebas, todas superadas, que cubren:
- Constructor (2)
- Comprobación de estado (4) — éxito, error 500, error de red, latencia
- Inicialización (2) — éxito, error
- Creación (2) — endpoint predeterminado, endpoint personalizado
- Obtención (4) — éxito, 404 → null, excepción distinta de 404, parámetros de ruta personalizados
- Actualización (2) — éxito, 404 → false
- Eliminación (2) — éxito, 404 → false
- Listado (2) — parámetros de consulta, nombres de parámetros personalizados
- Búsqueda (3) — parámetros de consulta, endpoint personalizado, serialización de opciones
- Encabezados de autenticación (2) — token Bearer, encabezados personalizados
- Fábrica (1)
Comprobación de tipos
Sección titulada «Comprobación de tipos»npm run typecheck:coreResultado esperado: 0 errores.
HagiCode
HagiCode es un espacio de trabajo de programación con agentes, flujos estructurados, ejecución multiagente y vistas de Hero Dungeon.
Convierte ideas en software útil con un flujo de trabajo con agentes más inteligente, rápido y ameno.

- SmartLos flujos estructurados convierten la intención en un itinerario ejecutable desde la idea hasta la entrega.
- EfficientLos flujos multiagente permiten avanzar en paralelo con la investigación, implementación y revisión.
- FunHero Dungeon hace que las largas sesiones de programación sean visuales y colaborativas.