100 lines
5.0 KiB
Markdown
100 lines
5.0 KiB
Markdown
# 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.
|