# RVBox v1 agent protocol The authoritative schemas are [`../protos/rvbox/v1/common.proto`](../protos/rvbox/v1/common.proto) and [`../protos/rvbox/v1/agent.proto`](../protos/rvbox/v1/agent.proto). This document specifies their use over WSS. ## Transport and compatibility The nginx-terminated `wss://` connection carries exactly one serialized `rvbox.v1.AgentEnvelope` in each binary WebSocket message. There is no extra length prefix. A decoded envelope may not exceed 1 MiB. A chunk's uncompressed payload may not exceed 64 KiB. Both peers validate declared and actual expanded sizes before allocation/decompression. The protobuf package is `rvbox.v1`. Registration negotiates a major/minor protocol range: incompatible majors are rejected; the highest shared minor is chosen. New fields are append-only. A peer ignores unknown optional fields but must respond with `PROTOCOL_ERROR` to an unknown required envelope feature. ## Heartbeat and reconnect Both endpoints use the same algorithm. Any received valid WebSocket frame is inbound activity. After 10 seconds without inbound activity, send a WebSocket Ping. After 30 seconds without inbound activity, close the session and treat it as dead. Pong processing is normal WebSocket behavior; it is not an application message and never queues behind command traffic. The client reconnects with full-jitter exponential backoff (initial 1 second, cap 60 seconds). A session stable for 60 seconds resets the backoff. The server does not reconnect; it waits for clients. A reconnect always registers again, receives a new fencing generation, resends unacknowledged delivery/output, and reconciles only commands the server still considers non-terminal. ## Session fencing `ClientHello` starts registration. `ServerWelcome` gives the selected version, random `session_id`, and `session_generation`. Except `ClientHello`, all envelopes carry those values. A new accepted registration fences and disconnects the prior one for the same client ID. The server accepts messages only from the current generation; command dispatches also identify their intended generation. ## Reliable work flows ### Dispatch and reconciliation The server persistently creates a command before dispatching `CommandDispatch`. It redelivers until it receives `CommandAccepted`. Clients durably deduplicate on `issue_uuid`. A client whose queue is full sends a capacity rejection. Following registration the server sends `ReconcileRequest` only for its non-terminal commands for that client. The client replies with a `ReconcileSnapshot` per requested known command, or `unknown_to_client`. It continues normal event retransmission from the server's last acknowledged event sequence. Terminal history already confirmed by the server is deliberately excluded. ### Output and events Client execution events use an increasing `event_seq`; retries reuse the same sequence and content. The server durably writes an event before sending `EventAck`. `EventAck` is cumulative through a sequence number. Output is Zstandard compressed with an explicit original-size field. Server storage can reuse the validated compressed bytes. When rolling output removes old retained segments, the server writes `OutputTruncation` metadata containing the removed event/byte ranges. Queries must show that marker rather than silently presenting an apparently complete stream. An offline client that reaches a cap sends `ClientOutputTruncated` before replaying its retained tail on reconnect. The server records it, permits the named event-sequence gap, and exposes it in output queries. Truncation metadata is not an execution event and does not consume an `event_seq`. ### Stdin and signals `StdinWrite` is binary-safe and ordered by `write_seq`; the client durably deduplicates it and returns `StdinAck`. `append_newline` is true by default in the CLI but explicit on the wire. `CloseStdin` is a separate idempotent action. Signal and cancellation requests carry a command revision to settle start/kill races. ### Scripts For a script command, dispatch first contains a `ScriptDescriptor`; then the server sends `ScriptChunk` messages and a commit. The client checks offset, chunk order, full length, and SHA-256 before it reports upload complete or launches the process. A retransmitted chunk is idempotent by offset/content. ## Flow control and failure containment No receive loop runs an executor, database write, decompressor, or slow socket operation inline. Each side has bounded staging queues. Durable spools are the source of truth and are charged to the 10 MiB/50 MiB/1 GiB compressed retention budgets described in the architecture document. A full staging queue pauses the related read/dispatch path and drains from disk; it never grows without bound. Malformed protobuf, over-size payload, invalid compressed data, impossible sequence, bad session token, or protocol-version violation yields a structured error where safe and closes that WebSocket session. It does not crash either daemon. Network loss is normal and is handled through idempotent resend.