Plan — Pasar el engine (nef-silo) a Hugging Face

DECIDIDO (2026-09-22): el engine va a OCI free, no a HF (ver [CREDENCIAL].md). Este documento queda como referencia de la ruta HF, útil solo para corridas por lote (p. ej. extracción).

Fecha: 2026-09-22. Estado: planning (Linode caído; ver 2026-09-22-linode-caido-permanentemente.md). Repo ya en GitHub: github.com:[usuario]/nef-silo.

Qué hay que reemplazar exactamente

El engine corre hoy como cron cada 30 min: capturar && exportar --dias 7 --perfil editorial (ver run.sh).

NecesidadDetalle medido
Cómputo programadocada 30 min, duración ~1–3 min por corrida
Estado persistentedata/silo.db (36 MB), estado.json, silo.jsonl, export-editorial.json
Depssolo feedparser>=6.0
Red122 fuentes RSS/API + RSSHub para X (51) y Telegram (6)

Punto clave: el estado son ~40 MB. No hace falta un disco grande; hace falta persistencia entre corridas y un disparador cada 30 min.

Verdicto: HF SÍ sirve, pero con la pieza correcta — Scheduled Jobs

⚠️ Spaces no es la respuesta para esto: disco efímero (free), y un Space CPU básico se pausa tras 48 h sin visitas → un cron que no recibe tráfico se duerme. Requiere keepalive hack + storage persistente de pago (~$5/mes).

✅ HF Scheduled Jobs sí es el fit:

hf jobs scheduled run --name silo-cap "*/30 * * * *" <imagen> <comando>
# o con uv:  hf jobs scheduled uv run "*/30 * * * *" script.py
  • cron nativo (sintaxis */30 * * * *), timeout default 30 min (nuestro job ~2 min).
  • Jobs = contenedores Docker efímeros, con red. Available en Pro / saldo > 0.
  • Estado en un Storage Bucket montado como volumen (/data) de lectura- escritura, o bajado/subido con hf_hub al inicio/final de cada corrida. silo.db (36 MB) cabe de sobra.

Arquitectura propuesta

[hf jobs scheduled "*/30 * * * *"]
        │
        ▼
  contenedor efímero
   1. monta bucket -> /data   (silo.db, estado.json, silo.jsonl)
   2. python silo_engine.py capturar
   3. python silo_engine.py exportar --dias 7 --perfil editorial
   4. deja /data actualizado (bucket)  -> surtido lo consume

El export-editorial.json resultante ya lo sirve nginx hoy; en HF habría que publicarlo (bucket público, o el mismo repo del engine) para que surtido (Vercel) lo lea.

🔴 El blocker real: RSSHub

silo_engine.py construye URLs de X y Telegram como RSSHUB + "/twitter/user/<handle>" (línea 633) y /telegram/channel/... (636), con RSSHUB_URL por defecto http://localhost:1200. En el Linode, RSSHub corría como sidecar Docker con TWITTER_AUTH_TOKEN. En un Job no hay sidecar. Tres salidas:

OpciónCómoConsecuencia
A. RSSHub en la misma imagenDockerfile con Node + RSSHub; el comando arranca RSSHub en background y luego el engineself-contained; imagen pesada (Node+deps), cold start mayor
B. RSSHub externoRSSHUB_URL=https://rsshub.app (público) o una instancia propiareintroduce un host; instancias públicas sin token suelen fallar en X
C. Sin X/TGdejar solo fuentes RSS (65 de 122)se pierde 51 X + 6 TG → degrada el engine

Recomendación: A (todo en HF, sin tercer host), con B como parche rápido para la Fase 0.

Costo (a medir, no a suponer)

  • Jobs = pago por segundo; Pro da $2/mes de crédito.
  • Aritmética: 48 corridas/día × ~2 min × 30 días ≈ 48 h de CPU/mes. Hay que medir 2 o no.
  • Storage Bucket: 40 MB → coste marginal ~0.
  • Sospecha: varias corridas cortas con cold start cada 30 min pueden salir más caras que un VPS pequeño fijo. Medir antes de comprometerse.

Fases (cada una termina en artefacto verificable)

  1. Medir: un hf jobs run manual que ejecute capturar UNA vez (RSSHUB externo o local en la imagen) + cronometrar duración y coste en la factura. → número de $/corrida. Decidir si HF es viable en coste.
  2. Empaquetar: Dockerfile del engine (feedparser + código + opción RSSHub). → hf jobs run <imagen> python silo_engine.py --help responde.
  3. Estado: bucket montado con un silo.db copiado de local. → estado.json leído/escribido persiste entre dos corridas.
  4. Scheduled Job cada 30 min con --continue/idempotencia. → 2 corridas seguidas sin duplicar items.
  5. Publicar salida (export-editorial.json) para surtido. → surtido lee la nueva URL.
  6. Apagar cualquier resto del Linode (cron/scripts) y actualizar run/deploy.

Riesgos

  • Cold start por corrida (descarga imagen + arranque) puede dominar el costo.
  • Drift de RSSHub: sin el token de X, las rutas fallan silenciosamente.
  • Límite de 30 min por job: si una corrida se cuelga, se corta (bien).
  • Tool-hopping: esto es mover infra, no arreglar el engine. Si el objetivo real es “que el engine siga vivo y barato”, un OCI free tier (VM always-on, ya documentado en 2026-09-20-oci-free-mlops-lab.md) es la respuesta aburrida y fiable; HF brilla si además quieres el cómputo cerca de modelos/GPU.

Decisión pendiente (antes de Fase 1)

  1. RSSHub: ¿A (dentro de la imagen), B (externo) o C (soltar X/TG)?
  2. ¿HF de verdad, o el objetivo es solo “engine vivo”? Si es lo segundo, OCI free tier gana en simplicidad.