Saltar al contenido principal
Jordi Izquierdo Montalbán
Proyectos

Asistente multiagente

Diseñé y desplegué en mi propio VPS un asistente multiagente que investiga en la web, gestiona mis notas, lee y redacta mis correos y me contesta por voz. Lo blindé contra la inyección de prompts partiendo el sistema en zonas de confianza, y lo respaldan más de 2.400 tests.

Código privado: te lo enseño en una entrevista

Python, LangGraph, Telegram Bot API, SQLite, Pydantic, python-telegram-bot, Ollama Cloud, Groq (voz a texto), Piper TTS, ONNX Runtime, NumPy, APScheduler, httpx, pypdf, PyOTP (TOTP), pytest, Linux (VPS), systemd

El problema

Quería un asistente de verdad: que investigara en internet por mí, me guardara notas y listas, leyera mi correo y me redactara respuestas, y que me mandara cada mañana un resumen del día. Todo desde Telegram y en un servidor mío.

El riesgo en un sistema así no es que alguien entre en el servidor. Es que el agente lea una página o un correo con instrucciones escondidas dirigidas a él, y haga algo que yo no pedí. La página se sirve con normalidad, el agente la lee como está diseñado y, si esa misma pieza tiene acceso a mis datos y capacidad de mandar correos, el desastre está servido.

Un sistema de agentes se vuelve peligroso cuando una misma pieza reúne tres cosas: acceso a datos que importan, exposición a contenido escrito por desconocidos y capacidad de comunicarse hacia fuera. Todo mi diseño parte de ahí.

La solución: zonas de confianza

En vez de intentar detectar inyecciones de prompt filtrando patrones (el lenguaje natural tiene infinitas formas de decir lo mismo), quito el efecto: partí el sistema en zonas para que ninguna pieza reúna las tres cosas a la vez.

ZonaAgentesLee contenido de fueraEscribe en discoActúa hacia fuera
RojaInvestigador, lector de correoSíNoNo
ÁmbarArchivero, memoriaNoSolo en la bóvedaNo
VerdeCarteroNoNoSí, con código TOTP

Además, el orquestador no tiene ninguna herramienta, el redactor tampoco, y el envío de correo no lo decide un modelo: lo ejecuta código determinista después de que yo lo autorice con un código de 6 dígitos de mi móvil.

Ninguna pieza reúne a la vez acceso a mis datos, lectura de contenido ajeno y capacidad de actuar hacia fuera.

Qué hace

  • /investiga: busca, lee hasta tres páginas y resume con fuentes. Rechaza direcciones de la red interna, protocolos raros y puertos que no son web antes de conectar, incluida la IP pública del propio servidor.
  • Notas y memoria: una bóveda de ficheros Markdown y una memoria donde cada recuerdo guarda su procedencia (lo dije yo o lo dedujo el modelo) y su fecha, con borrado exacto.
  • Correo: resume lo recibido en las últimas 24 horas y redacta correos por mí a partir de un recado en bruto («dile que el martes no puedo»). La vista previa es exactamente lo que se envía, y nada sale sin mi código TOTP.
  • Voz: le hablo con notas de voz (transcritas con Groq) y me contesta con voz generada en el propio servidor con Piper.
  • Aviso diario a las 08:00, que llega igual aunque falle una de sus partes.
  • /tools: enciendo y apago grupos de herramientas. Los más expuestos arrancan apagados en cada reinicio.

Cómo lo verifico

La regla del proyecto es «el objetivo no es que funcione: es poder explicarlo». De ahí salen cinco normas de verificación que documenté en un ADR:

  • Mutación en los controles críticos. Rompo el control a mano y compruebo que algún test se pone en rojo. Un test que nunca has visto fallar es una hipótesis, no una comprobación.
  • Probar en real siempre. Con la batería en verde, uso el bot de verdad en el servidor. Así encontré cuatro fallos que ningún test veía, entre ellos un /olvido que contestaba «He borrado la información» sin borrar nada.
  • Ninguna afirmación técnica sin fuente y fecha. Antes de elegir proveedores los comprobé uno a uno con peticiones HTTP reales, y tres de ellos no eran lo que decía internet: uno estaba retirado (HTTP 410), otro exigía tarjeta desde el registro y la capa gratuita del tercero no aparecía en su documentación oficial.
  • Cadena de suministro cerrada. Las dependencias se instalan solo desde un lockfile con hashes, y en producción no se instala nada de desarrollo.
  • Auditorías. Lo reviso con mi propio kit-iso (ISO 27001, 5055, 25010, 27701 y CRA) y lo audité con mi propio agente auditor de código. Cada hallazgo lo reproduje antes de corregirlo.

Stack desglosado

  • Orquestación: LangGraph con estado persistente en SQLite (sobrevive a un reinicio a mitad de conversación).
  • Modelos: Ollama Cloud con cadena de respaldo entre proveedores; Groq para transcribir voz y Piper (local) para sintetizarla.
  • Canal: python-telegram-bot, restringido a un único usuario y solo en privado. A cualquier otro no le responde ni le da pistas de que el bot existe.
  • Seguridad: TOTP con pyotp, validación con Pydantic, guardián de red anti-SSRF y registro de metadatos sin contenido.
  • Programación de tareas: APScheduler para el aviso diario.
  • Despliegue: servidor VPS propio, con copias de seguridad verificadas al restaurar.

Resultados y aprendizajes

Lo que funcionó: diseñar la seguridad por estructura y no por filtros. No tengo que ganar una carrera contra cada nueva forma de inyección: aunque el investigador se crea una instrucción maliciosa, no tiene con qué ejecutarla.

Lo que aprendí: el valor de verificar en vez de fiarse. De los informes de IA, de los blogs y de mis propios tests. Casi todos los fallos serios que encontré aparecieron al usar el bot de verdad o al romper un control a propósito, no leyendo el código.

Lo que sigue: cerrar la fase 14 (los hallazgos de la auditoría con mi agente auditor), y las cifras finales de la entrega.