Expand description
STOMP 1.2 client implementation.
§The handshake is a gate, not a formality
connect() does not return until the broker has answered CONNECT with a well-formed
CONNECTED carrying a version header naming 1.2. Anything else — an ERROR, a
CONNECTED with no version, a version we did not offer, some other command, silence until
the timeout, or a closed socket — is an Err, so client_startup records
ClientStatus::Error and no stomp_connected event is ever raised. This is exactly the
strictness async-stomp applies to our server (its Connector::connect() refuses to
hand back a transport otherwise), and it is the difference between a client that reports a
session and a client that has one.
§No per-connection state machine, deliberately
src/server/tcp/mod.rs carries Idle/Processing/Accumulating because raw TCP has no frame
boundaries, so a second read arriving mid-LLM-call has to be queued somewhere. STOMP has
frame boundaries: this loop reads, parses out whole frames, and handles them one at a time
in the same task. Bytes arriving during a model call sit in the socket buffer — TCP’s own
backpressure — and are parsed on the next pass. There is no window in which two model calls
could overlap on this connection, so there is nothing to track.
§Why there is no boxed follow-up recursion here
The root CLAUDE.md requires a depth-bounded, boxed recursive call for clients whose
action → event → action cycle is self-referential in-process. This client’s cycle is not:
every action it can take puts a frame on the wire, and the answer comes back as another
frame that the same read loop parses and reports. The chain continues by itself, through
the socket, exactly as datalink’s does through its pcap queue — so the model can
subscribe, be told about the MESSAGE that arrives, and publish in response, without any
function here calling itself. The only bound needed is the one every client already has,
client/llm_budget.rs.
§Graceful shutdown
The disconnect action does not hang up. It writes DISCONNECT with a fixed receipt
(actions::DISCONNECT_RECEIPT_ID) and this loop closes when the matching RECEIPT
arrives — the specification’s shutdown, and the only thing that tells a publisher the
broker durably took what it sent. A broker that never answers is bounded by
[DISCONNECT_GRACE] rather than leaving the loop parked forever.
Re-exports§
pub use actions::StompClientProtocol;
Modules§
- actions
- STOMP 1.2 client actions.
Structs§
- Stomp
Client - STOMP 1.2 client.