Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Transport de socket RPC

Le daemon expose une interface JSON-RPC 2.0 via un flux IPC local, un socket de domaine Unix sous Unix et un tube nommé (named pipe) sous Windows. Il s’agit du transport principal pour les clients locaux comme zerocode. La passerelle HTTP/WS demeure pour les webhooks, le tableau de bord web et les consommateurs REST distants.

Résolution de point de terminaison

Chaque répertoire de données dispose de son propre point de terminaison, de sorte que plusieurs instances de démon sur la même machine n’entrent pas en collision. Le répertoire de données est dérivé du répertoire de configuration (--config-dir / ZEROCLAW_CONFIG_DIR, ou ZEROCLAW_DATA_DIR).

OSPoint de terminaison par défaut
Linux<data_dir>/daemon.sock (socket de domaine Unix)
macOS<data_dir>/daemon.sock (socket de domaine Unix)
Windows\\.\pipe\zeroclaw-<hash> où <hash> est dérivé de data_dir

Remplacez par la variable d’environnement ZEROCLAW_SOCKET sur l’une ou l’autre plateforme :

sh

export ZEROCLAW_SOCKET=/tmp/my-zeroclaw.sock
zeroclaw daemon

PowerShell

$env:ZEROCLAW_SOCKET = '\\.\pipe\my-zeroclaw'
zeroclaw daemon

Protocole de communication

NDJSON (JSON délimité par des sauts de ligne). Chaque ligne est un message JSON-RPC 2.0 complet. Aucun cadrage HTTP, aucun préfixe de longueur. Le cadrage est identique sur toutes les plateformes ; les canaux nommés transportent le même flux d’octets que les sockets Unix.

{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1},"id":1}\n
{"jsonrpc":"2.0","result":{"protocolVersion":1,"serverVersion":"0.8.6"},"id":1}\n

Limites de connexion

Le point de terminaison local limite ce qu’un client peut retenir :

LimiteValeurCe que voit le client
Taille de trame8 Mio par ligneUne erreur -32600 avec id: null et data: {"reason": "frame_too_large", "limit_bytes": 8388608}, puis fin du flux. Le reste de la ligne trop volumineuse n’est jamais lu, donc la connexion ne peut pas continuer.
Initialiser30 s après la connexionUne connexion qui n’a pas terminé initialize 30 s après son établissement est fermée sans réponse, si bien qu’un client qui se connecte sans rien envoyer ne peut pas monopoliser un emplacement de connexion. Le délai couvre aussi l’authentification : un initialize envoyé à temps dont l’authentification est encore en cours lorsque le délai expire, par exemple lors d’une vérification OIDC lente, est fermé plutôt qu’accepté en retard.
Cadre inachevé30 s après le premier octet de la trameUne fois qu’une trame commence, la ligne entière doit arriver dans les 30 s suivant son premier octet, quelle que soit la régularité d’arrivée du reste. Passé ce délai, le client reçoit une erreur -32600 avec id: null et data: {"reason": "frame_timeout", "limit_ms": 30000}, puis la fin du flux. L’attente entre les trames n’est pas bornée, si bien qu’un client initialisé peut rester inactif.
Blocage à l’écriture30 s par imageUn client qui cesse de lire pendant 30 s alors que le daemon a des sorties en file d’attente pour lui est déconnecté. Les autres connexions ne sont pas affectées. La déconnexion met fin aux tours démarrés par cette connexion, comme toute autre déconnexion. Cela inclut un client suspendu : un zerocode arrêté avec Ctrl-Z, ou figé avec Ctrl-S, pendant qu’un tour est en cours de diffusion voit ce tour annulé après 30 s, alors qu’auparavant il se terminait une fois le client repris.
Connexions ouvertesrpc.max_local_connections, par défaut 512Une connexion au-delà de la limite reçoit une erreur -32004 unique avec id: null et data: {"reason": "connection_limit", "limit": N}, puis la fin du flux. Le daemon journalise un seul avertissement lorsqu’il commence à refuser les connexions. La valeur est lue au démarrage de l’écouteur.

