ACP, parte 2 — el día que el relé pasó mensajes que no conocía

En el post anterior contamos cómo implementamos A2A de punta a punta contra el
a2a.proto normativo. La parte más cara de ese trabajo no fue el transporte ni la
autenticación: fue el traductor. Nuestro agente habla su propio dialecto de
eventos, y para que del otro lado llegara A2A legítimo hubo que escribir un mapeo
campo por campo — alrededor de 250 líneas y 28 tests que existen para garantizar que
la traducción no pierda nada.
Esta semana implementamos ACP. El traductor no existe.
Primero, la confusión de siglas
Antes de nada hay que desactivar una mina que está por todo internet. Hubo otro ACP: Agent Communication Protocol, de IBM y Cisco, que sí competía de frente con A2A. Se descontinuó en agosto de 2025 y su equipo se sumó a A2A. Buena parte del contenido que hoy encuentras buscando "ACP vs A2A" habla de ése, no del vivo.
El ACP del que hablamos aquí es el Agent Client Protocol, nacido en Zed. Y no compite con nada, porque los tres protocolos que importan hoy resuelven ejes distintos:
Un agente serio los usa los tres, en capas. Preguntarse cuál gana es como preguntarse si gana HTTP o SQL.
Los números, medidos hoy
Antes de escribir código fuimos a la API de GitHub y a npm. Agosto de 2026:
Los números de A2A cuentan algo incómodo: más estrellas que MCP y 28 veces menos uso. Interés que todavía no se convierte en implementación.
Los de ACP miden mal, y hay que decirlo: ACP se implementa en Rust, compilado dentro del binario del editor o del agente. Nadie lo instala como dependencia de npm, así que el contador de descargas ve una fracción del mundo real.
La adopción hay que buscarla en otro lado. El repo ya no vive en
zed-industries: migró a una organización neutral, agentclientprotocol. Tiene
100+ contribuidores y 243 commits en los últimos 30 días. Entre quienes más commits
mandan hay gente de JetBrains y de Microsoft — un dev advocate de VS Code —
aunque la línea oficial de Microsoft siga siendo únicamente MCP.
Del lado de los clientes: Zed nativo, JetBrains AI Assistant, cuatro plugins de
neovim, emacs, extensiones de VS Code de terceros, Qt Creator, Obsidian, Jupyter. Y
del lado de los agentes hay un registro oficial con 38 que hablan ACP nativo:
Goose, opencode, Claude Code vía wrapper, Codex, Gemini CLI con --acp, Qwen, Cline,
Kimi, Copilot CLI.
Para contrastar: el audit más reciente de A2A rastreó 20,185 hosts en internet y encontró alrededor de 10 agent cards válidos.
Lo que construimos
Un template de microVM Firecracker con Goose dentro —el agente de Block, ahora en la Agentic AI Foundation, Apache-2.0— y un relé de Node que hace WebSocket ↔ stdio.
Ese relé existe por una sola razón. ACP asume que el cliente lanza al agente como proceso hijo y le habla por stdin/stdout. Cruzando la red no hay relación padre-hijo; eso es lo único que falta, no el stdio. Así que el relé es el padre dentro de la caja, y retransmite:
Funcionó a la primera. initialize → session/new → session/prompt → PONG,
atravesando Caddy, el router y el proxy público, con DeepSeek detrás — Goose trae
DeepSeek de primera clase, su definición declara deepseek-v4-flash y
deepseek-v4-pro. Cero cambios en Go: el WebSocket ya pasaba.
La diferencia que lo decidió todo
Aquí es donde los dos protocolos se separan de verdad.
Para A2A hubo que traducir, porque nuestro agente no habla A2A. Para ACP no hay nada que traducir, porque Goose ya lo habla nativo. El relé no interpreta: mueve líneas de JSON-RPC de un lado a otro.
Y eso se notó en la primera prueba real. Por el cable llegaron
agent_thought_chunk —el razonamiento del modelo, en vivo—, usage_update,
available_commands_update, session_info_update. El relé los pasó sin conocer
ninguno.
Con un traductor de por medio, cada uno de esos eventos habría que mapearlo a mano, y lo que no está mapeado no llega: se pierde en silencio, sin error, sin log. Una tubería no tiene esa clase de fugas.

