Dos productos, una pieza que falta
Grokbot ganó la experiencia de un solo jugador y nadie se la ha quitado. Una persona, un bot, un ordenador, y el bucle es tan ajustado que dejas de notar el software. Los clones están de acuerdo. Pasé un día leyendo Rakazo en el commit 616d2353, un clon de Grok Bot con licencia Apache-2.0, y es buen trabajo: una aprobación que persiste la llamada a herramienta pendiente y repite los argumentos exactos aprobados en lugar de pedirle al modelo que los deduzca otra vez. Vale la pena robarlo.
Luego busqué las piezas que me dejarían cambiar su comportamiento sin tocar su código. No estaban. La entrada está soldada a un destino: una URL de webhook es POST /api/v1/bots/:botId/webhook, así que quien llama nombra al bot y no queda nada que enrutar. Sin artefacto de reglas, así que nada que comparar. Sin id de traza persistente: AdapterContext.traceId es un campo de registro que lleva el id de la ejecución, y ninguna ejecución registra un padre, así que una cadena de bot a bot de seis saltos no se puede reconstruir con ninguna consulta. Nada de eso se puede optimizar, y ningún agente puede marcharse.
Buzz es la otra mitad de la molestia, y yo vendo hosting para él, así que puedo ser honestamente franco. Buzz tiene lo que Grokbot no puede expresar: más de un humano en la sala, con los agentes como miembros y no como complementos medidos por uso. Un miembro es un par de claves. Un agente es un par de claves cuyo evento de autenticación lleva la firma de su propietario, así que la insignia de bot y la certificación son hechos del relay, no adorno de la app (el mecanismo está aquí). Todo lo demás está soldado: la identidad se acuña en el escritorio en Keys::generate(), la clave privada viaja al ejecutor dentro de la carga de despliegue, y el cuerpo corre donde se haya iniciado. Cierra el portátil y tu compañero de equipo desaparece.
El bus es el sustrato
Waggle es lo que queda cuando te niegas a soldar nada de eso. Seis piezas, cada una reemplazable. La entrada estampa la confianza y la identidad. Un registro que solo crece guarda cada evento. Un fichero de reglas decide quién despierta. Los ejecutores son destinos intercambiables. Las credenciales viven en una custodia con arrendamientos y un proxy. Un único id de traza une todo el conjunto.
Nada de eso es abstracto. Un evento aterriza en ev.<tenant>.<source>.<type> con dos campos que la mayoría de los sistemas confunden: principal, quién lo puso ahí, y trust, si esa afirmación se llegó a comprobar. Un envío de Stripe con una firma válida es partner:stripe con confianza de socio. El mismo envío con una firma inválida es un 401 y nunca llega a añadirse. El id se deriva de una clave de idempotencia que incluye el inquilino, así que el mismo evento entregado dos veces obtiene el mismo id y el registro descarta la segunda copia.
El enrutador lee un fichero TOML. Ni un prompt, ni una fila de base de datos: un fichero con ids de reglas dentro, recargado en caliente, verificado contra el registro en vivo antes de escribirse, y rechazado entero si dos reglas del mismo inquilino comparten un id. Ese fichero es el artefacto: lo que comparas en un pull request, lo que un agente puede proponer cambiar, y lo que una traza nombra cuando dice por qué algo despertó. Un destino también es una fila, con un repartidor, así que cambiar dónde se ejecuta un agente es editar una sola fila.
Los agentes no hablan, emiten
La regla en la que más tardé en confiar es la que ahora más me gusta: los agentes no mantienen una conversación, emiten eventos, y un evento dirigido a un agente lo despierta. No hay un bus de mensajes para el chat y otro distinto para el trabajo. Hay un solo registro. Una respuesta es un evento con addressed_to establecido, y la traza la registra tanto si un humano está mirando como si no.
A los dos días de ejecutar turnos reales, descubrí que eso era falso. El 8 de septiembre a las 05:07:59Z le pregunté a agent:cos2, un jefe de gabinete sobre el runtime real de Claude, por su agente de marketing. Hizo lo correcto: emitió un ask dirigido a agent:growth, evento 3YRSRD08JX549QHDHCXPZXRK7P, bajo la traza 7MDV4GJYX2TWTMQH5SP23PYSJ8. Nada coincidió con él. Las reglas generadas coincidían con filas de despertar, mensajes de consola y avisos de custodia, así que un evento de socio cuyo sujeto era otro agente no coincidía con ninguna. El ask cayó al destino por defecto, growth nunca despertó, la ejecución de cos2 terminó tres segundos después, y el chat no mostró absolutamente nada.
El arreglo es lo que me convenció de que la forma es correcta, porque no fue una funcionalidad. Fue una regla estándar más generada para cada agente, r-<name>-addressed, que coincide con subject = "agent:<name>" y trust = "partner", colocada después de las propias filas de despertar del agente para que una regla específica siga ganando, con una migración de arranque que la añade una vez a cada agente que ya existía, y una comprobación de entrada que rechaza un emit dirigido a uno mismo. La conversación de agente a agente, en un bus, son cinco líneas de TOML generado. Sin uno, es un elemento de la hoja de ruta con fecha de lanzamiento.
El modelo nunca lee una clave
Las credenciales son donde esto deja de ser una cuestión de gusto arquitectónico. Un token de suscripción es una concesión en una custodia: servicio claude_code, campo oauth_token, host api.anthropic.com. Al despertar, la custodia emite un arrendamiento y al contenedor se le entrega el marcador __claude_code_oauth_token__, exactamente la variable de la que Claude Code lee un token. La única salida del contenedor es un proxy que sustituye el valor real en la cabecera de la petición, solo para ese host. El modelo ve un marcador. También el disco: el diario de una ejecución que pasa dice AGENTMAIL_API_KEY=__agentmail_api_key__, porque eso es lo que se le dio al proceso.
Cada paso es un evento y ninguno lleva un valor: credential.requested, credential.granted, credential.leased al despertar, credential.used una vez por cada petición que vio el proxy, credential.revoked cuando la retiras. El rastro de auditoría no es un registro que alguien se acordó de escribir, es el mismo registro donde está todo lo demás. El crédito a quien corresponde: la forma del runner y el broker es de Jake Gaylor, de Fountain, que tenía sandboxes por compañero de equipo que se aparcan y despiertan, y credenciales de inferencia del inquilino que el agente nunca lee, mucho antes de que yo tuviera nada de esto. Nombremos el antipatrón sin rodeos. Pon la clave en el entorno y cualquier comando de shell que ejecute el modelo puede leerla, y ninguna cantidad de redacción de subcadenas cierra eso, porque base64 existe.
Un humano, y la traza es el canal
Entonces, ¿adónde va la ventana de chat? Con un solo humano, a ninguna parte. Waggle es la interfaz y la traza es el canal: un mensaje que entra, la regla que coincidió, el despertar, las llamadas a herramientas del modelo, sus emisiones, el código de salida, todo bajo un id que puedes imprimir. hachi trace muestra el salto de entrada, la regla que se disparó, el destino que confirmó, y cada hijo enlazado bajo el salto que lo causó. Mostrar eso como una conversación es una vista, no un segundo producto.
Un segundo humano cambia el requisito sin cambiar la arquitectura. Lo que dos personas necesitan es un lugar compartido con lista de miembros, presencia e historial, que es exactamente en lo que un producto de chat es bueno y exactamente en lo que un bus no debería intentar convertirse. Así que una superficie son dos cosas corrientes: una fuente de entrada y un repartidor.
Para Buzz eso es una conexión de relay por cada agente vinculado, cada evento aceptado añadido como un sobre buzz.message con su firma reverificada al entrar, reglas corrientes que lo llevan al agente y la answer del agente de vuelta, y un repartidor que firma la respuesta como el agente. Calculé esa demo en unos cuatro días repartidos en seis elementos, y ninguno es un concepto nuevo: Buzz ya sabe invocar el binario de un proveedor. Slack son las mismas dos cosas con una corbata distinta. Buzz debería ser una superficie. Slack es una superficie.
El agente es tuyo, el espacio de trabajo es suyo
Una vez que ejecución, inferencia, credenciales e identidad son cosas separadas, la propiedad deja de ser una pregunta filosófica y se convierte en cuatro campos. La propiedad recae en el agente. La inferencia, la ejecución y los datos recaen en el vínculo, que es lo que sostiene un espacio de trabajo: qué suscripción paga el turno, qué ejecutor lo corre, y qué puede conservar. El id de un agente es su propia clave pública, acuñada en su creación, con la mitad privada como una concesión en la custodia que solo se revela dentro del daemon y nunca se arrienda a un ejecutor.
La experiencia se divide en la línea de privacidad: las notas de oficio pertenecen al propietario y viajan con él, las notas del inquilino pertenecen al espacio de trabajo y nunca salen de él. Un pasaporte lleva identidad, alma, oficio y referencias a las concesiones del propietario sin ningún valor dentro, y nunca la memoria de una empresa. El llavero que llevaría esas concesiones a otra instancia está redactado en origen como block/buzz PR #6011. Los términos son comprobables, no prometidos: en el mini configuré un vínculo con inference: owner para un propietario sin ninguna concesión de suscripción, y su siguiente despertar produjo un credential.refused que decía, en palabras, que ninguna concesión claude_code del propietario cubría a ese agente en ese inquilino. Los términos no son documentación. Son lo que lee quien emite el arrendamiento.
Los números, pequeños y reales
Nada de esto corre en un clúster. Un Mac mini, docker compose. Todo el guion, desde crear un jefe de gabinete hasta que marketing despierta con un arrendamiento que solo ve como un marcador, tarda 15 segundos desde la primera creación hasta el PASS, y cuatro turnos de contenedor. Cada uno de los ocho secretos del fichero de entorno se buscó con grep tanto en los registros de extremo a extremo como en el registro de compilación, antes de que esos registros salieran de la máquina. Cero coincidencias para cada uno. La clave falsa que pega la ejecución no se encontró en ningún registro, configuración, bandeja de salida o traza.
El plan estimaba de 39 a 54 días laborables para un ingeniero a lo largo de cinco fases, más de 14 a 24 para el hito de propiedad. Un enjambre de agentes construyó las cinco fases y pasaron en el mini el 7 de septiembre; la migración de propiedad se ejecutó una vez al arrancar el 8 de septiembre, convirtió 13 agentes existentes en objetos con dueño y sus propias identidades, y dejó intactas sus 33 reglas generadas. Dos días para las dos cosas. La cobertura del objetivo de verificación es del 88,7 por ciento y la suite en vivo del builder pasa 15 de 15. Las lagunas, porque una nota de campo sin ellas es un anuncio publicitario: una llamada saliente real a través del proxy con el marcador sustituido está cubierta por una prueba contra un upstream local y no por el mini, y el vínculo con Buzz descrito arriba es una lectura de diseño contra el código fuente del relay, no una funcionalidad publicada.
El sustrato es el producto
Aquí está el argumento sin barniz. El sustrato es el producto. Las superficies son mercancía: una app de chat es una lista de miembros, un relay y una caja de texto, y el año que viene habrá una mejor. Los ejecutores son mercancía: un contenedor hoy, cómputo alquilado mañana, una microVM después. Los modelos son mercancía y se están abaratando a propósito. Lo que no es mercancía es el registro de lo que pasó, por qué pasó, y qué se le permitió tocar. Ese registro es el único artefacto contra el que alguien puede optimizar. El foso defensivo es la traza.
Así que toma la prueba en lugar del argumento. Encuentra la última vez que uno de tus agentes hizo algo mal, y pregúntate qué artefacto editarías para que no vuelva a pasar. Si la respuesta es un prompt más largo, tienes un bot. Si la respuesta es un id de regla que puedes comparar y un id de traza que puedes imprimir, tienes un bus. Construye el bus.