Les charges utiles plus grandes qu’une trame sont transmises sous forme de téléversement fragmenté plutôt qu’en une seule ligne. Voir Téléversements fragmentés.

Établissement de connexion

Le premier appel RPC doit être initialize. Le démon rejette toutes les autres méthodes jusqu’à ce que initialize réussisse. Une incompatibilité de version du protocole produit une erreur structurée avec le code -32011.

{
  "jsonrpc": "2.0",
  « méthode »: "initialiser",
  "paramètres": {
    "protocolVersion": 1
  },
  "id": 1
}

Le point de terminaison ne nécessite pas de jeton d’appairage. Le contrôle d’accès est géré par le système d’exploitation :

  • Unix : le socket est 0o600, le répertoire parent est 0o700.
  • Windows : l’ACL du canal nommé est par défaut définie sur l’utilisateur créateur et SYSTEM.

Méthodes

MéthodeDirectionDescription
initializeclient -> daemonAuthentifiez-vous et négociez la version du protocole
session/newclient -> daemonCrée une session d’agent (nécessite agentAlias, facultatif cwd, sessionId ; un ID déjà actif rattache l’appelant à cette session canonique en mémoire au lieu de remplacer son historique d’agent, et est refusé avec FORBIDDEN lorsque l’environnement transmis que l’appelant peut utiliser avec ses autorisations actuelles diffère de celui avec lequel la session a été créée ; l’option keep_siblings supprime l’éviction des sessions sœurs inactives du même mode pour les clients multi-sessions qui gèrent eux-mêmes le cycle de vie de leurs sessions sœurs)
session/closeclient -> daemonSupprimer le propriétaire de la session active ; l’historique durable ACP reste reprenable
session/killclient -> daemonSupprimer la session active et placer en pierre tombale son historique durable ACP
session/deleteclient -> daemonSupprimer la session en cours et son historique durable sélectionné
session/promptclient -> daemonExécuter un tour (diffusé via les notifications session/update)
session/cancelclient -> daemonAnnuler un tour en cours
session/stateclient -> daemonLire l’état du cycle de vie de la session en cours, l’identité du tour actif et le plan courant facultatif ; le travail actif ou en file d’attente est représenté par state: "running" afin que les clients de récupération puissent confirmer l’état terminal avant de libérer le travail conservé
statusclient -> daemonVersion du serveur, version du protocole, liste des sessions actives
file/upload/begin, file/upload/chunk, file/upload/commitclient -> daemonTéléverser un fichier en plusieurs morceaux ordonnés ; voir Téléversements par morceaux
session/updatedémon -> clientNotification en streaming pendant un tour (fragments de texte, appels d’outils, approbations)
elicitation/createdémon -> clientDemander une saisie interactive pour les flux ask-user et poll

Écritures de configuration atomiques

config/set-many accepte un tableau ordonné sets contenant de 1 à 256 objets, chacun avec prop et value. Il utilise la même syntaxe de propriété et les mêmes règles de valeur que config/set. Par exemple, après avoir créé un profil de permissions nommé operator, un administrateur peut créer un utilisateur complet en une seule requête :

{"jsonrpc":"2.0",« méthode »:config/set-many,"paramètres":{sets:[{prop:users.example.uid,"valeur":1001},{prop:users.example.permission_profiles,"valeur":[opérateur]}]},"id":2}

Le succès renvoie {"props":["users.example.uid","users.example.permission_profiles"],"set":true}. Tous les champs sont préparés dans un candidat unique et validés avant un unique commit persistant. Les entrées ultérieures pour une même propriété l’emportent. Un échec de préparation ou de commit laisse la configuration persistée et la configuration active inchangées ; une erreur d’entrée invalide indique son index à partir de zéro.

