← Blog·Ingeniería

Por qué MONO está hecho en Go y no en Python

·Equipo MONO·8 min de lectura

El argumento Python: ecosistema IA

Python ganó AI por razones históricas: PyTorch y TensorFlow fueron escritos ahí, la comunidad académica publica en Python, y los SDKs de OpenAI, Anthropic, etc. salen primero en Python.

Para prototipar un agente, Python es imbatible: importas `anthropic`, escribes 20 líneas, tienes una demo. LangChain, LlamaIndex, CrewAI — todos Python.

El problema es que el prototipo y la plataforma de producción son problemas distintos. Para el primer MONO, Python habría sido suficiente. Para 1000 MONOs corriendo 24/7 cada uno esperando WhatsApp a cualquier hora, no.

Lo que rompe Python bajo carga real

1. GIL. El Global Interpreter Lock impide que un solo proceso Python use múltiples cores realmente. Para un agente que debe llamar LLMs, bases de datos, webhooks, APIs externas — todas IO-bound — esto se compensa con async, pero async en Python es notoriamente frágil (mixing sync/async libraries = errores silenciosos).

2. Memoria por goroutine vs por thread. Un thread en Python ocupa 8 MB. Una goroutine en Go ocupa 2 KB. Cuando tu MONO escucha webhooks de WhatsApp, Telegram, Gmail, Stripe, cron jobs y escribe a SurrealDB al mismo tiempo, quieres miles de "trabajadores" ligeros. Go te da eso gratis.

3. Startup time. El binario Go arranca en ~20ms. Un proceso Python con dependencias IA tarda 2-5 segundos. Esto importa cuando la VPS reinicia (actualizaciones, kernel patches) — queremos downtime de 100ms, no 5 segundos.

4. Deploy. Go compila a un binario estático de ~20MB. `scp binary vps:/opt/mono/`, reinicia, listo. Python requiere virtualenv, pip-install, versiones de C libraries compatibles, y la mitad de las veces algún paquete quiere `gcc`. Deploy a 100 VPSs simultaneamente con Go es un for-loop. Con Python es una pesadilla DevOps.

Lo que Go hace bien para agentes

  • Concurrencia con goroutines. Cada mensaje entrante spawns una goroutine. Agendamos tasks con time.AfterFunc. Fan-out de webhooks con sync.WaitGroup. Todo del standard library.
  • Channels. La metáfora de "comunicar por memoria compartida" vs "compartir memoria comunicando" encaja naturalmente con agentes que procesan streams de eventos (webhook → parse → route → respond).
  • Tooling de primera. go test -race encuentra race conditions automáticamente. pprof da perfiles de CPU/memoria sin instrumentar. Nada que instalar.
  • Sistema de tipos. Interfaces estructurales hacen el testing de componentes trivial. Los errores son valores, no excepciones — obligan a pensar en el flujo de fallas.
  • Binario estático. El VPS no tiene Python, ni virtualenv, ni dependencias del sistema. Solo /opt/mono/mono y systemctl start.

Lo que cuesta usar Go

No todo es victoria. Las trade-offs reales:

  • SDKs oficiales menos maduros. Anthropic tiene SDK Go, pero LangChain solo tiene Python/JS. Para MONO escribimos nuestro propio wrapper (~500 líneas) porque no necesitamos framework masivo.
  • Menos bibliotecas ML. Si tu agente incluye fine-tuning local, reranking, embeddings custom — Python te da más herramientas. MONO usa APIs externas para esto, así que no nos afecta.
  • Contratar es más difícil. Hay 10× más devs Python que Go. Pero los Go devs que encuentras tienden a ser senior, porque Go atrae a gente que ya sintió el dolor de Python en producción.
  • Verbosidad. if err != nil en cada call. Después de 10k líneas se vuelve mecánico y en realidad ayuda a la rigurosidad.

¿Cuándo sí usar Python?

Honestamente: si estás prototipando, validando product-market fit, o construyendo una herramienta interna con <100 usuarios. Python te hará iterar 3× más rápido al principio.

El momento de migrar (o reescribir) es cuando empiezas a ver: (a) bottlenecks de concurrencia, (b) deploys de >5 min, (c) memory leaks que requieren reiniciar workers cada hora, (d) equipos pasando más tiempo en ops que en features.

MONO empezó en Go directamente porque la arquitectura (1 VPS por usuario) hace que deploy y concurrencia sean el problema principal, no la velocidad de iteración.

Números

Binario MONO: 24MB. RAM base: 35MB. Arranque: 18ms. Latencia mediana de respuesta (sin LLM): 3ms. Mil goroutines idle: 5MB extra. Comparativo Python equivalente con FastAPI + uvicorn + asyncio: 180MB RAM base, 2.1s arranque, 12ms latencia mediana. Para un producto que corre en la VPS del usuario, ese delta se acumula.