Transporte de Socket RPC
El daemon expone una interfaz JSON-RPC 2.0 sobre un flujo IPC local, un socket de dominio Unix en Unix y un named pipe en Windows. Este es el transporte principal para clientes locales como zerocode. El gateway HTTP/WS se mantiene para webhooks, el panel de control web y los consumidores REST remotos.
Resolución de endpoint
Cada directorio de datos obtiene su propio endpoint, por lo que múltiples instancias del daemon en la misma máquina no colisionan. El directorio de datos se deriva del directorio de configuración (--config-dir / ZEROCLAW_CONFIG_DIR, o ZEROCLAW_DATA_DIR).
| SO | Endpoint predeterminado |
|---|---|
| Linux | <data_dir>/daemon.sock (socket de dominio Unix) |
| macOS | <data_dir>/daemon.sock (socket de dominio Unix) |
| Windows | \\.\pipe\zeroclaw-<hash> donde <hash> se deriva de data_dir |
Anula esto con la variable de entorno ZEROCLAW_SOCKET en cualquiera de las plataformas:
sh
export ZEROCLAW_SOCKET=/tmp/my-zeroclaw.sock
zeroclaw daemon
PowerShell
$env:ZEROCLAW_SOCKET = '\\.\pipe\my-zeroclaw'
zeroclaw daemon
Protocolo de cable
NDJSON (JSON delimitado por saltos de línea). Cada línea es un mensaje JSON-RPC 2.0 completo. Sin entramado HTTP, sin prefijo de longitud. El entramado es idéntico en todas las plataformas; las canalizaciones con nombre transportan el mismo flujo de bytes que los sockets de Unix.
{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1},"id":1}\n
{"jsonrpc":"2.0","result":{"protocolVersion":1,"serverVersion":"0.8.5"},"id":1}\n
Saludo inicial
La primera llamada RPC debe ser initialize. El daemon rechaza todos los demás métodos hasta que initialize se complete correctamente. Una discrepancia en la versión del protocolo produce un error estructurado con el código -32011.
{
"jsonrpc": "2.0",
"método": "inicializar",
"parámetros": {
"protocolVersion": 1
},
"id": 1
}
El endpoint no requiere un token de emparejamiento. El control de acceso lo gestiona el sistema operativo:
- Unix: el socket es
0o600, el directorio padre es0o700. - Windows: la ACL de la canalización con nombre se establece de forma predeterminada para el usuario creador y
SYSTEM.
Métodos
| Método | Dirección | Descripción |
|---|---|---|
initialize | cliente -> daemon | Autenticar y negociar la versión del protocolo |
session/new | cliente -> daemon | Crea una sesión de agente (requiere agentAlias; cwd y sessionId son opcionales; keep_siblings opcional evita la expulsión de sesiones hermanas inactivas del mismo modo para clientes con varias sesiones que gestionan por sí mismos el ciclo de vida de las sesiones hermanas) |
session/close | cliente -> daemon | Cerrar y limpiar una sesión |
sesión/indicación | cliente -> daemon | Ejecutar un turno (transmitido mediante notificaciones session/update) |
session/cancel | cliente -> daemon | Cancelar un turno en curso |
status | cliente -> daemon | Versión del servidor, versión del protocolo, lista de sesiones activas |
session/update | daemon -> client | Notificación de streaming durante un turno (fragmentos de texto, llamadas a herramientas, aprobaciones) |
elicitation/create | daemon -> client | Solicitar entrada interactiva para los flujos de ask-user y poll |
Solicitudes bidireccionales
Cualquiera de las partes puede enviar una solicitud a través del socket establecido. El receptor debe responder con el mismo id y exactamente uno de result o error. Cada par utiliza su propia secuencia zc-out-<number> para las solicitudes salientes; el prefijo no es un espacio de nombres globalmente único. La correlación sigue siendo direccional: cada par compara las respuestas únicamente con su propio mapa de solicitudes pendientes, por lo que el mismo ID textual puede estar en curso de forma independiente en direcciones opuestas.
{"jsonrpc":"2.0","método":"elicitation/create","parámetros":{"mensaje":"¿Continuar?"},"id":zc-out-0}
{"jsonrpc":"2.0","resultado":{acción:aceptar,"contenido":{respuesta:sí}},"id":zc-out-0}
Un "result": null explícito es una respuesta correcta y aun así resuelve al llamador pendiente. Un objeto error lo resuelve como un fallo. Si el par no responde, la operación ask-user o poll que la inició mantiene su comportamiento de tiempo de espera existente.
Cada trama debe ser un objeto JSON con "jsonrpc": "2.0". Las solicitudes requieren un method de tipo cadena; cuando está presente, params debe ser un objeto o una matriz. Las respuestas requieren un id de tipo cadena, numérico o nulo, y exactamente un miembro de respuesta. El JSON no válido produce -32700 (error de análisis). Una envolvente con forma de solicitud malformada produce -32600 (solicitud no válida), utilizando su ID de solicitud válido cuando se pueda recuperar. Una envolvente con forma de respuesta malformada se registra sin el contenido de la trama y se descarta sin responder, porque hacer eco de su ID podría completar una solicitud no relacionada en la dirección opuesta. Las envolventes cuya dirección es ambigua usan id: null. Los ID de respuesta válidos desconocidos también se registran y se ignoran en lugar de responder, lo que evita bucles de respuesta.
Activar streaming
session/prompt devuelve el resultado final cuando el turno se completa. Durante la ejecución, el daemon envía notificaciones session/update con eventos incrementales:
{"jsonrpc":"2.0","método":"session/update","parámetros":{"sessionId":"...","tipo":agent_message_chunk,"texto":"Hola"}}
{"jsonrpc":"2.0","método":"session/update","parámetros":{"sessionId":"...","tipo":"llamada_herramienta",toolCallId:tc_1,"nombre":bash,rawInput:{...}}}
{"jsonrpc":"2.0","método":"session/update","parámetros":{"sessionId":"...","tipo":"resultado_herramienta",toolCallId:tc_1,"nombre":bash,rawOutput:"..."}}
Tipos de evento: agent_message_chunk, agent_thought_chunk, tool_call, tool_result, approval_request.
Modo efímero
zeroclaw daemon --ephemeral rastrea los clientes conectados y se termina automáticamente cuando el último se desconecta (tras un período de gracia de 1 segundo). Una reconexión durante el período de gracia cancela el apagado. El daemon no saldrá hasta que al menos un cliente se haya conectado.
Los daemons iniciados sin --ephemeral ignoran el recuento de clientes y se ejecutan hasta que se detienen explícitamente.
Seguridad
- Directorio de socket Unix:
0o700(solo el propietario) - Archivo de socket Unix:
0o600(solo propietario) - Canalización con nombre de Windows: la ACL predeterminada concede acceso al usuario creador y a
SYSTEM SO_PEERCREDen Linux proporciona el PID y el UID del proceso que se conecta para el registro de auditoría; Windows registrapipe:localcomo etiqueta del par
Prueba rápida
Inicie el daemon en una terminal:
sh
zeroclaw daemon
En una segunda terminal en Unix, conéctate con socat:
sh
socat READLINE UNIX-CONNECT:~/.zeroclaw/data/daemon.sock
Pegue las líneas una a la vez:
{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1},"id":1}
{"jsonrpc":"2.0","method":"status","params":{},"id":2}
En Windows, usa cualquier cliente de named-pipe (PowerShell [System.IO.Pipes.NamedPipeClientStream], nc mediante WSL, o simplemente ejecuta zerocode).
Internos
La capa de despacho se encuentra en crates/zeroclaw-runtime/src/rpc/:
| Archivo | Rol |
|---|---|
transport.rs | rasgo RpcTransport |
turn.rs | execute_turn() ejecutor de turnos compartido |
session.rs | RpcSession, SessionStore |
dispatch.rs | Enrutamiento de métodos de RpcDispatcher |
local.rs | LocalTransport + listener (socket Unix / canalización con nombre de Windows) |
wss.rs | WSS (WebSocket Secure) transporte + aceptador TLS |
attachments.rs | Procesamiento de carga de archivos, deduplicación, generación de marcadores |
El trait RpcTransport está diseñado para que transportes adicionales (vsock, IPC personalizado) se integren sin tocar la lógica de despacho ni de sesión. El módulo local.rs envuelve las primitivas de Unix y Windows tras una única struct LocalTransport usando tokio::io::split, de modo que el bucle de lectura/escritura se comparte entre ambas plataformas.