El agente en su celda, con sus propias herramientas a la mano.
Tres decisiones del relé que no eran obvias
/busy se mide por sockets, no por turnos. Nuestro supervisor consulta ese
endpoint antes de congelar una microVM. Una sesión ACP abierta es trabajo aunque esté
callada: el agente está pensando, o el humano está leyendo. Con el criterio de turnos
la VM se congelaría con la sesión viva.
Auth por ticket firmado, no por Bearer. Un new WebSocket() de navegador no puede
poner el header Authorization — la API no lo permite, punto. El ticket viaja en el
query string con HMAC y ventana de tiempo.
Heartbeat propio. No es precaución teórica: el sidecar de colaboración (Hocuspocus) de esa misma flota ya descubrió que sin ping/pong algo en el camino corta las conexiones ociosas. En ACP la conexión ociosa es el caso normal, no el raro.
El detalle que valida toda la arquitectura de cajas
En ACP hay tres familias de métodos que van al revés de lo que uno esperaría:
fs/read_text_file, fs/write_text_file y terminal/* son cosas que el agente le
pide al cliente. Tiene sentido en el caso de origen: el editor es quien tiene los
archivos, incluidos los buffers sin guardar.
Son opcionales y se negocian en initialize. Si el cliente no las declara, el agente
usa su propio filesystem y su propia terminal — que es exactamente nuestro caso, con
una microVM aislada.
Y hay un RFD para ACP v2 que las elimina, con el argumento literal de que "many Agents are moving toward their own sandboxing and execution configuration instead". El protocolo camina hacia donde ya estábamos parados.
Lo que hoy es stdio, mañana puede no serlo
Lo único estandarizado hoy es stdio, pero la spec dice explícitamente que es
transport-agnostic y permite transportes propios. Existe un Transports Working Group
con un RFD en borrador de Streamable HTTP + WebSocket: endpoint único /acp, con
el upgrade a WebSocket como perfil de primera clase.
El día que aterrice, nuestro relé sobra. Y eso está perfectamente bien: escribimos 400 líneas para tapar un hueco temporal del protocolo, no para casarnos con ellas.
La crítica que sí hay que hacerle
Circula mucho la analogía "ACP es el LSP de los agentes". La propia comunidad la ha puesto en duda en Hacker News, con razón: un language server reporta sobre el código; un agente lo modifica. Eso arrastra diffs, aprobaciones y flujos con estado que LSP nunca tuvo que modelar. La analogía ayuda a entender la forma —un protocolo entre editores y motores— y estorba en cuanto entras al detalle.
El veredicto
No son alternativas. A2A y ACP resuelven ejes distintos y ninguno sustituye al otro. Pero si la pregunta es "¿con cuántas contrapartes puedo hablar mañana?", ACP tiene 38 agentes y varios editores reales, y A2A tiene diez agent cards en todo internet.
En corto:
- Tu producto es un agente que debe vivir dentro del editor de alguien → ACP.
- Tu producto conecta agentes entre organizaciones → A2A.
- Sólo necesitas darle herramientas a tu agente → ninguno de los dos, MCP y ya.
Lo que sigue
Nos quedamos pensando en algo. Si el "cliente" de ACP puede ser cualquier cosa que hable el protocolo, no tiene por qué ser un editor. Puede ser un chat de equipo.
En ese mundo, session/request_permission deja de ser un modal y se convierte en un
mensaje con botones. Y un hilo resulta ser mejor superficie de aprobación que un modal
en un IDE por tres razones: es asíncrono, es de todo el equipo, y el hilo mismo queda
como bitácora de auditoría.
Eso es lo próximo que vamos a construir.*