10/Documentation
El protocolo durable (experimental)
Qué viaja entre Collie y un durable agent, para desarrolladores: rutas, hello y la época, el ciclo de vida de un ask y los identificadores de solicitud, transportes por socket y token, límites y regla de versiones, con un enlace a WIRE.md para consultar las reglas exactas
Experimental. El cable puede cambiar, o la función puede eliminarse, en una versión menor posterior. Versiones y el saludo descriptivo describe cómo se transmiten los cambios.
El cable duradero es el medio con el que Collie se comunica con un agente duradero. Esta página lo explica para desarrolladores que creen un agente duradero sin el SDK o deseen saber qué hace el SDK. Las reglas exactas se encuentran en WIRE.md, y si esta página difiere de ese archivo, WIRE.md es el correcto. El SDK durable gestiona todo esto por usted.
Lo que se transmite
Collie llama y el agente persistente escucha, y cada intercambio es una solicitud HTTP con un cuerpo JSON.
El agente persistente escucha en un socket Unix privado o tras TLS en una URL (Transportes). Collie sondea: no hay push, ni WebSocket ni stream. Cada ruta empieza por /session/v1, que es un nombre fijo, no la versión. Cada llamada salvo la primera hello incluye la versión en la cabecera X-Collie-Session. El cuerpo de una solicitud es un único objeto JSON de 64 KiB como máximo, y un cuerpo POST vacío se interpreta como {}.
| método y ruta | qué hace | límite de respuesta |
|---|---|---|
GET /hello | Nombre del agente persistente, rango de versiones, epoch, estado de preparación y caps | 16 KiB |
GET /snapshot | La disposición: espacios de trabajo, pestañas, paneles con su estado, turno y asks abiertos, y starters. Incluye un ETag y responde 304 cuando no hay cambios | 4 MiB |
GET /pane/:id/events?after=N&epoch=E | Eventos del panel tras un cursor | 8 MiB |
POST /pane/:id/answer | Respuesta a una ask | 16 KiB |
POST /pane/:id/send | Texto para el panel, para dirigir el turno en ejecución o esperar al siguiente | 16 KiB |
POST /pane/:id/abort | Detener el turno en ejecución | 16 KiB |
POST /pane/:id/action | Pulsación en uno de los botones de la barra del panel | 16 KiB |
POST /rename | Un nuevo título para un espacio de trabajo o una pestaña | 16 KiB |
POST /close | Cerrar un panel, una pestaña o un espacio de trabajo | 16 KiB |
POST /starter/:id | Ejecutar un starter. La respuesta nombra el nuevo panel | 16 KiB |
GET /conversations?after=C&limit=N | Conversaciones del agente persistente, abiertas y cerradas, primero las más recientes | 1 MiB |
GET /conversation/:id/items?after=C&limit=N | Elementos de una conversación, primero los más antiguos | 8 MiB |
POST /reopen | Mostrar de nuevo una conversación cerrada | 16 KiB |
Cada error es un cuerpo JSON con un code, como 409 stale o 404 no-such-pane. Cualquier otra ruta responde 404 not-found. Una respuesta que supere su límite no se lee, y un POST cuya respuesta quede cortada tiene un resultado desconocido (Identificadores de solicitud).
Hello y epoch
hello indica quién es el agente persistente y el epoch indica a qué inicio del mismo se dirige Collie.
campo de hello | contenido |
|---|---|
service | Exactamente collie-durable. Sin él, Collie interpreta el agente persistente como otro servidor y no realiza más llamadas |
protocol | El rango de versiones de protocolo que admite el agente persistente, { min, max }. Actualmente { min: 1, max: 1 } |
host.name | Nombre propio del agente persistente, solo texto visible, 64 bytes como máximo |
epoch | Nuevo y aleatorio en cada inicio del agente persistente, de al menos 128 bits, nunca reutilizado |
ready | False mientras el agente duradero organiza sus paneles tras un inicio |
caps | Lo que el agente duradero puede hacer, cada uno como booleano. Una capacidad ausente es false |
Collie llama primero a hello sin la cabecera de versión, elige la versión más alta que ambos lados admiten y la envía en cada llamada posterior. Mientras ready sea false, cada ruta excepto hello responde 503 starting, y el teléfono muestra el agente duradero como iniciándose. Un agente duradero establece ready solo cuando cada conversación se vuelve a mostrar y cada solicitud pendiente se vuelve a plantear, de modo que un reinicio nunca muestra una disposición a medias.
El ETag de la instantánea es "<epoch>-<rev>". rev aumenta con cada cambio visible para un lector, por lo que un 304 casi no cuesta nada al agente duradero. Una nueva época indica a Collie que el agente duradero se reinició. Collie sigue mostrando los paneles que tiene hasta leer la nueva instantánea. Luego lee cada panel con su cursor anterior y obtiene toda la vista actual del panel en una sola respuesta.
Una lectura fallida tras una correcta mantiene la última instantánea para ese sondeo. Dos fallos seguidos muestran el agente duradero como inaccesible. Una ruta de socket que no pasa la comprobación de privacidad queda inaccesible de inmediato.
Solicitudes y respuestas
Una solicitud es una tarjeta de pregunta, y permanece abierta desde el evento ask del agente duradero hasta su ask-closed.
| estado | quién lo establece | lo que muestra el teléfono |
|---|---|---|
| Abierta | El agente duradero plantea la solicitud. El panel muestra waiting mientras haya alguna solicitud abierta | Una tarjeta en "Needs your answer", con el nombre del agente duradero y el tiempo restante cuando la solicitud tiene expiresAt |
| Modificada | El agente duradero modifica la solicitud in situ, y su revision aumenta | La tarjeta se redibuja. Un toque en el formulario anterior devuelve 409 stale, y la tarjeta indica "This ask changed. Read it again, then tap an answer." |
| Aceptada | El teléfono responde y el agente duradero cierra la solicitud con by: "phone" | La tarjeta desaparece y la herramienta se ejecuta o se deniega según indique la opción |
| Decidida en el agente duradero | El agente duradero cierra la solicitud por sí mismo, decidedBy: "program". Una detención del turno cuenta | Una línea en Chat que comienza con "Decided in the durable agent", con el título de la solicitud |
| Tiempo agotado | El reloj supera expiresAt, decidedBy: "timeout" | Una línea en Chat que comienza con "Timed out before an answer", con el título de la solicitud |
Un toque posterior a la decisión recibe 409 decided con el primer resultado, y nada se ejecuta. La tarjeta indica entonces quién decidió primero: "Decided in the durable agent.", "Timed out before an answer." o, tras otro toque, "Already answered. This card goes away on the next update.". Una solicitud que el agente duradero ya no reconoce responde 200 too-late. La tarjeta muestra entonces "Not taken. The question may show again in a moment.", porque tras un reinicio el agente duradero puede volver a plantearla bajo el mismo id.
| regla | por qué |
|---|---|
| El id de la solicitud es un hash del id del panel y del id de la llamada a la herramienta | La misma llamada a la herramienta planteada tras un reinicio tiene el mismo id, por lo que una respuesta enviada a través del reinicio llega a su destino |
Una tarjeta permission se construye a partir del nombre de la herramienta y sus argumentos, nunca a partir de las palabras del modelo | Un modelo que busque un sí describiría la llamada de forma favorable |
Un agente duradero detrás de TLS no debe ofrecer allow-always | El token es la única barrera, por lo que una sola respuesta de quien lo posea permitiría el paso a todas las llamadas posteriores |
El teléfono no ofrece allow-always en una tarjeta cuyas palabras se hayan cortado o limpiado, o que no mencione ninguna herramienta | La persona no puede ver qué cubre la concesión |
expiresAt es ISO 8601 con zona horaria | Pasada esta, la solicitud se decide mediante timeout, y las instantáneas dejan de listarla como abierta |
| El agente duradero conserva el resultado de al menos sus últimas 500 solicitudes decididas por panel | Una pulsación tardía recibe decided y el primer resultado, no un simple too-late |
Una solicitud es de uno de cuatro tipos: permission, choice, text o form. Cada una tiene su propio formato de respuesta, listado con Preguntas del SDK.
Identificadores de solicitud
Cada acción del teléfono tiene un id de petición, por lo que se ejecuta a lo sumo una vez.
El teléfono genera un id de petición por intención, y Collie escribe su propio recibo antes de pasar el id. El agente duradero conserva al menos los últimos 500 id de su época. La repetición de un id finalizado recibe la primera respuesta de nuevo con replayed: true y no ejecuta nada. El mismo id con otra carga útil recibe 409 request-id-reused.
Collie nunca reintenta un POST por sí mismo. Un POST que agota el tiempo de espera, pierde la conexión o recibe un 5xx tiene un resultado desconocido. Para una respuesta, la tarjeta muestra entonces "Not sure the answer arrived. Try again sends it once more.", y Reintentar envía el mismo id de petición, por lo que la memoria del agente duradero responde a la repetición.
Un reintento que encuentra la solicitud cerrada o el turno cambiado no puede determinar si el primer intento llegó. Collie responde entonces al teléfono "maybe applied", la tarjeta muestra "May already be applied. Check the chat.", y la acción nunca se envía una tercera vez.
Transportes
Un agente duradero utiliza uno de dos transportes: un socket privado en la máquina de Collie, o TLS con un token.
Los bytes son los mismos en ambos: las rutas, los cuerpos, la cabecera de versión, la ETag y los códigos de error. Solo difiere quién puede acceder al agente duradero.
| a través del socket | la regla |
|---|---|
| Ruta | Absoluta, de a lo sumo 103 bytes. Ambos lados la leen desde COLLIE_DURABLE_SOCKET |
| Modos | El socket tiene modo 0600, en una carpeta con modo 0700. El agente duradero elimina los archivos de socket obsoletos antes de escuchar |
| El recorrido de la ruta | Antes de cada conexión, Collie comprueba cada parte de la ruta con lstat. Ninguna parte es un enlace simbólico. La carpeta del socket le pertenece con modo 0700. Ninguna carpeta superior permite escritura a grupos u otros, excepto si es sticky como /tmp. El socket le pertenece, sin permisos para grupos u otros |
| Un recorrido fallido | Collie no se conecta, y muestra el agente duradero como inaccesible con socket-not-private y la parte que falló |
| Token | Ninguna. El socket privado es el control de acceso |
| Quién puede actuar | Cualquier proceso de su usuario. El agente duradero puede rechazar a sus propios descendientes con 403 caller-refused |
| Tiempos de espera | 1000 ms para hello, snapshot y events. 3000 ms para una respuesta, envío, detención, acción, cambio de nombre o cierre. 30000 ms para un iniciador |
| Con qué frecuencia consulta Collie | Cada 1 s mientras un teléfono esté observando; de lo contrario, cada 3 s. A un agente duradero caído se le consulta una vez por sondeo |
| Sistemas | Linux y macOS. Windows no dispone de transporte por socket |
| mediante TLS | la regla |
|---|---|
| URL | https://, o http://127.0.0.1:<port> o http://localhost:<port> para una prueba local. Como máximo 2048 bytes, sin nombre de usuario ni contraseña, sin consulta, sin fragmento. Collie le añade /session/v1 |
| Token | Al menos 32 bytes, ASCII imprimible sin espacios. Collie envía Authorization: Bearer <token> en cada petición, hello incluido |
| Comprobación del agente duradero | El token primero, antes de la versión, hello y ready, comparado en tiempo constante. Un token ausente o incorrecto recibe un 401 { "code": "unauthorized" } simple y nada más |
| Redirecciones | Collie no sigue ninguna. Una respuesta 3xx se propaga como estado, por lo que el token nunca llega a otro host |
| ETag | Un proxy que comprime una respuesta puede debilitar la etiqueta a W/"<epoch>-<rev>". Ambas partes la comparan tras retirar W/ |
| Un rechazo | Un 401 o 403 muestra al agente duradero como inaccesible con token-refused |
| Tiempos de espera | 5000 ms para cada lectura y escritura, porque un agente duradero que estuvo suspendido puede reactivarse en frío. 30000 ms para un starter |
| Con qué frecuencia consulta Collie | Cada 2 s mientras un teléfono observa; si no, cada 10 s. A un agente duradero caído o rechazado se le consulta como máximo una vez cada 10 s |
Cada tiempo de espera es un límite para todo el intercambio, desde la conexión hasta el último byte. Mediante TLS cada sondeo es una petición que el host puede facturar, un 304 incluido.
Límites
Collie corta o rechaza todo lo que supere estos límites, por lo que un agente duradero que se mantenga dentro de ellos no pierde nada.
| qué | límite |
|---|---|
| Espacios de trabajo por instantánea | 64 |
| Pestañas por instantánea | 256 |
| Paneles por instantánea | 256 |
| Peticiones abiertas por panel | 50, las más antiguas primero |
Opciones de un permission | 6 |
Opciones de un choice y de un campo de selección | 8 |
Campos de un form | 6 |
| Starters por instantánea | 50 |
| Botones de la barra por panel | 4, una etiqueta de 16 caracteres |
| Un id | De 1 a 128 bytes, sin caracteres de control, sin espacios en blanco, ninguno de /, ? o # |
| Un cuerpo de solicitud | 64 KiB, de lo contrario 413 too-large |
| Respuestas | hello 16 KiB, snapshot 4 MiB, events 8 MiB, conversations 1 MiB, items 8 MiB, un POST 16 KiB |
| Eventos por respuesta | 1000. Un lector rezagado recibe la vista actual, como máximo los 2000 elementos más recientes |
Texto de un elemento user, reply, thinking o notice | 64 KiB |
Texto de un elemento markdown o code | 32 KiB |
| Texto que envía el teléfono | 32 KiB |
Un elemento link | Solo https://, 2048 caracteres como máximo |
| Una página de historial | 100 conversaciones o 1000 elementos |
| Un cursor de historial | 512 bytes de ASCII imprimible, sin espacios |
Una cadena que supere su límite se recorta y se eliminan sus caracteres de control e invisibles. Una lista que supere su límite conserva sus primeras entradas. Una página de historial que supere su límite no se lee.
Versiones y el saludo descriptivo
Actualmente existe una versión de protocolo (wire), la 1, y Collie usa 1..1.
Cuando se lance la versión 2, Collie aceptará la versión 1 durante una versión menor posterior, y la versión menor siguiente dejará de admitirla. Un lector ignora campos, tipos de evento, clases de elemento o códigos desconocidos. Un campo añadido dentro de la versión 1, como expiresAt o decidedBy, es opcional. Un campo obligatorio nuevo, un significado nuevo para un campo existente o un campo eliminado requiere una versión nueva.
Una solicitud de un tipo desconocido para el teléfono se muestra como una que no puede responderse desde el teléfono, sin botones.
Cuando los dos rangos no coinciden, Collie no lee nada más del agente persistente. Envía un hello más por cada época del agente persistente, esta vez con su propia versión, para que el agente persistente pueda registrar la discrepancia en su lado. El agente persistente responde 409 incompatible con un refusal que indica ambos rangos. Este es el saludo informativo.
Collie escribe una línea en su registro por cada rango rechazado, y collie doctor muestra ambos rangos. La página principal del agente persistente en el teléfono muestra la misma frase, por ejemplo: "The durable agent speaks wire v2, this Collie speaks v1. Update Collie, or the agent's SDK." Actualice la parte más antigua.
Dónde se encuentran las reglas exactas
WIRE.md es el contrato, con cada estructura, código y regla.
Las decisiones subyacentes son registros en el repositorio:
| registro | lo que decide |
|---|---|
| ADR 0099 | Un agente durable es un multiplexor sin pantalla |
| ADR 0100 | Una consulta viaja como datos y se responde por id |
| ADR 0101 | Send y stop nombran el turno visto por el teléfono |
| ADR 0102 | Los iniciadores son filas en la página New |
| ADR 0103 | Los dos transportes |
| ADR 0104 | Un agente durable es una sesión y la salida |
| ADR 0105 | El SDK es un paquete npm |