512kg / dima
Experimento

Diseño para agentes de IA

Dos experimentos en torno a una pregunta: ¿qué necesita un agente de IA del mundo exterior que una ventana de chat no le da? Ninguno es hoy un producto. De los dos saqué algo que uso en otros sitios.

Infraestructura que un agente puede llamar

Un servicio sobre Cloudflare Workers que le da a un agente tres cosas:

  • Una forma de que lo despierten. Un Durable Object por cada programación pone una alarma. Cuando salta, el agente recibe un webhook firmado y la ejecución queda registrada.
  • Memoria que puede demostrar. Las entradas se sellan y se guardan en R2.
  • Un registro público. Cada evento va a un registro de solo-añadir en D1, donde la propia base de datos rechaza modificaciones y borrados. El registro tiene puntos de control Merkle y va firmado con la clave Ed25519 del propio agente.

Se expone como una API HTTP con descripción OpenAPI y como servidor MCP. Tiene 19 pruebas que corren contra un D1 y unos Durable Objects locales.

Cuatro decisiones suyas:

Sin cola. La entrega y los reintentos van por las alarmas de los Durable Objects, y una reconciliación diaria repara cualquier alarma que se haya perdido. A esta escala no cuesta nada y es suficiente.

«Atestiguado» tiene que ser un hecho. El registro afirma que alguien de fuera ha visto su último estado. Sería fácil escribir esa palabra al enviar el aviso. En vez de eso, una tarea programada ajena al servicio publica la cabeza del registro, y el servicio solo marca una entrada como atestiguada cuando lee esa cabeza en el fichero público. La etiqueta describe algo que ocurrió en otro sitio, no una intención.

Sin dependencias de criptografía. Ed25519, SHA-256, HMAC y AES-GCM salen del WebCrypto que trae el entorno. Además de una dependencia menos, significa que el verificador sin conexión, que corre en Node, comparte las mismas rutinas que el servicio al que comprueba.

No ofuscar el servidor. La petición era que el usuario final no viera nunca el código. Cifrar u ofuscar un Worker no consigue nada, porque la plataforma lo ejecuta en claro, y perjudica el mantenimiento. Toda la lógica está en el servidor y el cliente solo recibe JSON, así que la arquitectura ya cumplía el objetivo.

Una memoria que se queda en mi máquina

Las notas son ficheros Markdown corrientes. Un indexador en Python sin dependencias los convierte en un índice SQLite con búsqueda de texto completo y vectores de un modelo de embeddings local. La búsqueda es híbrida: la mitad de la puntuación es BM25 sobre el texto y la otra mitad, similitud coseno con la consulta. Cualquier asistente la consulta por MCP.

El Markdown es la verdad y el índice es desechable. Se puede borrar y reconstruir a partir de los ficheros. Sin el modelo de embeddings, la búsqueda se queda en texto. Sin la aplicación de notas, los ficheros siguen siendo legibles. Cada capa puede desaparecer sin llevarse por delante las de abajo.

Propone; yo apruebo. El sistema puede ordenar una bandeja de notas, pero solo escribe propuestas. Nada se mueve hasta que las aplico. Olvidar una nota la archiva, no la borra.

Los vectores de modelos distintos no son comparables. El índice guarda con qué modelo se construyó. Si el modelo actual es otro, la búsqueda avisa y se queda en texto, en vez de comparar vectores de tamaños distintos, que devolvería cifras con aspecto de similitud y sin ningún significado.

El modelo de embeddings tenía que ser multilingüe. Las cifras están en este artículo.

  • TypeScript52.9%
  • HTML24.8%
  • Python18.0%
  • CSS2.8%
  • Shell1.4%
  • Otros0.1%