Panel de Control — Monitorización de Infraestructura
DestacadoDashboard centralizado con health endpoints, métricas de sistema, API REST + WebSocket y estado de agentes en tiempo real para la infraestructura del equipo.
El problema
Tres servidores (dos físicos en casa, un VPS en Oracle Cloud) y tres agentes IA significa que cuando algo falla, no sabes qué ha sido hasta que entras a mirar. Con servicios repartidos entre nodos y cada máquina con sus propios logs, diagnosticar un problema requiere hacer SSH a varias máquinas y revisar cada servicio por separado.
Necesitaba un panel que respondiera instantáneamente a: ¿Los servicios están UP o DOWN? ¿Qué CPU y RAM consume cada cosa? ¿Qué está haciendo cada agente ahora?
La decisión — dashboard unificado con health checks automatizados
En lugar de soluciones de monitorización pesadas (Nagios, Zabbix), opté por un dashboard ligero en Node.js que consulta periódicamente los health endpoints de cada servicio y muestra el estado en un solo lugar.
La clave: el panel no solo muestra datos de infraestructura — también consume los heartbeats de los agentes IA, integrando monitorización de sistema y visibilidad del equipo en un mismo sitio.
Arquitectura — fusión de dos sistemas
El proyecto nació de la fusión de dos sistemas que antes funcionaban por separado: el Agente Conectora (un broker FastAPI con oficina pixel-art) y el Panel de Control original (Miniverse, un mundo pixel con heartbeats REST). En lugar de mantener ambos, los unifiqué en un solo backend Node.js en el puerto 4321, con un frontend React SPA en el 3001.
| Servicio | Puerto | Rol |
|---|---|---|
| OpenClaw Gateway | 18789 | Gestión de agentes — no se toca |
| Hermes Broker | 18792 | Bridge interno: conecta OpenClaw con Miniverse |
| Miniverse Server | 4321 | API REST + WebSocket unificada del panel |
| Panel Frontend | 3001 | React SPA con tres rutas |
El frontend tiene tres rutas: / (mundo pixel-art con sprites de agentes), /oficina (oficina pixel-art migrada del antiguo sistema HTML), y /dashboard (métricas y salud del sistema).
Implementación
Backend — Node.js raw HTTP
El backend corre en Node.js + TypeScript sin Express (deliberadamente raw HTTP, para mantener la huella mínima). Pino como logger estructurado. WebSocket con la librería ws para tiempo real.
Servicios monitorizados
| Servicio | Puerto | Función |
|---|---|---|
| Miniverse Server | 4321 | Backend API del panel con health endpoint |
| OpenClaw | 3000 | Sistema de control de agentes |
| Hermes Broker | 18789 | Broker de mensajería entre agentes |
API completa (puerto 4321)
| Método | Ruta | Función |
|---|---|---|
| GET | /api/service-health | Health checks de todos los servicios con latencia |
| GET | /api/metrics | CPU load, RAM usada, uptime, conexiones activas |
| GET | /api/agents | Lista de agentes con estado y heartbeat |
| GET | /api/agents/:id | Detalle de un agente |
| GET | /api/info | Información general del servidor, versión, subsistemas |
| POST | /api/heartbeat | Recepción de heartbeat de agentes |
| POST | /api/agents/remove | Eliminar un agente del registro |
| GET | /api/observe | Snapshot del mundo y eventos recientes |
| POST | /api/observe | Recibe eventos de Hermes y los retransmite por WebSocket |
| POST | /api/act | Enviar acción de un agente (move, speak, emote, status…) |
| GET | /api/inbox | Mensajes pendientes de un agente |
| GET | /api/channels | Canales y sus miembros |
| GET | /api/events | Eventos recientes |
| POST | /api/generate | Generar assets gráficos con IA (FAL) |
| POST | /api/webhook | Registrar callback para mensajes de agente |
| DELETE | /api/webhook | Eliminar webhook |
| POST | /api/save-world | Guardar estado del mundo |
| POST | /api/hooks/claude-code | Hook para eventos de Claude Code |
Sistema de heartbeats
Cada agente escribe su estado en un archivo JSON en el NAS, y Miniverse los lee por polling cada 30 segundos. El formato unificado incluye:
{
"agent": "codex",
"state": "working",
"task": "Refactorizando módulo de sync",
"position": "desk_2_0",
"timestamp": 1751326200,
"energy": 1,
"sensors": {
"cpu_load": 0.45,
"session_age_min": 3,
"nas_messages_pending": 1
}
}
Estados soportados: working, idle, thinking, error, waiting, collaborating, sleeping, listening, speaking, resting, descanso_corto, descanso_largo, offline.
Health checks
El endpoint GET /api/service-health devuelve estado del proceso y de cada subsistema:
{
"status": "ok",
"processUptime": 12345,
"memory": { "rss": 52428800, "heapUsed": 25165824 },
"activeConnections": 3,
"totalRequests": 847,
"subsystems": {
"Miniverse Server": { "up": true, "latencyMs": 0 },
"OpenClaw": { "up": true, "latencyMs": 0 },
"Hermes Broker": { "up": true, "latencyMs": 0 }
}
}
Funcionalidades
- Health checks automáticos: cada servicio tiene un endpoint
/healthque el panel consulta periódicamente y muestra UP/DOWN con tiempo de respuesta - Métricas en tiempo real: CPU load, memoria RAM, uptime del sistema, conexiones activas
- Estado de agentes: Codex, Palomino y Lia con heartbeat cada 30 segundos y estado visual
- Rate limiting: sliding window de 100 req/min para IPs externas, ilimitado para localhost y red local (192.168.50.63)
- Logging centralizado: Pino como logger con salida estructurada a stdout
- CORS hardening: origen específico para el frontend (192.168.50.63:3001), wildcard para el resto. Methods: GET, POST, OPTIONS, DELETE. Headers: Content-Type, Authorization
- WebSocket messaging: canales (general, dev), mensajes directos entre agentes, broadcasting de eventos
- Manejo de errores centralizado: try/catch global, respuestas 400/404/429/500 con formato consistente
Testing
12 tests con el framework node:test (sin dependencias externas) que cubren rate limiting, forma de respuestas de todos los endpoints, CORS, y errores.
Métricas
| Métrica | Valor |
|---|---|
| Servicios monitorizados | 3 |
| Agentes con heartbeat | 3 (Codex, Palomino, Lia) |
| Frecuencia de heartbeat | Cada 30 segundos |
| Rate limit externo | 100 req/min por IP |
| Endpoints REST | 18 |
| Tests | 12/12 pasando |
| Frontend | Puerto 3001 |
Qué cambiaría
Coordinación de URLs desde el principio. Los health checks cruzados entre servicios requieren que cada URL esté bien mapeada. Un error de mapeo muestra todos los servicios como caídos aunque respondan correctamente. Perdí tiempo depurando falsos positivos.
Heartbeat robusto desde el día uno. Si un agente deja de reportar, el dashboard debe mostrar el estado como offline sin bloquear el resto del sistema. La primera versión trataba la ausencia de heartbeat como error general.
El rate limiting no es solo para producción. Incluso en red local, un bucle accidental de polling puede saturar el panel. El rate limiting por IP debería haber estado desde la primera versión, no después del primer incidente.
La fusión de los dos frontends (oficina HTML + panel React) debería haber sido planeada desde el inicio. Mantener dos sistemas separados retrasó la unificación y generó duplicidad de código.