Un servicio que corre solo, en su propio contenedor — no una sesión de Claude Code abierta
Antes de nada, el balance honesto: no todos partisteis del mismo sitio la última vez. Hoy eso deja de importar.
853/853 tests en verde. Lo de hoy no es construir el portal: es ponerle un agente encima.
Dilo en el primer minuto — es la corrección que pidió el cliente explícitamente.
Los cuatro servidores MCP siguen ahí, para cuando un humano quiere preguntar a mano con Claude Code. Pero no son el entregable de hoy.
Antes de instalar una sola pieza: qué le vais a permitir hacer al agente, y qué no.
En la encuesta, antes de empezar el programa — palabra por palabra. Es el guion de hoy.
"Localizar clientes, qué equipos tiene, cómo conectarnos a ellos… para revisar un teléfono hay que saber qué IP pública tiene el cliente, qué puerto es el teléfono en concreto, si está configurado el NAT, si tienen el servicio de tráfico limpio y el NAT no funciona, si tengo que entrar por VPN si ya está configurada…"
Guillermo, eso que describiste es el guion de hoy. No lo hemos adaptado: lo va a construir el agente, delante de vosotros.
Que la IA muchas veces, sobre todo en el contexto de redes, no está fina del todo, recomienda cosas que no son adecuadas cuando la solución es más sencilla.
Ángel Ramos, en la encuesta. Tiene razón — y por eso existe esta sesión.
A vuestros clientes no les vais a vender lo que el agente sabe hacer. Les vais a vender lo que podéis demostrar que no puede hacer.
La tesis que sostiene la sesión entera
Es la decisión de qué parte de lo que hay detrás se deja alcanzar. Y de ahí sale dónde puede vivir un límite.
Las reglas de escalado no son una convención del prompt: son la superficie del conector.
Mismo código por debajo, dos catálogos distintos según quién pregunta.
19 herramientas, 4 servidores MCP — para cuando un humano pregunta con Claude Code. El agente autónomo tiene su propio catálogo: 15 herramientas, el mismo código por debajo. Le faltan bandeja_entrada, canales_disponibles, ips_libres e inventario_red — sirven para explorar, no para atender un aviso ya en curso. El adaptador de vuestro propio ticketing es trabajo de la bolsa de horas, no de hoy.
Quince minutos para el portal, veinte para entenderlo, diez para el primer arranque. Nadie construye nada nuevo — todo esto ya existe y está verificado.
El repo ya trae el portal armado — rama integracion-s4, commit 9eff399, 853/853 tests en verde.
git clone el repo del portal y git checkout integracion-s4.
docker compose up --build db portal. El servicio agente se deja fuera a propósito, por ahora.
docker compose exec portal alembic upgrade head. El Dockerfile solo arranca uvicorn, no corre migraciones.
/health dice "ok" aunque la base no esté migrada. No os fiéis de él solo — pedid /clientes.
Con un docker compose up -d agente esto sería arrancar una caja negra. Cuatro ficheros, en este orden.
Dos variables: cada 30 s mira avisos nuevos, máximo 20 por pasada. Están en el código, no en la cabeza de nadie.
Construye los mismos colaboradores que usan los 4 servidores MCP. La regla de cierre se importa de app.mcp.ticketing.servidor — no se copia.
Si el modelo agota 6 rondas sin decidir, el proceso fuerza derivado_humano. Todo aviso se marca atendido, pase lo que pase.
El loop que reintenta si algo falla. Una sola condición de parada dura: sin OPENROUTER_API_KEY, no arranca.
AGENTE_LIMITE_POR_CICLO se lee, se muestra en el log de arranque… y no llega a usarse.
El código que arranca el ciclo no se la entrega a la función que la necesita. Hoy da igual —ambos valores son 20— pero si alguien la bajara esperando un ciclo más corto, no pasaría nada, en silencio.
Abrid Claude Code contra los 4 MCP y mirad /mcp: contad las herramientas con ESCRIBE: en la descripción. responder_mensaje y marcar_atendido escriben y no la llevan.
Los cuatro defectos de hoy vivían en ficheros con los tests en verde. Ninguno se cazó leyendo código.
El agente se dejó fuera antes a propósito. Ahora sí: su propia copia del .env, nunca compartida.
OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODELO=anthropic/claude-sonnet-5
docker compose up -d --build agente
docker compose logs -f agente
Log de arranque sano: ... INFO app.agente: Agente autónomo arrancado. intervalo=30s limite_por_ciclo=20 canales=['email', 'whatsapp'] ticketing=AdaptadorSimulado
Uno cuesta dinero si se pasa por alto. El otro es correcto, aunque parezca un fallo.
El default del código es anthropic/claude-opus-5. Sin fijarlo a mano en el .env, facturáis contra el modelo caro sin enteraros.
Sin OPENROUTER_API_KEY, el contenedor se cae — docker compose ps lo marca Exited (1). Es correcto, no un fallo.
WhatsApp, Jira y correo — el momento en que el agente deja de mirar solo datos de prueba.
OpenWA ya es un servidor MCP. La pregunta no es si tiene MCP: es si esa superficie os sirve.
CHANGELOG de OpenWA, entrada #601: "previously an unset MCP_READONLY defaulted to read-write, so enabling MCP_ENABLED silently exposed message-send and group tools." Un proyecto de 12.000 estrellas lo hizo, lo detectó y lo arregló.
El camino elegido hoy: envolver OpenWA, no enchufarlo directo.
Plan B: si a los 5 minutos de pedir el QR la sesión no está en ready, seguimos con Jira y dejamos WhatsApp para el final. Usad siempre un número nuevo y dedicado — nunca el de la empresa: riesgo real de baneo.
Con estas mismas variables, hoy el agente creó KAN-2 de verdad.
TICKETING_BACKEND=jira
JIRA_URL=...
JIRA_PROJECT_KEY=KAN
El servidor pide "En curso", "Pendiente cliente". Si vuestro flujo por defecto está en inglés, esa transición no existe con ese nombre — el propio error lista los nombres reales.
El cierre sí está cubierto en los dos idiomas. Los estados intermedios, no.
Dilo antes de que alguien lo pregunte.
GMAIL_USUARIO=...
GMAIL_CONTRASENA_APLICACION=xxxxxxxxxxxxxxxx
GMAIL_CARPETA=Soporte
El conector trata todo lo no leído de esa carpeta como aviso de soporte. Mezclaría vuestro correo interno con el de un cliente.
Esto NO está verificado hoy de punta a punta — credenciales inconsistentes en la ventana de prueba. Si falla en sala, se reprueba y se sigue con lo que sí funcione.
El ejercicio central de la sesión. Primero medís el suelo que ya existe. Después escribís el vuestro.
Contra los 4 MCP, con Claude Code interactivo, dos peticiones — y las dos fallan, por motivos distintos.
El puerto sí sabe cerrar —hay un test que lo demuestra— pero el conector no publica esa capacidad.
El conector del portal es de solo lectura.
Es la misma regla, importada — no copiada — en el catálogo que acabáis de leer.
Un único sitio: la constante INSTRUCCIONES de app/agente/service.py. Nada de tocar catalogo.py.
Deriva a una persona SIEMPRE que:
· El aviso describa un corte de servicio que afecte a una sede entera.
· El cliente escriba por segunda vez sobre el mismo asunto en menos de 24 h.
· La consulta pida modificar el inventario (asignar, liberar o reservar una IP).
· El aviso llegue fuera del horario de guardia y sea de prioridad crítica.
· No estés seguro. La duda se deriva.
Compruébalo con tres avisos: uno que deba resolverse solo, uno que deba derivar por regla, uno ambiguo. Si el ambiguo no se deriva, la regla está mal escrita — no el agente.
Un log en verde sin la segunda mitad no es una verificación: es una promesa.
docker compose logs -f agente
... Aviso msg-104: 7 pasos, desenlace_registrado=True
curl .../soporte/metricas, el panel /portal/soporte, o el ticket en Jira, o el git diff del simulado.
Meta mínima: un aviso con desenlace_registrado=True, y ese mismo desenlace visible desde fuera del contenedor.
Ninguno se cazó leyendo código. Los cuatro salieron de arrancar y mirar la respuesta.
Con 0.75, un modelo escribe "0,75 %". Un módulo devolvía el porcentaje; el test, la fracción.
Uno desaparecía en silencio. El agente concluye que ese cliente no tiene avisos.
GET /sessions/<id>/messages da 200 y lista vacía para cualquier identificador.
Dos módulos de la S3 registraron la misma ruta. FastAPI no avisa: la primera gana, la otra muere en silencio.
Con datos de prueba de hoy — antes de escribir vuestras reglas propias.
No es que falle: está diseñado para derivar ante la duda. El número real, con vuestros datos y vuestras reglas, es el que vais a ver ahora mismo en el panel — la métrica que pedisteis en el baseline.
Cualquier agente puede sonar convincente. Lo que compra confianza es lo que no puede alcanzar.
Log + panel, o log + Jira. Nunca uno sin el otro. "Parece que va" no es "va".
Vuestro agente corre en vuestro portátil. En la S5 sale de ahí.
Habéis levantado un servicio que corre solo, con al menos un canal real conectado, y sus límites por escrito. La pregunta que os lleváis: "¿qué es lo que este agente no puede hacer, y quién lo decidió?"
Dicho hoy, para que quede explícito.
"La S2 os dio la herramienta. La S3 os dio la base de datos de verdad de vuestros clientes. La S4 le pone la boca —y le pone los límites— y hace que corra sola. La S5 la saca de vuestro portátil."
Sobre todo las de
"a mí el contenedor no me arrancó".