L’appelant a besoin de Config:Update ainsi que d’une autorisation pour chaque chemin demandé. Le daemon résout l’autorité actuelle en détenant le verrou d’écriture de la configuration, avant la mise en attente de la moindre entrée. Si un chemin est interdit, tout le lot est refusé avec FORBIDDEN, avec l’index de l’entrée, même si les chemins précédents étaient autorisés. La révocation survenue pendant l’attente de ce verrou entraîne aussi le refus du lot. Les vues fournisseur/modèle sont préparées à partir de la configuration candidate complète et installées après la validation réussie. Les suppressions et les opérations sur les clés de map ne font pas partie de cette méthode.

Requêtes bidirectionnelles

Chaque partie peut envoyer une requête sur le socket établi. Le récepteur doit répondre avec le même id et exactement l’un de result ou error. Chaque pair utilise sa propre séquence zc-out-<number> pour les requêtes sortantes ; le préfixe ne constitue pas un espace de noms globalement unique. La corrélation reste directionnelle : chaque pair ne met en correspondance les réponses qu’avec sa propre table des requêtes en attente ; le même ID textuel peut donc être en vol indépendamment dans des directions opposées.

{"jsonrpc":"2.0",« méthode »:"elicitation/create","paramètres":{"message":"Continuer ?"},"id":zc-out-0}
{"jsonrpc":"2.0","résultat":{action:"accepter","contenu":{"réponse":"oui"}},"id":zc-out-0}

Une réponse explicite "result": null est une réussite et résout tout de même l’appelant en attente. Un objet error le résout comme un échec. Si le pair ne répond pas, l’opération ask-user ou poll à l’origine conserve son comportement actuel en matière de délai d’attente.

Chaque trame doit être un objet JSON avec "jsonrpc": "2.0". Les requêtes doivent contenir une chaîne de caractères method ; si params est présent, il doit être un objet ou un tableau. Les réponses doivent contenir un id de type chaîne, numérique ou null, ainsi qu’exactement un membre de réponse. Un JSON non valide produit -32700 (erreur d’analyse). Une enveloppe mal formée de type requête produit -32600 (requête non valide), en utilisant son identifiant de requête valide lorsqu’il est possible de le récupérer. Une enveloppe mal formée de type réponse est consignée sans le contenu de la trame et ignorée sans réponse, car renvoyer son identifiant pourrait faire aboutir une requête sans rapport dans la direction opposée. Les enveloppes dont la direction est ambiguë utilisent id: null. Les identifiants de réponse valides mais inconnus sont également consignés et ignorés plutôt que de recevoir une réponse, ce qui empêche les boucles de réponses.

Activer le streaming

session/prompt retourne le résultat final lorsque le tour se termine. Pendant l’exécution, le démon envoie des notifications session/update avec des événements incrémentaux :

{"jsonrpc":"2.0",« méthode »:"session/mise à jour","paramètres":{"sessionId":"...","type":agent_message_chunk,« texte »:Bonjour}}
{"jsonrpc":"2.0",« méthode »:"session/mise à jour","paramètres":{"sessionId":"...","type":"tool_call",toolCallId:tc_1,« nom »:bash,rawInput:{...}}}
{"jsonrpc":"2.0",« méthode »:"session/mise à jour","paramètres":{"sessionId":"...","type":"résultat_de_l'outil",toolCallId:tc_1,« nom »:bash,rawOutput:"..."}}

Types d’événements : agent_message_chunk, agent_thought_chunk, tool_call, tool_result, approval_request.

Cycle de vie durable d’ACP

Le RPC natif sépare la transcription visible d’origine du contexte fournisseur conservé. La réduction automatique ne supprime ni ne renumérote les lignes de la transcription d’origine. Une notification de réduction n’est envoyée qu’après que son instantané du contexte conservé et la limite du point de contrôle couverte ont été validés ensemble ; une écriture échouée supprime cette notification. L’instantané exclut les invites système d’exécution, l’injection de mémoire rappelée et le raisonnement masqué. En cas d’interruption, la reprise ajoute une seule fois la progression visible du point de contrôle tout en restaurant le modèle à partir du dernier instantané conservé et des événements de point de contrôle ultérieurs. Un instantané conservé explicitement vide reste faisant autorité. Les sessions sans instantané conservent le chemin de rejeu hérité compatible avec le fournisseur.

