Infraestructura de sistemas centrales, banca
Administración de gateways de integración, balanceadores, brokers de mensajería y observabilidad con Splunk en un entorno bancario de alta disponibilidad.
Más de 15 años manteniendo encendidos sistemas críticos del sector financiero y de pagos, y trabajando con hardware desde el año 2000. Investigador independiente en sistemas de conocimiento con LLMs locales. Escribo sobre lo que mido, incluido lo que sale mal.
Consejo: abre la terminal y escribe help.
Sistema autónomo de conocimiento en Rust que construye un grafo de memoria episódica con un LLM local (Gemma 4 E2B sobre llama.cpp CUDA). Su aporte principal fue la auditoría de nodos fantasma y una arquitectura de honestidad epistémica.
Dataset abierto de normativa bancaria de la República Dominicana, limpio y listo para RAG y evaluación. 142 documentos bajo licencia CC-BY-4.0.
Microservicio en Rust (Tokio, canal MPSC, sqlx/PostgreSQL) que absorbe ráfagas de transacciones simuladas, responde ACK/NACK y registra anomalías con su motivo de detección.
Asistente sobre normativa tributaria y laboral dominicana en Telegram y WhatsApp, con PostgreSQL y la API de Claude como motor.
$ pacman -Q --explicit
$ git log --reverse=false trayectoria
Administración de gateways de integración, balanceadores, brokers de mensajería y observabilidad con Splunk en un entorno bancario de alta disponibilidad.
Diseño, auditoría y publicación de un sistema autónomo de conocimiento en Rust. Preprint en Zenodo, dataset en Hugging Face y artículos técnicos en dev.to.
$ cat /etc/pacman.d/mirrorlist
Ernesto Arias Díaz · 6 min
ANIMUS llevaba meses corriendo en modo autónomo. Cada ciclo leía una fuente, extraía hechos y los guardaba como nodos en un grafo. El contador subía de forma constante y yo lo usaba como prueba de que el sistema aprendía.
Un día decidí no creerle al contador y revisar los nodos uno por uno.
Encontré tres mecanismos. El primero, que el ciclo de "gaps" volvía a investigar el mismo concepto con preguntas casi idénticas y guardaba cada respuesta como un nodo nuevo. El segundo, que el modelo generaba reflexiones sobre sus propias reflexiones, y esas también entraban al grafo como si fueran conocimiento. El tercero, que no había ninguna verificación de igualdad antes de insertar.
Ninguno de los tres producía un error. El sistema funcionaba y el número crecía. Justamente por eso nadie lo notaba.
rfind() sobre el marcador de cierre, para que nada truncado entre al grafo.Una métrica que el sistema reporta sobre sí mismo no es una medición. En operaciones pasa igual: un dashboard verde dice lo que el agente de monitoreo cree, no lo que el usuario vive. Hay que verificar desde fuera.
Metodología y scripts completos en el preprint de Zenodo.
Ernesto Arias Díaz · 7 min
Un switch de pagos no puede esperar a que una base de datos termine de escribir. Si la respuesta tarda, la línea se queda ocupada y la cola crece. Por eso este servicio separa dos trabajos: recibir y confirmar, y luego persistir.
TcpListener de Tokio acepta conexiones y crea una tarea por socket.mpsc con capacidad para 10,000 tareas y responde ISO_8583_ACK de inmediato.ISO_8583_NACK y queda un warning en el log. Nunca silencio.La primera versión solo guardaba registros que ya venían marcados como anómalos por el simulador. En producción nadie te etiqueta el fraude. La versión actual inspecciona los campos:
fn motivo_ingesta(payload: &str) -> Option<String> {
let mut motivos = Vec::new();
let mti = extraer_campo_log(payload, "MTI:");
let proc_code = extraer_campo_log(payload, "PROC:");
if payload.contains("UNKNOWN") || payload.contains("ERR_VAL") {
motivos.push("campo_destructivo");
}
match mti.as_deref() {
None => motivos.push("mti_ausente"),
Some(m) if !MTIS_VALIDOS.contains(&m) => motivos.push("mti_invalido"),
_ => {}
}
if proc_code.as_deref() == Some("990000") {
motivos.push("proc_fraude");
}
(!motivos.is_empty()).then(|| motivos.join(","))
}
El motivo se guarda junto al registro. Así se puede medir después cuántas detecciones coinciden con la verdad del simulador.
Al principio puse un LLM a dar el veredicto final sobre cada registro. Fue un error de diseño. Las reglas ya estaban en la función de arriba: son deterministas, auditables y cuestan microsegundos. Un modelo de lenguaje en ese camino agrega latencia y no-determinismo donde un regulador exige lo contrario. Los LLMs sirven para explicar una anomalía a un analista, no para decidir si existe.
Otros detalles: credenciales fuera del código mediante DATABASE_URL, migraciones idempotentes con ADD COLUMN IF NOT EXISTS y columnas TEXT donde antes un VARCHAR(4) rechazaba justo el MTI inválido que se quería capturar.
Ernesto Arias Díaz · 4 min
El corpus tenía 817 PDFs de normativa bancaria dominicana. El pipeline los procesaba todos sin errores. Cuando crucé la lista de archivos contra los nodos del grafo, faltaban 262.
El procesador descartaba cualquier documento cuyo texto extraído tuviera menos de 100 caracteres. La regla tenía sentido para filtrar archivos vacíos. Pero los PDFs escaneados no tienen capa de texto: la extracción devolvía casi nada y el documento desaparecía sin dejar rastro en ningún log.
Cuando la extracción nativa no supera el umbral, el documento pasa por OCR con pytesseract antes de decidir. De los 262, se recuperaron 259. Los tres restantes eran ilegibles de verdad.
Después agregué 738 enlaces entre documentos relacionados, porque el grafo tenía forma de estrella: todo apuntaba a un nodo central y nada conectaba los documentos entre sí.
Todo filtro que descarta datos debe contar lo que descarta y decirlo en voz alta. Un continue sin log es un agujero negro.
Ernesto Arias Díaz · 5 min
ANIMUS siempre corrió en una laptop: primero una i5 sin GPU dedicada, después una Dell Precision con una RTX 3050 de 4 GB. Cambié el motor de inferencia tres veces.
| Motor | Hardware | Velocidad |
|---|---|---|
| Llama 3.2 3B ajustado (QLoRA, Q4_K_M) | CPU | 3.5 tok/s |
| BitNet b1.58 2B (i2_s) | CPU | 19 tok/s |
| Gemma 4 E2B (UD-Q4_K_XL) | RTX 3050, CUDA | 76.8–79.3 tok/s |
Medí la latencia completa de una consulta: mediana de 11.45 segundos. La generación era la parte pequeña. El arranque en frío, unos 7 segundos, dominaba todo. Optimizar tokens por segundo cuando el cuello de botella es otro es un buen recordatorio de por qué hay que medir de punta a punta.
Un modelo más fluido no es un modelo más correcto. Gemma confabulaba con fluidez de experto, algo más peligroso que las alucinaciones torpes de los modelos anteriores, porque convence. Eso obligó a construir la capa de validación estructural antes de dejarle escribir en el grafo.
llama-server persistente, con chequeo de /health antes de enviar trabajo.Ernesto Arias Díaz · 5 min
ANIMUS empezó en febrero de 2026 con una idea: un sistema que aprende solo, ciclo tras ciclo, y construye su propia memoria. Ocho meses después lo archivo. Este es el porqué.
Comparé ANIMUS contra un RAG vectorial simple y contra GraphRAG de Microsoft, con el mismo modelo generador y 36 preguntas verificadas.
| Sistema | Puntaje total |
|---|---|
| ANIMUS | 58.3% |
| GraphRAG | 56.9% (0% de alucinación) |
| RAG vectorial | 50.0% |
Con 36 preguntas, la diferencia entre ANIMUS y GraphRAG está dentro del ruido. Ninguno dominó. El techo lo ponía un modelo de 2B parámetros, no la arquitectura.
Cerrar un proyecto con datos es parte del trabajo. Lo más valioso que construí no fue el sistema, fue el hábito de auditarlo.