Dos Agentes, Una Tarea: Cómo Hago que mis IAs Hablen entre Sí en Tiempo Real (sin WebSockets)
Tenía memoria compartida y tareas, pero para una tarea en vivo que necesitaba ida y vuelta entre dos agentes, yo terminaba copiando y pegando mensajes de uno a otro. Así construí Agent Channels: mensajería directa 1:1 entre agentes de IA con handshake, opt-in y el piloto en el loop, en tiempo real sobre Postgres LISTEN/NOTIFY —sin Reverb ni WebSockets— y dos historias reales: dos IAs coordinándose para configurar un KVM entre Fedora y Windows, y un colega resolviendo una tarea en el servidor de un cliente —vía mi agente— sin que yo le diera acceso ni una sola credencial.
Dos Agentes, Una Tarea: Cómo Hago que mis IAs Hablen entre Sí en Tiempo Real (sin WebSockets)
Tenía memoria compartida. Tenía tareas con estado. Mis agentes podían colaborar de forma asíncrona —uno deja una nota, otro la lee mañana—. Pero llegó una tarea que necesitaba algo distinto: dos agentes resolviendo el mismo problema en vivo, con ida y vuelta inmediata. Y descubrí que, para eso, el bus de mensajes era yo.
Literal: un agente en mi laptop Fedora me decía algo, yo lo copiaba y lo pegaba en el chat del agente en mi máquina Windows, esperaba la respuesta, y la copiaba de vuelta. Era el cable humano entre dos inteligencias que, en teoría, eran más rápidas que yo. Ridículo.
Este post es sobre cómo construí Agent Channels: un canal directo para que mis agentes hablen entre sí en tiempo real —con las salvaguardas para que eso no se vuelva un caos— y sobre la primera vez que lo usé de verdad, cuando dos IAs se coordinaron solas para configurar un teclado y mouse compartido entre dos sistemas operativos distintos.
El Problema: Yo Era el Bus de Mensajes
La memoria resuelve el conocimiento durable. Las tareas resuelven el trabajo supervisado. Pero ninguna de las dos resuelve una conversación viva entre dos agentes que necesitan negociar algo paso a paso: "prueba esto" / "falló con este error" / "ah, entonces usa el otro método" / "listo, funcionó".
Sin un canal directo, esa conversación pasa por el humano. Y el humano —yo— es lento, se distrae, y se convierte en el cuello de botella exacto que la automatización debía eliminar. Necesitaba que el agente A le pudiera mandar un mensaje al agente B y recibir respuesta, sin que yo tocara nada.
Qué Quería (y Qué No)
Lo fácil habría sido abrir un canal global donde todos los agentes gritan. Mala idea: sin control, eso es ruido, fugas de contexto entre proyectos, y agentes hablando con agentes que no deberían. Me puse reglas claras:
- 1:1, no salas. Conversaciones directas entre dos agentes (las salas grupales las dejé para después).
- Opt-in de ambos lados. Un agente no recibe mensajes a menos que haya "abierto comunicaciones" explícitamente.
- El piloto en el loop. Cada enlace nuevo entre dos agentes requiere la aprobación del humano dueño. Mis agentes no se conectan con cualquiera a mis espaldas.
- Org-scoped y por handle único. Te diriges a otro agente por su handle dentro de la organización, no por una IP ni un secreto compartido.
- Con cierre explícito y TTL. Los enlaces se cierran a propósito, y si nadie habla, expiran solos.
El Handshake
El flujo es un apretón de manos con consentimiento en cada paso. Un agente anuncia presencia (comms_open), pide enlazar con otro (link_request), y el otro acepta —con su piloto aprobando—. Solo entonces el enlace queda OPEN y fluyen los mensajes (texto + un meta JSON + referencias a tareas o memorias), cada uno con su ack. Al terminar, link_close con una razón, y comms_close para volver a estar indisponible.
# Agente A
comms_open # "estoy disponible"
link_request to=<handle-de-B> # pide enlace (B + su piloto aprueban)
agent_send link=<id> body="prueba el método libei" meta={...}
agent_inbox wait=25 # long-poll por la respuesta
...
link_close link=<id> reason=task_completeEl estado de presencia va entre UNAVAILABLE y AVAILABLE; el enlace entre PENDING y OPEN. Todo org-scoped, todo auditable.
Tiempo Real sin WebSockets
El roadmap original asumía que el tiempo real significaba WebSockets —montar Reverb en Laravel, broadcasting, Redis—. Cuando me senté a diseñarlo, descarté esa ruta entera. Por tres razones:
- El servidor MCP es stateless. Crea un servidor nuevo por cada request HTTP y lo desconecta al terminar; no hay una conexión viva por agente a la que empujarle nada.
- No tenía infra de broadcasting y montarla sería construir un subsistema entero desde cero.
- Claude Code es turn-based. Un push a mitad de turno no sirve: el agente no reacciona hasta su siguiente turno de todos modos.
La solución resultó más simple y con cero infra nueva: Postgres LISTEN/NOTIFY. El inbox de un agente se bloquea escuchando un canal propio —inbox:<agent_id>— vía pgsqlGetNotify. Y el envío emite el aviso dentro de la misma transacción que inserta el mensaje:
-- dentro de la transacción de send()
INSERT INTO agent_messages (...);
SELECT pg_notify('inbox:' || :target_id, :payload);
-- Postgres entrega la notificación al COMMIT: atómico con el insertEl LISTEN se emite antes de re-chequear la bandeja, para no perder mensajes que llegan a mitad. Latencia: sub-milisegundo, contra el ~1 segundo del polling anterior. Cero servicios nuevos, cero Redis, cero Reverb. La base de datos que ya tenía hace de bus de mensajes.
Tres Formas de Entrega
No todos los runtimes reaccionan igual, así que terminé soportando tres modos, no excluyentes:
- Poll (el universal): el agente llama
agent_inbox wait=25en un long-poll; con NOTIFY por debajo, se siente instantáneo. Funciona con cualquier cliente MCP. - Push MCP stateful: un endpoint separado
/mcp/livecon sesión viva donde el inbox es un recurso suscribible (projecthub://inbox); el servidor emitenotifications/resources/updated. Para runtimes que sí reaccionan a mitad de sesión. - Webhook:
comms_openacepta uncallback_url; en cada mensaje se hace un POST firmado con HMAC-SHA256 al runtime. Para agentes con endpoint HTTP propio.
El /mcp stateless original quedó intacto; lo realtime se montó al lado sin romper nada.
La Prueba de Fuego: Dos Agentes Configuraron un KVM
Una feature no existe hasta que resuelve algo real. La primera prueba end-to-end fue esta: dejé que dos de mis agentes se coordinaran, solos, para una tarea de infraestructura de verdad.
Los protagonistas: un Claude Opus corriendo en Claude Code sobre mi laptop Fedora 43, y otro Claude Opus corriendo en Claude Code sobre mi máquina Windows 11. Misma organización. La tarea: montar un KVM por software —un solo teclado y mouse compartido entre ambas máquinas— con Synergy. El de Windows haría de servidor; el de Fedora, de cliente.
El flujo ocurrió entero sobre Agent Channels: ambos abrieron comunicaciones, el de Fedora pidió el enlace, el de Windows aceptó (con mi aprobación), y empezaron a negociar. "El servidor escucha en este puerto, sin TLS" / "intento conectar" / "Fedora 43 es Wayland-only, el método clásico de inyección de mouse no funciona" / "usa libei vía el portal de RemoteDesktop" / "layout: Fedora a la derecha del server". Mensajes de texto con meta JSON, cada uno con su ack, latencia de uno a dos segundos con el long-poll continuo.
Resultado: el cursor cruzaba de una pantalla a la otra y el teclado escribía en ambas máquinas. Sincronizado. Dos IAs en dos sistemas operativos distintos resolvieron una tarea de infra hablando entre ellas, y yo solo aprobé el handshake y miré.
Segundo Caso: Acceso Delegado sin Entregar las Llaves
El otro uso que terminó de convencerme no fue de velocidad, sino de seguridad. Mi laptop —donde vive mi Claude Code— tiene acceso SSH al servidor de un cliente. Un colega necesitaba hacer una tarea en ese servidor, y yo no quería darle acceso. No por desconfianza: por principio. Entre menos gente tenga las llaves de un cliente, mejor.
Antes, eso era un callejón sin salida: o le pasaba credenciales —mala idea— o hacía yo la tarea mientras él me dictaba los pasos por chat, lento y tedioso. Con Agent Channels apareció una tercera opción. Su agente y el mío abrieron un enlace. Su agente —que tenía el conocimiento de qué había que hacer— le iba dictando los pasos al mío; mi agente —que tenía el acceso— los ejecutaba en el servidor del cliente y le devolvía resultados y errores para que ajustara el siguiente paso. Completamos la tarea entre los dos, agente a agente.
El colega nunca tocó el servidor. Nunca vio una credencial. El acceso se quedó donde tenía que estar —en mi máquina—, y aun así su conocimiento se aplicó donde hacía falta. El canal transportó instrucciones, no llaves. Esa separación —el que sabe y el que puede, colaborando sin fusionar sus permisos— es, para mí, uno de los usos más potentes de todo esto: delegar capacidad sin delegar acceso.
Lo que Aprendí
La prueba dejó lecciones que no estaban en el diseño:
- El modelo poll necesita un loop activo. Un enlace anterior se auto-cerró por
idle_timeout(30 min) porque nadie sondeaba. Sondear es seguir vivo: si el agente no está en un bucle escuchando, el enlace lo da por ausente y lo cierra. Lo resolví con un loop continuo y long-poll de 25s. - El piloto en el loop no estorba, tranquiliza. Aprobar cada handshake suena tedioso, pero es lo que hace que delegar comunicación entre agentes no dé miedo. Yo decido quién habla con quién.
- El cierre explícito importa. Sin
link_close+ TTL, los enlaces zombies se acumulan. Cerrar con una razón (task_complete) deja además un rastro legible de por qué terminó la conversación.
El Código
Agent Channels vive en el backend abierto github.com/andyeswong/agentProjectHub (el modelo de presencia, enlaces, mensajes y el NOTIFY), y se expone a los agentes con ~13 tools MCP —comms_open, link_request, agent_send, agent_inbox…— en github.com/andyeswong/projecthub-mcp.
Conclusión
La memoria les dio a mis agentes un pasado común. Las tareas, una forma de rendir cuentas. Agent Channels les dio algo que no tenían: una voz para hablarse entre ellos, ahora, sin que yo sea el cable.
Y la parte que más me gusta es lo poco que costó en infraestructura. No hizo falta un broker de mensajes ni WebSockets ni un servicio nuevo: la base de datos que ya tenía, con LISTEN/NOTIFY, hace de bus en tiempo real. A veces la arquitectura correcta no es la más sofisticada del roadmap, sino la que usa lo que ya está. Dos agentes coordinando un KVM entre Fedora y Windows, en segundos, mientras yo solo miraba, es la prueba de que el cuello de botella nunca fueron los modelos. Era la falta de un canal.
artículos_relacionados
El Mes que un Agente de IA Me Costó 11,500 Pesos
Primer post de la serie cc_bridge: la busqueda del cerebro para mi agente autonomo con OpenClaw. Codex se quedo corto, Gemini Pro 4 me dejo una factura de 11,500 pesos en un mes, y lo barato no ejecutaba. La pregunta que destrabo todo: separar al que piensa del que ejecuta.
Una Sola Puerta para Todos los Agentes: Cómo Expongo ProjectHub vía MCP
Construí memoria, proyectos y comunicación entre agentes detrás de una API REST. Pero uso seis herramientas de IA distintas, y escribir y mantener una integración para cada una era volver al problema de la fragmentación, un nivel más arriba. Así expuse todo ProjectHub a través de un solo protocolo —MCP— para que cualquier agente, sin glue a la medida, tenga memoria, tareas y canales como herramientas nativas.
El Humano Supervisa, el Agente Opera: Tareas, Estados y Comentarios para Coordinar IAs
Cuando la memoria compartida volvió autónomos a mis agentes, apareció un problema nuevo: dejé de saber qué estaban haciendo y por qué. Mis correcciones vivían en chats que se perdían. Así construí la capa de proyectos de ProjectHub —tareas con estado, comentarios con intención (instrucción, corrección, pregunta, aprobación) y un registro de eventos— para que el humano supervise y el agente opere, sin perder el hilo.