Les sessions ACP peuvent conserver un historique durable avant d’atteindre un état terminal. Lorsque le propriétaire en mémoire est libéré, la récupération des prompts RPC ne prend en compte que les lignes durables dont le interaction_surface persisté est pris en charge ; une surface non prise en charge est laissée intacte afin qu’une récupération directe ultérieure puisse inspecter le point de contrôle d’origine.

Le mécanisme de reprise ajoute un marqueur d’interruption visible par le client pour un tour inachevé. La relecture vers le fournisseur exclut ce marqueur synthétique, et la sortie d’outil persistée reste soumise aux limites existantes de la transcription.

Les opérations du cycle de vie ont des significations durables distinctes :

  • session/close peut conserver un historique durable permettant la reprise.
  • session/kill conserve l’historique mais marque la ligne comme tombstone, si bien qu’elle n’est pas éligible à la réhydratation à l’exécution.
  • session/delete supprime la ligne ACP et son point de contrôle récupérable avant d’annuler l’enregistrement et de retirer la session active lorsque la cible est une session ACP active, ou lorsqu’aucun mode actif n’existe et qu’une ligne durable ACP est sélectionnée. Une session Chat active portant le même ID reste isolée du stockage ACP.

L’annulation par signal pour kill et delete intervient avant la tentative de leur opération SQLite faillible. Si cette opération échoue pour une cible inactive, le RPC signale une erreur interne et préserve le propriétaire actif et l’enregistrement du canal. Si l’annulation forcée a déjà retiré un propriétaire actif, le RPC ne peut pas restaurer cette génération active ; la ligne durable et le point de reprise récupérable restent disponibles une fois le stockage de nouveau opérationnel.

Téléversements fragmentés

file/attach transporte un fichier entier dans une seule trame, ce qui le plafonne bien en deçà de la limite par fichier une fois le base64 compté.

  1. file/upload/begin avec session_id, size_bytes et, facultativement, filename et sha256 (hexadécimal). Le résultat fournit un upload_id, chunk_bytes (1 MiB) et max_bytes (la limite de 10 Mo par fichier).
  2. file/upload/chunk avec upload_id, offset et data_b64, dans cet ordre. offset doit être égal au nombre d’octets déjà reçus ; le résultat indique received_bytes. Renvoyer un fragment déjà accepté avec les mêmes octets est acquitté sans modification, ce qui permet à un client de réessayer après la perte d’une réponse.
  3. file/upload/commit avec upload_id une fois que tous les octets déclarés sont arrivés. Un sha256 déclaré doit correspondre. Le résultat est une entrée de fichier file/attach.

Un envoi appartient à la connexion qui l’a démarré : aucune autre connexion ne peut le désigner, et il est supprimé lorsque cette connexion se ferme. Les limites :

  • Une connexion peut préparer quatre téléversements à la fois.
  • Le démon met en attente au plus 256 MiB pour l’ensemble des connexions. La charge comptabilisée couvre la charge utile de chaque envoi ainsi que les métadonnées qu’il conserve (identifiant de session, propriétaire, agent, nom de fichier, empreinte déclarée), de sorte qu’une charge utile vide ne puisse pas occuper de la mémoire gratuitement. Un filename dépassant 255 octets est refusé.
  • Un envoi inactif pendant cinq minutes est abandonné. Lorsqu’un nouvel envoi ne tient pas dans le budget, le démon récupère d’abord chaque envoi inactif depuis plus de ce délai, même si sa connexion est toujours ouverte, afin qu’un client silencieux ne puisse pas monopoliser le budget.

