English version: Building Kernel Memory Protocol: Navigable Memory for AI Agents
El problema de muchos agentes de IA no es que les falte texto en el prompt.
El problema es que no tienen una memoria que puedan consultar, recorrer y
auditar.
Hoy muchas soluciones intentan resolverlo de tres formas:
- copiando parte de conversaciones anteriores dentro del nuevo prompt;
- buscando fragmentos parecidos mediante embeddings;
- delegando la memoria a un framework que la guarda por dentro, pero que no siempre permite inspeccionarla, recorrerla o explicar cómo se llegó a una decisión.
Estas soluciones ayudan, pero se quedan cortas cuando un agente está haciendo
trabajo real. En ese contexto no basta con recuperar texto: hay que poder
reconstruir el proceso.
Las preguntas importantes son otras:
- ¿Qué sabía el agente cuando tomó una decisión?
- ¿Qué intentos de solución probó?
- ¿Cuál falló y por qué?
- ¿Qué información nueva cambió el rumbo del trabajo?
- ¿Qué secuencia de pasos llevó a la solución final?
- ¿Qué evidencias justifican una decisión o una respuesta?
- ¿Puede una persona revisar esas evidencias sin leer toda la conversación en bruto?
- ¿Puede otro modelo navegar la misma memoria sin saber dónde ni cómo está guardada por debajo?
Underpass KMP nació intentando resolver algo más pequeño: recuperar solo el
contexto necesario para que un agente pudiera continuar una tarea sin releer
toda la conversación anterior. A eso lo llamaba rehidratación de contexto:
tomar memoria ya registrada y reconstruir únicamente la parte útil para el
siguiente paso.
Pero al probarlo vi que el problema real era mayor. No bastaba con preparar
mejor el prompt. Necesitaba una capa de memoria que guardase qué ocurrió,
cuándo ocurrió, quién lo produjo, qué evidencias lo sostenían y cómo podía
recorrerse después.
De ahí nace Kernel Memory Protocol, o KMP: una API pequeña y explícita para
escribir, consultar, recorrer, trazar e inspeccionar memoria de agentes.
De buscar fragmentos a recorrer la memoria
El error inicial fue tratar la memoria como si fuera solo un buscador.
Un buscador puede devolver textos parecidos a lo que acabas de preguntar. Eso
sirve para encontrar información suelta, pero no basta para entender un proceso
de trabajo.
Cuando un agente resuelve una tarea, lo importante no es encontrar una frase
parecida. Lo importante es poder reconstruir qué pasó:
- qué información tenía el agente cuando tomó una decisión;
- qué intentos de solución probó;
- qué intento falló y por qué;
- qué datos nuevos cambiaron la dirección del trabajo;
- qué secuencia de pasos llevó a la solución final;
- qué evidencias justifican cada conclusión.
Esa diferencia dio forma al kernel. No quería construir otro mecanismo para
buscar texto. Quería construir una memoria navegable.
Por eso KMP no expone una API de base de datos vectorial. Expone movimientos de
memoria:
ingest -> registrar memoria
wake -> recuperar el estado necesario para continuar
ask -> preguntar a la memoria con evidencia
goto -> ir a un momento o referencia concreta
near -> ver qué ocurrió alrededor de un momento o referencia
rewind -> moverse hacia atrás
forward -> moverse hacia delante
trace -> explicar una ruta de relaciones
inspect -> inspeccionar un nodo de memoria
La idea es mantener pequeña la parte central del sistema. KMP no intenta ser el
agente ni decidir la respuesta final. Su responsabilidad es otra: guardar
memoria estructurada, permitir recorrerla de forma determinista y devolver
evidencias auditables.
La generación de la respuesta final, las reglas de negocio o los plugins de
dominio pueden añadirse alrededor de KMP, sin meterlos en esa parte central.
El modelo mental
El objeto central de Underpass KMP es un about.
Un about es el caso, tema o mundo de memoria sobre el que se está trabajando.
Puede ser un incidente, una tarea, un cliente, un benchmark, un repositorio, un
usuario o un proceso largo de un agente.
Dentro de ese about, la memoria no vive en una sola línea. Puede dividirse en
dimensiones:
about
dimension: session
dimension: agent
dimension: task
dimension: entity
dimension: preference
dimension: attempt
dimension: incident_phase
dimension: success_path
dimension: failure_path
Una dimensión puede representar una sesión, un agente, una tarea, una entidad,
un intento de solución o una fase del proceso.
El tiempo no es una dimensión más.
El tiempo es lo que permite preguntar qué se sabía antes de un paso, qué cambió
después o qué información todavía no existía cuando se tomó una decisión.
El modelo mental queda así:
about -> caso o mundo de memoria
dimensions -> planos de memoria dentro de ese caso
time -> eje temporal que atraviesa esos planos
relations -> por qué dos elementos están conectados
evidence -> evidencias que sostienen la memoria
provenance -> quién lo observó o escribió, y cuándo
Visualmente, una memoria KMP se parece más a esto que a una lista de mensajes:
Figura 1. Un mismo about puede tener varias dimensiones atravesadas por el
tiempo. Las flechas azules son relaciones semánticas; las discontinuas muestran
continuidad dentro de una dimensión.
Este modelo importa porque la memoria de un agente rara vez es lineal.
Una tarea larga puede involucrar a varios agentes. Cada agente puede tener su
propia sesión. Cada sesión puede producir hipótesis, intentos fallidos,
resultados de herramientas y decisiones finales. Una capa de memoria útil debe
permitir mirar una sola dimensión, varias dimensiones o todo el caso, dejando
claro en todo momento qué alcance se está consultando.
Por qué las dimensiones necesitan un espacio de nombres
Una de las decisiones importantes de implementación fue hacer que about
actuase como espacio de nombres de las dimensiones.
Cuando un cliente ingiere memoria, IngestRequest.about define el alcance por
defecto. Internamente, la identidad real de una dimensión equivale a algo como:
about:<about>:dimension:<dimension_id>
Puede parecer un detalle pequeño, pero evita errores importantes.
Si dos tareas distintas tienen una dimensión llamada session:1, no quiero que
se mezclen por accidente. Al meter la dimensión dentro de su about, cada
session:1 pertenece al caso que le corresponde.
Las lecturas también son explícitas:
-
CURRENT_ABOUTconsulta el caso actual; -
ABOUTSconsulta una lista concreta de casos; -
ALL_ABOUTSconsulta todos los casos, pero solo cuando el cliente lo pide de forma intencionada.
Si alguien pide ABOUTS pero no indica qué casos quiere consultar, el kernel
rechaza la petición. Y si alguien pide ALL_ABOUTS, queda claro que está
pidiendo cruzar todas las memorias disponibles.
La razón es sencilla: una consulta que parecía limitada a un caso no debería
acabar mezclando memoria de otros casos por accidente.
Primero el protocolo, luego las herramientas
MCP es una forma cómoda de que un modelo use herramientas. Por ejemplo, permite
que un LLM llame a operaciones como kernel_ask, kernel_near,
kernel_trace o kernel_inspect.
Eso es muy útil, pero no quería que MCP definiera cómo funciona la memoria.
La regla tenía que vivir en un sitio más estable: KMP. En la implementación
actual, esas operaciones están expuestas mediante el servicio gRPC tipado
KernelMemoryService.
La ventaja de separar las dos cosas es simple:
- un LLM puede usar KMP a través de herramientas MCP;
- una aplicación puede llamar directamente al servicio gRPC;
- en el futuro puede existir una API HTTP o un SDK;
- todos esos caminos deben hacer lo mismo cuando preguntan, recorren, trazan o inspeccionan memoria.
El proyecto mantiene una arquitectura hexagonal precisamente para poder
intercambiar esas entradas. La API principal es gRPC. MCP es la entrada
agéntica: la forma de exponer esas mismas operaciones para que una IA pueda
usarlas como herramientas sin confundirse.
He tenido mucho cuidado con la paridad entre MCP y gRPC. Las dos entradas deben
respetar el mismo comportamiento. Y si mañana aparece una API REST, un SDK u
otro tipo de integración, debería poder añadirse como otra entrada al mismo
protocolo, no como una versión distinta de la memoria.
El principio es:
KMP define la semántica de memoria.
gRPC, MCP, HTTP, SDKs y CLIs son formas de usar esa semántica.
La separación queda así:
Figura 2. MCP, gRPC y futuras entradas operan sobre la misma semántica de
memoria definida por KMP.
El tiempo no es un filtro más
En una memoria útil no basta con guardar qué se dijo. También importa cuándo se
dijo y en qué orden apareció la información.
Una respuesta puede tener sentido con la información disponible en un momento y
quedar obsoleta después. Una decisión puede ser razonable antes de recibir el
resultado de una herramienta, pero dejar de serlo cuando aparece un dato nuevo.
Incluso un intento fallido puede ser valioso si explica por qué se eligió
después otra solución.
Por eso KMP no trata el tiempo como un filtro secundario. Lo convierte en una
forma de navegar la memoria:
-
gotopermite ir a un momento o referencia concreta; -
nearmuestra lo que ocurrió alrededor; -
rewindpermite mirar hacia atrás; -
forwardpermite avanzar desde un punto; -
traceexplica una ruta de relaciones y evidencias; -
inspectpermite revisar los detalles de un nodo.
Así no hace falta pedirle a un LLM que relea una conversación enorme y adivine
qué pasó. Una persona o un modelo puede moverse por la memoria con operaciones
explícitas y reproducibles.
Para una persona, el proceso se vuelve inspeccionable. Para una IA, la memoria
se convierte en algo que puede usar mediante herramientas.
Escribir bien la memoria es la parte difícil
Pero todo lo anterior depende de una condición: la memoria tiene que estar bien
escrita.
goto, near, rewind, forward, trace e inspect solo son útiles si lo
que se guardó tiene estructura suficiente. Para poder recorrer una memoria
después, primero hay que escribirla bien.
No basta con guardar texto sin estructura. Eso permite buscar frases después,
pero no reconstruir bien el proceso: qué paso dependía de otro, qué decisión
corrigió una anterior, qué evidencia justificaba una conclusión o qué intento
quedó descartado.
Por eso la escritura es tan importante como la lectura.
Escribir memoria en KMP significa registrar entradas, relaciones, evidencias,
dimensiones y tiempo. También significa decidir cómo se conecta una nueva pieza
de memoria con lo que ya existía.
Ahí aparece una frontera importante. El kernel no tiene responsabilidad de
inferencia. La inferencia la hace quien lo usa: una persona, un agente, un
modelo o un adaptador.
Escribir en KMP no es solo añadir texto. También hay que decir a qué memoria se
conecta ese texto y por qué se conecta ahí. Esa relación es parte de la memoria,
no un detalle secundario. El kernel debe validar lo que se escribe y hacerlo
recorrible; no debe inventar el significado de lo que ocurrió.
A la pieza que escribe memoria la llamo writer. Puede ser:
- una persona;
- un agente;
- un modelo usando MCP;
- un adaptador de benchmark;
- un futuro modelo especializado en escribir memoria.
El writer es quien debe decir por qué una nueva entrada se conecta con memoria
anterior. El kernel comprueba que esa relación sea válida, que esté dentro del
alcance correcto, que tenga evidencia y que pueda auditarse después.
El flujo de escritura queda así:
Figura 3. El writer decide significado y relaciones. KMP valida lo escrito,
pero no infiere por su cuenta qué significa.
Esta separación llevó a dos formas de escribir:
kernel_ingest -> escritura canónica de bajo nivel
kernel_write_memory -> ayuda para writers, que acaba convirtiéndose en ingest
kernel_ingest es la entrada estricta. Recibe memoria ya estructurada.
kernel_write_memory es más cómodo para un writer. Le permite expresar una
entrada nueva y sus conexiones, pero sigue validando la calidad de lo que se va
a escribir:
- nombre de la relación;
- clase semántica;
- referencia del nodo objetivo;
-
why; - evidencia;
- contexto leído antes de escribir;
- calidad del fallback.
Esto importa porque una memoria llena de relaciones vagas no sirve de mucho.
Si todas las relaciones dicen supports_answer, la memoria está conectada,
pero no explica nada. No dice si una entrada depende de una respuesta anterior,
la contradice, la refina, la sustituye o simplemente aparece cerca de ella.
En KMP, la calidad de las relaciones forma parte de la calidad de la memoria.
Las relaciones deben ser honestas
También existe el riesgo contrario: inventar relaciones demasiado ricas.
Un writer no debe crear aristas aparentemente inteligentes solo para que el
grafo parezca mejor. Si no puede justificar una relación desde el contexto que
ha observado, debe caer a una relación más simple, anémica o estructural.
Ese fallback no es un fracaso. Es una señal honesta.
Un buen sistema de memoria debe poder decir:
Sé que estos nodos están relacionados por orden o cercanía.
Todavía no conozco una razón semántica más fuerte.
Esto crea métricas que puedo inspeccionar:
- relaciones ricas;
- relaciones anémicas;
- relaciones estructurales;
- relaciones sospechosas o rechazadas;
- contexto previo observado antes de escribir;
- cobertura de evidencia.
Estas métricas me dan una forma práctica de mejorar el writer sin ocultar la
incertidumbre.
La frontera entre memoria e interpretación
Para medir la calidad de KMP he estado trabajando principalmente con dos tipos
de benchmarks.
MemoryArena me interesa porque se parece más al tipo de memoria que quiero
construir: tareas con varios pasos, intentos, feedback, cambios de rumbo y
memoria que debe reutilizarse más adelante.
LongMemEval me interesa por otra razón. Es más conversacional, pero estresa un
caso muy útil: recuperar evidencia dispersa entre muchas sesiones y comprobar
si el sistema sabe usarla para responder.
Esa comparación dejó clara otra cosa: una memoria puede servir para muchos
casos de uso distintos, y no todos requieren el mismo tipo de interpretación.
El kernel puede recuperar la evidencia correcta y, aun así, no producir la
respuesta final si el lector tiene que hacer trabajo de dominio:
- sumar dinero;
- contar entidades;
- deduplicar eventos;
- elegir el valor más reciente;
- comparar fechas;
- normalizar código, URLs o monedas;
- decidir si un importe está pagado, planificado, cancelado o solo mencionado.
Ahí aparecen los plugins.
En este contexto, un plugin es una pieza especializada que interpreta la
evidencia que el kernel ha recuperado. Por ejemplo: detectar importes, sumar
dinero, comparar fechas, contar entidades, reconocer URLs, identificar código o
resolver cuál es el valor más reciente.
La razón para introducir plugins no es ganar un benchmark concreto. Es poder
adaptar la memoria a casos de uso distintos sin meter todas esas reglas dentro
de la parte central de KMP.
No quiero contaminar el kernel con lógica específica de un benchmark, de dinero,
de fechas, de preferencias o de cualquier otro dominio. El kernel debe seguir
siendo agnóstico al caso de uso: guarda memoria, relaciones, tiempo, evidencias
y trazas. La interpretación especializada debe vivir fuera.
El kernel debe recuperar memoria y evidencia de forma fiable. Los plugins y los
lectores pueden trabajar después sobre esa evidencia para resolver operaciones
de dominio.
La separación es esta:
kernel -> memoria, recorrido, prueba e inspección
plugins -> extracción de valores tipados y operaciones deterministas
lector -> construcción de respuesta y política de tarea
Figura 4. KMP recupera evidencia trazable. Los plugins interpretan valores
tipados y el reader construye la respuesta final.
Esta distinción es central.
Underpass KMP no debe convertirse en una solución hecha a medida para un
benchmark ni para un dominio concreto. Debe hacer bien su parte: recuperar
memoria, evidencias y relaciones de forma fiable para que lectores, plugins y
futuros modelos especializados puedan trabajar encima.
Por qué importa para los agentes
La memoria de un agente no debería servir solo para responder a una pregunta
mirando conversaciones antiguas.
Lo realmente interesante aparece cuando una IA trabaja durante varios pasos:
prueba una hipótesis, usa herramientas, se equivoca, corrige el rumbo, recibe
información nueva y finalmente llega a una solución. Ahí la memoria no es un
archivo de texto. Es el registro navegable de cómo se resolvió algo.
Con una memoria así, una persona o un modelo puede volver sobre el proceso y
preguntar:
- qué se sabía antes de tomar una decisión;
- qué intento de solución falló;
- qué dato nuevo cambió el rumbo;
- qué agente introdujo una suposición equivocada;
- por qué una respuesta posterior sustituyó a una anterior;
- qué secuencia de pasos llevó a la solución final;
- qué evidencias justifican el resultado.
Ahí es donde la memoria multidimensional y temporal se vuelve útil. Cada agente
puede ser una dimensión. Cada sesión, tarea, entidad, intento o fase del trabajo
puede ser otra. El tiempo permite atravesarlas y entender cómo cambió el estado
del proceso.
El grafo no es una visualización decorativa. Es la forma del proceso: qué pasó,
en qué orden, conectado con qué, y por qué.
La observabilidad no es opcional
Si la memoria de agentes es infraestructura, debe ser observable.
Necesito saber:
- si una escritura llegó a ser consultable;
- cuánto tardó la proyección;
- qué alcance usó una consulta;
- cuántas referencias fueron inspeccionadas;
- si funcionó la paginación de
trace; - si la prueba estaba completa;
- si un lector ignoró evidencia correcta;
- si un writer creó relaciones ricas, anémicas o sospechosas.
Por eso el kernel registra logs estructurados KMP y MCP, métricas OTel para
llamadas KMP, latencia de procesamiento de proyección, métricas de calidad de
relaciones y comportamiento explícito de inspect y trace.
El objetivo operativo es simple:
Una respuesta fallida de un agente debe poder clasificarse.
Las clases posibles incluyen:
- gap de ingesta;
- gap de proyección;
- gap de recuperación;
- gap de prueba;
- gap de consumo por parte del lector;
- gap de razonamiento de tarea.
Sin esa clasificación, todos los fallos parecen lo mismo: "la IA se equivocó".
Eso no es suficiente para agentes en producción.
Seguridad y auditabilidad
Una memoria navegable también puede ser una memoria sensible.
Si el sistema puede reconstruir qué ocurrió, quién lo dijo, qué decisión se
tomó y qué evidencias la justificaban, entonces también tiene que controlar muy
bien quién puede ver cada cosa y en qué nivel de detalle.
No es lo mismo pedir un resumen que pedir la memoria en bruto. No es lo mismo
consultar el caso actual que cruzar memoria de muchos casos. Y no es aceptable
que logs o trazas acaben exponiendo secretos, credenciales, prompts completos o
contenido que no hacía falta sacar.
Por eso KMP trata la seguridad y la auditoría como parte del diseño, no como un
añadido posterior:
- las fronteras de la API están tipadas;
- las lecturas tienen alcance explícito;
- la inspección en bruto es una opción deliberada;
- los errores fallan rápido en lugar de activar fallback silencioso;
- las referencias, evidencias y relaciones están pensadas para poder auditarse;
- TLS/mTLS se usa en las fronteras de infraestructura que lo soportan.
El objetivo es que una persona pueda revisar por qué el sistema devolvió una
respuesta sin tener que abrir toda la memoria, y que al mismo tiempo el sistema
no exponga más información de la necesaria.
Qué promete Underpass KMP
Antes de hablar de resultados, conviene dejar claro qué promete KMP y qué no
pretende resolver.
Underpass KMP no es:
- un sustituto general de una base de datos vectorial;
- un generador de respuestas finales;
- una solución hecha a medida para benchmarks;
- un framework de agentes oculto;
- una garantía de que cualquier modelo interpretará bien la evidencia.
Es una capa de memoria determinista y auditable. Su trabajo es conservar la
estructura suficiente para que personas, agentes, plugins, lectores y futuros
modelos especializados puedan trabajar sobre la memoria sin volver a leer todo
desde cero.
Benchmarks: qué aprendí
He tenido cuidado de no afirmar más de lo que soporta la evidencia actual.
El resultado temprano más importante no es "el kernel gana todos los benchmarks
de memoria". Lo importante es que el kernel hace visible una frontera que antes
era difusa:
¿Falló la recuperación de memoria, o falló el lector al usar evidencia correcta?
Esa distinción importa.
En una ejecución MemoryArena public-TLS de 100 tareas, progressive search y
smart-writer, el kernel alcanzó:
| Métrica | Resultado |
|---|---|
| Eventos KMP correctos | 2259/2259 |
| Consultas known-at-clean | 753/753 |
| Full-ref recall | 753/753 |
| Fugas de respuestas futuras | 0 |
| Score local alineado con el paper | 97/100 |
| Fallos finales | 3 |
Los 3 fallos finales quedaron clasificados como fallos de selección de
respuesta del lector sobre evidencia completa, no como fallos de recuperación
del kernel ni contaminación del grafo.
En un slice realista MemoryArena 2x/domain, el kernel alcanzó:
| Métrica | Resultado |
|---|---|
| Eventos KMP correctos | 221/221 |
| Consultas known-at-clean | 73/73 |
| Full-ref recall | 73/73 |
| Fugas futuras | 0 |
| Referencias inesperadas | 0 |
| Referencias perdidas | 0 |
Los fallos de tarea restantes fueron gaps del lector o del agente, no gaps de
evidencia.
LongMemEval enseñó una lección distinta. En un slice smart-writer multi-session
de 30 items, la evidencia recuperada fue completa, pero la misma evidencia
obtuvo resultados distintos según el lector:
| Lector | Resultado |
|---|---|
| GPT-4o | 22/30 |
| Gemma 4 31B | 25/30 |
En una prueba de 100 items usando un modelo externo de embeddings y
derivaciones, la frontera volvió a aparecer:
| Medida | Resultado |
|---|---|
| Recall amplio de evidencia | ~99% |
| QA agregado multi-session oficial end-to-end | 71,7% |
Los fallos restantes fueron sobre todo problemas de operandos estructurados:
predicados de conteo perdidos, evidencia calificadora omitida o errores de
comparación.
Para mí, esa información es valiosa.
Me dice que la siguiente mejora no consiste en esconder más lógica dentro del
kernel. La siguiente mejora está en recuperar mejor candidatos, reordenarlos
con un reranker, extraer operandos tipados y usar plugins de dominio
reutilizables.
Hoja de ruta
El siguiente paso es seguir validando la idea con casos reales y hacer que el
kernel sea más fácil de usar.
A corto plazo, el trabajo es práctico:
- ejecuciones más fuertes en MemoryArena y MemoryAgentBench;
- regresión LongMemEval de estilo oficial como benchmark secundario;
- recuperación híbrida de candidatos detrás de puertos;
- experimentos de reranking;
- exploración visual de grafo y línea temporal para recorrer la memoria;
- mejor observabilidad de prueba y recorrido;
- paginación, límites y alcances estables en KMP.
A medio plazo, la dirección se vuelve más interesante:
- un modelo pequeño especializado en operar herramientas del kernel, entrenado con trayectorias MCP auditadas;
- consultas de proceso como
known_at,why,failed_paths,final_pathybest_path; - plugins de interpretación reutilizables para dinero, fechas, conteos, URLs, código y operadores específicos de dominio;
- tests de conformidad para que la semántica del kernel sea independiente del backend de almacenamiento;
- demos visuales públicas que permitan reproducir un proceso agente como grafo y línea temporal.
El modelo operador me parece especialmente importante. No sería un agente
general ni un modelo que "entiende la memoria" de forma mágica. Sería un
especialista pequeño entrenado para usar KMP de forma eficiente:
¿Qué herramienta debería llamar ahora?
¿Con qué argumentos acotados?
¿Debo inspeccionar, trazar, moverme temporalmente o parar?
¿Qué referencias prueban que tengo evidencia suficiente?
Es un problema estrecho y medible.
La tesis del producto
La tesis detrás de Underpass KMP es sencilla:
Los agentes fiables necesitan memoria que puedan navegar, no solo contexto que
puedan recuperar.
Esa memoria debe estar:
- acotada por aquello sobre lo que trata;
- dividida en dimensiones significativas;
- recorrible a través del tiempo;
- conectada por relaciones honestas;
- respaldada por evidencia;
- inspeccionable por personas;
- usable mediante herramientas por LLMs;
- observable y auditable en producción.
Por eso estoy construyendo Kernel Memory Protocol: para que la memoria de un
agente no sea solo texto acumulado, sino una estructura que pueda recorrerse,
inspeccionarse y reutilizarse.
No se trata de hacer prompts más largos. Es justo lo contrario: reconstruir el
contexto útil sin obligar al modelo a leer todo el material en bruto, y hacer
que el consumo de tokens sea inteligente, medible y auditable.
La meta es convertir la memoria de los agentes en una capa real de trabajo.
Si os interesa esta línea de trabajo, podéis revisar el repositorio de
Underpass KMP. Y si os
parece útil, una estrella en GitHub ayuda a darle visibilidad al proyecto.
Escrito por Tirso García Ibáñez ·
LinkedIn ·
Underpass AI
Underpass KMP forma parte del proyecto Underpass AI. El repositorio está
licenciado bajo Apache License 2.0,
salvo que se indique lo contrario.
Copyright © 2026 Tirso García Ibáñez.




Top comments (0)