Les trois méthodes ne sont servies que sur les connexions locales ; un pair WSS reçoit -32012. Elles nécessitent la permission files:create, et begin vérifie que le principal est propriétaire de la session (pour un principal sans admin) et qu’il peut utiliser l’agent de la session.

Un envoi est lié à la session exacte pour laquelle il a été démarré : la session active portant cet identifiant, son propriétaire et son agent. commit le stocke en une seule étape sous le verrou de session. Il vérifie d’abord que la session est toujours la même, puis contrôle les identifiants de l’appelant, files:create, la propriété de la session et le droit d’accès de l’agent selon la politique en vigueur à cet instant, et seulement ensuite écrit et indexe le fichier. Une autorisation retirée pendant l’envoi, ou une session fermée ou recréée sous le même identifiant, fait échouer le commit avant toute écriture. La vérification et l’écriture maintiennent figées la politique d’autorisation acceptée et l’ensemble des jetons appairés, de sorte qu’une modification de politique ou un désappairage survenant après la fin de la vérification ne prend effet qu’une fois le fichier stocké : l’envoi est ordonné avant eux, jamais entre la vérification et l’écriture. file/attach passe par la même étape de stockage et impose la même exigence de propriété.

Une nouvelle transmission d’un fichier déjà indexé dans la session est comparée au fichier sur le disque. Si le fichier a été modifié, supprimé ou remplacé par un lien, les octets transmis sont réécrits au chemin indexé avant d’être renvoyés.

Mode éphémère

zeroclaw daemon --ephemeral suit les clients connectés et s’arrête automatiquement lorsque le dernier se déconnecte (après un délai de grâce de 1 seconde). Une reconnexion pendant le délai de grâce annule l’arrêt. Le daemon ne se terminera pas tant qu’au moins un client ne s’est pas connecté.

Les démons démarrés sans --ephemeral ignorent le nombre de clients et s’exécutent jusqu’à leur arrêt explicite.

Sécurité

  • Répertoire du socket Unix : 0o700 (propriétaire uniquement)
  • Fichier de socket Unix : 0o600 (propriétaire uniquement)
  • Canal nommé Windows : l’ACL par défaut accorde l’accès à l’utilisateur créateur et à SYSTEM
  • SO_PEERCRED sous Linux fournit le PID et l’UID du processus de connexion pour la journalisation d’audit ; Windows enregistre pipe:local comme étiquette du pair

Test rapide

Démarrez le daemon dans un terminal :

sh

zeroclaw daemon

Dans un second terminal sous Unix, connectez-vous avec socat :

sh

socat READLINE UNIX-CONNECT:~/.zeroclaw/data/daemon.sock

Collez les lignes une par une :

{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1},"id":1}
{"jsonrpc":"2.0","method":"status","params":{},"id":2}

Sous Windows, utilisez n’importe quel client de canal nommé (PowerShell [System.IO.Pipes.NamedPipeClientStream], nc via WSL, ou exécutez simplement zerocode).

Composants internes

La couche de répartition se trouve dans crates/zeroclaw-runtime/src/rpc/ :

FichierRôle
transport.rsTrait RpcTransport
turn.rsexecute_turn() exécuteur de tour partagé
session.rsRpcSession, SessionStore
dispatch.rsRoutage de méthode RpcDispatcher
local.rsLocalTransport + écouteur (socket Unix / canal nommé Windows)
wss.rsWSS (WebSocket Secure) transport + accepteur TLS
attachments.rsTraitement des téléchargements de fichiers, déduplication, génération de marqueurs
upload.rsMise en file d’attente du téléversement en plusieurs parties : ordre, limites par connexion et limites globales au processus

Le trait RpcTransport est conçu de sorte que des transports supplémentaires (vsock, IPC personnalisé) s’intègrent sans toucher à la logique de dispatch ou de session. Le module local.rs encapsule les primitives Unix et Windows derrière une seule structure LocalTransport à l’aide de tokio::io::split, de sorte que la boucle de lecture/écriture soit partagée entre les deux plateformes.